diff --git a/src/app/app.module.ts b/src/app/app.module.ts
index 1e64aa9..1bb6f06 100644
--- a/src/app/app.module.ts
+++ b/src/app/app.module.ts
@@ -87,6 +87,7 @@ import { CreateExperimentDialog } from './dialogs/create-experiment/create-exper
import { DeleteDialog } from './dialogs/delete-dialog/delete-dialog.dialog';
import { ExportExperimentDialog } from './dialogs/export-experiment/export-experiment.dialog';
import { MarkdownHelpDialog } from './dialogs/markdown-help/markdown-help.dialog';
+import { UiTemplateComponent } from "./components-small/ui-template/ui-template.component";
@NgModule({
declarations: [
@@ -134,6 +135,7 @@ import { MarkdownHelpDialog } from './dialogs/markdown-help/markdown-help.dialog
TabGroupListComponent,
ChooseTemplateDialog,
UiTemplatesPageComponent,
+ UiTemplateComponent,
],
imports: [
BrowserModule,
diff --git a/src/app/components-small/ui-template/ui-template.component.html b/src/app/components-small/ui-template/ui-template.component.html
new file mode 100644
index 0000000..25c2021
--- /dev/null
+++ b/src/app/components-small/ui-template/ui-template.component.html
@@ -0,0 +1,42 @@
+
+
+
+
+ {{tag}}
+
+
+ Tags
+
+ @for (tag of currentTags; track tag) {
+
+ {{tag}}
+
+
+ }
+
+
+
+
+
+
diff --git a/src/app/components-small/ui-template/ui-template.component.sass b/src/app/components-small/ui-template/ui-template.component.sass
new file mode 100644
index 0000000..e4ce0ec
--- /dev/null
+++ b/src/app/components-small/ui-template/ui-template.component.sass
@@ -0,0 +1,23 @@
+.header
+ display: flex
+ align-items: center
+ justify-content: space-between
+ gap: 1rem
+
+.header-buttons
+ display: flex
+ align-items: center
+ gap: 0.5rem
+
+.content, .content:last-child
+ padding: 0
+ margin-block-start: 0.5rem
+
+.name-field
+ flex-grow: 1
+
+.tags
+ margin-block-end: 0.5rem
+
+.tags-field
+ width: 100%
diff --git a/src/app/components-small/ui-template/ui-template.component.ts b/src/app/components-small/ui-template/ui-template.component.ts
new file mode 100644
index 0000000..fcf048a
--- /dev/null
+++ b/src/app/components-small/ui-template/ui-template.component.ts
@@ -0,0 +1,144 @@
+import { Component, Input, OnChanges, OnDestroy, OnInit, SimpleChanges } from '@angular/core';
+import { CommonModule } from '@angular/common';
+import { ApiLink, ChangedApiObject } from 'src/app/services/api-data-types';
+import { TemplateApiObject } from 'src/app/services/templates.service';
+import { PluginRegistryBaseService } from 'src/app/services/registry.service';
+import { MatChipInputEvent } from '@angular/material/chips';
+import { Subscription } from 'rxjs';
+import { MatDialog } from '@angular/material/dialog';
+import { DeleteDialog } from 'src/app/dialogs/delete-dialog/delete-dialog.dialog';
+
+@Component({
+ selector: 'qhana-ui-template',
+ templateUrl: './ui-template.component.html',
+ styleUrl: './ui-template.component.sass'
+})
+export class UiTemplateComponent implements OnChanges, OnInit, OnDestroy {
+
+ @Input() templateLink: ApiLink | null = null;
+
+
+ templateData: TemplateApiObject | null = null;
+ templateUpdateLink: ApiLink | null = null;
+ templateDeleteLink: ApiLink | null = null;
+
+ isEditing: boolean = false;
+
+ currentName: string | null = null;
+ currentTags: string[] | null = null;
+ currentDescription: string | null = null;
+
+ private tagsDirty: boolean = false;
+ private updateSubscription: Subscription | null = null;
+
+ constructor(private registry: PluginRegistryBaseService, private dialog: MatDialog) { }
+
+ ngOnInit(): void {
+ this.updateSubscription = this.registry.apiObjectSubject.subscribe((apiObject) => {
+ if (apiObject.self.href !== this.templateLink?.href) {
+ return;
+ }
+ if (apiObject.self.resourceType === "ui-template") {
+ this.templateData = apiObject as TemplateApiObject;
+ if (this.isEditing) {
+ this.currentTags = [...(apiObject as TemplateApiObject).tags];
+ this.tagsDirty = false;
+ }
+ }
+ });
+ }
+
+ ngOnDestroy(): void {
+ this.updateSubscription?.unsubscribe();
+ }
+
+ ngOnChanges(changes: SimpleChanges): void {
+ this.loadTemplate();
+ }
+
+ private async loadTemplate() {
+ if (this.templateLink == null) {
+ this.templateData = null;
+ return;
+ }
+ const templateResponse = await this.registry.getByApiLink(this.templateLink);
+ this.templateData = templateResponse?.data ?? null;
+
+ this.templateUpdateLink = templateResponse?.links?.find(link => link.rel.some(rel => rel === "update") && link.resourceType == "ui-template") ?? null;
+ this.templateDeleteLink = templateResponse?.links?.find(link => link.rel.some(rel => rel === "delete") && link.resourceType == "ui-template") ?? null;
+ }
+
+ get isDirty() {
+ if (!this.isEditing) {
+ return false;
+ }
+ if (this.currentName !== this.templateData?.name) {
+ return true;
+ }
+ if (this.currentDescription !== this.templateData?.description) {
+ return true;
+ }
+ if (this.tagsDirty) {
+ return true;
+ }
+ return false;
+ }
+
+ toggleEdit() {
+ if (this.templateData == null || this.templateUpdateLink == null) {
+ this.isEditing = false;
+ } else {
+ this.isEditing = !this.isEditing;
+ }
+ if (this.isEditing) {
+ this.currentName = this.templateData?.name ?? null;
+ this.currentTags = [...(this.templateData?.tags ?? [])];
+ this.currentDescription = this.templateData?.description ?? null;
+ this.tagsDirty = false;
+ } else {
+ this.currentName = null;
+ this.currentTags = null;
+ this.currentDescription = null;
+ this.tagsDirty = false;
+ }
+ }
+
+ removeTag(tag: string) {
+ this.currentTags = this.currentTags?.filter(t => t !== tag) ?? null;
+ this.tagsDirty = true;
+ }
+
+ addTag(event: MatChipInputEvent) {
+ const tag = event.value;
+ this.currentTags?.push(tag);
+ this.tagsDirty = true;
+ }
+
+ async updateTemplate() {
+ if (!this.isDirty || this.templateUpdateLink == null) {
+ return;
+ }
+
+ this.registry.submitByApiLink(this.templateUpdateLink, {
+ name: this.currentName,
+ description: this.currentDescription,
+ tags: this.currentTags,
+ });
+ }
+
+ async deleteTemplate() {
+ if (this.templateDeleteLink == null) {
+ return;
+ }
+
+ const dialogRef = this.dialog.open(DeleteDialog, {
+ data: this.templateLink,
+ });
+
+ const doDelete = await dialogRef.afterClosed().toPromise();
+ if (doDelete) {
+ this.registry.submitByApiLink(this.templateDeleteLink);
+ }
+ }
+
+}
diff --git a/src/app/components/ui-templates-page/ui-templates-page.component.html b/src/app/components/ui-templates-page/ui-templates-page.component.html
index 5a2541f..bb0f343 100644
--- a/src/app/components/ui-templates-page/ui-templates-page.component.html
+++ b/src/app/components/ui-templates-page/ui-templates-page.component.html
@@ -13,6 +13,7 @@
diff --git a/src/app/components/ui-templates-page/ui-templates-page.component.sass b/src/app/components/ui-templates-page/ui-templates-page.component.sass
index 8498478..d7f5fe7 100644
--- a/src/app/components/ui-templates-page/ui-templates-page.component.sass
+++ b/src/app/components/ui-templates-page/ui-templates-page.component.sass
@@ -47,6 +47,7 @@
.content-container .main-content
max-width: 60rem
margin-inline: auto
+ padding-block-start: 2rem
.title
display: flex
diff --git a/src/app/components/ui-templates-page/ui-templates-page.component.ts b/src/app/components/ui-templates-page/ui-templates-page.component.ts
index a68078c..cca6c4a 100644
--- a/src/app/components/ui-templates-page/ui-templates-page.component.ts
+++ b/src/app/components/ui-templates-page/ui-templates-page.component.ts
@@ -18,7 +18,10 @@ export class UiTemplatesPageComponent implements OnInit, OnDestroy {
highlightedTemplates: Set = new Set();
templateId: string | null = null;
+
private routeParamSubscription: Subscription | null = null;
+ private templateCreatedSubscription: Subscription | null = null;
+ private templateDeletedSubscription: Subscription | null = null;
constructor(private route: ActivatedRoute, private router: Router, private registry: PluginRegistryBaseService, private templates: TemplatesService, private dialog: MatDialog) { }
@@ -32,18 +35,37 @@ export class UiTemplatesPageComponent implements OnInit, OnDestroy {
} else {
this.highlightedTemplates.clear();
}
- console.log(this.highlightedTemplates, templateId)
+ this.templateId = templateId;
this.loadActiveTemplateFromId(templateId);
});
+ this.templateCreatedSubscription = this.registry.newApiObjectSubject.subscribe(created => {
+ if (created.new.resourceType === "ui-template") {
+ // new template created
+ if (this.templateId == null) {
+ this.selectTemplate(created.new);
+ }
+ }
+ });
+
+ this.templateDeletedSubscription = this.registry.deletedApiObjectSubject.subscribe(deleted => {
+ if (this.templateId == null) {
+ return;
+ }
+ if (deleted.deleted.resourceType === "ui-template" && deleted.deleted.resourceKey?.uiTemplateId === this.templateId) {
+ // current template was deleted, navigate to overview
+ this.selectTemplate(null);
+ }
+ });
}
ngOnDestroy(): void {
this.routeParamSubscription?.unsubscribe();
+ this.templateDeletedSubscription?.unsubscribe();
+ this.templateCreatedSubscription?.unsubscribe();
}
selectTemplate(templateLink: ApiLink | null) {
- console.log(templateLink)
if (templateLink == null) {
this.router.navigate(["/templates"]);
return;
@@ -72,6 +94,7 @@ export class UiTemplatesPageComponent implements OnInit, OnDestroy {
this.selectedTemplate = templatePage.data.items[0];
} else {
console.warn(`Template API returned an ambiguous response for template id ${newTemplateId}`, templatePage);
+ this.selectTemplate(null);
}
}