UNPKG

@c8y/ngx-components

Version:

Angular modules for Cumulocity IoT applications

604 lines (596 loc) 158 kB
import * as i0 from '@angular/core'; import { Injectable, Input, Component, ViewChild } from '@angular/core'; import * as i1$2 from '@c8y/ngx-components/branding/shared/data'; import { createGenericBrandingForm, brandingFormGroupTopLevelEntries, brandingFormGroupTopLevelEntriesToUnpack, createBrandingForm } from '@c8y/ngx-components/branding/shared/data'; import { merge, of, firstValueFrom, BehaviorSubject, defer, interval } from 'rxjs'; import { map, startWith, switchMap, shareReplay, distinctUntilChanged, filter, take, pairwise, delay } from 'rxjs/operators'; import * as i2$1 from '@angular/router'; import { RouterLink, RouterOutlet } from '@angular/router'; import * as i3 from '@c8y/ngx-components'; import { CoreModule, HumanizeAppNamePipe, ModalComponent, C8yTranslatePipe, BuiltInActionType, gettext as gettext$1, Status, DataGridModule, GENERIC_FILE_TYPE, ColorInputComponent } from '@c8y/ngx-components'; import * as i1 from '@c8y/client'; import { ApplicationType } from '@c8y/client'; import { saveAs } from 'file-saver'; import * as i3$1 from '@c8y/ngx-components/static-assets/data'; import * as i1$3 from 'ngx-bootstrap/modal'; import * as i2 from '@angular/forms'; import { ReactiveFormsModule, FormsModule, FormControl } from '@angular/forms'; import * as i4 from '@angular/common'; import { NgIf, NgForOf, AsyncPipe } from '@angular/common'; import { uniqBy, cloneDeep } from 'lodash-es'; import * as i1$1 from '@c8y/ngx-components/branding/shared/lazy/add-branding-modal'; import { gettext } from '@c8y/ngx-components/gettext'; import * as i1$4 from 'ngx-bootstrap/popover'; import { PopoverModule } from 'ngx-bootstrap/popover'; import * as i4$1 from 'ngx-bootstrap/collapse'; import { CollapseModule } from 'ngx-bootstrap/collapse'; import { StaticAssetsFilePickerComponent } from '@c8y/ngx-components/static-assets'; import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { EditorComponent } from '@c8y/ngx-components/editor'; class ApplyBrandingToAppService { constructor(apps) { this.apps = apps; } async getBrandableApps() { const { data: apps } = await this.apps.list({ pageSize: 2000, dropOverwrittenApps: true, type: ApplicationType.HOSTED }); const appsWithDynamicOptionsUrl = this.getHostedAppsWhereDynamicOptionsUrlIsUndefined(apps); return appsWithDynamicOptionsUrl; } getHostedAppsWhereDynamicOptionsUrlIsUndefined(apps) { return apps.filter(app => { const manifest = app.manifest; if (!manifest) { return false; } // filter out none web sdk based apps if (!manifest.webSdkVersion) { return false; } // filter out packages if (manifest.isPackage) { return false; } const dynamicOptionsUrl = manifest.dynamicOptionsUrl; return dynamicOptionsUrl === undefined || dynamicOptionsUrl === true; }); } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: ApplyBrandingToAppService, deps: [{ token: i1.ApplicationService }], target: i0.ɵɵFactoryTarget.Injectable }); } static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: ApplyBrandingToAppService, providedIn: 'root' }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: ApplyBrandingToAppService, decorators: [{ type: Injectable, args: [{ providedIn: 'root' }] }], ctorParameters: () => [{ type: i1.ApplicationService }] }); class ApplyBrandingToAppModalComponent { constructor(apply, formBuilder) { this.apply = apply; this.formBuilder = formBuilder; this.brandableApps = []; this.result = new Promise((resolve, reject) => { this._resovle = resolve; this._reject = reject; }); } async ngOnInit() { const brandableApps = await this.apply.getBrandableApps(); this.brandableApps = uniqBy(brandableApps, 'contextPath'); const tags = this.brandableApps.map(app => app.contextPath); this.form = this.initForm(tags); if (this.currentTags.length) { this.form.patchValue(this.currentTags.reduce((prev, curr) => Object.assign(prev, { [curr]: true }), {})); } this.numberOfSelectedApps$ = merge(this.form.valueChanges, of(this.form.value)).pipe(map(value => Object.values(value).filter(Boolean).length)); } save() { this._resovle(this.form.value); } cancel() { this._reject(); } initForm(tags) { const tagsFormMap = tags.reduceRight((prev, curr) => Object.assign(prev, { [curr]: this.formBuilder.control(false) }), {}); return this.formBuilder.group(tagsFormMap); } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: ApplyBrandingToAppModalComponent, deps: [{ token: ApplyBrandingToAppService }, { token: i2.FormBuilder }], target: i0.ɵɵFactoryTarget.Component }); } static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.14", type: ApplyBrandingToAppModalComponent, isStandalone: true, selector: "c8y-apply-branding-to-app-modal", inputs: { currentTags: "currentTags" }, ngImport: i0, template: "<c8y-modal\n [title]=\"'Apply branding to apps' | translate\"\n [disabled]=\"!form || form.invalid || !brandableApps.length\"\n [headerClasses]=\"'dialog-header'\"\n (onDismiss)=\"cancel()\"\n (onClose)=\"save()\"\n [labels]=\"{ cancel: 'Cancel', ok: 'Save' }\"\n>\n <ng-container c8y-modal-title>\n <span c8yIcon=\"palette\"></span>\n </ng-container>\n <c8y-list-group\n class=\"m-b-0 no-border-last\"\n *ngIf=\"form\"\n [formGroup]=\"form\"\n >\n <c8y-li>\n <p\n class=\"text-center text-medium\"\n *ngIf=\"numberOfSelectedApps$ | async as numberOfApps; else noapps\"\n translate\n >\n {{ numberOfApps }} apps selected for branding\n </p>\n <ng-template #noapps>\n <p\n class=\"text-center text-medium\"\n translate\n >\n No apps selected for branding\n </p>\n </ng-template>\n </c8y-li>\n <c8y-li *ngFor=\"let app of brandableApps\">\n <c8y-li-icon>\n <c8y-app-icon\n class=\"icon-40\"\n [app]=\"app\"\n ></c8y-app-icon>\n </c8y-li-icon>\n <c8y-li-checkbox\n [attr.data-cy]=\"'branding-apply-branding-to-app-checkbox-' + app.contextPath\"\n [formControlName]=\"app.contextPath\"\n ></c8y-li-checkbox>\n <c8y-li-body class=\"p-t-8 d-block\">{{ app | humanizeAppName | async }}</c8y-li-body>\n </c8y-li>\n </c8y-list-group>\n</c8y-modal>\n", dependencies: [{ kind: "directive", type: NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i2.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i2.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "ngmodule", type: CoreModule }, { kind: "component", type: i3.AppIconComponent, selector: "c8y-app-icon", inputs: ["contextPath", "name", "app"] }, { kind: "directive", type: i3.IconDirective, selector: "[c8yIcon]", inputs: ["c8yIcon"] }, { kind: "pipe", type: i3.C8yTranslatePipe, name: "translate" }, { kind: "directive", type: i3.C8yTranslateDirective, selector: "[translate],[ngx-translate]" }, { kind: "directive", type: i4.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "pipe", type: i4.AsyncPipe, name: "async" }, { kind: "pipe", type: i3.HumanizeAppNamePipe, name: "humanizeAppName" }, { kind: "component", type: i3.ModalComponent, selector: "c8y-modal", inputs: ["disabled", "close", "dismiss", "title", "body", "customFooter", "headerClasses", "labels"], outputs: ["onDismiss", "onClose"] }, { kind: "component", type: i3.ListGroupComponent, selector: "c8y-list-group" }, { kind: "component", type: i3.ListItemComponent, selector: "c8y-list-item, c8y-li", inputs: ["active", "highlighted", "emptyActions", "dense", "collapsed", "selectable"], outputs: ["collapsedChange"] }, { kind: "component", type: i3.ListItemIconComponent, selector: "c8y-list-item-icon, c8y-li-icon", inputs: ["icon", "status"] }, { kind: "component", type: i3.ListItemBodyComponent, selector: "c8y-list-item-body, c8y-li-body", inputs: ["body"] }, { kind: "component", type: i3.ListItemCheckboxComponent, selector: "c8y-list-item-checkbox, c8y-li-checkbox", inputs: ["selected", "indeterminate", "disabled", "displayAsSwitch"], outputs: ["onSelect"] }] }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: ApplyBrandingToAppModalComponent, decorators: [{ type: Component, args: [{ selector: 'c8y-apply-branding-to-app-modal', standalone: true, imports: [NgIf, ReactiveFormsModule, CoreModule], template: "<c8y-modal\n [title]=\"'Apply branding to apps' | translate\"\n [disabled]=\"!form || form.invalid || !brandableApps.length\"\n [headerClasses]=\"'dialog-header'\"\n (onDismiss)=\"cancel()\"\n (onClose)=\"save()\"\n [labels]=\"{ cancel: 'Cancel', ok: 'Save' }\"\n>\n <ng-container c8y-modal-title>\n <span c8yIcon=\"palette\"></span>\n </ng-container>\n <c8y-list-group\n class=\"m-b-0 no-border-last\"\n *ngIf=\"form\"\n [formGroup]=\"form\"\n >\n <c8y-li>\n <p\n class=\"text-center text-medium\"\n *ngIf=\"numberOfSelectedApps$ | async as numberOfApps; else noapps\"\n translate\n >\n {{ numberOfApps }} apps selected for branding\n </p>\n <ng-template #noapps>\n <p\n class=\"text-center text-medium\"\n translate\n >\n No apps selected for branding\n </p>\n </ng-template>\n </c8y-li>\n <c8y-li *ngFor=\"let app of brandableApps\">\n <c8y-li-icon>\n <c8y-app-icon\n class=\"icon-40\"\n [app]=\"app\"\n ></c8y-app-icon>\n </c8y-li-icon>\n <c8y-li-checkbox\n [attr.data-cy]=\"'branding-apply-branding-to-app-checkbox-' + app.contextPath\"\n [formControlName]=\"app.contextPath\"\n ></c8y-li-checkbox>\n <c8y-li-body class=\"p-t-8 d-block\">{{ app | humanizeAppName | async }}</c8y-li-body>\n </c8y-li>\n </c8y-list-group>\n</c8y-modal>\n" }] }], ctorParameters: () => [{ type: ApplyBrandingToAppService }, { type: i2.FormBuilder }], propDecorators: { currentTags: [{ type: Input }] } }); class BrandingTagsCellRendererComponent { constructor(context, appState) { this.appState = appState; const tags = context.value || []; this.hasLatestTag = tags.some(tag => tag === 'latest'); const appSpecificTags = tags .filter(tag => tag.startsWith('app-')) .map(tag => tag.replace('app-', '')); this.appContextPathsOrApps$ = this.getAppContextPathsOrApps$(appSpecificTags); } getAppContextPathsOrApps$(appContextPaths) { if (!appContextPaths.length) { return of([]); } return this.appState.currentAppsOfUser.pipe(map(apps => { return appContextPaths.map(tag => { const app = apps.find(app => app.contextPath === tag); return app || tag; }); }), startWith(appContextPaths)); } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: BrandingTagsCellRendererComponent, deps: [{ token: i3.CellRendererContext }, { token: i3.AppStateService }], target: i0.ɵɵFactoryTarget.Component }); } static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.14", type: BrandingTagsCellRendererComponent, isStandalone: true, selector: "c8y-branding-tags-cell-renderer", ngImport: i0, template: "<div class=\"d-flex a-i-center gap-4 flex-wrap\">\n <div\n class=\"tag tag--success\"\n *ngIf=\"hasLatestTag\"\n translate\n >\n Global\n </div>\n <div\n class=\"tag tag--info\"\n *ngFor=\"let tag of appContextPathsOrApps$ | async\"\n [attr.data-cy]=\"'branding-tags-cell-renderer--tag-' + (tag.contextPath ? tag.contextPath : tag)\"\n >\n {{ tag | humanizeAppName | async }}\n </div>\n</div>\n", dependencies: [{ kind: "directive", type: NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "pipe", type: HumanizeAppNamePipe, name: "humanizeAppName" }, { kind: "pipe", type: AsyncPipe, name: "async" }] }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: BrandingTagsCellRendererComponent, decorators: [{ type: Component, args: [{ selector: 'c8y-branding-tags-cell-renderer', standalone: true, imports: [NgIf, NgForOf, HumanizeAppNamePipe, AsyncPipe], template: "<div class=\"d-flex a-i-center gap-4 flex-wrap\">\n <div\n class=\"tag tag--success\"\n *ngIf=\"hasLatestTag\"\n translate\n >\n Global\n </div>\n <div\n class=\"tag tag--info\"\n *ngFor=\"let tag of appContextPathsOrApps$ | async\"\n [attr.data-cy]=\"'branding-tags-cell-renderer--tag-' + (tag.contextPath ? tag.contextPath : tag)\"\n >\n {{ tag | humanizeAppName | async }}\n </div>\n</div>\n" }] }], ctorParameters: () => [{ type: i3.CellRendererContext }, { type: i3.AppStateService }] }); class BrandingImportModalComponent { constructor(addBrandingModalService, zip, staticAssets, brandings) { this.addBrandingModalService = addBrandingModalService; this.zip = zip; this.staticAssets = staticAssets; this.brandings = brandings; this.result = new Promise((resolve, reject) => { this._resovle = resolve; this._reject = reject; }); this.files = []; this.loading = false; } async droppedFile(event) { this.loading = true; try { this.files = event; await this.import(event[0].file); } catch (e) { console.error(e); this.files = []; this.loading = false; return; } this.modal._close(); this._resovle(); } async import(file) { const versionDetails = await this.addBrandingModalService.openAddBrandingModal(); if (!versionDetails) { throw Error('No version details provided'); } // verify if branding is already present const { variants } = await this.brandings.loadBrandingVariants(); if (!variants?.length) { await this.brandings.getStartedUsingBranding(); } const importedZipEntries = await firstValueFrom(this.zip.getEntries(file)); const staticAssetsEntry = importedZipEntries.find(entry => entry.filename === this.staticAssets.fileNames.exportZipName); if (!staticAssetsEntry) { throw Error(`Missing "${this.staticAssets.fileNames.exportZipName}" file in the uploaded zip.`); } const staticAssetsZip = await firstValueFrom(this.zip.getData(staticAssetsEntry)); const staticAssetsZipFile = new File([staticAssetsZip], staticAssetsEntry.filename); const staticAssetsZipEntries = await firstValueFrom(this.zip.getEntries(staticAssetsZipFile)); const filesToAdd = new Array(); let oldAssets; let newAssets; for (const entry of staticAssetsZipEntries) { if (entry.filename === this.brandings.fileNames.manifest) { continue; } if (entry.filename === this.staticAssets.fileNames.contents) { const fileContent = await (await firstValueFrom(this.zip.getData(entry))).text(); oldAssets = JSON.parse(fileContent); continue; } const file = await firstValueFrom(this.zip.getData(entry)); filesToAdd.push(new File([file], entry.filename)); } if (filesToAdd.length > 0) { const currentStaticAssets = await this.staticAssets.listFilesCached('branding', true); newAssets = await this.staticAssets.addFilesToStaticAssets('branding', filesToAdd, currentStaticAssets, false); } const publicOptionsEntry = importedZipEntries.find(entry => entry.filename === this.brandings.fileNames.exportZipName); if (!publicOptionsEntry) { throw Error(`Missing "${this.brandings.fileNames.exportZipName}" file in the uploaded zip.`); } const publicOptionsZip = await firstValueFrom(this.zip.getData(publicOptionsEntry)); const publicOptionsZipFile = new File([publicOptionsZip], publicOptionsEntry.filename); const publicOptionsFiles = await firstValueFrom(this.zip.getEntries(publicOptionsZipFile)); const optionsFileEntry = publicOptionsFiles.find(entry => entry.filename === this.brandings.fileNames.options); if (!optionsFileEntry) { throw Error(`Missing "${this.brandings.fileNames.options}" file in "${this.brandings.fileNames.exportZipName}" file of the uploaded zip.`); } const optionsFile = await firstValueFrom(this.zip.getData(optionsFileEntry)); let options = JSON.parse(await optionsFile.text()); if (oldAssets.length && newAssets.length) { options = this.brandings.replaceBrandingAssetsInBrandingOptions(options, oldAssets, newAssets); } const zip = await this.brandings.getBrandingZip(options); await this.brandings.saveBranding(zip, `${versionDetails.brandingName}-1`, [ versionDetails.brandingName ]); } cancel() { this._reject(); } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: BrandingImportModalComponent, deps: [{ token: i1$1.AddBrandingModalService }, { token: i3.ZipService }, { token: i3$1.StaticAssetsService }, { token: i1$2.StoreBrandingService }], target: i0.ɵɵFactoryTarget.Component }); } static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.14", type: BrandingImportModalComponent, isStandalone: true, selector: "c8y-branding-import-modal", viewQueries: [{ propertyName: "modal", first: true, predicate: ModalComponent, descendants: true, static: true }], ngImport: i0, template: "<c8y-modal\n [title]=\"'Import branding variant'\"\n [headerClasses]=\"'dialog-header'\"\n (onDismiss)=\"cancel()\"\n [labels]=\"{ cancel: 'Cancel' }\"\n>\n <ng-container c8y-modal-title>\n <span c8yIcon=\"import\"></span>\n </ng-container>\n <div class=\"p-t-16 p-b-16 p-l-24 p-r-24 separator-bottom\">\n <p\n class=\"text-medium text-center text-16\"\n translate\n >\n Easily copy any branding variant from any tenant\n </p>\n <p translate class=\"p-t-8\">\n Export the branding variant from the source tenant and import the .zip file here. This process\n allows you to maintain consistent branding across multiple tenants efficiently.\n </p>\n </div>\n <div\n class=\"p-24 m-r-auto m-l-auto\"\n style=\"max-width: 300px\"\n >\n <c8y-drop-area\n [icon]=\"'upload'\"\n (dropped)=\"droppedFile($event)\"\n [files]=\"files\"\n [accept]=\"'.zip'\"\n [maxAllowedFiles]=\"1\"\n [loading]=\"loading\"\n ></c8y-drop-area>\n </div>\n</c8y-modal>\n", dependencies: [{ kind: "ngmodule", type: CoreModule }, { kind: "directive", type: i3.IconDirective, selector: "[c8yIcon]", inputs: ["c8yIcon"] }, { kind: "directive", type: i3.C8yTranslateDirective, selector: "[translate],[ngx-translate]" }, { kind: "component", type: i3.DropAreaComponent, selector: "c8y-drop-area", inputs: ["formControl", "title", "message", "icon", "loadingMessage", "forceHideList", "alwaysShow", "clickToOpen", "loading", "progress", "maxAllowedFiles", "files", "maxFileSizeInMegaBytes", "accept"], outputs: ["dropped"] }, { kind: "component", type: i3.ModalComponent, selector: "c8y-modal", inputs: ["disabled", "close", "dismiss", "title", "body", "customFooter", "headerClasses", "labels"], outputs: ["onDismiss", "onClose"] }] }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: BrandingImportModalComponent, decorators: [{ type: Component, args: [{ selector: 'c8y-branding-import-modal', standalone: true, imports: [CoreModule], template: "<c8y-modal\n [title]=\"'Import branding variant'\"\n [headerClasses]=\"'dialog-header'\"\n (onDismiss)=\"cancel()\"\n [labels]=\"{ cancel: 'Cancel' }\"\n>\n <ng-container c8y-modal-title>\n <span c8yIcon=\"import\"></span>\n </ng-container>\n <div class=\"p-t-16 p-b-16 p-l-24 p-r-24 separator-bottom\">\n <p\n class=\"text-medium text-center text-16\"\n translate\n >\n Easily copy any branding variant from any tenant\n </p>\n <p translate class=\"p-t-8\">\n Export the branding variant from the source tenant and import the .zip file here. This process\n allows you to maintain consistent branding across multiple tenants efficiently.\n </p>\n </div>\n <div\n class=\"p-24 m-r-auto m-l-auto\"\n style=\"max-width: 300px\"\n >\n <c8y-drop-area\n [icon]=\"'upload'\"\n (dropped)=\"droppedFile($event)\"\n [files]=\"files\"\n [accept]=\"'.zip'\"\n [maxAllowedFiles]=\"1\"\n [loading]=\"loading\"\n ></c8y-drop-area>\n </div>\n</c8y-modal>\n" }] }], ctorParameters: () => [{ type: i1$1.AddBrandingModalService }, { type: i3.ZipService }, { type: i3$1.StaticAssetsService }, { type: i1$2.StoreBrandingService }], propDecorators: { modal: [{ type: ViewChild, args: [ModalComponent, { static: true }] }] } }); class BrandingImportModalService { constructor(modal) { this.modal = modal; } async openBrandingImportModal() { let versionDetails; try { const modalRef = this.modal.show(BrandingImportModalComponent, { class: 'modal-sm', ignoreBackdropClick: true, keyboard: false }); versionDetails = await modalRef.content.result; } catch (e) { // modal closed return; } return versionDetails; } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: BrandingImportModalService, deps: [{ token: i1$3.BsModalService }], target: i0.ɵɵFactoryTarget.Injectable }); } static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: BrandingImportModalService, providedIn: 'root' }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: BrandingImportModalService, decorators: [{ type: Injectable, args: [{ providedIn: 'root' }] }], ctorParameters: () => [{ type: i1$3.BsModalService }] }); class BrandingNameCellRendererComponent { constructor(context, router) { this.router = router; this.name = context.value; this.routerLink = `${this.router.url}/${context.item.name}`; } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: BrandingNameCellRendererComponent, deps: [{ token: i3.CellRendererContext }, { token: i2$1.Router }], target: i0.ɵɵFactoryTarget.Component }); } static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.14", type: BrandingNameCellRendererComponent, isStandalone: true, selector: "c8y-branding-name-cell-renderer", ngImport: i0, template: "<a [routerLink]=\"routerLink\">{{ name }}</a>\n", dependencies: [{ kind: "directive", type: RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "routerLink"] }] }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: BrandingNameCellRendererComponent, decorators: [{ type: Component, args: [{ selector: 'c8y-branding-name-cell-renderer', standalone: true, imports: [RouterLink], template: "<a [routerLink]=\"routerLink\">{{ name }}</a>\n" }] }], ctorParameters: () => [{ type: i3.CellRendererContext }, { type: i2$1.Router }] }); class BrandingTagsHeaderCellRendererComponent { constructor() { this.heading = gettext('Applied to'); this.popoverContent = gettext(`<p class="m-b-8">Branding can be applied at two levels</p> <ul class="list-unstyled m-b-0"> <li class="m-b-4"> <strong>Global: </strong> <span>Applied to the entire platform, affecting all apps and interfaces.</span> </li> <li> <strong>App-specific: </strong> <span>Applied only to selected apps within the platform.</span> </li> </ul>`); } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: BrandingTagsHeaderCellRendererComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); } static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.14", type: BrandingTagsHeaderCellRendererComponent, isStandalone: true, selector: "c8y-branding-tags-header-cell-renderer", ngImport: i0, template: "<div class=\"d-flex\">\n <span\n class=\"text-truncate\"\n [title]=\"heading | translate\"\n >\n {{ heading | translate }}\n </span>\n <button\n class=\"btn-help btn-help--sm a-s-center\"\n [attr.aria-label]=\"'Help' | translate\"\n [popover]=\"applyBrandingPopover\"\n placement=\"bottom\"\n triggers=\"focus\"\n container=\"body\"\n type=\"button\"\n (click)=\"$event.stopPropagation()\"\n >\n <i c8yIcon=\"question-circle-o\"></i>\n </button>\n <ng-template #applyBrandingPopover>\n <div [innerHTML]=\"popoverContent | translate\"></div>\n </ng-template>\n</div>\n", dependencies: [{ kind: "ngmodule", type: PopoverModule }, { kind: "directive", type: i1$4.PopoverDirective, selector: "[popover]", inputs: ["adaptivePosition", "boundariesElement", "popover", "popoverContext", "popoverTitle", "placement", "outsideClick", "triggers", "container", "containerClass", "isOpen", "delay"], outputs: ["onShown", "onHidden"], exportAs: ["bs-popover"] }, { kind: "pipe", type: C8yTranslatePipe, name: "translate" }] }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: BrandingTagsHeaderCellRendererComponent, decorators: [{ type: Component, args: [{ selector: 'c8y-branding-tags-header-cell-renderer', standalone: true, imports: [PopoverModule, C8yTranslatePipe], template: "<div class=\"d-flex\">\n <span\n class=\"text-truncate\"\n [title]=\"heading | translate\"\n >\n {{ heading | translate }}\n </span>\n <button\n class=\"btn-help btn-help--sm a-s-center\"\n [attr.aria-label]=\"'Help' | translate\"\n [popover]=\"applyBrandingPopover\"\n placement=\"bottom\"\n triggers=\"focus\"\n container=\"body\"\n type=\"button\"\n (click)=\"$event.stopPropagation()\"\n >\n <i c8yIcon=\"question-circle-o\"></i>\n </button>\n <ng-template #applyBrandingPopover>\n <div [innerHTML]=\"popoverContent | translate\"></div>\n </ng-template>\n</div>\n" }] }] }); class BrandingComponent { constructor(brandings, activatedRoute, appState, applicationService, zip, staticAssets, router, modal, confirmModal, brandingVersionService, addBrandingModalService, alert, brandingImportModalService, brandingTracking) { this.brandings = brandings; this.activatedRoute = activatedRoute; this.appState = appState; this.applicationService = applicationService; this.zip = zip; this.staticAssets = staticAssets; this.router = router; this.modal = modal; this.confirmModal = confirmModal; this.brandingVersionService = brandingVersionService; this.addBrandingModalService = addBrandingModalService; this.alert = alert; this.brandingImportModalService = brandingImportModalService; this.brandingTracking = brandingTracking; this.pagination = { pageSize: 10, currentPage: 1 }; this.displayOptions = { bordered: false, striped: true, filter: true, gridHeader: true, hover: true }; this.actionControls = [ { type: BuiltInActionType.Edit, callback: (entry, _reload) => { this.editBranding(entry.name); } }, { type: BuiltInActionType.Export, callback: async (entry, _reload) => { await this.exportBranding(entry); } }, { type: 'preview', icon: 'external-link', text: gettext$1('Open preview'), callback: async (entry, _reload) => { this.brandings.openPreviewForBranding(entry.name); } }, { type: 'duplicate', icon: 'copy', text: gettext$1('Duplicate'), callback: async (entry, _reload) => { await this.duplicateVersion(entry); this.refresh(); } }, { type: 'apply-to-app', icon: 'form', text: gettext$1('Apply to specific apps'), callback: async (entry, _reload) => { await this.applyToApps(entry); this.refresh(); } }, { type: 'set-as-default', icon: 'globe1', text: gettext$1('Set as global'), showIf: (entry) => { if (entry.tags?.includes('latest')) { return false; } return true; }, callback: async (entry, _reload) => { try { await this.confirmModal.confirm(gettext$1('Set as global branding'), gettext$1('Are you sure that you want to set this variant as the global branding? By doing so, this variant will be applied to all apps that do not have a specific branding applied. Do you want to proceed?'), Status.INFO); } catch { // did not confirm return; } await this.brandings.markAsActive(entry); this.refresh(); } }, { type: BuiltInActionType.Delete, showIf: (entry) => { if (entry.tags?.includes('latest')) { return false; } return true; }, callback: async (entry, _reload) => { try { await this.confirmModal.confirm(gettext$1('Delete branding variant'), gettext$1('You are about to delete this branding variant. This action cannot be undone. Do you want to proceed?'), Status.DANGER); } catch { // did not confirm return; } this.brandingTracking.deleteBrandingVariant(); await this.applicationService.deleteVersionPackage(entry.publicOptionsApp, { version: entry.version }); this.refresh(); } } ]; this.columns = [ { name: 'name', header: gettext$1('Name'), path: 'name', filterable: true, cellRendererComponent: BrandingNameCellRendererComponent }, { name: gettext$1('Applied to`Applications a branding applies to (table column header)`'), path: 'tags', filterable: false, cellCSSClassName: 'small', cellRendererComponent: BrandingTagsCellRendererComponent, headerCellRendererComponent: BrandingTagsHeaderCellRendererComponent }, { name: 'owner', header: gettext$1('Owner'), path: 'owner', filterable: false }, { name: 'lastUpdated', header: gettext$1('Last updated'), path: 'lastUpdated', filterable: false } ]; this.currentAppContextPath = 'administration'; this.reloadTrigger = new BehaviorSubject(undefined); this.currentAppContextPath = this.appState.state.app?.contextPath || this.currentAppContextPath; this.availableBrandingVariants$ = this.reloadTrigger.pipe(switchMap(() => this.brandings.loadBrandingVariants()), // hide the fallback from users map(variants => { variants.variants = variants.variants.filter(v => !v.tags?.includes('fallback')); return variants; }), shareReplay({ refCount: true, bufferSize: 1 })); } async deleteAllBrandings(publicOptions) { const result = await this.confirmModal.confirm(gettext$1('Delete all branding variants'), gettext$1(`<p class="m-b-8"> This action will permanently remove all custom branding variants. Your tenant will revert to the default branding. </p> <p class="m-b-8"> Consider exporting your custom branding variants before proceeding, as this action cannot be undone. </p> <p> Are you sure you want to continue? </p>`), Status.DANGER, { cancel: gettext$1('Cancel'), ok: gettext$1('Delete all') }); if (result === false || (typeof result === 'object' && !result.confirmed)) { return; } this.brandingTracking.deleteAllBrandings(); await this.brandings.deleteAllBrandings(publicOptions); this.refresh(); } async exportBranding(variant) { this.brandingTracking.exportBranding(); const branding = await firstValueFrom(this.brandings.getZipForBinary(variant.id)); const staticAssetsApp = await this.staticAssets.getAppForTenant('branding'); const staticAssetsZip = await firstValueFrom(this.brandings.getZipForBinary(staticAssetsApp.activeVersionId, this.staticAssets.fileNames.exportZipName)); const blob = await this.zip.createZip([ { fileName: this.brandings.fileNames.exportZipName, blob: branding }, { fileName: this.staticAssets.fileNames.exportZipName, blob: staticAssetsZip } ]); saveAs(blob, `branding-export-${variant.name}-${variant.revision}-${new Date().getTime()}.zip`); } refresh() { this.reloadTrigger.next(); } async addNewVersion() { const versionDetails = await this.addBrandingModalService.openAddBrandingModal(); if (!versionDetails) { this.refresh(); return; } let fallBackBranding = {}; try { fallBackBranding = await this.brandings.getBrandingOptionsForVersion('latest'); } catch (e) { console.warn(`Failed to load latest branding`); } this.brandingTracking.addNewVersion(); try { await this.brandings.addBranding(this.brandingVersionService.createInitialBrandingVersion(versionDetails.brandingName), fallBackBranding, [versionDetails.brandingName]); await this.brandings.waitForBrandingToBePresent(versionDetails.brandingName); this.editBranding(versionDetails.brandingName); } catch (e) { this.alert.addServerFailure(e, 'danger'); } } async duplicateVersion(version) { const options = await this.brandings.getBrandingOptionsForVersion(version.version); const versionDetails = await this.addBrandingModalService.openDuplicateBrandingModal(); if (!versionDetails) { return; } this.brandingTracking.duplicateVersion(); try { await this.brandings.addBranding(this.brandingVersionService.createInitialBrandingVersion(versionDetails.brandingName), options, [versionDetails.brandingName]); await this.brandings.waitForBrandingToBePresent(versionDetails.brandingName); this.editBranding(versionDetails.brandingName); } catch (e) { this.alert.addServerFailure(e, 'danger'); } } async editBranding(brandingName) { return this.router.navigate([brandingName, 'edit'], { relativeTo: this.activatedRoute }); } async getStartedUsingBranding() { const result = await this.confirmModal.confirm(gettext$1(`Get started using branding variants`), gettext$1(`<p class="m-b-8">Confirming this action creates two branding variants:</p> <ul class="m-b-8 p-l-16"> <li>A default copy of your current global branding</li> <li>Your new customizable variant.</li> </ul> <p class="m-b-8"> After customization, you can set your new variant as global or apply it to specific apps. </p> <p> To revert changes, simply set the default branding as global or use the "Delete all variants" button at any time. </p>`)); if (result === false || (typeof result === 'object' && !result.confirmed)) { return; } this.brandingTracking.getStartedUsingBranding(); await this.brandings.getStartedUsingBranding(); await this.addNewVersion(); } async applyToApps(version) { const prefix = 'app-'; const currentTags = version.tags .filter(tag => tag.startsWith(prefix)) .map(tag => tag.replace(prefix, '')); const otherTags = version.tags.filter(tag => !tag.startsWith(prefix)); let selectedTags; try { const modalRef = this.modal.show(ApplyBrandingToAppModalComponent, { initialState: { currentTags }, ignoreBackdropClick: true, keyboard: false }); selectedTags = await modalRef.content.result; } catch (e) { // modal closed return; } const selectedApps = Object.keys(selectedTags).filter(key => selectedTags[key]); this.brandingTracking.applyToApps(selectedApps); const tagsToSet = [...otherTags, ...selectedApps.map(tag => `${prefix}${tag}`)]; await this.removeTagsFromOtherVersions(version.publicOptionsApp, tagsToSet, version.version); await this.applicationService.setPackageVersionTag(version.publicOptionsApp, version.version, tagsToSet); } async importBranding() { try { await this.brandingImportModalService.openBrandingImportModal(); this.brandings.refreshTriggerBrandingVariants.next(); this.reloadTrigger.next(); } catch (e) { // modal closed } } async removeTagsFromOtherVersions(publicOptionsApp, tagsToRemove, versionToIgnore) { for (const version of publicOptionsApp.applicationVersions) { if (version.version === versionToIgnore) { continue; } const removedTags = version.tags.filter(tag => !tagsToRemove.includes(tag)); if (removedTags.length === version.tags.length) { continue; } await this.applicationService.setPackageVersionTag(publicOptionsApp, version.version, removedTags); } } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: BrandingComponent, deps: [{ token: i1$2.StoreBrandingService }, { token: i2$1.ActivatedRoute }, { token: i3.AppStateService }, { token: i1.ApplicationService }, { token: i3.ZipService }, { token: i3$1.StaticAssetsService }, { token: i2$1.Router }, { token: i1$3.BsModalService }, { token: i3.ModalService }, { token: i1$2.BrandingVersionService }, { token: i1$1.AddBrandingModalService }, { token: i3.AlertService }, { token: BrandingImportModalService }, { token: i1$2.BrandingTrackingService }], target: i0.ɵɵFactoryTarget.Component }); } static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.14", type: BrandingComponent, isStandalone: true, selector: "c8y-branding", ngImport: i0, template: "<c8y-title translate>Branding</c8y-title>\n\n<c8y-breadcrumb>\n <c8y-breadcrumb-item\n [icon]=\"'cog'\"\n [label]=\"'Settings' | translate\"\n ></c8y-breadcrumb-item>\n <c8y-breadcrumb-item\n [icon]=\"'palette'\"\n [label]=\"'Branding' | translate\"\n [path]=\"'/branding-editor'\"\n ></c8y-breadcrumb-item>\n</c8y-breadcrumb>\n\n<ng-container *ngIf=\"availableBrandingVariants$ | async as brandingVariant; else loading\">\n <c8y-action-bar-item\n *ngIf=\"brandingVariant.publicOptions\"\n [placement]=\"'right'\"\n [priority]=\"30\"\n >\n <button\n class=\"btn btn-link\"\n (click)=\"addNewVersion()\"\n data-cy=\"branding-add-branding-variant\"\n >\n <i c8yIcon=\"plus-circle\"></i>\n <span translate>Add variant</span>\n </button>\n </c8y-action-bar-item>\n <c8y-action-bar-item\n [placement]=\"'right'\"\n [priority]=\"20\"\n >\n <button\n class=\"btn btn-link\"\n data-cy=\"branding-import-branding\"\n (click)=\"importBranding()\"\n >\n <i [c8yIcon]=\"'data-import'\"></i>\n <span translate>Import variant</span>\n </button>\n </c8y-action-bar-item>\n\n <c8y-action-bar-item\n *ngIf=\"brandingVariant.publicOptions\"\n [placement]=\"'right'\"\n [priority]=\"10\"\n >\n <button\n class=\"btn btn-link\"\n (click)=\"deleteAllBrandings(brandingVariant.publicOptions)\"\n data-cy=\"branding-remove-all-brandings\"\n >\n <i [c8yIcon]=\"'trash-o'\"></i>\n <span translate>Delete all variants</span>\n </button>\n </c8y-action-bar-item>\n\n <div class=\"content-fullpage d-flex d-col border-top\">\n <c8y-data-grid\n [title]=\"'Branding variants' | translate\"\n [columns]=\"columns\"\n [actionControls]=\"actionControls\"\n [pagination]=\"pagination\"\n [displayOptions]=\"displayOptions\"\n (onReload)=\"refresh()\"\n [rows]=\"brandingVariant.variants\"\n >\n <c8y-ui-empty-state\n [icon]=\"'palette'\"\n [title]=\"'No branding variants' | translate\"\n [subtitle]=\"\n 'Personalize your experience with a theme-able interface. Create a unique look that aligns with your brand identity.'\n | translate\n \"\n *ngIf=\"!brandingVariant.publicOptions\"\n [horizontal]=\"false\"\n >\n <button\n class=\"btn btn-default\"\n data-cy=\"branding-get-started-using-branding\"\n (click)=\"getStartedUsingBranding()\"\n translate\n >\n Add branding variant\n </button>\n </c8y-ui-empty-state>\n </c8y-data-grid>\n </div>\n</ng-container>\n\n<ng-template #loading>\n <c8y-loading></c8y-loading>\n</ng-template>\n", dependencies: [{ kind: "ngmodule", type: CoreModule }, { kind: "component", type: i3.ActionBarItemComponent, selector: "c8y-action-bar-item", inputs: ["placement", "priority", "itemClass", "injector", "groupId", "inGroupPriority"] }, { kind: "component", type: i3.BreadcrumbComponent, selector: "c8y-breadcrumb" }, { kind: "component", type: i3.BreadcrumbItemComponent, selector: "c8y-breadcrumb-item", inputs: ["icon", "translate", "label", "path", "injector"] }, { kind: "component", type: i3.EmptyStateComponent, selector: "c8y-ui-empty-state", inputs: ["icon", "title", "subtitle", "horizontal"] }, { kind: "directive", type: i3.IconDirective, selector: "[c8yIcon]", inputs: ["c8yIcon"] }, { kind: "pipe", type: i3.C8yTranslatePipe, name: "translate" }, { kind: "directive", type: i3.C8yTranslateDirective, selector: "[translate],[ngx-translate]" }, { kind: "directive", type: i4.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "pipe", type: i4.AsyncPipe, name: "async" }, { kind: "component", type: i3.LoadingComponent, selector: "c8y-loading", inputs: ["layout", "progress", "message"] }, { kind: "component", type: i3.DataGridComponent, selector: "c8y-data-grid", inputs: ["title", "loadMoreItemsLabel", "loadingItemsLabel", "showSearch", "refresh", "columns", "rows", "pagination", "childNodePagination", "infiniteScroll", "serverSideDataCallback", "selectable", "singleSelection", "selectionPrimaryKey", "displayOptions", "actionControls", "bulkActionControls", "headerActionControls", "searchText", "configureColumnsEnabled", "showCounterWarning", "activeClassName", "expandableRows", "treeGrid", "hideReload", "childNodesProperty", "parentNodeLabelProperty"], outputs: ["rowMouseOver", "rowMouseLeave", "rowClick", "onConfigChange", "onBeforeFilter", "onBeforeSearch", "onFilter", "itemsSelect", "onReload", "onAddCustomColumn", "onRemoveCustomColumn", "onColumnFilterReset", "onSort", "onPageSizeChange", "onColumnReordered", "onColumnVisibilityChange"] }, { kind: "component", type: i3.TitleComponent, selector: "c8y-title", inputs: ["pageTitleUpdate"] }, { kind: "ngmodule", type: DataGridModule }] }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: BrandingComponent, decorators: [{ type: Component, args: [{ selector: 'c8y-branding', standalone: true, imports: [CoreModule, DataGridModule], template: "<c8y-title translate>Branding</c8y-title>\n\n<c8y-breadcrumb>\n <c8y-breadcrumb-item\n [icon]=\"'cog'\"\n [label]=\"'Settings' | translate\"\n ></c8y-breadcrumb-item>\n <c8y-breadcrumb-item\n [icon]=\"'palette'\"\n [label]=\"'Branding' | translate\"\n [path]=\"'/branding-editor'\"\n ></c8y-breadcrumb-item>\n</c8y-breadcrumb>\n\n<ng-container *ngIf=\"availableBrandingVariants$ | async as brandingVariant; else loading\">\n <c8y-action-bar-item\n *ngIf=\"brandingVariant.publicOptions\"\n [placement]=\"'right'\"\n [priority]=\"30\"\n >\n <button\n class=\"btn btn-link\"\n (click)=\"addNewVersion()\"\n data-cy=\"branding-add-branding-variant\"\n >\n <i c8yIcon=\"plus-circle\"></i>\n <span translate>Add variant</span>\n </button>\n </c8y-action-bar-item>\n <c8y-action-bar-item\n [placement]=\"'right'\"\n [priority]=\"20\"\n >\n <button\n class=\"btn btn-link\"\n data-cy=\"branding-import-branding\"\n (click)=\"importBranding()\"\n >\n <i [c8yIcon]=\"'data-import'\"></i>\n <span translate>Import variant</span>\n </button>\n </c8y-action-bar-item>\n\n <c8y-action-bar-item\n *ngIf=\"brandingVariant.publicOptions\"\n [placement]=\"'right'\"\n [priority]=\"10\"\n >\n <button\n class=\"btn btn-link\"\n (click)=\"deleteAllBrandings(brandingVariant.publicOptions)\"\n data-cy=\"branding-remove-all-brandings\"\n >\n <i [c8yIcon]=\"'trash-o'\"></i>\n <span translate>Delete all variants</span>\n </button>\n </c8y-action-bar-item>\n\n <div class=\"content-fullpage d-flex d-col border-top\">\n <c8y-data-grid\n [title]=\"'Branding variants' | translate\"\n [columns]=\"columns\"\n [actionControls]=\"actionControls\"\n [pagination]=\"pagination\"\n [displayOptions]=\"displayOptions\"\n (onReload)=\"refresh()\"\n [rows]=\"brandingVariant.variants\"\n >\n <c8y-ui-empty-state\n [icon]=\"'palette'\"\n [title]=\"'No branding variants' | translate\"\n [subtitle]=\"\n 'Personalize your experience with a theme-able interface. Create a unique look that aligns with your brand identity.'\n | translate\n \"\n *ngIf=\"!brandingVariant.publicOptions\"\n [horizontal]=\"false\"\n >\n <button\n class=\"btn btn-default\"\n data-cy=\"branding-get-started-using-branding\"\n (click)=\"getStartedUsingBranding()\"\n translate\n >\n Add branding variant\n </button>\n </c8y-ui-empty-state>\n </c8y-data-grid>\n </div>\n</ng-container>\n\n<ng-template #loading>\n <c8y-loading></c8y-loading>\n</ng-template>\n" }] }], ctorParameters: () => [{ type: i1$2.StoreBrandingService }, { type: i2$1.ActivatedRoute }, { type: i3.AppStateService }, { type: i1.ApplicationService }, { ty