Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/UI improvements #76

Merged
merged 9 commits into from
Jul 26, 2024
20 changes: 10 additions & 10 deletions src/app/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,10 @@

import { HttpClientModule } from '@angular/common/http';
import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { MatBadgeModule } from '@angular/material/badge';
import { MatButtonModule } from '@angular/material/button';
import { MatButtonToggleModule } from '@angular/material/button-toggle';
import { MatSlideToggleModule } from '@angular/material/slide-toggle';
import { MatCardModule } from '@angular/material/card';
import { MatCheckboxModule } from '@angular/material/checkbox';
import { MatChipsModule } from '@angular/material/chips';
Expand All @@ -37,6 +36,7 @@ import { MatProgressBarModule } from '@angular/material/progress-bar';
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
import { MatRadioModule } from '@angular/material/radio';
import { MatSelectModule } from '@angular/material/select';
import { MatSlideToggleModule } from '@angular/material/slide-toggle';
import { MatTabsModule } from '@angular/material/tabs';
import { MatToolbarModule } from '@angular/material/toolbar';
import { MatTooltipModule } from '@angular/material/tooltip';
Expand All @@ -48,16 +48,20 @@ import { IframePreviewComponent } from './components-small/iframe-preview/iframe
import { ImagePreviewComponent } from './components-small/image-preview/image-preview.component';
import { ImportExperimentComponent } from './components-small/import-experiment/import-experiment.component';
import { MarkdownPreviewComponent } from './components-small/markdown-preview/markdown-preview.component';
import { PluginFilterEditorComponent } from './components-small/plugin-filter-editor/plugin-filter-editor.component';
import { PluginFilterNodeComponent } from './components-small/plugin-filter-node/plugin-filter-node.component';
import { PluginLastUsedComponent } from './components-small/plugin-last-used/plugin-last-used.component';
import { PluginListItemComponent } from './components-small/plugin-list-item/plugin-list-item.component';
import { PluginPreviewComponent } from './components-small/plugin-preview/plugin-preview.component';
import { QueryParamPreviewComponent } from './components-small/query-param-preview/query-param-preview.component';
import { RawTextPreviewComponent } from './components-small/raw-text-preview/raw-text-preview.component';
import { StepStatusComponent } from './components-small/step-status/step-status.component';
import { TemplateDetailsComponent } from './components-small/template-details/template-details.component';
import { DataDetailComponent } from './components/data-detail/data-detail.component';
import { DataPreviewComponent } from './components/data-preview/data-preview.component';
import { ExperimentDataComponent } from './components/experiment-data/experiment-data.component';
import { ExperimentTimelineComponent } from './components/experiment-timeline/experiment-timeline.component';
import { ExperimentWorkspaceDetailComponent } from './components/experiment-workspace-detail/experiment-workspace-detail.component';
import { ExperimentWorkspaceComponent } from './components/experiment-workspace/experiment-workspace.component';
import { ExperimentComponent } from './components/experiment/experiment.component';
import { ExperimentsPageComponent } from './components/experiments-page/experiments-page.component';
Expand All @@ -70,23 +74,18 @@ import { PluginUiframeComponent } from './components/plugin-uiframe/plugin-uifra
import { PreviewListComponent } from './components/preview-list/preview-list.component';
import { SettingsPageComponent } from './components/settings-page/settings-page.component';
import { SubstepsDetailsComponent } from './components/substeps-details/substeps-details.component';
import { TabGroupListComponent } from './components/tab-group-list/tab-group-list.component';
import { TimelineStepNavComponent } from './components/timeline-step-nav/timeline-step-nav.component';
import { TimelineStepComponent } from './components/timeline-step/timeline-step.component';
import { TimelineSubstepsComponent } from './components/timeline-substeps/timeline-substeps.component';
import { ChangeUiTemplateDialog } from './dialogs/change-ui-template/change-ui-template.dialog';
import { ChooseDataDialog } from './dialogs/choose-data/choose-data.dialog';
import { ChoosePluginDialog } from './dialogs/choose-plugin/choose-plugin.dialog';
import { ChooseTemplateDialog } from './dialogs/choose-template/choose-template.dialog';
import { CreateExperimentDialog } from './dialogs/create-experiment/create-experiment.dialog';
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 { ChangeUiTemplateDialog } from './dialogs/change-ui-template/change-ui-template.dialog';
import { ReactiveFormsModule } from '@angular/forms';
import { TemplateDetailsComponent } from './components-small/template-details/template-details.component';
import { ExperimentWorkspaceDetailComponent } from './components/experiment-workspace-detail/experiment-workspace-detail.component';
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.dialog';

@NgModule({
declarations: [
Expand Down Expand Up @@ -165,6 +164,7 @@ import { ChooseTemplateDialog } from './dialogs/choose-template/choose-template.
MatRadioModule,
MatMenuModule,
MatBadgeModule,
MatRadioModule,
],
providers: [
{ provide: MAT_FORM_FIELD_DEFAULT_OPTIONS, useValue: { appearance: "outline" } }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@
Filter
</button>
</div>
<mat-card appearance="outlined" *ngIf="!isEmpty" class="filter-node mat-elevation-z0" [ngClass]="{'filter-leaf' : type !== 'and' && type !== 'or'}">
<mat-card appearance="outlined" *ngIf="!isEmpty" class="filter-node mat-elevation-z0"
[ngClass]="{'filter-leaf' : type !== 'and' && type !== 'or'}">
<mat-card-header>
<ng-container *ngIf="children != null">
<div class="config-header">
Expand All @@ -22,11 +23,13 @@
<mat-button-toggle value="or">OR</mat-button-toggle>
</mat-button-toggle-group>
<div class="config-header-buttons">
<button mat-raised-button *ngIf="depth < 2" type="button" (click)="addFilter('and')" color="primary">
<button mat-raised-button *ngIf="depth < 2" type="button" (click)="addFilter('and')"
color="primary">
<mat-icon>add</mat-icon>
AND
</button>
<button mat-raised-button *ngIf="depth < 2" type="button" (click)="addFilter('or')" color="primary">
<button mat-raised-button *ngIf="depth < 2" type="button" (click)="addFilter('or')"
color="primary">
<mat-icon>add</mat-icon>
OR
</button>
Expand All @@ -45,7 +48,8 @@
<ng-container *ngIf="children != null">
<span *ngIf="inverted" class="t-subheading">Not:</span>
<qhana-plugin-filter-node *ngFor="let child of children; let i = index" [filterIn]="children[i]"
[depth]="depth + 1" (filterOut)="updateChild($event, i)" (delete)="deleteChild(i)"></qhana-plugin-filter-node>
[depth]="depth + 1" (filterOut)="updateChild($event, i)"
(delete)="deleteChild(i)"></qhana-plugin-filter-node>
</ng-container>
<div class="config-filter" *ngIf="value != null">
<span *ngIf="inverted" class="t-subheading">Not:</span>
Expand All @@ -69,12 +73,20 @@
<option value="dataloader"></option>
<option value="interaction"></option>
</datalist>
<mat-hint *ngIf="type === 'version'">
Examples: ">= 0.1.0", "== 1.0.2", "< 2.0.0", ">= 1.0.0, < 2.0.0" , "!= 1.0.14"
</mat-hint>
<mat-hint *ngIf="type === 'type'">
Examples: "processing", "conversion", "dataloader", "visualization", "interaction"
</mat-hint>
</mat-form-field>
<button mat-raised-button type="button" (click)="delete.emit()">
<mat-icon>delete</mat-icon>
</button>
</div>
<mat-checkbox [checked]="inverted" (change)="invertFilter($event)">Invert - <i>Negate this filter</i></mat-checkbox>
<mat-checkbox [checked]="inverted" (change)="invertFilter($event)">
Invert - <i>Negate this filter</i>
</mat-checkbox>
</mat-card-content>
</mat-card>
</div>
</div>
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
<h2>Template Tab</h2>
<form [formGroup]="templateForm" (ngSubmit)="onSubmit()">
<mat-form-field class="form-field">
<mat-label>Location</mat-label>
<mat-select formControlName="location">
<mat-option *ngFor="let location of tabGroupNameOverrides | keyvalue" [value]="location.key">
<div class="location-chooser">
<p style="margin-bottom: 0;">Show tab in:</p>
<mat-radio-group aria-label="Select the location of the template tab" formControlName="location"
[required]="true">
<mat-radio-button [value]="location.key" *ngFor="let location of tabGroupNameOverrides">
{{location.value}}
</mat-option>
</mat-select>
</mat-form-field>
</mat-radio-button>
</mat-radio-group>
</div>
<mat-form-field class="form-field">
<mat-label>Name:</mat-label>
<input matInput formControlName="name">
Expand All @@ -21,7 +22,8 @@ <h2>Template Tab</h2>
<mat-label>Sort Key:</mat-label>
<input matInput type="number" formControlName="sortKey">
</mat-form-field>
<qhana-plugin-filter-editor [tabLink]="tabLink" (filterEmitter)="filterString = $event"></qhana-plugin-filter-editor>
<qhana-plugin-filter-editor [tabLink]="tabLink"
(filterEmitter)="filterString = $event"></qhana-plugin-filter-editor>
<br>
<button mat-raised-button type="submit" color="primary">{{templateLink ? 'Create' : 'Update'}} Tab</button>
</form>
</form>
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@
resize: none
overflow: hidden

.location-chooser
margin-block-end: 1rem

details
margin-left: 2rem

Expand All @@ -19,4 +22,4 @@ dd
font-size: 0.9rem

dd
margin-bottom: 1em
margin-bottom: 1em
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,7 @@ import { Component, Input, OnInit } from '@angular/core';
import { FormBuilder, FormControl, FormGroup, Validators } from '@angular/forms';
import { ApiLink, ApiResponse } from 'src/app/services/api-data-types';
import { PluginRegistryBaseService } from 'src/app/services/registry.service';
import { TemplateApiObject, TemplateTabApiObject } from 'src/app/services/templates.service';
import { TAB_GROUP_NAME_OVERRIDES } from 'src/app/services/templates.service';
import { TAB_GROUP_NAME_OVERRIDES, TemplateApiObject, TemplateTabApiObject } from 'src/app/services/templates.service';

export function isInSetValidator(validValues: any[]): Validators {
return (control: FormControl): { [key: string]: any } | null => {
Expand All @@ -25,7 +24,18 @@ export class TemplateDetailsComponent implements OnInit {

filterString: string = "{}";

tabGroupNameOverrides = { ...TAB_GROUP_NAME_OVERRIDES };
tabGroupNameOverrides = Object.entries(TAB_GROUP_NAME_OVERRIDES)
.map(entry => { return { key: entry[0], value: entry[1] }; })
.sort((a, b) => {
if (a.key === "workspace") {
return -1;
}
else if (b.key === "workspace") {
return 1;
}
return a.value.localeCompare(b.value)
});


private initialValues = {
name: "",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ <h1 class="t-page-headline">Experiment Workspace</h1>
[hidden]="!expandedPluginDescription">expand_more</mat-icon>
<span class="t-title">{{activePlugin.title}}
(&#64;{{activePlugin.version}})</span>
<span class="t-title plugin-identifier">{{activePlugin.identifier}}</span>
<div class="spacer"></div>
<qhana-plugin-last-used class="plugin-status" [plugin]="activePlugin" [spinner]="20">
</qhana-plugin-last-used>
</h2>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,12 @@
justify-content: space-between
align-items: center

.plugin-identifier
font-weight: 200

.spacer
flex-grow: 1

.card-list
margin-inline: -16px

Expand Down
12 changes: 6 additions & 6 deletions src/app/components/navbar/navbar.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -5,25 +5,25 @@
</a>

<ng-container *ngIf="(currentExperiment|async) != null">
<a class="navigation-link" mat-button [routerLink]="['/experiments', (experimentId|async), 'info']" routerLinkActive="active">
<a class="navigation-link" mat-button [routerLink]="['/experiments', (experimentId|async), 'info']"
routerLinkActive="active">
Info
</a>
<a class="navigation-link" mat-button [routerLink]="['/experiments', (experimentId|async), 'workspace']"
queryParamsHandling="merge" routerLinkActive="active">
Workspace
</a>
<a class="navigation-link" mat-button [routerLink]="['/experiments', (experimentId|async), 'data']"
queryParamsHandling="merge" routerLinkActive="active">
queryParamsHandling="merge" routerLinkActive="active">
Data
</a>
<a class="navigation-link" mat-button [routerLink]="['/experiments', (experimentId|async), 'timeline']"
queryParamsHandling="merge" routerLinkActive="active">
queryParamsHandling="merge" routerLinkActive="active">
Timeline
</a>
<a class="navigation-link" mat-button
[routerLink]="['/experiments', (experimentId|async), 'extra', tab.resourceKey?.uiTemplateTabId]"
queryParamsHandling="merge" routerLinkActive="active"
*ngFor="let tab of experimentExtraTabs">
queryParamsHandling="merge" routerLinkActive="active" *ngFor="let tab of experimentExtraTabs">
{{tab.name}}
</a>
</ng-container>
Expand All @@ -36,7 +36,7 @@

<div class="toolbar-spacer"></div>

<div [hidden]="(currentExperiment|async) == null">
<div class="experiment-switcher" [hidden]="(currentExperiment|async) == null">
<span>{{currentExperiment|async}}</span>
<a class="navigation-link" mat-icon-button [hidden]="(currentExperiment|async) == null" [routerLink]="['/']"
aria-label="change experiment">
Expand Down
4 changes: 4 additions & 0 deletions src/app/components/navbar/navbar.component.sass
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,10 @@
.toolbar-spacer
flex-grow: 1

.experiment-switcher
display: flex
align-items: center

.settings-link
display: inline-flex
align-items: center
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,13 @@ export class PluginSidebarComponent implements OnInit, OnDestroy {
});

this.registry.resolveRecursiveRels([["plugin", "collection"]]).then((apiLink) => {
const pluginTypes = new Map<string, string>([["dataloader", "Dataloader Plugins"], ["processing", "Processing Plugins"], ["conversion", "Conversion Plugins"], ["visualization", "Visualization Plugins"]]);
const pluginTypes = new Map<string, string>([
["dataloader", "Dataloader Plugins"],
["processing", "Processing Plugins"],
["conversion", "Conversion Plugins"],
["visualization", "Visualization Plugins"],
["interaction", "Interaction Plugins"]
]);
pluginTypes.forEach((name, pluginType) => {
const query = new URLSearchParams();
query.set("type", pluginType)
Expand Down
3 changes: 2 additions & 1 deletion src/app/components/plugin-tab/plugin-tab.component.html
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
<nav mat-tab-nav-bar class="big-nav-tabs" *ngIf="plugins.length > 1">
<a mat-tab-link
[routerLink]="['/experiments', currentExperimentId, 'extra', currentTabId, 'plugin', plugin.resourceKey?.pluginId]"
[active]="plugin.resourceKey?.pluginId === currentPluginId" *ngFor="let plugin of plugins">
[queryParamsHandling]="'preserve'" [active]="plugin.resourceKey?.pluginId === currentPluginId"
*ngFor="let plugin of plugins">
{{plugin.name}}
</a>
</nav>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
<mat-icon *ngIf="!buttonsLeft" inline="false" aria-hidden="true">chevron_left</mat-icon>
</button>
<mat-divider [vertical]="true"></mat-divider>
<button mat-icon-button (click)="autofillLatest()" [disabled]="autofillData == null">
<button mat-icon-button style="text-align: left;" (click)="autofillLatest()" [disabled]="autofillData == null">
<mat-icon inline="true" aria-hidden="true">rocket_launch</mat-icon>
</button>
<button mat-icon-button (click)="fullscreen=!fullscreen">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
.floating-buttons
display: none
position: absolute
height: 2.5rem
height: 3rem
top: calc(-2.5rem - 3px)
right: 0.3rem
color: var(--text-card)
Expand Down
32 changes: 31 additions & 1 deletion src/app/components/plugin-uiframe/plugin-uiframe.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -277,6 +277,22 @@ export class PluginUiframeComponent implements OnChanges, OnDestroy {
return;
}

private calculateMaxHeight(): number | undefined {
const maxHeight = window.visualViewport?.height;
if (maxHeight == null) {
return undefined;
}
let offsetTop = 4; // start with 4px offset to avoid scrollbar
let currentElement: HTMLElement | Element | null = this.uiframe?.nativeElement ?? null;

while (currentElement != null && currentElement instanceof HTMLElement) {
offsetTop += currentElement.offsetTop ?? 0; // add up all ofets until root layout
currentElement = currentElement.offsetParent ?? null;
}

return maxHeight - offsetTop;
}

private selectPlugin(request: PluginUrlRequest) {
if (this.dialogActive) {
return; // only ever show one dialog at a time
Expand Down Expand Up @@ -471,7 +487,21 @@ export class PluginUiframeComponent implements OnChanges, OnDestroy {
}
} else { // assume object message
if (data?.type === "ui-resize") {
this.frontendHeight = Math.max(data.height ?? 100, 20);
let newHeight = Math.max(data.height ?? 100, 20);
if (data.targetHeight != null && Number.isFinite(data.targetHeight) && data.targetHeight > 20) {
// directly use target height (if it is the higher value)
newHeight = Math.max(newHeight, data.targetHeight);
// use target height * 2 as the maximum height (to allow for some slack)
newHeight = Math.min(newHeight, 2 * data.targetHeight);
}
if (data.targetHeight === "full") {
// if target height is full set iframe to max height to fill the current screen
const maxHeight = this.calculateMaxHeight();
if (maxHeight != null) {
newHeight = maxHeight;
}
}
this.frontendHeight = newHeight;
}
if (data?.type === "form-submit") {
if (!isFormSubmitData(data)) {
Expand Down
Loading
Loading