Add main nav (#308)

This commit is contained in:
Rinat Arsaev 2020-10-28 15:39:33 +03:00 committed by GitHub
parent 47ba5ceb8f
commit a799eebf5e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
25 changed files with 137 additions and 198 deletions

View File

@ -1,15 +1,7 @@
<div fxLayout fxLayoutGap="16px">
<!-- <dsh-action-item iconName="brightness_4" (click)="changeTheme()"></dsh-action-item> -->
<dsh-action-item iconName="subject" [dshDropdownTriggerFor]="claimsDropdown"></dsh-action-item>
<dsh-action-item iconName="person" [dshDropdownTriggerFor]="userDropdown"></dsh-action-item>
</div>
<dsh-dropdown width="590px" #claimsDropdown="dshDropdown">
<ng-template>
<dsh-claims (menuItemSelected)="closeDropdown()" (navigatedToAllClaims)="closeDropdown()"></dsh-claims>
</ng-template>
</dsh-dropdown>
<dsh-dropdown width="180px" #userDropdown="dshDropdown">
<ng-template><dsh-user></dsh-user></ng-template>
</dsh-dropdown>

View File

@ -15,7 +15,6 @@ import { StateNavModule } from '@dsh/components/navigation';
import { ClaimsService } from '../../api/claims';
import { ActionItemComponent } from './action-item';
import { ActionbarComponent } from './actionbar.component';
import { ClaimsComponent, ClaimsListComponent, ClaimsListItemComponent } from './claims';
import { UserComponent } from './user';
@NgModule({
@ -32,14 +31,7 @@ import { UserComponent } from './user';
CommonModule,
TranslocoModule,
],
declarations: [
ActionbarComponent,
ActionItemComponent,
ClaimsListComponent,
ClaimsListItemComponent,
UserComponent,
ClaimsComponent,
],
declarations: [ActionbarComponent, ActionItemComponent, UserComponent],
providers: [ClaimsService],
exports: [ActionbarComponent],
})

View File

@ -1,22 +0,0 @@
<div fxLayout fxLayoutGap="10px" fxLayoutAlign="space-between center" class="item">
<div *transloco="let claimType; read: 'claimType'" fxFlex="60">
{{ claimType[type] }}
</div>
<dsh-status *transloco="let claimStatus; read: 'claimStatus'" fxFlex="40" [color]="statusColor">{{
claimStatus[claim.status]
}}</dsh-status>
<button dsh-icon-button [matMenuTriggerFor]="menu">
<mat-icon svgIcon="more_vert"></mat-icon>
</button>
</div>
<mat-menu #menu="matMenu">
<button
*transloco="let t; scope: 'actionbar'; read: 'actionbar.claims'"
mat-menu-item
[routerLink]="['/claim', claim.id]"
(click)="menuItemSelected.emit()"
>
{{ t.details }}
</button>
</mat-menu>

View File

@ -1,5 +0,0 @@
$height: 50px;
.item {
min-height: $height;
}

View File

@ -1,36 +0,0 @@
import {
ChangeDetectionStrategy,
Component,
EventEmitter,
Input,
OnChanges,
Output,
SimpleChanges,
} from '@angular/core';
import { Claim } from '../../../../../api-codegen/claim-management/swagger-codegen';
import { StatusColor } from '../../../../../theme-manager';
import { claimStatusToColor, ClaimType, getClaimType } from '../../../../../view-utils';
@Component({
selector: 'dsh-claims-list-item',
templateUrl: 'claims-list-item.component.html',
styleUrls: ['claims-list-item.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class ClaimsListItemComponent implements OnChanges {
@Input() claim: Claim;
@Output() menuItemSelected = new EventEmitter();
type: ClaimType;
statusColor: StatusColor;
ngOnChanges({ claim }: SimpleChanges) {
if (claim.isFirstChange || claim.currentValue.id !== claim.previousValue.id) {
const { changeset, status } = claim.currentValue;
this.type = getClaimType(changeset);
this.statusColor = claimStatusToColor(status);
}
}
}

View File

@ -1 +0,0 @@
export * from './claims-list-item.component';

View File

@ -1,3 +0,0 @@
<div fxLayout="column" fxLayoutGap="10px">
<ng-content></ng-content>
</div>

View File

@ -1,8 +0,0 @@
import { ChangeDetectionStrategy, Component } from '@angular/core';
@Component({
selector: 'dsh-claims-list',
templateUrl: 'claims-list.component.html',
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class ClaimsListComponent {}

View File

@ -1,2 +0,0 @@
export * from './claims-list.component';
export * from './claims-list-item';

View File

@ -1,23 +0,0 @@
<dsh-dropdown-content
*transloco="let t; scope: 'actionbar'; read: 'actionbar.claims'"
fxLayout="column"
fxLayoutGap="10px"
>
<h1 class="mat-title">{{ t.title }}</h1>
<div class="loading-container" *ngIf="isLoading$ | async" fxLayout fxLayoutAlign="center center">
<dsh-spinner></dsh-spinner>
</div>
<dsh-claims-list>
<dsh-claims-list-item
*ngFor="let claim of claims$ | async"
[claim]="claim"
(menuItemSelected)="menuItemSelected.next()"
>
</dsh-claims-list-item>
</dsh-claims-list>
<div *ngIf="error$ | async" class="mat-body-1">{{ t.httpError }}</div>
<div *ngIf="noClaims$ | async" class="mat-body-1">
{{ t.noClaims }}
</div>
<button dsh-button class="action" (click)="navigateToClaims()">{{ t.goToAll }}</button>
</dsh-dropdown-content>

View File

@ -1,7 +0,0 @@
.action {
margin-bottom: -10px;
}
.loading-container {
height: 100px;
}

View File

@ -1,29 +0,0 @@
import { ChangeDetectionStrategy, Component, EventEmitter, Output } from '@angular/core';
import { Router } from '@angular/router';
import { ClaimsService } from './claims.service';
@Component({
selector: 'dsh-claims',
templateUrl: 'claims.component.html',
styleUrls: ['claims.component.scss'],
providers: [ClaimsService],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class ClaimsComponent {
@Output() menuItemSelected = new EventEmitter();
@Output() navigatedToAllClaims = new EventEmitter();
claims$ = this.claimsService.claims$;
isLoading$ = this.claimsService.isLoading$;
error$ = this.claimsService.error$;
noClaims$ = this.claimsService.noClaims$;
constructor(private router: Router, private claimsService: ClaimsService) {}
navigateToClaims() {
this.navigatedToAllClaims.next();
this.router.navigate(['claims']);
}
}

View File

@ -1,18 +0,0 @@
import { Injectable } from '@angular/core';
import { map, pluck, shareReplay } from 'rxjs/operators';
import { ClaimsService as ClaimsApiService } from '../../../api/claims/claims.service';
import { booleanDelay, takeError } from '../../../custom-operators';
import { filterViewClaims } from '../../../view-utils';
@Injectable()
export class ClaimsService {
claims$ = this.claimsService
.searchClaims(5, ['pending', 'review', 'accepted'])
.pipe(pluck('result'), map(filterViewClaims), shareReplay(1));
noClaims$ = this.claims$.pipe(map((c) => c.length === 0));
isLoading$ = this.claims$.pipe(booleanDelay());
error$ = this.claims$.pipe(takeError);
constructor(private claimsService: ClaimsApiService) {}
}

View File

@ -1,2 +0,0 @@
export * from './claims.component';
export * from './claims-list';

View File

@ -0,0 +1,59 @@
import { Injectable } from '@angular/core';
import { Router } from '@angular/router';
import { combineLatest } from 'rxjs';
import { distinctUntilChanged, map, shareReplay, startWith } from 'rxjs/operators';
import { WalletService } from '../../api';
import { RouteEnv } from '../../sections/route-env';
import { Link } from '../../shared';
import { findActivePath } from '../../shared/utils';
export interface ToolbarLink extends Link {
id: string;
hidden?: boolean;
}
export enum LinkId {
main = 'main',
payments = 'payments',
wallets = 'wallets',
claims = 'claims',
}
@Injectable()
export class ToolbarLinksService {
private url$ = this.router.events.pipe(
startWith(null),
map(() => this.router.url),
distinctUntilChanged(),
shareReplay(1)
);
links$ = this.walletsService.hasWallets$.pipe(
map((hasWallets) => this.createLinks(hasWallets)),
shareReplay(1)
);
active$ = combineLatest([this.url$, this.links$]).pipe(
map(([url, links]) => findActivePath(url, links)),
shareReplay(1)
);
constructor(private walletsService: WalletService, private router: Router) {}
private createLinks(hasWallets: boolean): ToolbarLink[] {
return [
{ id: LinkId.main, path: '', activateStartPaths: [''] },
{
id: LinkId.payments,
path: `payment-section/env/${RouteEnv.real}/operations/payments`,
activateStartPaths: ['/payment-section', '/invoice'],
},
{
id: LinkId.wallets,
path: 'wallet-section/wallets',
activateStartPaths: ['/wallet-section', '/wallet'],
hidden: !hasWallets,
},
{ id: LinkId.claims, path: 'claims', activateStartPaths: ['/claims', '/claim', '/onboarding'] },
];
}
}

View File

@ -1,18 +1,16 @@
<div class="toolbar" fxLayout fxLayoutGap="24px" fxLayoutAlign="start center">
<dsh-brand fxFlex="168px" [type]="brandType" class="dsh-side-section"></dsh-brand>
<!-- <nav *transloco="let t; scope: 'toolbar'; read: 'toolbar.nav'" dsh-tab-nav-bar type="main">
<a dsh-tab-link>
{{ t.main }}
</a>
<a dsh-tab-link>
{{ t.payments }}
</a>
<a dsh-tab-link>
{{ t.wallets }}
</a>
<a dsh-tab-link badge="1" [active]="true">
{{ t.claims }}
</a>
</nav> -->
<nav
*transloco="let t; scope: 'toolbar'; read: 'toolbar.nav'"
dsh-tab-nav-bar
type="main"
[inverted]="inverted$ | async"
>
<ng-container *ngFor="let link of links$ | async">
<a dsh-tab-link *ngIf="!link.hidden" [routerLink]="link.path" [active]="(active$ | async) === link">
{{ t[link.id] }}
</a>
</ng-container>
</nav>
<dsh-actionbar fxFlex="grow" fxLayoutAlign="end"></dsh-actionbar>
</div>

View File

@ -1,13 +1,22 @@
import { ChangeDetectionStrategy, Component, Input } from '@angular/core';
import { map } from 'rxjs/operators';
import { BrandType } from '../brand';
import { LinkId, ToolbarLinksService } from './toolbar-links.service';
@Component({
selector: 'dsh-toolbar',
templateUrl: './toolbar.component.html',
styleUrls: ['./toolbar.component.scss'],
providers: [ToolbarLinksService],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class ToolbarComponent {
@Input() brandType: BrandType = BrandType.normal;
links$ = this.toolbarLinksService.links$;
inverted$ = this.toolbarLinksService.active$.pipe(map((active) => active.id === LinkId.main));
active$ = this.toolbarLinksService.active$;
constructor(private toolbarLinksService: ToolbarLinksService) {}
}

View File

@ -1,3 +1,4 @@
export * from './components';
export * from './services';
export * from './pipes';
export * from './models';

View File

@ -0,0 +1 @@
export * from './link';

View File

@ -0,0 +1,4 @@
export interface Link {
path: string;
activateStartPaths?: string[];
}

View File

@ -0,0 +1,27 @@
import { Link } from '../models';
interface ActivePathsCount {
count: number;
length: number;
}
function countActivePaths(url: string, paths: string[]): ActivePathsCount {
return paths.reduce<ActivePathsCount>(
({ count, length }, p) =>
url.indexOf(p) === 0 ? { count: ++count, length: length + p.length } : { count, length },
{ count: 0, length: 0 }
);
}
export function findActivePath<L extends Pick<Link, 'activateStartPaths'>>(url: string, links: L[]): L {
return links.reduce<[L, ActivePathsCount]>(
(max, link) => {
const [, { count: maxCount, length: maxLength }] = max;
const { count, length } = countActivePaths(url, link.activateStartPaths || []);
return !count || maxCount > count || (maxCount === count && maxLength > length)
? max
: [link, { count, length }];
},
[null, { count: 0, length: 0 }]
)[0];
}

View File

@ -0,0 +1 @@
export * from './find-active-link';

View File

@ -9,20 +9,25 @@
$foreground: map-get($theme, foreground);
.dsh {
&-tab-label {
color: map-get($foreground, text);
}
&-tab-label-active {
color: map-get($primary, 400);
}
&-tab-labels,
&-tab-links {
border-color: map-get($foreground, divider);
}
&-tab-label {
color: map-get($foreground, text);
&-active {
color: map-get($primary, 400);
}
&-disabled {
color: map-get($foreground, disabled-text);
}
}
&-tab-link-badge {
color: map-get($foreground, contrast-text);
background-color: mat-color($warn, 300);
}
@ -30,8 +35,13 @@
background-color: map-get($primary, 400);
}
&-tab-label-disabled {
color: map-get($foreground, disabled-text);
&-tab-nav-bar-inverted {
.dsh-tab-label {
color: map-get($foreground, contrast-text);
}
.dsh-ink-bar {
background-color: map-get($foreground, contrast-text);
}
}
}
}

View File

@ -43,7 +43,6 @@ $TAB_LINK_MAIN_BADGE_SIZE: 24px;
}
&-badge {
color: #fff;
text-align: center;
margin-left: $TAB_LINK_BADGE_MARGIN;
border-radius: 9999px;

View File

@ -22,6 +22,7 @@ import { CanDisable, HasTabIndex } from '@angular/material/core';
import { merge, of as observableOf, Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators';
import { coerceBoolean } from '../../../../utils';
import { DshInkBarDirective } from '../ink-bar.directive';
import { TabType } from '../tab-type';
@ -38,6 +39,7 @@ export class TabNavComponent implements AfterContentChecked, AfterContentInit, O
@Input() type: TabType;
@HostBinding('class.dsh-tab-nav-bar') tabNavBar = true;
@Input() @coerceBoolean @HostBinding('class.dsh-tab-nav-bar-inverted') inverted = false;
private readonly _onDestroy = new Subject<void>();