mirror of
https://github.com/valitydev/control-center.git
synced 2024-11-06 02:25:17 +00:00
FE-1062: new section for claim n new conversation for them (#144)
* FE-1062: new section for claim n new conversation for them
This commit is contained in:
parent
ed7a8a9533
commit
3b472fe8fb
2
Jenkinsfile
vendored
2
Jenkinsfile
vendored
@ -39,5 +39,5 @@ build('control-center', 'docker-host') {
|
||||
}
|
||||
}
|
||||
}
|
||||
pipeDefault(pipeline, 'dr2.rbkmoney.com', 'jenkins_harbor')
|
||||
pipeDefault(pipeline)
|
||||
}
|
||||
|
@ -32,7 +32,14 @@
|
||||
"src/favicon.ico",
|
||||
"src/assets"
|
||||
],
|
||||
"styles": ["src/styles.scss"],
|
||||
"styles": [
|
||||
"src/app/styles/core.scss",
|
||||
{
|
||||
"input": "src/app/styles/themes/light.scss",
|
||||
"bundleName": "themes/light",
|
||||
"lazy": true
|
||||
}
|
||||
],
|
||||
"scripts": ["./node_modules/keycloak-js/dist/keycloak.js"]
|
||||
},
|
||||
"configurations": {
|
||||
|
@ -4,7 +4,7 @@ import { KeycloakService } from 'keycloak-angular';
|
||||
@Component({
|
||||
selector: 'cc-root',
|
||||
templateUrl: './app.component.html',
|
||||
styleUrls: ['./app.component.css'],
|
||||
styleUrls: ['./app.component.scss'],
|
||||
})
|
||||
export class AppComponent implements OnInit {
|
||||
username: string;
|
||||
|
@ -31,6 +31,8 @@ import { PayoutsModule } from './payouts/payouts.module';
|
||||
import { RepairingModule } from './repairing/repairing.module';
|
||||
import { PartyModule } from './sections/party/party.module';
|
||||
import { SearchClaimsModule } from './sections/search-claims/search-claims.module';
|
||||
import { SettingsModule } from './settings';
|
||||
import { ThemeManager, ThemeManagerModule, ThemeName } from './theme-manager';
|
||||
|
||||
/**
|
||||
* For use in specific locations (for example, questionary PDF document)
|
||||
@ -59,6 +61,8 @@ moment.locale('en');
|
||||
PartyModule,
|
||||
DomainModule,
|
||||
RepairingModule,
|
||||
ThemeManagerModule,
|
||||
SettingsModule,
|
||||
DepositsModule,
|
||||
ClaimMgtModule,
|
||||
PartyModule,
|
||||
@ -73,4 +77,8 @@ moment.locale('en');
|
||||
],
|
||||
bootstrap: [AppComponent],
|
||||
})
|
||||
export class AppModule {}
|
||||
export class AppModule {
|
||||
constructor(private themeManager: ThemeManager) {
|
||||
this.themeManager.change(ThemeName.light);
|
||||
}
|
||||
}
|
||||
|
@ -8,7 +8,7 @@ import { RecreateClaimService } from './recreate-claim';
|
||||
|
||||
@Component({
|
||||
templateUrl: 'claim.component.html',
|
||||
styleUrls: ['claim.component.css'],
|
||||
styleUrls: ['claim.component.scss'],
|
||||
providers: [ClaimManagementService, ClaimService, RecreateClaimService],
|
||||
})
|
||||
export class ClaimComponent implements OnInit {
|
||||
|
@ -5,7 +5,7 @@ import { FileContainerService } from './file-container.service';
|
||||
@Component({
|
||||
selector: 'cc-file-container',
|
||||
templateUrl: 'file-container.component.html',
|
||||
styleUrls: ['file-container.component.css'],
|
||||
styleUrls: ['file-container.component.scss'],
|
||||
providers: [FileContainerService],
|
||||
})
|
||||
export class FileContainerComponent implements OnInit {
|
||||
|
@ -4,13 +4,12 @@ import { FlexModule } from '@angular/flex-layout';
|
||||
import { MatCardModule } from '@angular/material/card';
|
||||
import { MatIconModule } from '@angular/material/icon';
|
||||
|
||||
import { FileStorageService } from '../../../../thrift-services/file-storage/file-storage.service';
|
||||
import { FileStorageModule } from '../../../../thrift-services/file-storage';
|
||||
import { FileContainerComponent } from './file-container.component';
|
||||
|
||||
@NgModule({
|
||||
imports: [CommonModule, MatCardModule, FlexModule, MatIconModule],
|
||||
imports: [CommonModule, MatCardModule, FlexModule, MatIconModule, FileStorageModule],
|
||||
exports: [FileContainerComponent],
|
||||
declarations: [FileContainerComponent],
|
||||
providers: [FileStorageService],
|
||||
})
|
||||
export class FileContainerModule {}
|
||||
|
@ -1,3 +0,0 @@
|
||||
.dsh-file-uploader:hover {
|
||||
cursor: pointer;
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
<div ngfSelect class="dsh-file-uploader" (filesChange)="startUploading($event)">
|
||||
<div ngfSelect class="cc-file-uploader" (filesChange)="startUploading($event)">
|
||||
<button mat-icon-button [disabled]="inProgress$ | async">
|
||||
<mat-icon>attach_file</mat-icon>
|
||||
</button>
|
||||
|
@ -0,0 +1,3 @@
|
||||
.cc-file-uploader:hover {
|
||||
cursor: pointer;
|
||||
}
|
@ -6,7 +6,7 @@ import { FileUploaderService } from './file-uploader.service';
|
||||
@Component({
|
||||
selector: 'cc-file-uploader',
|
||||
templateUrl: 'file-uploader.component.html',
|
||||
styleUrls: ['file-uploader.component.css'],
|
||||
styleUrls: ['file-uploader.component.scss'],
|
||||
})
|
||||
export class FileUploaderComponent {
|
||||
@Output()
|
||||
|
@ -13,7 +13,7 @@ import { RemoveConfirmComponent } from './remove-confirm/remove-confirm.componen
|
||||
@Component({
|
||||
selector: 'cc-party-modification-container',
|
||||
templateUrl: 'party-modification-container.component.html',
|
||||
styleUrls: ['./party-modification-container.component.css'],
|
||||
styleUrls: ['./party-modification-container.component.scss'],
|
||||
})
|
||||
export class PartyModificationContainerComponent implements OnInit {
|
||||
@Input()
|
||||
|
@ -8,7 +8,7 @@ import { ModificationGroupType, PartyModificationUnit } from '../model';
|
||||
@Component({
|
||||
selector: 'cc-party-modification-units',
|
||||
templateUrl: 'party-modification-units.component.html',
|
||||
styleUrls: ['./party-modification-units.component.css'],
|
||||
styleUrls: ['./party-modification-units.component.scss'],
|
||||
})
|
||||
export class PartyModificationUnitsComponent {
|
||||
@Input()
|
||||
|
@ -7,7 +7,7 @@ import { ClaimInfo } from '../../papi/model';
|
||||
@Component({
|
||||
selector: 'cc-claims-table',
|
||||
templateUrl: 'claims-table.component.html',
|
||||
styleUrls: ['./claims-table.component.css'],
|
||||
styleUrls: ['./claims-table.component.scss'],
|
||||
})
|
||||
export class ClaimsTableComponent {
|
||||
@Input()
|
||||
|
@ -8,6 +8,6 @@
|
||||
fxLayoutGap="20px"
|
||||
fxLayoutGap.xs="10px"
|
||||
>
|
||||
<button mat-button fxFlex fxFlex.xs="none" dsh-button (click)="cancel()">CANCEL</button>
|
||||
<button mat-button fxFlex fxFlex.xs="none" dsh-button (click)="confirm()">CONFIRM</button>
|
||||
<button mat-button fxFlex fxFlex.xs="none" (click)="cancel()">CANCEL</button>
|
||||
<button mat-button fxFlex fxFlex.xs="none" (click)="confirm()">CONFIRM</button>
|
||||
</div>
|
||||
|
@ -5,7 +5,7 @@ import { StatDeposit } from '../../thrift-services/fistful/gen-model/fistful_sta
|
||||
@Component({
|
||||
selector: 'cc-deposits-table',
|
||||
templateUrl: 'deposits-table.component.html',
|
||||
styleUrls: ['deposits-table.component.css'],
|
||||
styleUrls: ['deposits-table.component.scss'],
|
||||
})
|
||||
export class DepositsTableComponent {
|
||||
@Input()
|
||||
|
@ -8,7 +8,7 @@ import { Shop } from '../../../thrift-services/damsel/gen-model/domain';
|
||||
@Component({
|
||||
selector: 'cc-shops-table',
|
||||
templateUrl: 'shops-table.component.html',
|
||||
styleUrls: ['shops-table.component.css'],
|
||||
styleUrls: ['shops-table.component.scss'],
|
||||
})
|
||||
export class ShopsTableComponent implements OnChanges {
|
||||
@Input() shops: Shop[];
|
||||
|
@ -18,7 +18,7 @@ import { StatPayment } from '../../thrift-services/damsel/gen-model/merch_stat';
|
||||
@Component({
|
||||
selector: 'cc-payment-adjustment-table',
|
||||
templateUrl: './table.component.html',
|
||||
styleUrls: ['./table.component.css'],
|
||||
styleUrls: ['./table.component.scss'],
|
||||
})
|
||||
export class TableComponent implements OnInit, OnChanges {
|
||||
@Input()
|
||||
|
@ -20,7 +20,7 @@ import { CancelPayoutComponent } from '../cancel-payout/cancel-payout.component'
|
||||
@Component({
|
||||
selector: 'cc-payouts-table',
|
||||
templateUrl: 'payouts-table.component.html',
|
||||
styleUrls: ['./payouts-table.component.css'],
|
||||
styleUrls: ['./payouts-table.component.scss'],
|
||||
})
|
||||
export class PayoutsTableComponent implements OnInit, OnChanges {
|
||||
@Output()
|
||||
|
@ -8,7 +8,7 @@ import { SearchFormService } from './search-form/search-form.service';
|
||||
|
||||
@Component({
|
||||
templateUrl: 'payouts.component.html',
|
||||
styleUrls: ['./payouts.component.css'],
|
||||
styleUrls: ['./payouts.component.scss'],
|
||||
providers: [SearchFormService],
|
||||
})
|
||||
export class PayoutsComponent {
|
||||
|
@ -31,7 +31,7 @@ interface Element {
|
||||
@Component({
|
||||
selector: 'cc-repair-with-scenario',
|
||||
templateUrl: 'repair-with-scenario.component.html',
|
||||
styleUrls: ['../repairing.component.css'],
|
||||
styleUrls: ['../repairing.component.scss'],
|
||||
providers: [],
|
||||
})
|
||||
export class RepairWithScenarioComponent {
|
||||
|
@ -26,7 +26,7 @@ interface Element {
|
||||
@Component({
|
||||
selector: 'cc-repair',
|
||||
templateUrl: 'repair.component.html',
|
||||
styleUrls: ['../repairing.component.css'],
|
||||
styleUrls: ['../repairing.component.scss'],
|
||||
providers: [],
|
||||
})
|
||||
export class RepairComponent {
|
||||
|
@ -6,7 +6,7 @@ import { RepairingService } from './repairing.service';
|
||||
|
||||
@Component({
|
||||
templateUrl: 'repairing.component.html',
|
||||
styleUrls: ['repairing.component.css'],
|
||||
styleUrls: ['repairing.component.scss'],
|
||||
providers: [],
|
||||
})
|
||||
export class RepairingComponent {
|
||||
|
@ -31,7 +31,7 @@ interface Element {
|
||||
@Component({
|
||||
selector: 'cc-simple-repair',
|
||||
templateUrl: 'simple-repair.component.html',
|
||||
styleUrls: ['../repairing.component.css'],
|
||||
styleUrls: ['../repairing.component.scss'],
|
||||
providers: [],
|
||||
})
|
||||
export class SimpleRepairComponent {
|
||||
|
@ -1,4 +1,4 @@
|
||||
<form fxLayout="row" fxLayout.xs="column" fxLayoutGap="20px" [formGroup]="form">
|
||||
<form fxLayout="row" fxLayout.xs="column" fxLayoutGap="16px" [formGroup]="form">
|
||||
<mat-form-field fxFlex="25">
|
||||
<mat-select placeholder="Claim statuses" formControlName="statuses" multiple>
|
||||
<mat-option *ngFor="let status of claimStatuses" [value]="status">{{
|
||||
|
9
src/app/sections/party-claim/_party-claim-theme.scss
Normal file
9
src/app/sections/party-claim/_party-claim-theme.scss
Normal file
@ -0,0 +1,9 @@
|
||||
@import '~@angular/material/theming';
|
||||
|
||||
@mixin cc-party-claim-theme($theme) {
|
||||
$foreground: map-get($theme, foreground);
|
||||
|
||||
.cc-party-claim-header {
|
||||
color: mat-color($foreground, secondary-text);
|
||||
}
|
||||
}
|
@ -0,0 +1,5 @@
|
||||
<div ngfSelect class="cc-file-uploader" (filesChange)="startUploading($event)">
|
||||
<button mat-icon-button [disabled]="inProgress$ | async">
|
||||
<mat-icon>cloud_upload</mat-icon>
|
||||
</button>
|
||||
</div>
|
@ -0,0 +1,3 @@
|
||||
.cc-file-uploader:hover {
|
||||
cursor: pointer;
|
||||
}
|
@ -0,0 +1,31 @@
|
||||
import { Component, EventEmitter, OnInit, Output } from '@angular/core';
|
||||
|
||||
import { Modification } from '../../../thrift-services/damsel/gen-model/claim_management';
|
||||
import { FileUploaderService } from './file-uploader.service';
|
||||
|
||||
@Component({
|
||||
selector: 'cc-file-uploader',
|
||||
templateUrl: 'file-uploader.component.html',
|
||||
styleUrls: ['file-uploader.component.scss'],
|
||||
})
|
||||
export class FileUploaderComponent implements OnInit {
|
||||
@Output()
|
||||
filesUploaded: EventEmitter<Modification[]> = new EventEmitter();
|
||||
|
||||
startUploading$ = this.fileUploaderService.startUploading$;
|
||||
inProgress$ = this.fileUploaderService.inProgress$;
|
||||
|
||||
constructor(private fileUploaderService: FileUploaderService) {}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.fileUploaderService.filesUploaded$.subscribe((values) =>
|
||||
this.filesUploaded.emit(
|
||||
values.map((v) => this.fileUploaderService.createModification(v))
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
startUploading(files: File[]) {
|
||||
this.startUploading$.next(files);
|
||||
}
|
||||
}
|
@ -0,0 +1,25 @@
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { NgModule } from '@angular/core';
|
||||
import { FlexModule } from '@angular/flex-layout';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
import { MatIconModule } from '@angular/material/icon';
|
||||
import { ngfModule } from 'angular-file';
|
||||
|
||||
import { FileStorageModule } from '../../../thrift-services/file-storage';
|
||||
import { FileUploaderComponent } from './file-uploader.component';
|
||||
import { FileUploaderService } from './file-uploader.service';
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
FlexModule,
|
||||
ngfModule,
|
||||
CommonModule,
|
||||
MatIconModule,
|
||||
MatButtonModule,
|
||||
FileStorageModule,
|
||||
],
|
||||
exports: [FileUploaderComponent],
|
||||
declarations: [FileUploaderComponent],
|
||||
providers: [FileUploaderService],
|
||||
})
|
||||
export class FileUploaderModule {}
|
@ -0,0 +1,90 @@
|
||||
import { HttpClient } from '@angular/common/http';
|
||||
import { Injectable } from '@angular/core';
|
||||
import { MatSnackBar } from '@angular/material/snack-bar';
|
||||
import { progress } from '@rbkmoney/partial-fetcher/dist/progress';
|
||||
import * as moment from 'moment';
|
||||
import { forkJoin, merge, Observable, of, Subject } from 'rxjs';
|
||||
import { catchError, filter, map, shareReplay, switchMap } from 'rxjs/operators';
|
||||
|
||||
import { Modification } from '../../../thrift-services/damsel/gen-model/claim_management';
|
||||
import { FileStorageService } from '../../../thrift-services/file-storage/file-storage.service';
|
||||
import { NewFileResult } from '../../../thrift-services/file-storage/gen-model/file_storage';
|
||||
import { Value } from '../../../thrift-services/file-storage/gen-model/msgpack';
|
||||
|
||||
@Injectable()
|
||||
export class FileUploaderService {
|
||||
startUploading$ = new Subject<File[]>();
|
||||
|
||||
filesUploadingError$ = new Subject<null>();
|
||||
|
||||
filesUploaded$: Observable<string[]> = this.startUploading$.pipe(
|
||||
switchMap((files) =>
|
||||
this.uploadFiles(files).pipe(
|
||||
catchError(() => {
|
||||
this.filesUploadingError$.next(null);
|
||||
return of([]);
|
||||
})
|
||||
)
|
||||
),
|
||||
filter((v) => !!v.length),
|
||||
shareReplay(1)
|
||||
);
|
||||
|
||||
inProgress$: Observable<boolean> = progress(
|
||||
this.startUploading$,
|
||||
merge(this.filesUploaded$, this.filesUploadingError$)
|
||||
);
|
||||
|
||||
constructor(
|
||||
private fileStorageService: FileStorageService,
|
||||
private snackBar: MatSnackBar,
|
||||
private http: HttpClient
|
||||
) {
|
||||
this.filesUploadingError$.subscribe(() => this.snackBar.open('File uploading error', 'OK'));
|
||||
}
|
||||
|
||||
uploadFiles(files: File[]): Observable<string[]> {
|
||||
return forkJoin(
|
||||
files.map((file) =>
|
||||
this.getUploadLink().pipe(
|
||||
switchMap((uploadData) =>
|
||||
forkJoin([
|
||||
of(uploadData.file_data_id),
|
||||
this.uploadFileToUrl(file, uploadData.upload_url),
|
||||
])
|
||||
),
|
||||
map(([fileId]) => fileId)
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
createModification(id: string): Modification {
|
||||
return {
|
||||
claim_modification: {
|
||||
file_modification: {
|
||||
id,
|
||||
modification: {
|
||||
creation: {},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
private getUploadLink(): Observable<NewFileResult> {
|
||||
return this.fileStorageService.createNewFile(
|
||||
new Map<string, Value>(),
|
||||
moment().add(1, 'h').toISOString()
|
||||
);
|
||||
}
|
||||
|
||||
private uploadFileToUrl(file: File, url: string): Observable<any> {
|
||||
return this.http.put(url, file, {
|
||||
headers: {
|
||||
'Content-Disposition': `attachment;filename=${encodeURI(file.name)}`,
|
||||
'Content-Type': '',
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
1
src/app/sections/party-claim/index.ts
Normal file
1
src/app/sections/party-claim/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export * from './party-claim.module';
|
21
src/app/sections/party-claim/party-claim-routing.module.ts
Normal file
21
src/app/sections/party-claim/party-claim-routing.module.ts
Normal file
@ -0,0 +1,21 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { RouterModule } from '@angular/router';
|
||||
|
||||
import { AppAuthGuardService } from '../../app-auth-guard.service';
|
||||
import { PartyClaimComponent } from './party-claim.component';
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
RouterModule.forChild([
|
||||
{
|
||||
path: '',
|
||||
component: PartyClaimComponent,
|
||||
canActivate: [AppAuthGuardService],
|
||||
data: {
|
||||
roles: ['get_claims'],
|
||||
},
|
||||
},
|
||||
]),
|
||||
],
|
||||
})
|
||||
export class PartyClaimRoutingModule {}
|
29
src/app/sections/party-claim/party-claim.component.html
Normal file
29
src/app/sections/party-claim/party-claim.component.html
Normal file
@ -0,0 +1,29 @@
|
||||
<div fxLayout="column" fxLayoutAlign="space-around stretch" fxLayoutGap="32px">
|
||||
<div fxFlex fxLayout="row" fxLayoutAlign="space-between center">
|
||||
<h3 class="cc-headline">
|
||||
Claim <span class="cc-party-claim-header">#{{ claimID$ | async }}</span>
|
||||
</h3>
|
||||
<h3 class="cc-headline">{{ 'Review' }}</h3>
|
||||
</div>
|
||||
<div fxFlex fxLayout="row" fxLayoutAlign="space-between center" fxLayoutGap="16px">
|
||||
<h3 class="cc-title">Changeset</h3>
|
||||
<mat-form-field fxFlex="0 1 266px">
|
||||
<mat-label>Changeset filters</mat-label>
|
||||
<mat-select>
|
||||
<mat-option>
|
||||
eekekekkekek
|
||||
</mat-option>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
</div>
|
||||
<mat-card fxLayout="column" fxLayoutGap="24px">
|
||||
<mat-card-content fxLayout="row" fxLayoutGap="8px" fxLayoutAlign="space-between center">
|
||||
<cc-send-comment fxFlex="100"></cc-send-comment>
|
||||
<cc-file-uploader></cc-file-uploader>
|
||||
</mat-card-content>
|
||||
<mat-card-actions fxLayout="row" fxLayoutAlign="space-between center">
|
||||
<button mat-button>CHANGE STATUS</button>
|
||||
<button mat-button>ADD PARTY MODIFICATION</button>
|
||||
</mat-card-actions>
|
||||
</mat-card>
|
14
src/app/sections/party-claim/party-claim.component.ts
Normal file
14
src/app/sections/party-claim/party-claim.component.ts
Normal file
@ -0,0 +1,14 @@
|
||||
import { Component } from '@angular/core';
|
||||
import { ActivatedRoute } from '@angular/router';
|
||||
import { pluck, shareReplay } from 'rxjs/operators';
|
||||
|
||||
import { SHARE_REPLAY_CONF } from '../../shared/share-replay-conf';
|
||||
|
||||
@Component({
|
||||
templateUrl: 'party-claim.component.html',
|
||||
})
|
||||
export class PartyClaimComponent {
|
||||
claimID$ = this.route.params.pipe(pluck('claimID'), shareReplay(SHARE_REPLAY_CONF));
|
||||
|
||||
constructor(private route: ActivatedRoute) {}
|
||||
}
|
32
src/app/sections/party-claim/party-claim.module.ts
Normal file
32
src/app/sections/party-claim/party-claim.module.ts
Normal file
@ -0,0 +1,32 @@
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { NgModule } from '@angular/core';
|
||||
import { FlexModule } from '@angular/flex-layout';
|
||||
import { ReactiveFormsModule } from '@angular/forms';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
import { MatCardModule } from '@angular/material/card';
|
||||
import { MatIconModule } from '@angular/material/icon';
|
||||
import { MatInputModule } from '@angular/material/input';
|
||||
import { MatSelectModule } from '@angular/material/select';
|
||||
|
||||
import { FileUploaderModule } from './file-uploader/file-uploader.module';
|
||||
import { PartyClaimRoutingModule } from './party-claim-routing.module';
|
||||
import { PartyClaimComponent } from './party-claim.component';
|
||||
import { SendCommentModule } from './send-comment/send-comment.module';
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
PartyClaimRoutingModule,
|
||||
FlexModule,
|
||||
MatSelectModule,
|
||||
MatCardModule,
|
||||
FileUploaderModule,
|
||||
CommonModule,
|
||||
MatButtonModule,
|
||||
MatIconModule,
|
||||
ReactiveFormsModule,
|
||||
MatInputModule,
|
||||
SendCommentModule,
|
||||
],
|
||||
declarations: [PartyClaimComponent],
|
||||
})
|
||||
export class PartyClaimModule {}
|
@ -0,0 +1,9 @@
|
||||
@import '~@angular/material/theming';
|
||||
|
||||
@mixin cc-send-comment-theme($theme) {
|
||||
$warn: map-get($theme, warn);
|
||||
|
||||
.cc-send-comment-error {
|
||||
color: mat-color($warn, 500) !important;
|
||||
}
|
||||
}
|
1
src/app/sections/party-claim/send-comment/index.ts
Normal file
1
src/app/sections/party-claim/send-comment/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export * from './send-comment.component';
|
@ -0,0 +1,22 @@
|
||||
<form [formGroup]="form" fxLayout="row" fxLayoutGap="16px" fxLayoutAlign="end start">
|
||||
<mat-form-field fxFlex>
|
||||
<mat-label>Leave a comment...</mat-label>
|
||||
<input formControlName="comment" matInput type="text" autocomplete="off" />
|
||||
<mat-hint
|
||||
[ngClass]="{
|
||||
'cc-caption': true,
|
||||
'cc-send-comment-error': form.controls.comment.value?.length > 1000
|
||||
}"
|
||||
>{{ form.controls.comment.value?.length || 0 }}/1000</mat-hint
|
||||
>
|
||||
</mat-form-field>
|
||||
<div class="send-comment-action">
|
||||
<button
|
||||
mat-icon-button
|
||||
(click)="sendComment()"
|
||||
[disabled]="(inProgress$ | async) || !form.valid"
|
||||
>
|
||||
<mat-icon>send</mat-icon>
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
@ -0,0 +1,7 @@
|
||||
@import '~@angular/material/theming';
|
||||
|
||||
$cc-send-comment-action-padding: 8px 0 0 0;
|
||||
|
||||
.send-comment-action {
|
||||
padding: $cc-send-comment-action-padding;
|
||||
}
|
@ -0,0 +1,36 @@
|
||||
import { Component, EventEmitter, OnInit, Output } from '@angular/core';
|
||||
import { FormGroup } from '@angular/forms';
|
||||
|
||||
import { Modification } from '../../../thrift-services/damsel/gen-model/claim_management';
|
||||
import { SendCommentService } from './send-comment.service';
|
||||
|
||||
@Component({
|
||||
selector: 'cc-send-comment',
|
||||
templateUrl: 'send-comment.component.html',
|
||||
styleUrls: ['send-comment.component.scss'],
|
||||
})
|
||||
export class SendCommentComponent implements OnInit {
|
||||
@Output() conversationSaved: EventEmitter<Modification[]> = new EventEmitter();
|
||||
|
||||
form: FormGroup = this.sendCommentService.form;
|
||||
inProgress$ = this.sendCommentService.inProgress$;
|
||||
|
||||
constructor(private sendCommentService: SendCommentService) {}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.sendCommentService.conversationSaved$.subscribe((id) =>
|
||||
this.conversationSaved.emit([this.sendCommentService.createModification(id)])
|
||||
);
|
||||
this.inProgress$.subscribe((inProgress) => {
|
||||
if (inProgress) {
|
||||
this.form.controls.comment.disable();
|
||||
} else {
|
||||
this.form.controls.comment.enable();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
sendComment() {
|
||||
this.sendCommentService.sendComment();
|
||||
}
|
||||
}
|
@ -0,0 +1,29 @@
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { NgModule } from '@angular/core';
|
||||
import { FlexModule } from '@angular/flex-layout';
|
||||
import { ReactiveFormsModule } from '@angular/forms';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
import { MatFormFieldModule } from '@angular/material/form-field';
|
||||
import { MatIconModule } from '@angular/material/icon';
|
||||
import { MatInputModule } from '@angular/material/input';
|
||||
|
||||
import { MessagesModule } from '../../../thrift-services/messages';
|
||||
import { SendCommentComponent } from './send-comment.component';
|
||||
import { SendCommentService } from './send-comment.service';
|
||||
|
||||
@NgModule({
|
||||
declarations: [SendCommentComponent],
|
||||
providers: [SendCommentService],
|
||||
imports: [
|
||||
MessagesModule,
|
||||
MatIconModule,
|
||||
MatButtonModule,
|
||||
ReactiveFormsModule,
|
||||
MatFormFieldModule,
|
||||
FlexModule,
|
||||
MatInputModule,
|
||||
CommonModule,
|
||||
],
|
||||
exports: [SendCommentComponent],
|
||||
})
|
||||
export class SendCommentModule {}
|
@ -0,0 +1,94 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { FormBuilder, Validators } from '@angular/forms';
|
||||
import { MatSnackBar } from '@angular/material/snack-bar';
|
||||
import { progress } from '@rbkmoney/partial-fetcher/dist/progress';
|
||||
import get from 'lodash-es/get';
|
||||
import { BehaviorSubject, forkJoin, merge, Observable, of, Subject } from 'rxjs';
|
||||
import { catchError, filter, pluck, switchMap, tap } from 'rxjs/operators';
|
||||
import * as uuid from 'uuid/v4';
|
||||
|
||||
import { KeycloakTokenInfoService } from '../../../keycloak-token-info.service';
|
||||
import { Modification } from '../../../thrift-services/damsel/gen-model/claim_management';
|
||||
import { ConversationId, User } from '../../../thrift-services/messages/gen-model/messages';
|
||||
import { MessagesService } from '../../../thrift-services/messages/messages.service';
|
||||
import { createSingleMessageConversationParams } from '../../../thrift-services/messages/utils';
|
||||
|
||||
@Injectable()
|
||||
export class SendCommentService {
|
||||
private conversationId$: BehaviorSubject<ConversationId | null> = new BehaviorSubject(null);
|
||||
private error$: BehaviorSubject<any> = new BehaviorSubject({ hasError: false });
|
||||
private sendComment$: Subject<string> = new Subject();
|
||||
|
||||
form = this.fb.group({
|
||||
comment: ['', [Validators.maxLength(1000), Validators.required]],
|
||||
});
|
||||
conversationSaved$: Observable<ConversationId> = this.conversationId$.pipe(
|
||||
filter((id) => !!id)
|
||||
);
|
||||
errorCode$: Observable<string> = this.error$.pipe(pluck('code'));
|
||||
inProgress$: Observable<boolean> = progress(
|
||||
this.sendComment$,
|
||||
merge(this.conversationId$, this.error$)
|
||||
);
|
||||
|
||||
constructor(
|
||||
private fb: FormBuilder,
|
||||
private messagesService: MessagesService,
|
||||
private keycloakTokenInfoService: KeycloakTokenInfoService,
|
||||
private snackBar: MatSnackBar
|
||||
) {
|
||||
this.sendComment$
|
||||
.pipe(
|
||||
tap(() => this.error$.next({ hasError: false })),
|
||||
switchMap((text) => {
|
||||
const { name, email, sub } = this.keycloakTokenInfoService.decodedUserToken;
|
||||
const user: User = { fullname: name, email, user_id: sub };
|
||||
const conversation_id = uuid();
|
||||
const conversation = createSingleMessageConversationParams(
|
||||
conversation_id,
|
||||
text,
|
||||
sub
|
||||
);
|
||||
return forkJoin([
|
||||
of(conversation_id),
|
||||
this.messagesService.saveConversations([conversation], user).pipe(
|
||||
catchError((ex) => {
|
||||
console.error(ex);
|
||||
this.snackBar.open(
|
||||
`There was an error sending a comment: ${ex}`,
|
||||
'OK',
|
||||
{ duration: 5000 }
|
||||
);
|
||||
const error = { hasError: true, code: 'saveConversationsFailed' };
|
||||
this.error$.next(error);
|
||||
return of(error);
|
||||
})
|
||||
),
|
||||
]);
|
||||
}),
|
||||
filter(([, res]) => get(res, ['hasError']) !== true)
|
||||
)
|
||||
.subscribe(([conversation_id]) => {
|
||||
this.conversationId$.next(conversation_id);
|
||||
this.form.reset();
|
||||
});
|
||||
}
|
||||
|
||||
sendComment() {
|
||||
const { comment } = this.form.value;
|
||||
this.sendComment$.next(comment);
|
||||
}
|
||||
|
||||
createModification(id: ConversationId): Modification {
|
||||
return {
|
||||
claim_modification: {
|
||||
comment_modification: {
|
||||
id,
|
||||
modification: {
|
||||
creation: {},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
@ -3,5 +3,5 @@
|
||||
}
|
||||
|
||||
.action-cell {
|
||||
width: 10px;
|
||||
width: 8px;
|
||||
}
|
||||
|
@ -1,9 +1,10 @@
|
||||
<div fxLayout="column">
|
||||
<div fxLayout="column" fxLayoutGap="24px">
|
||||
<div fxFlex fxLayout="row" fxLayoutAlign="space-between center">
|
||||
<div class="mat-headline">Party claims</div>
|
||||
<div class="сс-headline">Party claims</div>
|
||||
<button mat-button color="primary" (click)="createClaim()">CREATE</button>
|
||||
</div>
|
||||
<div fxLayout="column" fxLayoutGap="20px">
|
||||
<div class="cc-headline">Party claims</div>
|
||||
<div fxLayout="column" fxLayoutGap="24px">
|
||||
<mat-card>
|
||||
<mat-card-content>
|
||||
<cc-claim-search-form
|
||||
@ -17,7 +18,7 @@
|
||||
</mat-card>
|
||||
<ng-container *ngIf="claims$ | async as claims">
|
||||
<cc-empty-search-result *ngIf="claims.length === 0"></cc-empty-search-result>
|
||||
<mat-card *ngIf="claims.length > 0" fxLayout="column" fxLayoutGap="20px">
|
||||
<mat-card *ngIf="claims.length > 0" fxLayout="column" fxLayoutGap="16px">
|
||||
<cc-claims-table [claims]="claims"></cc-claims-table>
|
||||
<button
|
||||
fxFlex="100"
|
||||
|
@ -1 +1 @@
|
||||
<div class="mat-headline">Party shops</div>
|
||||
<div class="cc-headline">Party shops</div>
|
||||
|
9
src/app/sections/party/_party-theme.scss
Normal file
9
src/app/sections/party/_party-theme.scss
Normal file
@ -0,0 +1,9 @@
|
||||
@import '~@angular/material/theming';
|
||||
|
||||
@mixin cc-party-theme($theme) {
|
||||
$foreground: map-get($theme, foreground);
|
||||
|
||||
.cc-party-header {
|
||||
color: mat-color($foreground, secondary-text);
|
||||
}
|
||||
}
|
@ -24,6 +24,15 @@ import { PartyComponent } from './party.component';
|
||||
roles: ['get_claims'],
|
||||
},
|
||||
},
|
||||
{
|
||||
path: 'claim/:claimID',
|
||||
loadChildren: () =>
|
||||
import('../party-claim').then((m) => m.PartyClaimModule),
|
||||
canActivate: [AppAuthGuardService],
|
||||
data: {
|
||||
roles: ['get_claims'],
|
||||
},
|
||||
},
|
||||
{
|
||||
path: 'shops',
|
||||
loadChildren: () =>
|
||||
|
@ -1,7 +1,7 @@
|
||||
<div class="party-container">
|
||||
<h3 class="mat-subheading-1">Party #{{ partyID }}</h3>
|
||||
<div class="party-container" fxLayout="column" fxLayoutGap="24px">
|
||||
<h3 class="cc-subheading-1 cc-party-header">Party #{{ partyID$ | async }}</h3>
|
||||
|
||||
<div fxLayout="column" fxLayoutGap="10px">
|
||||
<div fxLayout="column" fxLayoutGap="24px">
|
||||
<nav mat-tab-nav-bar>
|
||||
<a
|
||||
mat-tab-link
|
||||
@ -9,7 +9,7 @@
|
||||
[routerLink]="link.url"
|
||||
routerLinkActive
|
||||
#rla="routerLinkActive"
|
||||
[active]="rla.isActive"
|
||||
[active]="rla.isActive || (hasClaimID$ | async)"
|
||||
>
|
||||
{{ link.name }}
|
||||
</a>
|
||||
|
@ -1,8 +1,8 @@
|
||||
.party-container {
|
||||
max-width: 900px;
|
||||
margin: 20px auto;
|
||||
max-width: 936px;
|
||||
margin: 24px auto;
|
||||
}
|
||||
|
||||
router-outlet {
|
||||
margin-bottom: 0;
|
||||
margin-bottom: 0 !important;
|
||||
}
|
||||
|
@ -1,21 +1,25 @@
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { Component } from '@angular/core';
|
||||
import { ActivatedRoute } from '@angular/router';
|
||||
import { map, pluck, shareReplay } from 'rxjs/operators';
|
||||
|
||||
import { SHARE_REPLAY_CONF } from '../../shared/share-replay-conf';
|
||||
|
||||
@Component({
|
||||
templateUrl: 'party.component.html',
|
||||
styleUrls: ['party.component.scss'],
|
||||
})
|
||||
export class PartyComponent implements OnInit {
|
||||
export class PartyComponent {
|
||||
links = [
|
||||
{ name: 'Claims', url: 'claims' },
|
||||
{ name: 'Shops', url: 'shops' },
|
||||
];
|
||||
|
||||
partyID: string;
|
||||
partyID$ = this.route.params.pipe(pluck('partyID'), shareReplay(SHARE_REPLAY_CONF));
|
||||
hasClaimID$ = this.route.firstChild.params.pipe(
|
||||
pluck('claimID'),
|
||||
map((claimID) => !!claimID),
|
||||
shareReplay(SHARE_REPLAY_CONF)
|
||||
);
|
||||
|
||||
constructor(private route: ActivatedRoute) {}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.route.params.subscribe(({ partyID }) => (this.partyID = partyID));
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
<div class="search-claims-container" fxLayout="column">
|
||||
<div class="mat-headline">Claims</div>
|
||||
<div fxLayout="column" fxLayoutGap="20px">
|
||||
<div class="search-claims-container" fxLayout="column" fxLayoutGap="24px">
|
||||
<h1 class="cc-headline">Claims</h1>
|
||||
<div fxLayout="column" fxLayoutGap="24px">
|
||||
<mat-card>
|
||||
<mat-card-content>
|
||||
<cc-claim-search-form (valueChanges)="search($event)"></cc-claim-search-form>
|
||||
@ -11,7 +11,7 @@
|
||||
</mat-card>
|
||||
<ng-container *ngIf="claims$ | async as claims">
|
||||
<cc-empty-search-result *ngIf="claims.length === 0"></cc-empty-search-result>
|
||||
<mat-card *ngIf="claims.length > 0" fxLayout="column" fxLayoutGap="20px">
|
||||
<mat-card *ngIf="claims.length > 0" fxLayout="column" fxLayoutGap="18px">
|
||||
<cc-search-table [claims]="claims"></cc-search-table>
|
||||
<button
|
||||
fxFlex="100"
|
||||
|
@ -1,4 +1,4 @@
|
||||
.search-claims-container {
|
||||
max-width: 890px;
|
||||
margin: 20px auto;
|
||||
margin: 24px auto;
|
||||
}
|
||||
|
@ -3,7 +3,7 @@ table {
|
||||
}
|
||||
|
||||
.action-cell {
|
||||
width: 10px;
|
||||
width: 8px;
|
||||
}
|
||||
|
||||
.party-id {
|
||||
|
2
src/app/settings/index.ts
Normal file
2
src/app/settings/index.ts
Normal file
@ -0,0 +1,2 @@
|
||||
export * from './settings.module';
|
||||
export * from './settings.service';
|
8
src/app/settings/settings.module.ts
Normal file
8
src/app/settings/settings.module.ts
Normal file
@ -0,0 +1,8 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
|
||||
import { SettingsService } from './settings.service';
|
||||
|
||||
@NgModule({
|
||||
providers: [SettingsService],
|
||||
})
|
||||
export class SettingsModule {}
|
22
src/app/settings/settings.service.ts
Normal file
22
src/app/settings/settings.service.ts
Normal file
@ -0,0 +1,22 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
|
||||
@Injectable()
|
||||
export class SettingsService {
|
||||
set(key: string, value: string) {
|
||||
localStorage.setItem(this.getKeyName(key), value);
|
||||
}
|
||||
|
||||
setAll(keyValue: { [name: string]: string }) {
|
||||
for (const [k, v] of Object.entries(keyValue)) {
|
||||
this.set(k, v);
|
||||
}
|
||||
}
|
||||
|
||||
get(key: string): string {
|
||||
return localStorage.getItem(this.getKeyName(key));
|
||||
}
|
||||
|
||||
private getKeyName(name: string) {
|
||||
return `cc-${name}`;
|
||||
}
|
||||
}
|
@ -9,10 +9,10 @@
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
/deep/.card-container > * {
|
||||
::ng-deep.card-container > * {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
/deep/.card-container > *:last-child {
|
||||
::ng-deep.card-container > *:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
@ -3,6 +3,6 @@ import { Component } from '@angular/core';
|
||||
@Component({
|
||||
selector: 'cc-card-container',
|
||||
templateUrl: 'card-container.component.html',
|
||||
styleUrls: ['card-container.component.css'],
|
||||
styleUrls: ['card-container.component.scss'],
|
||||
})
|
||||
export class CardContainerComponent {}
|
||||
|
1
src/app/shared/components/card-container/index.ts
Normal file
1
src/app/shared/components/card-container/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export * from './card-container.component';
|
1
src/app/shared/components/empty-search-result/index.ts
Normal file
1
src/app/shared/components/empty-search-result/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export * from './empty-search-result.component';
|
5
src/app/shared/components/index.ts
Normal file
5
src/app/shared/components/index.ts
Normal file
@ -0,0 +1,5 @@
|
||||
export * from './card-container';
|
||||
export * from './details-item';
|
||||
export * from './empty-search-result';
|
||||
export * from './pretty-json';
|
||||
export * from './timeline';
|
1
src/app/shared/components/pretty-json/index.ts
Normal file
1
src/app/shared/components/pretty-json/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export * from './pretty-json.component';
|
4
src/app/shared/share-replay-conf.ts
Normal file
4
src/app/shared/share-replay-conf.ts
Normal file
@ -0,0 +1,4 @@
|
||||
import { ShareReplayConfig } from 'rxjs/internal/operators/shareReplay';
|
||||
|
||||
// Default share replay config
|
||||
export const SHARE_REPLAY_CONF: ShareReplayConfig = { bufferSize: 1, refCount: true };
|
@ -4,9 +4,11 @@ import { FlexModule } from '@angular/flex-layout';
|
||||
import { MatCardModule } from '@angular/material/card';
|
||||
import { PrettyJsonModule } from 'angular2-prettyjson';
|
||||
|
||||
import { CardContainerComponent } from './components/card-container/card-container.component';
|
||||
import { EmptySearchResultComponent } from './components/empty-search-result/empty-search-result.component';
|
||||
import { PrettyJsonComponent } from './components/pretty-json/pretty-json.component';
|
||||
import {
|
||||
CardContainerComponent,
|
||||
EmptySearchResultComponent,
|
||||
PrettyJsonComponent,
|
||||
} from './components';
|
||||
import {
|
||||
ClaimSourcePipe,
|
||||
ClaimStatusPipe,
|
||||
|
18
src/app/styles/core.scss
Normal file
18
src/app/styles/core.scss
Normal file
@ -0,0 +1,18 @@
|
||||
@import '~@angular/material/prebuilt-themes/indigo-pink.css';
|
||||
@import '~@angular/material/theming';
|
||||
|
||||
@import './overrides/body';
|
||||
|
||||
@import './utils/typography';
|
||||
|
||||
@mixin cc-override() {
|
||||
@include cc-body-override();
|
||||
}
|
||||
|
||||
@mixin cc-typography($config) {
|
||||
@include mat-core($config);
|
||||
@include cc-base-typography($config);
|
||||
}
|
||||
|
||||
@include cc-override();
|
||||
@include cc-typography(cc-typography-config());
|
5
src/app/styles/overrides/body.scss
Normal file
5
src/app/styles/overrides/body.scss
Normal file
@ -0,0 +1,5 @@
|
||||
@mixin cc-body-override() {
|
||||
body {
|
||||
margin: 0;
|
||||
}
|
||||
}
|
14
src/app/styles/themes/_theme.scss
Normal file
14
src/app/styles/themes/_theme.scss
Normal file
@ -0,0 +1,14 @@
|
||||
@import '~@angular/material/theming';
|
||||
|
||||
@import '../../sections/party-claim/party-claim-theme';
|
||||
@import '../../sections/party/party-theme';
|
||||
@import '../../sections/party-claim/send-comment/send-comment-theme';
|
||||
|
||||
@mixin cc-theme($theme) {
|
||||
body.#{map-get($theme, name)} {
|
||||
@include angular-material-theme($theme);
|
||||
@include cc-party-claim-theme($theme);
|
||||
@include cc-party-theme($theme);
|
||||
@include cc-send-comment-theme($theme);
|
||||
}
|
||||
}
|
22
src/app/styles/themes/light.scss
Normal file
22
src/app/styles/themes/light.scss
Normal file
@ -0,0 +1,22 @@
|
||||
@import '~@angular/material/theming';
|
||||
@import './theme';
|
||||
|
||||
body.light {
|
||||
background-color: mat-color($mat-gray, 50);
|
||||
}
|
||||
|
||||
$theme: (
|
||||
primary: mat-palette($mat-indigo),
|
||||
accent: mat-palette($mat-pink),
|
||||
warn: mat-palette($mat-red),
|
||||
is-dark: false,
|
||||
foreground: $mat-light-theme-foreground,
|
||||
background: $mat-light-theme-background,
|
||||
/*
|
||||
* Custom
|
||||
*
|
||||
*/ name: 'light',
|
||||
gray: mat-palette($mat-grey),
|
||||
);
|
||||
|
||||
@include cc-theme($theme);
|
136
src/app/styles/utils/_typography.scss
Normal file
136
src/app/styles/utils/_typography.scss
Normal file
@ -0,0 +1,136 @@
|
||||
@import '~@angular/material/theming';
|
||||
|
||||
@function cc-typography-config(
|
||||
$font-family: 'Roboto, "Helvetica Neue", sans-serif',
|
||||
$display-4: mat-typography-level(112px, 112px, 300, $letter-spacing: -0.05em),
|
||||
$display-3: mat-typography-level(56px, 56px, 400, $letter-spacing: -0.02em),
|
||||
$display-2: mat-typography-level(45px, 48px, 400, $letter-spacing: -0.005em),
|
||||
$display-1: mat-typography-level(34px, 40px, 400),
|
||||
$headline: mat-typography-level(24px, 32px, 400),
|
||||
$title: mat-typography-level(20px, 32px, 500),
|
||||
$subheading-2: mat-typography-level(16px, 28px, 400),
|
||||
$subheading-1: mat-typography-level(15px, 24px, 400),
|
||||
$body-2: mat-typography-level(14px, 24px, 500),
|
||||
$body-1: mat-typography-level(14px, 20px, 400),
|
||||
$caption: mat-typography-level(12px, 20px, 400),
|
||||
$button: mat-typography-level(14px, 14px, 500),
|
||||
$input: mat-typography-level(14px, 1.15, 400)
|
||||
) {
|
||||
// Declare an initial map with all of the levels.
|
||||
$config: (
|
||||
display-4: $display-4,
|
||||
display-3: $display-3,
|
||||
display-2: $display-2,
|
||||
display-1: $display-1,
|
||||
headline: $headline,
|
||||
title: $title,
|
||||
subheading-2: $subheading-2,
|
||||
subheading-1: $subheading-1,
|
||||
body-2: $body-2,
|
||||
body-1: $body-1,
|
||||
caption: $caption,
|
||||
button: $button,
|
||||
input: $input,
|
||||
);
|
||||
|
||||
// Loop through the levels and set the `font-family` of the ones that don't have one to the base.
|
||||
// Note that Sass can't modify maps in place, which means that we need to merge and re-assign.
|
||||
@each $key, $level in $config {
|
||||
@if map-get($level, font-family) == null {
|
||||
$new-level: map-merge(
|
||||
$level,
|
||||
(
|
||||
font-family: $font-family,
|
||||
)
|
||||
);
|
||||
$config: map-merge(
|
||||
$config,
|
||||
(
|
||||
$key: $new-level,
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Add the base font family to the config.
|
||||
@return map-merge(
|
||||
$config,
|
||||
(
|
||||
font-family: $font-family,
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
@mixin typo-truncate($width, $max-width) {
|
||||
width: $width;
|
||||
max-width: $max-width;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
@mixin cc-base-typography($config, $selector: '.cc-typography') {
|
||||
.cc-headline,
|
||||
#{$selector} h1 {
|
||||
@include mat-typography-level-to-styles($config, headline);
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.cc-title,
|
||||
#{$selector} h2 {
|
||||
@include mat-typography-level-to-styles($config, title);
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.cc-subheading-2,
|
||||
#{$selector} h3 {
|
||||
@include mat-typography-level-to-styles($config, subheading-2);
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.cc-subheading-1,
|
||||
#{$selector} h4 {
|
||||
@include mat-typography-level-to-styles($config, subheading-1);
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.cc-body-2 {
|
||||
@include mat-typography-level-to-styles($config, body-2);
|
||||
}
|
||||
|
||||
.cc-body-1,
|
||||
#{$selector} {
|
||||
@include mat-typography-level-to-styles($config, body-1);
|
||||
|
||||
p {
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.cc-caption {
|
||||
@include mat-typography-level-to-styles($config, caption);
|
||||
}
|
||||
|
||||
.cc-display-4,
|
||||
#{$selector} .cc-display-4 {
|
||||
@include mat-typography-level-to-styles($config, display-4);
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
#{$selector} .cc-display-3 {
|
||||
@include mat-typography-level-to-styles($config, display-3);
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.cc-display-2,
|
||||
#{$selector} .cc-display-2 {
|
||||
@include mat-typography-level-to-styles($config, display-2);
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.cc-display-1,
|
||||
#{$selector} .cc-display-1 {
|
||||
@include mat-typography-level-to-styles($config, display-1);
|
||||
margin: 0;
|
||||
}
|
||||
}
|
3
src/app/theme-manager/index.ts
Normal file
3
src/app/theme-manager/index.ts
Normal file
@ -0,0 +1,3 @@
|
||||
export * from './theme-manager.service';
|
||||
export * from './theme-manager.module';
|
||||
export * from './theme-name';
|
10
src/app/theme-manager/theme-manager.module.ts
Normal file
10
src/app/theme-manager/theme-manager.module.ts
Normal file
@ -0,0 +1,10 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
|
||||
import { SettingsModule } from '../settings';
|
||||
import { ThemeManager } from './theme-manager.service';
|
||||
|
||||
@NgModule({
|
||||
imports: [SettingsModule],
|
||||
providers: [ThemeManager],
|
||||
})
|
||||
export class ThemeManagerModule {}
|
72
src/app/theme-manager/theme-manager.service.ts
Normal file
72
src/app/theme-manager/theme-manager.service.ts
Normal file
@ -0,0 +1,72 @@
|
||||
import { DOCUMENT } from '@angular/common';
|
||||
import { Inject, Injectable } from '@angular/core';
|
||||
|
||||
import { environment } from '../../environments/environment';
|
||||
import { SettingsService } from '../settings';
|
||||
import { ThemeName } from './theme-name';
|
||||
|
||||
enum Type {
|
||||
JS = 'js',
|
||||
CSS = 'css',
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
export class ThemeManager {
|
||||
private static readonly KEY = 'theme';
|
||||
|
||||
current: ThemeName;
|
||||
|
||||
private element: HTMLScriptElement | HTMLLinkElement;
|
||||
|
||||
constructor(private settingsService: SettingsService, @Inject(DOCUMENT) private doc: Document) {
|
||||
const name = this.settingsService.get(ThemeManager.KEY);
|
||||
const correctedName = this.getCorrectName(name);
|
||||
this.change(correctedName);
|
||||
}
|
||||
|
||||
change(name: ThemeName) {
|
||||
this.removeCurrent();
|
||||
this.set(name);
|
||||
}
|
||||
|
||||
private getCorrectName(theme: ThemeName | string): ThemeName {
|
||||
if (!Object.values<string>(ThemeName).includes(theme)) {
|
||||
return this.current || ThemeName.light;
|
||||
}
|
||||
return theme as ThemeName;
|
||||
}
|
||||
|
||||
private set(name: ThemeName) {
|
||||
this.element = this.createElement(name);
|
||||
this.doc.head.appendChild(this.element);
|
||||
this.doc.body.classList.add(name);
|
||||
this.settingsService.set(ThemeManager.KEY, name);
|
||||
this.current = name;
|
||||
}
|
||||
|
||||
private removeCurrent() {
|
||||
if (this.doc.head.contains(this.element)) {
|
||||
this.doc.head.removeChild(this.element);
|
||||
}
|
||||
this.doc.body.classList.remove(this.current);
|
||||
}
|
||||
|
||||
private createElement(name: ThemeName): HTMLLinkElement | HTMLScriptElement {
|
||||
const fileType: Type = environment.production ? Type.CSS : Type.JS;
|
||||
const url = `themes/${name}.${fileType}`;
|
||||
return fileType === Type.JS ? this.createScriptElement(url) : this.createStyleElement(url);
|
||||
}
|
||||
|
||||
private createStyleElement(url: string): HTMLLinkElement {
|
||||
const styleElement = document.createElement('link');
|
||||
styleElement.href = url;
|
||||
styleElement.rel = 'stylesheet';
|
||||
return styleElement;
|
||||
}
|
||||
|
||||
private createScriptElement(url: string): HTMLScriptElement {
|
||||
const scriptElement = document.createElement('script');
|
||||
scriptElement.src = url;
|
||||
return scriptElement;
|
||||
}
|
||||
}
|
3
src/app/theme-manager/theme-name.ts
Normal file
3
src/app/theme-manager/theme-name.ts
Normal file
@ -0,0 +1,3 @@
|
||||
export enum ThemeName {
|
||||
light = 'light',
|
||||
}
|
@ -1,4 +1,8 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
|
||||
@NgModule({})
|
||||
import { FileStorageService } from './file-storage.service';
|
||||
|
||||
@NgModule({
|
||||
providers: [FileStorageService],
|
||||
})
|
||||
export class FileStorageModule {}
|
||||
|
@ -1,6 +0,0 @@
|
||||
@import '~@angular/material/prebuilt-themes/indigo-pink.css';
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
background: #fafafa;
|
||||
}
|
@ -20,8 +20,8 @@
|
||||
{
|
||||
"grouped-imports": true,
|
||||
"groups": [
|
||||
{ "name": "node_modules", "match": "^(?![.]|@dsh/)", "order": 10 },
|
||||
{ "name": "project", "match": "^@dsh/", "order": 20 },
|
||||
{ "name": "node_modules", "match": "^(?![.]|@cc/)", "order": 10 },
|
||||
{ "name": "project", "match": "^@cc/", "order": 20 },
|
||||
{ "name": "current", "match": "^[.]", "order": 30 }
|
||||
]
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user