diff --git a/client/src/app/admin/admin-routing.module.ts b/client/src/app/admin/admin-routing.module.ts index 2affdb39ad376a74b73c61d9246e0c2913090f3d..802f4b44f8dd6ca40b0a3ab8396c16ce57763ada 100644 --- a/client/src/app/admin/admin-routing.module.ts +++ b/client/src/app/admin/admin-routing.module.ts @@ -9,6 +9,7 @@ import { ConfigureInstanceComponent } from './containers/instance/configure-inst import { GroupListComponent } from './containers/group/group-list.component'; import { NewGroupComponent } from './containers/group/new-group.component'; import { EditGroupComponent } from './containers/group/edit-group.component'; +import { NewDatasetComponent } from './containers/dataset/new-dataset.component'; import { SurveyListComponent } from './containers/survey/survey-list.component'; import { NewSurveyComponent } from './containers/survey/new-survey.component'; import { EditSurveyComponent } from './containers/survey/edit-survey.component'; @@ -28,6 +29,7 @@ const routes: Routes = [ { path: 'configure-instance/:iname/group', component: GroupListComponent }, { path: 'configure-instance/:iname/new-group', component: NewGroupComponent }, { path: 'configure-instance/:iname/edit-group/:id', component: EditGroupComponent }, + { path: 'configure-instance/:iname/new-dataset', component: NewDatasetComponent }, { path: 'survey-list', component: SurveyListComponent }, { path: 'new-survey', component: NewSurveyComponent }, { path: 'edit-survey/:name', component: EditSurveyComponent }, @@ -55,6 +57,7 @@ export const routedComponents = [ GroupListComponent, NewGroupComponent, EditGroupComponent, + NewDatasetComponent, SurveyListComponent, NewSurveyComponent, EditSurveyComponent, diff --git a/client/src/app/admin/components/dataset-family/dataset-family-card.component.html b/client/src/app/admin/components/dataset-family/dataset-family-card.component.html index caf425ce30c19ebbe16692916fd2963663dcda8d..9bd7e3cc3ce2dbcca712ae3148e25e9d4684f7fd 100644 --- a/client/src/app/admin/components/dataset-family/dataset-family-card.component.html +++ b/client/src/app/admin/components/dataset-family/dataset-family-card.component.html @@ -6,17 +6,13 @@ (Display : {{ datasetFamily.display }}) </div> <div class="col-md-2 text-right"> - <app-edit-btn [type]="'dataset-family'" [label]="'Edit dataset family'" #editBtn> - <app-dataset-family-form [datasetFamily]="datasetFamily" (onSubmit)="editDatasetFamily.emit($event)" #formEditDatasetFamily> - <button [disabled]="!formEditDatasetFamily.form.valid || formEditDatasetFamily.form.pristine" (click)="editBtn.modalRef.hide()" type="submit" class="btn btn-primary"> - <span class="fa fa-database"></span> Edit dataset family - </button> - - <button (click)="editBtn.modalRef.hide()" type="button" class="btn btn-danger">Cancel</button> - </app-dataset-family-form> - </app-edit-btn> + <app-edit-dataset-family + [datasetFamily]="datasetFamily" + (onSubmit)="editDatasetFamily.emit($event)"> + </app-edit-dataset-family> <app-delete-btn + [disabled]="nbDatasetsByDatasetFamily() > 0" [type]="'dataset-family'" [label]="datasetFamily.label" (confirm)="deleteDatasetFamily.emit(datasetFamily)"> @@ -25,6 +21,20 @@ </div> </div> <div class="card-body"> - <ng-content></ng-content> + <div class="row row-cols-1 row-cols-sm-2 row-cols-md-3"> + <div *ngFor="let dataset of (datasetList | datasetListByFamily:datasetFamily.id)" class="col mb-3"> + <app-dataset-card + [dataset]="dataset" + (deleteDataset)="deleteDataset.emit($event)"> + </app-dataset-card> + </div> + <div class="col h-100 d-table"> + <div routerLink="new-dataset" [queryParams]="{id_dataset_family: datasetFamily.id}" class="card card-add d-table-cell align-middle pointer" title="Add new dataset"> + <div class="card-body text-center"> + <span class="fas fa-plus fa-4x text-light"></span> + </div> + </div> + </div> + </div> </div> </div> diff --git a/client/src/app/admin/components/dataset-family/dataset-family-card.component.ts b/client/src/app/admin/components/dataset-family/dataset-family-card.component.ts index d8e363ab457bb5af37a97b3c5e14cb82f4f1d810..0ac9565b0dc2633ff4ac5ad394466ca81e66af1b 100644 --- a/client/src/app/admin/components/dataset-family/dataset-family-card.component.ts +++ b/client/src/app/admin/components/dataset-family/dataset-family-card.component.ts @@ -1,6 +1,6 @@ import { Component, Input, Output, ChangeDetectionStrategy, EventEmitter } from '@angular/core'; -import { DatasetFamily } from 'src/app/metamodel/store/models'; +import { DatasetFamily, Dataset } from 'src/app/metamodel/store/models'; @Component({ selector: 'app-dataset-family-card', @@ -9,6 +9,12 @@ import { DatasetFamily } from 'src/app/metamodel/store/models'; }) export class DatasetFamilyCardComponent { @Input() datasetFamily: DatasetFamily; + @Input() datasetList: Dataset[]; @Output() editDatasetFamily: EventEmitter<DatasetFamily> = new EventEmitter(); @Output() deleteDatasetFamily: EventEmitter<DatasetFamily> = new EventEmitter(); + @Output() deleteDataset: EventEmitter<Dataset> = new EventEmitter(); + + nbDatasetsByDatasetFamily(): number { + return this.datasetList.filter(dataset => dataset.id_dataset_family === this.datasetFamily.id).length; + } } diff --git a/client/src/app/admin/components/dataset-family/edit-dataset-family.component.html b/client/src/app/admin/components/dataset-family/edit-dataset-family.component.html new file mode 100644 index 0000000000000000000000000000000000000000..369ae7d923d8a5288f9030387fb1d875b1614537 --- /dev/null +++ b/client/src/app/admin/components/dataset-family/edit-dataset-family.component.html @@ -0,0 +1,18 @@ +<button title="Edit this dataset family" (click)="openModal(template)" class="btn btn-outline-primary"> + <span class="fas fa-edit"></span> +</button> + +<ng-template #template> + <div class="modal-header"> + <h4 class="modal-title pull-left"><strong>Edit dataset family</strong></h4> + </div> + <div class="modal-body"> + <app-dataset-family-form [datasetFamily]="datasetFamily" (onSubmit)="onSubmit.emit($event)" #formEditDatasetFamily> + <button [disabled]="!formEditDatasetFamily.form.valid || formEditDatasetFamily.form.pristine" (click)="modalRef.hide()" type="submit" class="btn btn-primary"> + <span class="fa fa-database"></span> Edit dataset family + </button> + + <button (click)="modalRef.hide()" type="button" class="btn btn-danger">Cancel</button> + </app-dataset-family-form> + </div> +</ng-template> diff --git a/client/src/app/admin/components/dataset-family/edit-dataset-family.component.ts b/client/src/app/admin/components/dataset-family/edit-dataset-family.component.ts new file mode 100644 index 0000000000000000000000000000000000000000..e6a61e6b27a82d841624a980c13ec804a589cf05 --- /dev/null +++ b/client/src/app/admin/components/dataset-family/edit-dataset-family.component.ts @@ -0,0 +1,24 @@ +import { Component, Input, ChangeDetectionStrategy, TemplateRef, Output, EventEmitter } from '@angular/core'; + +import { BsModalService } from 'ngx-bootstrap/modal'; +import { BsModalRef } from 'ngx-bootstrap/modal/bs-modal-ref.service'; + +import { DatasetFamily } from 'src/app/metamodel/store/models'; + +@Component({ + selector: 'app-edit-dataset-family', + templateUrl: 'edit-dataset-family.component.html', + changeDetection: ChangeDetectionStrategy.OnPush +}) +export class EditDatasetFamilyComponent { + @Input() datasetFamily: DatasetFamily; + @Output() onSubmit: EventEmitter<DatasetFamily> = new EventEmitter(); + + modalRef: BsModalRef; + + constructor(private modalService: BsModalService) { } + + openModal(template: TemplateRef<any>) { + this.modalRef = this.modalService.show(template); + } +} diff --git a/client/src/app/admin/components/dataset-family/index.ts b/client/src/app/admin/components/dataset-family/index.ts index cfc9efdc13fc8ae19ba2dfe8ddb2a9bcb0e8558f..34177f45639e0468b770262246d8850b85d3e82c 100644 --- a/client/src/app/admin/components/dataset-family/index.ts +++ b/client/src/app/admin/components/dataset-family/index.ts @@ -1,7 +1,9 @@ import { DatasetFamilyCardComponent } from "./dataset-family-card.component"; +import { EditDatasetFamilyComponent } from "./edit-dataset-family.component"; import { DatasetFamilyFormComponent } from "./dataset-family-form.component"; export const datasetFamilyComponents = [ DatasetFamilyCardComponent, + EditDatasetFamilyComponent, DatasetFamilyFormComponent ]; diff --git a/client/src/app/admin/components/dataset/dataset-form-serverlink.component.html b/client/src/app/admin/components/dataset/dataset-form-serverlink.component.html new file mode 100644 index 0000000000000000000000000000000000000000..04a8c680fe6153ff454d7829e27bb0869a8b5dd0 --- /dev/null +++ b/client/src/app/admin/components/dataset/dataset-form-serverlink.component.html @@ -0,0 +1,12 @@ +<form [formGroup]="form" novalidate> + <accordion-group heading="Server link configuration"> + <div class="custom-control custom-switch"> + <input class="custom-control-input" type="checkbox" id="enabled" name="enabled" formControlName="enabled" (change)="checkDisablOpened()"> + <label class="custom-control-label" for="enabled">Enabled</label> + </div> + <div class="custom-control custom-switch"> + <input class="custom-control-input" type="checkbox" id="opened" name="opened" formControlName="opened"> + <label class="custom-control-label" for="opened">Opened</label> + </div> + </accordion-group> +</form> diff --git a/client/src/app/admin/components/dataset/dataset-form-serverlink.component.ts b/client/src/app/admin/components/dataset/dataset-form-serverlink.component.ts new file mode 100644 index 0000000000000000000000000000000000000000..883525d5d907fcd90866dde6558b34f7803f2f5f --- /dev/null +++ b/client/src/app/admin/components/dataset/dataset-form-serverlink.component.ts @@ -0,0 +1,19 @@ +import { Component, Input } from '@angular/core'; +import { FormGroup } from '@angular/forms'; + +@Component({ + selector: 'app-dataset-form-serverlink', + templateUrl: 'dataset-form-serverlink.component.html' +}) +export class DatasetFormServerlinkComponent { + @Input() form: FormGroup; + + checkDisablOpened() { + if (this.form.controls.enabled.value) { + this.form.controls.opened.enable(); + } else { + this.form.controls.opened.setValue(false); + this.form.controls.opened.disable(); + } + } +} diff --git a/client/src/app/admin/components/dataset/dataset-form-summary.component.html b/client/src/app/admin/components/dataset/dataset-form-summary.component.html new file mode 100644 index 0000000000000000000000000000000000000000..897809505510234e2c4172611e3be7149439a12b --- /dev/null +++ b/client/src/app/admin/components/dataset/dataset-form-summary.component.html @@ -0,0 +1,12 @@ +<form [formGroup]="form" novalidate> + <accordion-group heading="Summary configuration"> + <div class="custom-control custom-switch"> + <input class="custom-control-input" type="checkbox" id="enabled" name="enabled" formControlName="enabled" (change)="checkDisablOpened()"> + <label class="custom-control-label" for="enabled">Enabled</label> + </div> + <div class="custom-control custom-switch"> + <input class="custom-control-input" type="checkbox" id="opened" name="opened" formControlName="opened"> + <label class="custom-control-label" for="opened">Opened</label> + </div> + </accordion-group> +</form> diff --git a/client/src/app/admin/components/dataset/dataset-form-summary.component.ts b/client/src/app/admin/components/dataset/dataset-form-summary.component.ts new file mode 100644 index 0000000000000000000000000000000000000000..48a7582a881b5d515ec795697103e98758691fd2 --- /dev/null +++ b/client/src/app/admin/components/dataset/dataset-form-summary.component.ts @@ -0,0 +1,19 @@ +import { Component, Input } from '@angular/core'; +import { FormGroup } from '@angular/forms'; + +@Component({ + selector: 'app-dataset-form-summary', + templateUrl: 'dataset-form-summary.component.html' +}) +export class DatasetFormSummaryComponent { + @Input() form: FormGroup; + + checkDisablOpened() { + if (this.form.controls.enabled.value) { + this.form.controls.opened.enable(); + } else { + this.form.controls.opened.setValue(false); + this.form.controls.opened.disable(); + } + } +} diff --git a/client/src/app/admin/components/dataset/dataset-form.component.html b/client/src/app/admin/components/dataset/dataset-form.component.html index 84a60c2165921786c9f01c27ee9757665069722b..855c1f9a386e38c79a35dd27bebaaaedbf6e9211 100644 --- a/client/src/app/admin/components/dataset/dataset-form.component.html +++ b/client/src/app/admin/components/dataset/dataset-form.component.html @@ -1,402 +1,63 @@ <form [formGroup]="form" (ngSubmit)="submit()" novalidate> <accordion [isAnimated]="true"> - <accordion-group heading="General information" [isOpen]="'true'"> + <accordion-group heading="General information" [isOpen]="true"> <div class="form-group"> <label for="name">Name</label> - <input id="name" type="text" class="form-control" id="name" name="name" formControlName="name"> + <input type="text" class="form-control" id="name" name="name" formControlName="name"> </div> <div class="form-group"> <label for="label">Label</label> - <input id="label" type="text" class="form-control" id="label" name="label" formControlName="label"> + <input type="text" class="form-control" id="label" name="label" formControlName="label"> </div> <div class="form-group"> <label for="survey_name">Survey</label> - <select id="survey_name" class="form-control" id="survey_name" name="survey_name" formControlName="survey_name"> - <option>{{ model.survey_name }}</option> - <option *ngFor="let survey of surveyList" [value]="survey.name" [selected]="survey.name === model.survey_name">{{ survey.label }}</option> + <select class="form-control" id="survey_name" name="survey_name" formControlName="survey_name" (change)="onChangeSurvey()"> + <option></option> + <option *ngFor="let survey of surveyList" [ngValue]="survey.name">{{ survey.label }}</option> </select> </div> <div class="form-group"> <label for="table_ref">Table Ref</label> - <select id="table_ref" class="form-control" id="table_ref" name="table_ref" formControlName="table_ref"> - <option>{{ model.table_ref }}</option> - <option *ngFor="let table of tableList" [value]="table" [selected]="table === model.table_ref">{{ table }}</option> + <div *ngIf="tableListIsLoading" class="row ml-2 mt-1"> + <span class="fas fa-circle-notch fa-spin fa-1x"></span> + </div> + <select *ngIf="!tableListIsLoading" class="form-control" id="table_ref" name="table_ref" formControlName="table_ref"> + <option></option> + <option *ngFor="let table of tableList" [ngValue]="table">{{ table }}</option> </select> </div> <div class="form-group"> <label for="id_dataset_family">Family</label> - <input id="id_dataset_family" *ngIf="idDatasetFamilyQueryParam > 0" type="text" class="form-control" name="datasetFamilySelected" value="{{ getDatasetFamilyByIdQueryParam().label }}" disabled required> - <select *ngIf="idDatasetFamilyQueryParam === 0" class="form-control" name="id_dataset_family" [ngModel]="model.id_dataset_family" required> + <select class="form-control" id="id_dataset_family" name="id_dataset_family" formControlName="id_dataset_family"> <option></option> - <option *ngFor="let family of datasetFamilyList" [value]="family.id" [selected]="family.id === model.id_dataset_family">{{ family.label }}</option> + <option *ngFor="let family of datasetFamilyList" [ngValue]="family.id">{{ family.label }}</option> </select> </div> <div class="form-group"> <label for="description">Description</label> - <textarea id="description" class="form-control" rows="5" name="description" [ngModel]="model.description" required></textarea> + <textarea class="form-control" rows="5" id="description" name="description" formControlName="description"></textarea> </div> <div class="form-group"> <label for="data_path">Data path</label> - <div class="input-group mb-3"> - <div class="input-group-prepend"> - <button (click)="dataPathOpenModal(templateDataPath); $event.stopPropagation()" class="btn btn-outline-secondary" type="button"> - <i class="fas fa-folder-open"></i> - </button> - </div> - <input id="data_path" type="text" class="form-control" name="data_path" [ngModel]="model.data_path"> - </div> + <input type="text" class="form-control" id="data_path" name="data_path" formControlName="data_path"> </div> <div class="form-group"> <label for="display">Display</label> - <input id="display" type="number" class="form-control" name="display" [ngModel]="model.display" required> - </div> - <div class="custom-control custom-switch"> - <input class="custom-control-input" id="public" type="checkbox" name="public" [ngModel]="model.public"> - <label class="custom-control-label" for="public">Public</label> - </div> - </accordion-group> - <accordion-group [isDisabled]="!model.data_path" heading="Images configuration"> - <button (click)="newImageOpenModal(templateAddImage); $event.stopPropagation()" class="btn btn-outline-primary" type="button"> - <i class="far fa-image"></i> Add new image - </button> - <div *ngIf="datasetImages.length > 0" class="mt-2 table-responsive"> - <table class="table table-striped"> - <thead> - <tr> - <th scope="col">Path</th> - <th scope="col">Size</th> - <th scope="col">RA min/max</th> - <th scope="col">DEC min/max</th> - <th scope="col">Stretch</th> - <th scope="col">Pmin</th> - <th scope="col">Pmax</th> - <th scope="col">Edit</th> - <th scope="col">Delete</th> - </tr> - </thead> - <tbody> - <tr *ngFor="let image of datasetImages"> - <td class="align-middle">{{ image.name }}</td> - <td class="align-middle">{{ image.size | formatFileSize: false }}</td> - <td class="align-middle">[{{ image.ra_min }} ; {{ image.ra_max }}]</td> - <td class="align-middle">[{{ image.dec_min }} ; {{ image.dec_max }}]</td> - <td class="align-middle">{{ image.stretch }}</td> - <td class="align-middle">{{ image.pmin }}</td> - <td class="align-middle">{{ image.pmax }}</td> - <td class="align-middle"> - <button (click)="editImageOpenModal(templateEditImage, image)" title="Edit this image" class="btn btn-outline-primary" type="button"> - <span class="fas fa-edit"></span> - </button> - </td> - <td class="align-middle"> - <button title="Delete this image" (click)="deleteImage(image)" class="btn btn-outline-danger"> - <span class="fas fa-trash-alt"></span> - </button> - </td> - </tr> - </tbody> - </table> - </div> - </accordion-group> - <accordion-group heading="Cone-search configuration" [isDisabled]="isConeSearchDisabled()"> - <div class="custom-control custom-switch"> - <input class="custom-control-input" type="checkbox" id="cone_search_enabled" name="cone_search_enabled" [ngModel]="model.config.cone_search.enabled"> - <label class="custom-control-label" for="cone_search_enabled">Enabled</label> - </div> - <div class="custom-control custom-switch"> - <input class="custom-control-input" type="checkbox" id="cone_search_opened" name="cone_search_opened" [ngModel]="model.config.cone_search.opened" [disabled]="!ngForm.form.value.cone_search_enabled"> - <label class="custom-control-label" for="cone_search_opened">Opened</label> - </div> - <div class="form-group"> - <div class="form-row"> - <div class="col-md-6"> - <label for="column_ra">Column RA</label> - <select id="column_ra" class="form-control" name="column_ra" [ngModel]="model.config.cone_search.column_ra" [disabled]="!ngForm.form.value.cone_search_enabled"> - <option *ngFor="let attribute of attributeList" [value]="attribute.id">{{ attribute.form_label }}</option> - </select> - </div> - <div class="col-md-6"> - <label for="column_dec">Column DEC</label> - <select id="column_dec" class="form-control" name="column_dec" [ngModel]="model.config.cone_search.column_dec" [disabled]="!ngForm.form.value.cone_search_enabled"> - <option *ngFor="let attribute of attributeList" [value]="attribute.id">{{ attribute.form_label }}</option> - </select> - </div> - </div> - </div> - <div class="custom-control custom-switch"> - <input class="custom-control-input" type="checkbox" id="plot_enabled" name="plot_enabled" [ngModel]="model.config.cone_search.plot_enabled" [disabled]="!ngForm.form.value.cone_search_enabled"> - <label class="custom-control-label" for="plot_enabled">Activate plot</label> - </div> - <div *ngIf="ngForm.form.value.plot_enabled" class="mt-2 table-responsive"> - <table class="table"> - <thead> - <tr> - <th scope="col">Name</th> - <th scope="col">Enabled</th> - <th scope="col">Display</th> - </tr> - </thead> - <tbody> - <tr> - <td class="align-middle">SDSS (DR16)</td> - <td class="align-middle"> - <div class="custom-control custom-switch"> - <input class="custom-control-input" type="checkbox" id="sdss_enabled" name="sdss_enabled" [ngModel]="model.config.cone_search.sdss_enabled"> - <label class="custom-control-label" for="sdss_enabled"></label> - </div> - </td> - <td class="align-middle"> - <input id="sdss_display" type="number" class="form-control" name="sdss_display" value="10" [ngModel]="model.config.cone_search.sdss_display"> - </td> - </tr> - <tr *ngFor="let image of datasetImages"> - <td class="align-middle">{{ getImageFilename(image.name) }}</td> - <td class="align-middle"> - <div class="custom-control custom-switch"> - <input class="custom-control-input" type="checkbox" id="{{ image.id }}_enabled" name="{{ image.id }}_enabled" [ngModel]="getBackgroundEnabled(image)"> - <label class="custom-control-label" for="{{ image.id }}_enabled"></label> - </div> - </td> - <td class="align-middle"> - <input id="{{ image.id }}_display" type="number" class="form-control" name="{{ image.id }}_display" [ngModel]="getBackgroundDisplay(image)"> - </td> - </tr> - </tbody> - </table> - </div> - </accordion-group> - <accordion-group heading="Download configuration"> - <div class="custom-control custom-switch"> - <input class="custom-control-input" type="checkbox" id="download_enabled" name="download_enabled" [ngModel]="model.config.download.enabled"> - <label class="custom-control-label" for="download_enabled">Enabled</label> - </div> - <div class="custom-control custom-switch"> - <input class="custom-control-input" type="checkbox" id="download_opened" name="download_opened" [ngModel]="model.config.download.opened" [disabled]="!ngForm.form.value.download_enabled"> - <label class="custom-control-label" for="download_opened">Opened</label> - </div> - <div class="custom-control custom-switch"> - <input class="custom-control-input" type="checkbox" id="download_result_csv" name="download_result_csv" [ngModel]="model.config.download.csv" [disabled]="!ngForm.form.value.download_enabled"> - <label class="custom-control-label" for="download_result_csv">Display download results button in CSV format</label> - </div> - <div class="custom-control custom-switch"> - <input class="custom-control-input" type="checkbox" id="download_result_ascii" name="download_result_ascii" [ngModel]="model.config.download.ascii" [disabled]="!ngForm.form.value.download_enabled"> - <label class="custom-control-label" for="download_result_ascii">Display download results button in ASCII format</label> - </div> - <div class="custom-control custom-switch"> - <input class="custom-control-input" type="checkbox" id="download_result_vo" name="download_result_vo" [ngModel]="model.config.download.vo" [disabled]="!ngForm.form.value.download_enabled"> - <label class="custom-control-label" for="download_result_vo">Display download results button in VO format</label> - </div> - <div class="custom-control custom-switch"> - <input class="custom-control-input" type="checkbox" id="download_result_archive" name="download_result_archive" [ngModel]="model.config.download.archive" [disabled]="!ngForm.form.value.download_enabled"> - <label class="custom-control-label" for="download_result_archive">Display download results archive button</label> - </div> - </accordion-group> - <accordion-group heading="Summary configuration"> - <div class="custom-control custom-switch"> - <input class="custom-control-input" type="checkbox" id="summary_enabled" name="summary_enabled" [ngModel]="model.config.summary.enabled"> - <label class="custom-control-label" for="summary_enabled">Enabled</label> - </div> - <div class="custom-control custom-switch"> - <input class="custom-control-input" type="checkbox" id="summary_opened" name="summary_opened" [ngModel]="model.config.summary.opened" [disabled]="!ngForm.form.value.summary_enabled"> - <label class="custom-control-label" for="summary_opened">Opened</label> - </div> - </accordion-group> - <accordion-group heading="Server link configuration"> - <div class="custom-control custom-switch"> - <input class="custom-control-input" type="checkbox" id="server_link_enabled" name="server_link_enabled" [ngModel]="model.config.server_link.enabled"> - <label class="custom-control-label" for="server_link_enabled">Enabled</label> - </div> - <div class="custom-control custom-switch"> - <input class="custom-control-input" type="checkbox" id="server_link_opened" name="server_link_opened" [ngModel]="model.config.server_link.opened" [disabled]="!ngForm.form.value.server_link_enabled"> - <label class="custom-control-label" for="server_link_opened">Opened</label> - </div> - </accordion-group> - <accordion-group heading="SAMP configuration"> - <div class="custom-control custom-switch"> - <input class="custom-control-input" type="checkbox" id="samp_enabled" name="samp_enabled" [ngModel]="model.config.samp.enabled"> - <label class="custom-control-label" for="samp_enabled">Enabled</label> - </div> - <div class="custom-control custom-switch"> - <input class="custom-control-input" type="checkbox" id="samp_opened" name="samp_opened" [ngModel]="model.config.samp.opened" [disabled]="!ngForm.form.value.samp_enabled"> - <label class="custom-control-label" for="samp_opened">Opened</label> + <input type="number" class="form-control" id="display" name="display" formControlName="display"> </div> - </accordion-group> - <accordion-group heading="Datatable configuration"> - <div class="custom-control custom-switch"> - <input class="custom-control-input" type="checkbox" id="datatable_enabled" name="datatable_enabled" [ngModel]="model.config.datatable.enabled"> - <label class="custom-control-label" for="datatable_enabled">Enabled</label> - </div> - <div class="custom-control custom-switch"> - <input class="custom-control-input" type="checkbox" id="datatable_opened" name="datatable_opened" [ngModel]="model.config.datatable.enabled"> - <label class="custom-control-label" for="datatable_opened">Open the results datatable accordion</label> + <div class="custom-control custom-radio custom-control-inline"> + <input type="radio" class="custom-control-input" id="public" formControlName="public" [value]="true"> + <label class="custom-control-label" for="public"><i class="fas fa-globe"></i> Public</label> </div> - <div class="custom-control custom-switch"> - <input class="custom-control-input" type="checkbox" id="selectable_row" name="selectable_row" [ngModel]="model.config.datatable.selectable_row"> - <label class="custom-control-label" for="selectable_row">Datatable rows selectable</label> + <div class="custom-control custom-radio custom-control-inline"> + <input type="radio" class="custom-control-input" id="private" formControlName="public" [value]="false"> + <label class="custom-control-label" for="private"><i class="fas fa-lock"></i> Private</label> </div> </accordion-group> + <app-dataset-form-summary [form]="formSummaryPanel"></app-dataset-form-summary> + <app-dataset-form-serverlink [form]="formServerlinkPanel"></app-dataset-form-serverlink> </accordion> <div class="form-group mt-3"> <ng-content></ng-content> </div> </form> - -<ng-template #templateDataPath> - <div class="modal-header"> - <h4 class="modal-title pull-left">ANIS file explorer</h4> - </div> - <div> - <div *ngIf="rootDirectoryInfoIsLoading" class="row justify-content-center mt-5"> - <span class="fas fa-circle-notch fa-spin fa-3x"></span> - <span class="sr-only">Loading...</span> - </div> - - <p class="ml-3 mt-3"> - <i class="far fa-folder"></i> - {{ fileExplorerPath }} - </p> - <div *ngIf="rootDirectoryInfoIsLoaded" class="table-responsive"> - <table class="table table-hover"> - <thead> - <tr> - <th></th> - <th scope="col">Name</th> - <th scope="col">Size</th> - <th scope="col">MimeType</th> - </tr> - </thead> - <tbody> - <tr *ngFor="let fileInfo of rootDirectoryInfo" (click)="dataPathAction(fileInfo)" [class.cursor-pointer]="fileInfo.type === 'dir' && fileInfo.name !== '.'"> - <td> - <span *ngIf="fileInfo.type === 'dir'"><i class="far fa-folder"></i></span> - <span *ngIf="fileInfo.type === 'file'"><i class="far fa-file"></i></span> - </td> - <td class="align-middle"> - {{ fileInfo.name }} - </td> - <td class="align-middle">{{ fileInfo.size | formatFileSize: false }}</td> - <td class="align-middle">{{ fileInfo.mimetype }}</td> - </tr> - </tbody> - </table> - </div> - </div> - <div class="modal-footer"> - <button (click)="modalRef.hide()" class="btn btn-danger">Cancel</button> - - <button [disabled]="dataPathFileExplorerPristine" (click)="selectDirectory()" class="btn btn-primary">Select this directory</button> - </div> -</ng-template> - -<ng-template #templateAddImage> - <div class="modal-header"> - <h4 class="modal-title pull-left">Add an image</h4> - </div> - <div> - <div *ngIf="datasetDirectoryInfoIsLoading" class="row justify-content-center mt-5"> - <span class="fas fa-circle-notch fa-spin fa-3x"></span> - <span class="sr-only">Loading...</span> - </div> - - <p class="ml-3 mt-3"> - <i class="far fa-folder"></i> - {{ fileExplorerPath }} - </p> - <div *ngIf="datasetDirectoryInfoIsLoaded" class="table-responsive"> - <table class="table table-hover"> - <thead> - <tr> - <th></th> - <th scope="col">Name</th> - <th scope="col">Size</th> - <th scope="col">MimeType</th> - </tr> - </thead> - <tbody> - <tr *ngFor="let fileInfo of datasetDirectoryInfo" - (click)="newImageAction(fileInfo)" - [class.table-active]="fileInfo === imageSelected && imageLimitIsLoaded" - [class.cursor-pointer]="fileInfo.name !== '.'"> - <td> - <span *ngIf="fileInfo.type === 'dir'"><i class="far fa-folder"></i></span> - <span *ngIf="fileInfo.type === 'file'"><i class="far fa-file"></i></span> - </td> - <td class="align-middle"> - {{ fileInfo.name }} - </td> - <td class="align-middle">{{ fileInfo.size | formatFileSize: false }}</td> - <td class="align-middle">{{ fileInfo.mimetype }}</td> - </tr> - </tbody> - </table> - </div> - </div> - <div class="modal-footer"> - <button (click)="modalRef.hide()" class="btn btn-danger">Cancel</button> - - <button [disabled]="!imageSelected || !imageLimitIsLoaded" (click)="selectImage()" class="btn btn-primary"> - Select this image - <span *ngIf="imageLimitIsLoading"> - <span class="fas fa-circle-notch fa-spin fa-1x"></span> - </span> - </button> - </div> -</ng-template> - -<ng-template #templateEditImage> - <div class="modal-header"> - <h4 class="modal-title pull-left">Edit an image</h4> - </div> - <div class="modal-body"> - {{ imageSelected | json }} - <form name="formImage" #fi="ngForm" novalidate> - <div class="form-group"> - <label for="name">Name</label> - <input id="name" type="text" class="form-control" name="name" [ngModel]="imageSelected.name" [disabled]="imageSelected.name" required> - </div> - <div class="form-group"> - <label for="name">Size</label> - <input id="name" type="text" class="form-control" name="size" [ngModel]="imageSelected.size" [disabled]="imageSelected.size" required> - </div> - <div class="form-group"> - <label for="name">Ra min</label> - <input id="name" type="text" class="form-control" name="ra_min" [ngModel]="imageSelected.ra_min" [disabled]="imageSelected.ra_min" required> - </div> - <div class="form-group"> - <label for="name">Ra max</label> - <input id="name" type="text" class="form-control" name="ra_max" [ngModel]="imageSelected.ra_max" [disabled]="imageSelected.ra_max" required> - </div> - <div class="form-group"> - <label for="name">Dec min</label> - <input id="name" type="text" class="form-control" name="dec_min" [ngModel]="imageSelected.dec_min" [disabled]="imageSelected.dec_min" required> - </div> - <div class="form-group"> - <label for="name">Dec max</label> - <input id="name" type="text" class="form-control" name="dec_max" [ngModel]="imageSelected.dec_max" [disabled]="imageSelected.dec_max" required> - </div> - <div class="form-group"> - <label for="stretch">Stretch</label> - <select id="stretch" class="form-control" name="stretch" [ngModel]="imageSelected.stretch" required> - <option value="linear">Linear</option> - <option value="sqrt">Sqrt</option> - </select> - </div> - <div class="form-group"> - <label for="name">Pmin</label> - <input id="name" type="number" step="0.01" min="0" max="100" class="form-control" name="pmin" [ngModel]="imageSelected.pmin" required> - </div> - <div class="form-group"> - <label for="name">Pmax</label> - <input id="name" type="number" step="0.01" min="0" max="100" class="form-control" name="pmax" [ngModel]="imageSelected.pmax" required> - </div> - </form> - </div> - <div class="modal-footer"> - <button (click)="modalRef.hide()" class="btn btn-danger">Cancel</button> - - <button [disabled]="!fi.form.valid || fi.form.pristine" (click)="editImage(fi.form.value)" - type="submit" class="btn btn-primary"> - <i class="fa fa-database"></i> Update image information - </button> - </div> -</ng-template> diff --git a/client/src/app/admin/components/dataset/dataset-form.component.ts b/client/src/app/admin/components/dataset/dataset-form.component.ts index ad65a444190f2436febab50356800cc13f85ef70..fba264fb67d40f4f3cc3617ce726e532c4efc544 100644 --- a/client/src/app/admin/components/dataset/dataset-form.component.ts +++ b/client/src/app/admin/components/dataset/dataset-form.component.ts @@ -1,25 +1,32 @@ -import { Component, Input, Output, EventEmitter, TemplateRef, OnChanges, SimpleChanges } from '@angular/core'; +import { Component, Input, Output, EventEmitter, OnInit, OnChanges, SimpleChanges } from '@angular/core'; import { FormGroup, FormControl, Validators } from '@angular/forms'; -import { BsModalService } from 'ngx-bootstrap/modal'; -import { BsModalRef } from 'ngx-bootstrap/modal/bs-modal-ref.service'; - -import { Dataset, Survey, Attribute, DatasetFamily, FileInfo, ImageLimit } from 'src/app/metamodel/store/models'; +import { Dataset, Survey, DatasetFamily } from 'src/app/metamodel/store/models'; @Component({ selector: 'app-dataset-form', templateUrl: 'dataset-form.component.html' }) -export class FormDatasetComponent implements OnChanges { +export class DatasetFormComponent implements OnInit, OnChanges { @Input() dataset: Dataset; - @Output() onSubmit: EventEmitter<Dataset> = new EventEmitter(); @Input() surveyList: Survey[]; + @Input() tableListIsLoading: boolean; + @Input() tableListIsLoaded: boolean; @Input() tableList: string[]; @Input() datasetFamilyList: DatasetFamily[]; + @Input() idDatasetFamily: number; @Output() changeSurvey: EventEmitter<number> = new EventEmitter(); - @Output() loadRootDirectoryInfo: EventEmitter<string> = new EventEmitter(); - @Output() loadDatasetDirectoryInfo: EventEmitter<string> = new EventEmitter(); - @Output() loadFitsImageLimits: EventEmitter<string> = new EventEmitter(); + @Output() onSubmit: EventEmitter<Dataset> = new EventEmitter(); + + public formSummaryPanel = new FormGroup({ + enabled: new FormControl(false), + opened: new FormControl({value: false, disabled: true}) + }); + + public formServerlinkPanel = new FormGroup({ + enabled: new FormControl(false), + opened: new FormControl({value: false, disabled: true}) + }); public form = new FormGroup({ name: new FormControl('', [Validators.required]), @@ -31,245 +38,48 @@ export class FormDatasetComponent implements OnChanges { data_path: new FormControl('', [Validators.required]), display: new FormControl('', [Validators.required]), public: new FormControl('', [Validators.required]), - // config: new FormGroup({ - // images: new FormControl(''), - // cone_search: new FormGroup({ - // enabled: new FormControl(''), - // opened: new FormControl(''), - // column_ra: new FormControl(''), - // column_dec: new FormControl(''), - // plot_enabled: new FormControl(''), - // sdss_enabled: new FormControl(''), - // sdss_display: new FormControl(''), - // background: new FormControl('') - // }), - // download: new FormGroup({ - // enabled: new FormControl(''), - // opened: new FormControl(''), - // csv: new FormControl(''), - // ascii: new FormControl(''), - // vo: new FormControl(''), - // archive: new FormControl('') - // }), - // summary: new FormGroup({ - // enabled: new FormControl(''), - // opened: new FormControl('') - // }), - // server_link: new FormGroup({ - // enabled: new FormControl(''), - // opened: new FormControl('') - // }), - // samp: new FormGroup({ - // enabled: new FormControl(''), - // opened: new FormControl('') - // }), - // datatable: new FormGroup({ - // enabled: new FormControl(''), - // opened: new FormControl(''), - // selectable_row: new FormControl('') - // }) - // }) + config: new FormGroup({ + summary: this.formSummaryPanel, + server_link: this.formServerlinkPanel + }) }); - - modalRef: BsModalRef; - dataPathFileExplorerPristine = true; - fileExplorerPath = ''; - - datasetFileExplorerPath = ''; - imageSelected = null; - datasetImages = []; - - constructor(private modalService: BsModalService) { } - - ngOnChanges(changes: SimpleChanges) { - this.datasetImages = changes.model.currentValue.config.images.map(i => ({...i})); - } - - dataPathOpenModal(template: TemplateRef<any>) { - this.dataPathFileExplorerPristine = true; - this.fileExplorerPath = this.ngForm.controls['data_path'].value; - if (!this.fileExplorerPath) { - this.fileExplorerPath = ''; - } - this.modalRef = this.modalService.show(template); - this.loadRootDirectoryInfo.emit(this.fileExplorerPath); - } - - dataPathAction(fileInfo: FileInfo): void { - if (fileInfo.name === '.' || fileInfo.type !== 'dir') { - return; - } - - if (fileInfo.name === '..') { - this.fileExplorerPath = this.fileExplorerPath.substr(0, this.fileExplorerPath.lastIndexOf("/")); - } else { - this.fileExplorerPath += '/' + fileInfo.name; - } - this.dataPathFileExplorerPristine = false; - this.loadRootDirectoryInfo.emit(this.fileExplorerPath); - } - - selectDirectory() { - this.ngForm.controls['data_path'].setValue(this.fileExplorerPath); - this.ngForm.controls['data_path'].markAsDirty(); - this.modalRef.hide(); - } - - newImageOpenModal(template: TemplateRef<any>): void { - this.imageSelected = null; - this.modalRef = this.modalService.show(template); - this.loadDatasetDirectoryInfo.emit(this.datasetFileExplorerPath); - } - - newImageAction(fileInfo: FileInfo): void { - if (fileInfo.name === '.') { - return; - } - - if (fileInfo.type === 'file' && fileInfo.mimetype === 'image/fits') { - this.loadFitsImageLimits.emit(this.datasetFileExplorerPath + '/' + fileInfo.name); - this.imageSelected = fileInfo; - } else { - if (fileInfo.name === '..') { - this.datasetFileExplorerPath = this.datasetFileExplorerPath.substr(0, this.datasetFileExplorerPath.lastIndexOf("/")); - } else { - this.datasetFileExplorerPath += '/' + fileInfo.name; - } - this.imageSelected = null; - this.loadDatasetDirectoryInfo.emit(this.datasetFileExplorerPath); - } - } - - selectImage(): void { - const name = this.datasetFileExplorerPath + '/' + this.imageSelected.name; - let id = 1; - if (this.datasetImages.length > 0) { - id = Math.max(...this.datasetImages.map(i => i.id)) + 1; + ngOnInit() { + if (this.dataset) { + this.form.patchValue(this.dataset); } - this.datasetImages.push({ - id, - name, - size: this.imageSelected.size, - ...this.imageLimit, - stretch: 'linear', - pmin: 0.25, - pmax: 99.75 - }); - this.modalRef.hide(); - this.ngForm.controls['data_path'].markAsDirty(); - } - - editImageOpenModal(template: TemplateRef<any>, image) { - this.imageSelected = image; - this.modalRef = this.modalService.show(template); - } - - editImage(image) { - this.datasetImages = [ - ...this.datasetImages.filter(i => i.id !== this.imageSelected.id), - { - ...this.imageSelected, - ...image - } - ]; - this.modalRef.hide(); - this.ngForm.controls['data_path'].markAsDirty(); - } - - deleteImage(image): void { - this.datasetImages = this.datasetImages.filter(i => i.id !== image.id); - this.ngForm.controls['data_path'].markAsDirty(); - } - - isConeSearchDisabled(): boolean { - return (!this.model.name) ? true : false; - } - - getDatasetFamilyByIdQueryParam(): DatasetFamily { - return this.datasetFamilyList.find(datasetFamily => datasetFamily.id === this.idDatasetFamilyQueryParam); - } - - getBackgroundEnabled(image) { - const m = this.model.config.cone_search.background.find(i => i.id === image.id); - if (m) { - return m.enabled; - } else { - return false; + this.form.controls.table_ref.disable(); + if (this.idDatasetFamily) { + this.form.controls.id_dataset_family.setValue(this.idDatasetFamily); } } - getBackgroundDisplay(image) { - const m = this.model.config.cone_search.background.find(i => i.id === image.id); - if (m) { - return m.display; + ngOnChanges(changes: SimpleChanges) { + if (changes.tableListIsLoaded && changes.tableListIsLoaded.currentValue) { + this.form.controls.table_ref.enable(); } else { - return (image.id + 1) * 10; + this.form.controls.table_ref.disable(); } } - getImageFilename(path: string) { - return path.replace(/^.*[\\\/]/, ''); - } - - onChange(surveyName: string): void { - this.changeSurvey.emit(this.surveyList.find(survey => survey.name === surveyName).id_database); + submit() { + console.log(this.form.value); + // if (this.dataset) { + // this.onSubmit.emit({ + // ...this.dataset, + // ...this.form.value + // }); + // } else { + // this.onSubmit.emit(this.form.value); + // } } - emit(dataset: Dataset): void { - let datasetEmitted: Dataset; - (this.model.name) ? datasetEmitted = {name: this.model.name, ...dataset} : datasetEmitted = dataset; - if (!dataset.data_path) { - datasetEmitted.data_path = ''; - } - if (!dataset.vo) { - datasetEmitted.vo = false; - } - if (this.idDatasetFamilyQueryParam > 0) { - datasetEmitted.id_dataset_family = this.idDatasetFamilyQueryParam; + onChangeSurvey() { + const surveyName = this.form.controls.survey_name.value; + if (!surveyName) { + this.form.controls.table_ref.disable(); + } else { + this.changeSurvey.emit(this.surveyList.find(survey => survey.name === surveyName).id_database); } - datasetEmitted.config = { - images: this.datasetImages, - cone_search: { - enabled: this.ngForm.form.value.cone_search_enabled, - opened: this.ngForm.form.value.cone_search_opened, - column_ra: +this.ngForm.form.value.column_ra, - column_dec: +this.ngForm.form.value.column_dec, - plot_enabled: this.ngForm.form.value.plot_enabled, - sdss_enabled: this.ngForm.form.value.sdss_enabled, - sdss_display: this.ngForm.form.value.sdss_display, - background: this.datasetImages.map((v, i) => { - const e = `${v.id}_enabled`; - const d = `${v.id}_display`; - return {id: v.id, enabled: this.ngForm.form.value[e], display: this.ngForm.form.value[d]} - }) - }, - download: { - enabled: this.ngForm.form.value.download_enabled, - opened: this.ngForm.form.value.download_opened, - csv: this.ngForm.form.value.download_result_csv, - ascii: this.ngForm.form.value.download_result_ascii, - vo: this.ngForm.form.value.download_result_vo, - archive: this.ngForm.form.value.download_result_archive, - }, - summary: { - enabled: this.ngForm.form.value.summary_enabled, - opened: this.ngForm.form.value.summary_opened - }, - server_link: { - enabled: this.ngForm.form.value.server_link_enabled, - opened: this.ngForm.form.value.server_link_opened - }, - samp: { - enabled: this.ngForm.form.value.samp_enabled, - opened: this.ngForm.form.value.samp_opened - }, - datatable: { - enabled: this.ngForm.form.value.datatable_enabled, - opened: this.ngForm.form.value.datatable_opened, - selectable_row: this.ngForm.form.value.selectable_row - } - }; - this.submitted.emit(datasetEmitted); } } diff --git a/client/src/app/admin/components/dataset/index.ts b/client/src/app/admin/components/dataset/index.ts index 90f56c86134fa9764fabaf6212495a831c5f395e..d5bcefa0a2b37c955349ddd5e6781a04380c510d 100644 --- a/client/src/app/admin/components/dataset/index.ts +++ b/client/src/app/admin/components/dataset/index.ts @@ -1,5 +1,11 @@ -import { DatasetCardComponent } from "./dataset-card.component"; +import { DatasetCardComponent } from './dataset-card.component'; +import { DatasetFormComponent } from './dataset-form.component'; +import { DatasetFormSummaryComponent } from './dataset-form-summary.component'; +import { DatasetFormServerlinkComponent } from './dataset-form-serverlink.component'; export const datasetComponents = [ - DatasetCardComponent + DatasetCardComponent, + DatasetFormComponent, + DatasetFormSummaryComponent, + DatasetFormServerlinkComponent ]; diff --git a/client/src/app/admin/components/instance/index.ts b/client/src/app/admin/components/instance/index.ts index 07462a726098f4dab3c916dbc2214eac61f0a471..ccf85e01bd58bb0290d3754f7ed1da2eb56fd15a 100644 --- a/client/src/app/admin/components/instance/index.ts +++ b/client/src/app/admin/components/instance/index.ts @@ -1,7 +1,9 @@ import { InstanceCardComponent } from './instance-card.component'; +import { InstanceButtonsComponent } from './instance-buttons.component'; import { InstanceFormComponent } from './instance-form.component'; export const instanceComponents = [ InstanceCardComponent, + InstanceButtonsComponent, InstanceFormComponent ]; diff --git a/client/src/app/admin/components/instance/instance-buttons.component.html b/client/src/app/admin/components/instance/instance-buttons.component.html new file mode 100644 index 0000000000000000000000000000000000000000..2e854d9dca1970b7e13b69ed9c8dbbe43632595a --- /dev/null +++ b/client/src/app/admin/components/instance/instance-buttons.component.html @@ -0,0 +1,27 @@ +<div class="btn-toolbar mb-3" role="toolbar" aria-label="Toolbar with button groups"> + <div class="btn-group mr-2" role="group" aria-label="First group"> + <button (click)="openModal(template); $event.stopPropagation()" title="Add new dataset family" class="btn btn-outline-success"> + <span class="fas fa-plus"></span> New dataset family + </button> + </div> + <div class="btn-group mr-2" role="group" aria-label="Second group"> + <button routerLink="group" title="Handle groups" class="btn btn-outline-primary"> + <span class="fas fa-users"></span> Handle groups + </button> + </div> +</div> + +<ng-template #template> + <div class="modal-header"> + <h4 class="modal-title pull-left"><strong>Add a new option</strong></h4> + </div> + <div class="modal-body"> + <app-dataset-family-form (onSubmit)="addDatasetFamily.emit($event)" #formAddDatasetFamily> + <button [disabled]="!formAddDatasetFamily.form.valid || formAddDatasetFamily.form.pristine" (click)="modalRef.hide()" type="submit" class="btn btn-primary"> + <span class="fa fa-database"></span> Add new dataset family + </button> + + <button (click)="modalRef.hide()" type="button" class="btn btn-danger">Cancel</button> + </app-dataset-family-form> + </div> +</ng-template> diff --git a/client/src/app/admin/components/instance/instance-buttons.component.ts b/client/src/app/admin/components/instance/instance-buttons.component.ts new file mode 100644 index 0000000000000000000000000000000000000000..4d4938f0b38f48f3b33681680492b1a7024d9ad6 --- /dev/null +++ b/client/src/app/admin/components/instance/instance-buttons.component.ts @@ -0,0 +1,23 @@ +import { Component, Output, ChangeDetectionStrategy, EventEmitter, TemplateRef } from '@angular/core'; + +import { BsModalService } from 'ngx-bootstrap/modal'; +import { BsModalRef } from 'ngx-bootstrap/modal/bs-modal-ref.service'; + +import { DatasetFamily } from 'src/app/metamodel/store/models'; + +@Component({ + selector: 'app-instance-buttons', + templateUrl: 'instance-buttons.component.html', + changeDetection: ChangeDetectionStrategy.OnPush +}) +export class InstanceButtonsComponent { + @Output() addDatasetFamily: EventEmitter<DatasetFamily> = new EventEmitter(); + + modalRef: BsModalRef; + + constructor(private modalService: BsModalService) { } + + openModal(template: TemplateRef<any>) { + this.modalRef = this.modalService.show(template); + } +} diff --git a/client/src/app/admin/components/settings/add-option.component.html b/client/src/app/admin/components/settings/add-option.component.html new file mode 100644 index 0000000000000000000000000000000000000000..52195c4816b9ebe4835f69ce4f6d26a959bdae51 --- /dev/null +++ b/client/src/app/admin/components/settings/add-option.component.html @@ -0,0 +1,18 @@ +<button title="Add a new option" class="btn btn-outline-success" (click)="openModal(template)"> + <span class="fas fa-plus"></span> +</button> + +<ng-template #template> + <div class="modal-header"> + <h4 class="modal-title pull-left"><strong>Add a new option</strong></h4> + </div> + <div class="modal-body"> + <app-form-option (onSubmit)="onSubmit.emit($event)" #formAddOption> + <button [disabled]="!formAddOption.selectOptionForm.valid || formAddOption.selectOptionForm.pristine" (click)="modalRef.hide()" type="submit" class="btn btn-primary"> + <span class="fa fa-database"></span> Add new option + </button> + + <button (click)="modalRef.hide()" type="button" class="btn btn-danger">Cancel</button> + </app-form-option> + </div> +</ng-template> diff --git a/client/src/app/admin/components/shared/add-btn.component.ts b/client/src/app/admin/components/settings/add-option.component.ts similarity index 52% rename from client/src/app/admin/components/shared/add-btn.component.ts rename to client/src/app/admin/components/settings/add-option.component.ts index fc9035a5a21f98faef09aa25dfbf90558a2786f0..08d0cf611d7ce4c3c43c0c3066ab2b188b2a4b74 100644 --- a/client/src/app/admin/components/shared/add-btn.component.ts +++ b/client/src/app/admin/components/settings/add-option.component.ts @@ -1,17 +1,17 @@ -import { Component, Input, Output, ChangeDetectionStrategy, EventEmitter, TemplateRef } from '@angular/core'; +import { Component, Output, ChangeDetectionStrategy, EventEmitter, TemplateRef } from '@angular/core'; import { BsModalService } from 'ngx-bootstrap/modal'; import { BsModalRef } from 'ngx-bootstrap/modal/bs-modal-ref.service'; +import { SelectOption } from 'src/app/metamodel/store/models'; + @Component({ - selector: 'app-add-btn', - templateUrl: 'add-btn.component.html', + selector: 'app-add-option', + templateUrl: 'add-option.component.html', changeDetection: ChangeDetectionStrategy.OnPush }) -export class AddBtnComponent { - @Input() type: string; - @Input() label: string; - @Input() disabled: boolean = false; +export class AddOptionComponent { + @Output() onSubmit: EventEmitter<SelectOption> = new EventEmitter(); modalRef: BsModalRef; @@ -20,4 +20,4 @@ export class AddBtnComponent { openModal(template: TemplateRef<any>) { this.modalRef = this.modalService.show(template); } -} +} \ No newline at end of file diff --git a/client/src/app/admin/components/settings/edit-option.component.html b/client/src/app/admin/components/settings/edit-option.component.html new file mode 100644 index 0000000000000000000000000000000000000000..0a57daa0ae72480c7df266244880c1a5394fc625 --- /dev/null +++ b/client/src/app/admin/components/settings/edit-option.component.html @@ -0,0 +1,18 @@ +<button title="Edit this option" (click)="openModal(template)" class="btn btn-outline-primary"> + <span class="fas fa-edit"></span> +</button> + +<ng-template #template> + <div class="modal-header"> + <h4 class="modal-title pull-left"><strong>Edit option</strong></h4> + </div> + <div class="modal-body"> + <app-form-option [selectOption]="option" (onSubmit)="onSubmit.emit($event)" #formAddOption> + <button [disabled]="!formAddOption.selectOptionForm.valid || formAddOption.selectOptionForm.pristine" (click)="modalRef.hide()" type="submit" class="btn btn-primary"> + <span class="fa fa-database"></span> Edit select option + </button> + + <button (click)="modalRef.hide()" type="button" class="btn btn-danger">Cancel</button> + </app-form-option> + </div> +</ng-template> diff --git a/client/src/app/admin/components/settings/edit-option.component.ts b/client/src/app/admin/components/settings/edit-option.component.ts new file mode 100644 index 0000000000000000000000000000000000000000..c0ab889a3acc89b9f09f5b11013c63a2af2c702e --- /dev/null +++ b/client/src/app/admin/components/settings/edit-option.component.ts @@ -0,0 +1,24 @@ +import { Component, Input, ChangeDetectionStrategy, TemplateRef, Output, EventEmitter } from '@angular/core'; + +import { BsModalService } from 'ngx-bootstrap/modal'; +import { BsModalRef } from 'ngx-bootstrap/modal/bs-modal-ref.service'; + +import { SelectOption } from 'src/app/metamodel/store/models'; + +@Component({ + selector: 'app-edit-option', + templateUrl: 'edit-option.component.html', + changeDetection: ChangeDetectionStrategy.OnPush +}) +export class EditOptionComponent { + @Input() option: SelectOption; + @Output() onSubmit: EventEmitter<SelectOption> = new EventEmitter(); + + modalRef: BsModalRef; + + constructor(private modalService: BsModalService) { } + + openModal(template: TemplateRef<any>) { + this.modalRef = this.modalService.show(template); + } +} diff --git a/client/src/app/admin/components/settings/edit-select.component.html b/client/src/app/admin/components/settings/edit-select.component.html new file mode 100644 index 0000000000000000000000000000000000000000..188c393e9f8ddbf7200dac011e2ad69337e1531d --- /dev/null +++ b/client/src/app/admin/components/settings/edit-select.component.html @@ -0,0 +1,18 @@ +<button title="Edit this select" (click)="openModal(template)" class="btn btn-outline-primary"> + <span class="fas fa-edit"></span> +</button> + +<ng-template #template> + <div class="modal-header"> + <h4 class="modal-title pull-left"><strong>Edit select</strong></h4> + </div> + <div class="modal-body"> + <app-form-select [select]="select" (onSubmit)="onSubmit.emit($event)" #formEditSelect> + <button [disabled]="!formEditSelect.selectForm.valid || formEditSelect.selectForm.pristine" (click)="modalRef.hide()" type="submit" class="btn btn-primary"> + <span class="fa fa-database"></span> Update select information + </button> + + <button (click)="modalRef.hide()" type="button" class="btn btn-danger">Cancel</button> + </app-form-select> + </div> +</ng-template> diff --git a/client/src/app/admin/components/shared/edit-btn.component.ts b/client/src/app/admin/components/settings/edit-select.component.ts similarity index 50% rename from client/src/app/admin/components/shared/edit-btn.component.ts rename to client/src/app/admin/components/settings/edit-select.component.ts index 29717291cd35c466c5c2a114055603226457bb0d..8b7ea26c222bcf1e97b397ddf508f3edaa8b685a 100644 --- a/client/src/app/admin/components/shared/edit-btn.component.ts +++ b/client/src/app/admin/components/settings/edit-select.component.ts @@ -1,17 +1,18 @@ -import { Component, Input, ChangeDetectionStrategy, TemplateRef } from '@angular/core'; +import { Component, Input, ChangeDetectionStrategy, TemplateRef, Output, EventEmitter } from '@angular/core'; import { BsModalService } from 'ngx-bootstrap/modal'; import { BsModalRef } from 'ngx-bootstrap/modal/bs-modal-ref.service'; +import { Select } from 'src/app/metamodel/store/models'; + @Component({ - selector: 'app-edit-btn', - templateUrl: 'edit-btn.component.html', + selector: 'app-edit-select', + templateUrl: 'edit-select.component.html', changeDetection: ChangeDetectionStrategy.OnPush }) -export class EditBtnComponent { - @Input() type: string; - @Input() label: string; - @Input() disabled: boolean = false; +export class EditSelectComponent { + @Input() select: Select; + @Output() onSubmit: EventEmitter<Select> = new EventEmitter(); modalRef: BsModalRef; diff --git a/client/src/app/admin/components/settings/index.ts b/client/src/app/admin/components/settings/index.ts index ed1b9a88dd006e9fe3c2e69f4f2da826eaa1b809..ecef696fd07231172fc7658eb4fbdf501fe476ff 100644 --- a/client/src/app/admin/components/settings/index.ts +++ b/client/src/app/admin/components/settings/index.ts @@ -1,5 +1,8 @@ import { SelectListComponent } from "./select-list.component"; import { SelectButtonsComponent } from "./select-buttons.component"; +import { EditSelectComponent } from "./edit-select.component"; +import { AddOptionComponent } from "./add-option.component"; +import { EditOptionComponent } from "./edit-option.component"; import { OptionTableComponent } from "./option-table.component"; import { FormSelectComponent } from "./form-select.component"; import { FormOptionComponent } from "./form-option.component"; @@ -7,6 +10,9 @@ import { FormOptionComponent } from "./form-option.component"; export const settingsComponents = [ SelectListComponent, SelectButtonsComponent, + EditSelectComponent, + AddOptionComponent, + EditOptionComponent, OptionTableComponent, FormSelectComponent, FormOptionComponent diff --git a/client/src/app/admin/components/settings/option-table.component.html b/client/src/app/admin/components/settings/option-table.component.html index e7cba6c2f154a3d192b0bf3be6bb301887b0c891..47140132e0d3a12fc16cb7e48776ee3a7614e390 100644 --- a/client/src/app/admin/components/settings/option-table.component.html +++ b/client/src/app/admin/components/settings/option-table.component.html @@ -15,15 +15,10 @@ <td class="align-middle">{{ option.value }}</td> <td class="align-middle">{{ option.display }}</td> <td class="align-middle"> - <app-edit-btn [type]="'option'" [type]="'option'" [label]="option.label" #editBtn> - <app-form-option [selectOption]="option" (onSubmit)="editOption.emit($event)" #formAddOption> - <button [disabled]="!formAddOption.selectOptionForm.valid || formAddOption.selectOptionForm.pristine" (click)="editBtn.modalRef.hide()" type="submit" class="btn btn-primary"> - <span class="fa fa-database"></span> Edit select option - </button> - - <button (click)="editBtn.modalRef.hide()" type="button" class="btn btn-danger">Cancel</button> - </app-form-option> - </app-edit-btn> + <app-edit-option + [option]="option" + (onSubmit)="editOption.emit($event)"> + </app-edit-option> </td> <td class="align-middle"> <app-delete-btn diff --git a/client/src/app/admin/components/settings/select-buttons.component.html b/client/src/app/admin/components/settings/select-buttons.component.html index 4da6a2ff6de6133bed294bec48546e34cc541043..eb07d5f29f050e6110702ec5db70cb5e40546f62 100644 --- a/client/src/app/admin/components/settings/select-buttons.component.html +++ b/client/src/app/admin/components/settings/select-buttons.component.html @@ -1,22 +1,11 @@ -<app-add-btn [type]="'option'" [label]="getFormAddOptionLabel()" #addBtn> - <app-form-option (onSubmit)="addNewSelectOption($event)" #formAddOption> - <button [disabled]="!formAddOption.selectOptionForm.valid || formAddOption.selectOptionForm.pristine" (click)="addBtn.modalRef.hide()" type="submit" class="btn btn-primary"> - <span class="fa fa-database"></span> Add new option - </button> - - <button (click)="addBtn.modalRef.hide()" type="button" class="btn btn-danger">Cancel</button> - </app-form-option> -</app-add-btn> +<app-add-option + (onSubmit)="addNewSelectOption($event)"> +</app-add-option> -<app-edit-btn [type]="'select'" [label]="select.label" #editBtn> - <app-form-select [select]="select" (onSubmit)="editSelect.emit($event)" #formEditSelect> - <button [disabled]="!formEditSelect.selectForm.valid || formEditSelect.selectForm.pristine" (click)="editBtn.modalRef.hide()" type="submit" class="btn btn-primary"> - <span class="fa fa-database"></span> Update select information - </button> - - <button (click)="editBtn.modalRef.hide()" type="button" class="btn btn-danger">Cancel</button> - </app-form-select> -</app-edit-btn> +<app-edit-select + [select]="select" + (onSubmit)="editSelect.emit($event)"> +</app-edit-select> <app-delete-btn [type]="'select'" diff --git a/client/src/app/admin/components/settings/select-buttons.component.ts b/client/src/app/admin/components/settings/select-buttons.component.ts index 640892b5e3c8ee9c73a070cd1bf9b0fd498911e2..6efacea76e4c07777b6aaab7d8eac173f315052b 100644 --- a/client/src/app/admin/components/settings/select-buttons.component.ts +++ b/client/src/app/admin/components/settings/select-buttons.component.ts @@ -13,10 +13,6 @@ export class SelectButtonsComponent { @Output() editSelect: EventEmitter<Select> = new EventEmitter(); @Output() addOption: EventEmitter<SelectOption> = new EventEmitter(); - getFormAddOptionLabel() { - return `Add new option for : ${this.select.label}`; - } - addNewSelectOption(selectOption: SelectOption) { this.addOption.emit({...selectOption, select_name: this.select.name}); } diff --git a/client/src/app/admin/components/shared/add-btn.component.html b/client/src/app/admin/components/shared/add-btn.component.html deleted file mode 100644 index bafcf26f07bdee59c93e637182e82db9034306ea..0000000000000000000000000000000000000000 --- a/client/src/app/admin/components/shared/add-btn.component.html +++ /dev/null @@ -1,12 +0,0 @@ -<button title="Add {{ type }}" class="btn btn-outline-success" (click)="openModal(template)"> - <span class="fas fa-plus"></span> -</button> - -<ng-template #template> - <div class="modal-header"> - <h4 class="modal-title pull-left"><strong>{{ label }}</strong></h4> - </div> - <div class="modal-body"> - <ng-content></ng-content> - </div> -</ng-template> diff --git a/client/src/app/admin/components/shared/edit-btn.component.html b/client/src/app/admin/components/shared/edit-btn.component.html deleted file mode 100644 index 892b93828076d9ae2963f892f1c8e6a85dea3d9b..0000000000000000000000000000000000000000 --- a/client/src/app/admin/components/shared/edit-btn.component.html +++ /dev/null @@ -1,12 +0,0 @@ -<button [disabled]="disabled" title="Edit this {{ type }}" (click)="openModal(template)" class="btn btn-outline-primary"> - <span class="fas fa-edit"></span> -</button> - -<ng-template #template> - <div class="modal-header"> - <h4 class="modal-title pull-left"><strong>{{ label }}</strong></h4> - </div> - <div class="modal-body"> - <ng-content></ng-content> - </div> -</ng-template> diff --git a/client/src/app/admin/components/shared/index.ts b/client/src/app/admin/components/shared/index.ts index 9fff83b373b0ee8c0c83c342c733d0046038eaf3..a898e06a5199749c9935b4db51f46f307faee7b2 100644 --- a/client/src/app/admin/components/shared/index.ts +++ b/client/src/app/admin/components/shared/index.ts @@ -1,9 +1,5 @@ -import { AddBtnComponent } from "./add-btn.component"; -import { EditBtnComponent } from "./edit-btn.component"; import { DeleteBtnComponent } from "./delete-btn.component"; export const sharedComponents = [ - AddBtnComponent, - EditBtnComponent, DeleteBtnComponent ]; diff --git a/client/src/app/admin/components/survey/survey-form.component.html b/client/src/app/admin/components/survey/survey-form.component.html index ae53465a55ce3a0e9a04c002449f03cb47d148d1..e0a2f922ffcea21a41b7d75a30d94ddcf7b1040b 100644 --- a/client/src/app/admin/components/survey/survey-form.component.html +++ b/client/src/app/admin/components/survey/survey-form.component.html @@ -11,7 +11,7 @@ <label for="id_database">Database</label> <select class="form-control" id="id_database" name="id_database" formControlName="id_database"> <option></option> - <option *ngFor="let database of databaseList" [value]="database.id" [selected]="survey && database.id === survey.id_database">{{database.label}}</option> + <option *ngFor="let database of databaseList" [ngValue]="database.id">{{database.label}}</option> </select> </div> <div class="form-group"> diff --git a/client/src/app/admin/components/survey/survey-table.component.html b/client/src/app/admin/components/survey/survey-table.component.html index e92d696d3a3a4d4735e914fd4202f6902c5469ae..baa998b6eb582d520618865b5987e5b10eb91ac7 100644 --- a/client/src/app/admin/components/survey/survey-table.component.html +++ b/client/src/app/admin/components/survey/survey-table.component.html @@ -29,6 +29,7 @@ </td> <td class="align-middle"> <app-delete-btn + [disabled]="survey.nb_datasets > 0" [type]="'survey'" [label]="survey.label" (confirm)="deleteSurvey.emit(survey)"> diff --git a/client/src/app/admin/containers/dataset/new-dataset.component.html b/client/src/app/admin/containers/dataset/new-dataset.component.html new file mode 100644 index 0000000000000000000000000000000000000000..27392c9062819bb07cf88b1dbcf9104af1762483 --- /dev/null +++ b/client/src/app/admin/containers/dataset/new-dataset.component.html @@ -0,0 +1,40 @@ +<div class="container-fluid"> + <nav aria-label="breadcrumb"> + <ol class="breadcrumb"> + <li class="breadcrumb-item"> + <a routerLink="/admin/instance-list">Instances</a> + </li> + <li class="breadcrumb-item active" aria-current="page"> + <a routerLink="/admin/configure-instance/{{ instanceName | async }}"> + Configure instance {{ instanceName | async }} + </a> + </li> + <li class="breadcrumb-item active" aria-current="page">New dataset</li> + </ol> + </nav> +</div> + +<div class="container"> + <app-spinner *ngIf="(surveyListIsLoading | async) || (datasetFamilyListIsLoading | async)"></app-spinner> + + <div *ngIf="(surveyListIsLoaded | async) && (datasetFamilyListIsLoaded | async)" class="row"> + <div class="col-12"> + <app-dataset-form + [surveyList]="surveyList | async" + [tableListIsLoading]="tableListIsLoading | async" + [tableListIsLoaded]="tableListIsLoaded | async" + [tableList]="tableList | async" + [datasetFamilyList]="datasetFamilyList | async" + [idDatasetFamily]="idDatasetFamily | async" + (changeSurvey)="loadTableList($event)" + (onSubmit)="addNewDataset($event)" + #formDataset> + <button [disabled]="!formDataset.form.valid || formDataset.form.pristine" type="submit" class="btn btn-primary"> + <i class="fa fa-database"></i> Add new dataset + </button> + + <a routerLink="/admin/configure-instance/{{instanceName | async}}" type="button" class="btn btn-danger">Cancel</a> + </app-dataset-form> + </div> + </div> +</div> diff --git a/client/src/app/admin/containers/dataset/new-dataset.component.ts b/client/src/app/admin/containers/dataset/new-dataset.component.ts new file mode 100644 index 0000000000000000000000000000000000000000..afb389674faef1a586112cd780f865367b32df68 --- /dev/null +++ b/client/src/app/admin/containers/dataset/new-dataset.component.ts @@ -0,0 +1,63 @@ +import { Component, OnInit } from '@angular/core'; +import { ActivatedRoute } from '@angular/router'; + +import { Observable } from 'rxjs'; +import { map } from 'rxjs/operators'; +import { Store } from '@ngrx/store'; + +import { Survey, DatasetFamily, Dataset } from 'src/app/metamodel/store/models'; +import * as surveyActions from 'src/app/metamodel/store/actions/survey.actions'; +import * as surveySelector from 'src/app/metamodel/store/selectors/survey.selector'; +import * as tableActions from 'src/app/metamodel/store/actions/table.actions'; +import * as tableSelector from 'src/app/metamodel/store/selectors/table.selector'; +import * as datasetFamilyActions from 'src/app/metamodel/store/actions/dataset-family.actions'; +import * as datasetFamilySelector from 'src/app/metamodel/store/selectors/dataset-family.selector'; +import * as datasetActions from 'src/app/metamodel/store/actions/dataset.actions'; +import * as instanceSelector from 'src/app/metamodel/store/selectors/instance.selector'; + +@Component({ + selector: 'app-new-dataset', + templateUrl: 'new-dataset.component.html' +}) +export class NewDatasetComponent implements OnInit { + public instanceName: Observable<string>; + public surveyListIsLoading: Observable<boolean>; + public surveyListIsLoaded: Observable<boolean>; + public surveyList: Observable<Survey[]>; + public tableListIsLoading: Observable<boolean>; + public tableListIsLoaded: Observable<boolean>; + public tableList: Observable<string[]>; + public datasetFamilyListIsLoading: Observable<boolean>; + public datasetFamilyListIsLoaded: Observable<boolean>; + public datasetFamilyList: Observable<DatasetFamily[]>; + public idDatasetFamily: Observable<number>; + + constructor(private store: Store<{ }>, private route: ActivatedRoute) { + this.instanceName = store.select(instanceSelector.selectInstanceNameByRoute); + this.surveyListIsLoading = store.select(surveySelector.selectSurveyListIsLoading); + this.surveyListIsLoaded = store.select(surveySelector.selectSurveyListIsLoaded); + this.surveyList = store.select(surveySelector.selectAllSurveys); + this.tableListIsLoading = store.select(tableSelector.selectTableListIsLoading); + this.tableListIsLoaded = store.select(tableSelector.selectTableListIsLoaded); + this.tableList = store.select(tableSelector.selectAllTables); + this.datasetFamilyListIsLoading = store.select(datasetFamilySelector.selectDatasetFamilyListIsLoading); + this.datasetFamilyListIsLoaded = store.select(datasetFamilySelector.selectDatasetFamilyListIsLoaded); + this.datasetFamilyList = store.select(datasetFamilySelector.selectAllDatasetFamilies); + } + + ngOnInit() { + this.store.dispatch(surveyActions.loadSurveyList()); + this.store.dispatch(datasetFamilyActions.loadDatasetFamilyList()); + this.idDatasetFamily = this.route.queryParamMap.pipe( + map(params => +params.get('id_dataset_family')) + ); + } + + loadTableList(idDatabase: number) { + this.store.dispatch(tableActions.loadTableList({ idDatabase })); + } + + addNewDataset(dataset: Dataset) { + this.store.dispatch(datasetActions.addDataset({ dataset })); + } +} diff --git a/client/src/app/admin/containers/group/group-list.component.html b/client/src/app/admin/containers/group/group-list.component.html index 493934474a69954ff405fefddf7ad086fde4c5a7..2f0fcffeb2547f476486e04023a7abff8d36c438 100644 --- a/client/src/app/admin/containers/group/group-list.component.html +++ b/client/src/app/admin/containers/group/group-list.component.html @@ -2,16 +2,16 @@ <nav aria-label="breadcrumb"> <ol class="breadcrumb"> <li class="breadcrumb-item"> - <a routerLink="/instance-list"> + <a routerLink="/admin/instance-list"> Instances </a> </li> <li class="breadcrumb-item"> - <a routerLink="/configure-instance/{{ instanceName | async }}"> + <a routerLink="/admin/configure-instance/{{ instanceName | async }}"> Configure instance {{ instanceName | async }} </a> </li> - <li class="breadcrumb-item active" aria-current="page">Groups</li> + <li class="breadcrumb-item active" aria-current="page">Handle groups</li> </ol> </nav> diff --git a/client/src/app/admin/containers/group/new-group.component.html b/client/src/app/admin/containers/group/new-group.component.html index 5986584fb84c865920d2cb42b2f3f316c2b83240..6da30524ea5e9034ece59d1511436a3fd0e420f3 100644 --- a/client/src/app/admin/containers/group/new-group.component.html +++ b/client/src/app/admin/containers/group/new-group.component.html @@ -2,17 +2,17 @@ <nav aria-label="breadcrumb"> <ol class="breadcrumb"> <li class="breadcrumb-item"> - <a routerLink="/instance-list"> + <a routerLink="/admin/instance-list"> Instances </a> </li> <li class="breadcrumb-item"> - <a routerLink="/configure-instance/{{ instanceName | async }}"> + <a routerLink="/admin/configure-instance/{{ instanceName | async }}"> Configure instance {{ instanceName | async }} </a> </li> <li class="breadcrumb-item" aria-current="page"> - <a routerLink="/configure-instance/{{ instanceName | async }}/group"> + <a routerLink="/admin/configure-instance/{{ instanceName | async }}/group"> Groups </a> </li> diff --git a/client/src/app/admin/containers/instance/configure-instance.component.html b/client/src/app/admin/containers/instance/configure-instance.component.html index eecebe73140830f52de61b377297543a5048196e..2a3dd82c36f7a36bb417b42293f1eed14d308b72 100644 --- a/client/src/app/admin/containers/instance/configure-instance.component.html +++ b/client/src/app/admin/containers/instance/configure-instance.component.html @@ -1,7 +1,7 @@ <div class="container-fluid"> <nav aria-label="breadcrumb"> <ol class="breadcrumb"> - <li class="breadcrumb-item"><a routerLink="/instance-list">Instances</a></li> + <li class="breadcrumb-item"><a routerLink="/admin/instance-list">Instances</a></li> <li *ngIf="(instanceListIsLoaded | async)" class="breadcrumb-item active" aria-current="page">Configure instance {{ (instance | async).label }}</li> </ol> </nav> @@ -11,44 +11,17 @@ <app-spinner *ngIf="(instanceListIsLoading | async) || (datasetFamilyListIsLoading | async) || (datasetListIsLoading | async)"></app-spinner> <ng-container *ngIf="(instanceListIsLoaded | async) && (datasetFamilyListIsLoaded | async) && (datasetListIsLoaded | async)"> - <div class="btn-toolbar mb-3" role="toolbar" aria-label="Toolbar with button groups"> - <div class="btn-group mr-2" role="group" aria-label="First group"> - <app-add-btn [type]="'dataset-family'" [label]="'Add a new dataset family'" #addBtn> - <app-dataset-family-form (onSubmit)="addDatasetFamily($event)" #formAddDatasetFamily> - <button [disabled]="!formAddDatasetFamily.form.valid || formAddDatasetFamily.form.pristine" (click)="addBtn.modalRef.hide()" type="submit" class="btn btn-primary"> - <span class="fa fa-database"></span> Add new dataset family - </button> - - <button (click)="addBtn.modalRef.hide()" type="button" class="btn btn-danger">Cancel</button> - </app-dataset-family-form> - </app-add-btn> - </div> - <div class="btn-group mr-2" role="group" aria-label="Second group"> - <button routerLink="group" title="Handle groups" class="btn btn-outline-primary"> - <span class="fas fa-users"></span> Handle groups - </button> - </div> - </div> + <app-instance-buttons + (addDatasetFamily)="addDatasetFamily($event)"> + </app-instance-buttons> - <app-dataset-family-card *ngFor="let datasetFamily of (datasetFamilyList | async)" - [datasetFamily]="datasetFamily" - (editDatasetFamily)="editDatasetFamily($event)" - (deleteDatasetFamily)="deleteDatasetFamily($event)"> - <div class="row row-cols-1 row-cols-sm-2 row-cols-md-3"> - <div *ngFor="let dataset of (datasetList | async | datasetListByFamily:datasetFamily.id)" class="col mb-3"> - <app-dataset-card - [dataset]="dataset" - (deleteDataset)="deleteDataset($event)"> - </app-dataset-card> - </div> - <div class="col h-100 d-table"> - <div routerLink="new-dataset" [queryParams]="{id_dataset_family: datasetFamily.id}" class="card card-add d-table-cell align-middle pointer" title="Add new dataset"> - <div class="card-body text-center"> - <span class="fas fa-plus fa-4x text-light"></span> - </div> - </div> - </div> - </div> + <app-dataset-family-card + *ngFor="let datasetFamily of (datasetFamilyList | async)" + [datasetFamily]="datasetFamily" + [datasetList]="datasetList | async" + (editDatasetFamily)="editDatasetFamily($event)" + (deleteDatasetFamily)="deleteDatasetFamily($event)" + (deleteDataset)="deleteDataset($event)"> </app-dataset-family-card> </ng-container> </div> diff --git a/client/src/app/admin/containers/instance/edit-instance.component.html b/client/src/app/admin/containers/instance/edit-instance.component.html index 601bccad2abdb8fc1f204eaf165281dd0200b968..9a0c834d18f0c0541617763368ed39bd6cf98513 100644 --- a/client/src/app/admin/containers/instance/edit-instance.component.html +++ b/client/src/app/admin/containers/instance/edit-instance.component.html @@ -1,7 +1,7 @@ <div class="container-fluid"> <nav aria-label="breadcrumb"> <ol class="breadcrumb"> - <li class="breadcrumb-item"><a routerLink="/instance-list">Instances</a></li> + <li class="breadcrumb-item"><a routerLink="/admin/instance-list">Instances</a></li> <li *ngIf="instanceListIsLoaded | async" class="breadcrumb-item active" aria-current="page">Edit instance {{ (instance | async).name }}</li> </ol> </nav> diff --git a/client/src/app/admin/containers/survey/survey-list.component.html b/client/src/app/admin/containers/survey/survey-list.component.html index dc07404beaeaf1ceb52361a3f880490795d536a7..ed7ebd05540d31a89d083784e216eccf89140438 100644 --- a/client/src/app/admin/containers/survey/survey-list.component.html +++ b/client/src/app/admin/containers/survey/survey-list.component.html @@ -1,7 +1,7 @@ <div class="container-fluid"> <nav aria-label="breadcrumb"> <ol class="breadcrumb"> - <li class="breadcrumb-item active" aria-current="page">Databases</li> + <li class="breadcrumb-item active" aria-current="page">Surveys</li> </ol> </nav> diff --git a/client/src/app/metamodel/store/actions/table.actions.ts b/client/src/app/metamodel/store/actions/table.actions.ts new file mode 100644 index 0000000000000000000000000000000000000000..e9fe06021e58c9d93546d5fdda5108dee5aeb920 --- /dev/null +++ b/client/src/app/metamodel/store/actions/table.actions.ts @@ -0,0 +1,5 @@ +import { createAction, props } from '@ngrx/store'; + +export const loadTableList = createAction('[Metamodel] Load Table List', props<{ idDatabase: number }>()); +export const loadTableListSuccess = createAction('[Metamodel] Load Table List Success', props<{ tables: string[] }>()); +export const loadTableListFail = createAction('[Metamodel] Load Table List Fail'); diff --git a/client/src/app/metamodel/store/effects/index.ts b/client/src/app/metamodel/store/effects/index.ts index dca5034c456e317bd0730c5723c25a482884056f..278871a24804a44d6b0c6b6d4671decb807e5ae1 100644 --- a/client/src/app/metamodel/store/effects/index.ts +++ b/client/src/app/metamodel/store/effects/index.ts @@ -1,4 +1,5 @@ import { DatabaseEffects } from './database.effects'; +import { TableEffects } from './table.effects'; import { SurveyEffects } from './survey.effects'; import { InstanceEffects } from './instance.effects'; import { DatasetFamilyEffects } from './dataset-family.effects'; @@ -12,6 +13,7 @@ import { SelectOptionEffects } from './select-option.effects'; export const metamodelEffects = [ DatabaseEffects, + TableEffects, SurveyEffects, InstanceEffects, DatasetFamilyEffects, diff --git a/client/src/app/metamodel/store/effects/table.effects.ts b/client/src/app/metamodel/store/effects/table.effects.ts new file mode 100644 index 0000000000000000000000000000000000000000..74009b60b1246e0181ed61669455c368c79add65 --- /dev/null +++ b/client/src/app/metamodel/store/effects/table.effects.ts @@ -0,0 +1,28 @@ +import { Injectable } from '@angular/core'; + +import { Actions, createEffect, ofType } from '@ngrx/effects'; +import { of } from 'rxjs'; +import { map, mergeMap, catchError } from 'rxjs/operators'; + +import * as tableActions from '../actions/table.actions'; +import { TableService } from '../services/table.service'; + +@Injectable() +export class TableEffects { + loadTables$ = createEffect(() => + this.actions$.pipe( + ofType(tableActions.loadTableList), + mergeMap(action => this.tableService.retrieveTableList(action.idDatabase) + .pipe( + map(tables => tableActions.loadTableListSuccess({ tables })), + catchError(() => of(tableActions.loadTableListFail())) + ) + ) + ) + ); + + constructor( + private actions$: Actions, + private tableService: TableService + ) {} +} diff --git a/client/src/app/metamodel/store/reducers/attribute.reducer.ts b/client/src/app/metamodel/store/reducers/attribute.reducer.ts index f87fc7a3ec2f2906755aaf7fd14056bd2ebfa7b2..e57fde5a349f37895559259df59eef56b5afb571 100644 --- a/client/src/app/metamodel/store/reducers/attribute.reducer.ts +++ b/client/src/app/metamodel/store/reducers/attribute.reducer.ts @@ -37,7 +37,7 @@ export const attributeReducer = createReducer( on(attributeActions.loadAttributeListFail, (state) => { return { ...state, - attributeListIsLoaded: false + attributeListIsLoading: false } }) ); diff --git a/client/src/app/metamodel/store/reducers/criteria-family.reducer.ts b/client/src/app/metamodel/store/reducers/criteria-family.reducer.ts index 2c08878a2259ac06745d5fd8930887344414585e..ec988dea643ac66f2d9af37d19d2aa0415b46230 100644 --- a/client/src/app/metamodel/store/reducers/criteria-family.reducer.ts +++ b/client/src/app/metamodel/store/reducers/criteria-family.reducer.ts @@ -37,7 +37,7 @@ export const criteriaFamilyReducer = createReducer( on(criteriaFamilyActions.loadCriteriaFamilyListFail, (state) => { return { ...state, - criteriaFamilyListIsLoaded: false + criteriaFamilyListIsLoading: false } }), on(criteriaFamilyActions.addCriteriaFamilySuccess, (state, { criteriaFamily }) => { diff --git a/client/src/app/metamodel/store/reducers/dataset-family.reducer.ts b/client/src/app/metamodel/store/reducers/dataset-family.reducer.ts index 6a9e77700d56a3e80d2fc649ce0ed23732944035..6b087d19bf58ebfc8e85d82fae086d4b2cfeaae8 100644 --- a/client/src/app/metamodel/store/reducers/dataset-family.reducer.ts +++ b/client/src/app/metamodel/store/reducers/dataset-family.reducer.ts @@ -37,7 +37,7 @@ export const datasetFamilyReducer = createReducer( on(datasetFamilyActions.loadDatasetFamilyListFail, (state) => { return { ...state, - datasetFamilyListIsLoaded: false + datasetFamilyListIsLoading: false } }), on(datasetFamilyActions.addDatasetFamilySuccess, (state, { datasetFamily }) => { diff --git a/client/src/app/metamodel/store/reducers/index.ts b/client/src/app/metamodel/store/reducers/index.ts index af7b67e3bebbd3052fd7001bed3e64d5740a1090..a0d1cfb8d7e12a7f3a5ea3c3518cf6f2dbcb86f2 100644 --- a/client/src/app/metamodel/store/reducers/index.ts +++ b/client/src/app/metamodel/store/reducers/index.ts @@ -2,6 +2,7 @@ import { combineReducers, createFeatureSelector } from '@ngrx/store'; import { RouterReducerState } from 'src/app/custom-route-serializer'; import * as database from './database.reducer'; +import * as table from './table.reducer'; import * as survey from './survey.reducer'; import * as group from './group.reducer'; import * as dataset from './dataset.reducer'; @@ -16,6 +17,7 @@ import * as selectOption from './select-option.reducer'; export interface State { database: database.State; + table: table.State; survey: survey.State; group: group.State; dataset: dataset.State; @@ -31,6 +33,7 @@ export interface State { const reducers = { database: database.databaseReducer, + table: table.tableReducer, survey: survey.surveyReducer, group: group.groupReducer, dataset: dataset.datasetReducer, diff --git a/client/src/app/metamodel/store/reducers/instance.reducer.ts b/client/src/app/metamodel/store/reducers/instance.reducer.ts index f125b735b32d11aef510d1953a8191d12103b90b..05c908e018835f5a19d17c867c04c6ea9b4cdf9b 100644 --- a/client/src/app/metamodel/store/reducers/instance.reducer.ts +++ b/client/src/app/metamodel/store/reducers/instance.reducer.ts @@ -40,7 +40,7 @@ export const instanceReducer = createReducer( on(instanceActions.loadInstanceListFail, (state) => { return { ...state, - instanceListIsLoaded: false + instanceListIsLoading: false } }), on(instanceActions.addInstanceSuccess, (state, { instance }) => { diff --git a/client/src/app/metamodel/store/reducers/output-category.reducer.ts b/client/src/app/metamodel/store/reducers/output-category.reducer.ts index d4bd8c85ea061cf6bc4b184c0d452789a13d0b45..188118b0d310d193f3c72612b2ad1bf3705b09aa 100644 --- a/client/src/app/metamodel/store/reducers/output-category.reducer.ts +++ b/client/src/app/metamodel/store/reducers/output-category.reducer.ts @@ -37,7 +37,7 @@ export const outputCategoryReducer = createReducer( on(outputCategoryActions.loadOutputCategoryListFail, (state) => { return { ...state, - outputCategoryListIsLoaded: false + outputCategoryListIsLoading: false } }), on(outputCategoryActions.addOutputCategorySuccess, (state, { outputCategory }) => { diff --git a/client/src/app/metamodel/store/reducers/output-family.reducer.ts b/client/src/app/metamodel/store/reducers/output-family.reducer.ts index 350930bce118ea13dcd60fc42d615d031d828e80..8fc8f608d79b187880135007d114ae1686cd15c8 100644 --- a/client/src/app/metamodel/store/reducers/output-family.reducer.ts +++ b/client/src/app/metamodel/store/reducers/output-family.reducer.ts @@ -37,7 +37,7 @@ export const outputFamilyReducer = createReducer( on(outputFamilyActions.loadOutputFamilyListFail, (state) => { return { ...state, - outputFamilyListIsLoaded: false + outputFamilyListIsLoading: false } }), on(outputFamilyActions.loadOutputFamilyListFail, (state) => { diff --git a/client/src/app/metamodel/store/reducers/select-option.reducer.ts b/client/src/app/metamodel/store/reducers/select-option.reducer.ts index 55eddafac72136614b043c774301b27eaf7d2612..34e3d00967ecd99a2354a609c1503fe3c6654bae 100644 --- a/client/src/app/metamodel/store/reducers/select-option.reducer.ts +++ b/client/src/app/metamodel/store/reducers/select-option.reducer.ts @@ -37,7 +37,7 @@ export const selectOptionReducer = createReducer( on(selectOptionActions.loadSelectOptionListFail, (state) => { return { ...state, - selectOptionListIsLoaded: false + selectOptionListIsLoading: false } }), on(selectOptionActions.addSelectOptionSuccess, (state, { selectOption }) => { diff --git a/client/src/app/metamodel/store/reducers/select.reducer.ts b/client/src/app/metamodel/store/reducers/select.reducer.ts index bef99bfda12b72a2cc6907c7dffd60d6083a41fb..b93ac19f876009eaf0c71439e23e2609e8ba5ff0 100644 --- a/client/src/app/metamodel/store/reducers/select.reducer.ts +++ b/client/src/app/metamodel/store/reducers/select.reducer.ts @@ -40,7 +40,7 @@ export const selectReducer = createReducer( on(selectActions.loadSelectListFail, (state) => { return { ...state, - selectListIsLoaded: false + selectListIsLoading: false } }), on(selectActions.addSelectSuccess, (state, { select }) => { diff --git a/client/src/app/metamodel/store/reducers/survey.reducer.ts b/client/src/app/metamodel/store/reducers/survey.reducer.ts index cb6572c08746b2bde8b90f13eaba680ad860d4b8..17ec0796ba7d4e0345f83d6a1eae5cb80534e86f 100644 --- a/client/src/app/metamodel/store/reducers/survey.reducer.ts +++ b/client/src/app/metamodel/store/reducers/survey.reducer.ts @@ -40,7 +40,7 @@ export const surveyReducer = createReducer( on(surveyActions.loadSurveyListFail, (state) => { return { ...state, - surveyListIsLoaded: false + surveyListIsLoading: false } }), on(surveyActions.addSurveySuccess, (state, { survey }) => { diff --git a/client/src/app/metamodel/store/reducers/table.reducer.ts b/client/src/app/metamodel/store/reducers/table.reducer.ts new file mode 100644 index 0000000000000000000000000000000000000000..d7fe726c3976263e628207afbb3a0b776fc5e883 --- /dev/null +++ b/client/src/app/metamodel/store/reducers/table.reducer.ts @@ -0,0 +1,61 @@ +import { createReducer, on } from '@ngrx/store'; +import { EntityState, EntityAdapter, createEntityAdapter } from '@ngrx/entity'; + +import * as tableActions from '../actions/table.actions'; + +export interface State extends EntityState<string> { + tableListIsLoading: boolean; + tableListIsLoaded: boolean; +} + +export const adapter: EntityAdapter<string> = createEntityAdapter<string>({ + selectId: (table: string) => table, + sortComparer: (a: string, b: string) => a.localeCompare(b) +}); + +export const initialState: State = adapter.getInitialState({ + tableListIsLoading: false, + tableListIsLoaded: false +}); + +export const tableReducer = createReducer( + initialState, + on(tableActions.loadTableList, (state) => { + return { + ...state, + tableListIsLoading: true, + tableListIsLoaded: false + } + }), + on(tableActions.loadTableListSuccess, (state, { tables }) => { + return adapter.setAll( + tables, + { + ...state, + tableListIsLoading: false, + tableListIsLoaded: true + } + ); + }), + on(tableActions.loadTableListFail, (state) => { + return { + ...state, + tableListIsLoading: false + } + }) +); + +const { + selectIds, + selectEntities, + selectAll, + selectTotal, +} = adapter.getSelectors(); + +export const selectTableIds = selectIds; +export const selectTableEntities = selectEntities; +export const selectAllTables = selectAll; +export const selectTableTotal = selectTotal; + +export const selectTableListIsLoading = (state: State) => state.tableListIsLoading; +export const selectTableListIsLoaded = (state: State) => state.tableListIsLoaded; diff --git a/client/src/app/metamodel/store/selectors/table.selector.ts b/client/src/app/metamodel/store/selectors/table.selector.ts new file mode 100644 index 0000000000000000000000000000000000000000..ee4c53ff9fde063008a6e51863da153526abed07 --- /dev/null +++ b/client/src/app/metamodel/store/selectors/table.selector.ts @@ -0,0 +1,39 @@ +import { createSelector } from '@ngrx/store'; + +import * as reducer from '../reducers'; +import * as fromTable from '../reducers/table.reducer'; + +export const selectTableState = createSelector( + reducer.getMetamodelState, + (state: reducer.State) => state.table +); + +export const selectTableIds = createSelector( + selectTableState, + fromTable.selectTableIds +); + +export const selectTableEntities = createSelector( + selectTableState, + fromTable.selectTableEntities +); + +export const selectAllTables = createSelector( + selectTableState, + fromTable.selectAllTables +); + +export const selectTableTotal = createSelector( + selectTableState, + fromTable.selectTableTotal +); + +export const selectTableListIsLoading = createSelector( + selectTableState, + fromTable.selectTableListIsLoading +); + +export const selectTableListIsLoaded = createSelector( + selectTableState, + fromTable.selectTableListIsLoaded +); diff --git a/client/src/app/metamodel/store/services/database.service.ts b/client/src/app/metamodel/store/services/database.service.ts index f06ea28aea130f67e9958eae5b533c87fea8a7f0..74f5ca325e9a2cd2d0f2940743209176d571afec 100644 --- a/client/src/app/metamodel/store/services/database.service.ts +++ b/client/src/app/metamodel/store/services/database.service.ts @@ -15,10 +15,6 @@ export class DatabaseService { retrieveDatabaseList(): Observable<Database[]> { return this.http.get<Database[]>(this.API_PATH + 'database'); } - - retrieveTables(databaseId: number) { - return this.http.get<string[]>(this.API_PATH + 'database/' + databaseId + '/table'); - } retrieveColumns(datasetName: string) { return this.http.get<DbTableColumn[]>(this.API_PATH + 'dataset/' + datasetName + '/column'); diff --git a/client/src/app/metamodel/store/services/index.ts b/client/src/app/metamodel/store/services/index.ts index 3b0afbc6a09106d53933ca258ec0651d4239c1d6..21ab887b1b9ea8fb5390eced3d10f86faf238781 100644 --- a/client/src/app/metamodel/store/services/index.ts +++ b/client/src/app/metamodel/store/services/index.ts @@ -1,4 +1,5 @@ import { DatabaseService } from './database.service'; +import { TableService } from './table.service'; import { SurveyService } from './survey.service'; import { GroupService } from './group.service'; import { DatasetService } from './dataset.service'; @@ -13,6 +14,7 @@ import { SelectOptionService } from './select-option.service'; export const metamodelServices = [ DatabaseService, + TableService, SurveyService, GroupService, DatasetService, diff --git a/client/src/app/metamodel/store/services/table.service.ts b/client/src/app/metamodel/store/services/table.service.ts new file mode 100644 index 0000000000000000000000000000000000000000..8dee2add8abb0a97530b3f25f681253fdc38c515 --- /dev/null +++ b/client/src/app/metamodel/store/services/table.service.ts @@ -0,0 +1,17 @@ +import { Injectable } from '@angular/core'; +import { HttpClient } from '@angular/common/http'; + +import { Observable } from 'rxjs'; + +import { environment } from 'src/environments/environment'; + +@Injectable() +export class TableService { + private API_PATH: string = environment.apiUrl + '/'; + + constructor(private http: HttpClient) { } + + retrieveTableList(idDatabase: number): Observable<string[]> { + return this.http.get<string[]>(this.API_PATH + 'database/' + idDatabase + '/table'); + } +} diff --git a/client/src/styles.scss b/client/src/styles.scss index d59b59e57a7609777f671c1505e92283b3ed848d..4b00647c9a5d7d38a41fd8c5a31bb00845c39189 100644 --- a/client/src/styles.scss +++ b/client/src/styles.scss @@ -14,6 +14,7 @@ @import "~bootstrap/scss/card"; @import "~bootstrap/scss/breadcrumb"; @import "~bootstrap/scss/buttons"; +@import "~bootstrap/scss/button-group"; @import "~bootstrap/scss/transitions"; @import "~bootstrap/scss/dropdown"; @import "~bootstrap/scss/modal"; @@ -33,7 +34,7 @@ main { margin-top: 100px; } -.custom-switch label { +.custom-switch label, .custom-radio label { cursor: pointer; }