From fe1a481f43380aff3c8848d01f0774bb3bb0bf08 Mon Sep 17 00:00:00 2001 From: Aaron Louie Date: Mon, 24 Feb 2020 15:20:18 -0500 Subject: [PATCH] Adds navbar, footer, and Material theme --- angular.json | 6 +- package-lock.json | 6 +- package.json | 2 +- src/_config.scss | 67 +++++ src/_material.scss | 92 +++++++ src/app/_forms/validators/phone.regex.ts | 4 + src/app/_forms/validators/url.regex.ts | 23 ++ src/app/_forms/validators/url.validator.ts | 31 +++ src/app/app-routing.module.ts | 25 +- src/app/app.component.html | 6 +- src/app/app.component.ts | 4 +- src/app/app.module.ts | 9 +- src/app/footer/footer.component.html | 3 + src/app/footer/footer.component.scss | 8 + src/app/footer/footer.component.spec.ts | 25 ++ src/app/footer/footer.component.ts | 15 ++ src/app/home/home.component.html | 2 + src/app/home/home.component.scss | 0 src/app/home/home.component.spec.ts | 25 ++ src/app/home/home.component.ts | 15 ++ src/app/navbar/navbar.component.html | 42 ++++ src/app/navbar/navbar.component.scss | 35 +++ src/app/navbar/navbar.component.spec.ts | 56 +++++ src/app/navbar/navbar.component.ts | 67 +++++ src/material-theme.scss | 278 +++++++++++++++++++++ src/{styles.css => styles.scss} | 14 +- 26 files changed, 831 insertions(+), 29 deletions(-) create mode 100644 src/_config.scss create mode 100644 src/_material.scss create mode 100644 src/app/_forms/validators/phone.regex.ts create mode 100644 src/app/_forms/validators/url.regex.ts create mode 100644 src/app/_forms/validators/url.validator.ts create mode 100644 src/app/footer/footer.component.html create mode 100644 src/app/footer/footer.component.scss create mode 100644 src/app/footer/footer.component.spec.ts create mode 100644 src/app/footer/footer.component.ts create mode 100644 src/app/home/home.component.html create mode 100644 src/app/home/home.component.scss create mode 100644 src/app/home/home.component.spec.ts create mode 100644 src/app/home/home.component.ts create mode 100644 src/app/navbar/navbar.component.html create mode 100644 src/app/navbar/navbar.component.scss create mode 100644 src/app/navbar/navbar.component.spec.ts create mode 100644 src/app/navbar/navbar.component.ts create mode 100644 src/material-theme.scss rename src/{styles.css => styles.scss} (54%) diff --git a/angular.json b/angular.json index 2b3a0b4..c02d3cd 100644 --- a/angular.json +++ b/angular.json @@ -26,10 +26,9 @@ "src/assets" ], "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/bpmn-font/css/bpmn.css", - "src/styles.css" + "src/styles.scss" ], "scripts": [] }, @@ -145,8 +144,7 @@ "tsConfig": "src/tsconfig.spec.json", "karmaConfig": "karma.conf.js", "styles": [ - "./node_modules/@angular/material/prebuilt-themes/indigo-pink.css", - "src/styles.css" + "src/styles.scss" ], "scripts": [], "assets": [ diff --git a/package-lock.json b/package-lock.json index 4ec68d3..dea8929 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11641,9 +11641,9 @@ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" }, "sartography-workflow-lib": { - "version": "0.0.45", - "resolved": "https://registry.npmjs.org/sartography-workflow-lib/-/sartography-workflow-lib-0.0.45.tgz", - "integrity": "sha512-0c3XT5ID0gF8SM18DmD88jDwOccjaViEhJ6DWzh9irblPmvbeA/B7a1MS0i0fm6IC3pw7tdja9pBFeDXz0UOZw==", + "version": "0.0.46", + "resolved": "https://registry.npmjs.org/sartography-workflow-lib/-/sartography-workflow-lib-0.0.46.tgz", + "integrity": "sha512-gxm3mfSyQ/g9Axen4w0HAH+s3mQARVANN5KfPQZgSnvT1wP/ymnhZDikg/hfbUJQZ5qwMv+phKK53HeAsexU3w==", "requires": { "tslib": "^1.9.0" } diff --git a/package.json b/package.json index dc8f5a8..cef6b7e 100644 --- a/package.json +++ b/package.json @@ -47,7 +47,7 @@ "file-saver": "^2.0.2", "hammerjs": "^2.0.8", "rxjs": "~6.5.4", - "sartography-workflow-lib": "^0.0.45", + "sartography-workflow-lib": "^0.0.46", "tslib": "^1.10.0", "uuid": "^3.4.0", "zone.js": "~0.9.1" diff --git a/src/_config.scss b/src/_config.scss new file mode 100644 index 0000000..6293ca1 --- /dev/null +++ b/src/_config.scss @@ -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; diff --git a/src/_material.scss b/src/_material.scss new file mode 100644 index 0000000..fc6a68a --- /dev/null +++ b/src/_material.scss @@ -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); + diff --git a/src/app/_forms/validators/phone.regex.ts b/src/app/_forms/validators/phone.regex.ts new file mode 100644 index 0000000..f55868a --- /dev/null +++ b/src/app/_forms/validators/phone.regex.ts @@ -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; diff --git a/src/app/_forms/validators/url.regex.ts b/src/app/_forms/validators/url.regex.ts new file mode 100644 index 0000000..dfbddf9 --- /dev/null +++ b/src/app/_forms/validators/url.regex.ts @@ -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; diff --git a/src/app/_forms/validators/url.validator.ts b/src/app/_forms/validators/url.validator.ts new file mode 100644 index 0000000..f6d3641 --- /dev/null +++ b/src/app/_forms/validators/url.validator.ts @@ -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; + } +} diff --git a/src/app/app-routing.module.ts b/src/app/app-routing.module.ts index 4b4a84b..e525627 100644 --- a/src/app/app-routing.module.ts +++ b/src/app/app-routing.module.ts @@ -1,16 +1,25 @@ import {NgModule} from '@angular/core'; import {RouterModule, Routes} from '@angular/router'; import {SessionRedirectComponent} from 'sartography-workflow-lib'; +import {HomeComponent} from './home/home.component'; import {ModelerComponent} from './modeler/modeler.component'; import {SignInComponent} from './sign-in/sign-in.component'; import {SignOutComponent} from './sign-out/sign-out.component'; -import {WorkflowSpecListComponent} from './workflow-spec-list/workflow-spec-list.component'; -const appRoutes: Routes = [ - {path: 'modeler/:workflowSpecId', component: ModelerComponent}, - {path: 'modeler/:workflowSpecId/:fileMetaId', component: ModelerComponent}, - {path: '', component: WorkflowSpecListComponent}, +const routes: Routes = [ + { + path: '', + component: HomeComponent + }, + { + path: 'modeler/:workflowSpecId', + component: ModelerComponent + }, + { + path: 'modeler/:workflowSpecId/:fileMetaId', + component: ModelerComponent + }, { path: 'sign-in', component: SignInComponent @@ -28,7 +37,11 @@ const appRoutes: Routes = [ @NgModule({ declarations: [], imports: [ - RouterModule.forRoot(appRoutes) + RouterModule.forRoot(routes, { + scrollPositionRestoration: 'enabled', + anchorScrolling: 'enabled', + scrollOffset: [0, 84], + }) ], exports: [RouterModule] }) diff --git a/src/app/app.component.html b/src/app/app.component.html index 0680b43..c00c62a 100644 --- a/src/app/app.component.html +++ b/src/app/app.component.html @@ -1 +1,5 @@ - +
+ + + +
diff --git a/src/app/app.component.ts b/src/app/app.component.ts index 6ff07e1..bccfc9b 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -1,4 +1,5 @@ -import { Component } from '@angular/core'; +import {Component} from '@angular/core'; +import {isSignedIn} from 'sartography-workflow-lib'; @Component({ selector: 'app-root', @@ -7,4 +8,5 @@ import { Component } from '@angular/core'; }) export class AppComponent { title = 'CR Connect Configuration'; + isSignedIn = isSignedIn; } diff --git a/src/app/app.module.ts b/src/app/app.module.ts index 95c5ad7..9786fec 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -19,7 +19,7 @@ import {BrowserModule} from '@angular/platform-browser'; import {BrowserAnimationsModule} from '@angular/platform-browser/animations'; import {FormlyModule} from '@ngx-formly/core'; 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 {DeleteFileDialogComponent} from './_dialogs/delete-file-dialog/delete-file-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 {DiagramComponent} from './diagram/diagram.component'; import {FileListComponent} from './file-list/file-list.component'; +import {FooterComponent} from './footer/footer.component'; import {ModelerComponent} from './modeler/modeler.component'; +import {NavbarComponent} from './navbar/navbar.component'; import {SignInComponent} from './sign-in/sign-in.component'; import {SignOutComponent} from './sign-out/sign-out.component'; import {WorkflowSpecListComponent} from './workflow-spec-list/workflow-spec-list.component'; +import { HomeComponent } from './home/home.component'; export class ThisEnvironment implements AppEnvironment { production = environment.production; @@ -69,14 +72,18 @@ export class AppFormlyConfig { DiagramComponent, FileListComponent, FileMetaDialogComponent, + FooterComponent, GetIconCodePipe, ModelerComponent, + NavbarComponent, NewFileDialogComponent, OpenFileDialogComponent, + SessionRedirectComponent, SignInComponent, SignOutComponent, WorkflowSpecDialogComponent, WorkflowSpecListComponent, + HomeComponent, ], imports: [ BrowserAnimationsModule, diff --git a/src/app/footer/footer.component.html b/src/app/footer/footer.component.html new file mode 100644 index 0000000..f01d428 --- /dev/null +++ b/src/app/footer/footer.component.html @@ -0,0 +1,3 @@ + diff --git a/src/app/footer/footer.component.scss b/src/app/footer/footer.component.scss new file mode 100644 index 0000000..da58409 --- /dev/null +++ b/src/app/footer/footer.component.scss @@ -0,0 +1,8 @@ +@import "../../_config.scss"; + +footer { + background-color: $brand-gray; + color: white; + text-align: center; + padding: 20px; +} diff --git a/src/app/footer/footer.component.spec.ts b/src/app/footer/footer.component.spec.ts new file mode 100644 index 0000000..2ca6c45 --- /dev/null +++ b/src/app/footer/footer.component.spec.ts @@ -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; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ FooterComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(FooterComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/footer/footer.component.ts b/src/app/footer/footer.component.ts new file mode 100644 index 0000000..da17d82 --- /dev/null +++ b/src/app/footer/footer.component.ts @@ -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() { + } + +} diff --git a/src/app/home/home.component.html b/src/app/home/home.component.html new file mode 100644 index 0000000..8c5bd4a --- /dev/null +++ b/src/app/home/home.component.html @@ -0,0 +1,2 @@ + + diff --git a/src/app/home/home.component.scss b/src/app/home/home.component.scss new file mode 100644 index 0000000..e69de29 diff --git a/src/app/home/home.component.spec.ts b/src/app/home/home.component.spec.ts new file mode 100644 index 0000000..490e81b --- /dev/null +++ b/src/app/home/home.component.spec.ts @@ -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; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ HomeComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(HomeComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/home/home.component.ts b/src/app/home/home.component.ts new file mode 100644 index 0000000..6a863cd --- /dev/null +++ b/src/app/home/home.component.ts @@ -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() { + } + +} diff --git a/src/app/navbar/navbar.component.html b/src/app/navbar/navbar.component.html new file mode 100644 index 0000000..23a137f --- /dev/null +++ b/src/app/navbar/navbar.component.html @@ -0,0 +1,42 @@ + diff --git a/src/app/navbar/navbar.component.scss b/src/app/navbar/navbar.component.scss new file mode 100644 index 0000000..6c4b6c3 --- /dev/null +++ b/src/app/navbar/navbar.component.scss @@ -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; +} diff --git a/src/app/navbar/navbar.component.spec.ts b/src/app/navbar/navbar.component.spec.ts new file mode 100644 index 0000000..c38dabf --- /dev/null +++ b/src/app/navbar/navbar.component.spec.ts @@ -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; + 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(); + }); +}); diff --git a/src/app/navbar/navbar.component.ts b/src/app/navbar/navbar.component.ts new file mode 100644 index 0000000..6eade1a --- /dev/null +++ b/src/app/navbar/navbar.component.ts @@ -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'}, + ] + } + ]; + } +} diff --git a/src/material-theme.scss b/src/material-theme.scss new file mode 100644 index 0000000..36d59eb --- /dev/null +++ b/src/material-theme.scss @@ -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); + } + +} diff --git a/src/styles.css b/src/styles.scss similarity index 54% rename from src/styles.css rename to src/styles.scss index 11dcd9d..f0a98fc 100644 --- a/src/styles.css +++ b/src/styles.scss @@ -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/bpmn-font/css/bpmn-embedded.css'; @import '~bpmn-js-properties-panel/dist/assets/bpmn-js-properties-panel.css'; @import '~diagram-js-minimap/assets/diagram-js-minimap.css'; - -html, body { - height: 100%; - margin: 0; - padding: 0; - font-family: Roboto, "Helvetica Neue", sans-serif; -} - -.container, .row { - padding: 1em; -} +@import './material-theme.scss'; +@include cr-connect-theme($cr-connect-theme);