Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
[isLoading]="isLoading"
[placeholder]="'Hashlists...'"
[items]="selectHashlists"
[label]="'Select or search Hashlists *'"
[label]="'Select or search Hashlists'"
[mergeIdAndName]="true"
formControlName="hashlistIds"
style="flex: 1;"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@
[isLoading]="isLoading"
[placeholder]="'Tasks...'"
[items]="selectPretasks"
[label]="'Select or search Tasks *'"
[label]="'Select or search Tasks'"
[isRequired]="true"
[mergeIdAndName]="true"
formControlName="pretasks"
style="flex: 1;"
Expand Down
3 changes: 2 additions & 1 deletion src/app/hashlists/new-hashlist/new-hashlist.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@
[isLoading]="isLoadingHashtypes"
[placeholder]="'Hashtypes...'"
[items]="selectHashtypes"
[label]="'Hashtype *'"
[label]="'Select or search Hashtype'"
[isRequired]="true"
formControlName="hashTypeId"
[multiselectEnabled]="false"
[mergeIdAndName]="true"
Expand Down
30 changes: 20 additions & 10 deletions src/app/shared/input/multiselect/multiselect.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -5,34 +5,44 @@
<ng-template #content>
<mat-form-field class="custom-chip-grid">
<mat-label>{{label}}</mat-label>
<mat-chip-grid #chipGrid aria-label="Items selection">
<mat-chip-grid
#chipGridRef
[(ngModel)]="chipGridValidation"
#chipGridNgModel="ngModel"
name="selectedItems"
[required]="isRequired"
aria-label="Items selection"
>
<mat-chip-row *ngFor="let item of selectedItems" (removed)="remove(item)">
<div matTooltip="{{item.name }}">
<div matTooltip="{{ item.name }}">
{{ item.id }}
<span>{{ item.name.length > 30 ? (item.name | slice:0:30) + '...' : item.name }}</span>
<span>{{ item.name?.length > 30 ? (item.name | slice:0:30) + '...' : item.name }}</span>
</div>
<button matChipRemove [attr.aria-label]="'remove ' + item">
<mat-icon>cancel</mat-icon>
</button>
</mat-chip-row>

</mat-chip-grid>
<input
matInput
#selectInput
[placeholder]="placeholder"
[matChipInputFor]="chipGrid"
[matChipInputFor]="chipGridRef"
[matAutocomplete]="auto"
[matChipInputSeparatorKeyCodes]="separatorKeysCodes"
(matChipInputTokenEnd)="onChangeValue(value)"
(matChipInputTokenEnd)="onInputChipAdd($event)"
(input)="onSearchInputChange()"
[(ngModel)]="searchTerm"
[required]="isRequired"
placeholder="{{ placeholder }}"
/>
<mat-autocomplete #auto="matAutocomplete" (optionSelected)="selected($event)">

<mat-autocomplete #auto="matAutocomplete" (optionSelected)="onAutocompleteSelect($event.option.value)">
<mat-option *ngFor="let item of filteredItems | async" [value]="item">
<span [innerHTML]="updateHighlightedValue(item.id + ' - ' + item.name, searchTerm)"></span>
</mat-option>
</mat-autocomplete>
<mat-error>{{ label }} is required</mat-error>

<mat-error *ngIf="chipGridValidation.length === 0">
This field is required.
</mat-error>
</mat-form-field>
</ng-template>
110 changes: 74 additions & 36 deletions src/app/shared/input/multiselect/multiselect.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { COMMA, ENTER } from '@angular/cdk/keycodes';
import { AfterViewInit, ChangeDetectionStrategy, Component, Input, ViewChild, forwardRef } from '@angular/core';
import { NG_VALUE_ACCESSOR } from '@angular/forms';
import { MatAutocompleteSelectedEvent } from '@angular/material/autocomplete';
import { MatChipInputEvent } from '@angular/material/chips';
import { MatInput } from '@angular/material/input';
import { DomSanitizer, SafeHtml } from '@angular/platform-browser';

Expand All @@ -28,22 +29,27 @@ import { SelectOption, extractIds } from '@src/app/shared/utils/forms';
changeDetection: ChangeDetectionStrategy.OnPush,
standalone: false
})
export class InputMultiSelectComponent extends AbstractInputComponent<any> implements AfterViewInit {
export class InputMultiSelectComponent extends AbstractInputComponent<number | number[]> implements AfterViewInit {
@Input() label = 'Select or search:';
@Input() placeholder = 'Select or search';
@Input() isLoading = false;
@Input() items: SelectOption[] = [];
@Input() multiselectEnabled = true;
@Input() mergeIdAndName = false;
@Input() initialHashlistId: any;
@Input() initialHashlistId: string | number;

@ViewChild('selectInput', { read: MatInput }) selectInput: MatInput;

private searchInputSubject = new Subject<string>();
filteredItems: Observable<any[]>;
selectedItems: SelectOption[] = [];
filteredItems: Observable<SelectOption[]>;
searchTerm = '';

// Visual chips
selectedItems: SelectOption[] = [];

// Validation model (dummy, never displayed)
chipGridValidation: SelectOption[] = [];

readonly separatorKeysCodes: number[] = [COMMA, ENTER]; // ENTER and COMMA key codes

constructor(private sanitizer: DomSanitizer) {
Expand Down Expand Up @@ -79,19 +85,74 @@ export class InputMultiSelectComponent extends AbstractInputComponent<any> imple
}
}

public addChip(item: SelectOption): void {
if (!this.selectedItems.find((i) => i.id === item.id)) {
// Update visual array
if (this.multiselectEnabled) {
this.selectedItems.push(item);
} else {
this.selectedItems = [item];
}

// Update validation array
this.chipGridValidation = [...this.selectedItems];

// Remove item from available list
const index = this.items.findIndex((i) => i.id === item.id);
if (index !== -1) this.items.splice(index, 1);

// Update filtered items
this.searchInputSubject.next(this.searchTerm);

// Notify Angular forms
this.onChangeValue(this.selectedItems);
}
}

public remove(item: SelectOption): void {
const index = this.selectedItems.findIndex((i) => i.id === item.id);
if (index >= 0) {
this.selectedItems.splice(index, 1);
this.chipGridValidation = [...this.selectedItems];

// Put back to available items
this.items.push(item);
this.searchInputSubject.next(this.searchTerm);

this.onChangeValue(this.selectedItems);
}
}

// When typing a separator key (ENTER, COMMA)
onInputChipAdd(event: MatChipInputEvent) {
const value = event.value?.trim();
if (!value) return;

this.addChip({ id: value, name: value });
event.chipInput.clear();
this.searchTerm = '';
}

// When selecting from autocomplete
onAutocompleteSelect(selected: SelectOption) {
this.addChip(selected);
this.searchTerm = '';
this.searchInputSubject.next(this.searchTerm);
}

/**
* Handles the change in the input value and triggers the corresponding change events.
*
* @param {any} value - The new value of the input.
* @param value - The new value of the input.
* @returns {void}
*/
/**
* Handles the change in the input value and triggers the corresponding change events.
*
* @param {any} value - The new value of the input.
* @param value - The new value of the input.
* @returns {void}
*/
onChangeValue(value): void {
onChangeValue(value: SelectOption | SelectOption[]): void {
if (!this.multiselectEnabled) {
if (Array.isArray(value)) {
this.value = extractIds(value, 'id')[0];
Expand Down Expand Up @@ -122,41 +183,18 @@ export class InputMultiSelectComponent extends AbstractInputComponent<any> imple
this.searchInputSubject.next(this.searchTerm);
}

/**
* Removes the specified item from the selected items.
*
* @param {SelectOption} item - The item to be removed.
* @returns {void}
*/
public remove(item: SelectOption): void {
const index = this.selectedItems.indexOf(item);

if (index >= 0) {
// Add the removed item back to the unselected items
this.items.push(item);

this.items.sort((a, b) => parseInt(a.id) - parseInt(b.id));

// Remove the item from the selected items
this.selectedItems.splice(index, 1);

// Update the filteredItems observable
this.searchInputSubject.next(this.searchTerm);

// Notify about the change
this.onChangeValue(this.selectedItems);
// this.onTouched();
}
}

/**
* Filters the items based on the provided search value.
*
* @param {string} value - The search value to filter the items.
* @returns {SelectOption[]} - The filtered array of items.
*/
private _filter(value: string): SelectOption[] {
const filterValue = value.toLowerCase();
private _filter(value: string | SelectOption): SelectOption[] {
// If a SelectOption is passed by accident, convert to string
const searchString = typeof value === 'string' ? value : (value.name ?? '');

const filterValue = searchString.toLowerCase();

return this.items.filter((item: SelectOption) => {
const nameToSearch = this.mergeIdAndName ? `${item.id} ${item.name}`.toLowerCase() : item.name.toLowerCase();
return nameToSearch.includes(filterValue);
Expand Down
7 changes: 4 additions & 3 deletions src/app/shared/input/number/number.component.html
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
<mat-form-field>
<mat-label>{{ title }}</mat-label>
<div [class.is-invalid]="error">
<input matInput type="number" [id]="inputId" [(ngModel)]="value" (change)="onChange(value)" [class.is-invalid]="error">
<input #inputField="ngModel" matInput type="number" [id]="inputId" [(ngModel)]="value" (change)="onChange(value)" [required]="isRequired" [class.is-invalid]="error">
</div>
<div *ngIf="error">{{ error }}</div>
<mat-hint *ngIf="hint">{{ hint }}</mat-hint>
<mat-error *ngIf="inputField.errors && (inputField.dirty || inputField.touched)">
<span *ngIf="inputField.errors.required">{{ title }} is required</span>
</mat-error>
</mat-form-field>
23 changes: 12 additions & 11 deletions src/app/shared/input/number/number.component.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { AbstractInputComponent } from '../abstract-input';
import { Component, forwardRef } from '@angular/core';
import { NG_VALUE_ACCESSOR } from '@angular/forms';

import { AbstractInputComponent } from '@src/app/shared/input/abstract-input';

/**
* Custom Input Number Component.
*
Expand All @@ -15,16 +16,16 @@ import { NG_VALUE_ACCESSOR } from '@angular/forms';
* ```
*/
@Component({
selector: 'input-number',
templateUrl: './number.component.html',
providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => InputNumberComponent),
multi: true
}
],
standalone: false
selector: 'input-number',
templateUrl: './number.component.html',
providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => InputNumberComponent),
multi: true
}
],
standalone: false
})
export class InputNumberComponent extends AbstractInputComponent<boolean> {
constructor() {
Expand Down
6 changes: 3 additions & 3 deletions src/app/shared/input/text-area/text-area.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -4,21 +4,21 @@
matTooltip="{{ tooltip }}"
matTooltipPosition="below"
matTooltipClass="tooltip-custom-style"
container="body"
matTooltipShowDelay="500"
aria-hidden="true"
*ngIf="tooltip"
>
info
</mat-icon>

</mat-label>
<textarea
#inputField
matInput
[class.is-invalid]="error"
[id]="inputId"
[(ngModel)]="value"
(ngModelChange)="onChange(value)"
matAutosize
cdkTextareaAutosize
[required]="isRequired"
class="custom-textarea"
></textarea>
Expand Down
23 changes: 12 additions & 11 deletions src/app/shared/input/text-area/text-area.component.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { AbstractInputComponent } from '../abstract-input';
import { Component, forwardRef } from '@angular/core';
import { NG_VALUE_ACCESSOR } from '@angular/forms';

import { AbstractInputComponent } from '@src/app/shared/input/abstract-input';

/**
* Custom Input Text Component.
*
Expand All @@ -17,16 +18,16 @@ import { NG_VALUE_ACCESSOR } from '@angular/forms';
* ```
*/
@Component({
selector: 'input-text-area',
templateUrl: './text-area.component.html',
providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => InputTextAreaComponent),
multi: true
}
],
standalone: false
selector: 'input-text-area',
templateUrl: './text-area.component.html',
providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => InputTextAreaComponent),
multi: true
}
],
standalone: false
})
export class InputTextAreaComponent extends AbstractInputComponent<string> {
constructor() {
Expand Down
4 changes: 2 additions & 2 deletions src/app/tasks/edit-supertasks/edit-supertasks.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@
<h5>Add Pretasks</h5>
<form [formGroup]="updateForm">
<div fxLayout="column" fxLayoutAlign="start start" *ngIf="!isLoading; else loadingTemplate">
<input-multiselect [isLoading]="isLoading" [placeholder]="'Pretasks...'" [items]="selectPretasks"
[label]="'Select or search Pretasks *'" [mergeIdAndName]="true" formControlName="pretasks"
<input-multiselect [isLoading]="isLoading" [placeholder]="'Pretasks...'" [items]="selectPretasks" [isRequired]="true"
[label]="'Select or search Pretasks'" [mergeIdAndName]="true" formControlName="pretasks"
style="flex: 1;"></input-multiselect>
<div fxLayout="row" fxLayoutAlign="start center" style="margin-top: 10px;">
<button-submit [name]="'Add'" (click)="onSubmit()"></button-submit>
Expand Down
Loading