fix(console): new colorpicker, remove icons logo font (#1826)

* custom color picker, delete font

* delete asset, i18n

* escape deep styles

* ts, proto

* remove fallback img, color clickable, remove font

* stylelint

* fix spinner

* fix deprecated chipinput.input

* improve logo on login

* fix qr code

* always use black on white for qr code

* fix header and footer css

Co-authored-by: Livio Amstutz <livio.a@gmail.com>
This commit is contained in:
Max Peintner
2021-06-09 11:55:16 +02:00
committed by GitHub
parent 255139862d
commit 59af02b2f3
29 changed files with 1242 additions and 933 deletions

View File

@@ -34,6 +34,7 @@
"grpc-web": "^1.2.1", "grpc-web": "^1.2.1",
"libphonenumber-js": "^1.9.16", "libphonenumber-js": "^1.9.16",
"moment": "^2.29.1", "moment": "^2.29.1",
"ngx-color": "^7.0.0",
"ngx-quicklink": "^0.2.6", "ngx-quicklink": "^0.2.6",
"rxjs": "~6.6.7", "rxjs": "~6.6.7",
"ts-protoc-gen": "^0.14.0", "ts-protoc-gen": "^0.14.0",
@@ -2915,6 +2916,14 @@
"node": ">=4.0.0" "node": ">=4.0.0"
} }
}, },
"node_modules/@ctrl/tinycolor": {
"version": "3.4.0",
"resolved": "https://registry.npmjs.org/@ctrl/tinycolor/-/tinycolor-3.4.0.tgz",
"integrity": "sha512-JZButFdZ1+/xAfpguQHoabIXkcqRRKpMrWKBkpEZZyxfY9C1DpADFB8PEqGSTeFr135SaTRfKqGKx5xSCLI7ZQ==",
"engines": {
"node": ">=10"
}
},
"node_modules/@discoveryjs/json-ext": { "node_modules/@discoveryjs/json-ext": {
"version": "0.5.2", "version": "0.5.2",
"resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.5.2.tgz", "resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.5.2.tgz",
@@ -10058,6 +10067,11 @@
"node": ">=0.10.0" "node": ">=0.10.0"
} }
}, },
"node_modules/material-colors": {
"version": "1.2.6",
"resolved": "https://registry.npmjs.org/material-colors/-/material-colors-1.2.6.tgz",
"integrity": "sha512-6qE4B9deFBIa9YSpOc9O0Sgc43zTeVYbgDT5veRKSlB2+ZuHNoVVxA1L/ckMUayV9Ay9y7Z/SZCLcGteW9i7bg=="
},
"node_modules/mathml-tag-names": { "node_modules/mathml-tag-names": {
"version": "2.1.3", "version": "2.1.3",
"resolved": "https://registry.npmjs.org/mathml-tag-names/-/mathml-tag-names-2.1.3.tgz", "resolved": "https://registry.npmjs.org/mathml-tag-names/-/mathml-tag-names-2.1.3.tgz",
@@ -10690,6 +10704,20 @@
"integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==",
"dev": true "dev": true
}, },
"node_modules/ngx-color": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/ngx-color/-/ngx-color-7.0.0.tgz",
"integrity": "sha512-BiTapBTT/f3sFSEFqet3xe06bpqmBm7hmA24s09ogRYYVGL1J69U13XfLwTQG8PhX4qNBceh7p5nhKA1zczHMg==",
"dependencies": {
"@ctrl/tinycolor": "^3.4.0",
"material-colors": "^1.2.6",
"tslib": "^2.1.0"
},
"peerDependencies": {
"@angular/common": ">=12.0.0-0",
"@angular/core": ">=12.0.0-0"
}
},
"node_modules/ngx-quicklink": { "node_modules/ngx-quicklink": {
"version": "0.2.6", "version": "0.2.6",
"resolved": "https://registry.npmjs.org/ngx-quicklink/-/ngx-quicklink-0.2.6.tgz", "resolved": "https://registry.npmjs.org/ngx-quicklink/-/ngx-quicklink-0.2.6.tgz",
@@ -19366,6 +19394,11 @@
"integrity": "sha512-5a6wqoJV/xEdbRNKVo6I4hO3VjyDq//8q2f9I6PBAvMesJHFauXDorcNCsr9RzvsZnaWi5NYCcfyqP1QeFHFbw==", "integrity": "sha512-5a6wqoJV/xEdbRNKVo6I4hO3VjyDq//8q2f9I6PBAvMesJHFauXDorcNCsr9RzvsZnaWi5NYCcfyqP1QeFHFbw==",
"dev": true "dev": true
}, },
"@ctrl/tinycolor": {
"version": "3.4.0",
"resolved": "https://registry.npmjs.org/@ctrl/tinycolor/-/tinycolor-3.4.0.tgz",
"integrity": "sha512-JZButFdZ1+/xAfpguQHoabIXkcqRRKpMrWKBkpEZZyxfY9C1DpADFB8PEqGSTeFr135SaTRfKqGKx5xSCLI7ZQ=="
},
"@discoveryjs/json-ext": { "@discoveryjs/json-ext": {
"version": "0.5.2", "version": "0.5.2",
"resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.5.2.tgz", "resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.5.2.tgz",
@@ -25200,6 +25233,11 @@
"object-visit": "^1.0.0" "object-visit": "^1.0.0"
} }
}, },
"material-colors": {
"version": "1.2.6",
"resolved": "https://registry.npmjs.org/material-colors/-/material-colors-1.2.6.tgz",
"integrity": "sha512-6qE4B9deFBIa9YSpOc9O0Sgc43zTeVYbgDT5veRKSlB2+ZuHNoVVxA1L/ckMUayV9Ay9y7Z/SZCLcGteW9i7bg=="
},
"mathml-tag-names": { "mathml-tag-names": {
"version": "2.1.3", "version": "2.1.3",
"resolved": "https://registry.npmjs.org/mathml-tag-names/-/mathml-tag-names-2.1.3.tgz", "resolved": "https://registry.npmjs.org/mathml-tag-names/-/mathml-tag-names-2.1.3.tgz",
@@ -25710,6 +25748,16 @@
"integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==",
"dev": true "dev": true
}, },
"ngx-color": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/ngx-color/-/ngx-color-7.0.0.tgz",
"integrity": "sha512-BiTapBTT/f3sFSEFqet3xe06bpqmBm7hmA24s09ogRYYVGL1J69U13XfLwTQG8PhX4qNBceh7p5nhKA1zczHMg==",
"requires": {
"@ctrl/tinycolor": "^3.4.0",
"material-colors": "^1.2.6",
"tslib": "^2.1.0"
}
},
"ngx-quicklink": { "ngx-quicklink": {
"version": "0.2.6", "version": "0.2.6",
"resolved": "https://registry.npmjs.org/ngx-quicklink/-/ngx-quicklink-0.2.6.tgz", "resolved": "https://registry.npmjs.org/ngx-quicklink/-/ngx-quicklink-0.2.6.tgz",

View File

@@ -37,6 +37,7 @@
"grpc-web": "^1.2.1", "grpc-web": "^1.2.1",
"libphonenumber-js": "^1.9.16", "libphonenumber-js": "^1.9.16",
"moment": "^2.29.1", "moment": "^2.29.1",
"ngx-color": "^7.0.0",
"ngx-quicklink": "^0.2.6", "ngx-quicklink": "^0.2.6",
"rxjs": "~6.6.7", "rxjs": "~6.6.7",
"ts-protoc-gen": "^0.14.0", "ts-protoc-gen": "^0.14.0",

View File

@@ -25,8 +25,8 @@ import { QuicklinkModule } from 'ngx-quicklink';
import { from, Observable } from 'rxjs'; import { from, Observable } from 'rxjs';
import { OnboardingModule } from 'src/app/modules/onboarding/onboarding.module'; import { OnboardingModule } from 'src/app/modules/onboarding/onboarding.module';
import { RegExpPipeModule } from 'src/app/pipes/regexp-pipe/regexp-pipe.module'; import { RegExpPipeModule } from 'src/app/pipes/regexp-pipe/regexp-pipe.module';
import { AssetService } from 'src/app/services/asset.service';
import { SubscriptionService } from 'src/app/services/subscription.service'; import { SubscriptionService } from 'src/app/services/subscription.service';
import { UploadService } from 'src/app/services/upload.service';
import { environment } from '../environments/environment'; import { environment } from '../environments/environment';
import { AppRoutingModule } from './app-routing.module'; import { AppRoutingModule } from './app-routing.module';
@@ -181,7 +181,7 @@ const authConfig: AuthConfig = {
AuthenticationService, AuthenticationService,
GrpcAuthService, GrpcAuthService,
SubscriptionService, SubscriptionService,
UploadService, AssetService,
{ provide: 'windowObject', useValue: window }, { provide: 'windowObject', useValue: window },
], ],
bootstrap: [AppComponent], bootstrap: [AppComponent],

View File

@@ -133,7 +133,7 @@ export class IdpCreateComponent implements OnInit, OnDestroy {
} }
public addScope(event: MatChipInputEvent): void { public addScope(event: MatChipInputEvent): void {
const input = event.input; const input = event.chipInput?.inputElement;
const value = event.value.trim(); const value = event.value.trim();
if (value !== '') { if (value !== '') {

View File

@@ -190,7 +190,7 @@ export class IdpComponent implements OnInit, OnDestroy {
} }
public addScope(event: MatChipInputEvent): void { public addScope(event: MatChipInputEvent): void {
const input = event.input; const input = event.chipInput?.inputElement;
const value = event.value.trim(); const value = event.value.trim();
if (value !== '') { if (value !== '') {

View File

@@ -1,15 +1,19 @@
<div class="form-row"> <p class="name">{{name}} (current {{color}})</p>
<cnsl-form-field class="formfield">
<cnsl-label>{{name}} (current {{color}})</cnsl-label> <div class="wrapper">
<input cnslInput [(ngModel)]="previewColor" (keydown.enter)="name ? emitPreview(previewColor) : null" /> <button class="mat-elevation-z3" [style.background-color]="previewColor" (click)="isOpen = !isOpen" cdkOverlayOrigin #trigger="cdkOverlayOrigin" matTooltip="{{'ACTIONS.SET' | translate}}"> </button>
</cnsl-form-field>
<button matTooltip="{{'ACTIONS.SET' | translate}}" (click)="emitPreview(previewColor)" mat-icon-button><mat-icon>check</mat-icon></button> <div class="hex-wrapper" (click)="isOpen = !isOpen">
</div> <i class="las la-hashtag"></i>
<div class="color-selector"> <span class="hex">{{previewColorCropped}}</span>
<div *ngFor="let c of colors" class="circle mat-elevation-z3"
[ngClass]="{'active': color == c.color || previewColor == c.color}"
(click)="emitPreview(c.color)" [ngStyle]="{'background-color': c.color}">
<span *ngIf="previewColor == c.color">P</span>
<span *ngIf="color == c.color">C</span>
</div> </div>
</div> </div>
<ng-template
cdkConnectedOverlay
[cdkConnectedOverlayOrigin]="trigger"
[cdkConnectedOverlayOpen]="isOpen"
(overlayOutsideClick)="isOpen = false"
>
<color-chrome class="picker" [color]="previewColor" (onChangeComplete)="changeComplete($event)"></color-chrome>
</ng-template>

View File

@@ -1,52 +1,52 @@
.title { .name {
// font-size: 1rem; font-size: 14px;
display: block; margin-bottom: 0;
font-size: 18px; color: var(--grey);
&.border {
border-top: 1px solid var(--grey);
padding-top: 1.5rem;
}
} }
.form-row { .wrapper {
border-radius: .5rem;
padding: 0 1rem;
display: flex; display: flex;
align-items: flex-end; align-items: center;
.formfield { .hex-wrapper {
flex: 1; display: flex;
align-items: center;
flex-direction: row;
color: var(--grey);
font-size: 14px;
padding: 0 1rem;
cursor: pointer;
.hex {
font-size: 16px;
}
} }
button { button {
margin-bottom: .9rem; cursor: pointer;
} margin: .5rem 0;
} border-radius: .5rem;
height: 35px;
.color-selector { width: 35px;
display: flex; display: flex;
align-items: center; align-items: center;
flex-wrap: wrap; border: none;
margin: 0 -.25rem; justify-content: space-between;
width: 100%; }
padding-bottom: 1rem;
.circle {
height: 30px;
width: 30px;
background-color: #cd5c5c;
border-radius: 50%;
margin: .25rem;
box-sizing: border-box;
display: flex;
align-items: center;
justify-content: center;
span {
font-size: 18px;
} }
&.active { .picker {
border: 3px solid #ffffff90; margin: 1rem 0;
} }
// stylelint-disable
::ng-deep .chrome-picker {
border-radius: .5rem !important;
} }
::ng-deep .saturation {
border-radius: .5rem .5rem 0 0 !important;
} }
// stylelint-enable

View File

@@ -1,4 +1,5 @@
import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core'; import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core';
import { ColorEvent } from 'ngx-color';
import { ColorType } from '../private-labeling-policy.component'; import { ColorType } from '../private-labeling-policy.component';
@@ -77,6 +78,7 @@ export class ColorComponent implements OnInit {
]; ];
public colors: Array<{ name: string; color: string; }> = this.PRIMARY; public colors: Array<{ name: string; color: string; }> = this.PRIMARY;
public isOpen: boolean = false;
@Input() colorType: ColorType = ColorType.PRIMARY; @Input() colorType: ColorType = ColorType.PRIMARY;
@Input() color: string = ''; @Input() color: string = '';
@@ -89,8 +91,7 @@ export class ColorComponent implements OnInit {
this.previewChanged.emit(this.previewColor); this.previewChanged.emit(this.previewColor);
} }
ngOnInit(): void { public ngOnInit(): void {
switch (this.colorType) { switch (this.colorType) {
case ColorType.PRIMARY: case ColorType.PRIMARY:
this.colors = this.PRIMARY; this.colors = this.PRIMARY;
@@ -115,4 +116,16 @@ export class ColorComponent implements OnInit {
break; break;
} }
} }
public changeComplete(event: ColorEvent): void {
this.emitPreview(event.color.hex);
}
public get previewColorCropped(): string {
let s = this.previewColor;
while (s.charAt(0) === '#') {
s = s.substring(1);
}
return s;
}
} }

View File

@@ -3,10 +3,8 @@
<div class="dashed" [ngClass]="{'dark': theme === Theme.DARK, 'light': theme === Theme.LIGHT}" [style.background]="theme == Theme.DARK ? policy.backgroundColorDark : policy.backgroundColor"> <div class="dashed" [ngClass]="{'dark': theme === Theme.DARK, 'light': theme === Theme.LIGHT}" [style.background]="theme == Theme.DARK ? policy.backgroundColorDark : policy.backgroundColor">
<div class="login-wrapper" [style.color]="theme == Theme.DARK ? policy.fontColorDark : policy.fontColor"> <div class="login-wrapper" [style.color]="theme == Theme.DARK ? policy.fontColorDark : policy.fontColor">
<img *ngIf="images['previewLogo'] && theme == Theme.LIGHT" [src]="images['previewLogo']" alt="logo-mock" /> <img *ngIf="images['previewLogo'] && theme == Theme.LIGHT" [src]="images['previewLogo']" alt="logo-mock" />
<img *ngIf="!images['previewLogo'] && theme == Theme.LIGHT" src="../../../../assets/images/zitadel-logo-dark.svg" alt="logo-mock" />
<img *ngIf="images['previewDarkLogo'] && theme == Theme.DARK" [src]="images['previewDarkLogo']" alt="logo-mock" /> <img *ngIf="images['previewDarkLogo'] && theme == Theme.DARK" [src]="images['previewDarkLogo']" alt="logo-mock" />
<img *ngIf="!images['previewDarkLogo'] && theme == Theme.DARK" src="../../../../assets/images/zitadel-logo-light.svg" alt="logo-mock" />
<h1>{{'POLICY.PRIVATELABELING.PREVIEW.TITLE' | translate}}</h1> <h1>{{'POLICY.PRIVATELABELING.PREVIEW.TITLE' | translate}}</h1>
<p class="desc-text">{{'POLICY.PRIVATELABELING.PREVIEW.SECOND' | translate}}</p> <p class="desc-text">{{'POLICY.PRIVATELABELING.PREVIEW.SECOND' | translate}}</p>

View File

@@ -15,13 +15,6 @@
<mat-spinner diameter="30" *ngIf="loading" color="primary"></mat-spinner> <mat-spinner diameter="30" *ngIf="loading" color="primary"></mat-spinner>
</div> </div>
<ng-template appHasRole [appHasRole]="['policy.delete']">
<button *ngIf="serviceType === PolicyComponentServiceType.MGMT && !isDefault"
matTooltip="{{'POLICY.RESET' | translate}}" color="warn" (click)="removePolicy()" mat-stroked-button>
{{'POLICY.RESET' | translate}}
</button>
</ng-template>
<div class="top-row"> <div class="top-row">
<div> <div>
<p>{{'POLICY.PRIVATELABELING.THEME' | translate}}</p> <p>{{'POLICY.PRIVATELABELING.THEME' | translate}}</p>
@@ -35,6 +28,15 @@
</div> </div>
</div> </div>
<span class="fill-space"></span>
<ng-template appHasRole [appHasRole]="['policy.delete']">
<button class="reset-button" *ngIf="serviceType === PolicyComponentServiceType.MGMT && !isDefault"
matTooltip="{{'POLICY.RESET' | translate}}" color="warn" (click)="removePolicy()" mat-stroked-button>
{{'POLICY.RESET' | translate}}
</button>
</ng-template>
<button class="activate-button" [disabled]="serviceType == PolicyComponentServiceType.MGMT && (['label_policy.private_label'] | hasFeature | async) == false" mat-raised-button color="primary" (click)="activatePolicy()"> <button class="activate-button" [disabled]="serviceType == PolicyComponentServiceType.MGMT && (['label_policy.private_label'] | hasFeature | async) == false" mat-raised-button color="primary" (click)="activatePolicy()">
{{'POLICY.PRIVATELABELING.ACTIVATEPREVIEW' | translate}} {{'POLICY.PRIVATELABELING.ACTIVATEPREVIEW' | translate}}
</button> </button>
@@ -64,14 +66,26 @@
<mat-spinner class="spinner" color="primary" diameter="25" *ngIf="loadingImages"></mat-spinner> <mat-spinner class="spinner" color="primary" diameter="25" *ngIf="loadingImages"></mat-spinner>
<container [ngSwitch]="theme"> <container [ngSwitch]="theme">
<div class="logo-view" *ngSwitchCase="Theme.DARK"> <div class="logo-view" *ngSwitchCase="Theme.DARK">
<img matTooltip="Preview" class="prev" *ngIf="images['previewDarkLogo']" [src]="images['previewDarkLogo']" alt="dark logo preview"/> <div class="img-wrapper" *ngIf="images['previewDarkLogo']" >
<mat-icon matTooltip="{{'ACTIONS.DELETE' | translate}}" color="warn" class="dl-btn" (click)="deleteAsset(AssetType.LOGO, theme)">remove_circle</mat-icon>
<img matTooltip="Preview" class="prev" [src]="images['previewDarkLogo']" alt="dark logo preview"/>
</div>
<span class="fill-space"></span> <span class="fill-space"></span>
<img matTooltip="Current" class="curr" *ngIf="images['darkLogo']" [src]="images['darkLogo']" alt="dark logo"/> <div class="img-wrapper" *ngIf="images['darkLogo']">
<!-- <mat-icon matTooltip="{{'ACTIONS.DELETE' | translate}}" color="warn" class="dl-btn" (click)="deleteAsset(AssetType.LOGO, theme, Preview.PREVIEW)">remove_circle</mat-icon> -->
<img matTooltip="Current" class="curr" [src]="images['darkLogo']" alt="dark logo"/>
</div>
</div> </div>
<div class="logo-view" *ngSwitchCase="Theme.LIGHT"> <div class="logo-view" *ngSwitchCase="Theme.LIGHT">
<img matTooltip="Preview" class="prev" *ngIf="images['previewLogo']" [src]="images['previewLogo']" alt="logo preview"/> <div class="img-wrapper" *ngIf="images['previewLogo']">
<mat-icon matTooltip="{{'ACTIONS.DELETE' | translate}}" color="warn" class="dl-btn" (click)="deleteAsset(AssetType.LOGO, theme)">remove_circle</mat-icon>
<img matTooltip="Preview" class="prev" [src]="images['previewLogo']" alt="logo preview"/>
</div>
<span class="fill-space"></span> <span class="fill-space"></span>
<img matTooltip="Current" class="curr" *ngIf="images['logo']" [src]="images['logo']" alt="logo"/> <div class="img-wrapper" *ngIf="images['logo']">
<!-- <mat-icon matTooltip="{{'ACTIONS.DELETE' | translate}}" color="warn" class="dl-btn" (click)="deleteAsset(AssetType.LOGO, theme, Preview.PREVIEW)">remove_circle</mat-icon> -->
<img matTooltip="Current" class="curr" [src]="images['logo']" alt="logo"/>
</div>
</div> </div>
</container> </container>
<div class="dropzone" cnslDropzone (hovered)="toggleHoverLogo(theme, $event)" <div class="dropzone" cnslDropzone (hovered)="toggleHoverLogo(theme, $event)"
@@ -92,14 +106,26 @@
<mat-spinner class="spinner" color="primary" diameter="25" *ngIf="loadingImages"></mat-spinner> <mat-spinner class="spinner" color="primary" diameter="25" *ngIf="loadingImages"></mat-spinner>
<container [ngSwitch]="theme"> <container [ngSwitch]="theme">
<div class="logo-view" *ngSwitchCase="Theme.DARK"> <div class="logo-view" *ngSwitchCase="Theme.DARK">
<img matTooltip="Preview" class="prev" *ngIf="images['previewDarkIcon']" [src]="images['previewDarkIcon']" alt="dark icon preview"/> <div class="img-wrapper" *ngIf="images['previewDarkIcon']">
<mat-icon matTooltip="{{'ACTIONS.DELETE' | translate}}" color="warn" class="dl-btn" (click)="deleteAsset(AssetType.ICON, theme)">remove_circle</mat-icon>
<img matTooltip="Preview" class="prev" [src]="images['previewDarkIcon']" alt="dark icon preview"/>
</div>
<span class="fill-space"></span> <span class="fill-space"></span>
<img matTooltip="Current" class="curr" *ngIf="images['darkIcon']" [src]="images['darkIcon']" alt="dark icon"/> <div class="img-wrapper" *ngIf="images['darkIcon']">
<!-- <mat-icon matTooltip="{{'ACTIONS.DELETE' | translate}}" color="warn" class="dl-btn" (click)="deleteAsset(AssetType.ICON,theme, Preview.CURRENT)">remove_circle</mat-icon> -->
<img matTooltip="Current" class="curr" [src]="images['darkIcon']" alt="dark icon"/>
</div>
</div> </div>
<div class="logo-view" *ngSwitchCase="Theme.LIGHT"> <div class="logo-view" *ngSwitchCase="Theme.LIGHT">
<img matTooltip="Preview" class="prev" *ngIf="images['previewIcon']" [src]="images['previewIcon']" alt="icon preview"/> <div class="img-wrapper" *ngIf="images['previewIcon']">
<mat-icon matTooltip="{{'ACTIONS.DELETE' | translate}}" color="warn" class="dl-btn" (click)="deleteAsset(AssetType.ICON,theme)">remove_circle</mat-icon>
<img matTooltip="Preview" class="prev" [src]="images['previewIcon']" alt="icon preview"/>
</div>
<span class="fill-space"></span> <span class="fill-space"></span>
<img matTooltip="Current" class="curr" *ngIf="images['icon']" [src]="images['icon']" alt="icon"/> <div class="img-wrapper" *ngIf="images['icon']" >
<!-- <mat-icon matTooltip="{{'ACTIONS.DELETE' | translate}}" color="warn" class="dl-btn" (click)="deleteAsset(AssetType.ICON,theme, Preview.CURRENT)">remove_circle</mat-icon> -->
<img matTooltip="Current" class="curr"[src]="images['icon']" alt="icon"/>
</div>
</div> </div>
</container> </container>
<div class="dropzone" cnslDropzone (hovered)="toggleHoverIcon(theme, $event)" <div class="dropzone" cnslDropzone (hovered)="toggleHoverIcon(theme, $event)"
@@ -132,19 +158,19 @@
<ng-container *ngIf="theme==Theme.DARK"> <ng-container *ngIf="theme==Theme.DARK">
<div class="colors" *ngIf="data && previewData"> <div class="colors" *ngIf="data && previewData">
<div class="color"> <div class="color">
<cnsl-color [colorType]="ColorType.BACKGROUNDDARK"(previewChanged)="previewData.backgroundColorDark = $event; savePolicy()" name="Background Color Dark" [color]="data.backgroundColorDark" [previewColor]="previewData.backgroundColorDark"></cnsl-color> <cnsl-color [colorType]="ColorType.BACKGROUNDDARK" (previewChanged)="previewData.backgroundColorDark = $event" name="Background Color Dark" [color]="data.backgroundColorDark" [previewColor]="previewData.backgroundColorDark"></cnsl-color>
</div> </div>
<div class="color"> <div class="color">
<cnsl-color [colorType]="ColorType.PRIMARY"(previewChanged)="previewData.primaryColorDark = $event; savePolicy()" name="Preview Primary Color Dark" [color]="data.primaryColorDark" [previewColor]="previewData.primaryColorDark"></cnsl-color> <cnsl-color [colorType]="ColorType.PRIMARY"(previewChanged)="previewData.primaryColorDark = $event" name="Preview Primary Color Dark" [color]="data.primaryColorDark" [previewColor]="previewData.primaryColorDark"></cnsl-color>
</div> </div>
<div class="color"> <div class="color">
<cnsl-color [colorType]="ColorType.WARN" (previewChanged)="previewData.warnColorDark = $event; savePolicy()" name="Preview Warn Color Dark" [color]="data.warnColorDark" [previewColor]="previewData.warnColorDark"></cnsl-color> <cnsl-color [colorType]="ColorType.WARN" (previewChanged)="previewData.warnColorDark = $event" name="Preview Warn Color Dark" [color]="data.warnColorDark" [previewColor]="previewData.warnColorDark"></cnsl-color>
</div> </div>
<div class="color"> <div class="color">
<cnsl-color [colorType]="ColorType.FONTDARK"(previewChanged)="previewData.fontColorDark = $event; savePolicy()" name="Font Color Dark" [color]="data.fontColorDark" [previewColor]="previewData.fontColorDark"></cnsl-color> <cnsl-color [colorType]="ColorType.FONTDARK"(previewChanged)="previewData.fontColorDark = $event" name="Font Color Dark" [color]="data.fontColorDark" [previewColor]="previewData.fontColorDark"></cnsl-color>
</div> </div>
</div> </div>
</ng-container> </ng-container>
@@ -152,22 +178,26 @@
<ng-container *ngIf="theme==Theme.LIGHT"> <ng-container *ngIf="theme==Theme.LIGHT">
<div class="colors" *ngIf="data && previewData"> <div class="colors" *ngIf="data && previewData">
<div class="color"> <div class="color">
<cnsl-color [colorType]="ColorType.BACKGROUNDLIGHT" (previewChanged)="previewData.backgroundColor = $event; savePolicy()" name="Background Color Light" [color]="data.backgroundColor" [previewColor]="previewData.backgroundColor"></cnsl-color> <cnsl-color [colorType]="ColorType.BACKGROUNDLIGHT" (previewChanged)="previewData.backgroundColor = $event" name="Background Color Light" [color]="data.backgroundColor" [previewColor]="previewData.backgroundColor"></cnsl-color>
</div> </div>
<div class="color"> <div class="color">
<cnsl-color [colorType]="ColorType.PRIMARY" (previewChanged)="previewData.primaryColor = $event; savePolicy()" name="Preview Primary Color Light" [color]="data.primaryColor" [previewColor]="previewData.primaryColor"></cnsl-color> <cnsl-color [colorType]="ColorType.PRIMARY" (previewChanged)="previewData.primaryColor = $event" name="Preview Primary Color Light" [color]="data.primaryColor" [previewColor]="previewData.primaryColor"></cnsl-color>
</div> </div>
<div class="color"> <div class="color">
<cnsl-color [colorType]="ColorType.WARN" name="Preview Warn Color Light" (previewChanged)="previewData.warnColor= $event; savePolicy()" [color]="data.warnColor" [previewColor]="previewData.warnColor"></cnsl-color> <cnsl-color [colorType]="ColorType.WARN" name="Preview Warn Color Light" (previewChanged)="previewData.warnColor= $event" [color]="data.warnColor" [previewColor]="previewData.warnColor"></cnsl-color>
</div> </div>
<div class="color"> <div class="color">
<cnsl-color [colorType]="ColorType.FONTLIGHT" (previewChanged)="previewData.fontColor = $event; savePolicy()" name="Font Color" [color]="data.fontColor" [previewColor]="previewData.fontColor"></cnsl-color> <cnsl-color [colorType]="ColorType.FONTLIGHT" (previewChanged)="previewData.fontColor = $event" name="Font Color" [color]="data.fontColor" [previewColor]="previewData.fontColor"></cnsl-color>
</div> </div>
</div> </div>
</ng-container> </ng-container>
<div class="clr-btn-wrapper">
<button color="primary" mat-raised-button (click)="savePolicy()">{{'ACTIONS.SAVE' | translate}}</button>
</div>
</mat-expansion-panel> </mat-expansion-panel>
<mat-expansion-panel class="expansion"> <mat-expansion-panel class="expansion">
@@ -179,11 +209,12 @@
</mat-panel-title> </mat-panel-title>
</mat-expansion-panel-header> </mat-expansion-panel-header>
<div class="fonts"> <div class="fonts">
<div class="font-preview"> <div class="font-preview mat-elevation-z3" *ngIf="preview == Preview.PREVIEW && previewData.fontUrl || preview == Preview.CURRENT && data.fontUrl">
<mat-icon>text_fields</mat-icon> <mat-icon class="icon">text_fields</mat-icon>
<span>ABC • abc • 123</span> <span>ABC • abc • 123</span>
<span>_</span>
<span>Upload your favorite font for the UI</span> <span class="fill-space"></span>
<button matTooltip="{{'ACTIONS.REMOVE' | translate}}" mat-icon-button color="warn" (click)="deleteFont()"><mat-icon>remove_circle</mat-icon></button>
</div> </div>
<div class="dropzone" cnslDropzone (hovered)="toggleHoverFont($event)" <div class="dropzone" cnslDropzone (hovered)="toggleHoverFont($event)"

View File

@@ -83,7 +83,15 @@
.top-row { .top-row {
display: flex; display: flex;
justify-content: space-between;
.fill-space {
flex: 1;
}
.reset-button {
align-self: flex-end;
margin-right: 1rem;
}
.activate-button { .activate-button {
border-radius: 50%; border-radius: 50%;
@@ -180,7 +188,7 @@
outline: none; outline: none;
height: 150px; height: 150px;
width: 100%; width: 100%;
border-radius: 16px; border-radius: .5rem;
background: if($is-dark-theme, #2d2e30, #fff); background: if($is-dark-theme, #2d2e30, #fff);
border: 1px solid if($is-dark-theme, #4a4b4b, #ddd); border: 1px solid if($is-dark-theme, #4a4b4b, #ddd);
display: flex; display: flex;
@@ -257,13 +265,41 @@
display: flex; display: flex;
margin-bottom: 1rem; margin-bottom: 1rem;
.img-wrapper {
flex: 1;
position: relative;
min-height: 80px;
border: 1px solid if($is-dark-theme, #ffffff20, #00000020);
border-radius: .5rem;
max-width: 120px;
.dl-btn {
position: absolute;
top: -12px;
left: -12px;
cursor: pointer;
visibility: hidden;
}
.prev, .prev,
.curr { .curr {
height: 50px; position: absolute;
top: 50%;
transform: translateY(-50%);
left: 0;
max-height: 80px;
max-width: 120px;
object-fit: contain; object-fit: contain;
border-radius: .5rem; border-radius: .5rem;
} }
&:hover {
.dl-btn {
visibility: visible;
}
}
}
.fill-space { .fill-space {
flex: 1; flex: 1;
} }
@@ -280,6 +316,12 @@
} }
} }
.clr-btn-wrapper {
width: 100%;
display: flex;
justify-content: flex-end;
}
.fonts { .fonts {
.title { .title {
display: block; display: block;
@@ -288,10 +330,19 @@
.font-preview { .font-preview {
display: flex; display: flex;
flex-direction: column;
align-items: center; align-items: center;
padding: 30px 50px; padding: .5rem;
text-align: center; text-align: center;
border-radius: .5rem;
margin-bottom: 1rem;
.icon {
margin-right: 1rem;
}
.fill-space {
flex: 1;
}
} }
.font-selector { .font-selector {
@@ -299,7 +350,6 @@
align-items: center; align-items: center;
flex-wrap: wrap; flex-wrap: wrap;
margin: 0 -.25rem; margin: 0 -.25rem;
width: 100%;
padding-bottom: 1rem; padding-bottom: 1rem;
.font { .font {

View File

@@ -17,9 +17,9 @@ import {
} from 'src/app/proto/generated/zitadel/management_pb'; } from 'src/app/proto/generated/zitadel/management_pb';
import { LabelPolicy } from 'src/app/proto/generated/zitadel/policy_pb'; import { LabelPolicy } from 'src/app/proto/generated/zitadel/policy_pb';
import { AdminService } from 'src/app/services/admin.service'; import { AdminService } from 'src/app/services/admin.service';
import { AssetEndpoint, AssetService, AssetType } from 'src/app/services/asset.service';
import { ManagementService } from 'src/app/services/mgmt.service'; import { ManagementService } from 'src/app/services/mgmt.service';
import { ToastService } from 'src/app/services/toast.service'; import { ToastService } from 'src/app/services/toast.service';
import { DownloadEndpoint, UploadEndpoint, UploadService } from 'src/app/services/upload.service';
import { CnslLinks } from '../../links/links.component'; import { CnslLinks } from '../../links/links.component';
import { IAM_COMPLEXITY_LINK, IAM_LOGIN_POLICY_LINK, IAM_POLICY_LINK } from '../../policy-grid/policy-links'; import { IAM_COMPLEXITY_LINK, IAM_LOGIN_POLICY_LINK, IAM_POLICY_LINK } from '../../policy-grid/policy-links';
@@ -82,6 +82,7 @@ export class PrivateLabelingPolicyComponent implements OnDestroy {
public Theme: any = Theme; public Theme: any = Theme;
public Preview: any = Preview; public Preview: any = Preview;
public ColorType: any = ColorType; public ColorType: any = ColorType;
public AssetType: any = AssetType;
public refreshPreview: EventEmitter<void> = new EventEmitter(); public refreshPreview: EventEmitter<void> = new EventEmitter();
public loadingImages: boolean = false; public loadingImages: boolean = false;
@@ -90,7 +91,7 @@ export class PrivateLabelingPolicyComponent implements OnDestroy {
private route: ActivatedRoute, private route: ActivatedRoute,
private toast: ToastService, private toast: ToastService,
private injector: Injector, private injector: Injector,
private uploadService: UploadService, private assetService: AssetService,
private sanitizer: DomSanitizer, private sanitizer: DomSanitizer,
) { ) {
this.sub = this.route.data.pipe(switchMap(data => { this.sub = this.route.data.pipe(switchMap(data => {
@@ -133,17 +134,17 @@ export class PrivateLabelingPolicyComponent implements OnDestroy {
if (theme === Theme.DARK) { if (theme === Theme.DARK) {
switch (this.serviceType) { switch (this.serviceType) {
case PolicyComponentServiceType.MGMT: case PolicyComponentServiceType.MGMT:
return this.handleUploadPromise(this.uploadService.upload(UploadEndpoint.MGMTDARKLOGO, formData)); return this.handleUploadPromise(this.assetService.upload(AssetEndpoint.MGMTDARKLOGO, formData));
case PolicyComponentServiceType.ADMIN: case PolicyComponentServiceType.ADMIN:
return this.handleUploadPromise(this.uploadService.upload(UploadEndpoint.IAMDARKLOGO, formData)); return this.handleUploadPromise(this.assetService.upload(AssetEndpoint.IAMDARKLOGO, formData));
} }
} }
if (theme === Theme.LIGHT) { if (theme === Theme.LIGHT) {
switch (this.serviceType) { switch (this.serviceType) {
case PolicyComponentServiceType.MGMT: case PolicyComponentServiceType.MGMT:
return this.handleUploadPromise(this.uploadService.upload(UploadEndpoint.MGMTLIGHTLOGO, formData)); return this.handleUploadPromise(this.assetService.upload(AssetEndpoint.MGMTLOGO, formData));
case PolicyComponentServiceType.ADMIN: case PolicyComponentServiceType.ADMIN:
return this.handleUploadPromise(this.uploadService.upload(UploadEndpoint.IAMLIGHTLOGO, formData)); return this.handleUploadPromise(this.assetService.upload(AssetEndpoint.IAMLOGO, formData));
} }
} }
@@ -157,13 +158,87 @@ export class PrivateLabelingPolicyComponent implements OnDestroy {
formData.append('file', file); formData.append('file', file);
switch (this.serviceType) { switch (this.serviceType) {
case PolicyComponentServiceType.MGMT: case PolicyComponentServiceType.MGMT:
return this.uploadService.upload(UploadEndpoint.MGMTFONT, formData); return this.handleFontUploadPromise(this.assetService.upload(AssetEndpoint.MGMTFONT, formData));
case PolicyComponentServiceType.ADMIN: case PolicyComponentServiceType.ADMIN:
return this.uploadService.upload(UploadEndpoint.IAMFONT, formData); return this.handleFontUploadPromise(this.assetService.upload(AssetEndpoint.IAMFONT, formData));
} }
} }
} }
public deleteFont(): Promise<any> {
const handler = (prom: Promise<any>) => prom.then(() => {
this.toast.showInfo('POLICY.TOAST.DELETESUCCESS', true);
setTimeout(() => {
this.loadingImages = true;
this.getPreviewData().then(data => {
if (data.policy) {
this.previewData = data.policy;
this.loadPreviewImages();
}
});
}, 1000);
}).catch(error => this.toast.showError(error));
switch (this.serviceType) {
case PolicyComponentServiceType.MGMT:
return handler((this.service as ManagementService).removeLabelPolicyFont());
case PolicyComponentServiceType.ADMIN:
return handler((this.service as AdminService).removeLabelPolicyFont());
}
}
public deleteAsset(type: AssetType, theme: Theme): any {
const previewHandler = (prom: Promise<any>) => {
return prom.then(() => {
this.toast.showInfo('POLICY.TOAST.DELETESUCCESS', true);
setTimeout(() => {
this.loadingImages = true;
this.getPreviewData().then(data => {
if (data.policy) {
this.previewData = data.policy;
this.loadPreviewImages();
}
});
}, 1000);
}).catch(error => this.toast.showError(error));
};
switch (this.serviceType) {
case PolicyComponentServiceType.ADMIN:
if (type === AssetType.LOGO) {
if (theme === Theme.DARK) {
return previewHandler(this.service.removeLabelPolicyLogoDark());
} else if (theme === Theme.LIGHT) {
return previewHandler(this.service.removeLabelPolicyLogo());
}
} else if (type === AssetType.ICON) {
if (theme === Theme.DARK) {
return previewHandler(this.service.removeLabelPolicyIconDark());
} else if (theme === Theme.LIGHT) {
return previewHandler(this.service.removeLabelPolicyIcon());
}
}
break;
case PolicyComponentServiceType.MGMT:
if (type === AssetType.LOGO) {
if (theme === Theme.DARK) {
return previewHandler(this.service.removeLabelPolicyLogoDark());
} else if (theme === Theme.LIGHT) {
return previewHandler(this.service.removeLabelPolicyLogo());
}
} else if (type === AssetType.ICON) {
if (theme === Theme.DARK) {
return previewHandler(this.service.removeLabelPolicyIconDark());
} else if (theme === Theme.LIGHT) {
return previewHandler(this.service.removeLabelPolicyIcon());
}
}
break;
}
}
public toggleHoverIcon(theme: Theme, isHovering: boolean): void { public toggleHoverIcon(theme: Theme, isHovering: boolean): void {
if (theme === Theme.DARK) { if (theme === Theme.DARK) {
this.isHoveringOverDarkIcon = isHovering; this.isHoveringOverDarkIcon = isHovering;
@@ -182,26 +257,39 @@ export class PrivateLabelingPolicyComponent implements OnDestroy {
if (theme === Theme.DARK) { if (theme === Theme.DARK) {
switch (this.serviceType) { switch (this.serviceType) {
case PolicyComponentServiceType.MGMT: case PolicyComponentServiceType.MGMT:
this.handleUploadPromise(this.uploadService.upload(UploadEndpoint.MGMTDARKICON, formData)); this.handleUploadPromise(this.assetService.upload(AssetEndpoint.MGMTDARKICON, formData));
break; break;
case PolicyComponentServiceType.ADMIN: case PolicyComponentServiceType.ADMIN:
this.handleUploadPromise(this.uploadService.upload(UploadEndpoint.IAMDARKICON, formData)); this.handleUploadPromise(this.assetService.upload(AssetEndpoint.IAMDARKICON, formData));
break; break;
} }
} }
if (theme === Theme.LIGHT) { if (theme === Theme.LIGHT) {
switch (this.serviceType) { switch (this.serviceType) {
case PolicyComponentServiceType.MGMT: case PolicyComponentServiceType.MGMT:
this.handleUploadPromise(this.uploadService.upload(UploadEndpoint.MGMTLIGHTICON, formData)); this.handleUploadPromise(this.assetService.upload(AssetEndpoint.MGMTICON, formData));
break; break;
case PolicyComponentServiceType.ADMIN: case PolicyComponentServiceType.ADMIN:
this.handleUploadPromise(this.uploadService.upload(UploadEndpoint.IAMLIGHTICON, formData)); this.handleUploadPromise(this.assetService.upload(AssetEndpoint.IAMICON, formData));
break; break;
} }
} }
} }
} }
private handleFontUploadPromise(task: Promise<any>): Promise<any> {
return task.then(() => {
this.toast.showInfo('POLICY.TOAST.UPLOADSUCCESS', true);
setTimeout(() => {
this.getPreviewData().then(data => {
if (data.policy) {
this.previewData = data.policy;
}
});
}, 1000);
}).catch(error => this.toast.showError(error));
}
private handleUploadPromise(task: Promise<any>): Promise<any> { private handleUploadPromise(task: Promise<any>): Promise<any> {
return task.then(() => { return task.then(() => {
this.toast.showInfo('POLICY.TOAST.UPLOADSUCCESS', true); this.toast.showInfo('POLICY.TOAST.UPLOADSUCCESS', true);
@@ -250,63 +338,86 @@ export class PrivateLabelingPolicyComponent implements OnDestroy {
} }
private loadImages(): void { private loadImages(): void {
const promises: Promise<any>[] = [];
if (this.serviceType === PolicyComponentServiceType.ADMIN) { if (this.serviceType === PolicyComponentServiceType.ADMIN) {
if (this.data.logoUrlDark) { if (this.data.logoUrlDark) {
this.loadAsset('darkLogo', DownloadEndpoint.IAMDARKLOGOPREVIEW); promises.push(this.loadAsset('darkLogo', AssetEndpoint.IAMDARKLOGO));
} }
if (this.data.iconUrlDark) { if (this.data.iconUrlDark) {
this.loadAsset('darkIcon', DownloadEndpoint.IAMDARKICONPREVIEW); promises.push(this.loadAsset('darkIcon', AssetEndpoint.IAMDARKICON));
} }
if (this.data.logoUrl) { if (this.data.logoUrl) {
this.loadAsset('logo', DownloadEndpoint.IAMLOGOPREVIEW); promises.push(this.loadAsset('logo', AssetEndpoint.IAMLOGO));
} }
if (this.data.iconUrl) { if (this.data.iconUrl) {
this.loadAsset('icon', DownloadEndpoint.IAMICONPREVIEW); promises.push(this.loadAsset('icon', AssetEndpoint.IAMICON));
} }
} else if (this.serviceType === PolicyComponentServiceType.MGMT) { } else if (this.serviceType === PolicyComponentServiceType.MGMT) {
if (this.data.logoUrlDark) { if (this.data.logoUrlDark) {
this.loadAsset('darkLogo', DownloadEndpoint.MGMTDARKLOGOPREVIEW); promises.push(this.loadAsset('darkLogo', AssetEndpoint.MGMTDARKLOGO));
} }
if (this.data.iconUrlDark) { if (this.data.iconUrlDark) {
this.loadAsset('darkIcon', DownloadEndpoint.MGMTDARKICONPREVIEW); promises.push(this.loadAsset('darkIcon', AssetEndpoint.MGMTDARKICON));
} }
if (this.data.logoUrl) { if (this.data.logoUrl) {
this.loadAsset('logo', DownloadEndpoint.MGMTLOGOPREVIEW); promises.push(this.loadAsset('logo', AssetEndpoint.MGMTLOGO));
} }
if (this.data.iconUrl) { if (this.data.iconUrl) {
this.loadAsset('icon', DownloadEndpoint.MGMTICONPREVIEW); promises.push(this.loadAsset('icon', AssetEndpoint.MGMTICON));
} }
} }
if (promises.length) {
Promise.all(promises).then(() => {
this.loadingImages = false;
}).catch(error => {
this.loadingImages = false;
});
} else {
this.loadingImages = false;
}
} }
private loadPreviewImages(): void { private loadPreviewImages(): void {
const promises: Promise<any>[] = [];
if (this.serviceType === PolicyComponentServiceType.ADMIN) { if (this.serviceType === PolicyComponentServiceType.ADMIN) {
if (this.previewData.logoUrlDark) { if (this.previewData.logoUrlDark) {
this.loadAsset('previewDarkLogo', DownloadEndpoint.IAMDARKLOGOPREVIEW); promises.push(this.loadAsset('previewDarkLogo', AssetEndpoint.IAMDARKLOGOPREVIEW));
} }
if (this.previewData.iconUrlDark) { if (this.previewData.iconUrlDark) {
this.loadAsset('previewDarkIcon', DownloadEndpoint.IAMDARKICONPREVIEW); promises.push(this.loadAsset('previewDarkIcon', AssetEndpoint.IAMDARKICONPREVIEW));
} }
if (this.previewData.logoUrl) { if (this.previewData.logoUrl) {
this.loadAsset('previewLogo', DownloadEndpoint.IAMLOGOPREVIEW); promises.push(this.loadAsset('previewLogo', AssetEndpoint.IAMLOGOPREVIEW));
} }
if (this.previewData.iconUrl) { if (this.previewData.iconUrl) {
this.loadAsset('previewIcon', DownloadEndpoint.IAMICONPREVIEW); promises.push(this.loadAsset('previewIcon', AssetEndpoint.IAMICONPREVIEW));
} }
} else if (this.serviceType === PolicyComponentServiceType.MGMT) { } else if (this.serviceType === PolicyComponentServiceType.MGMT) {
if (this.previewData.logoUrlDark) { if (this.previewData.logoUrlDark) {
this.loadAsset('previewDarkLogo', DownloadEndpoint.MGMTDARKLOGOPREVIEW); promises.push(this.loadAsset('previewDarkLogo', AssetEndpoint.MGMTDARKLOGOPREVIEW));
} }
if (this.previewData.iconUrlDark) { if (this.previewData.iconUrlDark) {
this.loadAsset('previewDarkIcon', DownloadEndpoint.MGMTDARKICONPREVIEW); promises.push(this.loadAsset('previewDarkIcon', AssetEndpoint.MGMTDARKICONPREVIEW));
} }
if (this.previewData.logoUrl) { if (this.previewData.logoUrl) {
this.loadAsset('previewLogo', DownloadEndpoint.MGMTLOGOPREVIEW); promises.push(this.loadAsset('previewLogo', AssetEndpoint.MGMTLOGOPREVIEW));
} }
if (this.previewData.iconUrl) { if (this.previewData.iconUrl) {
this.loadAsset('previewIcon', DownloadEndpoint.MGMTICONPREVIEW); promises.push(this.loadAsset('previewIcon', AssetEndpoint.MGMTICONPREVIEW));
} }
} }
if (promises.length) {
Promise.all(promises).then(() => {
this.loadingImages = false;
}).catch(error => {
this.loadingImages = false;
});
} else {
this.loadingImages = false;
}
} }
public ngOnDestroy(): void { public ngOnDestroy(): void {
@@ -340,14 +451,12 @@ export class PrivateLabelingPolicyComponent implements OnDestroy {
} }
private loadAsset(imagekey: string, url: string): Promise<any> { private loadAsset(imagekey: string, url: string): Promise<any> {
return this.uploadService.load(`${url}`).then(data => { return this.assetService.load(`${url}`).then(data => {
const objectURL = URL.createObjectURL(data); const objectURL = URL.createObjectURL(data);
this.images[imagekey] = this.sanitizer.bypassSecurityTrustUrl(objectURL); this.images[imagekey] = this.sanitizer.bypassSecurityTrustUrl(objectURL);
this.refreshPreview.emit(); this.refreshPreview.emit();
this.loadingImages = false;
}).catch(error => { }).catch(error => {
this.toast.showError(error); this.toast.showError(error);
this.loadingImages = false;
}); });
} }

View File

@@ -1,3 +1,4 @@
import { OverlayModule } from '@angular/cdk/overlay';
import { CommonModule } from '@angular/common'; import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core'; import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms'; import { FormsModule } from '@angular/forms';
@@ -8,6 +9,7 @@ import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
import { MatSlideToggleModule } from '@angular/material/slide-toggle'; import { MatSlideToggleModule } from '@angular/material/slide-toggle';
import { MatTooltipModule } from '@angular/material/tooltip'; import { MatTooltipModule } from '@angular/material/tooltip';
import { TranslateModule } from '@ngx-translate/core'; import { TranslateModule } from '@ngx-translate/core';
import { ColorChromeModule } from 'ngx-color/chrome';
import { HasRoleModule } from 'src/app/directives/has-role/has-role.module'; import { HasRoleModule } from 'src/app/directives/has-role/has-role.module';
import { HasFeaturePipeModule } from 'src/app/pipes/has-feature-pipe/has-feature-pipe.module'; import { HasFeaturePipeModule } from 'src/app/pipes/has-feature-pipe/has-feature-pipe.module';
@@ -28,12 +30,14 @@ import { PrivateLabelingPolicyComponent } from './private-labeling-policy.compon
ColorComponent, ColorComponent,
], ],
imports: [ imports: [
ColorChromeModule,
PrivateLabelingPolicyRoutingModule, PrivateLabelingPolicyRoutingModule,
CommonModule, CommonModule,
FormsModule, FormsModule,
InputModule, InputModule,
MatButtonModule, MatButtonModule,
MatSlideToggleModule, MatSlideToggleModule,
OverlayModule,
MatIconModule, MatIconModule,
HasRoleModule, HasRoleModule,
MatTooltipModule, MatTooltipModule,

View File

@@ -102,7 +102,7 @@ export class SearchProjectAutocompleteComponent implements OnDestroy {
public add(event: MatChipInputEvent): void { public add(event: MatChipInputEvent): void {
if (!this.matAutocomplete.isOpen) { if (!this.matAutocomplete.isOpen) {
const input = event.input; const input = event.chipInput?.inputElement;
const value = event.value; const value = event.value;
if ((value || '').trim()) { if ((value || '').trim()) {

View File

@@ -68,7 +68,7 @@ export class SearchRolesAutocompleteComponent implements OnDestroy {
public add(event: MatChipInputEvent): void { public add(event: MatChipInputEvent): void {
if (!this.matAutocomplete.isOpen) { if (!this.matAutocomplete.isOpen) {
const input = event.input; const input = event.chipInput?.inputElement;
const value = event.value; const value = event.value;
if ((value || '').trim()) { if ((value || '').trim()) {

View File

@@ -101,7 +101,7 @@ export class SearchUserAutocompleteComponent implements OnInit, AfterContentChec
public add(event: MatChipInputEvent): void { public add(event: MatChipInputEvent): void {
if (!this.matAutocomplete.isOpen) { if (!this.matAutocomplete.isOpen) {
const input = event.input; const input = event.chipInput?.inputElement;
const value = event.value; const value = event.value;
if ((value || '').trim()) { if ((value || '').trim()) {

View File

@@ -1,6 +1,8 @@
import { HttpClient } from '@angular/common/http'; import { HttpClient } from '@angular/common/http';
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { PolicyComponentServiceType } from '../modules/policies/policy-component-types.enum';
import { Theme } from '../modules/policies/private-labeling-policy/private-labeling-policy.component';
import { Org } from '../proto/generated/zitadel/org_pb'; import { Org } from '../proto/generated/zitadel/org_pb';
import { StorageService } from './storage.service'; import { StorageService } from './storage.service';
@@ -11,22 +13,12 @@ const orgKey = 'x-zitadel-orgid';
const bearerPrefix = 'Bearer'; const bearerPrefix = 'Bearer';
const accessTokenStorageKey = 'access_token'; const accessTokenStorageKey = 'access_token';
export enum UploadEndpoint { export enum AssetType {
IAMFONT = 'iam/policy/label/font', LOGO,
MGMTFONT = 'org/policy/label/font', ICON,
IAMDARKLOGO = 'iam/policy/label/logo/dark',
IAMLIGHTLOGO = 'iam/policy/label/logo',
IAMDARKICON = 'iam/policy/label/icon/dark',
IAMLIGHTICON = 'iam/policy/label/icon',
MGMTDARKLOGO = 'org/policy/label/logo/dark',
MGMTLIGHTLOGO = 'org/policy/label/logo',
MGMTDARKICON = 'org/policy/label/icon/dark',
MGMTLIGHTICON = 'org/policy/label/icon',
} }
export enum DownloadEndpoint { export enum AssetEndpoint {
IAMFONT = 'iam/policy/label/font', IAMFONT = 'iam/policy/label/font',
MGMTFONT = 'org/policy/label/font', MGMTFONT = 'org/policy/label/font',
@@ -51,10 +43,33 @@ export enum DownloadEndpoint {
MGMTICONPREVIEW = 'org/policy/label/icon/_preview', MGMTICONPREVIEW = 'org/policy/label/icon/_preview',
} }
export const ENDPOINT = {
[Theme.DARK]: {
[PolicyComponentServiceType.ADMIN]: {
[AssetType.LOGO]: AssetEndpoint.IAMDARKLOGO,
[AssetType.ICON]: AssetEndpoint.IAMDARKICON,
},
[PolicyComponentServiceType.MGMT]: {
[AssetType.LOGO]: AssetEndpoint.MGMTDARKLOGO,
[AssetType.ICON]: AssetEndpoint.MGMTDARKICON,
},
},
[Theme.LIGHT]: {
[PolicyComponentServiceType.ADMIN]: {
[AssetType.LOGO]: AssetEndpoint.IAMLOGO,
[AssetType.ICON]: AssetEndpoint.IAMICON,
},
[PolicyComponentServiceType.MGMT]: {
[AssetType.LOGO]: AssetEndpoint.MGMTLOGO,
[AssetType.ICON]: AssetEndpoint.MGMTICON,
},
},
};
@Injectable({ @Injectable({
providedIn: 'root', providedIn: 'root',
}) })
export class UploadService { export class AssetService {
private serviceUrl: string = ''; private serviceUrl: string = '';
private accessToken: string = ''; private accessToken: string = '';
private org!: Org.AsObject; private org!: Org.AsObject;
@@ -81,7 +96,7 @@ export class UploadService {
}); });
} }
public upload(endpoint: UploadEndpoint, body: any): Promise<any> { public upload(endpoint: AssetEndpoint, body: any): Promise<any> {
return this.http.post(`${this.serviceUrl}/assets/v1/${endpoint}`, return this.http.post(`${this.serviceUrl}/assets/v1/${endpoint}`,
body, body,
{ {
@@ -103,4 +118,14 @@ export class UploadService {
}, },
}).toPromise(); }).toPromise();
} }
public delete(endpoint: AssetEndpoint): Promise<any> {
return this.http.delete(`${this.serviceUrl}/assets/v1/${endpoint}`,
{
headers: {
[authorizationKey]: `${bearerPrefix} ${this.accessToken}`,
[orgKey]: `${this.org.id}`,
},
}).toPromise();
}
} }

View File

@@ -12,6 +12,8 @@ import {
RemoveLabelPolicyIconResponse, RemoveLabelPolicyIconResponse,
RemoveLabelPolicyLogoDarkRequest, RemoveLabelPolicyLogoDarkRequest,
RemoveLabelPolicyLogoDarkResponse, RemoveLabelPolicyLogoDarkResponse,
RemoveLabelPolicyLogoRequest,
RemoveLabelPolicyLogoResponse,
} from '../proto/generated/zitadel/admin_pb'; } from '../proto/generated/zitadel/admin_pb';
import { AppQuery } from '../proto/generated/zitadel/app_pb'; import { AppQuery } from '../proto/generated/zitadel/app_pb';
import { KeyType } from '../proto/generated/zitadel/auth_n_key_pb'; import { KeyType } from '../proto/generated/zitadel/auth_n_key_pb';
@@ -212,8 +214,6 @@ import {
RemoveAppKeyResponse, RemoveAppKeyResponse,
RemoveAppRequest, RemoveAppRequest,
RemoveAppResponse, RemoveAppResponse,
RemoveCustomLabelPolicyLogoRequest,
RemoveCustomLabelPolicyLogoResponse,
RemoveHumanAuthFactorOTPRequest, RemoveHumanAuthFactorOTPRequest,
RemoveHumanAuthFactorOTPResponse, RemoveHumanAuthFactorOTPResponse,
RemoveHumanAuthFactorU2FRequest, RemoveHumanAuthFactorU2FRequest,
@@ -783,31 +783,31 @@ export class ManagementService {
public removeLabelPolicyFont(): public removeLabelPolicyFont():
Promise<RemoveLabelPolicyFontResponse.AsObject> { Promise<RemoveLabelPolicyFontResponse.AsObject> {
const req = new RemoveLabelPolicyFontRequest(); const req = new RemoveLabelPolicyFontRequest();
return this.grpcService.admin.removeLabelPolicyFont(req, null).then(resp => resp.toObject()); return this.grpcService.mgmt.removeCustomLabelPolicyFont(req, null).then(resp => resp.toObject());
} }
public removeLabelPolicyIcon(): public removeLabelPolicyIcon():
Promise<RemoveLabelPolicyIconResponse.AsObject> { Promise<RemoveLabelPolicyIconResponse.AsObject> {
const req = new RemoveLabelPolicyIconRequest(); const req = new RemoveLabelPolicyIconRequest();
return this.grpcService.admin.removeLabelPolicyIcon(req, null).then(resp => resp.toObject()); return this.grpcService.mgmt.removeCustomLabelPolicyIcon(req, null).then(resp => resp.toObject());
} }
public removeLabelPolicyIconDark(): public removeLabelPolicyIconDark():
Promise<RemoveLabelPolicyIconDarkResponse.AsObject> { Promise<RemoveLabelPolicyIconDarkResponse.AsObject> {
const req = new RemoveLabelPolicyIconDarkRequest(); const req = new RemoveLabelPolicyIconDarkRequest();
return this.grpcService.admin.removeLabelPolicyIconDark(req, null).then(resp => resp.toObject()); return this.grpcService.mgmt.removeCustomLabelPolicyIconDark(req, null).then(resp => resp.toObject());
} }
public removeCustomLabelPolicyLogo(): public removeLabelPolicyLogo():
Promise<RemoveCustomLabelPolicyLogoResponse.AsObject> { Promise<RemoveLabelPolicyLogoResponse.AsObject> {
const req = new RemoveCustomLabelPolicyLogoRequest(); const req = new RemoveLabelPolicyLogoRequest();
return this.grpcService.mgmt.removeCustomLabelPolicyLogo(req, null).then(resp => resp.toObject()); return this.grpcService.mgmt.removeCustomLabelPolicyLogo(req, null).then(resp => resp.toObject());
} }
public removeLabelPolicyLogoDark(): public removeLabelPolicyLogoDark():
Promise<RemoveLabelPolicyLogoDarkResponse.AsObject> { Promise<RemoveLabelPolicyLogoDarkResponse.AsObject> {
const req = new RemoveLabelPolicyLogoDarkRequest(); const req = new RemoveLabelPolicyLogoDarkRequest();
return this.grpcService.admin.removeLabelPolicyLogoDark(req, null).then(resp => resp.toObject()); return this.grpcService.mgmt.removeCustomLabelPolicyLogoDark(req, null).then(resp => resp.toObject());
} }
public getOrgIAMPolicy(): Promise<GetOrgIAMPolicyResponse.AsObject> { public getOrgIAMPolicy(): Promise<GetOrgIAMPolicyResponse.AsObject> {

View File

@@ -1,9 +1,9 @@
{ {
"authServiceUrl": "https://api.zitadel.io", "authServiceUrl": "https://api.zitadel.dev",
"mgmtServiceUrl": "https://api.zitadel.io", "mgmtServiceUrl": "https://api.zitadel.dev",
"adminServiceUrl":"https://api.zitadel.io", "adminServiceUrl":"https://api.zitadel.dev",
"subscriptionServiceUrl":"https://sub.zitadel.io", "subscriptionServiceUrl":"https://sub.zitadel.dev",
"uploadServiceUrl":"https://api.zitadel.io", "uploadServiceUrl":"https://api.zitadel.dev",
"issuer": "https://issuer.zitadel.io", "issuer": "https://issuer.zitadel.dev",
"clientid": "69234247558357051@zitadel" "clientid": "70669160379706195@zitadel"
} }

View File

@@ -718,6 +718,7 @@
"SET": "Richtline erfolgreich gesetzt!", "SET": "Richtline erfolgreich gesetzt!",
"RESETSUCCESS": "Richtline zurückgesetzt!", "RESETSUCCESS": "Richtline zurückgesetzt!",
"UPLOADSUCCESS": "Upload erfolgreich", "UPLOADSUCCESS": "Upload erfolgreich",
"DELETESUCCESS": "Löschen erfolgreich",
"UPLOADFAILED":"Upload fehlgeschlagen!" "UPLOADFAILED":"Upload fehlgeschlagen!"
} }
}, },

View File

@@ -720,6 +720,7 @@
"SET": "Policy set successfully!", "SET": "Policy set successfully!",
"RESETSUCCESS": "Policy reset successfully!", "RESETSUCCESS": "Policy reset successfully!",
"UPLOADSUCCESS": "Uploaded successfully!", "UPLOADSUCCESS": "Uploaded successfully!",
"DELETESUCCESS": "Deleted successfully!",
"UPLOADFAILED":"Upload failed!" "UPLOADFAILED":"Upload failed!"
} }
}, },

View File

@@ -14,7 +14,7 @@ $lgn-stroked-button-border-width: 1px;
$lgn-icon-button-size: 40px !default; $lgn-icon-button-size: 40px !default;
$lgn-icon-button-border-radius: 50% !default; $lgn-icon-button-border-radius: 50% !default;
$lgn-icon-button-line-height: 24px !default; $lgn-icon-button-line-height: 40px !default;
// adds base styles to all button types. // adds base styles to all button types.
@mixin lgn-button-base { @mixin lgn-button-base {

View File

@@ -100,7 +100,8 @@ $lgn-container-bottom-margin: 50px;
.lgn-left-action { .lgn-left-action {
position: absolute; position: absolute;
left: 1rem; left: 1rem;
top: -40px; top: -75px;
transform: translateY(-50%);
} }
.lgn-register-options { .lgn-register-options {

View File

@@ -30,8 +30,6 @@ footer {
.watermark { .watermark {
display: flex; display: flex;
position: relative;
width: 100%;
.powered { .powered {
font-size: 12px; font-size: 12px;
@@ -39,9 +37,9 @@ footer {
} }
.lgn-logo-watermark { .lgn-logo-watermark {
height: 34px; height: 40px;
width: 125px; min-width: 125px;
margin: 2px; margin: auto 2px;
} }
} }

View File

@@ -18,8 +18,8 @@
} }
.lgn-logo-watermark { .lgn-logo-watermark {
background: var(--zitadel-logo-powered-by); background: var(--zitadel-logo-powered-by) no-repeat;
background-position: auto; background-position: center;
background-size: contain; background-size: contain;
} }
} }

View File

@@ -1,16 +1,22 @@
$lgn-header-padding: 0; $lgn-header-padding: 0;
$lgn-header-margin: 1rem auto .5rem auto; $lgn-header-margin: auto;
.lgn-header { .lgn-header {
display: block; display: flex;
position: relative; position: relative;
margin: $lgn-header-margin; margin: $lgn-header-margin;
padding: $lgn-header-padding; padding: $lgn-header-padding;
width: 100%; max-width: 250px;
min-height: 150px;
align-items: center;
justify-content: center;
.lgn-logo { .lgn-logo {
display: block; display: block;
max-height: 100px; height: 100px;
margin: 0 auto; margin: 0 auto;
max-width: 250px;
max-height: 150px;
object-fit: contain;
} }
} }

View File

@@ -91,6 +91,7 @@
--zitadel-color-raised-button-background: var(--zitadel-color-white); --zitadel-color-raised-button-background: var(--zitadel-color-white);
--zitadel-color-white: #ffffff;
--zitadel-color-black: #000000; --zitadel-color-black: #000000;
--zitadel-color-grey-50: #fafafa; --zitadel-color-grey-50: #fafafa;
--zitadel-color-grey-100: #f5f5f5; --zitadel-color-grey-100: #f5f5f5;
@@ -109,8 +110,8 @@
--zitadel-color-google-text: #8b8d8d; --zitadel-color-google-text: #8b8d8d;
--zitadel-color-google-background: #ffffff; --zitadel-color-google-background: #ffffff;
--zitadel-color-qr: var(--zitadel-color-white); --zitadel-color-qr: var(--zitadel-color-black);
--zitadel-color-qr-background: var(--zitadel-color-black); --zitadel-color-qr-background: var(--zitadel-color-white);
} }
.lgn-dark-theme { .lgn-dark-theme {

View File

@@ -77,6 +77,7 @@
--zitadel-color-button-selected-background: var(--zitadel-color-grey-900); --zitadel-color-button-selected-background: var(--zitadel-color-grey-900);
--zitadel-color-button-disabled-selected-background: var(--zitadel-color-grey-800); --zitadel-color-button-disabled-selected-background: var(--zitadel-color-grey-800);
--zitadel-color-raised-button-background: var(--zitadel-color-white); --zitadel-color-raised-button-background: var(--zitadel-color-white);
--zitadel-color-white: #ffffff;
--zitadel-color-black: #000000; --zitadel-color-black: #000000;
--zitadel-color-grey-50: #fafafa; --zitadel-color-grey-50: #fafafa;
--zitadel-color-grey-100: #f5f5f5; --zitadel-color-grey-100: #f5f5f5;
@@ -92,8 +93,8 @@
--zitadel-logo-powered-by: url("../logo-dark.svg"); --zitadel-logo-powered-by: url("../logo-dark.svg");
--zitadel-color-google-text: #8b8d8d; --zitadel-color-google-text: #8b8d8d;
--zitadel-color-google-background: #ffffff; --zitadel-color-google-background: #ffffff;
--zitadel-color-qr: var(--zitadel-color-white); --zitadel-color-qr: var(--zitadel-color-black);
--zitadel-color-qr-background: var(--zitadel-color-black); --zitadel-color-qr-background: var(--zitadel-color-white);
} }
.lgn-dark-theme { .lgn-dark-theme {
@@ -221,30 +222,34 @@ footer a {
} }
footer .watermark { footer .watermark {
display: flex; display: flex;
position: relative;
width: 100%;
} }
footer .watermark .powered { footer .watermark .powered {
font-size: 12px; font-size: 12px;
margin: auto 2px; margin: auto 2px;
} }
footer .watermark .lgn-logo-watermark { footer .watermark .lgn-logo-watermark {
height: 34px; height: 40px;
width: 125px; min-width: 125px;
margin: 2px; margin: auto 2px;
} }
.lgn-header { .lgn-header {
display: block; display: flex;
position: relative; position: relative;
margin: 1rem auto 0.5rem auto; margin: auto;
padding: 0; padding: 0;
width: 100%; max-width: 250px;
min-height: 150px;
align-items: center;
justify-content: center;
} }
.lgn-header .lgn-logo { .lgn-header .lgn-logo {
display: block; display: block;
max-height: 100px; height: 100px;
margin: 0 auto; margin: 0 auto;
max-width: 250px;
max-height: 150px;
object-fit: contain;
} }
.lgn-button, .lgn-stroked-button, .lgn-icon-button { .lgn-button, .lgn-stroked-button, .lgn-icon-button {
@@ -310,7 +315,7 @@ footer .watermark .lgn-logo-watermark {
border-radius: 50%; border-radius: 50%;
} }
.lgn-icon-button i, .lgn-icon-button .mat-icon { .lgn-icon-button i, .lgn-icon-button .mat-icon {
line-height: 24px; line-height: 40px;
} }
.lgn-stroked-button { .lgn-stroked-button {
@@ -621,7 +626,8 @@ a.block {
.content-container .lgn-left-action { .content-container .lgn-left-action {
position: absolute; position: absolute;
left: 1rem; left: 1rem;
top: -40px; top: -75px;
transform: translateY(-50%);
} }
.content-container .lgn-register-options { .content-container .lgn-register-options {
display: flex; display: flex;
@@ -1105,30 +1111,34 @@ footer a {
} }
footer .watermark { footer .watermark {
display: flex; display: flex;
position: relative;
width: 100%;
} }
footer .watermark .powered { footer .watermark .powered {
font-size: 12px; font-size: 12px;
margin: auto 2px; margin: auto 2px;
} }
footer .watermark .lgn-logo-watermark { footer .watermark .lgn-logo-watermark {
height: 34px; height: 40px;
width: 125px; min-width: 125px;
margin: 2px; margin: auto 2px;
} }
.lgn-header { .lgn-header {
display: block; display: flex;
position: relative; position: relative;
margin: 1rem auto 0.5rem auto; margin: auto;
padding: 0; padding: 0;
width: 100%; max-width: 250px;
min-height: 150px;
align-items: center;
justify-content: center;
} }
.lgn-header .lgn-logo { .lgn-header .lgn-logo {
display: block; display: block;
max-height: 100px; height: 100px;
margin: 0 auto; margin: 0 auto;
max-width: 250px;
max-height: 150px;
object-fit: contain;
} }
.lgn-button, .lgn-stroked-button, .lgn-icon-button { .lgn-button, .lgn-stroked-button, .lgn-icon-button {
@@ -1194,7 +1204,7 @@ footer .watermark .lgn-logo-watermark {
border-radius: 50%; border-radius: 50%;
} }
.lgn-icon-button i, .lgn-icon-button .mat-icon { .lgn-icon-button i, .lgn-icon-button .mat-icon {
line-height: 24px; line-height: 40px;
} }
.lgn-stroked-button { .lgn-stroked-button {
@@ -1505,7 +1515,8 @@ a.block {
.content-container .lgn-left-action { .content-container .lgn-left-action {
position: absolute; position: absolute;
left: 1rem; left: 1rem;
top: -40px; top: -75px;
transform: translateY(-50%);
} }
.content-container .lgn-register-options { .content-container .lgn-register-options {
display: flex; display: flex;
@@ -2307,7 +2318,8 @@ i {
.content-container .lgn-left-action { .content-container .lgn-left-action {
position: absolute; position: absolute;
left: 1rem; left: 1rem;
top: -40px; top: -75px;
transform: translateY(-50%);
} }
.content-container .lgn-register-options { .content-container .lgn-register-options {
display: flex; display: flex;
@@ -2342,16 +2354,22 @@ i {
} }
.lgn-header { .lgn-header {
display: block; display: flex;
position: relative; position: relative;
margin: 1rem auto 0.5rem auto; margin: auto;
padding: 0; padding: 0;
width: 100%; max-width: 250px;
min-height: 150px;
align-items: center;
justify-content: center;
} }
.lgn-header .lgn-logo { .lgn-header .lgn-logo {
display: block; display: block;
max-height: 100px; height: 100px;
margin: 0 auto; margin: 0 auto;
max-width: 250px;
max-height: 150px;
object-fit: contain;
} }
@keyframes shake { @keyframes shake {
@@ -2705,8 +2723,8 @@ footer a {
color: var(--zitadel-color-primary); color: var(--zitadel-color-primary);
} }
footer .lgn-logo-watermark { footer .lgn-logo-watermark {
background: var(--zitadel-logo-powered-by); background: var(--zitadel-logo-powered-by) no-repeat;
background-position: auto; background-position: center;
background-size: contain; background-size: contain;
} }

File diff suppressed because one or more lines are too long