@c8y/ngx-components
Version:
Angular modules for Cumulocity IoT applications
604 lines (596 loc) • 158 kB
JavaScript
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