diff --git a/.circleci/config.yml b/.circleci/config.yml index d65ff4342..4b4dc994e 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -125,6 +125,7 @@ jobs: - run: yarn run build:mosaic-luxon-adapter - run: yarn run build:mosaic-moment-adapter - run: yarn run styles:built-all + - run: yarn run build:mosaic-common-components - persist_to_workspace: root: ~/mosaic paths: @@ -187,6 +188,13 @@ jobs: at: ~/mosaic - run: yarn run unit:mosaic-luxon-adapter + test_unit_mosaic-common-components: + <<: *job_defaults + steps: + - attach_workspace: + at: ~/mosaic + - run: yarn run unit:mosaic-common-components + deploy-docs-next: <<: *job_defaults steps: @@ -350,6 +358,10 @@ workflows: requires: - build_packages + - test_unit_mosaic-common-components: + requires: + - build_packages + - test_unit_mosaic: requires: - build_packages diff --git a/angular.json b/angular.json index 21cac9db9..7d950aed6 100644 --- a/angular.json +++ b/angular.json @@ -211,9 +211,6 @@ "tsConfig": "packages/mosaic/tsconfig.spec.json", "codeCoverage": true } - }, - "configurations": { - "production": {} } } }, @@ -301,6 +298,37 @@ } } }, + "mosaic-common-components": { + "projectType": "library", + "root": "packages/mosaic-common-components", + "sourceRoot": "packages/mosaic-common-components", + "architect": { + "packagr": { + "builder": "@angular-devkit/build-angular:ng-packagr", + "options": { + "tsConfig": "packages/mosaic-common-components/tsconfig.lib.json", + "project": "packages/mosaic-common-components/ng-package.json" + } + }, + "build": { + "builder": "@ptsecurity/builders:packager", + "options": { + "buildTarget": "packagr", + "versionPlaceholder": "{{VERSION}}", + "ngVersionPlaceholder": "{{NG_VERSION}}" + } + }, + "test": { + "builder": "@angular-devkit/build-angular:karma", + "options": { + "karmaConfig": "packages/mosaic-common-components/karma.conf.js", + "main": "packages/mosaic-common-components/test.ts", + "tsConfig": "packages/mosaic-common-components/tsconfig.spec.json", + "codeCoverage": true + } + } + } + }, "schematics": { "projectType": "library", "root": "packages/mosaic/schematics", @@ -2050,6 +2078,46 @@ } } } + }, + "dev-sites-menu": { + "projectType": "application", + "root": "packages/mosaic-dev/sites-menu", + "sourceRoot": "packages/mosaic-dev/sites-menu", + "architect": { + "build": { + "builder": "@angular-devkit/build-angular:browser", + "options": { + "outputPath": "dist/mosaic-dev/sites-menu", + "tsConfig": "packages/mosaic-dev/sites-menu/tsconfig.app.json", + "index": "packages/mosaic-dev/index.html", + "main": "packages/mosaic-dev/sites-menu/main.ts", + "polyfills": "packages/mosaic-dev/polyfills.ts" + }, + "configurations": { + "production": { + "budgets": [ + { + "type": "anyComponentStyle", + "maximumWarning": "6kb" + } + ], + "optimization": true, + "outputHashing": "all", + "sourceMap": false, + "extractCss": true, + "namedChunks": false, + "vendorChunk": false, + "statsJson": true + } + } + }, + "serve": { + "builder": "@angular-devkit/build-angular:dev-server", + "options": { + "browserTarget": "dev-sites-menu:build" + } + } + } } }, diff --git a/package.json b/package.json index 27498060a..950e8624e 100644 --- a/package.json +++ b/package.json @@ -121,6 +121,8 @@ "build:mosaic": "ng build mosaic", "build:mosaic-examples-module": "ts-node --project ./tools/example-module/tsconfig.json ./tools/example-module/index.ts", "build:mosaic-examples": "ng build mosaic-examples", + "build:mosaic-common-components": "ng build mosaic-common-components", + "build:mosaic-common-components:styles": "gulp mosaic-common-components:bundle-theming-scss", "build:schematics": "ng build schematics", "build:schematics-test": "ng build schematics-test", "build:tokens": "node ./packages/mosaic/design-tokens/build.js packages/mosaic/design-tokens/", @@ -131,6 +133,7 @@ "styles:built-all": "gulp styles:built-all", "unit:cdk": "ng test cdk", "unit:mosaic": "ng test mosaic", + "unit:mosaic-common-components": "ng test mosaic-common-components", "unit:mosaic-moment-adapter": "ng test mosaic-moment-adapter", "unit:mosaic-luxon-adapter": "ng test mosaic-luxon-adapter", "unit:schematics": "gulp unit:schematics", @@ -182,6 +185,7 @@ "server-dev:select": "ng serve dev-select --port 3003", "server-dev:sidebar": "ng serve dev-sidebar --port 3003", "server-dev:sidepanel": "ng serve dev-sidepanel --port 3003", + "server-dev:sitemenu": "ng serve dev-sites-menu --port 3003", "server-dev:splitter": "ng serve dev-splitter --port 3003", "server-dev:table": "ng serve dev-table --port 3003", "server-dev:tabs": "ng serve dev-tabs --port 3003", diff --git a/packages/docs/src/app/components/component-viewer/component-overview.template.html b/packages/docs/src/app/components/component-viewer/component-overview.template.html index 423294255..109bbdfce 100644 --- a/packages/docs/src/app/components/component-viewer/component-overview.template.html +++ b/packages/docs/src/app/components/component-viewer/component-overview.template.html @@ -1,19 +1,14 @@
- - {{documentName}} - -
- Oops, {{documentName}} component seems to be lost... But you can help us find it! - Just send a Pull Request to this repository: - Mosaic - -
- - + + + {{docItem.id}} + + + +
diff --git a/packages/docs/src/app/components/component-viewer/component-viewer.component.ts b/packages/docs/src/app/components/component-viewer/component-viewer.component.ts index 237c0dd2e..25ce2b14b 100644 --- a/packages/docs/src/app/components/component-viewer/component-viewer.component.ts +++ b/packages/docs/src/app/components/component-viewer/component-viewer.component.ts @@ -1,7 +1,7 @@ import { animate, state, style, transition, trigger } from '@angular/animations'; import { ChangeDetectorRef, Component, OnDestroy, ViewChild, ViewEncapsulation } from '@angular/core'; import { ActivatedRoute, Router, Params, NavigationStart } from '@angular/router'; -import { combineLatest, Subject } from 'rxjs'; +import { combineLatest, ReplaySubject, Subject } from 'rxjs'; import { map, takeUntil } from 'rxjs/operators'; import { DocItem, DocumentationItems } from '../../shared/documentation-items/documentation-items'; @@ -16,27 +16,42 @@ import { AnchorsComponent } from '../anchors/anchors.component'; }) export class ComponentViewerComponent implements OnDestroy { - componentDocItem: DocItem; + componentDocItem = new ReplaySubject(1); + sections: Set = new Set(['overview', 'api']); private destroyed: Subject = new Subject(); constructor(routeActivated: ActivatedRoute, + private router: Router, public docItems: DocumentationItems ) { + const routeAndParentParams = [routeActivated.params]; + + if (routeActivated.parent) { + routeAndParentParams.push(routeActivated.parent.params); + } + // Listen to changes on the current route for the doc id (e.g. button/checkbox) and the // parent route for the section (mosaic/cdk). - - combineLatest([routeActivated.params, routeActivated.parent.params]).pipe( - map((p: [Params, Params]) => ({id: p[0].id, section: p[1].section})), - map((p) => ({doc: docItems.getItemById(p.id, p.section), section: p.section}), + combineLatest(routeAndParentParams).pipe( + map((params: Params[]) => ({id: params[0].id, section: params[1].section})), + map((docIdAndSection: {id: string; section: string}) => + ({ + doc: docItems.getItemById(docIdAndSection.id, docIdAndSection.section), + section: docIdAndSection.section + } + ), takeUntil(this.destroyed)) - ).subscribe((d) => { - this.componentDocItem = d.doc; + ).subscribe((docItemAndSection: {doc: DocItem | undefined; section: string}) => { + if (docItemAndSection.doc !== undefined) { + this.componentDocItem.next(docItemAndSection.doc); + } }); } ngOnDestroy(): void { this.destroyed.next(); + this.destroyed.complete(); } } @@ -65,8 +80,6 @@ export class ComponentViewerComponent implements OnDestroy { export class ComponentOverviewComponent implements OnDestroy { currentUrl: string; routeSeparator: string = '/overview'; - documentName: string = ''; - documentLost: boolean = false; isLoad: boolean = true; @ViewChild('toc', {static: false}) anchorsComponent: AnchorsComponent; @@ -90,12 +103,22 @@ export class ComponentOverviewComponent implements OnDestroy { }); } + getOverviewDocumentUrl(doc: DocItem) { + // Use the explicit overview path if specified. Otherwise, compute an overview path based + // on the package name and doc item id. Overviews for components are commonly stored in a + // folder named after the component while the overview file is named similarly. e.g. + // `cdk#overlay` -> `cdk/overlay/overlay.md` + // `material#button` -> `material/button/button.md` + const overviewPath = doc.overviewPath || `${doc.packageName}/${doc.id}.html`; + + return `docs-content/overviews/${overviewPath}`; + } + getRoute(route: string): string { return route.split(this.routeSeparator)[0]; } scrollToSelectedContentSection() { - this.documentLost = false; this.showView(); this.createCopyIcons(); @@ -156,7 +179,6 @@ export class ComponentOverviewComponent implements OnDestroy { } showDocumentLostAlert() { - this.documentLost = true; this.showView(); if (this.anchorsComponent) { @@ -165,12 +187,12 @@ export class ComponentOverviewComponent implements OnDestroy { } showView() { - this.documentName = this.componentViewer.componentDocItem.id; this.isLoad = true; this.ref.detectChanges(); } ngOnDestroy() { this.destroyed.next(); + this.destroyed.complete(); } } diff --git a/packages/docs/src/app/components/main-layout/main-layout.component.scss b/packages/docs/src/app/components/main-layout/main-layout.component.scss index aa8e508dc..59fe79e02 100644 --- a/packages/docs/src/app/components/main-layout/main-layout.component.scss +++ b/packages/docs/src/app/components/main-layout/main-layout.component.scss @@ -7,7 +7,7 @@ .content { width: calc(100% - 600px); // for Edge margin: { - top: 64px; + top: 48px; left: 300px; right: 300px; bottom: 62px; diff --git a/packages/docs/src/app/components/main-layout/main-layout.component.ts b/packages/docs/src/app/components/main-layout/main-layout.component.ts index d054f5a9f..e192ffe6e 100644 --- a/packages/docs/src/app/components/main-layout/main-layout.component.ts +++ b/packages/docs/src/app/components/main-layout/main-layout.component.ts @@ -1,32 +1,33 @@ -import { Component } from '@angular/core'; -import { ActivatedRoute, NavigationExtras, Router } from '@angular/router'; +import { Component, ViewEncapsulation } from '@angular/core'; +import { NavigationExtras, Router } from '@angular/router'; @Component({ templateUrl: './main-layout.component.html', - styleUrls: ['./main-layout.component.scss'] + styleUrls: ['./main-layout.component.scss'], + encapsulation: ViewEncapsulation.None }) export class MainLayoutComponent { nextRoute: string = ''; + extras: NavigationExtras = { preserveFragment: true, queryParamsHandling: 'preserve' }; - constructor(private router: Router, - private route: ActivatedRoute) { + constructor(private router: Router) { const href = location.href; if (href.match('github')) { this.setNextRoute(); } else { - this.setDefaultRoute(); + this.setDefaultRoute(); } } setDefaultRoute() { if (location.pathname === '/') { - this.router.navigate(['button/overview'], this.extras); + this.router.navigate(['components/button'], this.extras); } } @@ -36,7 +37,7 @@ export class MainLayoutComponent { if (this.nextRoute) { this.router.navigate([this.nextRoute], this.extras); } else { - this.router.navigate(['button/overview'], this.extras); + this.router.navigate(['components/button'], this.extras); } localStorage.removeItem('PT_nextRoute'); } diff --git a/packages/docs/src/app/components/main-layout/main-layout.module.ts b/packages/docs/src/app/components/main-layout/main-layout.module.ts index a7cafd375..94e3bd927 100644 --- a/packages/docs/src/app/components/main-layout/main-layout.module.ts +++ b/packages/docs/src/app/components/main-layout/main-layout.module.ts @@ -1,12 +1,13 @@ import { CommonModule } from '@angular/common'; import { NgModule } from '@angular/core'; -import { RouterModule } from '@angular/router'; +import { RouterModule, Routes } from '@angular/router'; import { McButtonModule } from '@ptsecurity/mosaic/button'; import { McDropdownModule } from '@ptsecurity/mosaic/dropdown'; import { McIconModule } from '@ptsecurity/mosaic/icon'; import { McTreeModule } from '@ptsecurity/mosaic/tree'; import { AnchorsModule } from '../anchors/anchors.module'; +import { ComponentOverviewComponent, ComponentViewerComponent } from '../component-viewer/component-viewer.component'; import { FooterModule } from '../footer/footer.module'; import { NavbarModule } from '../navbar'; import { SidenavModule } from '../sidenav/sidenav.module'; @@ -14,12 +15,28 @@ import { SidenavModule } from '../sidenav/sidenav.module'; import { MainLayoutComponent } from './main-layout.component'; +const routes: Routes = [{ + path: '', + component: MainLayoutComponent, + children: [ + { + path: ':id', + component: ComponentViewerComponent, + children: [ + {path: '', redirectTo: 'overview', pathMatch: 'full'}, + {path: 'overview', component: ComponentOverviewComponent, pathMatch: 'full'} + ] + } + + ] +}]; + @NgModule({ imports: [ AnchorsModule, CommonModule, - RouterModule, + RouterModule.forChild(routes), McTreeModule, McButtonModule, diff --git a/packages/docs/src/app/components/navbar/_navbar-theme.scss b/packages/docs/src/app/components/navbar/_navbar-theme.scss index 981c596e6..e6e92aef7 100644 --- a/packages/docs/src/app/components/navbar/_navbar-theme.scss +++ b/packages/docs/src/app/components/navbar/_navbar-theme.scss @@ -1,25 +1,37 @@ @mixin mc-docs-navbar-theme($theme) { $foreground: map-get($theme, foreground); $background: map-get($theme, background); - $primary: map-get($theme, primary); $is-dark: map-get($theme, is-dark); $background-color: mc-color($background, background); - $hover: mc-color($background, overlay-hover); - $selected-color: map-get(map-get($theme, states), selected-color); $text: mc-color($foreground, text); $divider: mc-color($foreground, divider); $less-contrast-text: mc-color($foreground, text-less-contrast); $hint-text: mc-color($foreground, text-less-contrast); - .docs-navbar-header { - box-shadow: 0 1px $divider; - background: $background-color; + mc-navbar-title { + color: if($is-dark, #f0f0f0, #4d4d4d) !important; + opacity: 1 !important; + } + + .mc-navbar-item { + color: if($is-dark, #f0f0f0, #4d4d4d) !important; + } + + .mc-navbar .mc-icon { + color: if($is-dark, #f0f0f0, #4d4d4d); + } + + .mc-navbar-item:hover { + background-color: if($is-dark, #666, #d8eaf6) !important; + } - .docs-navbar-dropdown { color: $text; } + .mc-navbar-item.mc-navbar-item-active { + background-color: if($is-dark, #666, #d8eaf6) !important; } - .docs-navbar-logo { - color: $text; + .docs-navbar-header { + box-shadow: 0 1px $divider; + background: $background-color; } .docs-navbar-version { @@ -31,27 +43,13 @@ } } - .color-picker__dropdown-item { - &:hover { - background-color: $hover; - } - - &.mc-selected { - background: $selected-color; - } - } - - .color-picker__dropdown { - padding: 0; + .mc-navbar { + background-color: if($is-dark, #333, white); } } @mixin mc-navbar-typography($config) { - .docs-navbar-version .mc-icon-button { - @include mc-typography-level-to-styles($config, title); - } - .docs-navbar-version_bold { @include mc-typography-level-to-styles($config, body-strong); } diff --git a/packages/docs/src/app/components/navbar/navbar.component.ts b/packages/docs/src/app/components/navbar/navbar.component.ts index 8fb1049e2..40e9fea6f 100644 --- a/packages/docs/src/app/components/navbar/navbar.component.ts +++ b/packages/docs/src/app/components/navbar/navbar.component.ts @@ -165,7 +165,9 @@ export class NavbarComponent implements OnInit, OnDestroy { } ngOnDestroy() { - this.themingSubscription.unsubscribe(); + if (this.themingSubscription) { + this.themingSubscription.unsubscribe(); + } } goToVersion(i: number) { diff --git a/packages/docs/src/app/components/navbar/navbar.module.ts b/packages/docs/src/app/components/navbar/navbar.module.ts index 23b443415..cb1602edb 100644 --- a/packages/docs/src/app/components/navbar/navbar.module.ts +++ b/packages/docs/src/app/components/navbar/navbar.module.ts @@ -1,9 +1,11 @@ import { CommonModule } from '@angular/common'; import { NgModule } from '@angular/core'; +import { RouterModule } from '@angular/router'; import { McButtonModule } from '@ptsecurity/mosaic/button'; import { McDropdownModule } from '@ptsecurity/mosaic/dropdown'; import { McIconModule } from '@ptsecurity/mosaic/icon'; import { McLinkModule } from '@ptsecurity/mosaic/link'; +import { McNavbarModule } from '@ptsecurity/mosaic/navbar'; import { McSelectModule } from '@ptsecurity/mosaic/select'; import { NavbarComponent } from './navbar.component'; @@ -12,10 +14,12 @@ import { NavbarComponent } from './navbar.component'; @NgModule({ imports: [ CommonModule, + RouterModule, McButtonModule, McDropdownModule, McLinkModule, McIconModule, + McNavbarModule, McSelectModule ], exports: [NavbarComponent], diff --git a/packages/docs/src/app/components/navbar/navbar.scss b/packages/docs/src/app/components/navbar/navbar.scss index 63714e500..b83ae65e2 100644 --- a/packages/docs/src/app/components/navbar/navbar.scss +++ b/packages/docs/src/app/components/navbar/navbar.scss @@ -4,42 +4,6 @@ $doc-navbar-height: 64px; $doc-navbar-icon-font-size: 22px; $doc-navbar-padding: 10px; -.docs-navbar-header { - display: flex; - align-items: center; - height: $doc-navbar-height; - padding: 16px 32px; - - .docs-navbar-logo { - display: flex; - - &__img { - max-width: 100%; - width: 32px; - height: 32px; - } - - &__title { - line-height: 32px; - font-size: 28px; - padding: { - left: 8px; - right: 8px; - }; - } - } - - .docs-navbar-dropdown { - min-width: 140px; - - &_hidden { - visibility: hidden; - } - - &__icon { color: inherit; } - } -} - .color-picker { &__icon { font-size: $doc-navbar-icon-font-size; @@ -90,3 +54,29 @@ $doc-navbar-padding: 10px; .color-picker .mc.mc-icon { margin-right: 0; } + +.docs-navbar-dropdown { + min-width: 140px; + + &_hidden { + visibility: hidden; + } + + &__icon { + padding-left: 8px; + } +} + +mc-navbar-brand { + margin-left: 16px; + margin-right: 16px; + padding-right: 8px; + padding-left: 0; + + mc-navbar-logo { + $mc-logo-edge-length: 32px; + + height: $mc-logo-edge-length; + width: $mc-logo-edge-length; + } +} diff --git a/packages/docs/src/app/components/navbar/navbar.template.html b/packages/docs/src/app/components/navbar/navbar.template.html index d8529a026..c2096d6f9 100644 --- a/packages/docs/src/app/components/navbar/navbar.template.html +++ b/packages/docs/src/app/components/navbar/navbar.template.html @@ -1,71 +1,63 @@ -
- - -
- - - - - -
- -
- -
- - - - - -
- -
- - - - - -
+ -
- - - -
+ + + + + + + + + + + + + +
-
-
-
+ diff --git a/packages/docs/src/app/components/sidenav/sidenav.component.html b/packages/docs/src/app/components/sidenav/sidenav.component.html index a1990a99d..32db436c5 100644 --- a/packages/docs/src/app/components/sidenav/sidenav.component.html +++ b/packages/docs/src/app/components/sidenav/sidenav.component.html @@ -1,16 +1,19 @@ diff --git a/packages/docs/src/app/components/sidenav/sidenav.component.ts b/packages/docs/src/app/components/sidenav/sidenav.component.ts index 91d646feb..8d81e2fa0 100644 --- a/packages/docs/src/app/components/sidenav/sidenav.component.ts +++ b/packages/docs/src/app/components/sidenav/sidenav.component.ts @@ -1,5 +1,8 @@ +// tslint:disable:no-unbound-method import { animate, state, style, transition, trigger } from '@angular/animations'; -import { Component, ViewEncapsulation } from '@angular/core'; +import { Component, Input } from '@angular/core'; +import { ActivatedRoute, Params } from '@angular/router'; +import { combineLatest, Observable } from 'rxjs'; import { DocumentationItems } from '../../shared/documentation-items/documentation-items'; @@ -8,7 +11,6 @@ import { DocumentationItems } from '../../shared/documentation-items/documentati selector: 'app-sidenav', templateUrl: './sidenav.component.html', styleUrls: ['./sidenav.scss'], - encapsulation: ViewEncapsulation.None, animations: [ trigger('bodyExpansion', [ state('collapsed', style({maxHeight: '0', visibility: 'hidden'})), @@ -20,13 +22,22 @@ import { DocumentationItems } from '../../shared/documentation-items/documentati }) export class ComponentSidenav { - categories: any; + params: Observable | undefined; + icon: string = 'mc mc-angle-up-M_16'; iconClass: string = 'nav__trigger-icon'; iconClassExpanded: string = `${this.icon} ${this.iconClass} ${this.iconClass}_expanded`; iconClassCollapsed: string = `${this.icon} ${this.iconClass} ${this.iconClass}_collapsed`; - constructor(public docItems: DocumentationItems) { - this.categories = docItems.getCategories('components'); + constructor( + public docItems: DocumentationItems, + private route: ActivatedRoute) { + + } + + ngOnInit() { + // Combine params from all of the path into a single object. + this.params = combineLatest( + this.route.pathFromRoot.map((route) => route.params), Object.assign); } } diff --git a/packages/docs/src/app/components/sidenav/sidenav.module.ts b/packages/docs/src/app/components/sidenav/sidenav.module.ts index cda4da146..dbfde5dbe 100644 --- a/packages/docs/src/app/components/sidenav/sidenav.module.ts +++ b/packages/docs/src/app/components/sidenav/sidenav.module.ts @@ -11,9 +11,9 @@ import { ComponentSidenav } from './sidenav.component'; @NgModule({ imports: [ - RouterModule, CommonModule, McIconModule, + RouterModule, CdkAccordionModule ], exports: [ComponentSidenav], diff --git a/packages/docs/src/app/components/sidenav/sidenav.scss b/packages/docs/src/app/components/sidenav/sidenav.scss index 0f330c6b0..bae98edd4 100644 --- a/packages/docs/src/app/components/sidenav/sidenav.scss +++ b/packages/docs/src/app/components/sidenav/sidenav.scss @@ -7,7 +7,7 @@ .nav { position: fixed; - top: 64px; + top: 48px; bottom: 0; z-index: 101; @@ -56,7 +56,6 @@ & > a { position: relative; - text-decoration: none; padding: 6px 32px; &:hover, &:active, &:focus { text-decoration: none; } diff --git a/packages/docs/src/app/containers/homepage/homepage.module.ts b/packages/docs/src/app/containers/homepage/homepage.module.ts new file mode 100644 index 000000000..d53d1666d --- /dev/null +++ b/packages/docs/src/app/containers/homepage/homepage.module.ts @@ -0,0 +1,13 @@ +import { CommonModule } from '@angular/common'; +import { NgModule } from '@angular/core'; + + +@NgModule({ + imports: [ + CommonModule + ], + exports: [], + declarations: [] +}) +export class HomepageModule { +} diff --git a/packages/docs/src/app/docs.template.html b/packages/docs/src/app/docs.component.html similarity index 100% rename from packages/docs/src/app/docs.template.html rename to packages/docs/src/app/docs.component.html diff --git a/packages/docs/src/app/docs.component.ts b/packages/docs/src/app/docs.component.ts index 429267b65..837c63a7e 100644 --- a/packages/docs/src/app/docs.component.ts +++ b/packages/docs/src/app/docs.component.ts @@ -1,10 +1,11 @@ -import { Component } from '@angular/core'; +import { Component, ViewEncapsulation } from '@angular/core'; @Component({ selector: 'docs-app', - templateUrl: './docs.template.html', - styleUrls: ['./docs.scss'] + templateUrl: './docs.component.html', + styleUrls: ['./docs.scss'], + encapsulation: ViewEncapsulation.None }) export class DocsComponent { diff --git a/packages/docs/src/app/docs.module-routes.ts b/packages/docs/src/app/docs.module-routes.ts index 81f31fe66..d7f33ac6a 100644 --- a/packages/docs/src/app/docs.module-routes.ts +++ b/packages/docs/src/app/docs.module-routes.ts @@ -1,29 +1,12 @@ import { Routes } from '@angular/router'; -import { - ComponentOverviewComponent, - ComponentViewerComponent -} from './components/component-viewer/component-viewer.component'; -import { MainLayoutComponent } from './components/main-layout/main-layout.component'; - export const APP_ROUTES: Routes = [ - { - path: '', - component: MainLayoutComponent, - children: [ - { path: ':id', redirectTo: ':id', pathMatch: 'full' }, - { - path: ':id', - component: ComponentViewerComponent, - children: [ - {path: '', redirectTo: 'overview', pathMatch: 'full'}, - {path: 'overview', component: ComponentOverviewComponent, pathMatch: 'full'}, - {path: '**', redirectTo: 'overview'} - ] - } - ] + { + path: ':section', + loadChildren: () => + import('./components/main-layout/main-layout.module').then((m) => m.MainLayoutModule) }, - { path: '**', redirectTo: '' } + { path: '**', redirectTo: '/404' } ]; diff --git a/packages/docs/src/app/shared/documentation-items/documentation-items.ts b/packages/docs/src/app/shared/documentation-items/documentation-items.ts index 95fbe819b..7382da565 100644 --- a/packages/docs/src/app/shared/documentation-items/documentation-items.ts +++ b/packages/docs/src/app/shared/documentation-items/documentation-items.ts @@ -1,26 +1,67 @@ /* tslint:disable:naming-convention */ import { Injectable } from '@angular/core'; +import { EXAMPLE_COMPONENTS } from '@ptsecurity/mosaic-examples'; +export interface AdditionalApiDoc { + name: string; + path: string; +} + +export interface ExampleSpecs { + prefix: string; + exclude?: string[]; +} + export interface DocItem { + /** Id of the doc item. Used in the URL for linking to the doc. */ id: string; + /** Display name of the doc item. */ name: string; + /** Short summary of the doc item. */ summary?: string; + /** Package which contains the doc item. */ packageName?: string; + /** Specifications for which examples to be load. */ + exampleSpecs?: ExampleSpecs; + /** List of examples. */ examples?: string[]; + /** Optional id of the API document file. */ + apiDocId?: string; + /** Optional path to the overview file of this doc item. */ + overviewPath?: string; + /** List of additional API docs. */ + additionalApiDocs?: AdditionalApiDoc[]; } export interface DocCategory { id: string; name: string; + summary: string; items: DocItem[]; - summary?: string; } +export interface DocSection { + name: string; + summary: string; +} +const exampleNames = Object.keys(EXAMPLE_COMPONENTS); const COMPONENTS = 'components'; +const COMMON = 'common'; const CDK = 'cdk'; +export const SECTIONS: { [key: string]: DocSection } = { + [COMPONENTS]: { + name: 'Components', + summary: 'Angular Mosaic UI components' + }, + [CDK]: { + name: 'CDK', + summary: 'The Component Dev Kit (CDK) is a set of behavior primitives for building UI components.' + } +}; + const DOCS: { [key: string]: DocCategory[] } = { [COMPONENTS]: [ { @@ -347,8 +388,21 @@ const DOCS: { [key: string]: DocCategory[] } = { ] } ], - [CDK]: [ - + [CDK]: [], + [COMMON]: [ + { + id: 'components', + name: 'Components', + summary: '', + items: [ + { + id: 'sites-menu', + name: 'Sites Menu', + summary: '', + examples: ['sites-menu-types'] + } + ] + } ] }; @@ -364,10 +418,16 @@ for (const category of DOCS[CDK]) { } } +for (const category of DOCS[COMMON]) { + for (const doc of category.items) { + doc.packageName = 'common'; + } +} + const ALL_COMPONENTS = DOCS[COMPONENTS].reduce((result, category) => result.concat(category.items), []); -const ALL_CDK = DOCS[CDK].reduce((result, cdk) => result.concat(cdk.items), []); -const ALL_DOCS = ALL_COMPONENTS.concat(ALL_CDK); -const ALL_CATEGORIES = DOCS[COMPONENTS].concat(DOCS[CDK]); +const ALL_CDK = DOCS[CDK].reduce((result, cdk) => result.concat(cdk.items), []); +const ALL_COMMON = DOCS[COMMON].reduce((result, cdk) => result.concat(cdk.items), []); +const ALL_DOCS = ALL_COMPONENTS.concat(ALL_CDK).concat(ALL_COMMON); @Injectable() export class DocumentationItems { @@ -379,6 +439,9 @@ export class DocumentationItems { if (section === COMPONENTS) { return ALL_COMPONENTS; } + if (section === COMMON) { + return ALL_COMMON; + } if (section === CDK) { return ALL_CDK; } @@ -387,12 +450,12 @@ export class DocumentationItems { } getItemById(id: string, section: string): DocItem { - const sectionLookup = section === 'cdk' ? 'cdk' : 'mosaic'; + let aliasToSection = section; - return ALL_DOCS.find((doc) => doc.id === id && doc.packageName === sectionLookup); - } + if (section === 'components') { + aliasToSection = 'mosaic'; + } - getCategoryById(id: string): DocCategory { - return ALL_CATEGORIES.find((c) => c.id === id); + return ALL_DOCS.find((doc) => doc.id === id && doc.packageName === aliasToSection); } } diff --git a/packages/docs/src/app/shared/stackblitz/stackblitz-button.scss b/packages/docs/src/app/shared/stackblitz/stackblitz-button.scss index 24df55341..7cf5c5dad 100644 --- a/packages/docs/src/app/shared/stackblitz/stackblitz-button.scss +++ b/packages/docs/src/app/shared/stackblitz/stackblitz-button.scss @@ -6,6 +6,11 @@ z-index: 100; padding: 10px; + + svg { + margin-right: 4px; + margin-bottom: 2px; + } } &__icon { diff --git a/packages/docs/src/assets/stackblitz/.browserslistrc b/packages/docs/src/assets/stackblitz/.browserslistrc new file mode 100644 index 000000000..427441dc9 --- /dev/null +++ b/packages/docs/src/assets/stackblitz/.browserslistrc @@ -0,0 +1,17 @@ +# This file is used by the build system to adjust CSS and JS output to support the specified browsers below. +# For additional information regarding the format and rule options, please see: +# https://github.com/browserslist/browserslist#queries + +# For the full list of supported browsers by the Angular framework, please see: +# https://angular.io/guide/browser-support + +# You can see what browsers were selected by your queries by running: +# npx browserslist + +last 1 Chrome version +last 1 Firefox version +last 2 Edge major versions +last 2 Safari major versions +last 2 iOS major versions +Firefox ESR +not IE 11 # Angular supports IE 11 only as an opt-in. To opt-in, remove the 'not' prefix on this line. diff --git a/packages/docs/src/assets/stackblitz/angular.json b/packages/docs/src/assets/stackblitz/angular.json index 424d1e1ce..e0e0083ca 100644 --- a/packages/docs/src/assets/stackblitz/angular.json +++ b/packages/docs/src/assets/stackblitz/angular.json @@ -29,8 +29,8 @@ "optimization": true, "outputHashing": "all", "sourceMap": false, - "extractCss": true, "namedChunks": false, + "aot": true, "extractLicenses": true, "vendorChunk": false, "buildOptimizer": true, diff --git a/packages/docs/src/assets/stackblitz/main.ts b/packages/docs/src/assets/stackblitz/main.ts index ba9e97f23..30b87227e 100644 --- a/packages/docs/src/assets/stackblitz/main.ts +++ b/packages/docs/src/assets/stackblitz/main.ts @@ -7,8 +7,6 @@ import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; import { MosaicDocsExample } from './app/mosaic-docs-example'; import { DemoMosaicModule } from './mosaic-module'; -// tslint:disable-next-line:no-import-side-effect -import './polyfills'; @NgModule({ @@ -22,10 +20,11 @@ import './polyfills'; ], entryComponents: [MosaicDocsExample], declarations: [MosaicDocsExample], - bootstrap: [MosaicDocsExample], - providers: [] + bootstrap: [MosaicDocsExample] }) export class AppModule { } -platformBrowserDynamic().bootstrapModule(AppModule); +platformBrowserDynamic().bootstrapModule(AppModule) + // tslint:disable-next-line:no-console + .catch((err) => console.error(err)); diff --git a/packages/docs/src/assets/stackblitz/polyfills.ts b/packages/docs/src/assets/stackblitz/polyfills.ts index 3de84127e..aa9ca8a86 100644 --- a/packages/docs/src/assets/stackblitz/polyfills.ts +++ b/packages/docs/src/assets/stackblitz/polyfills.ts @@ -1,2 +1,2 @@ // tslint:disable:no-import-side-effect -import 'zone.js/dist/zone'; +import 'zone.js'; diff --git a/packages/docs/tsconfig.json b/packages/docs/tsconfig.json index f16bc6041..2cc434b2b 100644 --- a/packages/docs/tsconfig.json +++ b/packages/docs/tsconfig.json @@ -20,8 +20,5 @@ "es2018", "dom" ] - }, - "angularCompilerOptions": { - } } diff --git a/packages/mosaic-common-components/LICENSE b/packages/mosaic-common-components/LICENSE new file mode 100644 index 000000000..7d4e0a78d --- /dev/null +++ b/packages/mosaic-common-components/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2021 Positive Technologies + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/packages/mosaic-common-components/README.md b/packages/mosaic-common-components/README.md new file mode 100644 index 000000000..69b07738e --- /dev/null +++ b/packages/mosaic-common-components/README.md @@ -0,0 +1,6 @@ +Mosaic Common Components +======= + +The sources for this package are in the main [Mosaic](https://github.com/positive-js/mosaic) repo. + +License: MIT diff --git a/packages/mosaic-common-components/core/theming/_all-theme.scss b/packages/mosaic-common-components/core/theming/_all-theme.scss new file mode 100644 index 000000000..fc6c3746a --- /dev/null +++ b/packages/mosaic-common-components/core/theming/_all-theme.scss @@ -0,0 +1,8 @@ +// Import all the theming +@import '../../sites-menu/sites-menu.theme'; + + +@mixin mosaic-common-components($theme) { + + @include sites-menu-theme(); +} diff --git a/packages/mosaic-common-components/i18n/en-US/sites-menu.json b/packages/mosaic-common-components/i18n/en-US/sites-menu.json new file mode 100644 index 000000000..6f90b52e2 --- /dev/null +++ b/packages/mosaic-common-components/i18n/en-US/sites-menu.json @@ -0,0 +1,6 @@ +{ + "Common.Hamburger.Label.MainSiteTitle": "", + "Common.Hamburger.Label.OtherAppsTitle": "", + "Common.Hamburger.SearchInput.NotFound": "", + "Common.Hamburger.SearchPlaceholder.Title": "" +} diff --git a/packages/mosaic-common-components/i18n/ru-RU/sites-menu.json b/packages/mosaic-common-components/i18n/ru-RU/sites-menu.json new file mode 100644 index 000000000..78ba31e33 --- /dev/null +++ b/packages/mosaic-common-components/i18n/ru-RU/sites-menu.json @@ -0,0 +1,6 @@ +{ + "Common.Hamburger.Label.MainSiteTitle": "Главная площадка", + "Common.Hamburger.Label.OtherAppsTitle": "Другие площадки", + "Common.Hamburger.SearchInput.NotFound": "Ничего не найдено", + "Common.Hamburger.SearchPlaceholder.Title": "Название" +} diff --git a/packages/mosaic-common-components/index.ts b/packages/mosaic-common-components/index.ts new file mode 100644 index 000000000..7e1a213e3 --- /dev/null +++ b/packages/mosaic-common-components/index.ts @@ -0,0 +1 @@ +export * from './public-api'; diff --git a/packages/mosaic-common-components/karma.conf.js b/packages/mosaic-common-components/karma.conf.js new file mode 100644 index 000000000..c22722f95 --- /dev/null +++ b/packages/mosaic-common-components/karma.conf.js @@ -0,0 +1,10 @@ +const getBaseKarmaConfig = require('../../karma.conf'); + + +module.exports = function(config) { + + const baseConfig = getBaseKarmaConfig(); + config.set({ + ...baseConfig + }); +}; diff --git a/packages/mosaic-common-components/ng-package.json b/packages/mosaic-common-components/ng-package.json new file mode 100644 index 000000000..d6e2df61f --- /dev/null +++ b/packages/mosaic-common-components/ng-package.json @@ -0,0 +1,7 @@ +{ + "$schema": "../../node_modules/ng-packagr/ng-package.schema.json", + "dest": "../../dist/mosaic-common-components", + "lib": { + "entryFile": "index.ts" + } +} diff --git a/packages/mosaic-common-components/package.json b/packages/mosaic-common-components/package.json new file mode 100644 index 000000000..12e27c3df --- /dev/null +++ b/packages/mosaic-common-components/package.json @@ -0,0 +1,30 @@ +{ + "name": "@ptsecurity/mosaic-common-components", + "version": "{{VERSION}}", + "description": "Mosaic Common Components", + "repository": { + "type": "git", + "url": "https://github.com/positive-js/mosaic.git" + }, + "keywords": [ + "angular", + "mosaic", + "components", + "common", + "ptsecurity" + ], + "license": "MIT", + "bugs": { + "url": "https://github.com/positive-js/mosaic/issues" + }, + "homepage": "https://github.com/positive-js/mosaic#readme", + "peerDependencies": { + "@ptsecurity/cdk": "{{VERSION}}", + "@ptsecurity/mosaic": "{{VERSION}}", + "@ptsecurity/mosaic-moment-adapter": "{{VERSION}}", + "@ptsecurity/mosaic-icons": "^5.0.0" + }, + "dependencies": { + "tslib": "^2.0.1" + } +} diff --git a/packages/mosaic-common-components/public-api.ts b/packages/mosaic-common-components/public-api.ts new file mode 100644 index 000000000..cb0ff5c3b --- /dev/null +++ b/packages/mosaic-common-components/public-api.ts @@ -0,0 +1 @@ +export {}; diff --git a/packages/mosaic-common-components/sites-menu/_sites-menu.theme.scss b/packages/mosaic-common-components/sites-menu/_sites-menu.theme.scss new file mode 100644 index 000000000..35a2c1785 --- /dev/null +++ b/packages/mosaic-common-components/sites-menu/_sites-menu.theme.scss @@ -0,0 +1,21 @@ +@mixin sites-menu-theme($foreground) { + $text-color: map-get($foreground, text); + $text-less-contrast: map-get($foreground, text-less-contrast); + $divider: map-get($foreground, divider); + + .sites-tree-menu-item { + color: $text-color; + } + + .sites-tree-menu-item__description { + color: $text-less-contrast; + } + + .sites-tree-hamburger__divider { + background-color: $divider; + } + + .sites-tree-hamburger__search-button-wrap { + color: $text-color; + } +} diff --git a/packages/mosaic-common-components/sites-menu/index.ts b/packages/mosaic-common-components/sites-menu/index.ts new file mode 100644 index 000000000..7e1a213e3 --- /dev/null +++ b/packages/mosaic-common-components/sites-menu/index.ts @@ -0,0 +1 @@ +export * from './public-api'; diff --git a/packages/mosaic-common-components/sites-menu/package.json b/packages/mosaic-common-components/sites-menu/package.json new file mode 100644 index 000000000..5c646d456 --- /dev/null +++ b/packages/mosaic-common-components/sites-menu/package.json @@ -0,0 +1,10 @@ +{ + "ngPackage": { + "lib": { + "entryFile": "index.ts", + "umdModuleIds": { + "@ptsecurity/mosaic-common-components/sites-menu": "mc.sitesMenu" + } + } + } +} diff --git a/packages/mosaic-common-components/sites-menu/public-api.ts b/packages/mosaic-common-components/sites-menu/public-api.ts new file mode 100644 index 000000000..e1d2c307f --- /dev/null +++ b/packages/mosaic-common-components/sites-menu/public-api.ts @@ -0,0 +1,3 @@ +export * from './sites-menu.module'; +export * from './sites-menu.component'; +export * from './sites-menu.types'; diff --git a/packages/mosaic-common-components/sites-menu/sites-menu.component.spec.ts b/packages/mosaic-common-components/sites-menu/sites-menu.component.spec.ts new file mode 100644 index 000000000..1e270328a --- /dev/null +++ b/packages/mosaic-common-components/sites-menu/sites-menu.component.spec.ts @@ -0,0 +1,41 @@ +import { Component } from '@angular/core'; +import { ComponentFixture, fakeAsync, TestBed } from '@angular/core/testing'; + +import { SitesMenuModule } from './sites-menu.module'; + + +describe('SitesMenuComponent', () => { + + let component: TestApp; + let fixture: ComponentFixture; + + beforeEach(fakeAsync(() => { + TestBed.configureTestingModule({ + imports: [SitesMenuModule], + declarations: [TestApp] + }); + + TestBed.compileComponents(); + })); + + beforeEach(fakeAsync(() => { + fixture = TestBed.createComponent(TestApp); + component = fixture.componentInstance; + fixture.detectChanges(); + })); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); + + +@Component({ + selector: 'test-app', + template: ` + + ` +}) +class TestApp { + +} diff --git a/packages/mosaic-common-components/sites-menu/sites-menu.component.ts b/packages/mosaic-common-components/sites-menu/sites-menu.component.ts new file mode 100644 index 000000000..23c96c264 --- /dev/null +++ b/packages/mosaic-common-components/sites-menu/sites-menu.component.ts @@ -0,0 +1,301 @@ +// tslint:disable:no-unbound-method +import { + Component, + EventEmitter, + Input, + OnChanges, + Output, + TemplateRef, ViewChild, + ViewEncapsulation +} from '@angular/core'; +import { FlatTreeControl } from '@ptsecurity/cdk/tree'; +import { McHighlightPipe } from '@ptsecurity/mosaic/core'; +import { McTreeFlatDataSource, McTreeFlattener } from '@ptsecurity/mosaic/tree'; + +import { Site, Application, ApplicationTypeEnum } from './sites-menu.types'; +import { McInput } from '@ptsecurity/mosaic/input'; +import { McDropdownTrigger } from '@ptsecurity/mosaic/dropdown'; + + +// tslint:disable-next-line:naming-convention +interface UrlParts { + protocol: string; + hostname: string; + port: string; +} + +class MenuItemFlatNode { + id: string; + name: string; + level: number; + expandable: boolean; + applications: Application[]; + link?: string; + isMain: boolean; + alias: string; + parent: any; + // tslint:disable-next-line:no-reserved-keywords + type: ApplicationTypeEnum; +} + +@Component({ + selector: 'sites-menu', + templateUrl: './sites-menu.template.html', + styleUrls: ['./sites-menu.scss'], + encapsulation: ViewEncapsulation.None +}) +export class SitesMenuComponent implements OnChanges { + @Input() sites: Site; + @Input() searchPlaceholder: string = ''; + @Input() mainSiteTitle: TemplateRef; + @Input() otherAppsTitle: TemplateRef; + @Input() searchNothingFound: TemplateRef; + @Input() hasBackdrop = false; + @Input() backdropClass = 'cdk-overlay-transparent-backdrop'; + + @Output() onDetectCurrentSite = new EventEmitter(); + @Output() openedChange = new EventEmitter(); + + @ViewChild(McInput) mcInput: McInput; + @ViewChild(McDropdownTrigger) mcDropdownTrigger: McDropdownTrigger; + + otherSites: Site[]; + mainSite: Site; + currentSite: Site; + appsOfCurrentSite: Application[]; + + isSingleInstallation: boolean = false; + isShowingSearchInput: boolean = false; + + canShowAlias: boolean = false; + nothingFound: boolean = false; + + filterValue: string = ''; + + selectedAppIdInTree: string[]; + selectedAppIdInList: string[]; + + treeControl: FlatTreeControl; + treeFlattener: McTreeFlattener; + dataSource: McTreeFlatDataSource; + + constructor(private mcHighlightPipe: McHighlightPipe) { + this.configureTreeSelect(); + + this.dataSource.data = []; + } + + get opened(): boolean { + return this.mcDropdownTrigger?.opened; + } + + ngOnChanges(): void { + if (this.sites) { + // делаем плоский список для удобства работы + const flatSiteList = this.flattenRecursive([this.sites]); + + // формируем наборы данных для визуализации + this.isSingleInstallation = flatSiteList.length === 1; + + this.canShowAlias = [] + // @ts-ignore + .concat(...flatSiteList.map((site: Site) => site.applications || [])) + .filter((app: Application) => app.type === ApplicationTypeEnum.MPSIEM) + .length > 1; + + // @ts-ignore + this.currentSite = this.getCurrentSite(flatSiteList); + // @ts-ignore + this.mainSite = flatSiteList.find((site: Site) => site.id === this.sites.id); + this.otherSites = this.getOtherSites(flatSiteList); + + this.setAppsOfCurrentSite(this.currentSite!.applications); + this.dataSource.data = this.otherSites || []; + } + } + + highlightText(name, filterValue): string { + return this.mcHighlightPipe.transform(name, filterValue); + } + + toggleSearchInput() { + setTimeout(() => this.focusMcInput()); + this.filterValue = ''; + this.nothingFound = false; + this.treeControl.filterNodes(this.filterValue); + this.isShowingSearchInput = !this.isShowingSearchInput; + } + + hasChild(_: number, nodeData: MenuItemFlatNode): boolean { + return nodeData.expandable; + } + + onFilterChange(value: string) { + this.filterValue = value; + this.treeControl.filterNodes(this.filterValue); + this.nothingFound = this.treeControl.filterModel.selected.length === 0; + } + + handlerClose($event: MouseEvent) { + $event.stopPropagation(); + } + + resetFilter($event: MouseEvent) { + // mc-cleaner sets value to null which causes errors in the console + $event.stopPropagation(); + this.onFilterChange(''); + } + + hasMaxPatrol10Alias(node): boolean { + return this.canShowAlias && (node.type === ApplicationTypeEnum.MPSIEM); + } + + onSelectionChangedInTree() { + this.selectedAppIdInList = []; + } + + onSelectionChangedInList() { + this.selectedAppIdInTree = []; + } + + onDropDownOpened(): void { + this.openedChange.emit(true); + } + + onDropDownClosed(): void { + this.openedChange.emit(true); + } + + private focusMcInput() { + this.mcInput.focus(); + } + + private getCurrentSite(sites: Site[]) { + const currentSite = sites.find((site: Site) => site.isCurrent); + + // TODO + this.onDetectCurrentSite.emit(currentSite); + + return currentSite; + } + + private getOtherSites(sites: Site[]) { + const otherSites = [] + // @ts-ignore + .concat(...sites.filter((site: Site) => site.id !== this.currentSite?.id)); + + return otherSites.sort((a: Site, b: Site) => { + if (b.id === this.mainSite?.id) { + return 1; + } + + return a.name > b.name ? 1 : a.name === b.name ? 0 : -1; + }); + } + + private setAppsOfCurrentSite(applications: Application[]): void { + this.appsOfCurrentSite = applications || []; + this.selectCurrentApp(applications); + } + + private selectCurrentApp(applications: Application[]): void { + const currentApp = applications.find((app: Application) => { + const appUrlParts = this.getUrlParts(app.endpoint); + const currentWindowUrlParts = this.getUrlParts(window.location.href); + + return appUrlParts.protocol === currentWindowUrlParts.protocol && + appUrlParts.port === currentWindowUrlParts.port && + appUrlParts.hostname === currentWindowUrlParts.hostname; + }); + + this.selectedAppIdInList = currentApp ? [currentApp.id] : ['']; + } + + private configureTreeSelect() { + // @ts-ignore + this.treeFlattener = new McTreeFlattener( + this.transformer, + this.getLevel, + this.isExpandable, + // @ts-ignore + this.getChildren + ); + + this.treeControl = new FlatTreeControl( + this.getLevel, + this.isExpandable, + this.getValue, + this.getViewValue + ); + + this.dataSource = new McTreeFlatDataSource(this.treeControl, this.treeFlattener); + } + + private flattenRecursive(tree: Site[], flat: any[]= []): Site[] { + for (const node of tree) { + flat.push({ ...node, children: [] }); + + if (node.children) { + this.flattenRecursive(node.children, flat); + } + } + + return flat; + } + + private transformer(node, level: number, parent: any) { + const flatNode = new MenuItemFlatNode(); + + flatNode.id = node.id; + flatNode.name = node.name || node.displayName; + flatNode.alias = node.alias; + flatNode.parent = parent; + flatNode.level = level; + flatNode.expandable = !!node.children; + flatNode.link = node.endpoint; + flatNode.isMain = node.isMain; + flatNode.type = node.type; + + return flatNode; + } + + private getLevel(node: MenuItemFlatNode): number { + return node.level; + } + + private isExpandable(node: MenuItemFlatNode): boolean { + return node.expandable; + } + + private getChildren(node: MenuItemFlatNode): Application[] { + return node.applications; + } + + private getValue(node: MenuItemFlatNode): string { + return node.id; + } + + private getViewValue(node: MenuItemFlatNode): string { + return node.name; + } + + private getUrlParts(url: string): UrlParts { + const PROTOCOL_TO_PORT = { + http: 80, + https: 443 + }; + + const link = document.createElement('a'); + link.href = url; + + const protocol = link.protocol.replace(':', '').toLocaleLowerCase(); + const port = link.port ? link.port : PROTOCOL_TO_PORT[protocol]?.toString(); + + return { + hostname: link.hostname, + protocol, + port + }; + } +} + diff --git a/packages/mosaic-common-components/sites-menu/sites-menu.md b/packages/mosaic-common-components/sites-menu/sites-menu.md new file mode 100644 index 000000000..0bc5c5461 --- /dev/null +++ b/packages/mosaic-common-components/sites-menu/sites-menu.md @@ -0,0 +1,5 @@ + + + + + diff --git a/packages/mosaic-common-components/sites-menu/sites-menu.module.ts b/packages/mosaic-common-components/sites-menu/sites-menu.module.ts new file mode 100644 index 000000000..599b0cd15 --- /dev/null +++ b/packages/mosaic-common-components/sites-menu/sites-menu.module.ts @@ -0,0 +1,36 @@ +import { CommonModule } from '@angular/common'; +import { NgModule } from '@angular/core'; +import { FormsModule } from '@angular/forms'; +import { McHighlightModule, McHighlightPipe } from '@ptsecurity/mosaic/core'; +import { McDropdownModule } from '@ptsecurity/mosaic/dropdown'; +import { McFormFieldModule } from '@ptsecurity/mosaic/form-field'; +import { McIconModule } from '@ptsecurity/mosaic/icon'; +import { McInputModule } from '@ptsecurity/mosaic/input'; +import { McListModule } from '@ptsecurity/mosaic/list'; +import { McTreeModule } from '@ptsecurity/mosaic/tree'; + +import { SitesMenuComponent } from './sites-menu.component'; + + +const MOSAIC_MODULES = [ + McDropdownModule, + McFormFieldModule, + McTreeModule, + McInputModule, + McIconModule, + McListModule, + McHighlightModule +]; + +@NgModule({ + declarations: [SitesMenuComponent], + imports: [ + CommonModule, + FormsModule, + ...MOSAIC_MODULES + ], + exports: [SitesMenuComponent], + providers: [McHighlightPipe] +}) +export class SitesMenuModule { +} diff --git a/packages/mosaic-common-components/sites-menu/sites-menu.scss b/packages/mosaic-common-components/sites-menu/sites-menu.scss new file mode 100644 index 000000000..98da1439e --- /dev/null +++ b/packages/mosaic-common-components/sites-menu/sites-menu.scss @@ -0,0 +1,40 @@ +.sites-tree-hamburger { + height: 100%; + display: flex; + align-items: center; + padding: 0 16px; +} + +.sites-tree-hamburger__dropdown { + min-width: 290px !important; +} + +.sites-tree-hamburger__divider { + height: 1px; + margin: 4px 0; + padding: 0; +} + +.sites-tree-hamburger__search-button-wrap { + padding: 10px 16px 10px 18px; + font-weight: bold; + + .search-icon { + cursor: pointer; + } +} + +.sites-tree-hamburger__search-input_hidden { + display: none; +} + +.sites-tree-hamburger__search { + padding: 4px 16px; +} + +.sites-tree-hamburger__search_nothing-found { + height: 28px; + padding: 0 14px; + display: flex; + align-items: center; +} diff --git a/packages/mosaic-common-components/sites-menu/sites-menu.template.html b/packages/mosaic-common-components/sites-menu/sites-menu.template.html new file mode 100644 index 000000000..6f49917e8 --- /dev/null +++ b/packages/mosaic-common-components/sites-menu/sites-menu.template.html @@ -0,0 +1,105 @@ +
+ +
+ + + + + + {{currentSite.name}} + + + + + + + + + + {{application.displayName}} + + + {{application.alias}} + + + + + + +
+ +
+ +
+ +
+
+
+ +
+ + +
+ +
+ + + + + + + + + {{node.alias}} + + + + + + + + +   + + + + + + +
+
+
diff --git a/packages/mosaic-common-components/sites-menu/sites-menu.types.ts b/packages/mosaic-common-components/sites-menu/sites-menu.types.ts new file mode 100644 index 000000000..b093470ce --- /dev/null +++ b/packages/mosaic-common-components/sites-menu/sites-menu.types.ts @@ -0,0 +1,29 @@ +/* tslint:disable:naming-convention */ +export interface Site { + address: string; + alias: string; + applications: Application[]; + children: Site[]; + id: string; + isCurrent: boolean; + name: string; +} + +export interface Application { + id: string; + alias: string; + displayName: string; + endpoint: string; + // tslint:disable-next-line:no-reserved-keywords + type: ApplicationTypeEnum; +} + +export enum ApplicationTypeEnum { + AF = 'PT.AF', + CSC = 'PT.CSC', + Cybsi = 'PT.Cybsi', + MC = 'PTMC', + MPSIEM = 'PT.MPSIEM', + NAD = 'PT.NAD', + PTKB = 'PT.PTKB' +} diff --git a/packages/mosaic-common-components/test.ts b/packages/mosaic-common-components/test.ts new file mode 100644 index 000000000..94c1d875b --- /dev/null +++ b/packages/mosaic-common-components/test.ts @@ -0,0 +1,66 @@ +/* tslint:disable */ +import 'zone.js/dist/zone'; +import 'zone.js/dist/zone-testing'; + +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { + BrowserDynamicTestingModule, + platformBrowserDynamicTesting +} from '@angular/platform-browser-dynamic/testing'; + + +const testBed = TestBed.initTestEnvironment( + [BrowserDynamicTestingModule], + platformBrowserDynamicTesting() +); + +patchTestBedToDestroyFixturesAfterEveryTest(testBed); + +(window as any).module = {}; +(window as any).isNode = false; +(window as any).isBrowser = true; +(window as any).global = window; + + +/** + * Monkey-patches TestBed.resetTestingModule such that any errors that occur during component + * destruction are thrown instead of silently logged. Also runs TestBed.resetTestingModule after + * each unit test. + * + * Without this patch, the combination of two behaviors is problematic: + * - TestBed.resetTestingModule catches errors thrown on fixture destruction and logs them without + * the errors ever being thrown. This means that any component errors that occur in ngOnDestroy + * can encounter errors silently and still pass unit tests. + * - TestBed.resetTestingModule is only called *before* a test is run, meaning that even *if* the + * aforementioned errors were thrown, they would be reported for the wrong test (the test that's + * about to start, not the test that just finished). + */ +function patchTestBedToDestroyFixturesAfterEveryTest(testBedInstance: TestBed) { + + const _resetTestingModule = testBedInstance.resetTestingModule; + + // Monkey-patch the resetTestingModule to destroy fixtures outside of a try/catch block. + // With https://github.com/angular/angular/commit/2c5a67134198a090a24f6671dcdb7b102fea6eba + // errors when destroying components are no longer causing Jasmine to fail. + testBedInstance.resetTestingModule = function(this: {_activeFixtures: ComponentFixture[]}) { + try { + this._activeFixtures.forEach((fixture: ComponentFixture) => fixture.destroy()); + } finally { + this._activeFixtures = []; + // Regardless of errors or not, run the original reset testing module function. + _resetTestingModule.call(this); + } + }; + + // Angular's testing package resets the testing module before each test. This doesn't work well + // for us because it doesn't allow developers to see what test actually failed. + // Fixing this by resetting the testing module after each test. + // https://github.com/angular/angular/blob/master/packages/core/testing/src/before_each.ts#L25 + afterEach(() => testBedInstance.resetTestingModule()); +} + +declare const require: any; + +const context = require.context('./', true, /^((?!schematics).)*\.spec.ts$/); + +context.keys().map(context); diff --git a/packages/mosaic-common-components/tsconfig.json b/packages/mosaic-common-components/tsconfig.json new file mode 100644 index 000000000..1bdf93eb0 --- /dev/null +++ b/packages/mosaic-common-components/tsconfig.json @@ -0,0 +1,20 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "types": [ + "jasmine", + "node" + ] + }, + "angularCompilerOptions": { + "annotateForClosureCompiler": true, + "strictMetadataEmit": true, + "flatModuleId": "@ptsecurity/mosaic-common-components", + "skipTemplateCodegen": true, + "fullTemplateTypeCheck": true, + "enableIvy": false + }, + "include": [ + "**/*.ts" + ] +} diff --git a/packages/mosaic-common-components/tsconfig.lib.json b/packages/mosaic-common-components/tsconfig.lib.json new file mode 100644 index 000000000..8c9d2db7b --- /dev/null +++ b/packages/mosaic-common-components/tsconfig.lib.json @@ -0,0 +1,20 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "../../dist/out-tsc", + "target": "es2015", + "declaration": true, + "inlineSources": true, + "types": [], + "lib": [ + "dom", + "es2018" + ], + "paths": { + "@ptsecurity/cdk/*": ["./dist/cdk/*"], + "@ptsecurity/mosaic/*": ["./dist/mosaic/*"], + "@ptsecurity/mosaic-moment-adapter/*": ["./dist/mosaic-moment-adapter/*"], + "@ptsecurity/mosaic-luxon-adapter/*": ["./dist/mosaic-luxon-adapter/*"] + } + } +} diff --git a/packages/mosaic-common-components/tsconfig.spec.json b/packages/mosaic-common-components/tsconfig.spec.json new file mode 100644 index 000000000..511b2805b --- /dev/null +++ b/packages/mosaic-common-components/tsconfig.spec.json @@ -0,0 +1,13 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "../../dist/out-tsc/mosaic-common-components-spec" + }, + "files": [ + "test.ts" + ], + "include": [ + "**/*.spec.ts", + "./*/index.ts" + ] +} diff --git a/packages/mosaic-dev/sites-menu/main.ts b/packages/mosaic-dev/sites-menu/main.ts new file mode 100644 index 000000000..3d67e6daa --- /dev/null +++ b/packages/mosaic-dev/sites-menu/main.ts @@ -0,0 +1,10 @@ +import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; + +import { DemoModule } from './module'; + + +platformBrowserDynamic() + .bootstrapModule(DemoModule) + // tslint:disable-next-line:no-console + .catch((error) => console.error(error)); + diff --git a/packages/mosaic-dev/sites-menu/module.ts b/packages/mosaic-dev/sites-menu/module.ts new file mode 100644 index 000000000..661333809 --- /dev/null +++ b/packages/mosaic-dev/sites-menu/module.ts @@ -0,0 +1,166 @@ +// tslint:disable:no-console +import { Component, NgModule, ViewEncapsulation } from '@angular/core'; +import { BrowserModule } from '@angular/platform-browser'; +import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; +import { ApplicationTypeEnum, Site, SitesMenuModule } from '@ptsecurity/mosaic-common-components/sites-menu'; +import { McButtonModule } from '@ptsecurity/mosaic/button'; +import { McDropdownModule } from '@ptsecurity/mosaic/dropdown'; +import { McIconModule } from '@ptsecurity/mosaic/icon'; +import { McNavbarModule } from '@ptsecurity/mosaic/navbar'; + + +const data: Site = { + id: '00f3c418-93a6-4b58-a605-f7141aff4fa2', + name: 'Площадка', + alias: 'SITE', + address: 'mp-fb9.rd.ptsecurity.ru', + isCurrent: true, + children: [ + { + id: '76c74a26-9db1-4a72-8374-30ec806374c2', + name: 'mp-fb14', + alias: 'mp-fb14', + address: 'mp-fb14.rd.ptsecurity.ru', + isCurrent: false, + children: [], + applications: [ + { + id: '14157a7a-5ac0-0001-0000-000000000002', + alias: 'App-1', + displayName: 'Management and Configuration', + endpoint: 'https://mp-fb14.rd.ptsecurity.ru:3334', + type: ApplicationTypeEnum.MC + }, + { + id: '14157bc9-7000-0001-0000-000000000004', + alias: 'KB-1', + displayName: 'Knowledge Base', + endpoint: 'https://mp-fb14.rd.ptsecurity.ru:8091', + type: ApplicationTypeEnum.PTKB + }, + { + id: '14157b6f-9a00-0001-0000-000000000003', + alias: 'MP-1', + displayName: 'MaxPatrol 10', + endpoint: 'https://mp-fb14.rd.ptsecurity.ru', + type: ApplicationTypeEnum.MPSIEM + } + ] + }, + { + id: '74c4c617-c15b-41bd-845a-e299f9b5fb6b', + name: 'mp-fb21', + alias: 'mp-fb21', + address: 'mp-fb21.rd.ptsecurity.ru', + isCurrent: false, + children: [], + applications: [ + { + id: '1416902e-adc0-0001-0000-000000000002', + alias: 'App-3', + displayName: 'Management and Configuration', + endpoint: 'https://mp-fb19.rd.ptsecurity.ru:3334', + type: ApplicationTypeEnum.MC + }, + { + id: '141691c9-c200-0001-0000-000000000004', + alias: 'KB-3', + displayName: 'Knowledge Base', + endpoint: 'https://mp-fb19.rd.ptsecurity.ru:8091', + type: ApplicationTypeEnum.PTKB + }, + { + id: '1416914e-8a00-0001-0000-000000000003', + alias: 'MP-3', + displayName: 'MaxPatrol 10', + endpoint: 'https://mp-fb19.rd.ptsecurity.ru', + type: ApplicationTypeEnum.MPSIEM + } + ] + }, + { + id: '73c4c617-c15b-41bd-845a-e299f9b5fb6a', + name: 'mp-fb19', + alias: 'mp-fb19', + address: 'mp-fb19.rd.ptsecurity.ru', + isCurrent: false, + children: [], + applications: [ + { + id: '1416902e-adc0-0001-0000-000000000002', + alias: 'App-4', + displayName: 'Management and Configuration', + endpoint: 'https://mp-fb19.rd.ptsecurity.ru:3334', + type: ApplicationTypeEnum.MC + }, + { + id: '141691c9-c200-0001-0000-000000000004', + alias: 'KB-4', + displayName: 'Knowledge Base', + endpoint: 'https://mp-fb19.rd.ptsecurity.ru:8091', + type: ApplicationTypeEnum.PTKB + }, + { + id: '1416914e-8a00-0001-0000-000000000003', + alias: 'MP-4', + displayName: 'MaxPatrol 10', + endpoint: 'https://mp-fb19.rd.ptsecurity.ru', + type: ApplicationTypeEnum.MPSIEM + } + ] + } + ], + applications: [ + { + id: '1415ad7e-8400-0001-0000-000000000002', + alias: 'App-2', + displayName: 'Management and Configuration', + endpoint: 'http://localhost:4200/', + type: ApplicationTypeEnum.MC + }, + { + id: '1415af1c-a600-0001-0000-000000000004', + alias: 'KB-2', + displayName: 'Knowledge Base', + endpoint: 'https://mp-fb9.rd.ptsecurity.ru:8091', + type: ApplicationTypeEnum.PTKB + }, + { + id: '1415ae9c-dbc0-0001-0000-000000000003', + alias: 'MP-2', + displayName: 'MaxPatrol 10', + endpoint: 'https://mp-fb9.rd.ptsecurity.ru', + type: ApplicationTypeEnum.MPSIEM + } + ] +}; + +@Component({ + selector: 'app', + templateUrl: 'template.html', + styleUrls: ['../main.scss', 'styles.scss'], + encapsulation: ViewEncapsulation.None +}) +export class SitesMenuDemoComponent { + apiMenu = data; + +} + +@NgModule({ + imports: [ + BrowserModule, + BrowserAnimationsModule, + McButtonModule, + + McNavbarModule, + McIconModule, + McDropdownModule, + SitesMenuModule + ], + declarations: [ + SitesMenuDemoComponent + ], + entryComponents: [ SitesMenuDemoComponent ], + bootstrap: [ SitesMenuDemoComponent ] +}) +export class DemoModule {} diff --git a/packages/mosaic-dev/sites-menu/styles.scss b/packages/mosaic-dev/sites-menu/styles.scss new file mode 100644 index 000000000..362fee045 --- /dev/null +++ b/packages/mosaic-dev/sites-menu/styles.scss @@ -0,0 +1,10 @@ +@import '../../mosaic/core/visual/prebuilt/default-visual'; + +.sites-tree-hamburger-wrapper { + padding-left: 0; + padding-right: 0; + + sites-tree-hamburger { + height: 100%; + } +} diff --git a/packages/mosaic-dev/sites-menu/template.html b/packages/mosaic-dev/sites-menu/template.html new file mode 100644 index 000000000..7ee2309e3 --- /dev/null +++ b/packages/mosaic-dev/sites-menu/template.html @@ -0,0 +1,15 @@ + + + + + + + + + Test app + + + diff --git a/packages/mosaic-dev/sites-menu/tsconfig.app.json b/packages/mosaic-dev/sites-menu/tsconfig.app.json new file mode 100644 index 000000000..4107bb99e --- /dev/null +++ b/packages/mosaic-dev/sites-menu/tsconfig.app.json @@ -0,0 +1,13 @@ +{ + "extends": "../../../tsconfig.json", + "compilerOptions": { + "outDir": "../../../dist/out-tsc" + }, + "files": [ + "main.ts", + "../polyfills.ts" + ], + "include": [ + "**/*.ts" + ] +} diff --git a/packages/mosaic-dev/vertical-navbar/styles.scss b/packages/mosaic-dev/vertical-navbar/styles.scss index b925ffb54..bf42cb23f 100644 --- a/packages/mosaic-dev/vertical-navbar/styles.scss +++ b/packages/mosaic-dev/vertical-navbar/styles.scss @@ -21,7 +21,7 @@ body { flex-direction: column; text-align: center; flex: 0 0 auto; - background: lighten(mc-color($primary, 700), 15%); + background: lighten(mc-color(map-get($theme, primary), 700), 15%); border-radius: 50%; width: 40px; height: 40px; diff --git a/packages/mosaic-examples/mosaic-common/sites-menu/index.ts b/packages/mosaic-examples/mosaic-common/sites-menu/index.ts new file mode 100644 index 000000000..b7cbcbbea --- /dev/null +++ b/packages/mosaic-examples/mosaic-common/sites-menu/index.ts @@ -0,0 +1,22 @@ +import { CommonModule } from '@angular/common'; +import { NgModule } from '@angular/core'; + +import { SitesMenuOverviewExample } from './sites-menu-overview/sites-menu-overview-example'; + + +export { + SitesMenuOverviewExample +}; + +const EXAMPLES = [ + SitesMenuOverviewExample +]; + +@NgModule({ + imports: [ + CommonModule + ], + declarations: EXAMPLES, + exports: EXAMPLES +}) +export class SitesMenuExamplesModule {} diff --git a/packages/mosaic-examples/mosaic-common/sites-menu/package.json b/packages/mosaic-examples/mosaic-common/sites-menu/package.json new file mode 100644 index 000000000..289d2d373 --- /dev/null +++ b/packages/mosaic-examples/mosaic-common/sites-menu/package.json @@ -0,0 +1,7 @@ +{ + "ngPackage": { + "lib": { + "entryFile": "index.ts" + } + } +} diff --git a/packages/docs/scripts/.gitkeep b/packages/mosaic-examples/mosaic-common/sites-menu/sites-menu-overview/sites-menu-overview-example.css similarity index 100% rename from packages/docs/scripts/.gitkeep rename to packages/mosaic-examples/mosaic-common/sites-menu/sites-menu-overview/sites-menu-overview-example.css diff --git a/packages/mosaic-examples/mosaic-common/sites-menu/sites-menu-overview/sites-menu-overview-example.html b/packages/mosaic-examples/mosaic-common/sites-menu/sites-menu-overview/sites-menu-overview-example.html new file mode 100644 index 000000000..ec57e8e58 --- /dev/null +++ b/packages/mosaic-examples/mosaic-common/sites-menu/sites-menu-overview/sites-menu-overview-example.html @@ -0,0 +1 @@ +

Test sites-menu-overview-example

diff --git a/packages/mosaic-examples/mosaic-common/sites-menu/sites-menu-overview/sites-menu-overview-example.ts b/packages/mosaic-examples/mosaic-common/sites-menu/sites-menu-overview/sites-menu-overview-example.ts new file mode 100644 index 000000000..d69104f8d --- /dev/null +++ b/packages/mosaic-examples/mosaic-common/sites-menu/sites-menu-overview/sites-menu-overview-example.ts @@ -0,0 +1,12 @@ +import { Component } from '@angular/core'; + + +/** + * @title Basic site menu + */ +@Component({ + selector: 'sites-menu-overview-example', + templateUrl: 'sites-menu-overview-example.html', + styleUrls: ['sites-menu-overview-example.css'] +}) +export class SitesMenuOverviewExample {} diff --git a/packages/mosaic/design-tokens/_variables.scss b/packages/mosaic/design-tokens/_variables.scss index 70347d3ec..6d3f71f19 100644 --- a/packages/mosaic/design-tokens/_variables.scss +++ b/packages/mosaic/design-tokens/_variables.scss @@ -1,6 +1,6 @@ // Do not edit directly -// Generated on Tue, 22 Jun 2021 11:43:43 GMT +// Generated on Thu, 01 Jul 2021 11:07:16 GMT $light-color-scheme-primary-default: #338FCC; $light-color-scheme-second-default: #B3B3B3; @@ -57,8 +57,8 @@ $palette-blue-560: #277BB3; $palette-blue-600: #206EA2; $palette-blue-700: #114E77; $palette-blue-800: #07314D; -$palette-blue-a-100: rgba(0, 153, 255, 0.15); -$palette-blue-a-200: rgba(0, 153, 255, 0.3); +$palette-blue-a100: rgba(0, 153, 255, 0.15); +$palette-blue-a200: rgba(0, 153, 255, 0.3); $palette-blue-contrast-40: #4D4D4D; $palette-blue-contrast-60: #4D4D4D; $palette-blue-contrast-100: #4D4D4D; @@ -70,8 +70,8 @@ $palette-blue-contrast-560: white; $palette-blue-contrast-600: white; $palette-blue-contrast-700: white; $palette-blue-contrast-800: white; -$palette-blue-contrast-a-100: #4D4D4D; -$palette-blue-contrast-a-200: white; +$palette-blue-contrast-a100: #4D4D4D; +$palette-blue-contrast-a200: white; $palette-green-40: #F6FBF4; $palette-green-60: #EDF8E9; $palette-green-100: #DCF1D4; @@ -83,7 +83,7 @@ $palette-green-560: #449327; $palette-green-600: #3B8520; $palette-green-700: #276211; $palette-green-800: #163F07; -$palette-green-a-100: rgba(68, 255, 0, 0.15); +$palette-green-a100: rgba(68, 255, 0, 0.15); $palette-green-contrast-40: #4D4D4D; $palette-green-contrast-60: #4D4D4D; $palette-green-contrast-100: #4D4D4D; @@ -95,7 +95,7 @@ $palette-green-contrast-560: white; $palette-green-contrast-600: white; $palette-green-contrast-700: white; $palette-green-contrast-800: white; -$palette-green-contrast-a-100: #4D4D4D; +$palette-green-contrast-a100: #4D4D4D; $palette-red-40: #FEF7F6; $palette-red-60: #FCEFEC; $palette-red-100: #FADEDA; @@ -107,7 +107,7 @@ $palette-red-560: #C43E29; $palette-red-600: #B23522; $palette-red-700: #832112; $palette-red-800: #541208; -$palette-red-a-100: rgba(224, 79, 56, 0.15); +$palette-red-a100: rgba(224, 79, 56, 0.15); $palette-red-contrast-40: #4D4D4D; $palette-red-contrast-60: #4D4D4D; $palette-red-contrast-100: #4D4D4D; @@ -119,7 +119,7 @@ $palette-red-contrast-560: white; $palette-red-contrast-600: white; $palette-red-contrast-700: white; $palette-red-contrast-800: white; -$palette-red-contrast-a-100: #4D4D4D; +$palette-red-contrast-a100: #4D4D4D; $palette-grey-40: #F5F5F5; $palette-grey-60: #F0F0F0; $palette-grey-100: #E6E6E6; @@ -131,12 +131,12 @@ $palette-grey-560: #707070; $palette-grey-600: #666666; $palette-grey-700: #4D4D4D; $palette-grey-800: #333333; -$palette-grey-a-40: rgba(0, 0, 0, 0.04); -$palette-grey-a-60: rgba(0, 0, 0, 0.06); -$palette-grey-a-100: rgba(0, 0, 0, 0.1); -$palette-grey-a-200: rgba(0, 0, 0, 0.2); -$palette-grey-a-300: rgba(0, 0, 0, 0.3); -$palette-grey-a-500: rgba(0, 0, 0, 0.5); +$palette-grey-a40: rgba(0, 0, 0, 0.04); +$palette-grey-a60: rgba(0, 0, 0, 0.06); +$palette-grey-a100: rgba(0, 0, 0, 0.1); +$palette-grey-a200: rgba(0, 0, 0, 0.2); +$palette-grey-a300: rgba(0, 0, 0, 0.3); +$palette-grey-a500: rgba(0, 0, 0, 0.5); $palette-grey-contrast-40: #4D4D4D; $palette-grey-contrast-60: #4D4D4D; $palette-grey-contrast-100: #4D4D4D; @@ -159,7 +159,7 @@ $palette-yellow-560: #BB800A; $palette-yellow-600: #AA7408; $palette-yellow-700: #7D5504; $palette-yellow-800: #503602; -$palette-yellow-a-100: rgba(255, 170, 0, 0.15); +$palette-yellow-a100: rgba(255, 170, 0, 0.15); $palette-yellow-contrast-40: #4D4D4D; $palette-yellow-contrast-60: #4D4D4D; $palette-yellow-contrast-100: #4D4D4D; @@ -171,7 +171,7 @@ $palette-yellow-contrast-560: white; $palette-yellow-contrast-600: white; $palette-yellow-contrast-700: white; $palette-yellow-contrast-800: white; -$palette-yellow-contrast-a-100: #4D4D4D; +$palette-yellow-contrast-a100: #4D4D4D; $alert-light-color-scheme-error-background: #FCEFEC; $alert-light-color-scheme-error-border: #E76E5C; $alert-light-color-scheme-error-icon: #E76E5C; @@ -628,8 +628,8 @@ $toggle-small-font-default: caption; $tooltip-light-color-scheme-background: #4D4D4D; $tooltip-light-color-scheme-text: white; $tooltip-light-color-scheme-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.2); -$tooltip-dark-color-scheme-background: white; -$tooltip-dark-color-scheme-text: white; +$tooltip-dark-color-scheme-background: #F5F5F5; +$tooltip-dark-color-scheme-text: #4D4D4D; $tooltip-dark-color-scheme-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.2); $tooltip-size-max-width: 240px; $tooltip-size-border-radius: 3px; diff --git a/packages/mosaic/design-tokens/components/tooltip.json5 b/packages/mosaic/design-tokens/components/tooltip.json5 index f244ce067..b865f1abe 100644 --- a/packages/mosaic/design-tokens/components/tooltip.json5 +++ b/packages/mosaic/design-tokens/components/tooltip.json5 @@ -6,8 +6,8 @@ shadow: { value: '0 2px 4px 0 rgba(0, 0, 0, 0.2)' } }, 'dark-color-scheme': { - background: { value: 'white' }, - text: { value: '{dark-color-scheme.second.palette.value.contrast.700.value}' }, + background: { value: '{dark-color-scheme.second.palette.value.40.value}' }, + text: { value: '{dark-color-scheme.second.palette.value.contrast.40.value}' }, shadow: { value: '0 2px 4px 0 rgba(0, 0, 0, 0.2)' } }, size: { diff --git a/packages/mosaic/design-tokens/css-tokens.css b/packages/mosaic/design-tokens/css-tokens.css index a86a1d1a7..178ce440d 100644 --- a/packages/mosaic/design-tokens/css-tokens.css +++ b/packages/mosaic/design-tokens/css-tokens.css @@ -1,6 +1,6 @@ /** * Do not edit directly - * Generated on Tue, 22 Jun 2021 11:43:43 GMT + * Generated on Thu, 01 Jul 2021 11:07:16 GMT */ :root { diff --git a/packages/mosaic/design-tokens/tokens.js b/packages/mosaic/design-tokens/tokens.js index 5c1833b2f..03587a200 100644 --- a/packages/mosaic/design-tokens/tokens.js +++ b/packages/mosaic/design-tokens/tokens.js @@ -1,6 +1,6 @@ /** * Do not edit directly - * Generated on Tue, 22 Jun 2021 11:43:43 GMT + * Generated on Thu, 01 Jul 2021 11:07:16 GMT */ export const LightColorSchemePrimaryDefault = "#338FCC"; @@ -736,8 +736,8 @@ export const ToggleSmallFontDefault = "caption"; export const TooltipLightColorSchemeBackground = "#4D4D4D"; export const TooltipLightColorSchemeText = "white"; export const TooltipLightColorSchemeShadow = "0 2px 4px 0 rgba(0, 0, 0, 0.2)"; -export const TooltipDarkColorSchemeBackground = "white"; -export const TooltipDarkColorSchemeText = "white"; +export const TooltipDarkColorSchemeBackground = "#F5F5F5"; +export const TooltipDarkColorSchemeText = "#4D4D4D"; export const TooltipDarkColorSchemeShadow = "0 2px 4px 0 rgba(0, 0, 0, 0.2)"; export const TooltipSizeMaxWidth = "240px"; export const TooltipSizeBorderRadius = "3px"; diff --git a/packages/mosaic/list/_list-theme.scss b/packages/mosaic/list/_list-theme.scss index c1c68b094..fc962d48f 100644 --- a/packages/mosaic/list/_list-theme.scss +++ b/packages/mosaic/list/_list-theme.scss @@ -35,7 +35,8 @@ } @mixin mc-list-typography($config) { - .mc-list-item { + .mc-list-item, + .mc-list-option { @include mc-typography-level-to-styles($config, $list-font-item); } } diff --git a/tools/example-module/generate-example-module.ts b/tools/example-module/generate-example-module.ts index 26de11352..64b391ba0 100644 --- a/tools/example-module/generate-example-module.ts +++ b/tools/example-module/generate-example-module.ts @@ -61,7 +61,8 @@ function inlineExampleModuleTemplate(parsedData: AnalyzedExamples): string { name: data.module.name, importSpecifier: data.module.packagePath, // ptsecurity-mosaic-examples-mosaic-alerts - importPath: `ptsecurity-${splitPackagePath[0]}-examples-${splitPackagePath[0]}-${splitPackagePath[1]}` + // ptsecurity-mosaic-examples-mosaic-common-sites-menu + importPath: `ptsecurity-mosaic-examples-${splitPackagePath[0]}-${splitPackagePath[1]}` } }; diff --git a/tools/gulp/gulpfile.ts b/tools/gulp/gulpfile.ts index 471c799c7..946c2b215 100644 --- a/tools/gulp/gulpfile.ts +++ b/tools/gulp/gulpfile.ts @@ -6,3 +6,4 @@ import './tasks/docs'; import './tasks/styles'; import './tasks/schematic'; import './tasks/release'; +import './tasks/i18n'; diff --git a/tools/gulp/tasks/docs.ts b/tools/gulp/tasks/docs.ts index c2d156c29..0c7b304fb 100644 --- a/tools/gulp/tasks/docs.ts +++ b/tools/gulp/tasks/docs.ts @@ -107,8 +107,34 @@ task('markdown-docs-mosaic', () => { .pipe(dest('dist/docs-content/overviews/mosaic')); }); +task('markdown-docs-mosaic-common', () => { + + markdown.marked.Renderer.prototype.heading = (text: string, level: number): string => { + // tslint:disable-next-line:no-magic-numbers + if (level === 3 || level === 4) { + const escapedText = text.toLowerCase().replace(/\s/g, '-'); + + return ` + + `; + } else { + return ``; + } + }; + + return src(['packages/mosaic-common-components/**/!(README).md']) + .pipe(markdown(markdownOptions)) + .pipe(transform(transformMarkdownFiles)) + .pipe(flatten()) + .pipe(dest('dist/docs-content/overviews/common')); +}); + task('docs-content', series( 'markdown-docs-mosaic', + 'markdown-docs-mosaic-common', 'api-docs' ) ); diff --git a/tools/gulp/tasks/i18n.ts b/tools/gulp/tasks/i18n.ts new file mode 100644 index 000000000..61faf0b4c --- /dev/null +++ b/tools/gulp/tasks/i18n.ts @@ -0,0 +1,15 @@ +import { task, src, dest } from 'gulp'; +import { join } from 'path'; + + +const releasesDir = 'dist'; +const sourceDirProductComponents = 'packages/mosaic-common-components'; + + +const themingEntryPointPathProductComponents = join(sourceDirProductComponents, 'i18n', '*.json'); +const releasePathProductComponents = join(releasesDir, 'mosaic-common-components'); + +task('mosaic-common-components:bundle-i18n', () => { + return src(themingEntryPointPathProductComponents) + .pipe(dest(releasePathProductComponents)); +}); diff --git a/tools/gulp/tasks/styles.ts b/tools/gulp/tasks/styles.ts index 8a53001b4..4ed2eb63d 100644 --- a/tools/gulp/tasks/styles.ts +++ b/tools/gulp/tasks/styles.ts @@ -8,6 +8,7 @@ import { buildScssPipeline } from '../utils/build-scss-pipeline'; const sourceDir = 'packages/mosaic'; +const sourceDirMosaicCommonComponents = 'packages/mosaic-common-components'; /** Path to the directory where all releases are created. */ const releasesDir = 'dist'; @@ -19,10 +20,15 @@ const allScssDedupeGlob = join(buildConfig.packagesDir, '**/*.scss'); // Path to the release output of mosaic. const releasePath = join(releasesDir, 'mosaic'); +const releasePathMosaicCommonComponents = join(releasesDir, 'mosaic-common-components'); + // The entry-point for the scss theming bundle. const themingEntryPointPath = join(sourceDir, 'core', 'theming', '_all-theme.scss'); +const themingEntryPointPathMosaicCommonComponents = join(sourceDirMosaicCommonComponents, 'core', 'theming', '_all-theme.scss'); + // Output path for the scss theming bundle. const themingBundlePath = join(releasePath, '_theming.scss'); +const themingBundlePathMosaicCommonComponents = join(releasePathMosaicCommonComponents, '_theming.scss'); const visualEntryPointPath = join(sourceDir, 'core', 'visual', '_all-visual.scss'); @@ -42,6 +48,14 @@ task('mosaic:bundle-theming-scss', () => { }); }); +task('mosaic-common-components:bundle-theming-scss', () => { + + return new Bundler().bundle(themingEntryPointPathMosaicCommonComponents, [allScssDedupeGlob]).then((result) => { + mkdirpSync(releasePathMosaicCommonComponents); + writeFileSync(themingBundlePathMosaicCommonComponents, result.bundledContent); + }); +}); + task('mosaic:bundle-visual-scss', () => { // Instantiates the SCSS bundler and bundles all imports of the specified entry point SCSS file. // A glob of all SCSS files in the library will be passed to the bundler. The bundler takes an diff --git a/tools/highlight-files/highlight-files.ts b/tools/highlight-files/highlight-files.ts index 2586adf62..7506afa51 100644 --- a/tools/highlight-files/highlight-files.ts +++ b/tools/highlight-files/highlight-files.ts @@ -36,7 +36,7 @@ if (require.main === module) { const outDir = 'dist/docs-content/examples-highlighted'; const packageName = 'packages/mosaic-examples'; - const inputFiles = glob.sync('packages/mosaic-examples/mosaic/**/*.{html,css,ts}', { + const inputFiles = glob.sync('packages/mosaic-examples/**/*.{html,css,ts}', { ignore: ['**/index.ts', '**/modules.ts'] }); diff --git a/tools/package-docs-content/package-docs-content.ts b/tools/package-docs-content/package-docs-content.ts index 6fa4489e3..ccb92242b 100644 --- a/tools/package-docs-content/package-docs-content.ts +++ b/tools/package-docs-content/package-docs-content.ts @@ -6,14 +6,22 @@ import { dirname } from 'path'; if (require.main === module) { // copy Stackblitz Examples - const outDir = 'dist/docs-content/examples-source/mosaic'; - const execPath = 'packages/mosaic-examples/mosaic/'; + const outDir = [ + 'dist/docs-content/examples-source/mosaic', + 'dist/docs-content/examples-source/mosaic-common' + ]; + const execPath = [ + 'packages/mosaic-examples/mosaic/', + 'packages/mosaic-examples/mosaic-common/' + ]; - ensureDirSync(dirname(outDir)); + for (let i = 0; i < execPath.length; i++) { + ensureDirSync(dirname(outDir[i])); - copySync(execPath, outDir, { - filter: (path) => { - return !/.json/.test(path); - } - }); + copySync(execPath[i], outDir[i], { + filter: (path) => { + return !/.json/.test(path); + } + }); + } } diff --git a/tools/region-parser/region-parser.ts b/tools/region-parser/region-parser.ts index a17379269..7c46d7783 100644 --- a/tools/region-parser/region-parser.ts +++ b/tools/region-parser/region-parser.ts @@ -7,125 +7,124 @@ export type Region = { lines: string[]; open: boolean }; export type RegionMap = { [regionName: string]: Region }; export function regionParser(contents: string, fileType: string) { - return regionParserImpl(contents, fileType); + return regionParserImpl(contents, fileType); } - function regionParserImpl(contents: string, fileType: string) - : { contents: string; regions: { [regionName: string]: string } } { - const regionMatchers: { [fileType: string]: { [region: string]: RegExp } } = { - ts: inlineC, - js: inlineC, - es6: inlineC, - html, - css: blockC, - json: inlineC, - 'json.annotated': inlineC - }; - const regionMatcher = regionMatchers[fileType]; - const openRegions: string[] = []; - const regionMap: RegionMap = {}; - - if (regionMatcher) { - const lines = contents.split(/\r?\n/).filter((line) => { - // debugger; - const startRegion = line.match(regionMatcher.regionStartMatcher); - const endRegion = line.match(regionMatcher.regionEndMatcher); - - // start region processing - if (startRegion) { - // open up the specified region - const regionNames = getRegionNames(startRegion[1]); - if (regionNames.length === 0) { - regionNames.push(''); - } - regionNames.forEach((regionName) => { - const region = regionMap[regionName]; - if (region) { - if (region.open) { - throw new Error( - `Tried to open a region, named "${regionName}", that is already open` - ); + : { contents: string; regions: { [regionName: string]: string } } { + const regionMatchers: { [fileType: string]: { [region: string]: RegExp } } = { + ts: inlineC, + js: inlineC, + es6: inlineC, + html, + css: blockC, + json: inlineC, + 'json.annotated': inlineC + }; + const regionMatcher = regionMatchers[fileType]; + const openRegions: string[] = []; + const regionMap: RegionMap = {}; + + if (regionMatcher) { + const lines = contents.split(/\r?\n/).filter((line) => { + // debugger; + const startRegion = line.match(regionMatcher.regionStartMatcher); + const endRegion = line.match(regionMatcher.regionEndMatcher); + + // start region processing + if (startRegion) { + // open up the specified region + const regionNames = getRegionNames(startRegion[1]); + if (regionNames.length === 0) { + regionNames.push(''); + } + regionNames.forEach((regionName) => { + const region = regionMap[regionName]; + if (region) { + if (region.open) { + throw new Error( + `Tried to open a region, named "${regionName}", that is already open` + ); + } + region.open = true; + } else { + regionMap[regionName] = {lines: [], open: true}; + } + openRegions.push(regionName); + }); + + // end region processing + } else if (endRegion) { + if (openRegions.length === 0) { + throw new Error('Tried to close a region when none are open'); + } + // close down the specified region (or most recent if no name is given) + const regionNames = getRegionNames(endRegion[1]); + if (regionNames.length === 0) { + regionNames.push(openRegions[openRegions.length - 1]); + } + + regionNames.forEach((regionName) => { + const region = regionMap[regionName]; + if (!region || !region.open) { + throw new Error( + `Tried to close a region, named "${regionName}", that is not open`); + } + region.open = false; + removeLast(openRegions, regionName); + }); + + } else { + openRegions.forEach((regionName) => regionMap[regionName].lines.push(line)); + + // do not filter out this line from the content + return true; } - region.open = true; - } else { - regionMap[regionName] = {lines: [], open: true}; - } - openRegions.push(regionName); - }); - // end region processing - } else if (endRegion) { - if (openRegions.length === 0) { - throw new Error('Tried to close a region when none are open'); - } - // close down the specified region (or most recent if no name is given) - const regionNames = getRegionNames(endRegion[1]); - if (regionNames.length === 0) { - regionNames.push(openRegions[openRegions.length - 1]); - } - - regionNames.forEach((regionName) => { - const region = regionMap[regionName]; - if (!region || !region.open) { - throw new Error( - `Tried to close a region, named "${regionName}", that is not open`); - } - region.open = false; - removeLast(openRegions, regionName); + // this line contained an annotation so let's filter it out + return false; }); + if (!regionMap['']) { + regionMap[''] = {lines, open: false}; + } - } else { - openRegions.forEach((regionName) => regionMap[regionName].lines.push(line)); - - // do not filter out this line from the content - return true; - } - - // this line contained an annotation so let's filter it out - return false; - }); - if (!regionMap['']) { - regionMap[''] = {lines, open: false}; + return { + contents: lines.join('\n'), + regions: mapObject(regionMap, (regionName: string, region: Region) => + leftAlign(region.lines).join('\n')) + }; + } else { + return {contents, regions: {}}; } - - return { - contents: lines.join('\n'), - regions: mapObject(regionMap, (regionName: string, region: Region) => - leftAlign(region.lines).join('\n')) - }; - } else { - return {contents, regions: {}}; - } } function mapObject(obj: RegionMap, mapper: (regionName: string, region: Region) => string) { - const mappedObj: { [regionName: string]: string } = {}; - Object.keys(obj).forEach((key: string) => { - mappedObj[key] = mapper(key, obj[key]); - }); + const mappedObj: { [regionName: string]: string } = {}; + Object.keys(obj).forEach((key: string) => { + mappedObj[key] = mapper(key, obj[key]); + }); - return mappedObj; + return mappedObj; } function getRegionNames(input: string): string[] { - return (input.trim() === '') ? [] : input.split(',').map((name) => name.trim()); + return (input.trim() === '') ? [] : input.split(',').map((name) => name.trim()); } function removeLast(array: string[], item: string) { - const index = array.lastIndexOf(item); - array.splice(index, 1); + const index = array.lastIndexOf(item); + array.splice(index, 1); } function leftAlign(lines: string[]): string[] { - let indent = Number.MAX_VALUE; - lines.forEach((line) => { - const lineIndent = line.search(/\S/); + let indent = Number.MAX_VALUE; + lines.forEach((line) => { + const lineIndent = line.search(/\S/); - if (lineIndent !== -1) { - indent = Math.min(lineIndent, indent); - } - }); + if (lineIndent !== -1) { + indent = Math.min(lineIndent, indent); + } + }); - return lines.map((line) => line.substr(indent)); + return lines.map((line) => line.substr(indent)); } diff --git a/tsconfig.json b/tsconfig.json index 134304a2b..35e850723 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -68,7 +68,8 @@ "@ptsecurity/mosaic-luxon-adapter": ["packages/mosaic-luxon-adapter/index.ts"], "@ptsecurity/mosaic-moment-adapter": ["packages/mosaic-moment-adapter/index.ts"], "@ptsecurity/mosaic-luxon-adapter/adapter": ["packages/mosaic-luxon-adapter/adapter/index.ts"], - "@ptsecurity/mosaic-moment-adapter/adapter": ["packages/mosaic-moment-adapter/adapter/index.ts"] + "@ptsecurity/mosaic-moment-adapter/adapter": ["packages/mosaic-moment-adapter/adapter/index.ts"], + "@ptsecurity/mosaic-common-components/sites-menu": ["packages/mosaic-common-components/sites-menu/index.ts"] } }, "exclude": [