From 9039b55ab749ed6e3ba97c1756788729bc33abdc Mon Sep 17 00:00:00 2001 From: infacc Date: Thu, 27 Jul 2023 17:09:09 +0200 Subject: [PATCH 01/14] show/change experiment default template in UI --- .../experiment-workspace-detail.component.ts | 2 +- .../experiment/experiment.component.html | 12 ++++++++- .../experiment/experiment.component.ts | 26 ++++++++++++++++++- .../services/current-experiment.service.ts | 3 +-- src/app/services/qhana-backend.service.ts | 21 +++++++++++++++ src/app/services/templates.service.ts | 15 ++++++++--- 6 files changed, 71 insertions(+), 8 deletions(-) diff --git a/src/app/components/experiment-workspace-detail/experiment-workspace-detail.component.ts b/src/app/components/experiment-workspace-detail/experiment-workspace-detail.component.ts index 238e91d..3d11c31 100644 --- a/src/app/components/experiment-workspace-detail/experiment-workspace-detail.component.ts +++ b/src/app/components/experiment-workspace-detail/experiment-workspace-detail.component.ts @@ -203,7 +203,7 @@ export class ExperimentWorkspaceDetailComponent implements OnInit { } private async updateTemplateId(templateId: string | null) { - if (templateId == null) { + if (templateId == null || templateId === "") { return; // no template selected } if (templateId === this.templateId) { diff --git a/src/app/components/experiment/experiment.component.html b/src/app/components/experiment/experiment.component.html index 9487b82..8c0c5e5 100644 --- a/src/app/components/experiment/experiment.component.html +++ b/src/app/components/experiment/experiment.component.html @@ -32,11 +32,21 @@

Experiment

+ + Default Template + + + None + + + {{template.value}} + + + -
diff --git a/src/app/components/experiment/experiment.component.ts b/src/app/components/experiment/experiment.component.ts index c8803ca..eb2bc27 100644 --- a/src/app/components/experiment/experiment.component.ts +++ b/src/app/components/experiment/experiment.component.ts @@ -4,8 +4,11 @@ import { ActivatedRoute, Router } from '@angular/router'; import { BehaviorSubject, Subscription } from 'rxjs'; import { debounceTime, filter, map } from 'rxjs/operators'; import { ExportExperimentDialog } from 'src/app/dialogs/export-experiment/export-experiment.component'; +import { PageApiObject } from 'src/app/services/api-data-types'; import { CurrentExperimentService } from 'src/app/services/current-experiment.service'; import { ExperimentApiObject, QhanaBackendService } from 'src/app/services/qhana-backend.service'; +import { PluginRegistryBaseService } from 'src/app/services/registry.service'; +import { TemplatesService } from 'src/app/services/templates.service'; @Component({ selector: 'qhana-experiment', @@ -34,7 +37,10 @@ export class ExperimentComponent implements OnInit, OnDestroy { experimentDescription: string = ""; // only updated on initial experiment load currentExperimentDescription: string = ""; - constructor(private route: ActivatedRoute, private router: Router, private experimentService: CurrentExperimentService, private backend: QhanaBackendService, public dialog: MatDialog) { } + experimentTemplates: { [id: string]: string } = {}; + experimentTemplateId: string | number | null = null; + + constructor(private route: ActivatedRoute, private router: Router, private experimentService: CurrentExperimentService, private backend: QhanaBackendService, private registry: PluginRegistryBaseService, private templates: TemplatesService, public dialog: MatDialog) { } ngOnInit(): void { this.routeSubscription = this.route.params.pipe(map(params => params.experimentId)).subscribe(experimentId => { @@ -48,6 +54,7 @@ export class ExperimentComponent implements OnInit, OnDestroy { this.lastSavedDescription = experiment?.description ?? ""; this.experimentDescription = experiment?.description ?? ""; this.currentExperimentDescription = experiment?.description ?? ""; + this.experimentTemplateId = experiment?.templateId ?? null; }); this.autoSaveTitleSubscription = this.titleUpdates.pipe( filter(value => value != null && value !== this.lastSavedTitle), @@ -57,6 +64,15 @@ export class ExperimentComponent implements OnInit, OnDestroy { filter(value => value != null && value !== this.lastSavedDescription), debounceTime(500) ).subscribe(this.saveDescription); + this.registry.getByRel([["ui-template", "collection"]]).then(result => { + result?.data.items.forEach(item => { + const templateId = item.resourceKey?.uiTemplateId; + const name = item.name; + if (templateId != null && name != null) { + this.experimentTemplates[templateId] = name; + } + }); + }); } ngOnDestroy(): void { @@ -156,4 +172,12 @@ export class ExperimentComponent implements OnInit, OnDestroy { }); } + async changeDefaultTemplate(templateId: string | null) { + if (this.experimentId == null) { + return; + } + this.experimentTemplateId = templateId; + await this.templates.setDefaultTemplate(this.experimentId, templateId); + this.experimentService.reloadExperiment(); + } } diff --git a/src/app/services/current-experiment.service.ts b/src/app/services/current-experiment.service.ts index 4228a10..d73c4e0 100644 --- a/src/app/services/current-experiment.service.ts +++ b/src/app/services/current-experiment.service.ts @@ -64,7 +64,7 @@ export class CurrentExperimentService { this.currentExperimentId.next(experimentId); } const current = this.currentExperiment.getValue(); - if (current != experiment || current?.name !== experiment?.name || current?.description !== experiment?.description) { + if (current != experiment || current?.name !== experiment?.name || current?.description !== experiment?.description || current?.templateId !== experiment?.templateId) { this.currentExperiment.next(experiment); } } @@ -81,5 +81,4 @@ export class CurrentExperimentService { this.updateCurrentExperiment(experimentId); } } - } diff --git a/src/app/services/qhana-backend.service.ts b/src/app/services/qhana-backend.service.ts index 6d2e9f3..19baa25 100644 --- a/src/app/services/qhana-backend.service.ts +++ b/src/app/services/qhana-backend.service.ts @@ -177,6 +177,15 @@ export interface TimelineStepPageOptions { unclearedSubstep?: number; } +export interface TemplateApiObject extends ApiObject { + templateId: string; +} + +export interface TemplatePostResponseApiObject extends ApiObject { + experimentId: number; + templateId?: string; +} + function urlIsString(url: string | null): url is string { return url != null; } @@ -491,4 +500,16 @@ export class QhanaBackendService { rootUrl => this.http.get(`${rootUrl}/experiments/${experimentId}/timeline/${step}/substeps/${substep}`) ); } + + public getExperimentDefaultTemplate(experimentId: number | string): Observable { + return this.callWithRootUrl( + rootUrl => this.http.get(`${rootUrl}/experiments/${experimentId}/template`) + ); + } + + public updateExperimentDefaultTemplate(experimentId: number | string, templateId: string | number | null): Observable { + return this.callWithRootUrl( + rootUrl => this.http.post(`${rootUrl}/experiments/${experimentId}/template`, { templateId: templateId }) + ); + } } diff --git a/src/app/services/templates.service.ts b/src/app/services/templates.service.ts index fb27dfd..dfcb4ee 100644 --- a/src/app/services/templates.service.ts +++ b/src/app/services/templates.service.ts @@ -20,8 +20,9 @@ import { ApiLink, ApiObject, PageApiObject } from './api-data-types'; import { CurrentExperimentService } from './current-experiment.service'; import { PluginRegistryBaseService } from './registry.service'; import { EnvService } from './env.service'; -import { distinctUntilChanged } from 'rxjs/operators'; +import { distinctUntilChanged, take } from 'rxjs/operators'; import { ActivatedRoute } from '@angular/router'; +import { QhanaBackendService } from './qhana-backend.service'; export interface TemplateApiObject extends ApiObject { // TODO check fields @@ -104,7 +105,7 @@ export class TemplatesService { return this.currentTemplateSubject.asObservable(); } - constructor(private registry: PluginRegistryBaseService, private env: EnvService, private currentExperiment: CurrentExperimentService, private route: ActivatedRoute) { + constructor(private registry: PluginRegistryBaseService, private env: EnvService, private currentExperiment: CurrentExperimentService, private backend: QhanaBackendService, private route: ActivatedRoute) { this.envSubscription = env.uiTemplateId.subscribe((defaultTemplateId) => { this.envTemplateIdSubject.next(defaultTemplateId); }); @@ -200,7 +201,7 @@ export class TemplatesService { } private async updateTemplate(templateId: string | null, subject: BehaviorSubject) { - if (templateId == null) { + if (templateId == null || templateId === "") { subject.next(null); return; } @@ -231,4 +232,12 @@ export class TemplatesService { const templateResponse = await this.getTemplate(templateId, ignoreCache); return templateResponse?.data?.groups ?? []; } + + async setDefaultTemplate(experimentId: string, templateId: string | null) { + this.backend.updateExperimentDefaultTemplate(experimentId, templateId).pipe(take(1)).subscribe( + response => { + this.defaultTemplateIdSubject.next(response?.templateId ?? null); + } + ); + } } From 6346a35d5b36d5402cb7fd941037922f3750b11e Mon Sep 17 00:00:00 2001 From: infacc Date: Thu, 27 Jul 2023 17:29:02 +0200 Subject: [PATCH 02/14] add default template getter to experiment service --- src/app/services/current-experiment.service.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/app/services/current-experiment.service.ts b/src/app/services/current-experiment.service.ts index d73c4e0..73e06aa 100644 --- a/src/app/services/current-experiment.service.ts +++ b/src/app/services/current-experiment.service.ts @@ -45,6 +45,10 @@ export class CurrentExperimentService { return this.currentExperiment.asObservable().pipe(map(experiment => experiment?.description ?? null)); } + get experimentTemplateId() { + return this.currentExperiment.asObservable().pipe(map(experiment => experiment?.templateId ?? null)); + } + constructor(private backend: QhanaBackendService) { } private updateCurrentExperiment(experimentId: string | null) { From e73caa5565698641666e4ef0fb6a28ad55dc8270 Mon Sep 17 00:00:00 2001 From: infacc Date: Mon, 31 Jul 2023 11:37:49 +0200 Subject: [PATCH 03/14] refactor: show/change experiment default template --- .../experiment/experiment.component.html | 4 ++-- .../experiment/experiment.component.ts | 16 ++++++++++------ src/app/services/templates.service.ts | 4 ++-- 3 files changed, 14 insertions(+), 10 deletions(-) diff --git a/src/app/components/experiment/experiment.component.html b/src/app/components/experiment/experiment.component.html index 8c0c5e5..1a51bfe 100644 --- a/src/app/components/experiment/experiment.component.html +++ b/src/app/components/experiment/experiment.component.html @@ -34,11 +34,11 @@

Experiment

Default Template - + None - + {{template.value}} diff --git a/src/app/components/experiment/experiment.component.ts b/src/app/components/experiment/experiment.component.ts index eb2bc27..f39bb1f 100644 --- a/src/app/components/experiment/experiment.component.ts +++ b/src/app/components/experiment/experiment.component.ts @@ -27,6 +27,7 @@ export class ExperimentComponent implements OnInit, OnDestroy { private descriptionUpdates: BehaviorSubject = new BehaviorSubject(null); private autoSaveTitleSubscription: Subscription | null = null; private autoSaveDescriptionSubscription: Subscription | null = null; + private uiTemplateIdSubscription: Subscription | null = null; updateStatus: "original" | "changed" | "saved" = "original"; @@ -37,8 +38,8 @@ export class ExperimentComponent implements OnInit, OnDestroy { experimentDescription: string = ""; // only updated on initial experiment load currentExperimentDescription: string = ""; - experimentTemplates: { [id: string]: string } = {}; - experimentTemplateId: string | number | null = null; + uiTemplates: Map = new Map(); + uiTemplateId: string | number | null = null; constructor(private route: ActivatedRoute, private router: Router, private experimentService: CurrentExperimentService, private backend: QhanaBackendService, private registry: PluginRegistryBaseService, private templates: TemplatesService, public dialog: MatDialog) { } @@ -54,7 +55,10 @@ export class ExperimentComponent implements OnInit, OnDestroy { this.lastSavedDescription = experiment?.description ?? ""; this.experimentDescription = experiment?.description ?? ""; this.currentExperimentDescription = experiment?.description ?? ""; - this.experimentTemplateId = experiment?.templateId ?? null; + this.uiTemplateId = experiment?.templateId ?? null; + }); + this.uiTemplateIdSubscription = this.experimentService.experimentTemplateId.subscribe(templateId => { + this.uiTemplateId = templateId; }); this.autoSaveTitleSubscription = this.titleUpdates.pipe( filter(value => value != null && value !== this.lastSavedTitle), @@ -69,7 +73,7 @@ export class ExperimentComponent implements OnInit, OnDestroy { const templateId = item.resourceKey?.uiTemplateId; const name = item.name; if (templateId != null && name != null) { - this.experimentTemplates[templateId] = name; + this.uiTemplates.set(templateId, name); } }); }); @@ -80,6 +84,7 @@ export class ExperimentComponent implements OnInit, OnDestroy { this.experimentSubscription?.unsubscribe(); this.autoSaveTitleSubscription?.unsubscribe(); this.autoSaveDescriptionSubscription?.unsubscribe(); + this.uiTemplateIdSubscription?.unsubscribe(); } cloneExperiment() { @@ -176,8 +181,7 @@ export class ExperimentComponent implements OnInit, OnDestroy { if (this.experimentId == null) { return; } - this.experimentTemplateId = templateId; - await this.templates.setDefaultTemplate(this.experimentId, templateId); + await this.templates.setExperimentDefaultTemplate(this.experimentId, templateId); this.experimentService.reloadExperiment(); } } diff --git a/src/app/services/templates.service.ts b/src/app/services/templates.service.ts index dfcb4ee..3191a64 100644 --- a/src/app/services/templates.service.ts +++ b/src/app/services/templates.service.ts @@ -233,10 +233,10 @@ export class TemplatesService { return templateResponse?.data?.groups ?? []; } - async setDefaultTemplate(experimentId: string, templateId: string | null) { + async setExperimentDefaultTemplate(experimentId: string, templateId: string | null) { this.backend.updateExperimentDefaultTemplate(experimentId, templateId).pipe(take(1)).subscribe( response => { - this.defaultTemplateIdSubject.next(response?.templateId ?? null); + this.experimentTemplateIdSubject.next(response?.templateId ?? null); } ); } From f6b830ca64462f9698eb7d269ecae19dedfe1e39 Mon Sep 17 00:00:00 2001 From: infacc Date: Mon, 31 Jul 2023 19:26:37 +0200 Subject: [PATCH 04/14] paginate experiment default template selection --- .../experiment/experiment.component.html | 12 +++- .../experiment/experiment.component.ts | 57 ++++++++++++++----- src/app/services/registry.service.ts | 8 ++- 3 files changed, 61 insertions(+), 16 deletions(-) diff --git a/src/app/components/experiment/experiment.component.html b/src/app/components/experiment/experiment.component.html index 1a51bfe..6bf7aea 100644 --- a/src/app/components/experiment/experiment.component.html +++ b/src/app/components/experiment/experiment.component.html @@ -35,10 +35,18 @@

Experiment

Default Template + + + - None + -- none -- - + + {{uiTemplateName}} + + + {{template.value}} diff --git a/src/app/components/experiment/experiment.component.ts b/src/app/components/experiment/experiment.component.ts index f39bb1f..8d4e22a 100644 --- a/src/app/components/experiment/experiment.component.ts +++ b/src/app/components/experiment/experiment.component.ts @@ -1,3 +1,4 @@ +import { KeyValue } from '@angular/common'; import { Component, OnDestroy, OnInit } from '@angular/core'; import { MatDialog } from '@angular/material/dialog'; import { ActivatedRoute, Router } from '@angular/router'; @@ -16,7 +17,6 @@ import { TemplatesService } from 'src/app/services/templates.service'; styleUrls: ['./experiment.component.sass'] }) export class ExperimentComponent implements OnInit, OnDestroy { - private routeSubscription: Subscription | null = null; private experimentSubscription: Subscription | null = null; @@ -38,8 +38,13 @@ export class ExperimentComponent implements OnInit, OnDestroy { experimentDescription: string = ""; // only updated on initial experiment load currentExperimentDescription: string = ""; + readonly itemCount: number = 10; + uiTemplatePageIndex: number = 0; + uiTemplates: Map = new Map(); - uiTemplateId: string | number | null = null; + uiTemplateId: string | null = null; + uiTemplateName: string | null = null; + uiTemplateCollectionSize: number = 0; constructor(private route: ActivatedRoute, private router: Router, private experimentService: CurrentExperimentService, private backend: QhanaBackendService, private registry: PluginRegistryBaseService, private templates: TemplatesService, public dialog: MatDialog) { } @@ -55,10 +60,17 @@ export class ExperimentComponent implements OnInit, OnDestroy { this.lastSavedDescription = experiment?.description ?? ""; this.experimentDescription = experiment?.description ?? ""; this.currentExperimentDescription = experiment?.description ?? ""; - this.uiTemplateId = experiment?.templateId ?? null; + this.uiTemplateId = experiment?.templateId?.toString() ?? null; }); this.uiTemplateIdSubscription = this.experimentService.experimentTemplateId.subscribe(templateId => { - this.uiTemplateId = templateId; + this.uiTemplateId = templateId?.toString() ?? null; + this.getTemplatePage(); + if (this.uiTemplateId == null) { + return; + } + this.templates.getTemplate(this.uiTemplateId).then(template => { + this.uiTemplateName = template?.data.name ?? null; + }); }); this.autoSaveTitleSubscription = this.titleUpdates.pipe( filter(value => value != null && value !== this.lastSavedTitle), @@ -68,15 +80,6 @@ export class ExperimentComponent implements OnInit, OnDestroy { filter(value => value != null && value !== this.lastSavedDescription), debounceTime(500) ).subscribe(this.saveDescription); - this.registry.getByRel([["ui-template", "collection"]]).then(result => { - result?.data.items.forEach(item => { - const templateId = item.resourceKey?.uiTemplateId; - const name = item.name; - if (templateId != null && name != null) { - this.uiTemplates.set(templateId, name); - } - }); - }); } ngOnDestroy(): void { @@ -179,9 +182,37 @@ export class ExperimentComponent implements OnInit, OnDestroy { async changeDefaultTemplate(templateId: string | null) { if (this.experimentId == null) { + this.uiTemplatePageIndex = 0; + this.uiTemplateId = null; + this.uiTemplateName = null; return; } + this.uiTemplateName = this.uiTemplates.get(templateId ?? "") ?? null; await this.templates.setExperimentDefaultTemplate(this.experimentId, templateId); this.experimentService.reloadExperiment(); } + + async getTemplatePage(pageIndex: number = 0) { + const uiTemplates = new Map(); + const params = new URLSearchParams(); + params.set("sort", "name"); + params.set("item-count", this.itemCount.toString()); + params.set("cursor", (pageIndex + 1).toString()); + await this.registry.getByRel([["ui-template", "collection"]], params).then(result => { + this.uiTemplateCollectionSize = result?.data.collectionSize ?? 0; + result?.data.items.forEach(item => { + const templateId = item.resourceKey?.uiTemplateId; + const name = item.name; + if (templateId != null && name != null) { + uiTemplates.set(templateId, name); + } + }); + }); + this.uiTemplates = uiTemplates; + this.uiTemplatePageIndex = pageIndex; + } + + originalOrder = (a: KeyValue, b: KeyValue): number => { + return 0; + } } diff --git a/src/app/services/registry.service.ts b/src/app/services/registry.service.ts index fd0ee59..fb0687e 100644 --- a/src/app/services/registry.service.ts +++ b/src/app/services/registry.service.ts @@ -483,7 +483,13 @@ export class PluginRegistryBaseService { public async getByApiLink(link: ApiLink, searchParams: URLSearchParams | null = null, ignoreCache: boolean | "ignore-embedded" = false): Promise | null> { const url = new URL(link.href) - searchParams?.forEach((value, key) => url.searchParams.append(key, value)); + searchParams?.forEach((value, key) => { + if (url.searchParams.has(key)) { + url.searchParams.set(key, value); + } else { + url.searchParams.append(key, value); + } + }); return await this._fetch>(url.toString(), ignoreCache); } From 8349b5f5dd28e79d85b25ddc2edb5fa0d2c68120 Mon Sep 17 00:00:00 2001 From: infacc Date: Tue, 1 Aug 2023 14:30:18 +0200 Subject: [PATCH 05/14] refactor: show/change experiment default template --- src/app/app.module.ts | 2 + .../experiment/experiment.component.html | 38 +++++------ .../experiment/experiment.component.sass | 12 ++++ .../experiment/experiment.component.ts | 68 ++++++------------- .../choose-template.component.html | 15 ++++ .../choose-template.component.sass | 0 .../choose-template.component.spec.ts | 23 +++++++ .../choose-template.component.ts | 40 +++++++++++ 8 files changed, 131 insertions(+), 67 deletions(-) create mode 100644 src/app/dialogs/choose-template/choose-template.component.html create mode 100644 src/app/dialogs/choose-template/choose-template.component.sass create mode 100644 src/app/dialogs/choose-template/choose-template.component.spec.ts create mode 100644 src/app/dialogs/choose-template/choose-template.component.ts diff --git a/src/app/app.module.ts b/src/app/app.module.ts index b6fe4e0..5489021 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -86,6 +86,7 @@ import { ExperimentWorkspaceDetailComponent } from './components/experiment-work import { PluginFilterNodeComponent } from './components-small/plugin-filter-node/plugin-filter-node.component'; import { PluginFilterEditorComponent } from './components-small/plugin-filter-editor/plugin-filter-editor.component'; import { TabGroupListComponent } from './components/tab-group-list/tab-group-list.component'; +import { ChooseTemplateComponent } from './dialogs/choose-template/choose-template.component'; @NgModule({ declarations: [ @@ -131,6 +132,7 @@ import { TabGroupListComponent } from './components/tab-group-list/tab-group-lis PluginFilterNodeComponent, PluginFilterEditorComponent, TabGroupListComponent, + ChooseTemplateComponent, ], imports: [ BrowserModule, diff --git a/src/app/components/experiment/experiment.component.html b/src/app/components/experiment/experiment.component.html index 6bf7aea..f832857 100644 --- a/src/app/components/experiment/experiment.component.html +++ b/src/app/components/experiment/experiment.component.html @@ -31,26 +31,24 @@

Experiment

cancel - - - Default Template - - - - - - -- none -- - - - {{uiTemplateName}} - - - - {{template.value}} - - - + +
+ + Default Template: + + + {{uiTemplate?.name ?? 'All Plugins'}} + + + + +
diff --git a/src/app/components/experiment/experiment.component.sass b/src/app/components/experiment/experiment.component.sass index edfdfeb..723a618 100644 --- a/src/app/components/experiment/experiment.component.sass +++ b/src/app/components/experiment/experiment.component.sass @@ -36,3 +36,15 @@ display: inline-flex align-items: center gap: 0.5em + +.experiment-settings + display: flex + flex-direction: column + gap: 1rem + +.default-template-selection + display: flex + flex-direction: row + justify-content: flex-start + align-items: center + gap: 1rem \ No newline at end of file diff --git a/src/app/components/experiment/experiment.component.ts b/src/app/components/experiment/experiment.component.ts index 8d4e22a..ff4d709 100644 --- a/src/app/components/experiment/experiment.component.ts +++ b/src/app/components/experiment/experiment.component.ts @@ -1,15 +1,13 @@ -import { KeyValue } from '@angular/common'; import { Component, OnDestroy, OnInit } from '@angular/core'; import { MatDialog } from '@angular/material/dialog'; import { ActivatedRoute, Router } from '@angular/router'; import { BehaviorSubject, Subscription } from 'rxjs'; import { debounceTime, filter, map } from 'rxjs/operators'; import { ExportExperimentDialog } from 'src/app/dialogs/export-experiment/export-experiment.component'; -import { PageApiObject } from 'src/app/services/api-data-types'; +import { ChooseTemplateComponent } from 'src/app/dialogs/choose-template/choose-template.component'; import { CurrentExperimentService } from 'src/app/services/current-experiment.service'; import { ExperimentApiObject, QhanaBackendService } from 'src/app/services/qhana-backend.service'; -import { PluginRegistryBaseService } from 'src/app/services/registry.service'; -import { TemplatesService } from 'src/app/services/templates.service'; +import { TemplateApiObject, TemplatesService } from 'src/app/services/templates.service'; @Component({ selector: 'qhana-experiment', @@ -38,15 +36,9 @@ export class ExperimentComponent implements OnInit, OnDestroy { experimentDescription: string = ""; // only updated on initial experiment load currentExperimentDescription: string = ""; - readonly itemCount: number = 10; - uiTemplatePageIndex: number = 0; + uiTemplate: TemplateApiObject | null = null; - uiTemplates: Map = new Map(); - uiTemplateId: string | null = null; - uiTemplateName: string | null = null; - uiTemplateCollectionSize: number = 0; - - constructor(private route: ActivatedRoute, private router: Router, private experimentService: CurrentExperimentService, private backend: QhanaBackendService, private registry: PluginRegistryBaseService, private templates: TemplatesService, public dialog: MatDialog) { } + constructor(private route: ActivatedRoute, private router: Router, private experimentService: CurrentExperimentService, private backend: QhanaBackendService, private templates: TemplatesService, public dialog: MatDialog) { } ngOnInit(): void { this.routeSubscription = this.route.params.pipe(map(params => params.experimentId)).subscribe(experimentId => { @@ -60,16 +52,14 @@ export class ExperimentComponent implements OnInit, OnDestroy { this.lastSavedDescription = experiment?.description ?? ""; this.experimentDescription = experiment?.description ?? ""; this.currentExperimentDescription = experiment?.description ?? ""; - this.uiTemplateId = experiment?.templateId?.toString() ?? null; }); this.uiTemplateIdSubscription = this.experimentService.experimentTemplateId.subscribe(templateId => { - this.uiTemplateId = templateId?.toString() ?? null; - this.getTemplatePage(); - if (this.uiTemplateId == null) { + if (templateId == null) { + this.uiTemplate = null; return; } - this.templates.getTemplate(this.uiTemplateId).then(template => { - this.uiTemplateName = template?.data.name ?? null; + this.templates.getTemplate(templateId.toString()).then(templateResponse => { + this.uiTemplate = templateResponse?.data ?? null; }); }); this.autoSaveTitleSubscription = this.titleUpdates.pipe( @@ -180,39 +170,23 @@ export class ExperimentComponent implements OnInit, OnDestroy { }); } - async changeDefaultTemplate(templateId: string | null) { + showSelectDefaultTemplateDialog() { + const dialogRef = this.dialog.open(ChooseTemplateComponent, { + minWidth: "20rem", maxWidth: "40rem", width: "60%", + }); + dialogRef.afterClosed().subscribe(templateId => { + if (templateId != null) { + this.updateExperimentDefaultTemplate(templateId); + } + }); + } + + async updateExperimentDefaultTemplate(templateId: string | null) { if (this.experimentId == null) { - this.uiTemplatePageIndex = 0; - this.uiTemplateId = null; - this.uiTemplateName = null; + console.warn("Experiment ID is null!"); return; } - this.uiTemplateName = this.uiTemplates.get(templateId ?? "") ?? null; await this.templates.setExperimentDefaultTemplate(this.experimentId, templateId); this.experimentService.reloadExperiment(); } - - async getTemplatePage(pageIndex: number = 0) { - const uiTemplates = new Map(); - const params = new URLSearchParams(); - params.set("sort", "name"); - params.set("item-count", this.itemCount.toString()); - params.set("cursor", (pageIndex + 1).toString()); - await this.registry.getByRel([["ui-template", "collection"]], params).then(result => { - this.uiTemplateCollectionSize = result?.data.collectionSize ?? 0; - result?.data.items.forEach(item => { - const templateId = item.resourceKey?.uiTemplateId; - const name = item.name; - if (templateId != null && name != null) { - uiTemplates.set(templateId, name); - } - }); - }); - this.uiTemplates = uiTemplates; - this.uiTemplatePageIndex = pageIndex; - } - - originalOrder = (a: KeyValue, b: KeyValue): number => { - return 0; - } } diff --git a/src/app/dialogs/choose-template/choose-template.component.html b/src/app/dialogs/choose-template/choose-template.component.html new file mode 100644 index 0000000..ae2bf39 --- /dev/null +++ b/src/app/dialogs/choose-template/choose-template.component.html @@ -0,0 +1,15 @@ +

Choose Template

+
+
+ + +
+
+
+ + +
\ No newline at end of file diff --git a/src/app/dialogs/choose-template/choose-template.component.sass b/src/app/dialogs/choose-template/choose-template.component.sass new file mode 100644 index 0000000..e69de29 diff --git a/src/app/dialogs/choose-template/choose-template.component.spec.ts b/src/app/dialogs/choose-template/choose-template.component.spec.ts new file mode 100644 index 0000000..a71f02d --- /dev/null +++ b/src/app/dialogs/choose-template/choose-template.component.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { ChooseTemplateComponent } from './choose-template.component'; + +describe('ChooseTemplateDialog', () => { + let component: ChooseTemplateComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ ChooseTemplateComponent ] + }) + .compileComponents(); + + fixture = TestBed.createComponent(ChooseTemplateComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/dialogs/choose-template/choose-template.component.ts b/src/app/dialogs/choose-template/choose-template.component.ts new file mode 100644 index 0000000..4edb63f --- /dev/null +++ b/src/app/dialogs/choose-template/choose-template.component.ts @@ -0,0 +1,40 @@ +import { Component, OnInit } from '@angular/core'; +import { TemplateApiObject } from 'src/app/services/qhana-backend.service'; +import { MatDialogRef } from '@angular/material/dialog'; +import { ApiLink } from 'src/app/services/api-data-types'; +import { PluginRegistryBaseService } from 'src/app/services/registry.service'; + +@Component({ + selector: 'qhana-choose-template', + templateUrl: './choose-template.component.html', + styleUrls: ['./choose-template.component.sass'] +}) +export class ChooseTemplateComponent implements OnInit { + highlightedTemplateSet: Set = new Set(); + templateId: string | null = null; + + constructor(public dialogRef: MatDialogRef) { } + + ngOnInit(): void { + } + + async selectTemplate(templateLink: ApiLink) { + const templateId = templateLink.resourceKey?.uiTemplateId ?? null; + if (templateId == null) { + return; + } + if (this.highlightedTemplateSet.has(templateId)) { + // double click deselects template + this.highlightedTemplateSet.clear(); + this.templateId = null; + return; + } + this.highlightedTemplateSet.clear(); + this.highlightedTemplateSet = new Set([templateId]); + this.templateId = templateId; + } + + onCancel(): void { + this.dialogRef.close(); + } +} From c04e8578fad74433219e7de83ba8e82c6ac58411 Mon Sep 17 00:00:00 2001 From: infacc Date: Tue, 1 Aug 2023 15:22:36 +0200 Subject: [PATCH 06/14] show selected template in choose template dialog --- .../experiment/experiment.component.html | 5 +---- .../experiment/experiment.component.ts | 6 ++++-- .../choose-template.component.html | 7 +++++-- .../choose-template.component.sass | 3 +++ .../choose-template.component.ts | 19 +++++++++++++++---- 5 files changed, 28 insertions(+), 12 deletions(-) diff --git a/src/app/components/experiment/experiment.component.html b/src/app/components/experiment/experiment.component.html index f832857..209ede0 100644 --- a/src/app/components/experiment/experiment.component.html +++ b/src/app/components/experiment/experiment.component.html @@ -39,13 +39,10 @@

Experiment

{{uiTemplate?.name ?? 'All Plugins'}} - -
diff --git a/src/app/components/experiment/experiment.component.ts b/src/app/components/experiment/experiment.component.ts index ff4d709..fb89bce 100644 --- a/src/app/components/experiment/experiment.component.ts +++ b/src/app/components/experiment/experiment.component.ts @@ -172,11 +172,13 @@ export class ExperimentComponent implements OnInit, OnDestroy { showSelectDefaultTemplateDialog() { const dialogRef = this.dialog.open(ChooseTemplateComponent, { - minWidth: "20rem", maxWidth: "40rem", width: "60%", + minWidth: "20rem", maxWidth: "40rem", width: "60%", maxHeight: "95%", + data: this.uiTemplate }); dialogRef.afterClosed().subscribe(templateId => { if (templateId != null) { - this.updateExperimentDefaultTemplate(templateId); + const id = templateId === 'all-plugins' ? null : templateId; + this.updateExperimentDefaultTemplate(id); } }); } diff --git a/src/app/dialogs/choose-template/choose-template.component.html b/src/app/dialogs/choose-template/choose-template.component.html index ae2bf39..68e82b1 100644 --- a/src/app/dialogs/choose-template/choose-template.component.html +++ b/src/app/dialogs/choose-template/choose-template.component.html @@ -1,12 +1,15 @@ -

Choose Template

+

Select a default template:

-
+
+ Selected Template: {{template?.name ?? 'All Plugins'}}
+ {{template?.description ?? 'This template contains all plugins that are available in Qhana.'}}
+ -
Date: Wed, 2 Aug 2023 12:40:36 +0200 Subject: [PATCH 11/14] fix: reload experiment after default template update The reload of the experiment was done in the wrong place. Sometimes the experiment template id subject signaled an update before the templateId was updated. --- src/app/components/experiment/experiment.component.ts | 5 ++--- src/app/services/templates.service.ts | 3 ++- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/app/components/experiment/experiment.component.ts b/src/app/components/experiment/experiment.component.ts index 5cf992d..8d1249c 100644 --- a/src/app/components/experiment/experiment.component.ts +++ b/src/app/components/experiment/experiment.component.ts @@ -183,12 +183,11 @@ export class ExperimentComponent implements OnInit, OnDestroy { }); } - async updateExperimentDefaultTemplate(templateId: string | null) { + updateExperimentDefaultTemplate(templateId: string | null) { if (this.experimentId == null) { console.warn("Experiment ID is null!"); return; } - await this.templates.setExperimentDefaultTemplate(this.experimentId, templateId); - this.experimentService.reloadExperiment(); + this.templates.setExperimentDefaultTemplate(this.experimentId, templateId); } } diff --git a/src/app/services/templates.service.ts b/src/app/services/templates.service.ts index 083939e..49560a0 100644 --- a/src/app/services/templates.service.ts +++ b/src/app/services/templates.service.ts @@ -234,10 +234,11 @@ export class TemplatesService { return templateResponse?.data?.groups ?? []; } - async setExperimentDefaultTemplate(experimentId: string, templateId: string | null) { + setExperimentDefaultTemplate(experimentId: string, templateId: string | null) { this.backend.updateExperimentDefaultTemplate(experimentId, templateId).pipe(take(1)).subscribe( response => { this.experimentTemplateIdSubject.next(response?.templateId ?? null); + this.currentExperiment.reloadExperiment(); } ); } From 6c4789449f07d512032ec4bc41b9bc69c7939cac Mon Sep 17 00:00:00 2001 From: infacc Date: Thu, 3 Aug 2023 15:00:29 +0200 Subject: [PATCH 12/14] justify buttons --- .../dialogs/choose-template/choose-template.component.html | 5 ++--- .../dialogs/choose-template/choose-template.component.sass | 5 ++++- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/app/dialogs/choose-template/choose-template.component.html b/src/app/dialogs/choose-template/choose-template.component.html index 68e82b1..28b1ee4 100644 --- a/src/app/dialogs/choose-template/choose-template.component.html +++ b/src/app/dialogs/choose-template/choose-template.component.html @@ -10,9 +10,8 @@

Select a default template:

- - +
\ No newline at end of file diff --git a/src/app/dialogs/choose-template/choose-template.component.sass b/src/app/dialogs/choose-template/choose-template.component.sass index 13016d5..4b2f9d1 100644 --- a/src/app/dialogs/choose-template/choose-template.component.sass +++ b/src/app/dialogs/choose-template/choose-template.component.sass @@ -1,3 +1,6 @@ .template-list max-height: 33rem - overflow-y: auto \ No newline at end of file + overflow-y: auto + +.dialog-actions + justify-content: flex-end \ No newline at end of file From 29a6db916cbb619735522e3b0d0e7b411a286d0b Mon Sep 17 00:00:00 2001 From: infacc Date: Thu, 3 Aug 2023 15:30:36 +0200 Subject: [PATCH 13/14] add search field to choose template dialog --- .../experiment/experiment.component.ts | 2 +- .../choose-template.component.html | 17 ++++++++++++----- .../choose-template.component.sass | 11 +++++++++-- 3 files changed, 22 insertions(+), 8 deletions(-) diff --git a/src/app/components/experiment/experiment.component.ts b/src/app/components/experiment/experiment.component.ts index 8d1249c..f5d95d8 100644 --- a/src/app/components/experiment/experiment.component.ts +++ b/src/app/components/experiment/experiment.component.ts @@ -172,7 +172,7 @@ export class ExperimentComponent implements OnInit, OnDestroy { showSelectDefaultTemplateDialog() { const dialogRef = this.dialog.open(ChooseTemplateDialog, { - minWidth: "20rem", maxWidth: "40rem", width: "60%", maxHeight: "95%", + minWidth: "20rem", maxWidth: "40rem", width: "20%", maxHeight: "95%", data: this.uiTemplate }); dialogRef.afterClosed().subscribe(templateId => { diff --git a/src/app/dialogs/choose-template/choose-template.component.html b/src/app/dialogs/choose-template/choose-template.component.html index 28b1ee4..51ac5cb 100644 --- a/src/app/dialogs/choose-template/choose-template.component.html +++ b/src/app/dialogs/choose-template/choose-template.component.html @@ -1,12 +1,19 @@ -

Select a default template:

+

Select Default Template

-
- + Search templates + + search + +
+
- Selected Template: {{template?.name ?? 'All Plugins'}}
- {{template?.description ?? 'This template contains all plugins that are available in Qhana.'}} +
+ Selected Template: {{template?.name ?? 'All Plugins'}}
+ {{template?.description ?? 'This template contains all plugins that are available in Qhana.'}} +
diff --git a/src/app/dialogs/choose-template/choose-template.component.sass b/src/app/dialogs/choose-template/choose-template.component.sass index 4b2f9d1..806af0a 100644 --- a/src/app/dialogs/choose-template/choose-template.component.sass +++ b/src/app/dialogs/choose-template/choose-template.component.sass @@ -1,6 +1,13 @@ .template-list - max-height: 33rem + max-height: 14rem overflow-y: auto + border-radius: 0.25rem .dialog-actions - justify-content: flex-end \ No newline at end of file + justify-content: flex-end + +.selected-template + padding-top: 1rem + +.full-width + width: 100% \ No newline at end of file From 7b1c4a9741af32b43ae541e0678f0430adb313ec Mon Sep 17 00:00:00 2001 From: infacc Date: Thu, 17 Aug 2023 14:43:56 +0200 Subject: [PATCH 14/14] rename: choose-template.component --> choose-template.dialog --- src/app/app.module.ts | 2 +- src/app/components/experiment/experiment.component.ts | 2 +- ...se-template.component.html => choose-template.dialog.html} | 0 ...se-template.component.sass => choose-template.dialog.sass} | 0 ...plate.component.spec.ts => choose-template.dialog.spec.ts} | 2 +- ...choose-template.component.ts => choose-template.dialog.ts} | 4 ++-- 6 files changed, 5 insertions(+), 5 deletions(-) rename src/app/dialogs/choose-template/{choose-template.component.html => choose-template.dialog.html} (100%) rename src/app/dialogs/choose-template/{choose-template.component.sass => choose-template.dialog.sass} (100%) rename src/app/dialogs/choose-template/{choose-template.component.spec.ts => choose-template.dialog.spec.ts} (89%) rename src/app/dialogs/choose-template/{choose-template.component.ts => choose-template.dialog.ts} (94%) diff --git a/src/app/app.module.ts b/src/app/app.module.ts index 81de03c..7194de8 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -86,7 +86,7 @@ import { ExperimentWorkspaceDetailComponent } from './components/experiment-work import { PluginFilterNodeComponent } from './components-small/plugin-filter-node/plugin-filter-node.component'; import { PluginFilterEditorComponent } from './components-small/plugin-filter-editor/plugin-filter-editor.component'; import { TabGroupListComponent } from './components/tab-group-list/tab-group-list.component'; -import { ChooseTemplateDialog } from './dialogs/choose-template/choose-template.component'; +import { ChooseTemplateDialog } from './dialogs/choose-template/choose-template.dialog'; @NgModule({ declarations: [ diff --git a/src/app/components/experiment/experiment.component.ts b/src/app/components/experiment/experiment.component.ts index f5d95d8..97d75d3 100644 --- a/src/app/components/experiment/experiment.component.ts +++ b/src/app/components/experiment/experiment.component.ts @@ -4,7 +4,7 @@ import { ActivatedRoute, Router } from '@angular/router'; import { BehaviorSubject, Subscription } from 'rxjs'; import { debounceTime, filter, map } from 'rxjs/operators'; import { ExportExperimentDialog } from 'src/app/dialogs/export-experiment/export-experiment.component'; -import { ChooseTemplateDialog } from 'src/app/dialogs/choose-template/choose-template.component'; +import { ChooseTemplateDialog } from 'src/app/dialogs/choose-template/choose-template.dialog'; import { CurrentExperimentService } from 'src/app/services/current-experiment.service'; import { ExperimentApiObject, QhanaBackendService } from 'src/app/services/qhana-backend.service'; import { ALL_PLUGINS_TEMPLATE_ID, TemplateApiObject, TemplatesService } from 'src/app/services/templates.service'; diff --git a/src/app/dialogs/choose-template/choose-template.component.html b/src/app/dialogs/choose-template/choose-template.dialog.html similarity index 100% rename from src/app/dialogs/choose-template/choose-template.component.html rename to src/app/dialogs/choose-template/choose-template.dialog.html diff --git a/src/app/dialogs/choose-template/choose-template.component.sass b/src/app/dialogs/choose-template/choose-template.dialog.sass similarity index 100% rename from src/app/dialogs/choose-template/choose-template.component.sass rename to src/app/dialogs/choose-template/choose-template.dialog.sass diff --git a/src/app/dialogs/choose-template/choose-template.component.spec.ts b/src/app/dialogs/choose-template/choose-template.dialog.spec.ts similarity index 89% rename from src/app/dialogs/choose-template/choose-template.component.spec.ts rename to src/app/dialogs/choose-template/choose-template.dialog.spec.ts index bf554b2..f1483bc 100644 --- a/src/app/dialogs/choose-template/choose-template.component.spec.ts +++ b/src/app/dialogs/choose-template/choose-template.dialog.spec.ts @@ -1,6 +1,6 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; -import { ChooseTemplateDialog } from './choose-template.component'; +import { ChooseTemplateDialog } from './choose-template.dialog'; describe('ChooseTemplateDialog', () => { let component: ChooseTemplateDialog; diff --git a/src/app/dialogs/choose-template/choose-template.component.ts b/src/app/dialogs/choose-template/choose-template.dialog.ts similarity index 94% rename from src/app/dialogs/choose-template/choose-template.component.ts rename to src/app/dialogs/choose-template/choose-template.dialog.ts index 23dd28b..d88d818 100644 --- a/src/app/dialogs/choose-template/choose-template.component.ts +++ b/src/app/dialogs/choose-template/choose-template.dialog.ts @@ -6,8 +6,8 @@ import { TemplateApiObject } from 'src/app/services/templates.service'; @Component({ selector: 'qhana-choose-template', - templateUrl: './choose-template.component.html', - styleUrls: ['./choose-template.component.sass'] + templateUrl: './choose-template.dialog.html', + styleUrls: ['./choose-template.dialog.sass'] }) export class ChooseTemplateDialog implements OnInit { highlightedTemplateSet: Set = new Set();