diff --git a/packages/context/src/lib/context-manager/context-list/context-list.component.html b/packages/context/src/lib/context-manager/context-list/context-list.component.html index 1f82f7606..39b51b309 100644 --- a/packages/context/src/lib/context-manager/context-list/context-list.component.html +++ b/packages/context/src/lib/context-manager/context-list/context-list.component.html @@ -178,10 +178,12 @@ - - + ngFor + let-context + [ngForOf]="$any(groupContexts).value" + > = new BehaviorSubject( - this.contextsInitial - ); - - change$ = new ReplaySubject(1); - - private change$$: Subscription; - - @Input() - get contexts(): ContextsList { - return this._contexts; - } - set contexts(value: ContextsList) { - this._contexts = value; - this.next(); - } - private _contexts: ContextsList = { ours: [] }; - - @Input() - get selectedContext(): DetailedContext { - return this._selectedContext; - } - set selectedContext(value: DetailedContext) { - this._selectedContext = value; - this.cdRef.detectChanges(); - } - private _selectedContext: DetailedContext; - - @Input() - get map(): IgoMap { - return this._map; - } - set map(value: IgoMap) { - this._map = value; - } - private _map: IgoMap; - - @Input() - get defaultContextId(): string { - return this.contextConfigs - ? this._defaultContextId - : (this.storageService.get('favorite.context.uri') as string) || - this._defaultContextId; - } - set defaultContextId(value: string) { - this._defaultContextId = value; - } - private _defaultContextId: string; - - public collapsed: { contextScope }[] = []; - - @Output() select = new EventEmitter(); - @Output() unselect = new EventEmitter(); - @Output() edit = new EventEmitter(); - @Output() delete = new EventEmitter(); - @Output() save = new EventEmitter(); - @Output() clone = new EventEmitter(); - @Output() create = new EventEmitter<{ title: string; empty: boolean }>(); - @Output() hide = new EventEmitter(); - @Output() show = new EventEmitter(); - @Output() showHiddenContexts = new EventEmitter(); - @Output() favorite = new EventEmitter(); - @Output() managePermissions = new EventEmitter(); - @Output() manageTools = new EventEmitter(); - @Output() filterPermissionsChanged = new EventEmitter< - ContextUserPermission[] - >(); - - public titleMapping = { - ours: 'igo.context.contextManager.ourContexts', - shared: 'igo.context.contextManager.sharedContexts', - public: 'igo.context.contextManager.publicContexts' - }; - - public users: ContextProfils[]; - public permissions: ContextUserPermission[] = []; - - public actionStore = new ActionStore([]); - public actionbarMode = ActionbarMode.Overlay; - - public color = 'primary'; - - public showHidden = false; - - /** - * Context filter term - */ - @Input() - set term(value: string) { - this._term = value; - this.next(); - } - get term(): string { - return this._term; - } - public _term: string = ''; - - get sortedAlpha(): boolean { - return this._sortedAlpha; - } - set sortedAlpha(value: boolean) { - this._sortedAlpha = value; - this.next(); - } - private _sortedAlpha: boolean = undefined; - - public showContextFilter = ContextListControlsEnum.always; - - public thresholdToFilter = 5; - - get isEmpty(): boolean { - return ( - !this.contexts.ours.length && - !this.contexts.public?.length && - !this.contexts.shared?.length - ); - } - - constructor( - private cdRef: ChangeDetectorRef, - public configService: ConfigService, - public auth: AuthService, - private dialog: MatDialog, - private languageService: LanguageService, - private storageService: StorageService - ) { - this.contextConfigs = this.configService.getConfig('context'); - } - - ngOnInit() { - this.change$$ = this.change$ - .pipe(debounce(() => timer(50))) - .subscribe(() => { - this.contexts$.next(this.filterContextsList(this.contexts)); - }); - - this.actionStore.load([ - { - id: 'emptyContext', - title: this.languageService.translate.instant( - 'igo.context.contextManager.emptyContext' - ), - icon: 'map-outline', - tooltip: this.languageService.translate.instant( - 'igo.context.contextManager.emptyContextTooltip' - ), - handler: () => { - this.createContext(true); - } - }, - { - id: 'contextFromMap', - title: this.languageService.translate.instant( - 'igo.context.contextManager.contextMap' - ), - icon: 'map-check', - tooltip: this.languageService.translate.instant( - 'igo.context.contextManager.contextMapTooltip' - ), - handler: () => { - this.createContext(false); - } - } - ]); - } - - private next() { - this.change$.next(); - } - - private filterContextsList(contexts: ContextsList): ContextsList { - if (this.term === '') { - if (this.sortedAlpha) { - contexts = this.sortContextsList(contexts); - } - return contexts; - } else { - const ours = contexts.ours.filter((context) => { - const filterNormalized = this.term - .toLowerCase() - .normalize('NFD') - .replace(/[\u0300-\u036f]/g, ''); - const contextTitleNormalized = context.title - .toLowerCase() - .normalize('NFD') - .replace(/[\u0300-\u036f]/g, ''); - return contextTitleNormalized.includes(filterNormalized); - }); - - let updateContexts: ContextsList = { - ours - }; - - if (this.contexts.public) { - const publics = contexts.public.filter((context) => { - const filterNormalized = this.term - .toLowerCase() - .normalize('NFD') - .replace(/[\u0300-\u036f]/g, ''); - const contextTitleNormalized = context.title - .toLowerCase() - .normalize('NFD') - .replace(/[\u0300-\u036f]/g, ''); - return contextTitleNormalized.includes(filterNormalized); - }); - updateContexts.public = publics; - } - - if (this.contexts.shared) { - const shared = contexts.shared.filter((context) => { - const filterNormalized = this.term - .toLowerCase() - .normalize('NFD') - .replace(/[\u0300-\u036f]/g, ''); - const contextTitleNormalized = context.title - .toLowerCase() - .normalize('NFD') - .replace(/[\u0300-\u036f]/g, ''); - return contextTitleNormalized.includes(filterNormalized); - }); - updateContexts.shared = shared; - } - - if (this.sortedAlpha) { - updateContexts = this.sortContextsList(updateContexts); - } - return updateContexts; - } - } - - ngOnDestroy() { - this.change$$.unsubscribe(); - } - - public showFilter() { - switch (this.showContextFilter) { - case ContextListControlsEnum.always: - return true; - case ContextListControlsEnum.never: - return false; - default: - let totalLength = this.contexts.ours.length; - this.contexts.public - ? (totalLength += this.contexts.public.length) - : (totalLength += 0); - this.contexts.shared - ? (totalLength += this.contexts.shared.length) - : (totalLength += 0); - if (totalLength >= this.thresholdToFilter) { - return true; - } - return false; - } - } - - sortContextsList(contexts: ContextsList) { - if (contexts) { - const contextsList = JSON.parse(JSON.stringify(contexts)); - contextsList.ours.sort((a, b) => { - if (this.normalize(a.title) < this.normalize(b.title)) { - return -1; - } - if (this.normalize(a.title) > this.normalize(b.title)) { - return 1; - } - return 0; - }); - - if (contextsList.shared) { - contextsList.shared.sort((a, b) => { - if (this.normalize(a.title) < this.normalize(b.title)) { - return -1; - } - if (this.normalize(a.title) > this.normalize(b.title)) { - return 1; - } - return 0; - }); - } else if (contextsList.public) { - contextsList.public.sort((a, b) => { - if (this.normalize(a.title) < this.normalize(b.title)) { - return -1; - } - if (this.normalize(a.title) > this.normalize(b.title)) { - return 1; - } - return 0; - }); - } - return contextsList; - } - } - - normalize(str: string) { - return str - .normalize('NFD') - .replace(/[\u0300-\u036f]/g, '') - .toLowerCase(); - } - - toggleSort(sortAlpha: boolean) { - this.sortedAlpha = sortAlpha; - } - - clearFilter() { - this.term = ''; - } - - createContext(empty?: boolean) { - this.dialog - .open(BookmarkDialogComponent, { disableClose: false }) - .afterClosed() - .pipe(take(1)) - .subscribe((title: string) => { - if (title) { - this.create.emit({ title, empty }); - } - }); - } - - getPermission(user?): ContextUserPermission { - if (user) { - const permission = this.permissions.find((p) => p.name === user.name); - return permission; - } - } - - handleToggleCategory(user, parent?) { - const permission = this.getPermission(user); - if (permission) { - permission.checked = !permission.checked; - this.storageService.set( - 'contexts.permissions.' + permission.name, - permission.checked - ); - permission.indeterminate = false; - } - - if (parent) { - let indeterminate = false; - - for (const c of parent.childs) { - const cPermission = this.getPermission(c); - if (cPermission.checked !== permission.checked) { - indeterminate = true; - break; - } - } - const parentPermission = this.getPermission(parent); - if (parentPermission) { - parentPermission.checked = permission.checked; - this.storageService.set( - 'contexts.permissions.' + parentPermission.name, - permission.checked - ); - parentPermission.indeterminate = indeterminate; - } - } - - if (user.childs) { - for (const c of user.childs) { - const childrenPermission = this.getPermission(c); - if ( - childrenPermission && - childrenPermission.checked !== permission.checked - ) { - childrenPermission.checked = permission.checked; - this.storageService.set( - 'contexts.permissions.' + childrenPermission.name, - permission.checked - ); - } - } - } - - this.filterPermissionsChanged.emit(this.permissions); - } - - hideContext(context: DetailedContext) { - context.hidden = true; - if (!this.showHidden) { - const contexts: ContextsList = { ours: [], shared: [], public: [] }; - contexts.ours = this.contexts.ours.filter((c) => c.id !== context.id); - contexts.shared = this.contexts.shared.filter((c) => c.id !== context.id); - contexts.public = this.contexts.public.filter((c) => c.id !== context.id); - this.contexts = contexts; - } - this.hide.emit(context); - } - - showContext(context: DetailedContext) { - context.hidden = false; - this.show.emit(context); - } -} +import { AsyncPipe, KeyValuePipe, NgFor, NgIf } from '@angular/common'; +import { + ChangeDetectionStrategy, + ChangeDetectorRef, + Component, + EventEmitter, + Input, + OnDestroy, + OnInit, + Output +} from '@angular/core'; +import { FormsModule } from '@angular/forms'; +import { MatButtonModule } from '@angular/material/button'; +import { MatCheckboxModule } from '@angular/material/checkbox'; +import { MatDialog } from '@angular/material/dialog'; +import { MatFormFieldModule } from '@angular/material/form-field'; +import { MatIconModule } from '@angular/material/icon'; +import { MatInputModule } from '@angular/material/input'; +import { MatMenuModule } from '@angular/material/menu'; +import { MatTooltipModule } from '@angular/material/tooltip'; + +import { AuthService } from '@igo2/auth'; +import { + ActionStore, + ActionbarComponent, + ActionbarMode, + CollapsibleComponent, + ListComponent, + ListItemDirective +} from '@igo2/common'; +import { ConfigService } from '@igo2/core/config'; +import { LanguageService } from '@igo2/core/language'; +import { StorageService } from '@igo2/core/storage'; +import type { IgoMap } from '@igo2/geo'; + +import { TranslateModule } from '@ngx-translate/core'; +import { BehaviorSubject, ReplaySubject, Subscription, timer } from 'rxjs'; +import { take } from 'rxjs/operators'; +import { debounce } from 'rxjs/operators'; + +import { BookmarkDialogComponent } from '../../context-map-button/bookmark-button/bookmark-dialog.component'; +import { ContextItemComponent } from '../context-item/context-item.component'; +import { + ContextProfils, + ContextServiceOptions, + ContextUserPermission, + ContextsList, + DetailedContext +} from '../shared/context.interface'; +import { ContextListControlsEnum } from './context-list.enum'; + +@Component({ + selector: 'igo-context-list', + templateUrl: './context-list.component.html', + styleUrls: ['./context-list.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, + standalone: true, + imports: [ + ListComponent, + NgIf, + MatFormFieldModule, + MatInputModule, + FormsModule, + MatButtonModule, + MatIconModule, + MatTooltipModule, + ActionbarComponent, + MatMenuModule, + NgFor, + MatCheckboxModule, + CollapsibleComponent, + ContextItemComponent, + ListItemDirective, + AsyncPipe, + KeyValuePipe, + TranslateModule + ] +}) +export class ContextListComponent implements OnInit, OnDestroy { + public contextConfigs: ContextServiceOptions; + private contextsInitial: ContextsList = { ours: [] }; + contexts$: BehaviorSubject = new BehaviorSubject( + this.contextsInitial + ); + + change$ = new ReplaySubject(1); + + private change$$: Subscription; + + @Input() + get contexts(): ContextsList { + return this._contexts; + } + set contexts(value: ContextsList) { + this._contexts = value; + this.next(); + } + private _contexts: ContextsList = { ours: [] }; + + @Input() selectedContext: DetailedContext; + + @Input() map: IgoMap; + + @Input() + get defaultContextId(): string { + return this.contextConfigs + ? this._defaultContextId + : (this.storageService.get('favorite.context.uri') as string) || + this._defaultContextId; + } + set defaultContextId(value: string) { + this._defaultContextId = value; + } + private _defaultContextId: string; + + public collapsed: { contextScope }[] = []; + + @Output() select = new EventEmitter(); + @Output() unselect = new EventEmitter(); + @Output() edit = new EventEmitter(); + @Output() delete = new EventEmitter(); + @Output() save = new EventEmitter(); + @Output() clone = new EventEmitter(); + @Output() create = new EventEmitter<{ title: string; empty: boolean }>(); + @Output() hide = new EventEmitter(); + @Output() show = new EventEmitter(); + @Output() showHiddenContexts = new EventEmitter(); + @Output() favorite = new EventEmitter(); + @Output() managePermissions = new EventEmitter(); + @Output() manageTools = new EventEmitter(); + @Output() filterPermissionsChanged = new EventEmitter< + ContextUserPermission[] + >(); + + public titleMapping = { + ours: 'igo.context.contextManager.ourContexts', + shared: 'igo.context.contextManager.sharedContexts', + public: 'igo.context.contextManager.publicContexts' + }; + + public users: ContextProfils[]; + public permissions: ContextUserPermission[] = []; + + public actionStore = new ActionStore([]); + public actionbarMode = ActionbarMode.Overlay; + + public color = 'primary'; + + public showHidden = false; + + /** + * Context filter term + */ + @Input() + set term(value: string) { + this._term = value; + this.next(); + } + get term(): string { + return this._term; + } + public _term: string = ''; + + get sortedAlpha(): boolean { + return this._sortedAlpha; + } + set sortedAlpha(value: boolean) { + this._sortedAlpha = value; + this.next(); + } + private _sortedAlpha: boolean = undefined; + + public showContextFilter = ContextListControlsEnum.always; + + public thresholdToFilter = 5; + + get isEmpty(): boolean { + return ( + !this.contexts.ours.length && + !this.contexts.public?.length && + !this.contexts.shared?.length + ); + } + + constructor( + private cdRef: ChangeDetectorRef, + public configService: ConfigService, + public auth: AuthService, + private dialog: MatDialog, + private languageService: LanguageService, + private storageService: StorageService + ) { + this.contextConfigs = this.configService.getConfig('context'); + } + + ngOnInit() { + this.change$$ = this.change$ + .pipe(debounce(() => timer(50))) + .subscribe(() => { + this.contexts$.next(this.filterContextsList(this.contexts)); + }); + + this.actionStore.load([ + { + id: 'emptyContext', + title: this.languageService.translate.instant( + 'igo.context.contextManager.emptyContext' + ), + icon: 'map-outline', + tooltip: this.languageService.translate.instant( + 'igo.context.contextManager.emptyContextTooltip' + ), + handler: () => { + this.createContext(true); + } + }, + { + id: 'contextFromMap', + title: this.languageService.translate.instant( + 'igo.context.contextManager.contextMap' + ), + icon: 'map-check', + tooltip: this.languageService.translate.instant( + 'igo.context.contextManager.contextMapTooltip' + ), + handler: () => { + this.createContext(false); + } + } + ]); + } + + private next() { + this.change$.next(); + } + + private filterContextsList(contexts: ContextsList): ContextsList { + if (this.term === '') { + if (this.sortedAlpha) { + contexts = this.sortContextsList(contexts); + } + return contexts; + } else { + const ours = contexts.ours.filter((context) => { + const filterNormalized = this.term + .toLowerCase() + .normalize('NFD') + .replace(/[\u0300-\u036f]/g, ''); + const contextTitleNormalized = context.title + .toLowerCase() + .normalize('NFD') + .replace(/[\u0300-\u036f]/g, ''); + return contextTitleNormalized.includes(filterNormalized); + }); + + let updateContexts: ContextsList = { + ours + }; + + if (this.contexts.public) { + const publics = contexts.public.filter((context) => { + const filterNormalized = this.term + .toLowerCase() + .normalize('NFD') + .replace(/[\u0300-\u036f]/g, ''); + const contextTitleNormalized = context.title + .toLowerCase() + .normalize('NFD') + .replace(/[\u0300-\u036f]/g, ''); + return contextTitleNormalized.includes(filterNormalized); + }); + updateContexts.public = publics; + } + + if (this.contexts.shared) { + const shared = contexts.shared.filter((context) => { + const filterNormalized = this.term + .toLowerCase() + .normalize('NFD') + .replace(/[\u0300-\u036f]/g, ''); + const contextTitleNormalized = context.title + .toLowerCase() + .normalize('NFD') + .replace(/[\u0300-\u036f]/g, ''); + return contextTitleNormalized.includes(filterNormalized); + }); + updateContexts.shared = shared; + } + + if (this.sortedAlpha) { + updateContexts = this.sortContextsList(updateContexts); + } + return updateContexts; + } + } + + ngOnDestroy() { + this.change$$.unsubscribe(); + } + + public showFilter() { + switch (this.showContextFilter) { + case ContextListControlsEnum.always: + return true; + case ContextListControlsEnum.never: + return false; + default: + let totalLength = this.contexts.ours.length; + this.contexts.public + ? (totalLength += this.contexts.public.length) + : (totalLength += 0); + this.contexts.shared + ? (totalLength += this.contexts.shared.length) + : (totalLength += 0); + if (totalLength >= this.thresholdToFilter) { + return true; + } + return false; + } + } + + sortContextsList(contexts: ContextsList) { + if (contexts) { + const contextsList = JSON.parse(JSON.stringify(contexts)); + contextsList.ours.sort((a, b) => { + if (this.normalize(a.title) < this.normalize(b.title)) { + return -1; + } + if (this.normalize(a.title) > this.normalize(b.title)) { + return 1; + } + return 0; + }); + + if (contextsList.shared) { + contextsList.shared.sort((a, b) => { + if (this.normalize(a.title) < this.normalize(b.title)) { + return -1; + } + if (this.normalize(a.title) > this.normalize(b.title)) { + return 1; + } + return 0; + }); + } else if (contextsList.public) { + contextsList.public.sort((a, b) => { + if (this.normalize(a.title) < this.normalize(b.title)) { + return -1; + } + if (this.normalize(a.title) > this.normalize(b.title)) { + return 1; + } + return 0; + }); + } + return contextsList; + } + } + + normalize(str: string) { + return str + .normalize('NFD') + .replace(/[\u0300-\u036f]/g, '') + .toLowerCase(); + } + + toggleSort(sortAlpha: boolean) { + this.sortedAlpha = sortAlpha; + } + + clearFilter() { + this.term = ''; + } + + createContext(empty?: boolean) { + this.dialog + .open(BookmarkDialogComponent, { disableClose: false }) + .afterClosed() + .pipe(take(1)) + .subscribe((title: string) => { + if (title) { + this.create.emit({ title, empty }); + } + }); + } + + getPermission(user?): ContextUserPermission { + if (user) { + const permission = this.permissions.find((p) => p.name === user.name); + return permission; + } + } + + handleToggleCategory(user, parent?) { + const permission = this.getPermission(user); + if (permission) { + permission.checked = !permission.checked; + this.storageService.set( + 'contexts.permissions.' + permission.name, + permission.checked + ); + permission.indeterminate = false; + } + + if (parent) { + let indeterminate = false; + + for (const c of parent.childs) { + const cPermission = this.getPermission(c); + if (cPermission.checked !== permission.checked) { + indeterminate = true; + break; + } + } + const parentPermission = this.getPermission(parent); + if (parentPermission) { + parentPermission.checked = permission.checked; + this.storageService.set( + 'contexts.permissions.' + parentPermission.name, + permission.checked + ); + parentPermission.indeterminate = indeterminate; + } + } + + if (user.childs) { + for (const c of user.childs) { + const childrenPermission = this.getPermission(c); + if ( + childrenPermission && + childrenPermission.checked !== permission.checked + ) { + childrenPermission.checked = permission.checked; + this.storageService.set( + 'contexts.permissions.' + childrenPermission.name, + permission.checked + ); + } + } + } + + this.filterPermissionsChanged.emit(this.permissions); + } + + hideContext(context: DetailedContext) { + context.hidden = true; + if (!this.showHidden) { + const contexts: ContextsList = { ours: [], shared: [], public: [] }; + contexts.ours = this.contexts.ours.filter((c) => c.id !== context.id); + contexts.shared = this.contexts.shared.filter((c) => c.id !== context.id); + contexts.public = this.contexts.public.filter((c) => c.id !== context.id); + this.contexts = contexts; + } + this.hide.emit(context); + } + + showContext(context: DetailedContext) { + context.hidden = false; + this.show.emit(context); + } +}