diff --git a/AMW_angular/io/src/app/resources/resource-edit-page/resource-edit-page.component.html b/AMW_angular/io/src/app/resources/resource-edit-page/resource-edit-page.component.html index ade6cbd6e..831b16475 100644 --- a/AMW_angular/io/src/app/resources/resource-edit-page/resource-edit-page.component.html +++ b/AMW_angular/io/src/app/resources/resource-edit-page/resource-edit-page.component.html @@ -4,5 +4,23 @@
Loaded resource: {{ this.resource()?.name }}
+ + + +
diff --git a/AMW_angular/io/src/app/resources/resource-edit-page/resource-edit-page.component.spec.ts b/AMW_angular/io/src/app/resources/resource-edit-page/resource-edit-page.component.spec.ts index 12a1c508f..697fa9201 100644 --- a/AMW_angular/io/src/app/resources/resource-edit-page/resource-edit-page.component.spec.ts +++ b/AMW_angular/io/src/app/resources/resource-edit-page/resource-edit-page.component.spec.ts @@ -1,5 +1,4 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; -import { ComponentRef } from '@angular/core'; import { provideHttpClient, withInterceptorsFromDi } from '@angular/common/http'; import { provideHttpClientTesting } from '@angular/common/http/testing'; import { ResourceEditPageComponent } from './resource-edit-page.component'; @@ -7,9 +6,8 @@ import { ActivatedRoute } from '@angular/router'; import { of, Subject } from 'rxjs'; import { RouterTestingModule } from '@angular/router/testing'; -describe('ResourceEditComponent', () => { +describe('ResourceEditPageComponent', () => { let component: ResourceEditPageComponent; - let componentRef: ComponentRef; let fixture: ComponentFixture; const mockRoute: any = { queryParamMap: of() }; @@ -30,7 +28,6 @@ describe('ResourceEditComponent', () => { fixture = TestBed.createComponent(ResourceEditPageComponent); component = fixture.componentInstance; - componentRef = fixture.componentRef; fixture.detectChanges(); }); diff --git a/AMW_angular/io/src/app/resources/resource-edit-page/resource-edit-page.component.ts b/AMW_angular/io/src/app/resources/resource-edit-page/resource-edit-page.component.ts index cc64f7489..cde80a3b7 100644 --- a/AMW_angular/io/src/app/resources/resource-edit-page/resource-edit-page.component.ts +++ b/AMW_angular/io/src/app/resources/resource-edit-page/resource-edit-page.component.ts @@ -1,4 +1,4 @@ -import { Component, computed, inject, Signal } from '@angular/core'; +import { Component, computed, inject, Signal, signal } from '@angular/core'; import { LoadingIndicatorComponent } from '../../shared/elements/loading-indicator.component'; import { PageComponent } from '../../layout/page/page.component'; import { ActivatedRoute } from '@angular/router'; @@ -6,14 +6,18 @@ import { distinctUntilChanged, map } from 'rxjs/operators'; import { toSignal } from '@angular/core/rxjs-interop'; import { ResourceService } from '../../resource/resource.service'; import { Resource } from '../../resource/resource'; +import { EntryAction, TileListEntry, TileListEntryOutput } from '../../shared/tile/tile-list/tile-list.component'; +import { NgbModal } from '@ng-bootstrap/ng-bootstrap'; +import { TileComponent } from '../../shared/tile/tile.component'; @Component({ - selector: 'app-resources-edit', + selector: 'app-resources-edit-page', standalone: true, - imports: [LoadingIndicatorComponent, PageComponent], + imports: [LoadingIndicatorComponent, PageComponent, TileComponent], templateUrl: './resource-edit-page.component.html', }) export class ResourceEditPageComponent { + private modalService = inject(NgbModal); private resourceService = inject(ResourceService); private route = inject(ActivatedRoute); @@ -33,4 +37,68 @@ export class ResourceEditPageComponent { return false; } }); + + templatesData = signal([ + { + title: 'Instance Templates', + entries: [ + { name: 'startJob_0.sh', description: 'startJob_0.sh', id: 0 }, + { name: 'startJob_1.sh', description: 'job 2 again', id: 1 }, + ] as TileListEntry[], + canEdit: true, + canDelete: true, + }, + { + title: 'Resource Type Templates', + entries: [{ name: 'seg', description: 'segmentation', id: 121 }] as TileListEntry[], + canOverwrite: false, + }, + ]); + + functionsData = signal([ + { + title: 'Resource Instance Functions', + entries: [ + { name: 'Function1', description: 'bla', id: 42 }, + { name: 'Function 2', description: 'whatever', id: 69 }, + ], + canEdit: true, + canDelete: true, + }, + { + title: 'Resource Type Functions', + entries: [{ name: 'seg', description: 'segmentation', id: 666 }] as TileListEntry[], + canOverwrite: false, + }, + ]); + + add() { + this.modalService.open('This would open a modal to add something'); + } + + doListAction($event: TileListEntryOutput) { + switch ($event.action) { + case EntryAction.edit: + this.editFunction($event.id); + return; + case EntryAction.delete: + this.deleteFunction($event.id); + return; + case EntryAction.overwrite: + this.overwriteFunction($event.id); + return; + } + } + + private editFunction(id: number) { + this.modalService.open('This would open a modal to edit function with id:' + id); + } + + private deleteFunction(id: number) { + this.modalService.open('This would open a modal to delete function with id:' + id); + } + + private overwriteFunction(id: number) { + this.modalService.open('This would open a modal to overwrite function with id:' + id); + } } diff --git a/AMW_angular/io/src/app/shared/tile/tile-list/tile-list.component.scss b/AMW_angular/io/src/app/shared/tile/tile-list/tile-list.component.scss new file mode 100644 index 000000000..5436f70db --- /dev/null +++ b/AMW_angular/io/src/app/shared/tile/tile-list/tile-list.component.scss @@ -0,0 +1,30 @@ +.title { + font-size: 16px; + font-weight: bold; + margin-bottom: 0.5em; +} + +ul { + padding-left: 0; +} + +.list-entry { + display: flex; + flex-direction: row; + justify-content: space-between; + margin-bottom: 0.5em; + + .list-entry-text { + display: flex; + flex-direction: column; + + .desc { + color: gray; + } + } + + .list-entry-actions { + display: flex; + gap: 0.5em + } +} diff --git a/AMW_angular/io/src/app/shared/tile/tile-list/tile-list.component.spec.ts b/AMW_angular/io/src/app/shared/tile/tile-list/tile-list.component.spec.ts new file mode 100644 index 000000000..9e9af49be --- /dev/null +++ b/AMW_angular/io/src/app/shared/tile/tile-list/tile-list.component.spec.ts @@ -0,0 +1,33 @@ +import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; +import { TileListComponent } from './tile-list.component'; +import { ComponentRef } from '@angular/core'; + +describe('TileListComponent', () => { + let component: TileListComponent; + let componentRef: ComponentRef; + let fixture: ComponentFixture; + + const tileListData = [ + { name: 'startJob_0.sh', description: 'startJob_0.sh', id: 0 }, + { name: 'startJob_1.sh', description: 'job 2 again', id: 1 }, + ]; + + beforeEach(waitForAsync(() => { + TestBed.configureTestingModule({ + imports: [TileListComponent], + }).compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(TileListComponent); + component = fixture.componentInstance; + componentRef = fixture.componentRef; + componentRef.setInput('title', 'my title'); + componentRef.setInput('data', tileListData); + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/AMW_angular/io/src/app/shared/tile/tile-list/tile-list.component.ts b/AMW_angular/io/src/app/shared/tile/tile-list/tile-list.component.ts new file mode 100644 index 000000000..ab4f05cd2 --- /dev/null +++ b/AMW_angular/io/src/app/shared/tile/tile-list/tile-list.component.ts @@ -0,0 +1,80 @@ +import { Component, input, output } from '@angular/core'; +import { ButtonComponent } from '../../button/button.component'; +import { IconComponent } from '../../icon/icon.component'; + +export interface TileListEntry { + id: number; + name: string; + description: string; +} + +export enum EntryAction { + edit = 'edit', + delete = 'delete', + overwrite = 'overwrite', +} + +export interface TileListEntryOutput { + action: EntryAction; + id: number; +} + +@Component({ + selector: 'app-tile-list', + template: ` +
{{ title() }}
+ + @if (!data() || data().length <= 0) { +
+ No {{ title() }} for this resource +
+ } +
    + @for (entry of data(); track entry) { +
  • +
    + {{ entry.name }} + {{ entry.description }} +
    +
    + @if (canEdit()) { + Edit + + } @if (canDelete()) { + + Delete + + } @if (canOverwrite()) { + + Overwrite + + } +
    +
  • + } +
+ `, + styleUrls: ['tile-list.component.scss'], + standalone: true, + imports: [ButtonComponent, IconComponent], +}) +export class TileListComponent { + title = input.required(); + data = input.required(); + canEdit = input(false); + canDelete = input(false); + canOverwrite = input(false); + edit = output(); + delete = output(); + overwrite = output(); + protected readonly EntryAction = EntryAction; +} diff --git a/AMW_angular/io/src/app/shared/tile/tile.component.scss b/AMW_angular/io/src/app/shared/tile/tile.component.scss new file mode 100644 index 000000000..725968b02 --- /dev/null +++ b/AMW_angular/io/src/app/shared/tile/tile.component.scss @@ -0,0 +1,35 @@ +.tile { + border: 1px solid #ddd; + padding: 1em; + margin: 0.5em; + + .tile-header { + display: flex; + flex-direction: row; + justify-content: space-between; + } + + .tile-title { + font-weight: bold; + font-size: 18px; + } + + .tile-action-bar, span { + padding-left: 0.5em; + } + + .tile-body { + margin: 1em 0 0.5em; + + .no-content { + font-style: italic; + } + } + +} + +.list-element { + font-weight: bold; +} + + diff --git a/AMW_angular/io/src/app/shared/tile/tile.component.spec.ts b/AMW_angular/io/src/app/shared/tile/tile.component.spec.ts new file mode 100644 index 000000000..5bcea06d6 --- /dev/null +++ b/AMW_angular/io/src/app/shared/tile/tile.component.spec.ts @@ -0,0 +1,42 @@ +import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; +import { TileComponent } from './tile.component'; +import { ComponentRef } from '@angular/core'; + +describe('TileComponent', () => { + let component: TileComponent; + let componentRef: ComponentRef; + let fixture: ComponentFixture; + + const tileData = [ + { + title: 'Instance Templates', + entries: [ + { name: 'startJob_0.sh', description: 'startJob_0.sh', id: 0 }, + { name: 'startJob_1.sh', description: 'job 2 again', id: 1 }, + ], + canEdit: true, + canDelete: true, + }, + ]; + + beforeEach(waitForAsync(() => { + TestBed.configureTestingModule({ + imports: [TileComponent], + }).compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(TileComponent); + component = fixture.componentInstance; + componentRef = fixture.componentRef; + componentRef.setInput('title', 'my title'); + componentRef.setInput('actionName', 'myAction'); + componentRef.setInput('canAction', true); + componentRef.setInput('lists', tileData); + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/AMW_angular/io/src/app/shared/tile/tile.component.ts b/AMW_angular/io/src/app/shared/tile/tile.component.ts new file mode 100644 index 000000000..f3d80adb6 --- /dev/null +++ b/AMW_angular/io/src/app/shared/tile/tile.component.ts @@ -0,0 +1,81 @@ +import { Component, input, output } from '@angular/core'; +import { NgClass, NgComponentOutlet } from '@angular/common'; +import { FormsModule } from '@angular/forms'; +import { NgbDatepicker, NgbPopover, NgbTimepicker } from '@ng-bootstrap/ng-bootstrap'; +import { IconComponent } from '../icon/icon.component'; +import { ButtonComponent } from '../button/button.component'; +import { TileListComponent, TileListEntry, TileListEntryOutput } from './tile-list/tile-list.component'; + +@Component({ + selector: 'app-tile-component', + template: ` +
+
+
{{ title() }}
+ @if (canAction()) { +
+ + + {{ actionName() }} +
+ } +
+ +
+ @if (!lists()) { +
+ No {{ title() }} for this resource +
+ } @else if (lists().length <= 0) { +
+ You are not allowed to view {{ title() }} for this resource +
+ } + + @for (list of lists(); track list) { + + } +
+
+ `, + styleUrls: ['./tile.component.scss'], + providers: [], + standalone: true, + imports: [ + FormsModule, + NgClass, + NgbPopover, + NgComponentOutlet, + IconComponent, + NgbDatepicker, + NgbTimepicker, + ButtonComponent, + TileListComponent, + ], +}) +export class TileComponent { + title = input.required(); + actionName = input.required(); + canAction = input(false); + lists = input.required< + { + title: string; + entries: TileListEntry[]; + canEdit?: boolean; + canDelete?: boolean; + canOverwrite?: boolean; + }[] + >(); + tileAction = output(); + listAction = output(); +}