mirror of
https://github.com/Alfresco/alfresco-ng2-components.git
synced 2025-05-12 17:04:57 +00:00
[ACS-5183] properties facet file size and file type (#8766)
* ACS-5183 Created component which displays form to search nodes by file type and size * ACS-5183 Validate proper value in number input and allow to use custom file types * ACS-5183 Corrected problem with styles, case insensitive compare for extensions * ACS-5183 Added translations, styles for selecting type, proper comparator for types * ACS-5183 Prevent adding custom file type when selecting existing one * ACS-5183 Corrected bytes for each file size unit and clear number input when value is incorrect * ACS-5183 Added documentation for search properties component, updated documentation for search chip autocomplete input and taking values from settings * ACS-5183 Unit tests * ACS-5183 Added automation ids * ACS-5183 Added missing license header * ACS-5183 Fixed lint issues * ACS-5183 Fixed build issue
This commit is contained in:
parent
cffbdd51e4
commit
d70f689e06
@ -297,6 +297,7 @@ for more information about installing and using the source code.
|
|||||||
| [Search Filter component](content-services/components/search-filter.component.md) | Represents a main container component for custom search and faceted search settings. | [Source](../lib/content-services/src/lib/search/components/search-filter/search-filter.component.ts) |
|
| [Search Filter component](content-services/components/search-filter.component.md) | Represents a main container component for custom search and faceted search settings. | [Source](../lib/content-services/src/lib/search/components/search-filter/search-filter.component.ts) |
|
||||||
| [Search Form component](content-services/components/search-form.component.md) | Search Form screenshot | [Source](../lib/content-services/src/lib/search/components/search-form/search-form.component.ts) |
|
| [Search Form component](content-services/components/search-form.component.md) | Search Form screenshot | [Source](../lib/content-services/src/lib/search/components/search-form/search-form.component.ts) |
|
||||||
| [Search Logical Filter component](content-services/components/search-logical-filter.component.md) | Displays 3 chip inputs each representing different logical condition for search query. | [Source](../lib/content-services/src/lib/search/components/search-logical-filter/search-logical-filter.component.ts) |
|
| [Search Logical Filter component](content-services/components/search-logical-filter.component.md) | Displays 3 chip inputs each representing different logical condition for search query. | [Source](../lib/content-services/src/lib/search/components/search-logical-filter/search-logical-filter.component.ts) |
|
||||||
|
| [Search Properties component](content-services/components/search-properties.component.md) | Allows to search by file size and type.| [Source](../lib/content-services/src/lib/search/components/search-properties/search-properties.component.ts) |
|
||||||
| [Search number range component](content-services/components/search-number-range.component.md) | Implements a number range widget for the Search Filter component. | [Source](../lib/content-services/src/lib/search/components/search-number-range/search-number-range.component.ts) |
|
| [Search number range component](content-services/components/search-number-range.component.md) | Implements a number range widget for the Search Filter component. | [Source](../lib/content-services/src/lib/search/components/search-number-range/search-number-range.component.ts) |
|
||||||
| [Search radio component](content-services/components/search-radio.component.md) | Implements a radio button list widget for the Search Filter component. | [Source](../lib/content-services/src/lib/search/components/search-radio/search-radio.component.ts) |
|
| [Search radio component](content-services/components/search-radio.component.md) | Implements a radio button list widget for the Search Filter component. | [Source](../lib/content-services/src/lib/search/components/search-radio/search-radio.component.ts) |
|
||||||
| [Search slider component](content-services/components/search-slider.component.md) | Implements a numeric slider widget for the Search Filter component. | [Source](../lib/content-services/src/lib/search/components/search-slider/search-slider.component.ts) |
|
| [Search slider component](content-services/components/search-slider.component.md) | Implements a numeric slider widget for the Search Filter component. | [Source](../lib/content-services/src/lib/search/components/search-slider/search-slider.component.ts) |
|
||||||
|
@ -29,6 +29,10 @@ Represents an input with autocomplete options.
|
|||||||
| autocompleteOptions | `string[]` | [] | Options for autocomplete |
|
| autocompleteOptions | `string[]` | [] | Options for autocomplete |
|
||||||
| onReset$ | [`Observable`](https://rxjs.dev/guide/observable)`<void>` | | Observable that will listen to any reset event causing component to clear the chips and input |
|
| onReset$ | [`Observable`](https://rxjs.dev/guide/observable)`<void>` | | Observable that will listen to any reset event causing component to clear the chips and input |
|
||||||
| allowOnlyPredefinedValues | boolean | true | A flag that indicates whether it is possible to add a value not from the predefined ones |
|
| allowOnlyPredefinedValues | boolean | true | A flag that indicates whether it is possible to add a value not from the predefined ones |
|
||||||
|
| placeholder | string | 'SEARCH.FILTER.ACTIONS.ADD_OPTION' | Placeholder which should be displayed in input. |
|
||||||
|
| compareOption | (option1: string, option2: string) => boolean | | Function which is used to selected options with all options so it allows to detect which options are already selected. |
|
||||||
|
| formatChipValue | (option: string) => string | | Function which is used to format custom typed options. |
|
||||||
|
| filter | (options: string[], value: string) => string[] | | Function which is used to filter out possibile options from hint. By default it checks if option includes typed value and is case insensitive. |
|
||||||
|
|
||||||
### Events
|
### Events
|
||||||
|
|
||||||
|
@ -0,0 +1,61 @@
|
|||||||
|
---
|
||||||
|
Title: Search Properties component
|
||||||
|
Added: v6.2.0
|
||||||
|
Status: Active
|
||||||
|
Last reviewed: 2023-07-13
|
||||||
|
---
|
||||||
|
|
||||||
|
# [Search Properties component](../../../lib/content-services/src/lib/search/components/search-properties/search-properties.component.ts "Defined in search-properties.component.ts")
|
||||||
|
|
||||||
|
Allows to search by file size and type.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
## Basic usage
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"id": "properties",
|
||||||
|
"name": "Properties",
|
||||||
|
"enabled": true,
|
||||||
|
"component": {
|
||||||
|
"selector": "properties",
|
||||||
|
"settings": {
|
||||||
|
"field": "content.size,cm:name",
|
||||||
|
"fileExtensions": [
|
||||||
|
"3g2", "3gp", "acp", "aep", "ai", "aiff", "apk", "arw", "avi", "bin", "bmp", "cgm", "class", "cr2",
|
||||||
|
"css", "csv", "dita", "dng", "doc", "docm", "docx", "dotm","dwg", "dwt", "eps", "flac", "flv", "fm",
|
||||||
|
"fodg", "gif", "gtar", "gz", "htm", "html", "icns", "ics", "ief", "indd", "jar", "java", "jp2", "jpeg",
|
||||||
|
"jpg", "js", "json", "jsp", "m4v", "man", "md", "mov", "mp3", "mp4", "mpeg", "mpp", "mrw", "msg", "nef",
|
||||||
|
"numbers", "odb", "odf", "odg", "odi", "odm", "odp", "ods", "odt", "oga", "ogg", "ogv", "ogx", "orf",
|
||||||
|
"ott", "pages", "pbm", "pdf", "pef", "pgm", "pmd", "png", "pnm", "pot", "potx", "ppam", "ppj", "pps",
|
||||||
|
"ppsm", "ppt", "pptm", "pptx", "ps", "psd", "rad", "raf", "rar", "rgb", "rss", "rtf", "rw2", "rwl",
|
||||||
|
"sda", "sdc", "sdd", "sdp", "sds", "sdw", "sgi", "sgl", "sgml", "sh", "sldm", "smf", "stw", "svg",
|
||||||
|
"swf", "sxi", "tar", "tex", "texi", "tif", "tiff", "ts", "tsv", "txt", "vsd", "vsdm", "vsdx", "vssm",
|
||||||
|
"vstm", "vstx", "wav", "webm", "wma", "wmv", "wpd", "wrl", "x3f", "xdp", "xhtml", "xla", "xlam", "xls",
|
||||||
|
"xlsb", "xlsm", "xlsx", "xltm", "xml", "xpm", "xwd", "z", "zip"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Settings
|
||||||
|
|
||||||
|
| Name | Type | Description |
|
||||||
|
|----------------|----------|-------------------------------------------------------------------------------------------|
|
||||||
|
| field | string | Field/fields to apply the query to. First field for size, second for name. Required value |
|
||||||
|
| fileExtensions | string[] | List of preconfigured hints for extensions. |
|
||||||
|
|
||||||
|
|
||||||
|
## See also
|
||||||
|
|
||||||
|
- [Search Configuration Guide](../../user-guide/search-configuration-guide.md)
|
||||||
|
- [Search Query Builder service](../services/search-query-builder.service.md)
|
||||||
|
- [Search Widget Interface](../interfaces/search-widget.interface.md)
|
||||||
|
- [Search check list component](search-check-list.component.md)
|
||||||
|
- [Search date range component](search-date-range.component.md)
|
||||||
|
- [Search number range component](search-number-range.component.md)
|
||||||
|
- [Search radio component](search-radio.component.md)
|
||||||
|
- [Search slider component](search-slider.component.md)
|
||||||
|
- [Search text component](search-text.component.md)
|
BIN
docs/docassets/images/search-properties.png
Normal file
BIN
docs/docassets/images/search-properties.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 65 KiB |
@ -12,6 +12,7 @@ backend services have been tested with each released version of ADF.
|
|||||||
|
|
||||||
## Versions
|
## Versions
|
||||||
|
|
||||||
|
- [v6.2.0](#v620)
|
||||||
- [v6.1.0](#v610)
|
- [v6.1.0](#v610)
|
||||||
- [v6.0.0](#v600)
|
- [v6.0.0](#v600)
|
||||||
- [v5.1.0](#v510)
|
- [v5.1.0](#v510)
|
||||||
@ -42,6 +43,14 @@ backend services have been tested with each released version of ADF.
|
|||||||
- [v2.1.0](#v210)
|
- [v2.1.0](#v210)
|
||||||
- [v2.0.0](#v200)
|
- [v2.0.0](#v200)
|
||||||
|
|
||||||
|
## v6.2.0
|
||||||
|
|
||||||
|
<!--v620 start-->
|
||||||
|
|
||||||
|
- [Search Properties component](content-services/components/search-properties.component.md)
|
||||||
|
|
||||||
|
<!--v620 end-->
|
||||||
|
|
||||||
## v6.1.0
|
## v6.1.0
|
||||||
|
|
||||||
<!--v610 start-->
|
<!--v610 start-->
|
||||||
|
@ -413,6 +413,22 @@
|
|||||||
"EXCLUDE": "EXCLUDE",
|
"EXCLUDE": "EXCLUDE",
|
||||||
"EXCLUDE_LABEL": "EXCLUDE these words",
|
"EXCLUDE_LABEL": "EXCLUDE these words",
|
||||||
"EXCLUDE_HINT": "Results will exclude matches with these words"
|
"EXCLUDE_HINT": "Results will exclude matches with these words"
|
||||||
|
},
|
||||||
|
"SEARCH_PROPERTIES": {
|
||||||
|
"FILE_SIZE": "File Size",
|
||||||
|
"FILE_SIZE_PLACEHOLDER": "Enter file size",
|
||||||
|
"FILE_SIZE_OPERATOR": {
|
||||||
|
"AT_LEAST": "At Least",
|
||||||
|
"AT_MOST": "At Most",
|
||||||
|
"EXACTLY": "Exactly"
|
||||||
|
},
|
||||||
|
"FILE_SIZE_UNIT_ABBREVIATION": {
|
||||||
|
"KB": "KB",
|
||||||
|
"MB": "MB",
|
||||||
|
"GB": "GB"
|
||||||
|
},
|
||||||
|
"FILE_TYPE": "File Type",
|
||||||
|
"FILE_TYPE_PLACEHOLDER": "Add file type"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"PERMISSION": {
|
"PERMISSION": {
|
||||||
|
@ -21,7 +21,7 @@ import { Pipe, PipeTransform } from '@angular/core';
|
|||||||
name: 'adfIsIncluded'
|
name: 'adfIsIncluded'
|
||||||
})
|
})
|
||||||
export class IsIncludedPipe<T> implements PipeTransform {
|
export class IsIncludedPipe<T> implements PipeTransform {
|
||||||
transform(value: T, array: T[]): boolean {
|
transform(value: T, array: T[], compare?: (value1: T, value2: T) => boolean): boolean {
|
||||||
return array.includes(value);
|
return compare ? array.some((arrayValue) => compare(value, arrayValue)) : array.includes(value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -10,19 +10,22 @@
|
|||||||
</button>
|
</button>
|
||||||
</mat-chip>
|
</mat-chip>
|
||||||
<input
|
<input
|
||||||
placeholder="{{ 'SEARCH.FILTER.ACTIONS.ADD_OPTION' | translate }}"
|
placeholder="{{ placeholder | translate }}"
|
||||||
aria-controls="adf-search-chip-autocomplete"
|
aria-controls="adf-search-chip-autocomplete"
|
||||||
#optionInput
|
#optionInput
|
||||||
[formControl]="formCtrl"
|
[formControl]="formCtrl"
|
||||||
[matAutocomplete]="auto"
|
[matAutocomplete]="auto"
|
||||||
[matChipInputFor]="chipList"
|
[matChipInputFor]="chipList"
|
||||||
[matChipInputSeparatorKeyCodes]="separatorKeysCodes"
|
[matChipInputSeparatorKeyCodes]="separatorKeysCodes"
|
||||||
[attr.aria-label]="'SEARCH.FILTER.ACTIONS.ADD_OPTION' | translate"
|
[attr.aria-label]="placeholder | translate"
|
||||||
(matChipInputTokenEnd)="add($event)">
|
(matChipInputTokenEnd)="add($event)"
|
||||||
|
(blur)="activeAnyOption = false"
|
||||||
|
data-automation-id="adf-search-chip-autocomplete-input">
|
||||||
</mat-chip-list>
|
</mat-chip-list>
|
||||||
<mat-autocomplete #auto="matAutocomplete" (optionSelected)="selected($event)" id="adf-search-chip-autocomplete">
|
<mat-autocomplete #auto="matAutocomplete" (optionSelected)="selected($event)" id="adf-search-chip-autocomplete"
|
||||||
<mat-option [disabled]="option | adfIsIncluded: selectedOptions" *ngFor="let option of filteredOptions$ | async"
|
(optionActivated)="activeAnyOption = true" (closed)="activeAnyOption = false">
|
||||||
[ngClass]="(option | adfIsIncluded: selectedOptions) && 'adf-autocomplete-added-option'">
|
<mat-option [disabled]="option | adfIsIncluded: selectedOptions : compareOption" *ngFor="let option of filteredOptions$ | async"
|
||||||
|
[ngClass]="(option | adfIsIncluded: selectedOptions : compareOption) && 'adf-autocomplete-added-option'">
|
||||||
{{option}}
|
{{option}}
|
||||||
</mat-option>
|
</mat-option>
|
||||||
</mat-autocomplete>
|
</mat-autocomplete>
|
||||||
|
@ -22,6 +22,7 @@ import { TranslateModule } from '@ngx-translate/core';
|
|||||||
import { Subject } from 'rxjs';
|
import { Subject } from 'rxjs';
|
||||||
import { ContentTestingModule } from '../../../testing/content.testing.module';
|
import { ContentTestingModule } from '../../../testing/content.testing.module';
|
||||||
import { SearchChipAutocompleteInputComponent } from './search-chip-autocomplete-input.component';
|
import { SearchChipAutocompleteInputComponent } from './search-chip-autocomplete-input.component';
|
||||||
|
import { DebugElement } from '@angular/core';
|
||||||
|
|
||||||
describe('SearchChipAutocompleteInputComponent', () => {
|
describe('SearchChipAutocompleteInputComponent', () => {
|
||||||
let component: SearchChipAutocompleteInputComponent;
|
let component: SearchChipAutocompleteInputComponent;
|
||||||
@ -44,16 +45,20 @@ describe('SearchChipAutocompleteInputComponent', () => {
|
|||||||
component.autocompleteOptions = ['option1', 'option2'];
|
component.autocompleteOptions = ['option1', 'option2'];
|
||||||
});
|
});
|
||||||
|
|
||||||
|
function getInput(): HTMLInputElement {
|
||||||
|
return fixture.debugElement.query(By.css('input')).nativeElement;
|
||||||
|
}
|
||||||
|
|
||||||
function enterNewInputValue(value: string) {
|
function enterNewInputValue(value: string) {
|
||||||
const inputElement = fixture.debugElement.query(By.css('input'));
|
const inputElement = getInput();
|
||||||
inputElement.nativeElement.dispatchEvent(new Event('focusin'));
|
inputElement.dispatchEvent(new Event('focusin'));
|
||||||
inputElement.nativeElement.value = value;
|
inputElement.value = value;
|
||||||
inputElement.nativeElement.dispatchEvent(new Event('input'));
|
inputElement.dispatchEvent(new Event('input'));
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
}
|
}
|
||||||
|
|
||||||
function addNewOption(value: string) {
|
function addNewOption(value: string) {
|
||||||
const inputElement = fixture.debugElement.query(By.css('input')).nativeElement;
|
const inputElement = getInput();
|
||||||
inputElement.value = value;
|
inputElement.value = value;
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
inputElement.dispatchEvent(new KeyboardEvent('keydown', {keyCode: 13}));
|
inputElement.dispatchEvent(new KeyboardEvent('keydown', {keyCode: 13}));
|
||||||
@ -68,6 +73,14 @@ describe('SearchChipAutocompleteInputComponent', () => {
|
|||||||
return fixture.debugElement.queryAll(By.css('mat-chip span')).map((chip) => chip.nativeElement)[index].innerText;
|
return fixture.debugElement.queryAll(By.css('mat-chip span')).map((chip) => chip.nativeElement)[index].innerText;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getOptionElements(): DebugElement[] {
|
||||||
|
return fixture.debugElement.queryAll(By.css('mat-option'));
|
||||||
|
}
|
||||||
|
|
||||||
|
function getAddedOptionElements(): DebugElement[] {
|
||||||
|
return fixture.debugElement.queryAll(By.css('.adf-autocomplete-added-option'));
|
||||||
|
}
|
||||||
|
|
||||||
it('should add new option only if value is predefined when allowOnlyPredefinedValues = true', () => {
|
it('should add new option only if value is predefined when allowOnlyPredefinedValues = true', () => {
|
||||||
addNewOption('test');
|
addNewOption('test');
|
||||||
addNewOption('option1');
|
addNewOption('option1');
|
||||||
@ -83,15 +96,25 @@ describe('SearchChipAutocompleteInputComponent', () => {
|
|||||||
expect(getChipValue(0)).toBe('test');
|
expect(getChipValue(0)).toBe('test');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should add new formatted option based on formatChipValue', () => {
|
||||||
|
component.allowOnlyPredefinedValues = false;
|
||||||
|
const option = 'abc';
|
||||||
|
component.formatChipValue = (value) => value.replace('.', '');
|
||||||
|
|
||||||
|
addNewOption(`.${option}`);
|
||||||
|
expect(getChipList().length).toBe(1);
|
||||||
|
expect(getChipValue(0)).toBe(option);
|
||||||
|
});
|
||||||
|
|
||||||
it('should add new option upon clicking on option from autocomplete', async () => {
|
it('should add new option upon clicking on option from autocomplete', async () => {
|
||||||
const optionsChangedSpy = spyOn(component.optionsChanged, 'emit');
|
const optionsChangedSpy = spyOn(component.optionsChanged, 'emit');
|
||||||
enterNewInputValue('op');
|
enterNewInputValue('op');
|
||||||
await fixture.whenStable();
|
await fixture.whenStable();
|
||||||
|
|
||||||
const matOptions = document.querySelectorAll('mat-option');
|
const matOptions = getOptionElements();
|
||||||
expect(matOptions.length).toBe(2);
|
expect(matOptions.length).toBe(2);
|
||||||
|
|
||||||
const optionToClick = matOptions[0] as HTMLElement;
|
const optionToClick = matOptions[0].nativeElement as HTMLElement;
|
||||||
optionToClick.click();
|
optionToClick.click();
|
||||||
|
|
||||||
expect(optionsChangedSpy).toHaveBeenCalledOnceWith(['option1']);
|
expect(optionsChangedSpy).toHaveBeenCalledOnceWith(['option1']);
|
||||||
@ -103,7 +126,7 @@ describe('SearchChipAutocompleteInputComponent', () => {
|
|||||||
addNewOption('option1');
|
addNewOption('option1');
|
||||||
enterNewInputValue('op');
|
enterNewInputValue('op');
|
||||||
|
|
||||||
const addedOptions = fixture.debugElement.queryAll(By.css('.adf-autocomplete-added-option'));
|
const addedOptions = getAddedOptionElements();
|
||||||
|
|
||||||
await fixture.whenStable();
|
await fixture.whenStable();
|
||||||
|
|
||||||
@ -111,12 +134,24 @@ describe('SearchChipAutocompleteInputComponent', () => {
|
|||||||
expect(addedOptions.length).toBe(1);
|
expect(addedOptions.length).toBe(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should apply class to already selected options based on custom compareOption function', async () => {
|
||||||
|
component.allowOnlyPredefinedValues = false;
|
||||||
|
component.autocompleteOptions = ['.test1', 'test3', '.test2', 'test1.'];
|
||||||
|
component.compareOption = (option1, option2) => option1.split('.')[1] === option2;
|
||||||
|
|
||||||
|
fixture.detectChanges();
|
||||||
|
addNewOption('test1');
|
||||||
|
enterNewInputValue('t');
|
||||||
|
|
||||||
|
const addedOptions = getAddedOptionElements();
|
||||||
|
await fixture.whenStable();
|
||||||
|
expect(addedOptions.length).toBe(1);
|
||||||
|
});
|
||||||
|
|
||||||
it('should limit autocomplete list to 15 values max', () => {
|
it('should limit autocomplete list to 15 values max', () => {
|
||||||
component.autocompleteOptions = ['a1','a2','a3','a4','a5','a6','a7','a8','a9','a10','a11','a12','a13','a14','a15','a16'];
|
component.autocompleteOptions = ['a1','a2','a3','a4','a5','a6','a7','a8','a9','a10','a11','a12','a13','a14','a15','a16'];
|
||||||
enterNewInputValue('a');
|
enterNewInputValue('a');
|
||||||
|
expect(getOptionElements().length).toBe(15);
|
||||||
const matOptions = document.querySelectorAll('mat-option');
|
|
||||||
expect(matOptions.length).toBe(15);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should not add a value if same value has already been added', () => {
|
it('should not add a value if same value has already been added', () => {
|
||||||
@ -127,14 +162,19 @@ describe('SearchChipAutocompleteInputComponent', () => {
|
|||||||
|
|
||||||
it('should show autocomplete list if similar predefined values exists', () => {
|
it('should show autocomplete list if similar predefined values exists', () => {
|
||||||
enterNewInputValue('op');
|
enterNewInputValue('op');
|
||||||
const matOptions = document.querySelectorAll('mat-option');
|
expect(getOptionElements().length).toBe(2);
|
||||||
expect(matOptions.length).toBe(2);
|
});
|
||||||
|
|
||||||
|
it('should show autocomplete list based on custom filtering', () => {
|
||||||
|
component.autocompleteOptions = ['.test1', 'test1', 'test1.', '.test2', '.test12'];
|
||||||
|
component.filter = (options, value) => options.filter((option) => option.split('.')[1] === value);
|
||||||
|
enterNewInputValue('test1');
|
||||||
|
expect(getOptionElements().length).toBe(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should not show autocomplete list if there are no similar predefined values', () => {
|
it('should not show autocomplete list if there are no similar predefined values', () => {
|
||||||
enterNewInputValue('test');
|
enterNewInputValue('test');
|
||||||
const matOptions = document.querySelectorAll('mat-option');
|
expect(getOptionElements().length).toBe(0);
|
||||||
expect(matOptions.length).toBe(0);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should emit new value when selected options changed', () => {
|
it('should emit new value when selected options changed', () => {
|
||||||
@ -146,11 +186,21 @@ describe('SearchChipAutocompleteInputComponent', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should clear the input after a new value is added', () => {
|
it('should clear the input after a new value is added', () => {
|
||||||
const input = fixture.debugElement.query(By.css('input')).nativeElement;
|
const input = getInput();
|
||||||
addNewOption('option1');
|
addNewOption('option1');
|
||||||
expect(input.value).toBe('');
|
expect(input.value).toBe('');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should use correct default placeholder for input', () => {
|
||||||
|
expect(getInput().placeholder).toBe('SEARCH.FILTER.ACTIONS.ADD_OPTION');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should use placeholder for input passed as component input', () => {
|
||||||
|
component.placeholder = 'Some placeholder';
|
||||||
|
fixture.detectChanges();
|
||||||
|
expect(getInput().placeholder).toBe(component.placeholder);
|
||||||
|
});
|
||||||
|
|
||||||
it('should reset all options when onReset$ event is emitted', () => {
|
it('should reset all options when onReset$ event is emitted', () => {
|
||||||
addNewOption('option1');
|
addNewOption('option1');
|
||||||
addNewOption('option2');
|
addNewOption('option2');
|
||||||
|
@ -21,7 +21,7 @@ import { FormControl } from '@angular/forms';
|
|||||||
import { MatAutocompleteSelectedEvent } from '@angular/material/autocomplete';
|
import { MatAutocompleteSelectedEvent } from '@angular/material/autocomplete';
|
||||||
import { MatChipInputEvent } from '@angular/material/chips';
|
import { MatChipInputEvent } from '@angular/material/chips';
|
||||||
import { Observable, Subject } from 'rxjs';
|
import { Observable, Subject } from 'rxjs';
|
||||||
import { map, startWith, takeUntil } from 'rxjs/operators';
|
import { map, startWith, takeUntil, tap } from 'rxjs/operators';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'adf-search-chip-autocomplete-input',
|
selector: 'adf-search-chip-autocomplete-input',
|
||||||
@ -42,6 +42,21 @@ export class SearchChipAutocompleteInputComponent implements OnInit, OnDestroy {
|
|||||||
@Input()
|
@Input()
|
||||||
allowOnlyPredefinedValues = true;
|
allowOnlyPredefinedValues = true;
|
||||||
|
|
||||||
|
@Input()
|
||||||
|
placeholder = 'SEARCH.FILTER.ACTIONS.ADD_OPTION';
|
||||||
|
|
||||||
|
@Input()
|
||||||
|
compareOption?: (option1: string, option2: string) => boolean;
|
||||||
|
|
||||||
|
@Input()
|
||||||
|
formatChipValue?: (option: string) => string;
|
||||||
|
|
||||||
|
@Input()
|
||||||
|
filter = (options: string[], value: string): string[] => {
|
||||||
|
const filterValue = value.toLowerCase();
|
||||||
|
return options.filter(option => option.toLowerCase().includes(filterValue));
|
||||||
|
};
|
||||||
|
|
||||||
@Output()
|
@Output()
|
||||||
optionsChanged: EventEmitter<string[]> = new EventEmitter();
|
optionsChanged: EventEmitter<string[]> = new EventEmitter();
|
||||||
|
|
||||||
@ -50,11 +65,17 @@ export class SearchChipAutocompleteInputComponent implements OnInit, OnDestroy {
|
|||||||
filteredOptions$: Observable<string[]>;
|
filteredOptions$: Observable<string[]>;
|
||||||
selectedOptions: string[] = [];
|
selectedOptions: string[] = [];
|
||||||
private onDestroy$ = new Subject<void>();
|
private onDestroy$ = new Subject<void>();
|
||||||
|
private _activeAnyOption = false;
|
||||||
|
|
||||||
|
set activeAnyOption(active: boolean) {
|
||||||
|
this._activeAnyOption = active;
|
||||||
|
}
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this.filteredOptions$ = this.formCtrl.valueChanges.pipe(
|
this.filteredOptions$ = this.formCtrl.valueChanges.pipe(
|
||||||
startWith(null),
|
startWith(null),
|
||||||
map((value: string | null) => (value ? this.filter(value) : []))
|
tap(() => this.activeAnyOption = false),
|
||||||
|
map((value: string | null) => (value ? this.filter(this.autocompleteOptions, value).slice(0, 15) : []))
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -68,13 +89,18 @@ export class SearchChipAutocompleteInputComponent implements OnInit, OnDestroy {
|
|||||||
}
|
}
|
||||||
|
|
||||||
add(event: MatChipInputEvent) {
|
add(event: MatChipInputEvent) {
|
||||||
const value = (event.value || '').trim();
|
if (!this._activeAnyOption) {
|
||||||
|
let value = (event.value || '').trim();
|
||||||
|
if (this.formatChipValue) {
|
||||||
|
value = this.formatChipValue(value);
|
||||||
|
}
|
||||||
|
|
||||||
if (value && this.isExists(value) && !this.isAdded(value)) {
|
if (value && this.isExists(value) && !this.isAdded(value)) {
|
||||||
this.selectedOptions.push(value);
|
this.selectedOptions.push(value);
|
||||||
this.optionsChanged.emit(this.selectedOptions);
|
this.optionsChanged.emit(this.selectedOptions);
|
||||||
event.chipInput.clear();
|
event.chipInput.clear();
|
||||||
this.formCtrl.setValue(null);
|
this.formCtrl.setValue(null);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -96,11 +122,6 @@ export class SearchChipAutocompleteInputComponent implements OnInit, OnDestroy {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private filter(value: string): string[] {
|
|
||||||
const filterValue = value.toLowerCase();
|
|
||||||
return this.autocompleteOptions.filter(option => option.toLowerCase().includes(filterValue)).slice(0, 15);
|
|
||||||
}
|
|
||||||
|
|
||||||
private isAdded(value: string): boolean {
|
private isAdded(value: string): boolean {
|
||||||
return this.selectedOptions.includes(value);
|
return this.selectedOptions.includes(value);
|
||||||
}
|
}
|
||||||
|
@ -19,7 +19,7 @@
|
|||||||
<mat-icon>{{ chipIcon }}</mat-icon>
|
<mat-icon>{{ chipIcon }}</mat-icon>
|
||||||
</mat-chip>
|
</mat-chip>
|
||||||
|
|
||||||
<mat-menu #menu="matMenu" backdropClass="adf-search-filter-chip-menu" (closed)="onClosed()">
|
<mat-menu #menu="matMenu" backdropClass="adf-search-filter-chip-menu" [class]="'adf-search-filter-chip-menu-panel-' + category.id" (closed)="onClosed()">
|
||||||
<div #menuContainer [attr.data-automation-id]="'search-field-' + category.name">
|
<div #menuContainer [attr.data-automation-id]="'search-field-' + category.name">
|
||||||
<adf-search-filter-menu-card (click)="$event.stopPropagation()"
|
<adf-search-filter-menu-card (click)="$event.stopPropagation()"
|
||||||
(keydown.tab)="$event.stopPropagation();"
|
(keydown.tab)="$event.stopPropagation();"
|
||||||
|
@ -0,0 +1,25 @@
|
|||||||
|
/*!
|
||||||
|
* @license
|
||||||
|
* Copyright © 2005-2023 Hyland Software, Inc. and its affiliates. All rights reserved.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { FileSizeOperator } from './file-size-operator.enum';
|
||||||
|
import { FileSizeUnit } from './file-size-unit.enum';
|
||||||
|
|
||||||
|
export interface FileSizeCondition {
|
||||||
|
fileSizeOperator: FileSizeOperator;
|
||||||
|
fileSize?: number;
|
||||||
|
fileSizeUnit: FileSizeUnit;
|
||||||
|
}
|
@ -0,0 +1,22 @@
|
|||||||
|
/*!
|
||||||
|
* @license
|
||||||
|
* Copyright © 2005-2023 Hyland Software, Inc. and its affiliates. All rights reserved.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
export enum FileSizeOperator {
|
||||||
|
AT_LEAST = 'SEARCH.SEARCH_PROPERTIES.FILE_SIZE_OPERATOR.AT_LEAST',
|
||||||
|
AT_MOST = 'SEARCH.SEARCH_PROPERTIES.FILE_SIZE_OPERATOR.AT_MOST',
|
||||||
|
EXACTLY = 'SEARCH.SEARCH_PROPERTIES.FILE_SIZE_OPERATOR.EXACTLY'
|
||||||
|
}
|
@ -0,0 +1,24 @@
|
|||||||
|
/*!
|
||||||
|
* @license
|
||||||
|
* Copyright © 2005-2023 Hyland Software, Inc. and its affiliates. All rights reserved.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
export class FileSizeUnit {
|
||||||
|
static readonly KB = new FileSizeUnit('SEARCH.SEARCH_PROPERTIES.FILE_SIZE_UNIT_ABBREVIATION.KB', 1024);
|
||||||
|
static readonly MB = new FileSizeUnit('SEARCH.SEARCH_PROPERTIES.FILE_SIZE_UNIT_ABBREVIATION.MB', 1048576);
|
||||||
|
static readonly GB = new FileSizeUnit('SEARCH.SEARCH_PROPERTIES.FILE_SIZE_UNIT_ABBREVIATION.GB', 1073741824);
|
||||||
|
|
||||||
|
private constructor(readonly abbreviation: string, readonly bytes: number) {}
|
||||||
|
}
|
@ -0,0 +1,54 @@
|
|||||||
|
<form [formGroup]="form">
|
||||||
|
<label
|
||||||
|
for="adf-search-properties-file-size"
|
||||||
|
class="adf-search-properties-file-size-label">
|
||||||
|
{{ 'SEARCH.SEARCH_PROPERTIES.FILE_SIZE' | translate }}
|
||||||
|
</label>
|
||||||
|
<mat-form-field
|
||||||
|
[style.width.px]="fileSizeOperatorsMaxWidth"
|
||||||
|
class="adf-search-properties-file-size-operator">
|
||||||
|
<mat-select
|
||||||
|
data-automation-id="adf-search-properties-file-size-operator-select"
|
||||||
|
[formControl]="form.controls.fileSizeOperator"
|
||||||
|
#fileSizeOperatorSelect>
|
||||||
|
<mat-option
|
||||||
|
*ngFor="let fileSizeOperator of fileSizeOperators"
|
||||||
|
[value]="fileSizeOperator">
|
||||||
|
{{ fileSizeOperator | translate }}
|
||||||
|
</mat-option>
|
||||||
|
</mat-select>
|
||||||
|
</mat-form-field>
|
||||||
|
<input
|
||||||
|
[formControl]="form.controls.fileSize"
|
||||||
|
type="number"
|
||||||
|
min="0"
|
||||||
|
step="any"
|
||||||
|
(input)="narrowDownAllowedCharacters($event)"
|
||||||
|
(keydown)="preventIncorrectNumberCharacters($event)"
|
||||||
|
id="adf-search-properties-file-size"
|
||||||
|
[placeholder]="'SEARCH.SEARCH_PROPERTIES.FILE_SIZE_PLACEHOLDER' | translate"
|
||||||
|
(blur)="clearNumberFieldWhenInvalid($event)"
|
||||||
|
data-automation-id="adf-search-properties-file-size-input" />
|
||||||
|
<mat-form-field class="adf-search-properties-file-size-unit">
|
||||||
|
<mat-select
|
||||||
|
[formControl]="form.controls.fileSizeUnit"
|
||||||
|
data-automation-id="adf-search-properties-file-size-unit-select">
|
||||||
|
<mat-option
|
||||||
|
*ngFor="let fileSizeUnit of fileSizeUnits"
|
||||||
|
[value]="fileSizeUnit">
|
||||||
|
{{ fileSizeUnit.abbreviation | translate }}
|
||||||
|
</mat-option>
|
||||||
|
</mat-select>
|
||||||
|
</mat-form-field>
|
||||||
|
<p class="adf-search-properties-file-type-label">{{ 'SEARCH.SEARCH_PROPERTIES.FILE_TYPE' | translate }}</p>
|
||||||
|
<adf-search-chip-autocomplete-input
|
||||||
|
[autocompleteOptions]="settings?.fileExtensions"
|
||||||
|
(optionsChanged)="selectedExtensions = $event"
|
||||||
|
[onReset$]="reset$"
|
||||||
|
[allowOnlyPredefinedValues]="false"
|
||||||
|
[compareOption]="compareFileExtensions"
|
||||||
|
[formatChipValue]="getExtensionWithoutDot"
|
||||||
|
[filter]="filterExtensions"
|
||||||
|
placeholder="SEARCH.SEARCH_PROPERTIES.FILE_TYPE">
|
||||||
|
</adf-search-chip-autocomplete-input>
|
||||||
|
</form>
|
@ -0,0 +1,89 @@
|
|||||||
|
adf-search-properties {
|
||||||
|
.adf-search-properties-file-size-label {
|
||||||
|
display: block;
|
||||||
|
margin-top: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
input {
|
||||||
|
height: 25px;
|
||||||
|
border: 1px solid var(--adf-theme-mat-grey-color-a400);
|
||||||
|
border-radius: 5px;
|
||||||
|
margin-top: 5px;
|
||||||
|
padding: 5px;
|
||||||
|
font-size: 14px;
|
||||||
|
color: var(--adf-theme-foreground-text-color);
|
||||||
|
margin-left: 8px;
|
||||||
|
margin-right: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.adf-search-properties-file-size-operator,
|
||||||
|
.adf-search-properties-file-size-unit {
|
||||||
|
.mat-form-field-infix {
|
||||||
|
border: 1px solid var(--adf-theme-mat-grey-color-a400);
|
||||||
|
border-radius: 5px;
|
||||||
|
padding: 9px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.mat-focused {
|
||||||
|
.mat-form-field-infix {
|
||||||
|
outline: 2px auto -webkit-focus-ring-color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.mat-form-field-underline {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.adf-search-properties-file-size-unit {
|
||||||
|
width: 78px;
|
||||||
|
}
|
||||||
|
|
||||||
|
adf-search-chip-autocomplete-input {
|
||||||
|
display: block;
|
||||||
|
|
||||||
|
.mat-form-field-outline {
|
||||||
|
.mat-form-field-outline {
|
||||||
|
&-start,
|
||||||
|
&-end {
|
||||||
|
border-top: 1px solid var(--adf-theme-mat-grey-color-a400);
|
||||||
|
border-bottom: 1px solid var(--adf-theme-mat-grey-color-a400);
|
||||||
|
}
|
||||||
|
|
||||||
|
&-start {
|
||||||
|
border-left: 1px solid var(--adf-theme-mat-grey-color-a400);
|
||||||
|
}
|
||||||
|
|
||||||
|
&-end {
|
||||||
|
border-right: 1px solid var(--adf-theme-mat-grey-color-a400);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.mat-focused {
|
||||||
|
.mat-form-field-outline {
|
||||||
|
outline: 2px auto -webkit-focus-ring-color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.mat-form-field-appearance-outline {
|
||||||
|
.mat-form-field-outline-thick {
|
||||||
|
.mat-form-field-outline {
|
||||||
|
&-start,
|
||||||
|
&-end {
|
||||||
|
border-width: 1px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.adf-search-properties-file-type-label {
|
||||||
|
margin-top: 10px;
|
||||||
|
margin-bottom: 6px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.adf-search-filter-chip-menu-panel-properties {
|
||||||
|
max-width: unset;
|
||||||
|
}
|
@ -0,0 +1,432 @@
|
|||||||
|
/*!
|
||||||
|
* @license
|
||||||
|
* Copyright © 2005-2023 Hyland Software, Inc. and its affiliates. All rights reserved.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
import { SearchPropertiesComponent } from './search-properties.component';
|
||||||
|
import { ContentTestingModule } from '../../../testing/content.testing.module';
|
||||||
|
import { TranslateModule } from '@ngx-translate/core';
|
||||||
|
import { By } from '@angular/platform-browser';
|
||||||
|
import { MatOption } from '@angular/material/core';
|
||||||
|
import { SearchChipAutocompleteInputComponent, SearchQueryBuilderService } from '@alfresco/adf-content-services';
|
||||||
|
import { FileSizeUnit } from './file-size-unit.enum';
|
||||||
|
import { FileSizeOperator } from './file-size-operator.enum';
|
||||||
|
import { SearchProperties } from './search-properties';
|
||||||
|
|
||||||
|
describe('SearchPropertiesComponent', () => {
|
||||||
|
let component: SearchPropertiesComponent;
|
||||||
|
let fixture: ComponentFixture<SearchPropertiesComponent>;
|
||||||
|
|
||||||
|
const clickFileSizeOperatorsSelect = () => {
|
||||||
|
fixture.debugElement.query(By.css('[data-automation-id=adf-search-properties-file-size-operator-select]')).nativeElement.click();
|
||||||
|
fixture.detectChanges();
|
||||||
|
};
|
||||||
|
|
||||||
|
const clickFileSizeUnitsSelect = () => {
|
||||||
|
fixture.debugElement.query(By.css('[data-automation-id=adf-search-properties-file-size-unit-select]')).nativeElement.click();
|
||||||
|
fixture.detectChanges();
|
||||||
|
};
|
||||||
|
|
||||||
|
const getSelectOptions = () => fixture.debugElement.queryAll(By.directive(MatOption));
|
||||||
|
|
||||||
|
const getFileSizeInput = () => fixture.debugElement.query(By.css('#adf-search-properties-file-size'));
|
||||||
|
|
||||||
|
const typeInFileSizeInput = (value = '321', data = '1'): HTMLInputElement => {
|
||||||
|
const fileSizeElement = getFileSizeInput();
|
||||||
|
fileSizeElement.nativeElement.value = value;
|
||||||
|
const event = new InputEvent('input', {
|
||||||
|
data
|
||||||
|
});
|
||||||
|
spyOnProperty(event, 'target').and.returnValue(fileSizeElement.nativeElement);
|
||||||
|
fileSizeElement.triggerEventHandler('input', event);
|
||||||
|
fixture.detectChanges();
|
||||||
|
return fileSizeElement.nativeElement;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getSearchChipAutocompleteInputComponent = (): SearchChipAutocompleteInputComponent => fixture.debugElement.query(
|
||||||
|
By.directive(SearchChipAutocompleteInputComponent)
|
||||||
|
).componentInstance;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
declarations: [ SearchPropertiesComponent ],
|
||||||
|
imports: [ ContentTestingModule, TranslateModule.forRoot() ]
|
||||||
|
}).compileComponents();
|
||||||
|
|
||||||
|
fixture = TestBed.createComponent(SearchPropertiesComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('File size', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should display correct operators for file size after opening select', () => {
|
||||||
|
clickFileSizeOperatorsSelect();
|
||||||
|
|
||||||
|
const options = getSelectOptions();
|
||||||
|
expect(options.length).toBe(3);
|
||||||
|
expect(options[0].nativeElement.innerText).toBe('SEARCH.SEARCH_PROPERTIES.FILE_SIZE_OPERATOR.AT_LEAST');
|
||||||
|
expect(options[1].nativeElement.innerText).toBe('SEARCH.SEARCH_PROPERTIES.FILE_SIZE_OPERATOR.AT_MOST');
|
||||||
|
expect(options[2].nativeElement.innerText).toBe('SEARCH.SEARCH_PROPERTIES.FILE_SIZE_OPERATOR.EXACTLY');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should display correct units for file size after opening select', () => {
|
||||||
|
clickFileSizeUnitsSelect();
|
||||||
|
|
||||||
|
const options = getSelectOptions();
|
||||||
|
expect(options.length).toBe(3);
|
||||||
|
expect(options[0].nativeElement.innerText).toBe('SEARCH.SEARCH_PROPERTIES.FILE_SIZE_UNIT_ABBREVIATION.KB');
|
||||||
|
expect(options[1].nativeElement.innerText).toBe('SEARCH.SEARCH_PROPERTIES.FILE_SIZE_UNIT_ABBREVIATION.MB');
|
||||||
|
expect(options[2].nativeElement.innerText).toBe('SEARCH.SEARCH_PROPERTIES.FILE_SIZE_UNIT_ABBREVIATION.GB');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should allow type digit value in file size input', () => {
|
||||||
|
const fileSizeElement = typeInFileSizeInput();
|
||||||
|
expect(fileSizeElement.value).toBe('321');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should allow type decimal value in file size input', () => {
|
||||||
|
const value = '3.21';
|
||||||
|
const fileSizeElement = typeInFileSizeInput(value);
|
||||||
|
expect(fileSizeElement.value).toBe(value);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should allow clear value in file size input', () => {
|
||||||
|
const value = '';
|
||||||
|
const fileSizeElement = typeInFileSizeInput(value);
|
||||||
|
expect(fileSizeElement.value).toBe(value);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not allow type non digit value in file size input', () => {
|
||||||
|
const fileSizeElement = typeInFileSizeInput('e');
|
||||||
|
expect(fileSizeElement.value).toBe('');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should call preventIncorrectNumberCharacters on keydown event for file size input', () => {
|
||||||
|
spyOn(component, 'preventIncorrectNumberCharacters');
|
||||||
|
const event = new KeyboardEvent('keydown');
|
||||||
|
const fileSizeElement = getFileSizeInput();
|
||||||
|
|
||||||
|
fileSizeElement.triggerEventHandler('keydown', event);
|
||||||
|
expect(component.preventIncorrectNumberCharacters).toHaveBeenCalledWith(event);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('File type', () => {
|
||||||
|
let searchChipAutocompleteInputComponent: SearchChipAutocompleteInputComponent;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
fixture.detectChanges();
|
||||||
|
searchChipAutocompleteInputComponent = getSearchChipAutocompleteInputComponent();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should set autocompleteOptions for SearchChipAutocompleteInputComponent from settings', () => {
|
||||||
|
component.settings = {
|
||||||
|
field: 'field',
|
||||||
|
fileExtensions: ['pdf', 'doc', 'txt']
|
||||||
|
};
|
||||||
|
|
||||||
|
fixture.detectChanges();
|
||||||
|
expect(searchChipAutocompleteInputComponent.autocompleteOptions).toBe(component.settings.fileExtensions);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should set onReset$ for SearchChipAutocompleteInputComponent to correct value', () => {
|
||||||
|
expect(searchChipAutocompleteInputComponent.onReset$).toBe(component.reset$);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should set allowOnlyPredefinedValues for SearchChipAutocompleteInputComponent to false', () => {
|
||||||
|
expect(searchChipAutocompleteInputComponent.allowOnlyPredefinedValues).toBeFalse();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should compare file extensions case insensitive after calling compareOption on SearchChipAutocompleteInputComponent', () => {
|
||||||
|
const option1 = 'pdf';
|
||||||
|
const option2 = 'PdF';
|
||||||
|
expect(searchChipAutocompleteInputComponent.compareOption(option1, option2)).toBeTrue();
|
||||||
|
expect(searchChipAutocompleteInputComponent.compareOption(option1, `${option2}1`)).toBeFalse();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should remove preceding dot after calling formatChipValue on SearchChipAutocompleteInputComponent', () => {
|
||||||
|
const extension = 'pdf';
|
||||||
|
expect(searchChipAutocompleteInputComponent.formatChipValue(`.${extension}`)).toBe(extension);
|
||||||
|
expect(searchChipAutocompleteInputComponent.formatChipValue(extension)).toBe(extension);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should filter file extensions case insensitive without dots after calling filter on SearchChipAutocompleteInputComponent', () => {
|
||||||
|
const extensions = ['pdf', 'jpg', 'txt', 'png'];
|
||||||
|
const searchValue = 'p';
|
||||||
|
|
||||||
|
expect(searchChipAutocompleteInputComponent.filter(extensions, searchValue)).toEqual(['pdf', 'jpg', 'png']);
|
||||||
|
expect(searchChipAutocompleteInputComponent.filter(extensions, `.${searchValue}`)).toEqual(['pdf', 'png']);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should set placeholder for SearchChipAutocompleteInputComponent to correct value', () => {
|
||||||
|
expect(searchChipAutocompleteInputComponent.placeholder).toBe('SEARCH.SEARCH_PROPERTIES.FILE_TYPE');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('submitValues', () => {
|
||||||
|
const sizeField = 'content.size';
|
||||||
|
const nameField = 'cm:name';
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
component.id = 'properties';
|
||||||
|
component.settings = {
|
||||||
|
field: `${sizeField},${nameField}`
|
||||||
|
};
|
||||||
|
component.context = TestBed.inject(SearchQueryBuilderService);
|
||||||
|
fixture.detectChanges();
|
||||||
|
spyOn(component.displayValue$, 'next');
|
||||||
|
spyOn(component.context, 'update');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not search when settings is not set', () => {
|
||||||
|
component.settings = undefined;
|
||||||
|
typeInFileSizeInput();
|
||||||
|
|
||||||
|
component.submitValues();
|
||||||
|
expect(component.displayValue$.next).not.toHaveBeenCalled();
|
||||||
|
expect(component.context.queryFragments[component.id]).toBeUndefined();
|
||||||
|
expect(component.context.update).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not search when context is not set', () => {
|
||||||
|
component.context = undefined;
|
||||||
|
typeInFileSizeInput();
|
||||||
|
|
||||||
|
component.submitValues();
|
||||||
|
expect(component.displayValue$.next).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should set empty search when nothing is set', () => {
|
||||||
|
component.submitValues();
|
||||||
|
expect(component.displayValue$.next).toHaveBeenCalledWith('');
|
||||||
|
expect(component.context.queryFragments[component.id]).toBe('');
|
||||||
|
expect(component.context.update).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should search by at least KB by default when any size is typed', () => {
|
||||||
|
typeInFileSizeInput();
|
||||||
|
|
||||||
|
component.submitValues();
|
||||||
|
expect(component.displayValue$.next).toHaveBeenCalledWith('SEARCH.SEARCH_PROPERTIES.FILE_SIZE_OPERATOR.AT_LEAST 321 SEARCH.SEARCH_PROPERTIES.FILE_SIZE_UNIT_ABBREVIATION.KB');
|
||||||
|
expect(component.context.queryFragments[component.id]).toBe(`${sizeField}:[328704 TO MAX]`);
|
||||||
|
expect(component.context.update).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should search by at most MB after selecting proper options', () => {
|
||||||
|
typeInFileSizeInput();
|
||||||
|
clickFileSizeOperatorsSelect();
|
||||||
|
getSelectOptions()[1].nativeElement.click();
|
||||||
|
fixture.detectChanges();
|
||||||
|
clickFileSizeUnitsSelect();
|
||||||
|
getSelectOptions()[1].nativeElement.click();
|
||||||
|
fixture.detectChanges();
|
||||||
|
|
||||||
|
component.submitValues();
|
||||||
|
expect(component.displayValue$.next).toHaveBeenCalledWith('SEARCH.SEARCH_PROPERTIES.FILE_SIZE_OPERATOR.AT_MOST 321 SEARCH.SEARCH_PROPERTIES.FILE_SIZE_UNIT_ABBREVIATION.MB');
|
||||||
|
expect(component.context.queryFragments[component.id]).toBe(`${sizeField}:[0 TO 336592896]`);
|
||||||
|
expect(component.context.update).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should search by exactly GB after selecting proper options', () => {
|
||||||
|
typeInFileSizeInput();
|
||||||
|
clickFileSizeOperatorsSelect();
|
||||||
|
getSelectOptions()[2].nativeElement.click();
|
||||||
|
fixture.detectChanges();
|
||||||
|
clickFileSizeUnitsSelect();
|
||||||
|
getSelectOptions()[2].nativeElement.click();
|
||||||
|
fixture.detectChanges();
|
||||||
|
|
||||||
|
component.submitValues();
|
||||||
|
expect(component.displayValue$.next).toHaveBeenCalledWith('SEARCH.SEARCH_PROPERTIES.FILE_SIZE_OPERATOR.EXACTLY 321 SEARCH.SEARCH_PROPERTIES.FILE_SIZE_UNIT_ABBREVIATION.GB');
|
||||||
|
expect(component.context.queryFragments[component.id]).toBe(`${sizeField}:[344671125504 TO 344671125504]`);
|
||||||
|
expect(component.context.update).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should search by single file type', () => {
|
||||||
|
const extension = 'pdf';
|
||||||
|
getSearchChipAutocompleteInputComponent().optionsChanged.emit([extension]);
|
||||||
|
|
||||||
|
component.submitValues();
|
||||||
|
expect(component.displayValue$.next).toHaveBeenCalledWith(extension);
|
||||||
|
expect(component.context.queryFragments[component.id]).toBe(`${nameField}:("*.${extension}")`);
|
||||||
|
expect(component.context.update).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should search by multiple file types', () => {
|
||||||
|
getSearchChipAutocompleteInputComponent().optionsChanged.emit(['pdf', 'txt']);
|
||||||
|
|
||||||
|
component.submitValues();
|
||||||
|
expect(component.displayValue$.next).toHaveBeenCalledWith('pdf, txt');
|
||||||
|
expect(component.context.queryFragments[component.id]).toBe(`${nameField}:("*.pdf" OR "*.txt")`);
|
||||||
|
expect(component.context.update).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should search by file size and type', () => {
|
||||||
|
typeInFileSizeInput();
|
||||||
|
getSearchChipAutocompleteInputComponent().optionsChanged.emit(['pdf', 'txt']);
|
||||||
|
|
||||||
|
component.submitValues();
|
||||||
|
expect(component.displayValue$.next).toHaveBeenCalledWith('SEARCH.SEARCH_PROPERTIES.FILE_SIZE_OPERATOR.AT_LEAST 321 SEARCH.SEARCH_PROPERTIES.FILE_SIZE_UNIT_ABBREVIATION.KB, pdf, txt');
|
||||||
|
expect(component.context.queryFragments[component.id]).toBe(`${sizeField}:[328704 TO MAX] AND ${nameField}:("*.pdf" OR "*.txt")`);
|
||||||
|
expect(component.context.update).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('hasValidValue', () => {
|
||||||
|
it('should return true', () => {
|
||||||
|
expect(component.hasValidValue()).toBeTrue();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('getCurrentValue', () => {
|
||||||
|
it('should return correct value when nothing changed', () => {
|
||||||
|
expect(component.getCurrentValue()).toEqual({
|
||||||
|
fileSizeCondition: {
|
||||||
|
fileSize: null,
|
||||||
|
fileSizeUnit: FileSizeUnit.KB,
|
||||||
|
fileSizeOperator: FileSizeOperator.AT_LEAST
|
||||||
|
},
|
||||||
|
fileExtensions: undefined
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return correct value when inputs changed', () => {
|
||||||
|
fixture.detectChanges();
|
||||||
|
typeInFileSizeInput();
|
||||||
|
clickFileSizeOperatorsSelect();
|
||||||
|
getSelectOptions()[1].nativeElement.click();
|
||||||
|
fixture.detectChanges();
|
||||||
|
clickFileSizeUnitsSelect();
|
||||||
|
getSelectOptions()[1].nativeElement.click();
|
||||||
|
fixture.detectChanges();
|
||||||
|
const extensions = ['pdf', 'txt'];
|
||||||
|
getSearchChipAutocompleteInputComponent().optionsChanged.emit(extensions);
|
||||||
|
|
||||||
|
expect(component.getCurrentValue()).toEqual({
|
||||||
|
fileSizeCondition: {
|
||||||
|
fileSize: 321,
|
||||||
|
fileSizeUnit: FileSizeUnit.MB,
|
||||||
|
fileSizeOperator: FileSizeOperator.AT_MOST
|
||||||
|
},
|
||||||
|
fileExtensions: extensions
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('reset', () => {
|
||||||
|
let searchChipAutocompleteInputComponent: SearchChipAutocompleteInputComponent;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
fixture.detectChanges();
|
||||||
|
typeInFileSizeInput();
|
||||||
|
clickFileSizeOperatorsSelect();
|
||||||
|
getSelectOptions()[1].nativeElement.click();
|
||||||
|
fixture.detectChanges();
|
||||||
|
clickFileSizeUnitsSelect();
|
||||||
|
getSelectOptions()[1].nativeElement.click();
|
||||||
|
fixture.detectChanges();
|
||||||
|
searchChipAutocompleteInputComponent = getSearchChipAutocompleteInputComponent();
|
||||||
|
searchChipAutocompleteInputComponent.optionsChanged.emit(['pdf', 'txt']);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should reset form', () => {
|
||||||
|
spyOn(searchChipAutocompleteInputComponent.optionsChanged, 'emit');
|
||||||
|
|
||||||
|
component.reset();
|
||||||
|
fixture.detectChanges();
|
||||||
|
expect(component.form.value).toEqual({
|
||||||
|
fileSize: null,
|
||||||
|
fileSizeUnit: FileSizeUnit.KB,
|
||||||
|
fileSizeOperator: FileSizeOperator.AT_LEAST
|
||||||
|
});
|
||||||
|
expect(searchChipAutocompleteInputComponent.optionsChanged.emit).toHaveBeenCalledWith([]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should display empty value', () => {
|
||||||
|
spyOn(component.displayValue$, 'next');
|
||||||
|
|
||||||
|
component.reset();
|
||||||
|
expect(component.displayValue$.next).toHaveBeenCalledWith('');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('setValue', () => {
|
||||||
|
let searchProperties: SearchProperties;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
searchProperties = {
|
||||||
|
fileSizeCondition: {
|
||||||
|
fileSize: 321,
|
||||||
|
fileSizeUnit: FileSizeUnit.MB,
|
||||||
|
fileSizeOperator: FileSizeOperator.AT_MOST
|
||||||
|
},
|
||||||
|
fileExtensions: ['pdf', 'txt']
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should fill form', () => {
|
||||||
|
component.setValue(searchProperties);
|
||||||
|
expect(component.form.value).toEqual(searchProperties.fileSizeCondition);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should search based on passed value', () => {
|
||||||
|
const sizeField = 'content.size';
|
||||||
|
const nameField = 'cm:name';
|
||||||
|
component.id = 'properties';
|
||||||
|
component.settings = {
|
||||||
|
field: `${sizeField},${nameField}`
|
||||||
|
};
|
||||||
|
component.context = TestBed.inject(SearchQueryBuilderService);
|
||||||
|
component.ngOnInit();
|
||||||
|
spyOn(component.displayValue$, 'next');
|
||||||
|
spyOn(component.context, 'update');
|
||||||
|
|
||||||
|
component.setValue(searchProperties);
|
||||||
|
expect(component.displayValue$.next).toHaveBeenCalledWith('SEARCH.SEARCH_PROPERTIES.FILE_SIZE_OPERATOR.AT_MOST 321 SEARCH.SEARCH_PROPERTIES.FILE_SIZE_UNIT_ABBREVIATION.MB, pdf, txt');
|
||||||
|
expect(component.context.queryFragments[component.id]).toBe(`${sizeField}:[0 TO 336592896] AND ${nameField}:("*.pdf" OR "*.txt")`);
|
||||||
|
expect(component.context.update).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('preventIncorrectNumberCharacters', () => {
|
||||||
|
it('should prevent typing - character', () => {
|
||||||
|
expect(component.preventIncorrectNumberCharacters(new KeyboardEvent('keydown', {
|
||||||
|
key: '-'
|
||||||
|
}))).toBeFalse();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should prevent typing e character', () => {
|
||||||
|
expect(component.preventIncorrectNumberCharacters(new KeyboardEvent('keydown', {
|
||||||
|
key: 'e'
|
||||||
|
}))).toBeFalse();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should prevent typing + character', () => {
|
||||||
|
expect(component.preventIncorrectNumberCharacters(new KeyboardEvent('keydown', {
|
||||||
|
key: '+'
|
||||||
|
}))).toBeFalse();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should allow typing digit', () => {
|
||||||
|
expect(component.preventIncorrectNumberCharacters(new KeyboardEvent('keydown', {
|
||||||
|
key: '1'
|
||||||
|
}))).toBeTrue();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
@ -0,0 +1,216 @@
|
|||||||
|
/*!
|
||||||
|
* @license
|
||||||
|
* Copyright © 2005-2023 Hyland Software, Inc. and its affiliates. All rights reserved.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { AfterViewChecked, Component, ElementRef, OnInit, ViewChild, ViewEncapsulation } from '@angular/core';
|
||||||
|
import { FormBuilder } from '@angular/forms';
|
||||||
|
import { FileSizeCondition } from './file-size-condition';
|
||||||
|
import { FileSizeOperator } from './file-size-operator.enum';
|
||||||
|
import { FileSizeUnit } from './file-size-unit.enum';
|
||||||
|
import { Subject } from 'rxjs';
|
||||||
|
import { SearchWidgetSettings } from '../../models/search-widget-settings.interface';
|
||||||
|
import { SearchQueryBuilderService } from '../../services/search-query-builder.service';
|
||||||
|
import { SearchProperties } from './search-properties';
|
||||||
|
import { TranslateService } from '@ngx-translate/core';
|
||||||
|
import { SearchWidget } from '../../models/search-widget.interface';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'adf-search-properties',
|
||||||
|
templateUrl: './search-properties.component.html',
|
||||||
|
styleUrls: ['./search-properties.component.scss'],
|
||||||
|
encapsulation: ViewEncapsulation.None
|
||||||
|
})
|
||||||
|
export class SearchPropertiesComponent implements OnInit, AfterViewChecked, SearchWidget {
|
||||||
|
id: string;
|
||||||
|
settings?: SearchWidgetSettings;
|
||||||
|
context?: SearchQueryBuilderService;
|
||||||
|
startValue: SearchProperties;
|
||||||
|
displayValue$ = new Subject<string>();
|
||||||
|
|
||||||
|
private _form = this.formBuilder.nonNullable.group<FileSizeCondition>({
|
||||||
|
fileSizeOperator: FileSizeOperator.AT_LEAST,
|
||||||
|
fileSize: undefined,
|
||||||
|
fileSizeUnit: FileSizeUnit.KB
|
||||||
|
});
|
||||||
|
private _fileSizeOperators = Object.keys(FileSizeOperator).map<string>(key => FileSizeOperator[key]);
|
||||||
|
private _fileSizeUnits = [FileSizeUnit.KB, FileSizeUnit.MB, FileSizeUnit.GB];
|
||||||
|
private canvas = document.createElement('canvas');
|
||||||
|
private _fileSizeOperatorsMaxWidth: number;
|
||||||
|
private _selectedExtensions: string[];
|
||||||
|
private _reset$ = new Subject<void>();
|
||||||
|
private sizeField: string;
|
||||||
|
private nameField: string;
|
||||||
|
|
||||||
|
@ViewChild('fileSizeOperatorSelect', {read: ElementRef})
|
||||||
|
fileSizeOperatorSelectElement: ElementRef;
|
||||||
|
|
||||||
|
get form(): SearchPropertiesComponent['_form'] {
|
||||||
|
return this._form;
|
||||||
|
}
|
||||||
|
|
||||||
|
get fileSizeOperators(): string[] {
|
||||||
|
return this._fileSizeOperators;
|
||||||
|
}
|
||||||
|
|
||||||
|
get fileSizeUnits(): FileSizeUnit[] {
|
||||||
|
return this._fileSizeUnits;
|
||||||
|
}
|
||||||
|
|
||||||
|
get fileSizeOperatorsMaxWidth(): number {
|
||||||
|
return this._fileSizeOperatorsMaxWidth;
|
||||||
|
}
|
||||||
|
|
||||||
|
get reset$(): Subject<void> {
|
||||||
|
return this._reset$;
|
||||||
|
}
|
||||||
|
|
||||||
|
set selectedExtensions(extensions: string[]) {
|
||||||
|
this._selectedExtensions = extensions;
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(private formBuilder: FormBuilder, private translateService: TranslateService) {}
|
||||||
|
|
||||||
|
ngOnInit() {
|
||||||
|
if (this.settings) {
|
||||||
|
if (!this.settings.fileExtensions) {
|
||||||
|
this.settings.fileExtensions = [];
|
||||||
|
}
|
||||||
|
[this.sizeField, this.nameField] = this.settings.field.split(',');
|
||||||
|
}
|
||||||
|
if (this.startValue) {
|
||||||
|
this.setValue(this.startValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ngAfterViewChecked() {
|
||||||
|
if (this.fileSizeOperatorSelectElement?.nativeElement.clientWidth && !this._fileSizeOperatorsMaxWidth) {
|
||||||
|
setTimeout(() => {
|
||||||
|
const extraFreeSpace = 20;
|
||||||
|
this._fileSizeOperatorsMaxWidth = Math.max(...this._fileSizeOperators.map((operator) =>
|
||||||
|
this.getOperatorNameWidth(operator, this.getCanvasFont(this.fileSizeOperatorSelectElement.nativeElement)))) +
|
||||||
|
this.fileSizeOperatorSelectElement.nativeElement.querySelector('.mat-select-arrow-wrapper').clientWidth +
|
||||||
|
extraFreeSpace;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
narrowDownAllowedCharacters(event: Event) {
|
||||||
|
const value = (event.target as HTMLInputElement).value;
|
||||||
|
if (!(event.target as HTMLInputElement).value) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if ((event as InputEvent).data !== ',' && (event as InputEvent).data !== '.') {
|
||||||
|
(event.target as HTMLInputElement).value = value.replace(/[^0-9.,]/g, '');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
clearNumberFieldWhenInvalid(event: FocusEvent) {
|
||||||
|
if (!(event.target as HTMLInputElement).validity.valid) {
|
||||||
|
this.form.controls.fileSize.setValue(undefined);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
preventIncorrectNumberCharacters(event: KeyboardEvent): boolean {
|
||||||
|
return event.key !== '-' && event.key !== 'e' && event.key !== '+';
|
||||||
|
}
|
||||||
|
|
||||||
|
compareFileExtensions(extension1: string, extension2: string): boolean {
|
||||||
|
return extension1.toUpperCase() === extension2.toUpperCase();
|
||||||
|
}
|
||||||
|
|
||||||
|
getExtensionWithoutDot(extension: string): string {
|
||||||
|
const extensionSplitByDot = extension.split('.');
|
||||||
|
return extensionSplitByDot[extensionSplitByDot.length - 1];
|
||||||
|
}
|
||||||
|
|
||||||
|
filterExtensions = (extensions: string[], filterValue: string): string[] => {
|
||||||
|
const filterValueLowerCase = this.getExtensionWithoutDot(filterValue).toLowerCase();
|
||||||
|
const extensionWithDot = filterValue.startsWith('.');
|
||||||
|
return extensions.filter((option) => {
|
||||||
|
const optionLowerCase = option.toLowerCase();
|
||||||
|
return extensionWithDot && filterValueLowerCase ? optionLowerCase.startsWith(filterValueLowerCase) : optionLowerCase.includes(filterValue);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
reset() {
|
||||||
|
this.form.reset();
|
||||||
|
this.reset$.next();
|
||||||
|
this.displayValue$.next('');
|
||||||
|
}
|
||||||
|
|
||||||
|
submitValues() {
|
||||||
|
if (this.settings && this.context) {
|
||||||
|
let query = '';
|
||||||
|
let displayedValue = '';
|
||||||
|
if (this.form.value.fileSize !== undefined && this.form.value.fileSize !== null) {
|
||||||
|
displayedValue = `${this.translateService.instant(this.form.value.fileSizeOperator)} ${this.form.value.fileSize} ${this.translateService.instant(this.form.value.fileSizeUnit.abbreviation)}`;
|
||||||
|
const size = this.form.value.fileSize * this.form.value.fileSizeUnit.bytes;
|
||||||
|
switch (this.form.value.fileSizeOperator) {
|
||||||
|
case FileSizeOperator.AT_MOST:
|
||||||
|
query = `${this.sizeField}:[0 TO ${size}]`;
|
||||||
|
break;
|
||||||
|
case FileSizeOperator.AT_LEAST:
|
||||||
|
query = `${this.sizeField}:[${size} TO MAX]`;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
query = `${this.sizeField}:[${size} TO ${size}]`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (this._selectedExtensions?.length) {
|
||||||
|
if (query) {
|
||||||
|
query += ' AND ';
|
||||||
|
displayedValue += ', ';
|
||||||
|
}
|
||||||
|
query += `${this.nameField}:("*.${this._selectedExtensions.join('" OR "*.')}")`;
|
||||||
|
displayedValue += this._selectedExtensions.join(', ');
|
||||||
|
}
|
||||||
|
this.displayValue$.next(displayedValue);
|
||||||
|
this.context.queryFragments[this.id] = query;
|
||||||
|
this.context.update();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
hasValidValue(): boolean {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
getCurrentValue(): SearchProperties {
|
||||||
|
return {
|
||||||
|
fileSizeCondition: this.form.getRawValue(),
|
||||||
|
fileExtensions: this._selectedExtensions
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
setValue(searchProperties: SearchProperties) {
|
||||||
|
this.form.patchValue(searchProperties.fileSizeCondition);
|
||||||
|
this.selectedExtensions = searchProperties.fileExtensions;
|
||||||
|
this.submitValues();
|
||||||
|
}
|
||||||
|
|
||||||
|
private getOperatorNameWidth(operator: string, font: string): number {
|
||||||
|
const context = this.canvas.getContext('2d');
|
||||||
|
context.font = font;
|
||||||
|
return context.measureText(this.translateService.instant(operator)).width;
|
||||||
|
}
|
||||||
|
|
||||||
|
private getCssStyle(element: HTMLElement, property: string): string {
|
||||||
|
return window.getComputedStyle(element, null).getPropertyValue(property);
|
||||||
|
}
|
||||||
|
|
||||||
|
private getCanvasFont(el: HTMLElement): string {
|
||||||
|
return `${this.getCssStyle(el, 'font-weight')} ${this.getCssStyle(el, 'font-size')} ${this.getCssStyle(el, 'font-family')}`;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,23 @@
|
|||||||
|
/*!
|
||||||
|
* @license
|
||||||
|
* Copyright © 2005-2023 Hyland Software, Inc. and its affiliates. All rights reserved.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { FileSizeCondition } from './file-size-condition';
|
||||||
|
|
||||||
|
export interface SearchProperties {
|
||||||
|
fileSizeCondition: FileSizeCondition;
|
||||||
|
fileExtensions: string[];
|
||||||
|
}
|
@ -51,6 +51,7 @@ import { SearchWidgetChipComponent } from './components/search-filter-chips/sear
|
|||||||
import { SearchFacetChipComponent } from './components/search-filter-chips/search-facet-chip/search-facet-chip.component';
|
import { SearchFacetChipComponent } from './components/search-filter-chips/search-facet-chip/search-facet-chip.component';
|
||||||
import { SearchLogicalFilterComponent } from './components/search-logical-filter/search-logical-filter.component';
|
import { SearchLogicalFilterComponent } from './components/search-logical-filter/search-logical-filter.component';
|
||||||
import { ResetSearchDirective } from './components/reset-search.directive';
|
import { ResetSearchDirective } from './components/reset-search.directive';
|
||||||
|
import { SearchPropertiesComponent } from './components/search-properties/search-properties.component';
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [
|
imports: [
|
||||||
@ -88,7 +89,8 @@ import { ResetSearchDirective } from './components/reset-search.directive';
|
|||||||
SearchWidgetChipComponent,
|
SearchWidgetChipComponent,
|
||||||
SearchFacetChipComponent,
|
SearchFacetChipComponent,
|
||||||
SearchLogicalFilterComponent,
|
SearchLogicalFilterComponent,
|
||||||
ResetSearchDirective
|
ResetSearchDirective,
|
||||||
|
SearchPropertiesComponent
|
||||||
],
|
],
|
||||||
exports: [
|
exports: [
|
||||||
SearchComponent,
|
SearchComponent,
|
||||||
|
@ -25,6 +25,7 @@ import { SearchDateRangeComponent } from '../components/search-date-range/search
|
|||||||
import { SearchDatetimeRangeComponent } from '../components/search-datetime-range/search-datetime-range.component';
|
import { SearchDatetimeRangeComponent } from '../components/search-datetime-range/search-datetime-range.component';
|
||||||
import { SearchLogicalFilterComponent } from '../components/search-logical-filter/search-logical-filter.component';
|
import { SearchLogicalFilterComponent } from '../components/search-logical-filter/search-logical-filter.component';
|
||||||
import { SearchFilterAutocompleteChipsComponent } from '../components/search-filter-autocomplete-chips/search-filter-autocomplete-chips.component';
|
import { SearchFilterAutocompleteChipsComponent } from '../components/search-filter-autocomplete-chips/search-filter-autocomplete-chips.component';
|
||||||
|
import { SearchPropertiesComponent } from '../components/search-properties/search-properties.component';
|
||||||
|
|
||||||
@Injectable({
|
@Injectable({
|
||||||
providedIn: 'root'
|
providedIn: 'root'
|
||||||
@ -38,6 +39,7 @@ export class SearchFilterService {
|
|||||||
text: SearchTextComponent,
|
text: SearchTextComponent,
|
||||||
radio: SearchRadioComponent,
|
radio: SearchRadioComponent,
|
||||||
slider: SearchSliderComponent,
|
slider: SearchSliderComponent,
|
||||||
|
properties: SearchPropertiesComponent,
|
||||||
'number-range': SearchNumberRangeComponent,
|
'number-range': SearchNumberRangeComponent,
|
||||||
'check-list': SearchCheckListComponent,
|
'check-list': SearchCheckListComponent,
|
||||||
'date-range': SearchDateRangeComponent,
|
'date-range': SearchDateRangeComponent,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user