Feature custom icon in Custom icon init implementation.

This commit is contained in:
deaflynx 2025-09-05 12:51:31 +03:00
parent 04005f203e
commit a3c28e9db4
6 changed files with 127 additions and 62 deletions

View File

@ -14,8 +14,9 @@
/// limitations under the License. /// limitations under the License.
/// ///
import { ChangeDetectionStrategy, Component, Input, OnInit } from '@angular/core'; import { ChangeDetectionStrategy, Component, Input, OnInit, OnChanges, SimpleChanges } from '@angular/core';
import { MenuSection } from '@core/services/menu.models'; import { MenuSection } from '@core/services/menu.models';
import { tbImageIcon } from '@shared/models/custom-menu.models';
@Component({ @Component({
selector: 'tb-menu-link', selector: 'tb-menu-link',
@ -23,14 +24,27 @@ import { MenuSection } from '@core/services/menu.models';
styleUrls: ['./menu-link.component.scss'], styleUrls: ['./menu-link.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush changeDetection: ChangeDetectionStrategy.OnPush
}) })
export class MenuLinkComponent implements OnInit { export class MenuLinkComponent implements OnInit, OnChanges {
@Input() section: MenuSection; @Input() section: MenuSection;
isCustomIcon: boolean;
constructor() { constructor() {
} }
ngOnInit() { ngOnInit() {
} }
ngOnChanges(changes: SimpleChanges): void {
for (const propName of Object.keys(changes)) {
const change = changes[propName];
if (change.currentValue !== change.previousValue) {
if (propName === 'section' && change.currentValue) {
this.isCustomIcon = tbImageIcon(change.currentValue.icon);
}
}
}
}
} }

View File

@ -37,6 +37,10 @@
[disabled]="disabled" [disabled]="disabled"
#matButton #matButton
(click)="openIconPopup($event, matButton)"> (click)="openIconPopup($event, matButton)">
<tb-icon matButtonIcon>{{materialIconFormGroup.get('icon').value}}</tb-icon> @if (!isCustomIcon) {
<tb-icon matButtonIcon>{{materialIconFormGroup.get('icon').value}}</tb-icon>
} @else {
<img class="mat-icon" alt="icon" [src]="materialIconFormGroup.get('icon').value | image | async">
}
</button> </button>
</ng-template> </ng-template>

View File

@ -36,6 +36,7 @@ import { TbPopoverService } from '@shared/components/popover.service';
import { MaterialIconsComponent } from '@shared/components/material-icons.component'; import { MaterialIconsComponent } from '@shared/components/material-icons.component';
import { MatButton } from '@angular/material/button'; import { MatButton } from '@angular/material/button';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { tbImageIcon } from '@shared/models/custom-menu.models';
@Component({ @Component({
selector: 'tb-material-icon-select', selector: 'tb-material-icon-select',
@ -71,6 +72,12 @@ export class MaterialIconSelectComponent extends PageComponent implements OnInit
@coerceBoolean() @coerceBoolean()
iconClearButton = false; iconClearButton = false;
@Input()
@coerceBoolean()
allowedCustomIcon = false;
isCustomIcon = false;
private requiredValue: boolean; private requiredValue: boolean;
get required(): boolean { get required(): boolean {
return this.requiredValue; return this.requiredValue;
@ -131,11 +138,13 @@ export class MaterialIconSelectComponent extends PageComponent implements OnInit
this.materialIconFormGroup.patchValue( this.materialIconFormGroup.patchValue(
{ icon: this.modelValue }, {emitEvent: false} { icon: this.modelValue }, {emitEvent: false}
); );
this.defineIconType(value);
} }
private updateModel() { private updateModel() {
const icon: string = this.materialIconFormGroup.get('icon').value; const icon: string = this.materialIconFormGroup.get('icon').value;
if (this.modelValue !== icon) { if (this.modelValue !== icon) {
this.defineIconType(icon);
this.modelValue = icon; this.modelValue = icon;
this.propagateChange(this.modelValue); this.propagateChange(this.modelValue);
} }
@ -169,7 +178,8 @@ export class MaterialIconSelectComponent extends PageComponent implements OnInit
this.viewContainerRef, MaterialIconsComponent, 'left', true, null, this.viewContainerRef, MaterialIconsComponent, 'left', true, null,
{ {
selectedIcon: this.materialIconFormGroup.get('icon').value, selectedIcon: this.materialIconFormGroup.get('icon').value,
iconClearButton: this.iconClearButton iconClearButton: this.iconClearButton,
allowedCustomIcon: this.allowedCustomIcon,
}, },
{}, {},
{}, {}, true); {}, {}, true);
@ -188,4 +198,10 @@ export class MaterialIconSelectComponent extends PageComponent implements OnInit
this.materialIconFormGroup.get('icon').patchValue(null, {emitEvent: true}); this.materialIconFormGroup.get('icon').patchValue(null, {emitEvent: true});
this.cd.markForCheck(); this.cd.markForCheck();
} }
private defineIconType(icon: string) {
if (this.allowedCustomIcon) {
this.isCustomIcon = tbImageIcon(icon);
}
}
} }

View File

@ -17,62 +17,84 @@
--> -->
<div class="tb-material-icons-panel"> <div class="tb-material-icons-panel">
<div *ngIf="showTitle" class="tb-material-icons-title" translate>icon.icons</div> <div *ngIf="showTitle" class="tb-material-icons-title" translate>icon.icons</div>
<mat-form-field class="tb-material-icons-search tb-inline-field" appearance="outline" subscriptSizing="dynamic"> @if (allowedCustomIcon) {
<mat-icon matPrefix>search</mat-icon> <div class="flex w-full flex-row items-center justify-end">
<input matInput [formControl]="searchIconControl" placeholder="{{ 'icon.search-icon' | translate }}"/> <tb-toggle-select [(ngModel)]="isCustomIcon" [ngModelOptions]="{standalone: true}" (ngModelChange)="selectedIcon = null">
<button *ngIf="searchIconControl.value" <tb-toggle-option [value]="false">{{ 'resource.system' | translate }}</tb-toggle-option>
type="button" <tb-toggle-option [value]="true">{{ 'icon.custom' | translate }}</tb-toggle-option>
matSuffix mat-icon-button aria-label="Clear" </tb-toggle-select>
(click)="clearSearch()">
<mat-icon class="material-icons">close</mat-icon>
</button>
</mat-form-field>
<cdk-virtual-scroll-viewport [class.!hidden]="notFound" #iconsPanel
[itemSize]="iconsRowHeight" class="tb-material-icons-viewport"
[style.width]="iconsPanelWidth"
[style.height]="iconsPanelHeight">
<div *cdkVirtualFor="let iconRow of iconRows$ | async" class="tb-material-icons-row">
<ng-container *ngFor="let icon of iconRow">
<button *ngIf="icon.name === selectedIcon"
class="tb-select-icon-button"
mat-raised-button
color="primary"
(click)="selectIcon(icon)"
matTooltip="{{ icon.displayName }}"
matTooltipPosition="above"
type="button">
<tb-icon matButtonIcon>{{icon.name}}</tb-icon>
</button>
<button *ngIf="icon.name !== selectedIcon"
class="tb-select-icon-button"
mat-button
(click)="selectIcon(icon)"
matTooltip="{{ icon.displayName }}"
matTooltipPosition="above"
type="button">
<tb-icon matButtonIcon>{{icon.name}}</tb-icon>
</button>
</ng-container>
</div> </div>
</cdk-virtual-scroll-viewport> }
<ng-container *ngIf="notFound"> @if (!isCustomIcon) {
<div class="tb-no-data-available" [style.width]="iconsPanelWidth"> <mat-form-field class="tb-material-icons-search tb-inline-field" appearance="outline" subscriptSizing="dynamic">
<div class="tb-no-data-bg"></div> <mat-icon matPrefix>search</mat-icon>
<div class="tb-no-data-text">{{ 'icon.no-icons-found' | translate:{iconSearch: searchIconControl.value} }}</div> <input matInput [formControl]="searchIconControl" placeholder="{{ 'icon.search-icon' | translate }}"/>
<button *ngIf="searchIconControl.value"
type="button"
matSuffix mat-icon-button aria-label="Clear"
(click)="clearSearch()">
<mat-icon class="material-icons">close</mat-icon>
</button>
</mat-form-field>
<cdk-virtual-scroll-viewport [class.!hidden]="notFound" #iconsPanel
[itemSize]="iconsRowHeight" class="tb-material-icons-viewport"
[style.width]="iconsPanelWidth"
[style.height]="iconsPanelHeight">
<div *cdkVirtualFor="let iconRow of iconRows$ | async" class="tb-material-icons-row">
<ng-container *ngFor="let icon of iconRow">
<button *ngIf="icon.name === selectedIcon"
class="tb-select-icon-button"
mat-raised-button
color="primary"
(click)="selectIcon(icon.name)"
matTooltip="{{ icon.displayName }}"
matTooltipPosition="above"
type="button">
<tb-icon matButtonIcon>{{icon.name}}</tb-icon>
</button>
<button *ngIf="icon.name !== selectedIcon"
class="tb-select-icon-button"
mat-button
(click)="selectIcon(icon.name)"
matTooltip="{{ icon.displayName }}"
matTooltipPosition="above"
type="button">
<tb-icon matButtonIcon>{{icon.name}}</tb-icon>
</button>
</ng-container>
</div>
</cdk-virtual-scroll-viewport>
<ng-container *ngIf="notFound">
<div class="tb-no-data-available" [style.width]="iconsPanelWidth">
<div class="tb-no-data-bg"></div>
<div class="tb-no-data-text">{{ 'icon.no-icons-found' | translate:{iconSearch: searchIconControl.value} }}</div>
</div>
</ng-container>
<div class="tb-material-icons-panel-buttons" *ngIf="iconClearButton || !showAllSubject.value">
<button *ngIf="iconClearButton"
mat-button
color="primary"
type="button"
(click)="clearIcon()"
[disabled]="!selectedIcon">
{{ 'action.clear' | translate }}
</button>
<span class="flex-1"></span>
<button *ngIf="!showAllSubject.value" class="tb-material-icons-show-more" mat-button color="primary" (click)="showAllSubject.next(true)">
{{ 'action.show-more' | translate }}
</button>
</div> </div>
</ng-container> } @else {
<div class="tb-material-icons-panel-buttons" *ngIf="iconClearButton || !showAllSubject.value"> <tb-gallery-image-input class="min-w-[520px]"
<button *ngIf="iconClearButton" [(ngModel)]="selectedIcon">
mat-button </tb-gallery-image-input>
color="primary" <div class="tb-material-icons-panel-buttons">
type="button" <span class="flex-1"></span>
(click)="clearIcon()" <button class="tb-material-icons-show-more" mat-button color="primary"
[disabled]="!selectedIcon"> [disabled]="!selectedIcon"
{{ 'action.clear' | translate }} (click)="selectIcon(selectedIcon)">
</button> {{ 'action.set' | translate }}
<span class="flex-1"></span> </button>
<button *ngIf="!showAllSubject.value" class="tb-material-icons-show-more" mat-button color="primary" (click)="showAllSubject.next(true)"> </div>
{{ 'action.show-more' | translate }} }
</button>
</div>
</div> </div>

View File

@ -37,6 +37,7 @@ import { TbPopoverComponent } from '@shared/components/popover.component';
import { BreakpointObserver } from '@angular/cdk/layout'; import { BreakpointObserver } from '@angular/cdk/layout';
import { MediaBreakpoints } from '@shared/models/constants'; import { MediaBreakpoints } from '@shared/models/constants';
import { coerceBoolean } from '@shared/decorators/coercion'; import { coerceBoolean } from '@shared/decorators/coercion';
import { tbImageIcon } from '@shared/models/custom-menu.models';
@Component({ @Component({
selector: 'tb-material-icons', selector: 'tb-material-icons',
@ -61,6 +62,10 @@ export class MaterialIconsComponent extends PageComponent implements OnInit {
@coerceBoolean() @coerceBoolean()
showTitle = true; showTitle = true;
@Input()
@coerceBoolean()
allowedCustomIcon = false;
@Input() @Input()
popover: TbPopoverComponent; popover: TbPopoverComponent;
@ -71,6 +76,8 @@ export class MaterialIconsComponent extends PageComponent implements OnInit {
showAllSubject = new BehaviorSubject<boolean>(false); showAllSubject = new BehaviorSubject<boolean>(false);
searchIconControl: UntypedFormControl; searchIconControl: UntypedFormControl;
isCustomIcon = false;
iconsRowHeight = 48; iconsRowHeight = 48;
iconsPanelHeight: string; iconsPanelHeight: string;
@ -122,14 +129,15 @@ export class MaterialIconsComponent extends PageComponent implements OnInit {
map((data) => data.iconRows), map((data) => data.iconRows),
share() share()
); );
this.isCustomIcon = tbImageIcon(this.selectedIcon)
} }
clearSearch() { clearSearch() {
this.searchIconControl.patchValue('', {emitEvent: true}); this.searchIconControl.patchValue('', {emitEvent: true});
} }
selectIcon(icon: MaterialIcon) { selectIcon(icon: string) {
this.iconSelected.emit(icon.name); this.iconSelected.emit(icon);
} }
clearIcon() { clearIcon() {

View File

@ -9507,6 +9507,7 @@
"icon": { "icon": {
"icon": "Icon", "icon": "Icon",
"icons": "Icons", "icons": "Icons",
"custom": "Custom",
"select-icon": "Select icon", "select-icon": "Select icon",
"material-icons": "Material icons", "material-icons": "Material icons",
"show-all": "Show all icons", "show-all": "Show all icons",