Browse Source

update: feature-management package for the latest upgrade (state management and html properties)

pull/25690/head
sumeyye 2 months ago
parent
commit
2e949642a4
  1. 2
      npm/ng-packs/packages/feature-management/project.json
  2. 224
      npm/ng-packs/packages/feature-management/src/lib/components/feature-management/feature-management.component.html
  3. 30
      npm/ng-packs/packages/feature-management/src/lib/components/feature-management/feature-management.component.ts
  4. 10
      npm/ng-packs/packages/feature-management/src/lib/directives/free-text-input.directive.ts

2
npm/ng-packs/packages/feature-management/project.json

@ -31,7 +31,7 @@
"executor": "@nx/vitest:test",
"outputs": ["{options.reportsDirectory}"],
"options": {
"reportsDirectory": "../../coverage/packages/feature-management"
"reportsDirectory": "{projectRoot}/../../coverage/packages/feature-management"
}
}
}

224
npm/ng-packs/packages/feature-management/src/lib/components/feature-management/feature-management.component.html

@ -1,22 +1,35 @@
@if (visible) {
<abp-modal [(visible)]="visible" [busy]="modalBusy" [options]="{ size: 'lg' }">
<ng-template #abpHeader>
<h3>
{{ 'AbpFeatureManagement::Features' | abpLocalization }}
@if (providerTitle()) {
- {{ providerTitle() }}
}
</h3>
</ng-template>
<abp-modal [(visible)]="visible" [busy]="modalBusy" [options]="{ size: 'lg' }">
<ng-template #abpHeader>
<h3>
{{ 'AbpFeatureManagement::Features' | abpLocalization }}
@if (providerTitle()) {
- {{ providerTitle() }}
}
</h3>
</ng-template>
<ng-template #abpBody>
<div class="row">
@if (groups.length) {
<div ngTabs orientation="vertical" class="row">
<div class="col-md-4">
<div ngTabList orientation="vertical" selectionMode="follow" [(selectedTab)]="selectedGroupDisplayName" class="nav nav-pills flex-column">
<div
ngTabList
orientation="vertical"
selectionMode="follow"
[(selectedTab)]="selectedGroupDisplayName"
class="nav nav-pills flex-column"
>
@for (group of groups; track group.name) {
<button ngTab #tab="ngTab" type="button" [value]="group.displayName" class="nav-link text-start" [class.active]="tab.selected()">
<button
ngTab
#tab="ngTab"
type="button"
[value]="group.displayName"
class="nav-link text-start"
[class.active]="tab.selected()"
>
{{ group.displayName }}
</button>
}
@ -35,76 +48,100 @@
<h4>{{ selectedGroupDisplayName }}</h4>
<hr class="mt-2 mb-3" />
@for (feature of features[group.name]; track feature.id || i; let i = $index) {
@let provider = feature.provider.name;
@let isFeatureDisabled = !feature.parentName ? isParentDisabled(feature.name, group.name, provider) :
(provider !== providerName() && provider !== defaultProviderName);
@for (
feature of features[group.name];
track feature.name ?? i;
let i = $index
) {
@let provider = feature.provider.name;
@let isFeatureDisabled =
!feature.parentName
? isParentDisabled(feature.name, group.name, provider)
: provider !== providerName() && provider !== defaultProviderName;
<div class="mt-2" [style]="feature.style" (keyup.enter)="save()">
@switch (feature.valueType?.name) {
@case (valueTypes.ToggleStringValueType) {
<div class="form-check" [class.px-4]="!!feature.parentName">
<input class="form-check-input" type="checkbox" [id]="feature.name" [(ngModel)]="feature.value"
(ngModelChange)="onCheckboxClick($event, feature)" [disabled]="isFeatureDisabled" />
<div class="mt-2" [style]="feature.style" (keyup.enter)="save()">
@switch (feature.valueType?.name) {
@case (valueTypes.ToggleStringValueType) {
<div class="form-check" [class.px-4]="!!feature.parentName">
<input
class="form-check-input"
type="checkbox"
[id]="feature.name"
[(ngModel)]="feature.value"
(ngModelChange)="onCheckboxClick($event, feature)"
[disabled]="isFeatureDisabled"
/>
<label class="form-check-label" [htmlFor]="feature.name">
{{ feature.displayName }}
@if (isFeatureDisabled) {
<span>({{ provider }})</span>
}
</label>
<ng-container *ngTemplateOutlet="
<label class="form-check-label" [htmlFor]="feature.name">
{{ feature.displayName }}
@if (isFeatureDisabled) {
<span>({{ provider }})</span>
}
</label>
<ng-container
*ngTemplateOutlet="
descTmp;
context: { $implicit: feature.description }
"></ng-container>
</div>
}
@case (valueTypes.FreeTextStringValueType) {
<div class="mb-3 form-group" [class.px-2]="!!feature.parentName">
<label [htmlFor]="feature.name" class="form-label">
{{ feature.displayName }}
@if (isFeatureDisabled) {
<span>({{ provider }})</span>
}
</label>
<input class="form-control" type="text" [id]="feature.name" [(ngModel)]="feature.value"
[abpFeatureManagementFreeText]="feature" [disabled]="isFeatureDisabled" />
"
/>
</div>
}
@case (valueTypes.FreeTextStringValueType) {
<div class="mb-3 form-group" [class.px-2]="!!feature.parentName">
<label [htmlFor]="feature.name" class="form-label">
{{ feature.displayName }}
@if (isFeatureDisabled) {
<span>({{ provider }})</span>
}
</label>
<input
class="form-control"
type="text"
[id]="feature.name"
[(ngModel)]="feature.value"
[abpFeatureManagementFreeText]="feature"
[disabled]="isFeatureDisabled"
/>
<ng-container *ngTemplateOutlet="
<ng-container
*ngTemplateOutlet="
descTmp;
context: { $implicit: feature.description }
"></ng-container>
</div>
}
@case (valueTypes.SelectionStringValueType) {
@if (feature.valueType.itemSource?.items?.length) {
<div class="mb-3 form-group" [class.px-2]="!!feature.parentName">
<label [htmlFor]="feature.name" class="form-label">
{{ feature.displayName }}
@if (isFeatureDisabled) {
<span>({{ provider }})</span>
}
</label>
<select class="form-select" [id]="feature.name" [(ngModel)]="feature.value"
[disabled]="isFeatureDisabled">
@for (
item of feature.valueType.itemSource?.items;
track item.value
) {
<option [ngValue]="item.value">
{{
item.displayText?.resourceName +
'::' +
item.displayText?.name | abpLocalization
}}
</option>
}
</select>
<ng-container *ngTemplateOutlet="
"
/>
</div>
}
@case (valueTypes.SelectionStringValueType) {
@if (getSelectionItems(feature).length) {
<div class="mb-3 form-group" [class.px-2]="!!feature.parentName">
<label [htmlFor]="feature.name" class="form-label">
{{ feature.displayName }}
@if (isFeatureDisabled) {
<span>({{ provider }})</span>
}
</label>
<select
class="form-select"
[id]="feature.name"
[(ngModel)]="feature.value"
[disabled]="isFeatureDisabled"
>
@for (item of getSelectionItems(feature); track item.value) {
<option [ngValue]="item.value">
{{
item.displayText?.resourceName +
'::' +
item.displayText?.name | abpLocalization
}}
</option>
}
</select>
<ng-container
*ngTemplateOutlet="
descTmp;
context: { $implicit: feature.description }
"
></ng-container>
/>
</div>
}
}
@ -129,23 +166,32 @@
</div>
</ng-template>
<ng-template #abpFooter>
<button abpClose type="button" class="btn btn-link">
{{ 'AbpFeatureManagement::Cancel' | abpLocalization }}
</button>
<ng-template #abpFooter>
<button abpClose type="button" class="btn btn-link">
{{ 'AbpFeatureManagement::Cancel' | abpLocalization }}
</button>
@if (groups.length) {
<abp-button buttonClass="btn btn-outline-primary" [disabled]="modalBusy" (click)="resetToDefault()"
aria-hidden="true">
{{ 'AbpFeatureManagement::ResetToDefault' | abpLocalization }}
</abp-button>
}
@if (groups.length) {
<abp-button
buttonClass="btn btn-outline-primary"
[disabled]="modalBusy"
(click)="resetToDefault()"
aria-hidden="true"
>
{{ 'AbpFeatureManagement::ResetToDefault' | abpLocalization }}
</abp-button>
}
@if (groups.length) {
<abp-button iconClass="fa fa-check" [disabled]="modalBusy" (click)="save()" aria-hidden="true">
{{ 'AbpFeatureManagement::Save' | abpLocalization }}
</abp-button>
}
</ng-template>
</abp-modal>
}
@if (groups.length) {
<abp-button
iconClass="fa fa-check"
[disabled]="modalBusy"
(click)="save()"
aria-hidden="true"
>
{{ 'AbpFeatureManagement::Save' | abpLocalization }}
</abp-button>
}
</ng-template>
</abp-modal>
}

30
npm/ng-packs/packages/feature-management/src/lib/components/feature-management/feature-management.component.ts

@ -29,6 +29,26 @@ enum ValueTypes {
const DEFAULT_PROVIDER_NAME = 'D';
/**
* FeatureDto.ValueType is typed as IStringValueType in Application.Contracts, but the API
* serializes concrete value types at runtime (e.g. SelectionStringValueType includes itemSource).
* generate-proxy only reflects the interface contract, so keep runtime-only shapes here not in proxy/.
* See: modules/feature-management (FeatureDto, StringValueTypeJsonConverter) and Blazor/MVC cast pattern.
*/
type FeatureWithStyle = FeatureDto & {
style?: Record<string, number>;
initialValue: unknown;
};
type SelectionStringValueType = FeatureDto['valueType'] & {
itemSource?: {
items?: Array<{
value?: string;
displayText?: { resourceName?: string; name?: string };
}>;
};
};
@Component({
selector: 'abp-feature-management',
templateUrl: './feature-management.component.html',
@ -73,7 +93,7 @@ export class FeatureManagementComponent {
groups: Pick<FeatureGroupDto, 'name' | 'displayName'>[] = [];
features: {
[group: string]: Array<FeatureDto & { style?: { [key: string]: number }; initialValue: any }>;
[group: string]: FeatureWithStyle[];
};
valueTypes = ValueTypes;
@ -194,6 +214,14 @@ export class FeatureManagementComponent {
}
}
getSelectionItems(feature: FeatureWithStyle) {
if (feature.valueType?.name !== ValueTypes.SelectionStringValueType) {
return [];
}
return (feature.valueType as SelectionStringValueType).itemSource?.items ?? [];
}
isParentDisabled(parentName: string, groupName: string, provider: string): boolean {
const children = this.features[groupName]?.filter(f => f.parentName === parentName);
const providerNameValue = this.providerName();

10
npm/ng-packs/packages/feature-management/src/lib/directives/free-text-input.directive.ts

@ -1,13 +1,7 @@
import { Directive, effect, inject, input, ElementRef, Renderer2 } from '@angular/core';
import type { FeatureDto } from '@abp/ng.feature-management/proxy';
// TODO: improve this type
export interface FreeTextType {
valueType: {
validator: {
name: string;
};
};
}
export type FreeTextType = Pick<FeatureDto, 'valueType'>;
export const INPUT_TYPES: Record<string, string> = {
numeric: 'number',

Loading…
Cancel
Save