diff --git a/src/app/components/plugin-uiframe/plugin-uiframe.component.sass b/src/app/components/plugin-uiframe/plugin-uiframe.component.sass
index e0ed25c..271e2c5 100644
--- a/src/app/components/plugin-uiframe/plugin-uiframe.component.sass
+++ b/src/app/components/plugin-uiframe/plugin-uiframe.component.sass
@@ -26,12 +26,41 @@
background-color: var(--background)
color: var(--warn-text)
-.fullscreen
+.floating-buttons
+ display: none
position: absolute
- top: 0
- left: 0
- right: 0
- min-height: 97%
+ height: 2.5rem
+ top: calc(-2.5rem - 3px)
+ right: 0.3rem
+ color: var(--text-card)
+ background-color: var(--background-card)
+ border: solid
+ border-width: 2px
+ border-radius: 2px
+ border-color: var(--border-color)
+ z-index: 2
+
+.floating-buttons.left
+ right: unset
+ left: 0.3rem
+
+.microfrontend-container:focus-within, .microfrontend-container:focus, .microfrontend-container:hover
+ .floating-buttons
+ display: flex
+
+.fullscreen
+ .floating-buttons
+ display: flex
+ top: 2px
+
+.fullscreen
+ position: fixed
+ top: 0px
+ left: 0px
+ right: 0px
+ bottom: 0px
+ background-color: var(--background)
+ z-index: 1
.fsbutton
position: absolute
diff --git a/src/app/components/plugin-uiframe/plugin-uiframe.component.ts b/src/app/components/plugin-uiframe/plugin-uiframe.component.ts
index c4c3770..086f64a 100644
--- a/src/app/components/plugin-uiframe/plugin-uiframe.component.ts
+++ b/src/app/components/plugin-uiframe/plugin-uiframe.component.ts
@@ -3,10 +3,10 @@ import { MatDialog } from '@angular/material/dialog';
import { DomSanitizer, SafeResourceUrl } from '@angular/platform-browser';
import { ActivatedRoute } from '@angular/router';
import { Observable, of } from 'rxjs';
-import { concatAll, filter, map, mergeAll, toArray } from 'rxjs/operators';
+import { catchError, concatAll, filter, map, mergeAll, mergeMap, toArray } from 'rxjs/operators';
import { ChooseDataDialog } from 'src/app/dialogs/choose-data/choose-data.dialog';
import { ChoosePluginDialog } from 'src/app/dialogs/choose-plugin/choose-plugin.dialog';
-import { CollectionApiObject } from 'src/app/services/api-data-types';
+import { ApiLink, CollectionApiObject } from 'src/app/services/api-data-types';
import { PluginApiObject } from 'src/app/services/qhana-api-data-types';
import { ApiObjectList, ExperimentDataApiObject, QhanaBackendService } from 'src/app/services/qhana-backend.service';
import { PluginRegistryBaseService } from 'src/app/services/registry.service';
@@ -20,6 +20,12 @@ export interface FormSubmitData {
resultUrl: string;
}
+export interface PluginUiContext {
+ experimentId?: string | number;
+ stepId?: string | number;
+ data?: Array<{ downloadUrl: string, dataType: string, contentType: string }>;
+}
+
function isFormSubmitData(data: any): data is FormSubmitData {
if (data == null) {
return false;
@@ -161,6 +167,9 @@ export class PluginUiframeComponent implements OnChanges, OnDestroy {
@Input() url: string | null = null;
@Output() formDataSubmit: EventEmitter
= new EventEmitter();
+ @Input() plugin: ApiLink | null = null;
+ @Input() context: PluginUiContext | null = null;
+
blank: SafeResourceUrl;
pluginOrigin: string | null = null;
@@ -171,11 +180,16 @@ export class PluginUiframeComponent implements OnChanges, OnDestroy {
hasFullscreenMode: boolean = false;
fullscreen: boolean = false;
+ buttonsLeft: boolean = false;
+
loading: boolean = true;
error: { code: number, status: string } | null = null;
+ autofillData: { value: string, encoding: string } | null = null;
+
private dialogActive = false;
+
listenerFunction = (event: MessageEvent) => this.handleMicroFrontendEvent(event);
constructor(private sanitizer: DomSanitizer, private dialog: MatDialog, private backend: QhanaBackendService, private registry: PluginRegistryBaseService, private route: ActivatedRoute) {
@@ -202,11 +216,65 @@ export class PluginUiframeComponent implements OnChanges, OnDestroy {
this.frontendHeight = 100;
return;
}
- this.loading = true;
- this.pluginOrigin = (new URL(url)).origin;
- this.frontendHeight = 100;
- this.frontendUrl = this.sanitizer.bypassSecurityTrustResourceUrl(url);
- this.hasFullscreenMode = false;
+ if (changes.url != null) {
+ this.loading = true;
+ this.pluginOrigin = (new URL(url)).origin;
+ this.frontendHeight = 100;
+ this.frontendUrl = this.sanitizer.bypassSecurityTrustResourceUrl(url);
+ this.hasFullscreenMode = false;
+ }
+ if (changes.plugin != null || changes.context != null) {
+ this.processContext();
+ }
+ }
+
+ async autofillLatest() {
+ if (this.autofillData != null) {
+ this.sendAutofillData(this.autofillData.value, this.autofillData.encoding);
+ }
+ }
+
+ private async processContext() {
+ if (this.plugin == null) {
+ this.autofillData = null;
+ return;
+ }
+ const plugin = (await this.registry.getByApiLink(this.plugin))?.data ?? null;
+ if (plugin == null) {
+ this.autofillData = null;
+ return;
+ }
+ if (this.context?.experimentId != null) {
+ this.backend.getTimelineStepsPage(this.context.experimentId, { sort: -1, pluginName: plugin.identifier, version: plugin.version, itemCount: 1 }).pipe(
+ map(steps => {
+ if (steps.items.length == 1) {
+ const step = steps.items[0];
+ return {
+ parametersUrl: step.parameters,
+ encoding: step.parametersContentType,
+ };
+ }
+ return null;
+ }),
+ mergeMap(params => {
+ if (params == null) {
+ return of(null);
+ }
+ return this.backend.getTimelineStepParameters(params.parametersUrl).pipe(map(value => {
+ return { value: value, encoding: params.encoding };
+ }))
+ }),
+ catchError((err) => {
+ console.log(err)
+ return of(null);
+ }),
+ ).subscribe(result => {
+ this.autofillData = result;
+ });
+ return;
+ }
+ this.autofillData = null;
+ return;
}
private selectPlugin(request: PluginUrlRequest) {
@@ -299,6 +367,14 @@ export class PluginUiframeComponent implements OnChanges, OnDestroy {
}
}
+ private sendAutofillData(value: string, encoding: string) {
+ this.sendMessage({
+ type: "autofill-response",
+ value: value,
+ encoding: encoding,
+ });
+ }
+
private sendMessage(message: any) {
const iframe: HTMLIFrameElement | null = this.uiframe?.nativeElement ?? null;
iframe?.contentWindow?.postMessage?.(message, this.pluginOrigin ?? "*");
diff --git a/src/app/services/qhana-backend.service.ts b/src/app/services/qhana-backend.service.ts
index c794373..ddfc0bc 100644
--- a/src/app/services/qhana-backend.service.ts
+++ b/src/app/services/qhana-backend.service.ts
@@ -474,6 +474,12 @@ export class QhanaBackendService {
);
}
+ public getTimelineStepParameters(url: string): Observable {
+ return this.callWithRootUrl(
+ rootUrl => this.http.get(url, { responseType: "text" })
+ );
+ }
+
public getTimelineStepNotes(experimentId: number | string, step: number | string): Observable {
return this.callWithRootUrl(
rootUrl => this.http.get(`${rootUrl}/experiments/${experimentId}/timeline/${step}/notes`)