diff --git a/pkg/view/component/modal.go b/pkg/view/component/modal.go index 13b67bd03e..36aa948b21 100644 --- a/pkg/view/component/modal.go +++ b/pkg/view/component/modal.go @@ -21,6 +21,7 @@ type ModalConfig struct { Form *Form `json:"form,omitempty"` Opened bool `json:"opened"` ModalSize ModalSize `json:"size,omitempty"` + Buttons []Button `json:"buttons,omitempty"` } // UnmarshalJSON unmarshals a modal config from JSON. @@ -30,6 +31,7 @@ func (m *ModalConfig) UnmarshalJSON(data []byte) error { Form *Form `json:"form,omitempty"` Opened bool `json:"opened"` ModalSize ModalSize `json:"size,omitempty"` + Buttons []Button `json:"buttons,omitempty"` }{} if err := json.Unmarshal(data, &x); err != nil { @@ -47,6 +49,7 @@ func (m *ModalConfig) UnmarshalJSON(data []byte) error { m.Form = x.Form m.Opened = x.Opened m.ModalSize = x.ModalSize + m.Buttons = x.Buttons return nil } @@ -82,6 +85,11 @@ func (m *Modal) SetSize(size ModalSize) { m.Config.ModalSize = size } +// AddButton is a helper to add a custom button +func (m *Modal) AddButton(button Button) { + m.Config.Buttons = append(m.Config.Buttons, button) +} + // Open opens a modal. A modal is closed by default. func (m *Modal) Open() { m.Config.Opened = true diff --git a/web/src/app/modules/shared/components/presentation/card/card.component.html b/web/src/app/modules/shared/components/presentation/card/card.component.html index 07a1406e1a..74923d8777 100644 --- a/web/src/app/modules/shared/components/presentation/card/card.component.html +++ b/web/src/app/modules/shared/components/presentation/card/card.component.html @@ -1,45 +1,56 @@ - +
+
+ +
+ +
+ +

{{ currentAction.title }}

- -
-
- -
-
- - {{ v.config.alert.message }} - -
-
-
-

- -

+ + + -
- + + + + + + +
+
+ + {{ v.config.alert.message }} +
+
+

+ +

- +
+
diff --git a/web/src/app/modules/shared/components/presentation/card/card.component.spec.ts b/web/src/app/modules/shared/components/presentation/card/card.component.spec.ts index 16021f1721..87dde1ee88 100644 --- a/web/src/app/modules/shared/components/presentation/card/card.component.spec.ts +++ b/web/src/app/modules/shared/components/presentation/card/card.component.spec.ts @@ -12,10 +12,13 @@ import { FormBuilder, FormGroup } from '@angular/forms'; import { ViewService } from '../../../services/view/view.service'; import { viewServiceStub } from 'src/app/testing/view-service.stub'; import { SharedModule } from '../../../shared.module'; +import { FormComponent } from '../form/form.component'; describe('CardComponent', () => { let component: CardComponent; let fixture: ComponentFixture; + let formComponent: FormComponent; + let formFixture: ComponentFixture; const formBuilder: FormBuilder = new FormBuilder(); const action: Action = { @@ -83,12 +86,17 @@ describe('CardComponent', () => { formGroupExample: 'justForTest', }); - component.onActionSubmit(formGroup); + formFixture = TestBed.createComponent(FormComponent); + formComponent = formFixture.componentInstance; + formComponent.formGroup = formGroup; + component.appForm = formComponent; + + component.onActionSubmit(); expect(component.currentAction).toBeUndefined(); }); it('should not submit action', () => { - component.onActionSubmit({} as FormGroup); + component.onActionSubmit(); expect(component.currentAction).toBeDefined(); }); @@ -117,10 +125,7 @@ describe('CardComponent', () => { it('should call "onActionCancel" method when cancelling the form', fakeAsync(() => { spyOn(component, 'onActionCancel'); - fixture.detectChanges(); - component.appForm.onFormCancel(); - tick(); - fixture.detectChanges(); + component.onActionCancel(); expect(component.onActionCancel).toHaveBeenCalled(); })); }); diff --git a/web/src/app/modules/shared/components/presentation/card/card.component.ts b/web/src/app/modules/shared/components/presentation/card/card.component.ts index 6bd22a0131..fd45fdbcf4 100644 --- a/web/src/app/modules/shared/components/presentation/card/card.component.ts +++ b/web/src/app/modules/shared/components/presentation/card/card.component.ts @@ -1,6 +1,5 @@ -import { Component, Input, ViewChild } from '@angular/core'; +import { Component, ViewChild } from '@angular/core'; import { Action, CardView, TitleView, View } from '../../../models/content'; -import { FormGroup } from '@angular/forms'; import { ActionService } from '../../../services/action/action.service'; import { FormComponent } from '../form/form.component'; import { AbstractViewComponent } from '../../abstract-view/abstract-view.component'; @@ -28,9 +27,9 @@ export class CardComponent extends AbstractViewComponent { this.body = this.v.config.body; } - onActionSubmit(formGroup: FormGroup) { - if (formGroup && formGroup.value) { - this.actionService.perform(formGroup.value); + onActionSubmit() { + if (this.appForm?.formGroup && this.appForm?.formGroup.value) { + this.actionService.perform(this.appForm.formGroup.value); this.currentAction = undefined; } } diff --git a/web/src/app/modules/shared/components/presentation/form/form.component.html b/web/src/app/modules/shared/components/presentation/form/form.component.html index ae4648167f..8d12a22aca 100644 --- a/web/src/app/modules/shared/components/presentation/form/form.component.html +++ b/web/src/app/modules/shared/components/presentation/form/form.component.html @@ -1,95 +1,91 @@ -
-
-
-

{{ title }}

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Unable to display form field type {{ field.type }} - - + + + + + + + + + + + {{ field.error }} + -
- -
+ + + + + + + + {{ field.error }} + + + + + + + {{ field.error }} + + + + + + + {{ field.error }} + + + + + + + {{ field.error }} + + + + + + + {{ field.error }} + + + + + + + {{ field.error }} + + + + + Unable to display form field type {{ field.type }} + + +
diff --git a/web/src/app/modules/shared/components/presentation/form/form.component.spec.ts b/web/src/app/modules/shared/components/presentation/form/form.component.spec.ts index f3bff5c13b..91fcfaeaf2 100644 --- a/web/src/app/modules/shared/components/presentation/form/form.component.spec.ts +++ b/web/src/app/modules/shared/components/presentation/form/form.component.spec.ts @@ -25,8 +25,6 @@ describe('FormComponent', () => { component.form = { fields: [], }; - component.title = 'Title'; - fixture.detectChanges(); }); diff --git a/web/src/app/modules/shared/components/presentation/form/form.component.ts b/web/src/app/modules/shared/components/presentation/form/form.component.ts index 1dc9416933..f0cb7053be 100644 --- a/web/src/app/modules/shared/components/presentation/form/form.component.ts +++ b/web/src/app/modules/shared/components/presentation/form/form.component.ts @@ -2,13 +2,13 @@ // SPDX-License-Identifier: Apache-2.0 // -import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core'; +import { Component, Input, OnInit, Output } from '@angular/core'; import { ActionField, ActionForm } from '../../../models/content'; import { - AbstractControl, FormBuilder, - FormControl, FormGroup, + ValidatorFn, + Validators, } from '@angular/forms'; interface Choice { @@ -26,44 +26,40 @@ export class FormComponent implements OnInit { @Input() form: ActionForm; - @Input() - title: string; - - @Output() - submit: EventEmitter = new EventEmitter(true); - - @Output() - cancel: EventEmitter = new EventEmitter(true); - formGroup: FormGroup; constructor(private formBuilder: FormBuilder) {} ngOnInit() { if (this.form) { - const controls: { [name: string]: AbstractControl } = {}; + const controls: { [name: string]: any } = {}; this.form.fields.forEach(field => { - const value = field.value; - controls[field.name] = new FormControl(value); + controls[field.name] = [ + field.value, + this.getValidators(field.validators), + ]; }); this.formGroup = this.formBuilder.group(controls); } } - onFormSubmit() { - this.submit.emit(this.formGroup); - } - - onFormCancel() { - this.cancel.emit(true); + getValidators(validators: string[]): ValidatorFn[] { + if (validators) { + const vFn: ValidatorFn[] = []; + validators.forEach(v => { + vFn.push(Validators[v]); + }); + return vFn; + } + return []; } fieldChoices(field: ActionField) { return field.configuration.choices as Choice[]; } - trackByFn(index, item) { + trackByFn(index, _) { return index; } } diff --git a/web/src/app/modules/shared/components/presentation/modal/modal.component.html b/web/src/app/modules/shared/components/presentation/modal/modal.component.html index 90a3c43023..cd0d6937ad 100644 --- a/web/src/app/modules/shared/components/presentation/modal/modal.component.html +++ b/web/src/app/modules/shared/components/presentation/modal/modal.component.html @@ -4,119 +4,24 @@ - - - - \ No newline at end of file diff --git a/web/src/app/modules/shared/components/presentation/modal/modal.component.ts b/web/src/app/modules/shared/components/presentation/modal/modal.component.ts index c93dc5b11a..623480886d 100644 --- a/web/src/app/modules/shared/components/presentation/modal/modal.component.ts +++ b/web/src/app/modules/shared/components/presentation/modal/modal.component.ts @@ -1,22 +1,17 @@ import { Component, ViewChild, OnInit, OnDestroy } from '@angular/core'; import { AbstractViewComponent } from '../../abstract-view/abstract-view.component'; import { - ActionField, TitleView, ModalView, View, ActionForm, + Button, } from '../../../models/content'; +import { FormComponent } from '../form/form.component'; import { ModalService } from '../../../services/modal/modal.service'; import { Subscription } from 'rxjs'; -import { - FormBuilder, - FormGroup, - ValidatorFn, - Validators, -} from '@angular/forms'; -import { ClrForm } from '@clr/angular'; import { WebsocketService } from '../../../services/websocket/websocket.service'; +import { ActionService } from '../../../services/action/action.service'; interface Choice { label: string; @@ -32,20 +27,20 @@ interface Choice { export class ModalComponent extends AbstractViewComponent implements OnInit, OnDestroy { - @ViewChild(ClrForm) clrForm: ClrForm; + @ViewChild('modalAppForm') modalAppForm: FormComponent; title: TitleView[]; body: View; form: ActionForm; opened = false; size: string; - formGroup: FormGroup; action: string; + buttons: Button[]; private modalSubscription: Subscription; constructor( - private formBuilder: FormBuilder, + private actionService: ActionService, private modalService: ModalService, private websocketService: WebsocketService ) { @@ -71,47 +66,22 @@ export class ModalComponent this.form = this.v.config.form; this.opened = this.v.config.opened; this.modalService.setState(this.opened); - - if (this.form) { - const controls: { [name: string]: any } = {}; - this.form.fields.forEach(field => { - controls[field.name] = [ - field.value, - this.getValidators(field.validators), - ]; - }); - this.action = this.form.action; - this.formGroup = this.formBuilder.group(controls); - } - } - - getValidators(validators: string[]): ValidatorFn[] { - if (validators) { - const vFn: ValidatorFn[] = []; - validators.forEach(v => { - vFn.push(Validators[v]); - }); - return vFn; - } - return []; - } - - trackByFn(index, _) { - return index; - } - - fieldChoices(field: ActionField) { - return field.configuration.choices as Choice[]; + this.action = this.form?.action; + this.buttons = this.v.config.buttons; } onFormSubmit() { - if (this.formGroup.invalid) { - this.clrForm.markAsTouched(); - } else { + if (this.modalAppForm && this.modalAppForm.formGroup.valid) { this.websocketService.sendMessage('action.octant.dev/performAction', { action: this.action, - formGroup: this.formGroup.value, + formGroup: this.modalAppForm.formGroup.value, }); + this.opened = false; } } + + onClick(payload: {}) { + this.actionService.perform(payload); + this.opened = false; + } } diff --git a/web/src/app/modules/shared/components/presentation/stepper/stepper.component.ts b/web/src/app/modules/shared/components/presentation/stepper/stepper.component.ts index b5f66fb6a2..7835d6a3b2 100644 --- a/web/src/app/modules/shared/components/presentation/stepper/stepper.component.ts +++ b/web/src/app/modules/shared/components/presentation/stepper/stepper.component.ts @@ -8,7 +8,6 @@ import { } from '@angular/forms'; import { WebsocketService } from '../../../services/websocket/websocket.service'; import { AbstractViewComponent } from '../../abstract-view/abstract-view.component'; -import { ModalService } from '../../../services/modal/modal.service'; interface Choice { label: string; diff --git a/web/src/app/modules/shared/components/presentation/summary/summary.component.html b/web/src/app/modules/shared/components/presentation/summary/summary.component.html index a468add054..5e11bfb433 100644 --- a/web/src/app/modules/shared/components/presentation/summary/summary.component.html +++ b/web/src/app/modules/shared/components/presentation/summary/summary.component.html @@ -1,45 +1,58 @@ - - +
+
+ +
+
+ + +
+ +
-
-
- -
-
-

{{ title }}

+
+

{{ title }}

- + - - - - - - - -
{{ item.header }} - -
-
- + + + + + + + +
{{ item.header }} + +
+ + + + + + + + \ No newline at end of file diff --git a/web/src/app/modules/shared/components/presentation/summary/summary.component.ts b/web/src/app/modules/shared/components/presentation/summary/summary.component.ts index 7900bc584e..fa303fb517 100644 --- a/web/src/app/modules/shared/components/presentation/summary/summary.component.ts +++ b/web/src/app/modules/shared/components/presentation/summary/summary.component.ts @@ -2,7 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 // -import { Component } from '@angular/core'; +import { Component, ViewChild } from '@angular/core'; import { Action, SummaryItem, @@ -10,6 +10,7 @@ import { } from 'src/app/modules/shared/models/content'; import { FormGroup } from '@angular/forms'; import { ActionService } from '../../../services/action/action.service'; +import { FormComponent } from '../form/form.component'; import { ViewService } from '../../../services/view/view.service'; import { AbstractViewComponent } from '../../abstract-view/abstract-view.component'; @@ -19,6 +20,8 @@ import { AbstractViewComponent } from '../../abstract-view/abstract-view.compone styleUrls: ['./summary.component.scss'], }) export class SummaryComponent extends AbstractViewComponent { + @ViewChild('appForm') appForm: FormComponent; + title: string; isLoading = false; @@ -48,9 +51,9 @@ export class SummaryComponent extends AbstractViewComponent { this.currentAction = action; } - onActionSubmit(formGroup: FormGroup) { - if (formGroup && formGroup.value) { - this.actionService.perform(formGroup.value); + onActionSubmit() { + if (this.appForm?.formGroup && this.appForm?.formGroup.value) { + this.actionService.perform(this.appForm.formGroup.value); this.currentAction = undefined; } } diff --git a/web/src/app/modules/shared/models/content.ts b/web/src/app/modules/shared/models/content.ts index d8981931ca..82017e9352 100644 --- a/web/src/app/modules/shared/models/content.ts +++ b/web/src/app/modules/shared/models/content.ts @@ -192,6 +192,7 @@ export interface ModalView extends View { opened: boolean; size?: string; form?: ActionForm; + buttons?: Button[]; }; }