diff --git a/package.json b/package.json index b455e945..10d32e78 100644 --- a/package.json +++ b/package.json @@ -5,6 +5,7 @@ "ng": "ng", "postinstall": "patch-package --patch-dir ./node_modules/arlas-web-core/patches && patch-package --patch-dir node_modules/arlas-d3/patches", "start": "ng serve", + "start:iam": "ng serve --ssl --ssl-cert=$EXPLO_SSL_CERT --ssl-key=$EXPLO_SSL_KEY", "build": "ng build --configuration production --aot --base-href='$ARLAS_BUILDER_BASE_HREF/'", "build:stats": "ng build --stats-json", "test": "ng test", diff --git a/src/app/modules/analytics-config/components/edit-resultlist-visualisation/edit-resultlist-visualisation.component.html b/src/app/modules/analytics-config/components/edit-resultlist-visualisation/edit-resultlist-visualisation.component.html new file mode 100644 index 00000000..9b821277 --- /dev/null +++ b/src/app/modules/analytics-config/components/edit-resultlist-visualisation/edit-resultlist-visualisation.component.html @@ -0,0 +1,55 @@ + +
+ @for (visu of visualisation; track visu.value; let visualisationIndex = $index) { +
+
+
+ + +
+
+ drag_indicator + +
+
+ +
+ @for (itemFamily of (visu | castVisualisationItemFamily).controls; track itemFamily; let itemIndex = $index) { +
+
+ drag_indicator + +
+
+ +
+
+ + +
+ +
+ + +
+
+ + } +
+ +
+
+
+ } +
diff --git a/src/app/modules/analytics-config/components/edit-resultlist-visualisation/edit-resultlist-visualisation.component.scss b/src/app/modules/analytics-config/components/edit-resultlist-visualisation/edit-resultlist-visualisation.component.scss new file mode 100644 index 00000000..88923beb --- /dev/null +++ b/src/app/modules/analytics-config/components/edit-resultlist-visualisation/edit-resultlist-visualisation.component.scss @@ -0,0 +1,103 @@ +/* + * Licensed to Gisaïa under one or more contributor + * license agreements. See the NOTICE.txt file distributed with + * this work for additional information regarding copyright + * ownership. Gisaïa licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + + +.result-list-visualisation { + display: flex; + flex-direction: column; + background: white; + padding: 12px 6px; + border-radius: 6px; + margin-top: 12px; + + &_main-meta { + display: flex; + align-items: center; + justify-content: space-between; + } + + &_item-family { + display: flex; + flex-direction: column; + gap: 12px; + } +} + +:host ::ng-deep { + .item-family arlas-config-form-control mat-form-field, + arlas-config-form-control.main-meta_input-desc mat-form-field { + width: 100%!important; + } +} + +.item-family { + display: flex; + flex-direction: column; + gap: 6px; + flex: 1 1 auto; + background: white; + padding: 0 12px; + margin-left: 12px; + + &_header { + flex: 1 1 auto; + display: flex; + background: #fafafa; + align-items: center; + } + + &_section { + display: flex; + gap: 16px; + align-items: center; + } + + &_action { + display: flex; + justify-content: center; + } + + &_inputs { + display: flex; + gap: 12px; + flex: 1 1 auto; + max-width: 70%; + } + + &_input { + flex: 1 1 200px; + max-width: 400px; + } +} +.drag { + &:hover { + cursor: grab; + } +} + +.main-meta { +display: flex; + gap: 12px; + flex: 1 1 auto; + max-width: 840px; + padding-left: 6px; + &_input-desc { + flex: 1 1 400px; + } +} diff --git a/src/app/modules/analytics-config/components/edit-resultlist-visualisation/edit-resultlist-visualisation.component.spec.ts b/src/app/modules/analytics-config/components/edit-resultlist-visualisation/edit-resultlist-visualisation.component.spec.ts new file mode 100644 index 00000000..af826776 --- /dev/null +++ b/src/app/modules/analytics-config/components/edit-resultlist-visualisation/edit-resultlist-visualisation.component.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { EditResultlistVisualisationComponent } from './edit-resultlist-visualisation.component'; + +describe('EditResultlistVisualisationComponent', () => { + let component: EditResultlistVisualisationComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [EditResultlistVisualisationComponent] + }) + .compileComponents(); + + fixture = TestBed.createComponent(EditResultlistVisualisationComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/modules/analytics-config/components/edit-resultlist-visualisation/edit-resultlist-visualisation.component.ts b/src/app/modules/analytics-config/components/edit-resultlist-visualisation/edit-resultlist-visualisation.component.ts new file mode 100644 index 00000000..30f6f3e5 --- /dev/null +++ b/src/app/modules/analytics-config/components/edit-resultlist-visualisation/edit-resultlist-visualisation.component.ts @@ -0,0 +1,78 @@ +import { Component, inject, Input, OnInit } from '@angular/core'; +import { ConfigFormControl, SelectFormControl } from '@shared-models/config-form'; +import { FormArray } from '@angular/forms'; +import { + ResultlistFormBuilderService, + ResultListVisualisationsFormGroup +} from '@analytics-config/services/resultlist-form-builder/resultlist-form-builder.service'; +import { CdkDragDrop, CdkDropList, moveItemInArray } from '@angular/cdk/drag-drop'; +import { TranslateModule } from '@ngx-translate/core'; +import { MatButton, MatIconButton } from '@angular/material/button'; +import { MatIcon } from '@angular/material/icon'; +import { MatTooltip } from '@angular/material/tooltip'; +import { SharedModule } from '@shared/shared.module'; +import { + CastToConfigFormGroupPipe, + CastVisualisationItemFamilyPipe +} from "@shared/pipes/cast-visualisation-item-family.pipe"; + +@Component({ + selector: 'arlas-edit-resultlist-visualisation', + standalone: true, + imports: [ + TranslateModule, + CdkDropList, + MatButton, + MatIcon, + MatIconButton, + MatTooltip, + SharedModule, + CastVisualisationItemFamilyPipe, + CastVisualisationItemFamilyPipe, + CastToConfigFormGroupPipe + ], + templateUrl: './edit-resultlist-visualisation.component.html', + styleUrl: './edit-resultlist-visualisation.component.scss' +}) +export class EditResultlistVisualisationComponent { + + @Input() public collectionControl: SelectFormControl; + @Input() public control: FormArray; + private resultlistFormBuilder = inject(ResultlistFormBuilderService); + + + public get visualisation(): ResultListVisualisationsFormGroup[] { + return this.control? this.control.controls as Array : []; + } + + public constructor() { } + + public removeVisualisation(quicklookIndex: number) { + this.control.removeAt(quicklookIndex); + } + + public dropVisualisation(event: CdkDragDrop) { + const previousIndex = this.control.controls.findIndex(row => row === event.item.data); + moveItemInArray(this.control.controls, previousIndex, event.currentIndex); + } + + public dropItemFamily(event: CdkDragDrop, index: number){ + const previousIndex = (this.control.controls[index].get('itemsFamilies') as FormArray).controls.findIndex(row => row === event.item.data); + moveItemInArray(this.control.controls, previousIndex, event.currentIndex); + } + + public removeItemFamily(index: number, itemIndex: number) { + (this.control.controls[index].get('itemsFamilies') as FormArray).removeAt(itemIndex); + } + + public addVisualisation() { + this.control.push(this.resultlistFormBuilder.buildVisualisation()); + } + + public addItemFamily(index: number) { + console.error(this.resultlistFormBuilder.buildVisualisationsItemFamily(this.collectionControl.value)); + (this.control.controls[index].get('itemsFamilies') as FormArray).push( + this.resultlistFormBuilder.buildVisualisationsItemFamily(this.collectionControl.value) + ); + } +} diff --git a/src/app/modules/analytics-config/services/resultlist-form-builder/resultlist-form-builder.service.ts b/src/app/modules/analytics-config/services/resultlist-form-builder/resultlist-form-builder.service.ts index d4f57a84..c927799f 100644 --- a/src/app/modules/analytics-config/services/resultlist-form-builder/resultlist-form-builder.service.ts +++ b/src/app/modules/analytics-config/services/resultlist-form-builder/resultlist-form-builder.service.ts @@ -48,6 +48,9 @@ import { Router } from '@angular/router'; import { ConfigFormGroupComponent } from '@shared-components/config-form-group/config-form-group.component'; import { ArlasColorGeneratorLoader } from 'arlas-wui-toolkit'; import { CollectionReferenceDescriptionProperty } from 'arlas-api'; +import { + EditResultlistVisualisationComponent +} from '@analytics-config/components/edit-resultlist-visualisation/edit-resultlist-visualisation.component'; export class ResultlistConfigForm extends WidgetConfigFormGroup { @@ -312,6 +315,16 @@ export class ResultlistConfigForm extends WidgetConfigFormGroup { dependsOn: () => [this.customControls.dataStep.collection] } ), + visualisationSection:new ConfigFormGroup({ + visualisationsList: new FormArray([]), + visualisations: new ComponentFormControl( + EditResultlistVisualisationComponent, + { + collectionControl: () => this.customControls.dataStep.collection, + control: () => this.customControls.zactionStep.visualisationSection.visualisationsList + } + ) + }).withTitle(marker('Add a visualization configuration')) }).withTabName(marker('Actions')), unmanagedFields: new FormGroup({ dataStep: new FormGroup({}), @@ -373,7 +386,10 @@ export class ResultlistConfigForm extends WidgetConfigFormGroup { }, zactionStep: { visualisationLink: this.get('zactionStep.visualisationLink') as InputFormControl, - downloadLink: this.get('zactionStep.downloadLink') as InputFormControl + downloadLink: this.get('zactionStep.downloadLink') as InputFormControl, + visualisationSection: { + visualisationsList: this.get('zactionStep.visualisationSection.visualisationsList') as FormArray + } }, unmanagedFields: { dataStep: {}, @@ -693,6 +709,116 @@ export class ResultlistQuicklookFormGroup extends FormGroup { } +export class ResultListVisualisationsFormGroup extends FormGroup { + public constructor(fieldsObs?: Observable>, collection?: string, collectionService?: CollectionService) { + super({ + name: new InputFormControl( + '', + marker('Name'), + marker('Name'), + ), + description: new InputFormControl( + '', + marker('Description'), + marker('Description'), + ), + itemsFamilies: new FormArray([]) + }); + } + + public customControls = { + name: this.get('name') as InputFormControl, + description: this.get('description') as TextareaFormControl, + itemsFamilies: this.get('itemsFamilies') as FormArray + }; +} + +export class ResultListVisualisationsItemFamily extends FormGroup { + public constructor(fieldsObs?: Observable>, collection?: string, collectionService?: CollectionService) { + super({ + itemsFamily: new InputFormControl( + '', + marker('Item family name'), + '' + ), + filter: new ConfigFormGroup({ + field: new SelectFormControl( + '', + marker('Condition fields'), + marker('Condition fields'), + true, + toOptionsObs(fieldsObs), + { + optional: true + } + ), + values: new MultipleSelectFormControl( + // Mark as invalid if there is a value on filterField and not there + '', + marker('Condition values'), + marker('Condition values description'), + false, + [], + { + optional: true, + dependsOn: () => [this.customControls.filter.field], + onDependencyChange: (control: MultipleSelectFormControl) => { + if (!this.customControls.filter.field.touched) { + // Avoid to reset the imported configuration when first loading it + } else if (this.customControls.filter.field.value !== '' && !!this.customControls.filter.field.syncOptions + && this.customControls.filter.field.syncOptions.map(f => f.value).includes(this.customControls.filter.field.value)) { + control.setSyncOptions([]); + collectionService.getTermAggregation( + collection, + this.customControls.filter.field.value) + .then(keywords => { + control.setSyncOptions(keywords.map(k => ({ value: k, label: k }))); + }); + } else { + control.setSyncOptions([]); + } + control.markAsUntouched(); + } + }, + false + ) + }), + protocol: new SelectFormControl( + '', + marker('Protocol'), + marker('Protocol'), + false, + [ + { label: marker('Titiler'), value: 'titiler' }, + { label: marker('Other'), value: 'other' }, + ], + ), + visualisationUrl: new InputFormControl( + '', + marker('View URL'), + '', + 'text', + { + validators: [Validators.pattern('^(http|https)\:\/\/.*')] + } + ), + }); + } + + public customControls = { + itemsFamily: this.get('itemsFamily') as InputFormControl, + protocol: this.get('protocol') as SelectFormControl, + filter: { + field: this.get('filter.field') as SelectFormControl, + values: this.get('filter.values') as MultipleSelectFormControl + }, + visualisationUrl: this.get('visualisationUrl') as InputFormControl + }; +} + + + + @Injectable({ providedIn: 'root' }) @@ -771,4 +897,17 @@ export class ResultlistFormBuilderService extends WidgetFormBuilder { return control; } + public buildVisualisation() { + return new ResultListVisualisationsFormGroup(); + } + + public buildVisualisationsItemFamily(collection: string) { + const fieldObs = this.collectionService.getCollectionFields(collection, TEXT_OR_KEYWORD); + const control = new ResultListVisualisationsItemFamily(fieldObs, + collection, + this.collectionService); + ConfigFormGroupComponent.listenToAllControlsOnDependencyChange(control.get('filter') as ConfigFormGroup, []); + return control; + } + } diff --git a/src/app/modules/result-list-config/services/result-list-import/result-list-import.service.ts b/src/app/modules/result-list-config/services/result-list-import/result-list-import.service.ts index 90b71adc..a0069e80 100644 --- a/src/app/modules/result-list-config/services/result-list-import/result-list-import.service.ts +++ b/src/app/modules/result-list-config/services/result-list-import/result-list-import.service.ts @@ -152,6 +152,7 @@ export class ResultListImportService { control: renderStep.cellBackgroundStyle } ]); + if (contributor.fieldsConfiguration.urlImageTemplate) { const quicklook = this.resultlistFormBuilder.buildQuicklook(contributor.collection); importElements([ diff --git a/src/app/services/main-form-manager/models-config.ts b/src/app/services/main-form-manager/models-config.ts index 21fd8e26..2e6b241b 100644 --- a/src/app/services/main-form-manager/models-config.ts +++ b/src/app/services/main-form-manager/models-config.ts @@ -306,6 +306,7 @@ export interface AnalyticComponentInputConfig { } export interface AnalyticComponentResultListInputConfig extends AnalyticComponentInputConfig { + visualisationsList?: []; options?: AnalyticComponentResultListInputOptions; detailWidth?: number; visualisationLink?: string; diff --git a/src/app/shared/components/config-form-group/config-form-group.component.html b/src/app/shared/components/config-form-group/config-form-group.component.html index c5c7212c..317aa872 100644 --- a/src/app/shared/components/config-form-group/config-form-group.component.html +++ b/src/app/shared/components/config-form-group/config-form-group.component.html @@ -94,4 +94,4 @@

{{formControl.title | t - \ No newline at end of file + diff --git a/src/app/shared/pipes/cast-visualisation-item-family.pipe.ts b/src/app/shared/pipes/cast-visualisation-item-family.pipe.ts new file mode 100644 index 00000000..6aa59280 --- /dev/null +++ b/src/app/shared/pipes/cast-visualisation-item-family.pipe.ts @@ -0,0 +1,29 @@ +import { Pipe, PipeTransform } from '@angular/core'; +import { + ResultListVisualisationsFormGroup, ResultListVisualisationsItemFamily +} from '@analytics-config/services/resultlist-form-builder/resultlist-form-builder.service'; +import { FormArray } from '@angular/forms'; +import { ConfigFormGroup } from '@shared-models/config-form'; + +@Pipe({ + name: 'castVisualisationItemFamily', + standalone: true +}) +export class CastVisualisationItemFamilyPipe implements PipeTransform { + + public transform(item: ResultListVisualisationsFormGroup) { + return item.get('itemsFamilies') as FormArray; + } + +} + +@Pipe({ + name: 'castToConfigFormGroup', + standalone: true +}) +export class CastToConfigFormGroupPipe implements PipeTransform { + + public transform(item: any) { + return item as ConfigFormGroup; + } +} diff --git a/src/assets/i18n/en.json b/src/assets/i18n/en.json index 8471a762..ea3895c6 100644 --- a/src/assets/i18n/en.json +++ b/src/assets/i18n/en.json @@ -911,5 +911,11 @@ "OWNER_GROUP_TITLE": "Collections of {{org}}", "SHARED_GROUP_TITLE": "Collections shared with {{org}}", "PUBLIC_GROUP_TITLE": "Public collections", - "COLLECTIONS_GROUP_TITLE": "Collections" + "COLLECTIONS_GROUP_TITLE": "Collections", + "View URL": "View URL", + "Protocol": "Protocol", + "Item family name": "Item family name", + "Add a condition": "Add a condition", + "Add a visualization configuration": "Add a visualization configuration", + "Add a visualization": "Add a visualization" } diff --git a/src/assets/i18n/fr.json b/src/assets/i18n/fr.json index 128e15af..913cfb39 100644 --- a/src/assets/i18n/fr.json +++ b/src/assets/i18n/fr.json @@ -910,5 +910,11 @@ "OWNER_GROUP_TITLE": "Collections appartenant à {{org}}", "SHARED_GROUP_TITLE": "Collections partagées avec {{org}}", "PUBLIC_GROUP_TITLE": "Collections publiques", - "COLLECTIONS_GROUP_TITLE": "Collections" + "COLLECTIONS_GROUP_TITLE": "Collections", + "View URL": "URL de la visualisation", + "Protocol": "Protocole", + "Item family name": " Nom de famille d'item", + "Add a condition": "Ajouter une condition", + "Add a visualization configuration": "Ajouter une configuration de visualisation" + "Add a visualization": "Ajouter une visualisation" }