Browse Source

Refactor lookup and permission components, add styles

Refactored lookup-search and permission-checkbox-list components to use SCSS for dropdown and list container styling. Improved resource permission management component with signals and untracked for state updates, and replaced manual destroy logic with Angular's DestroyRef and takeUntilDestroyed. Updated HTML templates for better readability and accessibility.
pull/24459/head
Fahri Gedik 1 month ago
parent
commit
faf4ea1bda
  1. 83
      npm/ng-packs/packages/components/lookup/src/lib/lookup-search.component.html
  2. 5
      npm/ng-packs/packages/components/lookup/src/lib/lookup-search.component.scss
  3. 23
      npm/ng-packs/packages/components/lookup/src/lib/lookup-search.component.ts
  4. 10
      npm/ng-packs/packages/permission-management/src/lib/components/resource-permission-management/permission-checkbox-list/permission-checkbox-list.component.html
  5. 4
      npm/ng-packs/packages/permission-management/src/lib/components/resource-permission-management/permission-checkbox-list/permission-checkbox-list.component.scss
  6. 1
      npm/ng-packs/packages/permission-management/src/lib/components/resource-permission-management/permission-checkbox-list/permission-checkbox-list.component.ts
  7. 17
      npm/ng-packs/packages/permission-management/src/lib/components/resource-permission-management/provider-key-search/provider-key-search.component.ts
  8. 29
      npm/ng-packs/packages/permission-management/src/lib/components/resource-permission-management/resource-permission-management.component.ts

83
npm/ng-packs/packages/components/lookup/src/lib/lookup-search.component.html

@ -1,64 +1,45 @@
<div class="abp-lookup-container position-relative">
@if (label()) {
<label class="form-label">{{ label() | abpLocalization }}</label>
<label class="form-label">{{ label() | abpLocalization }}</label>
}
<div class="input-group">
<input
type="text"
class="form-control"
[placeholder]="placeholder() | abpLocalization"
[ngModel]="displayValue()"
(ngModelChange)="onSearchInput($event)"
(focus)="onSearchFocus()"
(blur)="onSearchBlur($event)"
[disabled]="disabled()"
/>
<input type="text" class="form-control" [placeholder]="placeholder() | abpLocalization" [ngModel]="displayValue()"
(ngModelChange)="onSearchInput($event)" (focus)="onSearchFocus()" (blur)="onSearchBlur($event)"
[disabled]="disabled()" />
@if (displayValue() && !disabled()) {
<button
type="button"
class="btn btn-outline-secondary"
(mousedown)="clearSelection()"
tabindex="-1"
>
<i class="fa fa-times"></i>
</button>
<button type="button" class="btn btn-outline-secondary" (mousedown)="clearSelection()" tabindex="-1">
<i class="fa fa-times"></i>
</button>
}
</div>
@if (showDropdown() && !disabled()) {
<div
class="abp-lookup-dropdown list-group position-absolute w-100 shadow"
style="z-index: 1050; max-height: 200px; overflow-y: auto"
>
@if (isLoading()) {
<div class="list-group-item text-center py-3">
<i class="fa fa-spinner fa-spin me-2"></i>
{{ 'AbpUi::Loading' | abpLocalization }}
</div>
} @else if (searchResults().length > 0) {
@for (item of searchResults(); track item.key) {
<button
type="button"
class="list-group-item list-group-item-action"
(mousedown)="selectItem(item)"
>
@if (itemTemplate()) {
<ng-container *ngTemplateOutlet="itemTemplate()!; context: { $implicit: item }" />
} @else {
{{ getDisplayValue(item) }}
}
</button>
}
} @else if (displayValue()) {
@if (noResultsTemplate()) {
<ng-container *ngTemplateOutlet="noResultsTemplate()!" />
} @else {
<div class="list-group-item text-muted">
{{ 'AbpUi::NoRecordsFound' | abpLocalization }}
</div>
}
<div class="abp-lookup-dropdown list-group position-absolute w-100 shadow">
@if (isLoading()) {
<div class="list-group-item text-center py-3">
<i class="fa fa-spinner fa-spin me-2"></i>
{{ 'AbpUi::Loading' | abpLocalization }}
</div>
} @else if (searchResults().length > 0) {
@for (item of searchResults(); track item.key) {
<button type="button" class="list-group-item list-group-item-action" (mousedown)="selectItem(item)">
@if (itemTemplate()) {
<ng-container *ngTemplateOutlet="itemTemplate()!; context: { $implicit: item }" />
} @else {
{{ getDisplayValue(item) }}
}
</button>
}
} @else if (displayValue()) {
@if (noResultsTemplate()) {
<ng-container *ngTemplateOutlet="noResultsTemplate()!" />
} @else {
<div class="list-group-item text-muted">
{{ 'AbpUi::NoRecordsFound' | abpLocalization }}
</div>
}
}
</div>
}
</div>
</div>

5
npm/ng-packs/packages/components/lookup/src/lib/lookup-search.component.scss

@ -0,0 +1,5 @@
.abp-lookup-dropdown {
z-index: 1050;
max-height: 200px;
overflow-y: auto;
}

23
npm/ng-packs/packages/components/lookup/src/lib/lookup-search.component.ts

@ -5,15 +5,17 @@ import {
model,
signal,
OnInit,
OnDestroy,
ChangeDetectionStrategy,
TemplateRef,
contentChild,
DestroyRef,
inject,
} from '@angular/core';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { CommonModule } from '@angular/common';
import { FormsModule } from '@angular/forms';
import { LocalizationPipe } from '@abp/ng.core';
import { Subject, Observable, debounceTime, distinctUntilChanged, takeUntil, of } from 'rxjs';
import { Subject, Observable, debounceTime, distinctUntilChanged, of, finalize } from 'rxjs';
export interface LookupItem {
key: string;
@ -26,10 +28,12 @@ export type LookupSearchFn<T = LookupItem> = (filter: string) => Observable<T[]>
@Component({
selector: 'abp-lookup-search',
templateUrl: './lookup-search.component.html',
styleUrl: './lookup-search.component.scss',
imports: [CommonModule, FormsModule, LocalizationPipe],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class LookupSearchComponent<T extends LookupItem = LookupItem> implements OnInit, OnDestroy {
export class LookupSearchComponent<T extends LookupItem = LookupItem> implements OnInit {
private readonly destroyRef = inject(DestroyRef);
readonly label = input<string>();
readonly placeholder = input<string>('');
@ -55,23 +59,17 @@ export class LookupSearchComponent<T extends LookupItem = LookupItem> implements
readonly isLoading = signal(false);
private readonly searchSubject = new Subject<string>();
private readonly destroy$ = new Subject<void>();
ngOnInit() {
this.searchSubject.pipe(
debounceTime(this.debounceTime()),
distinctUntilChanged(),
takeUntil(this.destroy$)
takeUntilDestroyed(this.destroyRef)
).subscribe(filter => {
this.performSearch(filter);
});
}
ngOnDestroy() {
this.destroy$.next();
this.destroy$.complete();
}
onSearchInput(filter: string) {
this.displayValue.set(filter);
this.showDropdown.set(true);
@ -120,15 +118,14 @@ export class LookupSearchComponent<T extends LookupItem = LookupItem> implements
this.isLoading.set(true);
this.searchFn()(filter).pipe(
takeUntil(this.destroy$)
takeUntilDestroyed(this.destroyRef),
finalize(() => this.isLoading.set(false))
).subscribe({
next: results => {
this.searchResults.set(results);
this.isLoading.set(false);
},
error: () => {
this.searchResults.set([]);
this.isLoading.set(false);
}
});
}

10
npm/ng-packs/packages/permission-management/src/lib/components/resource-permission-management/permission-checkbox-list/permission-checkbox-list.component.html

@ -3,20 +3,20 @@
<h5>{{ title() | abpLocalization }}</h5>
}
<div class="form-check form-switch mb-2">
<input class="form-check-input" type="checkbox" [id]="'grantAll-' + idPrefix()"
<input class="form-check-input" type="checkbox" id="grantAll-{{ idPrefix() }}"
[checked]="state.allPermissionsSelected()"
(change)="state.toggleAllPermissions(!state.allPermissionsSelected())" />
<label class="form-check-label" [for]="'grantAll-' + idPrefix()">
<label class="form-check-label" for="grantAll-{{ idPrefix() }}">
{{ 'AbpPermissionManagement::GrantAllResourcePermissions' | abpLocalization }}
</label>
</div>
<div class="border rounded p-3" style="max-height: 300px; overflow-y: auto;">
<div class="abp-permission-list-container border rounded p-3">
@for (perm of permissions(); track perm.name) {
<div class="form-check">
<input class="form-check-input" type="checkbox" [id]="'perm-' + idPrefix() + '-' + perm.name"
<input class="form-check-input" type="checkbox" id="perm-{{ idPrefix() }}-{{ perm.name }}"
[checked]="state.isPermissionSelected(perm.name || '')"
(change)="state.togglePermission(perm.name || '')" />
<label class="form-check-label" [for]="'perm-' + idPrefix() + '-' + perm.name">
<label class="form-check-label" for="perm-{{ idPrefix() }}-{{ perm.name }}">
{{ perm.displayName }}
</label>
</div>

4
npm/ng-packs/packages/permission-management/src/lib/components/resource-permission-management/permission-checkbox-list/permission-checkbox-list.component.scss

@ -0,0 +1,4 @@
.abp-permission-list-container {
max-height: 300px;
overflow-y: auto;
}

1
npm/ng-packs/packages/permission-management/src/lib/components/resource-permission-management/permission-checkbox-list/permission-checkbox-list.component.ts

@ -10,6 +10,7 @@ interface PermissionItem {
@Component({
selector: 'abp-permission-checkbox-list',
templateUrl: './permission-checkbox-list.component.html',
styleUrl: './permission-checkbox-list.component.scss',
imports: [LocalizationPipe],
changeDetection: ChangeDetectionStrategy.OnPush,
})

17
npm/ng-packs/packages/permission-management/src/lib/components/resource-permission-management/provider-key-search/provider-key-search.component.ts

@ -1,7 +1,8 @@
import { Component, input, inject, OnInit, OnDestroy, ChangeDetectionStrategy } from '@angular/core';
import { Component, input, inject, OnInit, ChangeDetectionStrategy, DestroyRef } from '@angular/core';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { PermissionsService } from '@abp/ng.permission-management/proxy';
import { LookupSearchComponent, LookupItem } from '@abp/ng.components/lookup';
import { Observable, map, Subject, takeUntil } from 'rxjs';
import { Observable, map } from 'rxjs';
import { ResourcePermissionStateService } from '../../../services/resource-permission-state.service';
interface ProviderKeyLookupItem extends LookupItem {
@ -15,25 +16,19 @@ interface ProviderKeyLookupItem extends LookupItem {
imports: [LookupSearchComponent],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class ProviderKeySearchComponent implements OnInit, OnDestroy {
export class ProviderKeySearchComponent implements OnInit {
readonly state = inject(ResourcePermissionStateService);
private readonly service = inject(PermissionsService);
private readonly destroyRef = inject(DestroyRef);
readonly resourceName = input.required<string>();
private readonly destroy$ = new Subject<void>();
searchFn: (filter: string) => Observable<ProviderKeyLookupItem[]> = () => new Observable();
ngOnInit() {
this.searchFn = (filter: string) => this.loadProviderKeys(filter);
}
ngOnDestroy() {
this.destroy$.next();
this.destroy$.complete();
}
onItemSelected(item: ProviderKeyLookupItem) {
// State is already updated via displayValue and selectedValue bindings
// This handler can be used for additional side effects if needed
@ -60,7 +55,7 @@ export class ProviderKeySearchComponent implements OnInit, OnDestroy {
providerKey: k.providerKey || '',
providerDisplayName: k.providerDisplayName || undefined,
}))),
takeUntil(this.destroy$)
takeUntilDestroyed(this.destroyRef)
);
}
}

29
npm/ng-packs/packages/permission-management/src/lib/components/resource-permission-management/resource-permission-management.component.ts

@ -18,6 +18,8 @@ import {
model,
OnInit,
effect,
untracked,
signal,
} from '@angular/core';
import { finalize, switchMap, of } from 'rxjs';
import { ResourcePermissionStateService } from '../../services/resource-permission-state.service';
@ -26,6 +28,8 @@ import { ResourcePermissionFormComponent } from './resource-permission-form/reso
import { eResourcePermissionViewModes } from '../../enums/view-modes';
const DEFAULT_MAX_RESULT_COUNT = 10;
@Component({
selector: 'abp-resource-permission-management',
templateUrl: './resource-permission-management.component.html',
@ -55,33 +59,40 @@ export class ResourcePermissionManagementComponent implements OnInit {
readonly visible = model<boolean>(false);
private previousVisible = false;
private readonly previousVisible = signal(false);
constructor() {
effect(() => {
this.state.resourceName.set(this.resourceName());
this.state.resourceKey.set(this.resourceKey());
this.state.resourceDisplayName.set(this.resourceDisplayName());
const resourceName = this.resourceName();
const resourceKey = this.resourceKey();
const resourceDisplayName = this.resourceDisplayName();
untracked(() => {
this.state.resourceName.set(resourceName);
this.state.resourceKey.set(resourceKey);
this.state.resourceDisplayName.set(resourceDisplayName);
});
});
effect(() => {
const isVisible = this.visible();
if (isVisible && !this.previousVisible) {
const wasVisible = this.previousVisible();
if (isVisible && !wasVisible) {
this.openModal();
} else if (!isVisible && this.previousVisible) {
} else if (!isVisible && wasVisible) {
this.state.reset();
}
this.previousVisible = isVisible;
untracked(() => this.previousVisible.set(isVisible));
});
}
ngOnInit() {
this.list.maxResultCount = 10;
this.list.maxResultCount = DEFAULT_MAX_RESULT_COUNT;
this.list.hookToQuery(query => {
const allData = this.state.allResourcePermissions();
const skipCount = query.skipCount || 0;
const maxResultCount = query.maxResultCount || 10;
const maxResultCount = query.maxResultCount || DEFAULT_MAX_RESULT_COUNT;
const paginatedData = allData.slice(skipCount, skipCount + maxResultCount);

Loading…
Cancel
Save