diff --git a/projects/aca-folder-rules/assets/i18n/en.json b/projects/aca-folder-rules/assets/i18n/en.json index af73a207f..287676ae1 100644 --- a/projects/aca-folder-rules/assets/i18n/en.json +++ b/projects/aca-folder-rules/assets/i18n/en.json @@ -8,7 +8,7 @@ "CREATE": "Create", "CREATE_TITLE": "Create a rule", "UPDATE": "Update", - "UPDATE_TITLE": "Update a rule" + "UPDATE_TITLE": "Edit a rule" }, "RULE_DETAILS": { "LABEL": { @@ -38,7 +38,7 @@ "IS_ASYNCHRONOUS": "Run rule in the background", "DISABLE_RULE": "Disable rule", "ERROR_SCRIPT": "If errors occur run script", - "SELECT_ACTION": "Select action" + "NO_SCRIPT": "None" }, "COMPARATORS": { "EQUALS": "(=) Equals", diff --git a/projects/aca-folder-rules/src/lib/folder-rules.module.ts b/projects/aca-folder-rules/src/lib/folder-rules.module.ts index fab75c403..f519b98f8 100644 --- a/projects/aca-folder-rules/src/lib/folder-rules.module.ts +++ b/projects/aca-folder-rules/src/lib/folder-rules.module.ts @@ -37,7 +37,7 @@ import { RuleSimpleConditionUiComponent } from './rule-details/conditions/rule-s import { GenericErrorModule, PageLayoutModule } from '@alfresco/aca-shared'; import { BreadcrumbModule, DocumentListModule } from '@alfresco/adf-content-services'; import { RuleListItemUiComponent } from './rules-list/rule/rule-list-item.ui-component'; -import { RulesListUiComponent } from './rules-list/rules-list.ui-component'; +import { RuleListUiComponent } from './rules-list/rule-list.ui-component'; import { RuleTriggersUiComponent } from './rule-details/triggers/rule-triggers.ui-component'; import { RuleOptionsUiComponent } from './rule-details/options/rule-options.ui-component'; import { RuleActionListUiComponent } from './rule-details/actions/rule-action-list.ui-component'; @@ -70,7 +70,7 @@ const routes: Routes = [ RuleCompositeConditionUiComponent, RuleDetailsUiComponent, RuleSimpleConditionUiComponent, - RulesListUiComponent, + RuleListUiComponent, RuleListItemUiComponent, RuleTriggersUiComponent, RuleOptionsUiComponent diff --git a/projects/aca-folder-rules/src/lib/manage-rules/manage-rules.smart-component.html b/projects/aca-folder-rules/src/lib/manage-rules/manage-rules.smart-component.html index e8baf52f7..1ca8e1d07 100644 --- a/projects/aca-folder-rules/src/lib/manage-rules/manage-rules.smart-component.html +++ b/projects/aca-folder-rules/src/lib/manage-rules/manage-rules.smart-component.html @@ -30,29 +30,38 @@
- +
-
-
- {{ selectedRule.name }} -
- - + +
+
+
+ {{ selectedRule.name }} +
+
+ {{ selectedRule.description }}
-

{{ selectedRule.description }}

+ +
+ + +
+
+ +
+ +
- -
diff --git a/projects/aca-folder-rules/src/lib/manage-rules/manage-rules.smart-component.scss b/projects/aca-folder-rules/src/lib/manage-rules/manage-rules.smart-component.scss index a8e4850f2..add9b93cf 100644 --- a/projects/aca-folder-rules/src/lib/manage-rules/manage-rules.smart-component.scss +++ b/projects/aca-folder-rules/src/lib/manage-rules/manage-rules.smart-component.scss @@ -17,40 +17,65 @@ &__container { display: grid; - grid-template-columns: minmax(200px,1fr) 3fr; - padding: 32px; - overflow: scroll; + grid-template-columns: minmax(250px,1fr) 3fr; + padding: 20px; + gap: 12px; + overflow-y: auto; - &__preview { - padding: 0 20px; + &__rule-details { + border: 1px solid var(--theme-border-color); + border-radius: 12px; + overflow-y: scroll; - &__toolbar { + &__header { + position: sticky; + top: 0; + z-index: 1; display: flex; + flex-direction: row; align-items: center; justify-content: space-between; + padding: 12px 20px; + border-bottom: 1px solid var(--theme-border-color); + background-color: var(--theme-background-color); - span { - font-style: normal; - font-weight: 700; - font-size: 14px; - line-height: 20px; + &__title { + display: flex; + flex-direction: column; + gap: 4px; + + &__name { + font-size: 1.2em; + font-weight: bold; + } + + &__description { + font-size: 0.8em; + font-style: italic; + } } &__buttons { - display: inline-block; + display: flex; + flex-direction: row; + align-items: stretch; + gap: 4px; + + button { + color: var(--theme-text-color); + + &#delete-rule-btn { + padding: 0 8px; + min-width: unset; + } + + .mat-icon { + // Something pops out of this button for some reason so this is necessary + overflow: hidden !important; + } + } } } - - p { - font-style: normal; - font-weight: 400; - font-size: 12px; - line-height: 16px; - } - } - - &__rule-details { - overflow-x: scroll; } } } diff --git a/projects/aca-folder-rules/src/lib/manage-rules/manage-rules.smart-component.spec.ts b/projects/aca-folder-rules/src/lib/manage-rules/manage-rules.smart-component.spec.ts index 7869ee11a..7a9a52cf3 100644 --- a/projects/aca-folder-rules/src/lib/manage-rules/manage-rules.smart-component.spec.ts +++ b/projects/aca-folder-rules/src/lib/manage-rules/manage-rules.smart-component.spec.ts @@ -79,7 +79,7 @@ describe('ManageRulesSmartComponent', () => { expect(folderRulesService.loadRules).toHaveBeenCalledOnceWith(component.nodeId); - const rules = debugElement.queryAll(By.css('.aca-rule')); + const rules = debugElement.queryAll(By.css('.aca-rule-list-item')); const ruleDetails = debugElement.queryAll(By.css('aca-rule-details')); const deleteRuleBtn = debugElement.query(By.css('#delete-rule-btn')); diff --git a/projects/aca-folder-rules/src/lib/model/rule.model.ts b/projects/aca-folder-rules/src/lib/model/rule.model.ts index 872f54585..466989b0d 100644 --- a/projects/aca-folder-rules/src/lib/model/rule.model.ts +++ b/projects/aca-folder-rules/src/lib/model/rule.model.ts @@ -41,3 +41,20 @@ export interface Rule { conditions: RuleCompositeCondition; actions: RuleAction[]; } + +export interface RuleOptions { + isEnabled: boolean; + isInheritable: boolean; + isAsynchronous: boolean; + errorScript: string; +} + +export interface RuleForForm { + id: string; + name: string; + description: string; + triggers: RuleTrigger[]; + conditions: RuleCompositeCondition; + actions: RuleAction[]; + options: RuleOptions; +} diff --git a/projects/aca-folder-rules/src/lib/rule-details/options/rule-options.ui-component.html b/projects/aca-folder-rules/src/lib/rule-details/options/rule-options.ui-component.html index 582d3b1b6..10d961170 100644 --- a/projects/aca-folder-rules/src/lib/rule-details/options/rule-options.ui-component.html +++ b/projects/aca-folder-rules/src/lib/rule-details/options/rule-options.ui-component.html @@ -1,34 +1,36 @@ -
-
+ +
+ (change)="toggleErrorScriptDropdown($event)" + data-automation-id="rule-option-checkbox-asynchronous"> {{ 'ACA_FOLDER_RULES.RULE_DETAILS.OPTIONS.IS_ASYNCHRONOUS' | translate }} -
- {{ 'ACA_FOLDER_RULES.RULE_DETAILS.OPTIONS.ERROR_SCRIPT' | translate}}: - - - {{ 'ACA_FOLDER_RULES.RULE_DETAILS.OPTIONS.SELECT_ACTION' | translate}} - - -
+ + + + {{ 'ACA_FOLDER_RULES.RULE_DETAILS.OPTIONS.NO_SCRIPT' | translate }} + +
-
+ + +
+ data-automation-id="rule-option-checkbox-inheritable"> {{ 'ACA_FOLDER_RULES.RULE_DETAILS.OPTIONS.IS_INHERITABLE' | translate }} +
+ +
+ formControlName="isDisabled" + data-automation-id="rule-option-checkbox-disabled"> {{ 'ACA_FOLDER_RULES.RULE_DETAILS.OPTIONS.DISABLE_RULE' | translate }}
-
- +
diff --git a/projects/aca-folder-rules/src/lib/rule-details/options/rule-options.ui-component.scss b/projects/aca-folder-rules/src/lib/rule-details/options/rule-options.ui-component.scss index 11783d8b3..34a18c083 100644 --- a/projects/aca-folder-rules/src/lib/rule-details/options/rule-options.ui-component.scss +++ b/projects/aca-folder-rules/src/lib/rule-details/options/rule-options.ui-component.scss @@ -1,20 +1,14 @@ -.options-list { +.aca-rule-options { display: flex; - column-gap: 25px; - padding: 0.75em 0; + flex-direction: row; + gap: 24px; - &__rest { + &__option { display: flex; - flex-wrap: wrap; - column-gap: 25px; - } -} - -.select-action { - margin-left: 5px; - margin-top: 12px; - - span { - display: block; + flex-direction: column; + } + + &.read-only .mat-checkbox-inner-container { + display: none; } } diff --git a/projects/aca-folder-rules/src/lib/rule-details/options/rule-options.ui-component.spec.ts b/projects/aca-folder-rules/src/lib/rule-details/options/rule-options.ui-component.spec.ts index 4990e1157..51c4caa6a 100644 --- a/projects/aca-folder-rules/src/lib/rule-details/options/rule-options.ui-component.spec.ts +++ b/projects/aca-folder-rules/src/lib/rule-details/options/rule-options.ui-component.spec.ts @@ -23,16 +23,16 @@ * along with Alfresco. If not, see . */ -import { ComponentFixture, TestBed, inject, waitForAsync } from '@angular/core/testing'; +import { ComponentFixture, TestBed } from '@angular/core/testing'; import { CUSTOM_ELEMENTS_SCHEMA, DebugElement } from '@angular/core'; -import { FormsModule, ReactiveFormsModule, FormBuilder } from '@angular/forms'; +import { FormsModule, ReactiveFormsModule } from '@angular/forms'; import { RuleOptionsUiComponent } from './rule-options.ui-component'; import { CoreTestingModule } from '@alfresco/adf-core'; import { By } from '@angular/platform-browser'; describe('RuleOptionsUiComponent', () => { - let component: RuleOptionsUiComponent; let fixture: ComponentFixture; + let component: RuleOptionsUiComponent; const getByDataAutomationId = (dataAutomationId: string): DebugElement => fixture.debugElement.query(By.css(`[data-automation-id="${dataAutomationId}"]`)); @@ -41,54 +41,75 @@ describe('RuleOptionsUiComponent', () => { ((getByDataAutomationId(dataAutomationId).nativeElement as HTMLElement).children[0] as HTMLElement).click(); }; - beforeEach( - waitForAsync(() => { - TestBed.configureTestingModule({ - schemas: [CUSTOM_ELEMENTS_SCHEMA], - imports: [FormsModule, ReactiveFormsModule, CoreTestingModule], - declarations: [RuleOptionsUiComponent] - }).compileComponents(); - }) - ); + beforeEach(() => { + TestBed.configureTestingModule({ + schemas: [CUSTOM_ELEMENTS_SCHEMA], + imports: [FormsModule, ReactiveFormsModule, CoreTestingModule], + declarations: [RuleOptionsUiComponent] + }); - beforeEach(inject([FormBuilder], (fb: FormBuilder) => { fixture = TestBed.createComponent(RuleOptionsUiComponent); component = fixture.componentInstance; - component.form = fb.group({ - isAsynchronous: [false], - isInheritable: [false], - isEnabled: [true], - errorScript: [''] + component.writeValue({ + isEnabled: true, + isInheritable: false, + isAsynchronous: false, + errorScript: '' }); - fixture.detectChanges(); - })); + }); - it('checkboxes should be falsy by default, selector is disabled', () => { - expect(component).toBeTruthy(); + it('should be able to write to the component', () => { + fixture.detectChanges(); expect(getByDataAutomationId('rule-option-checkbox-asynchronous').componentInstance.checked).toBeFalsy(); expect(getByDataAutomationId('rule-option-checkbox-inheritable').componentInstance.checked).toBeFalsy(); - expect(getByDataAutomationId('rule-option-checkbox-enabled').componentInstance.checked).toBeFalsy(); + expect(getByDataAutomationId('rule-option-checkbox-disabled').componentInstance.checked).toBeFalsy(); expect(getByDataAutomationId('rule-option-select-errorScript').componentInstance.disabled).toBeTruthy(); + + component.writeValue({ + isEnabled: false, + isInheritable: true, + isAsynchronous: true, + errorScript: '' + }); + fixture.detectChanges(); + + expect(getByDataAutomationId('rule-option-checkbox-asynchronous').componentInstance.checked).toBeTruthy(); + expect(getByDataAutomationId('rule-option-checkbox-inheritable').componentInstance.checked).toBeTruthy(); + expect(getByDataAutomationId('rule-option-checkbox-disabled').componentInstance.checked).toBeTruthy(); + expect(getByDataAutomationId('rule-option-select-errorScript').componentInstance.disabled).toBeFalsy(); }); it('should enable selector when async checkbox is truthy', () => { - toggleMatCheckbox('rule-option-checkbox-asynchronous'); - fixture.detectChanges(); + toggleMatCheckbox('rule-option-checkbox-asynchronous'); + fixture.detectChanges(); + expect(getByDataAutomationId('rule-option-checkbox-asynchronous').componentInstance.checked).toBeTruthy(); expect(getByDataAutomationId('rule-option-select-errorScript').componentInstance.disabled).toBeFalsy(); }); - it('should hide some fields in preview mode', () => { - component.preview = true; + it('should hide disabled checkbox and unselected checkboxes in read-only mode', () => { + component.readOnly = true; + fixture.detectChanges(); + + expect(getByDataAutomationId('rule-option-checkbox-asynchronous')).toBeFalsy(); + expect(getByDataAutomationId('rule-option-checkbox-inheritable')).toBeFalsy(); + expect(getByDataAutomationId('rule-option-checkbox-enabled')).toBeFalsy(); + expect(getByDataAutomationId('rule-option-select-errorScript')).toBeFalsy(); + + component.writeValue({ + isEnabled: false, + isInheritable: true, + isAsynchronous: true, + errorScript: '' + }); fixture.detectChanges(); - expect(getByDataAutomationId('rule-option-checkbox-asynchronous')).toBeTruthy(); expect(getByDataAutomationId('rule-option-checkbox-asynchronous')).toBeTruthy(); expect(getByDataAutomationId('rule-option-checkbox-inheritable')).toBeTruthy(); expect(getByDataAutomationId('rule-option-checkbox-enabled')).toBeFalsy(); - expect(getByDataAutomationId('rule-option-select-errorScript')).toBeFalsy(); + expect(getByDataAutomationId('rule-option-select-errorScript')).toBeTruthy(); }); }); diff --git a/projects/aca-folder-rules/src/lib/rule-details/options/rule-options.ui-component.ts b/projects/aca-folder-rules/src/lib/rule-details/options/rule-options.ui-component.ts index de7a28683..8376acb3f 100644 --- a/projects/aca-folder-rules/src/lib/rule-details/options/rule-options.ui-component.ts +++ b/projects/aca-folder-rules/src/lib/rule-details/options/rule-options.ui-component.ts @@ -23,20 +23,98 @@ * along with Alfresco. If not, see . */ -import { Component, Input } from '@angular/core'; -import { FormGroup } from '@angular/forms'; +import { Component, forwardRef, HostBinding, OnDestroy, ViewEncapsulation } from '@angular/core'; +import { AbstractControl, ControlValueAccessor, FormControl, FormGroup, NG_VALUE_ACCESSOR } from '@angular/forms'; +import { MatCheckboxChange } from '@angular/material/checkbox'; +import { RuleOptions } from '../../model/rule.model'; @Component({ selector: 'aca-rule-options', templateUrl: 'rule-options.ui-component.html', - styleUrls: ['rule-options.ui-component.scss'] + styleUrls: ['rule-options.ui-component.scss'], + encapsulation: ViewEncapsulation.None, + host: { class: 'aca-rule-options' }, + providers: [ + { + provide: NG_VALUE_ACCESSOR, + multi: true, + useExisting: forwardRef(() => RuleOptionsUiComponent) + } + ] }) -export class RuleOptionsUiComponent { - @Input() form: FormGroup; - @Input() preview: boolean; - disableSelector = true; +export class RuleOptionsUiComponent implements ControlValueAccessor, OnDestroy { + form = new FormGroup({ + isDisabled: new FormControl(), + isInheritable: new FormControl(), + isAsynchronous: new FormControl(), + errorScript: new FormControl() + }); - toggleScriptSelector() { - this.disableSelector = !this.disableSelector; + formSubscription = this.form.valueChanges.subscribe((value: any) => { + this.onChange({ + isEnabled: !value.isDisabled, + isInheritable: value.isInheritable, + isAsynchronous: value.isAsynchronous, + errorScript: value.errorScript ?? '' + }); + this.onTouch(); + }); + + @HostBinding('class.read-only') + readOnly = false; + + onChange: (options: RuleOptions) => void = () => undefined; + onTouch: () => void = () => undefined; + + get isAsynchronousChecked(): boolean { + return this.form.get('isAsynchronous').value; + } + get isInheritableChecked(): boolean { + return this.form.get('isInheritable').value; + } + + writeValue(options: RuleOptions) { + const isAsynchronousFormControl = this.form.get('isAsynchronous'); + const errorScriptFormControl = this.form.get('errorScript'); + this.form.get('isDisabled').setValue(!options.isEnabled); + this.form.get('isInheritable').setValue(options.isInheritable); + this.form.get('isAsynchronous').setValue(options.isAsynchronous); + errorScriptFormControl.setValue(options.errorScript ?? ''); + if (isAsynchronousFormControl.value) { + errorScriptFormControl.enable(); + } else { + errorScriptFormControl.disable(); + } + } + + registerOnChange(fn: () => void) { + this.onChange = fn; + } + + registerOnTouched(fn: () => void) { + this.onTouch = fn; + } + + setDisabledState(isDisabled: boolean) { + if (isDisabled) { + this.form.disable(); + this.readOnly = true; + } else { + this.form.enable(); + this.readOnly = false; + } + } + + ngOnDestroy() { + this.formSubscription.unsubscribe(); + } + + toggleErrorScriptDropdown(value: MatCheckboxChange) { + const formControl: AbstractControl = this.form.get('errorScript'); + if (value.checked) { + formControl.enable(); + } else { + formControl.disable(); + } } } diff --git a/projects/aca-folder-rules/src/lib/rule-details/rule-details.ui-component.html b/projects/aca-folder-rules/src/lib/rule-details/rule-details.ui-component.html index b0e4c9c00..2b105e991 100644 --- a/projects/aca-folder-rules/src/lib/rule-details/rule-details.ui-component.html +++ b/projects/aca-folder-rules/src/lib/rule-details/rule-details.ui-component.html @@ -1,7 +1,7 @@ -
+ -
+
@@ -14,7 +14,7 @@
-
+
@@ -28,8 +28,6 @@
-
-
{{ 'ACA_FOLDER_RULES.RULE_DETAILS.LABEL.WHEN' | translate }}
@@ -38,12 +36,10 @@
-
- - - {{ getErrorMessage(conditions) | translate }} - -
+
+ + {{ getErrorMessage(conditions) | translate }} +
{{ 'ACA_FOLDER_RULES.RULE_DETAILS.LABEL.PERFORM_ACTIONS' | translate }}
@@ -55,11 +51,9 @@
-
- -
+
{{ 'ACA_FOLDER_RULES.RULE_DETAILS.LABEL.OPTIONS' | translate }}
- +
diff --git a/projects/aca-folder-rules/src/lib/rule-details/rule-details.ui-component.scss b/projects/aca-folder-rules/src/lib/rule-details/rule-details.ui-component.scss index fff9c0de2..3a120fc98 100644 --- a/projects/aca-folder-rules/src/lib/rule-details/rule-details.ui-component.scss +++ b/projects/aca-folder-rules/src/lib/rule-details/rule-details.ui-component.scss @@ -1,9 +1,31 @@ .aca-rule-details { &__form { - padding: 20px; + position: relative; + + & > div { + padding: 8px 20px; + width: 100%; + box-sizing: border-box; + overflow-x: auto; + + &:not(:nth-child(1)) { + border-top: 1px solid var(--theme-border-color); + } + + &.aca-rule-details__form__name { + padding-bottom: 0; + } + + &.aca-rule-details__form__description { + padding-top: 0; + border: none; + align-items: flex-start; + } + } &__row { display: flex; + align-items: baseline; gap: 8px; & > label, & > .label { @@ -11,7 +33,7 @@ width: 20%; min-width: 100px; max-width: 150px; - padding-top: 0.75em; + padding: 0.75em 0; } & > div { @@ -19,6 +41,7 @@ mat-form-field { width: 100%; + max-width: 400px; font-size: inherit; .mat-form-field-infix { @@ -28,23 +51,15 @@ } } - &__triggers { - padding: 0.75em 0; + &__conditions { + width: 100%; - & > .label { - padding: 0; + & > .rule-details-error { + margin-left: 16px; + color: inherit; } } - hr { - border: none; - border-bottom: 1px solid var(--theme-border-color); - } - - *:disabled, .mat-select-disabled .mat-select-value { - color: inherit; - } - textarea { min-height: 4em; } @@ -54,15 +69,20 @@ height: 1em; } - & > .aca-rule-composite-condition + .rule-details-error { - margin-left: 16px; - color: inherit; - } - &__actions { .aca-rule-action-list { flex: 1; } } + + &.read-only, .mat-form-field-disabled { + .mat-form-field-underline, .mat-select-arrow-wrapper { + display: none; + } + + *:disabled, .mat-select-disabled .mat-select-value { + color: inherit; + } + } } } diff --git a/projects/aca-folder-rules/src/lib/rule-details/rule-details.ui-component.spec.ts b/projects/aca-folder-rules/src/lib/rule-details/rule-details.ui-component.spec.ts index 1cafd93d6..1cc4766ba 100644 --- a/projects/aca-folder-rules/src/lib/rule-details/rule-details.ui-component.spec.ts +++ b/projects/aca-folder-rules/src/lib/rule-details/rule-details.ui-component.spec.ts @@ -30,7 +30,6 @@ import { Rule } from '../model/rule.model'; import { By } from '@angular/platform-browser'; import { RuleCompositeConditionUiComponent } from './conditions/rule-composite-condition.ui-component'; import { RuleTriggersUiComponent } from './triggers/rule-triggers.ui-component'; -import { MatCheckbox } from '@angular/material/checkbox'; import { RuleOptionsUiComponent } from './options/rule-options.ui-component'; import { RuleActionListUiComponent } from './actions/rule-action-list.ui-component'; import { RuleActionUiComponent } from './actions/rule-action.ui-component'; @@ -46,7 +45,8 @@ describe('RuleDetailsUiComponent', () => { triggers: ['update', 'outbound'], isAsynchronous: true, isInheritable: true, - isEnabled: true + isEnabled: true, + errorScript: '' }; const getHtmlElement = (dataAutomationId: string) => @@ -79,16 +79,17 @@ describe('RuleDetailsUiComponent', () => { const nameInput = getHtmlElement('rule-details-name-input'); const descriptionTextarea = getHtmlElement('rule-details-description-textarea'); const ruleTriggersComponent = getComponentInstance('rule-details-triggers-component'); - const ruleOptionAsynchronous = getComponentInstance('rule-option-checkbox-asynchronous'); - const ruleOptionInheritable = getComponentInstance('rule-option-checkbox-inheritable'); - const ruleOptionDisabled = getComponentInstance('rule-option-checkbox-enabled'); + const ruleOptionsComponent = getComponentInstance('rule-details-options-component'); expect(nameInput.value).toBe(testValue.name); expect(descriptionTextarea.value).toBe(testValue.description); expect(ruleTriggersComponent.value).toEqual(testValue.triggers); - expect(ruleOptionAsynchronous.checked).toBe(testValue.isAsynchronous); - expect(ruleOptionInheritable.checked).toBe(testValue.isInheritable); - expect(ruleOptionDisabled.checked).toBe(!testValue.isEnabled); + expect(ruleOptionsComponent.form.value).toEqual({ + isDisabled: !testValue.isEnabled, + isInheritable: testValue.isInheritable, + isAsynchronous: testValue.isAsynchronous, + errorScript: testValue.errorScript + }); }); it('should modify the form if the value input property is modified', () => { @@ -109,38 +110,46 @@ describe('RuleDetailsUiComponent', () => { }); it('should be editable if not read-only', () => { + component.value = testValue; component.readOnly = false; fixture.detectChanges(); const nameInput = getHtmlElement('rule-details-name-input'); const descriptionTextarea = getHtmlElement('rule-details-description-textarea'); const ruleTriggersComponent = getComponentInstance('rule-details-triggers-component'); - const ruleOptionAsynchronous = getComponentInstance('rule-option-checkbox-asynchronous'); - const ruleOptionInheritable = getComponentInstance('rule-option-checkbox-inheritable'); - const ruleOptionDisabled = getComponentInstance('rule-option-checkbox-enabled'); + const ruleOptionsComponent = getComponentInstance('rule-details-options-component'); expect(nameInput.disabled).toBeFalsy(); expect(descriptionTextarea.disabled).toBeFalsy(); expect(ruleTriggersComponent.readOnly).toBeFalsy(); - expect(ruleOptionAsynchronous.disabled).toBeFalsy(); - expect(ruleOptionInheritable.disabled).toBeFalsy(); - expect(ruleOptionDisabled.disabled).toBeFalsy(); + expect(ruleOptionsComponent.readOnly).toBeFalsy(); }); it('should not be editable if read-only', () => { + component.value = testValue; component.readOnly = true; fixture.detectChanges(); const nameInput = getHtmlElement('rule-details-name-input'); const descriptionTextarea = getHtmlElement('rule-details-description-textarea'); const ruleTriggersComponent = getComponentInstance('rule-details-triggers-component'); - const ruleOptionAsynchronous = getComponentInstance('rule-option-checkbox-asynchronous'); - const ruleOptionInheritable = getComponentInstance('rule-option-checkbox-inheritable'); + const ruleOptionsComponent = getComponentInstance('rule-details-options-component'); expect(nameInput.disabled).toBeTruthy(); expect(descriptionTextarea.disabled).toBeTruthy(); expect(ruleTriggersComponent.readOnly).toBeTruthy(); - expect(ruleOptionAsynchronous.disabled).toBeTruthy(); - expect(ruleOptionInheritable.disabled).toBeTruthy(); + expect(ruleOptionsComponent.readOnly).toBeTruthy(); + }); + + it('should hide the options section entirely in read-only mode if it has no selected options', () => { + component.value = { + ...testValue, + isInheritable: false, + isAsynchronous: false + }; + component.readOnly = true; + fixture.detectChanges(); + + expect(getComponentInstance('rule-details-options-component')).toBeFalsy(); }); }); diff --git a/projects/aca-folder-rules/src/lib/rule-details/rule-details.ui-component.ts b/projects/aca-folder-rules/src/lib/rule-details/rule-details.ui-component.ts index e570302a8..f8f754052 100644 --- a/projects/aca-folder-rules/src/lib/rule-details/rule-details.ui-component.ts +++ b/projects/aca-folder-rules/src/lib/rule-details/rule-details.ui-component.ts @@ -27,7 +27,7 @@ import { Component, EventEmitter, Input, OnDestroy, OnInit, Output, ViewEncapsul import { AbstractControl, UntypedFormGroup, UntypedFormControl, Validators } from '@angular/forms'; import { Subject } from 'rxjs'; import { distinctUntilChanged, map, takeUntil } from 'rxjs/operators'; -import { Rule } from '../model/rule.model'; +import { Rule, RuleForForm } from '../model/rule.model'; import { ruleCompositeConditionValidator } from './validators/rule-composite-condition.validator'; import { FolderRulesService } from '../services/folder-rules.service'; import { ActionDefinitionTransformed } from '../model/rule-action.model'; @@ -57,28 +57,38 @@ export class RuleDetailsUiComponent implements OnInit, OnDestroy { } } } - private _initialValue: Partial = FolderRulesService.emptyRule; + private _initialValue: RuleForForm = FolderRulesService.emptyRuleForForm; @Input() get value(): Partial { - return this.form ? this.form.value : this._initialValue; + let value = this.form ? this.form.value : this._initialValue; + if (value.options) { + value = { + ...value, + ...(value.options ?? FolderRulesService.emptyRuleOptions) + }; + delete value.options; + } + return value; } set value(newValue: Partial) { - newValue = { + const newValueForForm: RuleForForm = { id: newValue.id || FolderRulesService.emptyRule.id, name: newValue.name || FolderRulesService.emptyRule.name, description: newValue.description || FolderRulesService.emptyRule.description, triggers: newValue.triggers || FolderRulesService.emptyRule.triggers, conditions: newValue.conditions || FolderRulesService.emptyRule.conditions, - isAsynchronous: newValue.isAsynchronous || FolderRulesService.emptyRule.isAsynchronous, - errorScript: newValue.errorScript || FolderRulesService.emptyRule.errorScript, - isInheritable: newValue.isInheritable || FolderRulesService.emptyRule.isInheritable, - isEnabled: typeof newValue.isInheritable == 'boolean' ? newValue.isEnabled : FolderRulesService.emptyRule.isEnabled, - actions: newValue.actions || FolderRulesService.emptyRule.actions + actions: newValue.actions || FolderRulesService.emptyRule.actions, + options: { + isEnabled: typeof newValue.isInheritable == 'boolean' ? newValue.isEnabled : FolderRulesService.emptyRule.isEnabled, + isInheritable: newValue.isInheritable || FolderRulesService.emptyRule.isInheritable, + isAsynchronous: newValue.isAsynchronous || FolderRulesService.emptyRule.isAsynchronous, + errorScript: newValue.errorScript || FolderRulesService.emptyRule.errorScript + } }; if (this.form) { - this.form.setValue(newValue); + this.form.setValue(newValueForForm); } else { - this._initialValue = newValue; + this._initialValue = newValueForForm; } } @Input() @@ -108,17 +118,9 @@ export class RuleDetailsUiComponent implements OnInit, OnDestroy { get conditions(): UntypedFormControl { return this.form.get('conditions') as UntypedFormControl; } - get isAsynchronous(): UntypedFormControl { - return this.form.get('isAsynchronous') as UntypedFormControl; - } - get errorScript(): UntypedFormControl { - return this.form.get('errorScript') as UntypedFormControl; - } - get isInheritable(): UntypedFormControl { - return this.form.get('isInheritable') as UntypedFormControl; - } - get isEnabled(): UntypedFormControl { - return this.form.get('isEnabled') as UntypedFormControl; + + get showOptionsSection(): boolean { + return !this.readOnly || this.value.isAsynchronous || this.value.isInheritable; } ngOnInit() { @@ -136,11 +138,13 @@ export class RuleDetailsUiComponent implements OnInit, OnDestroy { }, ruleCompositeConditionValidator() ), - isAsynchronous: new UntypedFormControl(this.value.isAsynchronous), - errorScript: new UntypedFormControl(this.value.errorScript), - isInheritable: new UntypedFormControl(this.value.isInheritable), - isEnabled: new UntypedFormControl(this.value.isEnabled), - actions: new UntypedFormControl(this.value.actions, [Validators.required, ruleActionsValidator(this.actionDefinitions)]) + actions: new UntypedFormControl(this.value.actions, [Validators.required, ruleActionsValidator(this.actionDefinitions)]), + options: new UntypedFormControl({ + isEnabled: this.value.isEnabled, + isInheritable: this.value.isInheritable, + isAsynchronous: this.value.isAsynchronous, + errorScript: this.value.errorScript + }) }); this.readOnly = this._readOnly; @@ -155,8 +159,8 @@ export class RuleDetailsUiComponent implements OnInit, OnDestroy { }); this.formValidationChanged.emit(this.form.valid); - this.form.valueChanges.pipe(takeUntil(this.onDestroy$)).subscribe((newFormValue: any) => { - this.formValueChanged.emit(newFormValue); + this.form.valueChanges.pipe(takeUntil(this.onDestroy$)).subscribe(() => { + this.formValueChanged.emit(this.value); }); } diff --git a/projects/aca-folder-rules/src/lib/rule-details/triggers/rule-triggers.ui-component.html b/projects/aca-folder-rules/src/lib/rule-details/triggers/rule-triggers.ui-component.html index b8c0ec63d..dc03cc0b3 100644 --- a/projects/aca-folder-rules/src/lib/rule-details/triggers/rule-triggers.ui-component.html +++ b/projects/aca-folder-rules/src/lib/rule-details/triggers/rule-triggers.ui-component.html @@ -11,7 +11,6 @@ {{ 'ACA_FOLDER_RULES.RULE_DETAILS.TRIGGERS.' + trigger | uppercase | translate }} diff --git a/projects/aca-folder-rules/src/lib/rule-details/triggers/rule-triggers.ui-component.ts b/projects/aca-folder-rules/src/lib/rule-details/triggers/rule-triggers.ui-component.ts index 50ad3f7dc..295f39bfd 100644 --- a/projects/aca-folder-rules/src/lib/rule-details/triggers/rule-triggers.ui-component.ts +++ b/projects/aca-folder-rules/src/lib/rule-details/triggers/rule-triggers.ui-component.ts @@ -57,7 +57,7 @@ export class RuleTriggersUiComponent implements ControlValueAccessor { this.onChange = fn; } - registerOnTouched(fn: any) { + registerOnTouched(fn: () => void) { this.onTouch = fn; } diff --git a/projects/aca-folder-rules/src/lib/rules-list/rule-list.ui-component.html b/projects/aca-folder-rules/src/lib/rules-list/rule-list.ui-component.html new file mode 100644 index 000000000..29d28ab23 --- /dev/null +++ b/projects/aca-folder-rules/src/lib/rules-list/rule-list.ui-component.html @@ -0,0 +1,10 @@ +
+ + +
diff --git a/projects/aca-folder-rules/src/lib/rules-list/rule-list.ui-component.scss b/projects/aca-folder-rules/src/lib/rules-list/rule-list.ui-component.scss new file mode 100644 index 000000000..085fb2c3a --- /dev/null +++ b/projects/aca-folder-rules/src/lib/rules-list/rule-list.ui-component.scss @@ -0,0 +1,5 @@ +.aca-rule-list { + display: flex; + flex-direction: column; + gap: 4px; +} diff --git a/projects/aca-folder-rules/src/lib/rules-list/rules-list.ui-component.spec.ts b/projects/aca-folder-rules/src/lib/rules-list/rule-list.ui-component.spec.ts similarity index 76% rename from projects/aca-folder-rules/src/lib/rules-list/rules-list.ui-component.spec.ts rename to projects/aca-folder-rules/src/lib/rules-list/rule-list.ui-component.spec.ts index 04b6f990d..47fc8ee6c 100644 --- a/projects/aca-folder-rules/src/lib/rules-list/rules-list.ui-component.spec.ts +++ b/projects/aca-folder-rules/src/lib/rules-list/rule-list.ui-component.spec.ts @@ -24,25 +24,25 @@ */ import { ComponentFixture, TestBed } from '@angular/core/testing'; -import { RulesListUiComponent } from './rules-list.ui-component'; +import { RuleListUiComponent } from './rule-list.ui-component'; import { dummyRules } from '../mock/rules.mock'; import { DebugElement } from '@angular/core'; import { By } from '@angular/platform-browser'; import { CoreTestingModule } from '@alfresco/adf-core'; import { AcaFolderRulesModule } from '@alfresco/aca-folder-rules'; -describe('RulesListComponent', () => { - let component: RulesListUiComponent; - let fixture: ComponentFixture; +describe('RuleListComponent', () => { + let component: RuleListUiComponent; + let fixture: ComponentFixture; let debugElement: DebugElement; beforeEach(() => { TestBed.configureTestingModule({ imports: [CoreTestingModule, AcaFolderRulesModule], - declarations: [RulesListUiComponent] + declarations: [RuleListUiComponent] }); - fixture = TestBed.createComponent(RulesListUiComponent); + fixture = TestBed.createComponent(RuleListUiComponent); component = fixture.componentInstance; debugElement = fixture.debugElement; }); @@ -54,17 +54,17 @@ describe('RulesListComponent', () => { fixture.detectChanges(); - const rules = debugElement.queryAll(By.css('.aca-rule')); + const rules = debugElement.queryAll(By.css('.aca-rule-list-item')); expect(rules).toBeTruthy('Could not find rules'); expect(rules.length).toBe(2, 'Unexpected number of rules'); - const rule = debugElement.query(By.css('.aca-rule:first-child')); - const title = rule.query(By.css('.rule-info__header__title')); - const description = rule.query(By.css('p')); + const rule = debugElement.query(By.css('.aca-rule-list-item:first-child')); + const name = rule.query(By.css('.aca-rule-list-item__header__name')); + const description = rule.query(By.css('.aca-rule-list-item__description')); const toggleBtn = rule.query(By.css('mat-slide-toggle')); - expect(title.nativeElement.textContent).toBe(dummyRules[0].name); + expect(name.nativeElement.textContent).toBe(dummyRules[0].name); expect(toggleBtn).toBeTruthy(); expect(description.nativeElement.textContent).toBe(dummyRules[0].description); }); diff --git a/projects/aca-folder-rules/src/lib/rules-list/rules-list.ui-component.ts b/projects/aca-folder-rules/src/lib/rules-list/rule-list.ui-component.ts similarity index 82% rename from projects/aca-folder-rules/src/lib/rules-list/rules-list.ui-component.ts rename to projects/aca-folder-rules/src/lib/rules-list/rule-list.ui-component.ts index d82b2c8ab..de21bee4f 100644 --- a/projects/aca-folder-rules/src/lib/rules-list/rules-list.ui-component.ts +++ b/projects/aca-folder-rules/src/lib/rules-list/rule-list.ui-component.ts @@ -23,21 +23,21 @@ * along with Alfresco. If not, see . */ -import { Component, EventEmitter, Input, Output } from '@angular/core'; +import { Component, EventEmitter, Input, Output, ViewEncapsulation } from '@angular/core'; import { Rule } from '../model/rule.model'; @Component({ - selector: 'aca-rules-list', - templateUrl: 'rules-list.ui-component.html', - styleUrls: ['rules-list.ui-component.scss'] + selector: 'aca-rule-list', + templateUrl: 'rule-list.ui-component.html', + styleUrls: ['rule-list.ui-component.scss'], + encapsulation: ViewEncapsulation.None, + host: { class: 'aca-rule-list' } }) -export class RulesListUiComponent { +export class RuleListUiComponent { @Input() rules: Rule[]; - @Input() selectedRule: Rule; - @Input() nodeId: string; diff --git a/projects/aca-folder-rules/src/lib/rules-list/rule/rule-list-item.ui-component.html b/projects/aca-folder-rules/src/lib/rules-list/rule/rule-list-item.ui-component.html index fe3f5abf4..b94a1b365 100644 --- a/projects/aca-folder-rules/src/lib/rules-list/rule/rule-list-item.ui-component.html +++ b/projects/aca-folder-rules/src/lib/rules-list/rule/rule-list-item.ui-component.html @@ -1,9 +1,5 @@ -
-
-
- {{rule.name}} - -
-

{{rule.description}}

-
-
+
+ {{ rule.name }} + +
+
{{ rule.description }}
diff --git a/projects/aca-folder-rules/src/lib/rules-list/rule/rule-list-item.ui-component.scss b/projects/aca-folder-rules/src/lib/rules-list/rule/rule-list-item.ui-component.scss index f73e02d79..ee9c4c174 100644 --- a/projects/aca-folder-rules/src/lib/rules-list/rule/rule-list-item.ui-component.scss +++ b/projects/aca-folder-rules/src/lib/rules-list/rule/rule-list-item.ui-component.scss @@ -1,11 +1,13 @@ -.aca-rule{ +.aca-rule-list-item { display: flex; - padding: 16px 24px; + flex-direction: column; + gap: 4px; + padding: 12px 20px; border-radius: 12px; margin-bottom: 8px; cursor: pointer; - p{ + p { margin: 6px 0 0 0; color: rgba(33, 35, 40, 0.7); font-style: normal; @@ -13,24 +15,24 @@ font-size: 12px; line-height: 16px; } -} -.rule-info{ - width: 100%; - - &__header{ + &__header { display: flex; justify-content: space-between; + align-items: flex-end; - &__title{ - font-weight: 900; - font-size: 14px; - color: #212121; - line-height: 20px; + &__name { + font-size: 1.2em; + font-weight: bold; } } -} -.selected{ - background: rgba(31, 116, 219, 0.24); + &__description { + font-size: 0.8em; + font-style: italic; + } + + &.selected { + background: var(--theme-selected-button-bg-color); + } } diff --git a/projects/aca-folder-rules/src/lib/rules-list/rule/rule-list-item.ui-component.ts b/projects/aca-folder-rules/src/lib/rules-list/rule/rule-list-item.ui-component.ts index 9724cfebb..74fa5909a 100644 --- a/projects/aca-folder-rules/src/lib/rules-list/rule/rule-list-item.ui-component.ts +++ b/projects/aca-folder-rules/src/lib/rules-list/rule/rule-list-item.ui-component.ts @@ -23,19 +23,25 @@ * along with Alfresco. If not, see . */ -import { Component, Input } from '@angular/core'; +import { Component, HostBinding, Input, ViewEncapsulation } from '@angular/core'; import { Rule } from '../../model/rule.model'; import { FolderRulesService } from '../../services/folder-rules.service'; @Component({ - selector: 'aca-rule', + selector: 'aca-rule-list-item', templateUrl: 'rule-list-item.ui-component.html', - styleUrls: ['rule-list-item.ui-component.scss'] + styleUrls: ['rule-list-item.ui-component.scss'], + encapsulation: ViewEncapsulation.None, + host: { class: 'aca-rule-list-item' } }) export class RuleListItemUiComponent { - @Input() rule: Rule; - @Input() isSelected: boolean; - @Input() nodeId: string; + @Input() + rule: Rule; + @Input() + nodeId: string; + @Input() + @HostBinding('class.selected') + isSelected: boolean; constructor(private folderRulesService: FolderRulesService) {} diff --git a/projects/aca-folder-rules/src/lib/rules-list/rules-list.ui-component.html b/projects/aca-folder-rules/src/lib/rules-list/rules-list.ui-component.html deleted file mode 100644 index 553779f4c..000000000 --- a/projects/aca-folder-rules/src/lib/rules-list/rules-list.ui-component.html +++ /dev/null @@ -1,4 +0,0 @@ -
- -
diff --git a/projects/aca-folder-rules/src/lib/rules-list/rules-list.ui-component.scss b/projects/aca-folder-rules/src/lib/rules-list/rules-list.ui-component.scss deleted file mode 100644 index 5a7d37cf3..000000000 --- a/projects/aca-folder-rules/src/lib/rules-list/rules-list.ui-component.scss +++ /dev/null @@ -1,3 +0,0 @@ -.aca-rules-list { - margin-right: 24px; -} diff --git a/projects/aca-folder-rules/src/lib/services/folder-rules.service.ts b/projects/aca-folder-rules/src/lib/services/folder-rules.service.ts index c22648381..af9868fc3 100644 --- a/projects/aca-folder-rules/src/lib/services/folder-rules.service.ts +++ b/projects/aca-folder-rules/src/lib/services/folder-rules.service.ts @@ -27,7 +27,7 @@ import { Injectable } from '@angular/core'; import { AlfrescoApiService } from '@alfresco/adf-core'; import { BehaviorSubject, forkJoin, from, Observable, of } from 'rxjs'; import { catchError, finalize, map } from 'rxjs/operators'; -import { Rule } from '../model/rule.model'; +import { Rule, RuleForForm, RuleOptions } from '../model/rule.model'; import { ContentApiService } from '@alfresco/aca-shared'; import { NodeInfo } from '@alfresco/aca-shared/store'; import { RuleCompositeCondition } from '../model/rule-composite-condition.model'; @@ -46,22 +46,39 @@ export class FolderRulesService { }; } + public static get emptyRuleOptions(): RuleOptions { + return { + isEnabled: true, + isInheritable: false, + isAsynchronous: false, + errorScript: '' + }; + } + public static get emptyRule(): Rule { return { id: '', name: '', description: '', - isEnabled: true, - isInheritable: false, - isAsynchronous: false, - errorScript: '', isShared: false, triggers: ['inbound'], conditions: FolderRulesService.emptyCompositeCondition, - actions: [] + actions: [], + ...FolderRulesService.emptyRuleOptions }; } + public static get emptyRuleForForm(): RuleForForm { + const value = { + ...FolderRulesService.emptyRule, + options: FolderRulesService.emptyRuleOptions + }; + Object.keys(value.options).forEach((key: string) => { + delete value[key]; + }); + return value; + } + private rulesListingSource = new BehaviorSubject([]); rulesListing$: Observable = this.rulesListingSource.asObservable(); private folderInfoSource = new BehaviorSubject(null);