diff --git a/npm/ng-packs/packages/permission-management/src/lib/components/index.ts b/npm/ng-packs/packages/permission-management/src/lib/components/index.ts index 2c1ce75527..074ddb880d 100644 --- a/npm/ng-packs/packages/permission-management/src/lib/components/index.ts +++ b/npm/ng-packs/packages/permission-management/src/lib/components/index.ts @@ -1,2 +1,6 @@ export * from './permission-management.component'; export * from './resource-permission-management/resource-permission-management.component'; +export * from './resource-permission-management/provider-key-search/provider-key-search.component'; +export * from './resource-permission-management/permission-checkbox-list/permission-checkbox-list.component'; +export * from './resource-permission-management/resource-permission-list/resource-permission-list.component'; +export * from './resource-permission-management/resource-permission-form/resource-permission-form.component'; diff --git a/npm/ng-packs/packages/permission-management/src/lib/components/resource-permission-management/permission-checkbox-list/permission-checkbox-list.component.ts b/npm/ng-packs/packages/permission-management/src/lib/components/resource-permission-management/permission-checkbox-list/permission-checkbox-list.component.ts new file mode 100644 index 0000000000..e59cc67e12 --- /dev/null +++ b/npm/ng-packs/packages/permission-management/src/lib/components/resource-permission-management/permission-checkbox-list/permission-checkbox-list.component.ts @@ -0,0 +1,57 @@ +import { Component, input, inject, ChangeDetectionStrategy } from '@angular/core'; +import { LocalizationPipe } from '@abp/ng.core'; +import { ResourcePermissionStateService } from '../../../services/resource-permission-state.service'; + +interface PermissionItem { + name?: string | null; + displayName?: string | null; +} + +@Component({ + selector: 'abp-permission-checkbox-list', + template: ` +
+ @if (showTitle()) { +
{{ title() | abpLocalization }}
+ } +
+ + +
+
+ @for (perm of permissions(); track perm.name) { +
+ + +
+ } +
+
+ `, + imports: [LocalizationPipe], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class PermissionCheckboxListComponent { + readonly state = inject(ResourcePermissionStateService); + + readonly permissions = input.required(); + readonly idPrefix = input('default'); + readonly title = input('AbpPermissionManagement::ResourcePermissionPermissions'); + readonly showTitle = input(true); +} diff --git a/npm/ng-packs/packages/permission-management/src/lib/components/resource-permission-management/provider-key-search/provider-key-search.component.ts b/npm/ng-packs/packages/permission-management/src/lib/components/resource-permission-management/provider-key-search/provider-key-search.component.ts new file mode 100644 index 0000000000..17a37e7456 --- /dev/null +++ b/npm/ng-packs/packages/permission-management/src/lib/components/resource-permission-management/provider-key-search/provider-key-search.component.ts @@ -0,0 +1,105 @@ +import { Component, input, inject, output, OnInit, OnDestroy, ChangeDetectionStrategy } from '@angular/core'; +import { FormsModule } from '@angular/forms'; +import { LocalizationPipe } from '@abp/ng.core'; +import { PermissionsService, SearchProviderKeyInfo } from '@abp/ng.permission-management/proxy'; +import { Subject, debounceTime, distinctUntilChanged, takeUntil } from 'rxjs'; +import { ResourcePermissionStateService } from '../../../services/resource-permission-state.service'; + +@Component({ + selector: 'abp-provider-key-search', + template: ` +
+ + + @if (state.searchResults().length > 0 && state.showDropdown()) { +
+ @for (result of state.searchResults(); track result.providerKey) { + + } +
+ } +
+ `, + imports: [FormsModule, LocalizationPipe], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class ProviderKeySearchComponent implements OnInit, OnDestroy { + readonly state = inject(ResourcePermissionStateService); + private readonly service = inject(PermissionsService); + + readonly resourceName = input.required(); + + readonly keySelected = output(); + + private readonly searchSubject = new Subject(); + private readonly destroy$ = new Subject(); + + ngOnInit() { + this.searchSubject.pipe( + debounceTime(300), + distinctUntilChanged(), + takeUntil(this.destroy$) + ).subscribe(filter => { + this.loadProviderKeys(filter); + }); + } + + ngOnDestroy() { + this.destroy$.next(); + this.destroy$.complete(); + } + + onSearchInput(filter: string) { + this.state.searchFilter.set(filter); + this.state.showDropdown.set(true); + this.searchSubject.next(filter); + } + + onSearchFocus() { + this.state.showDropdown.set(true); + this.loadProviderKeys(this.state.searchFilter() || ''); + } + + onSearchBlur(event: FocusEvent) { + const relatedTarget = event.relatedTarget as HTMLElement; + if (!relatedTarget?.closest('.list-group')) { + this.state.showDropdown.set(false); + } + } + + selectProviderKey(key: SearchProviderKeyInfo) { + this.state.selectProviderKey(key); + this.keySelected.emit(key); + } + + private loadProviderKeys(filter: string) { + const providerName = this.state.selectedProviderName(); + if (!providerName) return; + + this.service.searchResourceProviderKey( + this.resourceName(), + providerName, + filter, + 1 + ).subscribe(res => { + this.state.searchResults.set(res.keys || []); + }); + } +} diff --git a/npm/ng-packs/packages/permission-management/src/lib/components/resource-permission-management/resource-permission-form/resource-permission-form.component.ts b/npm/ng-packs/packages/permission-management/src/lib/components/resource-permission-management/resource-permission-form/resource-permission-form.component.ts new file mode 100644 index 0000000000..afb2ff14bb --- /dev/null +++ b/npm/ng-packs/packages/permission-management/src/lib/components/resource-permission-management/resource-permission-form/resource-permission-form.component.ts @@ -0,0 +1,70 @@ +import { Component, input, inject, output, ChangeDetectionStrategy } from '@angular/core'; +import { FormsModule } from '@angular/forms'; +import { LocalizationPipe } from '@abp/ng.core'; +import { ResourcePermissionStateService } from '../../../services/resource-permission-state.service'; +import { ProviderKeySearchComponent } from '../provider-key-search/provider-key-search.component'; +import { PermissionCheckboxListComponent } from '../permission-checkbox-list/permission-checkbox-list.component'; + +export type FormMode = 'add' | 'edit'; + +@Component({ + selector: 'abp-resource-permission-form', + template: ` + @if (mode() === 'add') { +
+ +
+ @for (provider of state.providers(); track provider.name; let i = $index) { +
+ + +
+ } +
+ + +
+ + + } @else { +
+

{{ 'AbpPermissionManagement::Permissions' | abpLocalization }}

+ +
+ } + `, + imports: [ + FormsModule, + LocalizationPipe, + ProviderKeySearchComponent, + PermissionCheckboxListComponent, + ], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class ResourcePermissionFormComponent { + readonly state = inject(ResourcePermissionStateService); + + readonly mode = input.required(); + readonly resourceName = input.required(); + + readonly save = output(); + readonly cancel = output(); +} diff --git a/npm/ng-packs/packages/permission-management/src/lib/components/resource-permission-management/resource-permission-list/resource-permission-list.component.ts b/npm/ng-packs/packages/permission-management/src/lib/components/resource-permission-management/resource-permission-list/resource-permission-list.component.ts new file mode 100644 index 0000000000..b422d9e991 --- /dev/null +++ b/npm/ng-packs/packages/permission-management/src/lib/components/resource-permission-management/resource-permission-list/resource-permission-list.component.ts @@ -0,0 +1,75 @@ +import { Component, inject, output, ChangeDetectionStrategy } from '@angular/core'; +import { ListService, LocalizationPipe } from '@abp/ng.core'; +import { ExtensibleTableComponent, EXTENSIONS_IDENTIFIER } from '@abp/ng.components/extensible'; +import { ResourcePermissionGrantInfoDto } from '@abp/ng.permission-management/proxy'; +import { ResourcePermissionStateService } from '../../../services/resource-permission-state.service'; +import { ePermissionManagementComponents } from '../../../enums/components'; +import { configureResourcePermissionExtensions } from '../../../services/extensions.service'; + +@Component({ + selector: 'abp-resource-permission-list', + template: ` +
+ +
+ + +
+ + +
+
+ + @if (state.resourcePermissions().length > 0) { + + } @else { +
+ {{ 'AbpPermissionManagement::NoPermissionsAssigned' | abpLocalization }} +
+ } + `, + providers: [ + ListService, + { + provide: EXTENSIONS_IDENTIFIER, + useValue: ePermissionManagementComponents.ResourcePermissions, + }, + ], + imports: [LocalizationPipe, ExtensibleTableComponent], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class ResourcePermissionListComponent { + readonly state = inject(ResourcePermissionStateService); + readonly list = inject(ListService); + + readonly addClicked = output(); + readonly editClicked = output(); + readonly deleteClicked = output(); + + constructor() { + configureResourcePermissionExtensions(); + } +} diff --git a/npm/ng-packs/packages/permission-management/src/lib/components/resource-permission-management/resource-permission-management.component.html b/npm/ng-packs/packages/permission-management/src/lib/components/resource-permission-management/resource-permission-management.component.html index b8cc1afd1e..7ffbf3b6b1 100644 --- a/npm/ng-packs/packages/permission-management/src/lib/components/resource-permission-management/resource-permission-management.component.html +++ b/npm/ng-packs/packages/permission-management/src/lib/components/resource-permission-management/resource-permission-management.component.html @@ -1,165 +1,60 @@ - + - @if (!hasResourcePermission() || !hasProviderKeyLookupService()) { + @if (!state.hasResourcePermission() || !state.hasProviderKeyLookupService()) { } @else { - - @if (viewMode() === 'list') { -
- -
- - -
- - -
-
- - @if (resourcePermissions().length > 0) { - - } @else { -
- {{ 'AbpPermissionManagement::NoPermissionsAssigned' | abpLocalization }} -
+ @switch (state.viewMode()) { + @case ('list') { + } + @case ('add') { + } - - - @if (viewMode() === 'add') { -
- -
- @for (provider of providers(); track provider.name; let i = $index) { -
- - -
- } -
- -
- - - @if (searchResults().length > 0 && showDropdown()) { -
- @for (result of searchResults(); track result.providerKey) { - - } -
- } -
-
- -
-
{{ 'AbpPermissionManagement::ResourcePermissionPermissions' | abpLocalization }}
-
- - -
-
- @for (perm of permissionDefinitions(); track perm.name) { -
- - -
- } -
-
+ @case ('edit') { + } - - - @if (viewMode() === 'edit') { -
-

{{ 'AbpPermissionManagement::Permissions' | abpLocalization }}

-
- - -
- @for (perm of permissionsWithProvider(); track perm.name) { -
- - -
- } -
} }
- @if (viewMode() === 'list') { + @if (state.isListMode()) { } @else { - - @if (viewMode() === 'add') { - + {{ 'AbpUi::Save' | abpLocalization }} - } @else { - - {{ 'AbpUi::Save' | abpLocalization }} - - } }
\ No newline at end of file diff --git a/npm/ng-packs/packages/permission-management/src/lib/components/resource-permission-management/resource-permission-management.component.ts b/npm/ng-packs/packages/permission-management/src/lib/components/resource-permission-management/resource-permission-management.component.ts index 3f0334ce5e..18b810fd51 100644 --- a/npm/ng-packs/packages/permission-management/src/lib/components/resource-permission-management/resource-permission-management.component.ts +++ b/npm/ng-packs/packages/permission-management/src/lib/components/resource-permission-management/resource-permission-management.component.ts @@ -10,116 +10,66 @@ import { import { PermissionsService, ResourcePermissionGrantInfoDto, - ResourceProviderDto, - SearchProviderKeyInfo, - ResourcePermissionDefinitionDto, - ResourcePermissionWithProdiverGrantInfoDto, } from '@abp/ng.permission-management/proxy'; -import { - ExtensibleTableComponent, - EXTENSIONS_IDENTIFIER, -} from '@abp/ng.components/extensible'; import { Component, - EventEmitter, inject, - Input, - Output, - signal, - computed, + input, + model, OnInit, + effect, } from '@angular/core'; -import { FormsModule } from '@angular/forms'; -import { finalize, switchMap, debounceTime, Subject, distinctUntilChanged, of } from 'rxjs'; -import { ePermissionManagementComponents } from '../../enums/components'; -import { configureResourcePermissionExtensions } from '../../services/extensions.service'; - -type ViewMode = 'list' | 'add' | 'edit'; +import { finalize, switchMap, of } from 'rxjs'; +import { ResourcePermissionStateService } from '../../services/resource-permission-state.service'; +import { ResourcePermissionListComponent } from './resource-permission-list/resource-permission-list.component'; +import { ResourcePermissionFormComponent } from './resource-permission-form/resource-permission-form.component'; @Component({ selector: 'abp-resource-permission-management', templateUrl: './resource-permission-management.component.html', exportAs: 'abpResourcePermissionManagement', - providers: [ - ListService, - { - provide: EXTENSIONS_IDENTIFIER, - useValue: ePermissionManagementComponents.ResourcePermissions, - }, - ], + providers: [ResourcePermissionStateService, ListService], imports: [ - FormsModule, ModalComponent, LocalizationPipe, ButtonComponent, ModalCloseDirective, - ExtensibleTableComponent, + ResourcePermissionListComponent, + ResourcePermissionFormComponent, ], }) export class ResourcePermissionManagementComponent implements OnInit { protected readonly service = inject(PermissionsService); protected readonly toasterService = inject(ToasterService); protected readonly confirmationService = inject(ConfirmationService); - readonly list = inject(ListService); - - @Input() resourceName!: string; - @Input() resourceKey!: string; - @Input() resourceDisplayName?: string; - - protected _visible = false; - - @Input() - get visible(): boolean { - return this._visible; - } - - set visible(value: boolean) { - if (value === this._visible) return; - - if (value) { - this.openModal(); - } else { - this.resetState(); - } - this._visible = value; - this.visibleChange.emit(value); - } - - @Output() readonly visibleChange = new EventEmitter(); + protected readonly state = inject(ResourcePermissionStateService); + private readonly list = inject(ListService); - viewMode = signal('list'); - modalBusy = signal(false); - hasResourcePermission = signal(false); - hasProviderKeyLookupService = signal(false); + readonly resourceName = input.required(); + readonly resourceKey = input.required(); + readonly resourceDisplayName = input(); - allResourcePermissions = signal([]); // All data for client-side pagination - resourcePermissions = signal([]); // Paginated data for table - totalCount = signal(0); - providers = signal([]); - permissionDefinitions = signal([]); - searchResults = signal([]); - permissionsWithProvider = signal([]); + readonly visible = model(false); - selectedProviderName = signal(''); - selectedProviderKey = signal(''); - searchFilter = signal(''); - selectedPermissions = signal([]); - - editProviderName = signal(''); - editProviderKey = signal(''); - - showDropdown = signal(false); - - private searchSubject = new Subject(); + private previousVisible = false; constructor() { - configureResourcePermissionExtensions(); + // Sync input values to state + effect(() => { + this.state.resourceName.set(this.resourceName()); + this.state.resourceKey.set(this.resourceKey()); + this.state.resourceDisplayName.set(this.resourceDisplayName()); + }); - this.searchSubject.pipe( - debounceTime(300), - distinctUntilChanged() - ).subscribe(filter => { - this.performSearch(filter); + // Handle visibility changes + effect(() => { + const isVisible = this.visible(); + if (isVisible && !this.previousVisible) { + this.openModal(); + } else if (!isVisible && this.previousVisible) { + this.state.reset(); + } + this.previousVisible = isVisible; }); } @@ -127,7 +77,7 @@ export class ResourcePermissionManagementComponent implements OnInit { this.list.maxResultCount = 10; this.list.hookToQuery(query => { - const allData = this.allResourcePermissions(); + const allData = this.state.allResourcePermissions(); const skipCount = query.skipCount || 0; const maxResultCount = query.maxResultCount || 10; @@ -138,34 +88,28 @@ export class ResourcePermissionManagementComponent implements OnInit { totalCount: allData.length }); }).subscribe(result => { - this.resourcePermissions.set(result.items); - this.totalCount.set(result.totalCount); + this.state.resourcePermissions.set(result.items); + this.state.totalCount.set(result.totalCount); }); } openModal() { - this.modalBusy.set(true); + this.state.modalBusy.set(true); - this.service.getResource(this.resourceName, this.resourceKey).pipe( + this.service.getResource(this.resourceName(), this.resourceKey()).pipe( switchMap(permRes => { - this.allResourcePermissions.set(permRes.permissions || []); - this.totalCount.set(permRes.permissions?.length || 0); + this.state.setResourceData(permRes.permissions || []); this.list.get(); - return this.service.getResourceProviderKeyLookupServices(this.resourceName); + return this.service.getResourceProviderKeyLookupServices(this.resourceName()); }), switchMap(providerRes => { - this.providers.set(providerRes.providers || []); - this.hasProviderKeyLookupService.set((providerRes.providers?.length || 0) > 0); - if (providerRes.providers?.length) { - this.selectedProviderName.set(providerRes.providers[0].name || ''); - } - return this.service.getResourceDefinitions(this.resourceName); + this.state.setProviders(providerRes.providers || []); + return this.service.getResourceDefinitions(this.resourceName()); }), - finalize(() => this.modalBusy.set(false)) + finalize(() => this.state.modalBusy.set(false)) ).subscribe({ next: defRes => { - this.permissionDefinitions.set(defRes.permissions || []); - this.hasResourcePermission.set((defRes.permissions?.length || 0) > 0); + this.state.setDefinitions(defRes.permissions || []); }, error: () => { this.toasterService.error('AbpPermissionManagement::ErrorLoadingPermissions'); @@ -173,190 +117,29 @@ export class ResourcePermissionManagementComponent implements OnInit { }); } - resetState() { - this.viewMode.set('list'); - this.allResourcePermissions.set([]); - this.resourcePermissions.set([]); - this.totalCount.set(0); - this.selectedProviderName.set(''); - this.selectedProviderKey.set(''); - this.searchFilter.set(''); - this.selectedPermissions.set([]); - this.searchResults.set([]); + onAddClicked() { + this.state.goToAddMode(); } - goToAddMode() { - this.viewMode.set('add'); - this.selectedPermissions.set([]); - this.selectedProviderKey.set(''); - this.searchResults.set([]); - } - - goToEditMode(grant: ResourcePermissionGrantInfoDto) { - this.editProviderName.set(grant.providerName || ''); - this.editProviderKey.set(grant.providerKey || ''); - this.modalBusy.set(true); + onEditClicked(grant: ResourcePermissionGrantInfoDto) { + this.state.prepareEditMode(grant); + this.state.modalBusy.set(true); this.service.getResourceByProvider( - this.resourceName, - this.resourceKey, + this.resourceName(), + this.resourceKey(), grant.providerName || '', grant.providerKey || '' ).pipe( - finalize(() => this.modalBusy.set(false)) - ).subscribe({ - next: res => { - this.permissionsWithProvider.set(res.permissions || []); - this.selectedPermissions.set( - (res.permissions || []).filter(p => p.isGranted).map(p => p.name || '') - ); - this.viewMode.set('edit'); - } - }); - } - - goToListMode() { - this.viewMode.set('list'); - this.selectedPermissions.set([]); - } - - onProviderChange(providerName: string) { - this.selectedProviderName.set(providerName); - this.selectedProviderKey.set(''); - this.searchResults.set([]); - this.searchFilter.set(''); - } - - onSearchInput(filter: string) { - this.searchFilter.set(filter); - this.showDropdown.set(true); - this.searchSubject.next(filter); - } - - onSearchFocus() { - this.showDropdown.set(true); - this.loadProviderKeys(this.searchFilter() || ''); - } - - onSearchBlur() { - setTimeout(() => { - this.showDropdown.set(false); - }, 200); - } - - private loadProviderKeys(filter: string) { - if (!this.selectedProviderName()) return; - - this.service.searchResourceProviderKey( - this.resourceName, - this.selectedProviderName(), - filter, - 1 - ).subscribe(res => { - this.searchResults.set(res.keys || []); - }); - } - - private performSearch(filter: string) { - this.loadProviderKeys(filter); - } - - selectProviderKey(key: SearchProviderKeyInfo) { - this.selectedProviderKey.set(key.providerKey || ''); - this.searchFilter.set(key.providerDisplayName || key.providerKey || ''); - this.searchResults.set([]); - this.showDropdown.set(false); - } - - togglePermission(permissionName: string) { - const current = this.selectedPermissions(); - if (current.includes(permissionName)) { - this.selectedPermissions.set(current.filter(p => p !== permissionName)); - } else { - this.selectedPermissions.set([...current, permissionName]); - } - } - - toggleAllPermissions(selectAll: boolean) { - if (this.viewMode() === 'add') { - this.selectedPermissions.set( - selectAll - ? this.permissionDefinitions().map(p => p.name || '') - : [] - ); - } else { - this.selectedPermissions.set( - selectAll - ? this.permissionsWithProvider().map(p => p.name || '') - : [] - ); - } - } - - isPermissionSelected(permissionName: string): boolean { - return this.selectedPermissions().includes(permissionName); - } - - allPermissionsSelected = computed(() => { - const definitions = this.viewMode() === 'add' - ? this.permissionDefinitions() - : this.permissionsWithProvider(); - return definitions.length > 0 && - definitions.every(p => this.selectedPermissions().includes(p.name || '')); - }); - - saveAddPermission() { - if (!this.selectedProviderKey() || this.selectedPermissions().length === 0) { - this.toasterService.warn('AbpPermissionManagement::PleaseSelectProviderAndPermissions'); - return; - } - - this.modalBusy.set(true); - this.service.updateResource( - this.resourceName, - this.resourceKey, - { - providerName: this.selectedProviderName(), - providerKey: this.selectedProviderKey(), - permissions: this.selectedPermissions() - } - ).pipe( - switchMap(() => this.service.getResource(this.resourceName, this.resourceKey)), - finalize(() => this.modalBusy.set(false)) - ).subscribe({ - next: res => { - this.allResourcePermissions.set(res.permissions || []); - this.list.get(); - this.toasterService.success('AbpUi::SavedSuccessfully'); - this.goToListMode(); - } - }); - } - - saveEditPermission() { - this.modalBusy.set(true); - this.service.updateResource( - this.resourceName, - this.resourceKey, - { - providerName: this.editProviderName(), - providerKey: this.editProviderKey(), - permissions: this.selectedPermissions() - } - ).pipe( - switchMap(() => this.service.getResource(this.resourceName, this.resourceKey)), - finalize(() => this.modalBusy.set(false)) + finalize(() => this.state.modalBusy.set(false)) ).subscribe({ next: res => { - this.allResourcePermissions.set(res.permissions || []); - this.list.get(); - this.toasterService.success('AbpUi::SavedSuccessfully'); - this.goToListMode(); + this.state.setEditModePermissions(res.permissions || []); } }); } - deletePermission(grant: ResourcePermissionGrantInfoDto) { + onDeleteClicked(grant: ResourcePermissionGrantInfoDto) { this.confirmationService .warn( 'AbpPermissionManagement::PermissionDeletionConfirmationMessage', @@ -367,18 +150,18 @@ export class ResourcePermissionManagementComponent implements OnInit { ) .subscribe((status: Confirmation.Status) => { if (status === Confirmation.Status.confirm) { - this.modalBusy.set(true); + this.state.modalBusy.set(true); this.service.deleteResource( - this.resourceName, - this.resourceKey, + this.resourceName(), + this.resourceKey(), grant.providerName || '', grant.providerKey || '' ).pipe( - switchMap(() => this.service.getResource(this.resourceName, this.resourceKey)), - finalize(() => this.modalBusy.set(false)) + switchMap(() => this.service.getResource(this.resourceName(), this.resourceKey())), + finalize(() => this.state.modalBusy.set(false)) ).subscribe({ next: res => { - this.allResourcePermissions.set(res.permissions || []); + this.state.setResourceData(res.permissions || []); this.list.get(); this.toasterService.success('AbpUi::SuccessfullyDeleted'); } @@ -386,4 +169,36 @@ export class ResourcePermissionManagementComponent implements OnInit { } }); } + + savePermission() { + const isEdit = this.state.isEditMode(); + const providerName = isEdit ? this.state.editProviderName() : this.state.selectedProviderName(); + const providerKey = isEdit ? this.state.editProviderKey() : this.state.selectedProviderKey(); + + if (!isEdit && !this.state.canSave()) { + this.toasterService.warn('AbpPermissionManagement::PleaseSelectProviderAndPermissions'); + return; + } + + this.state.modalBusy.set(true); + this.service.updateResource( + this.resourceName(), + this.resourceKey(), + { + providerName, + providerKey, + permissions: this.state.selectedPermissions() + } + ).pipe( + switchMap(() => this.service.getResource(this.resourceName(), this.resourceKey())), + finalize(() => this.state.modalBusy.set(false)) + ).subscribe({ + next: res => { + this.state.setResourceData(res.permissions || []); + this.list.get(); + this.toasterService.success('AbpUi::SavedSuccessfully'); + this.state.goToListMode(); + } + }); + } } diff --git a/npm/ng-packs/packages/permission-management/src/lib/services/index.ts b/npm/ng-packs/packages/permission-management/src/lib/services/index.ts index 29bda44023..f4dae3a571 100644 --- a/npm/ng-packs/packages/permission-management/src/lib/services/index.ts +++ b/npm/ng-packs/packages/permission-management/src/lib/services/index.ts @@ -1 +1,2 @@ export * from './extensions.service'; +export * from './resource-permission-state.service'; diff --git a/npm/ng-packs/packages/permission-management/src/lib/services/resource-permission-state.service.ts b/npm/ng-packs/packages/permission-management/src/lib/services/resource-permission-state.service.ts new file mode 100644 index 0000000000..8fa2daeda5 --- /dev/null +++ b/npm/ng-packs/packages/permission-management/src/lib/services/resource-permission-state.service.ts @@ -0,0 +1,165 @@ +import { Injectable, signal, computed } from '@angular/core'; +import { + ResourcePermissionGrantInfoDto, + ResourceProviderDto, + ResourcePermissionDefinitionDto, + SearchProviderKeyInfo, + ResourcePermissionWithProdiverGrantInfoDto, +} from '@abp/ng.permission-management/proxy'; + +export type ViewMode = 'list' | 'add' | 'edit'; + +@Injectable() +export class ResourcePermissionStateService { + // View state + readonly viewMode = signal('list'); + readonly modalBusy = signal(false); + readonly hasResourcePermission = signal(false); + readonly hasProviderKeyLookupService = signal(false); + + // Resource data + readonly resourceName = signal(''); + readonly resourceKey = signal(''); + readonly resourceDisplayName = signal(undefined); + + // Permissions data + readonly allResourcePermissions = signal([]); + readonly resourcePermissions = signal([]); + readonly totalCount = signal(0); + readonly permissionDefinitions = signal([]); + readonly permissionsWithProvider = signal([]); + readonly selectedPermissions = signal([]); + + // Provider data + readonly providers = signal([]); + readonly selectedProviderName = signal(''); + readonly selectedProviderKey = signal(''); + + // Edit mode specific + readonly editProviderName = signal(''); + readonly editProviderKey = signal(''); + + // Search state + readonly searchFilter = signal(''); + readonly searchResults = signal([]); + readonly showDropdown = signal(false); + + // Computed properties + readonly isAddMode = computed(() => this.viewMode() === 'add'); + readonly isEditMode = computed(() => this.viewMode() === 'edit'); + readonly isListMode = computed(() => this.viewMode() === 'list'); + + readonly currentPermissionsList = computed(() => + this.isAddMode() ? this.permissionDefinitions() : this.permissionsWithProvider() + ); + + readonly allPermissionsSelected = computed(() => { + const definitions = this.currentPermissionsList(); + return definitions.length > 0 && + definitions.every(p => this.selectedPermissions().includes(p.name || '')); + }); + + readonly canSave = computed(() => { + if (this.isAddMode()) { + return !!this.selectedProviderKey() && this.selectedPermissions().length > 0; + } + return this.selectedPermissions().length >= 0; + }); + + // State transition methods + goToListMode() { + this.viewMode.set('list'); + this.selectedPermissions.set([]); + } + + goToAddMode() { + this.viewMode.set('add'); + this.selectedPermissions.set([]); + this.selectedProviderKey.set(''); + this.searchResults.set([]); + this.searchFilter.set(''); + } + + prepareEditMode(grant: ResourcePermissionGrantInfoDto) { + this.editProviderName.set(grant.providerName || ''); + this.editProviderKey.set(grant.providerKey || ''); + } + + setEditModePermissions(permissions: ResourcePermissionWithProdiverGrantInfoDto[]) { + this.permissionsWithProvider.set(permissions); + this.selectedPermissions.set( + permissions.filter(p => p.isGranted).map(p => p.name || '') + ); + this.viewMode.set('edit'); + } + + // Permission selection methods + togglePermission(permissionName: string) { + const current = this.selectedPermissions(); + if (current.includes(permissionName)) { + this.selectedPermissions.set(current.filter(p => p !== permissionName)); + } else { + this.selectedPermissions.set([...current, permissionName]); + } + } + + toggleAllPermissions(selectAll: boolean) { + const permissions = this.currentPermissionsList(); + this.selectedPermissions.set( + selectAll ? permissions.map(p => p.name || '') : [] + ); + } + + isPermissionSelected(permissionName: string): boolean { + return this.selectedPermissions().includes(permissionName); + } + + // Provider search methods + onProviderChange(providerName: string) { + this.selectedProviderName.set(providerName); + this.selectedProviderKey.set(''); + this.searchResults.set([]); + this.searchFilter.set(''); + } + + selectProviderKey(key: SearchProviderKeyInfo) { + this.selectedProviderKey.set(key.providerKey || ''); + this.searchFilter.set(key.providerDisplayName || key.providerKey || ''); + this.searchResults.set([]); + this.showDropdown.set(false); + } + + // Reset all state + reset() { + this.viewMode.set('list'); + this.allResourcePermissions.set([]); + this.resourcePermissions.set([]); + this.totalCount.set(0); + this.selectedProviderName.set(''); + this.selectedProviderKey.set(''); + this.searchFilter.set(''); + this.selectedPermissions.set([]); + this.searchResults.set([]); + this.editProviderName.set(''); + this.editProviderKey.set(''); + } + + // Data loading helpers + setResourceData(permissions: ResourcePermissionGrantInfoDto[]) { + this.allResourcePermissions.set(permissions); + this.totalCount.set(permissions.length); + } + + setProviders(providers: ResourceProviderDto[]) { + this.providers.set(providers); + this.hasProviderKeyLookupService.set(providers.length > 0); + if (providers.length) { + this.selectedProviderName.set(providers[0].name || ''); + } + } + + setDefinitions(permissions: ResourcePermissionDefinitionDto[]) { + this.permissionDefinitions.set(permissions); + this.hasResourcePermission.set(permissions.length > 0); + } +}