Adds navbar, footer, and Material theme

This commit is contained in:
Aaron Louie 2020-02-24 15:20:18 -05:00
parent f9a47e36f7
commit fe1a481f43
26 changed files with 831 additions and 29 deletions

View File

@ -26,10 +26,9 @@
"src/assets" "src/assets"
], ],
"styles": [ "styles": [
"./node_modules/@angular/material/prebuilt-themes/indigo-pink.css",
"node_modules/bpmn-js/dist/assets/diagram-js.css", "node_modules/bpmn-js/dist/assets/diagram-js.css",
"node_modules/bpmn-js/dist/assets/bpmn-font/css/bpmn.css", "node_modules/bpmn-js/dist/assets/bpmn-font/css/bpmn.css",
"src/styles.css" "src/styles.scss"
], ],
"scripts": [] "scripts": []
}, },
@ -145,8 +144,7 @@
"tsConfig": "src/tsconfig.spec.json", "tsConfig": "src/tsconfig.spec.json",
"karmaConfig": "karma.conf.js", "karmaConfig": "karma.conf.js",
"styles": [ "styles": [
"./node_modules/@angular/material/prebuilt-themes/indigo-pink.css", "src/styles.scss"
"src/styles.css"
], ],
"scripts": [], "scripts": [],
"assets": [ "assets": [

6
package-lock.json generated
View File

@ -11641,9 +11641,9 @@
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
}, },
"sartography-workflow-lib": { "sartography-workflow-lib": {
"version": "0.0.45", "version": "0.0.46",
"resolved": "https://registry.npmjs.org/sartography-workflow-lib/-/sartography-workflow-lib-0.0.45.tgz", "resolved": "https://registry.npmjs.org/sartography-workflow-lib/-/sartography-workflow-lib-0.0.46.tgz",
"integrity": "sha512-0c3XT5ID0gF8SM18DmD88jDwOccjaViEhJ6DWzh9irblPmvbeA/B7a1MS0i0fm6IC3pw7tdja9pBFeDXz0UOZw==", "integrity": "sha512-gxm3mfSyQ/g9Axen4w0HAH+s3mQARVANN5KfPQZgSnvT1wP/ymnhZDikg/hfbUJQZ5qwMv+phKK53HeAsexU3w==",
"requires": { "requires": {
"tslib": "^1.9.0" "tslib": "^1.9.0"
} }

View File

@ -47,7 +47,7 @@
"file-saver": "^2.0.2", "file-saver": "^2.0.2",
"hammerjs": "^2.0.8", "hammerjs": "^2.0.8",
"rxjs": "~6.5.4", "rxjs": "~6.5.4",
"sartography-workflow-lib": "^0.0.45", "sartography-workflow-lib": "^0.0.46",
"tslib": "^1.10.0", "tslib": "^1.10.0",
"uuid": "^3.4.0", "uuid": "^3.4.0",
"zone.js": "~0.9.1" "zone.js": "~0.9.1"

67
src/_config.scss Normal file
View File

@ -0,0 +1,67 @@
// GLOBAL SCSS VARIABLES
$body-font-family: '"franklin-gothic-urw", sans-serif';
$heading-font-family: '"franklin-gothic-urw-cond", sans-serif';
// COLOR PALETTE
// gray
$brand-gray: #4e4e4e;
$brand-gray-tint-1: #666666;
$brand-gray-tint-2: #DADADA;
$brand-gray-tint-3: #F1F1EF;
$brand-gray-tint-4: scale-color($brand-gray, $lightness: 90%);
$body-color: $brand-gray;
$body-color-muted: $brand-gray-tint-1;
$body-color-light: $brand-gray-tint-4;
$brand-gray-muted: $brand-gray-tint-1;
$brand-gray-light: $brand-gray-tint-4;
// primary (UVA "Jefferson Blue")
$brand-primary: #232D4B;
$brand-primary-tint-1: #394E79;
$brand-primary-tint-2: #6C799C;
$brand-primary-tint-3: #A9AFC7;
$brand-primary-tint-4: scale-color($brand-primary, $lightness: 90%);
$brand-primary-shade-1: #092255;
$brand-primary-shade-2: #041D4F;
$brand-primary-shade-3: #02194A;
$brand-primary-shade-4: #021745;
$brand-primary-shade-5: #03143E;
$brand-primary-muted: $brand-primary-tint-1;
$brand-primary-light: $brand-primary-tint-4;
// accent (UVA "Rotunda Orange")
$brand-accent: #E57200;
$brand-accent-tint-1: #F69350;
$brand-accent-tint-2: #FAB584;
$brand-accent-tint-3: #FDD8BB;
$brand-accent-tint-4: scale-color($brand-accent, $lightness: 90%);
$brand-accent-shade-1: #E76E25;
$brand-accent-shade-2: #DD6923;
$brand-accent-shade-3: #D36421;
$brand-accent-shade-4: #C8601F;
$brand-accent-shade-5: #C05B1D;
$brand-accent-muted: $brand-accent-tint-1;
$brand-accent-light: $brand-accent-tint-4;
// warning (UVA "Emergency Red")
$brand-warning: #DF1E43;
$brand-warning-muted: desaturate(scale-color($brand-warning, $lightness: 30%), 20%);
$brand-warning-light: scale-color($brand-warning, $lightness: 90%);
$easeDuration: 300ms;
$animationDuration: 500ms;
$tile-height-1x: 226px;
$tile-height-2x: 288px;
$uva-header-height: 40px;
$site-header-height: 64px;
$search-bar-height-sm: 64px;
$search-bar-height-md: 128px;
$search-bar-height-lg: 64px;
$header-height-sm: ($uva-header-height + $site-header-height + $search-bar-height-sm);
$header-height-md: ($uva-header-height + $site-header-height + $search-bar-height-md);
$header-height-lg: ($uva-header-height + $site-header-height + $search-bar-height-lg);
// Green
$brand-green: #64B343;
$brand-green-muted: #8EC774;
$brand-green-light: #B5D9A3;

92
src/_material.scss Normal file
View File

@ -0,0 +1,92 @@
@import 'config';
@import '../node_modules/@angular/material/theming';
// Define a custom typography config that overrides the font-family
$custom-typography: mat-typography-config(
$font-family: $body-font-family,
$display-4: mat-typography-level(2.500rem, 1.0, 700, $heading-font-family),
$display-3: mat-typography-level(2.250rem, 1.0, 700, $heading-font-family),
$display-2: mat-typography-level(2.000rem, 1.0, 700, $heading-font-family),
$display-1: mat-typography-level(1.750rem, 1.0, 700, $heading-font-family),
$headline: mat-typography-level(48px, 1.0, 700, $heading-font-family),
$title: mat-typography-level(1.500rem, 1.0, 700, $body-font-family),
$subheading-2: mat-typography-level(1.375rem, 1.0, 500, $body-font-family),
$subheading-1: mat-typography-level(1.250rem, 1.0, 500, $body-font-family),
$body-2: mat-typography-level(1.000rem, 1.5, 500, $body-font-family),
$body-1: mat-typography-level(1.000rem, 1.5, 500, $body-font-family),
$caption: mat-typography-level(1.000rem, 1.5, 500, $body-font-family),
$button: mat-typography-level(1.000rem, 1.5, 500, $body-font-family),
$input: mat-typography-level(1.000rem, 1.5, 500, $body-font-family)
);
$mat-blue: (
50: #f1f5f7,
100: #b3c1d3,
200: $brand-primary-tint-3,
300: $brand-primary-tint-2,
400: $brand-primary-tint-1,
500: $brand-primary,
600: $brand-primary-shade-1,
700: $brand-primary-shade-2,
800: $brand-primary-shade-3,
900: $brand-primary-shade-4,
A100: #b3c1d3,
A200: $brand-primary-tint-3,
A400: $brand-primary-tint-2,
A700: $brand-primary-tint-1,
contrast: (
50: $body-color,
100: $body-color,
200: $body-color,
300: #ffffff,
400: #ffffff,
500: #ffffff,
600: #ffffff,
700: #ffffff,
800: #ffffff,
900: #ffffff,
A100: $body-color,
A200: #ffffff,
A400: #ffffff,
A700: #ffffff,
)
);
$mat-orange: (
50: #fceee0,
100: $brand-accent-tint-3,
200: $brand-accent-tint-2,
300: $brand-accent-tint-1,
400: $brand-accent,
500: $brand-accent-shade-1,
600: $brand-accent-shade-2,
700: $brand-accent-shade-3,
800: $brand-accent-shade-4,
900: $brand-accent-shade-5,
A100: #fceee0,
A200: $brand-accent-tint-3,
A400: $brand-accent-tint-2,
A700: $brand-accent-tint-1,
contrast: (
50: $body-color,
100: $body-color,
200: $body-color,
300: $body-color,
400: $body-color,
500: #ffffff,
600: #ffffff,
700: #ffffff,
800: #ffffff,
900: #ffffff,
A100: $body-color,
A200: $body-color,
A400: $body-color,
A700: $body-color,
)
);
$cr-connect-primary: mat-palette($mat-blue);
$cr-connect-accent: mat-palette($mat-orange);
$cr-connect-warn: mat-palette($mat-red);
$cr-connect-theme: mat-light-theme($cr-connect-primary, $cr-connect-accent, $cr-connect-warn);

View File

@ -0,0 +1,4 @@
// tslint:disable-next-line:max-line-length
export const PHONE_REGEX = /^(?:\+?1\s*(?:[.-]\s*)?)?(?:\(\s*([2-9]1[02-9]|[2-9][02-8]1|[2-9][02-8][02-9])\s*\)|([2-9]1[02-9]|[2-9][02-8]1|[2-9][02-8][02-9]))\s*(?:[.-]\s*)?([2-9]1[02-9]|[2-9][02-9]1|[2-9][02-9]{2})\s*(?:[.-]\s*)?([0-9]{4})(?:\s*(?:#|x\.?|ext\.?|extension)\s*(\d+))?$/i;
export default PHONE_REGEX;

View File

@ -0,0 +1,23 @@
export const URL_REGEX = new RegExp(
'^' +
'(?:(?:https?|ftp)://)' +
'(?:\\S+(?::\\S*)?@)?' +
'(?:' +
'(?!(?:10|127)(?:\\.\\d{1,3}){3})' +
'(?!(?:169\\.254|192\\.168)(?:\\.\\d{1,3}){2})' +
'(?!172\\.(?:1[6-9]|2\\d|3[0-1])(?:\\.\\d{1,3}){2})' +
'(?:[1-9]\\d?|1\\d\\d|2[01]\\d|22[0-3])' +
'(?:\\.(?:1?\\d{1,2}|2[0-4]\\d|25[0-5])){2}' +
'(?:\\.(?:[1-9]\\d?|1\\d\\d|2[0-4]\\d|25[0-4]))' +
'|' +
'(?:(?:[a-z\\u00a1-\\uffff0-9]-*)*[a-z\\u00a1-\\uffff0-9]+)' +
'(?:\\.(?:[a-z\\u00a1-\\uffff0-9]-*)*[a-z\\u00a1-\\uffff0-9]+)*' +
'(?:\\.(?:[a-z\\u00a1-\\uffff]{2,}))' +
'\\.?' +
')' +
'(?::\\d{2,5})?' +
'(?:[/?#]\\S*)?' +
'$', 'i'
);
export default URL_REGEX;

View File

@ -0,0 +1,31 @@
import { AbstractControl, ValidationErrors } from '@angular/forms';
export function ValidateUrl(control: AbstractControl): ValidationErrors {
const urlRegEx = new RegExp(
'^' +
'(?:(?:https?|ftp)://)' +
'(?:\\S+(?::\\S*)?@)?' +
'(?:' +
'(?!(?:10|127)(?:\\.\\d{1,3}){3})' +
'(?!(?:169\\.254|192\\.168)(?:\\.\\d{1,3}){2})' +
'(?!172\\.(?:1[6-9]|2\\d|3[0-1])(?:\\.\\d{1,3}){2})' +
'(?:[1-9]\\d?|1\\d\\d|2[01]\\d|22[0-3])' +
'(?:\\.(?:1?\\d{1,2}|2[0-4]\\d|25[0-5])){2}' +
'(?:\\.(?:[1-9]\\d?|1\\d\\d|2[0-4]\\d|25[0-4]))' +
'|' +
'(?:(?:[a-z\\u00a1-\\uffff0-9]-*)*[a-z\\u00a1-\\uffff0-9]+)' +
'(?:\\.(?:[a-z\\u00a1-\\uffff0-9]-*)*[a-z\\u00a1-\\uffff0-9]+)*' +
'(?:\\.(?:[a-z\\u00a1-\\uffff]{2,}))' +
'\\.?' +
')' +
'(?::\\d{2,5})?' +
'(?:[/?#]\\S*)?' +
'$', 'i'
);
if (!urlRegEx.test(control.value) && control.value && control.value !== '') {
const error: ValidationErrors = { url: true };
return error;
}
}

View File

@ -1,16 +1,25 @@
import {NgModule} from '@angular/core'; import {NgModule} from '@angular/core';
import {RouterModule, Routes} from '@angular/router'; import {RouterModule, Routes} from '@angular/router';
import {SessionRedirectComponent} from 'sartography-workflow-lib'; import {SessionRedirectComponent} from 'sartography-workflow-lib';
import {HomeComponent} from './home/home.component';
import {ModelerComponent} from './modeler/modeler.component'; import {ModelerComponent} from './modeler/modeler.component';
import {SignInComponent} from './sign-in/sign-in.component'; import {SignInComponent} from './sign-in/sign-in.component';
import {SignOutComponent} from './sign-out/sign-out.component'; import {SignOutComponent} from './sign-out/sign-out.component';
import {WorkflowSpecListComponent} from './workflow-spec-list/workflow-spec-list.component';
const appRoutes: Routes = [ const routes: Routes = [
{path: 'modeler/:workflowSpecId', component: ModelerComponent}, {
{path: 'modeler/:workflowSpecId/:fileMetaId', component: ModelerComponent}, path: '',
{path: '', component: WorkflowSpecListComponent}, component: HomeComponent
},
{
path: 'modeler/:workflowSpecId',
component: ModelerComponent
},
{
path: 'modeler/:workflowSpecId/:fileMetaId',
component: ModelerComponent
},
{ {
path: 'sign-in', path: 'sign-in',
component: SignInComponent component: SignInComponent
@ -28,7 +37,11 @@ const appRoutes: Routes = [
@NgModule({ @NgModule({
declarations: [], declarations: [],
imports: [ imports: [
RouterModule.forRoot(appRoutes) RouterModule.forRoot(routes, {
scrollPositionRestoration: 'enabled',
anchorScrolling: 'enabled',
scrollOffset: [0, 84],
})
], ],
exports: [RouterModule] exports: [RouterModule]
}) })

View File

@ -1 +1,5 @@
<router-outlet></router-outlet> <div class="mat-typography">
<app-navbar *ngIf="isSignedIn()" class="mat-elevation-z6" id="globalHeader"></app-navbar>
<router-outlet></router-outlet>
<app-footer></app-footer>
</div>

View File

@ -1,4 +1,5 @@
import { Component } from '@angular/core'; import {Component} from '@angular/core';
import {isSignedIn} from 'sartography-workflow-lib';
@Component({ @Component({
selector: 'app-root', selector: 'app-root',
@ -7,4 +8,5 @@ import { Component } from '@angular/core';
}) })
export class AppComponent { export class AppComponent {
title = 'CR Connect Configuration'; title = 'CR Connect Configuration';
isSignedIn = isSignedIn;
} }

View File

@ -19,7 +19,7 @@ import {BrowserModule} from '@angular/platform-browser';
import {BrowserAnimationsModule} from '@angular/platform-browser/animations'; import {BrowserAnimationsModule} from '@angular/platform-browser/animations';
import {FormlyModule} from '@ngx-formly/core'; import {FormlyModule} from '@ngx-formly/core';
import {FormlyMaterialModule} from '@ngx-formly/material'; import {FormlyMaterialModule} from '@ngx-formly/material';
import {AppEnvironment, AuthInterceptor} from 'sartography-workflow-lib'; import {AppEnvironment, AuthInterceptor, SessionRedirectComponent} from 'sartography-workflow-lib';
import {environment} from '../environments/environment'; import {environment} from '../environments/environment';
import {DeleteFileDialogComponent} from './_dialogs/delete-file-dialog/delete-file-dialog.component'; import {DeleteFileDialogComponent} from './_dialogs/delete-file-dialog/delete-file-dialog.component';
import {DeleteWorkflowSpecDialogComponent} from './_dialogs/delete-workflow-spec-dialog/delete-workflow-spec-dialog.component'; import {DeleteWorkflowSpecDialogComponent} from './_dialogs/delete-workflow-spec-dialog/delete-workflow-spec-dialog.component';
@ -33,10 +33,13 @@ import {AppRoutingModule} from './app-routing.module';
import {AppComponent} from './app.component'; import {AppComponent} from './app.component';
import {DiagramComponent} from './diagram/diagram.component'; import {DiagramComponent} from './diagram/diagram.component';
import {FileListComponent} from './file-list/file-list.component'; import {FileListComponent} from './file-list/file-list.component';
import {FooterComponent} from './footer/footer.component';
import {ModelerComponent} from './modeler/modeler.component'; import {ModelerComponent} from './modeler/modeler.component';
import {NavbarComponent} from './navbar/navbar.component';
import {SignInComponent} from './sign-in/sign-in.component'; import {SignInComponent} from './sign-in/sign-in.component';
import {SignOutComponent} from './sign-out/sign-out.component'; import {SignOutComponent} from './sign-out/sign-out.component';
import {WorkflowSpecListComponent} from './workflow-spec-list/workflow-spec-list.component'; import {WorkflowSpecListComponent} from './workflow-spec-list/workflow-spec-list.component';
import { HomeComponent } from './home/home.component';
export class ThisEnvironment implements AppEnvironment { export class ThisEnvironment implements AppEnvironment {
production = environment.production; production = environment.production;
@ -69,14 +72,18 @@ export class AppFormlyConfig {
DiagramComponent, DiagramComponent,
FileListComponent, FileListComponent,
FileMetaDialogComponent, FileMetaDialogComponent,
FooterComponent,
GetIconCodePipe, GetIconCodePipe,
ModelerComponent, ModelerComponent,
NavbarComponent,
NewFileDialogComponent, NewFileDialogComponent,
OpenFileDialogComponent, OpenFileDialogComponent,
SessionRedirectComponent,
SignInComponent, SignInComponent,
SignOutComponent, SignOutComponent,
WorkflowSpecDialogComponent, WorkflowSpecDialogComponent,
WorkflowSpecListComponent, WorkflowSpecListComponent,
HomeComponent,
], ],
imports: [ imports: [
BrowserAnimationsModule, BrowserAnimationsModule,

View File

@ -0,0 +1,3 @@
<footer>
CR Connect - University of Virginia
</footer>

View File

@ -0,0 +1,8 @@
@import "../../_config.scss";
footer {
background-color: $brand-gray;
color: white;
text-align: center;
padding: 20px;
}

View File

@ -0,0 +1,25 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { FooterComponent } from './footer.component';
describe('FooterComponent', () => {
let component: FooterComponent;
let fixture: ComponentFixture<FooterComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ FooterComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(FooterComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -0,0 +1,15 @@
import { Component, OnInit } from '@angular/core';
@Component({
selector: 'app-footer',
templateUrl: './footer.component.html',
styleUrls: ['./footer.component.scss']
})
export class FooterComponent implements OnInit {
constructor() { }
ngOnInit() {
}
}

View File

@ -0,0 +1,2 @@
<app-sign-in *ngIf="!isSignedIn()"></app-sign-in>
<app-workflow-spec-list *ngIf="isSignedIn()"></app-workflow-spec-list>

View File

View File

@ -0,0 +1,25 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { HomeComponent } from './home.component';
describe('HomeComponent', () => {
let component: HomeComponent;
let fixture: ComponentFixture<HomeComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ HomeComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(HomeComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -0,0 +1,15 @@
import {Component} from '@angular/core';
import {isSignedIn} from 'sartography-workflow-lib';
@Component({
selector: 'app-home',
templateUrl: './home.component.html',
styleUrls: ['./home.component.scss']
})
export class HomeComponent {
isSignedIn = isSignedIn;
constructor() {
}
}

View File

@ -0,0 +1,42 @@
<nav *ngIf="isSignedIn() && user">
<a
[routerLink]="['/']"
class="site-title mat-display-1"
mat-button
>CR Connect</a>
<div class="flex-spacer"></div>
<ng-container *ngFor="let link of navLinks">
<a *ngIf="!link.links"
[id]="link.id"
[ngClass]="{'active': isLinkActive(link.path)}"
[routerLink]="link.path"
mat-button
>
{{link.label}}
</a>
<ng-container *ngIf="link.links">
<button
[attr.aria-label]="link.label"
[id]="link.id"
[matMenuTriggerFor]="menu"
mat-button
>
<mat-icon>{{link.icon}}</mat-icon>
{{link.label}}
</button>
<mat-menu #menu="matMenu" xPosition="before">
<button
*ngFor="let menuLink of link.links"
[id]="menuLink.id"
[ngClass]="{'active': isLinkActive(menuLink.path)}"
[routerLink]="menuLink.path"
mat-menu-item
>
<mat-icon *ngIf="menuLink.icon">{{menuLink.icon}}</mat-icon>
<span>{{menuLink.label}}</span>
</button>
</mat-menu>
</ng-container>
</ng-container>
</nav>

View File

@ -0,0 +1,35 @@
@import "../../config";
.flex-spacer {
flex-grow: 1;
}
nav {
display: flex;
flex-wrap: wrap;
align-items: center;
padding: 8px 16px;
background-color: $brand-primary;
color: white;
height: 48px;
.site-title {
color: white;
font-size: 1.5rem;
line-height: 48px;
margin-bottom: 0;
background-color: $brand-primary;
text-transform: uppercase;
}
}
a.active {
background-color: $brand-primary-tint-1;
color: white;
}
.mat-menu-item.active {
background-color: $brand-gray-tint-2;
color: $brand-gray;
font-weight: bold;
}

View File

@ -0,0 +1,56 @@
import {HttpClientTestingModule, HttpTestingController} from '@angular/common/http/testing';
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import {MatIconModule} from '@angular/material/icon';
import {MatMenuModule} from '@angular/material/menu';
import {Router} from '@angular/router';
import {RouterTestingModule} from '@angular/router/testing';
import {ApiService, MockEnvironment, mockUser} from 'sartography-workflow-lib';
import { NavbarComponent } from './navbar.component';
describe('NavbarComponent', () => {
let component: NavbarComponent;
let fixture: ComponentFixture<NavbarComponent>;
let httpMock: HttpTestingController;
const mockRouter = {navigate: jasmine.createSpy('navigate')};
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [
NavbarComponent
],
imports: [
HttpClientTestingModule,
MatIconModule,
MatMenuModule,
RouterTestingModule,
],
providers: [
ApiService,
{
provide: Router,
useValue: mockRouter
},
{provide: 'APP_ENVIRONMENT', useClass: MockEnvironment},
],
})
.compileComponents();
}));
beforeEach(() => {
localStorage.setItem('token', 'some_token');
httpMock = TestBed.get(HttpTestingController);
fixture = TestBed.createComponent(NavbarComponent);
component = fixture.componentInstance;
fixture.detectChanges();
const uReq = httpMock.expectOne('apiRoot/user');
expect(uReq.request.method).toEqual('GET');
uReq.flush(mockUser);
expect(component.user).toEqual(mockUser);
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -0,0 +1,67 @@
import {Component, OnInit} from '@angular/core';
import {Router} from '@angular/router';
import {ApiService, isSignedIn, User} from 'sartography-workflow-lib';
interface NavItem {
path?: string;
id: string;
label: string;
icon?: string;
links?: NavItem[];
action?: () => void;
}
@Component({
selector: 'app-navbar',
templateUrl: './navbar.component.html',
styleUrls: ['./navbar.component.scss']
})
export class NavbarComponent implements OnInit {
navLinks: NavItem[];
user: User;
isSignedIn = isSignedIn;
constructor(
private router: Router,
private api: ApiService,
) {
this._loadUser();
}
ngOnInit() {
}
isLinkActive(path: string) {
return path === this.router.url;
}
private _loadUser() {
if (isSignedIn()) {
this.api.getUser().subscribe(u => {
this.user = u;
this._loadNavLinks();
}, error => {
localStorage.removeItem('token');
this.api.openUrl('/');
});
}
}
private _loadNavLinks() {
const displayName = this.user.display_name || this.user.first_name || this.user.last_name;
this.navLinks = [
{path: '/', id: 'nav_home', label: 'Home'},
{path: '/inbox', id: 'nav_inbox', label: 'Inbox'},
{path: '/help', id: 'nav_help', label: 'Help'},
{
id: 'nav_account', label: `${displayName} (${this.user.email_address})`,
icon: 'account_circle',
links: [
{path: '/profile', id: 'nav_profile', label: 'Profile', icon: 'person'},
{path: '/notifications', id: 'nav_notifications', label: 'Notifications', icon: 'notifications'},
{path: '/sign-out', id: 'nav_sign_out', label: 'Sign out', icon: 'exit_to_app'},
]
}
];
}
}

278
src/material-theme.scss Normal file
View File

@ -0,0 +1,278 @@
@import "material";
@include mat-core($custom-typography);
@include angular-material-theme($cr-connect-theme);
@mixin cr-connect-theme($theme) {
$primary: map-get($theme, primary);
$accent: map-get($theme, accent);
h1 {
text-transform: uppercase;
color: $brand-accent;
}
html, body {
height: 100%;
margin: 0;
padding: 0;
font-family: Roboto, "Helvetica Neue", sans-serif;
}
body {
margin: 0;
padding-top: 64px;
}
mat-radio-button, .mat-checkbox {
padding-right: 16px;
}
formly-field mat-form-field {
margin-bottom: 2rem;
}
formly-field.textarea-cols {
display: flow-root;
& > formly-wrapper-mat-form-field > mat-form-field {
width: auto !important;
.mat-form-field-infix {
width: auto !important;
}
}
}
formly-field.read-only {
.mat-form-field-outline {
background-color: $brand-gray-tint-2;
border-radius: 5px;
}
}
formly-field.vertical-radio-group {
mat-radio-button {
margin: 5px;
padding-right: 16px;
display: block;
label.mat-radio-label {
white-space: normal;
}
}
}
formly-field.vertical-checkbox-group {
.mat-checkbox {
margin: 5px;
padding-right: 16px;
display: block;
.mat-checkbox-layout {
white-space: normal;
}
}
}
.full-height {
width: 100%;
height: calc(100vh - 64px);
}
.container, .row {
padding: 1em;
}
//.container {
// display: grid;
// justify-content: center;
// justify-items: center;
// grid-template-columns: 1fr;
// position: relative;
// max-width: 100vw;
//
// .row {
// margin-top: 4em;
// margin-bottom: 4em;
// position: relative;
//
// max-width: 100vw;
// @media (min-width: 576px) {
// max-width: calc(100% - 40px);
// }
// @media (min-width: 768px) {
// max-width: calc(100% - 80px);
// }
// @media (min-width: 992px) {
// max-width: calc(100% - 100px);
// }
// @media (min-width: 1200px) {
// max-width: calc(100% - 120px);
// }
// }
//}
button {
&.btn-xl {
font-size: 24px;
padding-left: 24px;
padding-right: 24px;
padding-top: 8px;
padding-bottom: 8px;
}
&.btn-lg {
font-size: 20px;
padding-left: 20px;
padding-right: 20px;
padding-top: 6px;
padding-bottom: 6px;
}
&.disabled {
background-color: $brand-gray !important;
}
}
mat-form-field.mat-form-field {
font-size: 16px;
}
.mat-form-field-wrapper .mat-form-field-subscript-wrapper {
position: static;
}
.alert {
padding: 2em;
text-align: center;
border-radius: 4px;
margin-bottom: 2em;
&.alert-info {
background-color: $brand-primary-light;
color: black;
}
&.alert-warn {
background-color: $brand-warning;
color: white;
}
}
.mat-drawer .mat-nav-list .mat-list-item {
min-width: 320px;
}
.mat-drawer .mat-nav-list .mat-list-item.active {
background-color: $brand-primary;
color: white;
}
.pad-0 {
padding: 0px;
}
.pad-1 {
padding: 1em;
}
.pad-2 {
padding: 2em;
}
.pad-3 {
padding: 3em;
}
.pad-4 {
padding: 4em;
}
.pad-5 {
padding: 5em;
}
.pad-6 {
padding: 6em;
}
.pad-7 {
padding: 7em;
}
.pad-8 {
padding: 8em;
}
.margin-top-none, .row.margin-top-none {
margin-top: 0px !important;
}
.margin-bottom-none, .row.margin-bottom-none {
margin-bottom: 0px !important;
}
.ghost {
opacity: 0;
}
// XS
@media (max-width: 575px) {
.cdk-overlay-wrapper .cdk-overlay-pane {
width: 100%;
height: 100%;
}
mat-dialog-container.mat-dialog-container {
border-radius: 0;
}
}
// SM
@media (min-width: 576px) {
.cdk-overlay-wrapper .cdk-overlay-pane {
width: 90%;
height: 90%;
}
mat-dialog-container.mat-dialog-container {
width: 100vw;
}
}
// MD
@media (min-width: 768px) {
.cdk-overlay-wrapper .cdk-overlay-pane {
width: 75%;
height: 75%;
}
mat-dialog-container.mat-dialog-container {
width: 90vw;
}
}
// LG
@media (min-width: 992px) {
.cdk-overlay-wrapper .cdk-overlay-pane {
width: 75%;
height: 75%;
}
mat-dialog-container.mat-dialog-container {
width: 80vw;
}
}
// XL
@media (min-width: 1200px) {
.cdk-overlay-wrapper .cdk-overlay-pane {
width: 75%;
height: 75%;
}
mat-dialog-container.mat-dialog-container {
width: 70vw;
}
}
.mat-dialog-content[mat-dialog-content] {
max-height: 85vh;
}
.loading {
height: calc(100vh - 64px);
}
}

View File

@ -1,16 +1,6 @@
@import '~@angular/material/prebuilt-themes/indigo-pink.css';
@import '~bpmn-js/dist/assets/diagram-js.css'; @import '~bpmn-js/dist/assets/diagram-js.css';
@import '~bpmn-js/dist/assets/bpmn-font/css/bpmn-embedded.css'; @import '~bpmn-js/dist/assets/bpmn-font/css/bpmn-embedded.css';
@import '~bpmn-js-properties-panel/dist/assets/bpmn-js-properties-panel.css'; @import '~bpmn-js-properties-panel/dist/assets/bpmn-js-properties-panel.css';
@import '~diagram-js-minimap/assets/diagram-js-minimap.css'; @import '~diagram-js-minimap/assets/diagram-js-minimap.css';
@import './material-theme.scss';
html, body { @include cr-connect-theme($cr-connect-theme);
height: 100%;
margin: 0;
padding: 0;
font-family: Roboto, "Helvetica Neue", sans-serif;
}
.container, .row {
padding: 1em;
}