From 22f84b6a1edb71631afc6410004bb39b5b34fb4c Mon Sep 17 00:00:00 2001 From: "De Laet Robby (External)" Date: Thu, 12 Jul 2018 17:13:29 +0200 Subject: [PATCH] feat(stark-ui): implement the Stark-Pretty-Print component ISSUES CLOSED: #494, #496 --- packages/stark-ui/package.json | 5 +- packages/stark-ui/src/modules.ts | 1 + packages/stark-ui/src/modules/pretty-print.ts | 2 + .../src/modules/pretty-print/components.ts | 1 + .../components/_pretty-print.component.scss | 14 + .../components/pretty-print.component.html | 8 + .../components/pretty-print.component.spec.ts | 859 ++++++++++++++++++ .../components/pretty-print.component.ts | 167 ++++ .../pretty-print/pretty-print.module.ts | 10 + showcase/package.json | 10 +- .../example-viewer.component.html | 42 +- .../example-viewer.component.scss | 7 +- .../example-viewer.component.spec.ts | 25 +- .../example-viewer.component.ts | 28 +- showcase/src/app/shared/shared.module.ts | 12 +- showcase/src/styles/_theme.scss | 1 + showcase/src/styles/styles.scss | 1 + starter/package.json | 8 +- 18 files changed, 1129 insertions(+), 72 deletions(-) create mode 100644 packages/stark-ui/src/modules/pretty-print.ts create mode 100644 packages/stark-ui/src/modules/pretty-print/components.ts create mode 100644 packages/stark-ui/src/modules/pretty-print/components/_pretty-print.component.scss create mode 100644 packages/stark-ui/src/modules/pretty-print/components/pretty-print.component.html create mode 100644 packages/stark-ui/src/modules/pretty-print/components/pretty-print.component.spec.ts create mode 100644 packages/stark-ui/src/modules/pretty-print/components/pretty-print.component.ts create mode 100644 packages/stark-ui/src/modules/pretty-print/pretty-print.module.ts diff --git a/packages/stark-ui/package.json b/packages/stark-ui/package.json index 932dac5308..8b674f26d4 100644 --- a/packages/stark-ui/package.json +++ b/packages/stark-ui/package.json @@ -28,7 +28,10 @@ "dependencies": { "@mdi/angular-material": "2.4.85", "@types/nouislider": "9.0.4", - "nouislider": "11.1.0" + "@types/prismjs": "1.9.0", + "nouislider": "11.1.0", + "pretty-data": "0.40.0", + "prismjs": "1.15.0" }, "devDependencies": { "@nationalbankbelgium/stark-testing": "../stark-testing" diff --git a/packages/stark-ui/src/modules.ts b/packages/stark-ui/src/modules.ts index d191c9ebfa..b039f00d13 100644 --- a/packages/stark-ui/src/modules.ts +++ b/packages/stark-ui/src/modules.ts @@ -1,4 +1,5 @@ export * from "./modules/action-bar"; export * from "./modules/app-logo"; +export * from "./modules/pretty-print"; export * from "./modules/slider"; export * from "./modules/svg-view-box"; diff --git a/packages/stark-ui/src/modules/pretty-print.ts b/packages/stark-ui/src/modules/pretty-print.ts new file mode 100644 index 0000000000..6244f2c83b --- /dev/null +++ b/packages/stark-ui/src/modules/pretty-print.ts @@ -0,0 +1,2 @@ +export * from "./pretty-print/pretty-print.module"; +export * from "./pretty-print/components"; diff --git a/packages/stark-ui/src/modules/pretty-print/components.ts b/packages/stark-ui/src/modules/pretty-print/components.ts new file mode 100644 index 0000000000..59133e6654 --- /dev/null +++ b/packages/stark-ui/src/modules/pretty-print/components.ts @@ -0,0 +1 @@ +export * from "./components/pretty-print.component"; diff --git a/packages/stark-ui/src/modules/pretty-print/components/_pretty-print.component.scss b/packages/stark-ui/src/modules/pretty-print/components/_pretty-print.component.scss new file mode 100644 index 0000000000..fac3c07077 --- /dev/null +++ b/packages/stark-ui/src/modules/pretty-print/components/_pretty-print.component.scss @@ -0,0 +1,14 @@ +/* ============================================================================== */ +/* S t a r k P r e t t y P r i n t */ +/* ============================================================================== */ +/* stark: src/modules/pretty-print/components/_pretty-print-theme.scss */ + +.stark-pretty-print { + & pre, + & pre[class*="language-"] > code { + white-space: pre-wrap; + word-wrap: break-word; + } +} + +/* end stark: src/modules/pretty-print/components/_pretty-print-theme.scss */ diff --git a/packages/stark-ui/src/modules/pretty-print/components/pretty-print.component.html b/packages/stark-ui/src/modules/pretty-print/components/pretty-print.component.html new file mode 100644 index 0000000000..d9b5f4c0ca --- /dev/null +++ b/packages/stark-ui/src/modules/pretty-print/components/pretty-print.component.html @@ -0,0 +1,8 @@ +
+
+
+
+
+
{{ prettyString }}
+
+
diff --git a/packages/stark-ui/src/modules/pretty-print/components/pretty-print.component.spec.ts b/packages/stark-ui/src/modules/pretty-print/components/pretty-print.component.spec.ts new file mode 100644 index 0000000000..c50d1ce4bd --- /dev/null +++ b/packages/stark-ui/src/modules/pretty-print/components/pretty-print.component.spec.ts @@ -0,0 +1,859 @@ +/* tslint:disable:completed-docs no-big-function */ + +import { async, ComponentFixture, TestBed } from "@angular/core/testing"; +import { Component, ViewChild } from "@angular/core"; + +/* stark-core imports */ +import { STARK_LOGGING_SERVICE } from "@nationalbankbelgium/stark-core"; +import { MockStarkLoggingService } from "@nationalbankbelgium/stark-core/testing"; + +/* stark-ui imports */ +import { StarkPrettyPrintComponent } from "./pretty-print.component"; + +/*** + * To be able to test changes to the input fields, the Pretty-Print component is hosted inside the TestComponentHost class. + */ +@Component({ + selector: `host-component`, + template: ` + ` +}) +class TestHostComponent { + @ViewChild(StarkPrettyPrintComponent) public prettyPrintComponent: StarkPrettyPrintComponent; + + public data: string; + public format: string; + public enableHighlighting: boolean; +} + +describe("PrettyPrintComponent", () => { + let component: StarkPrettyPrintComponent; + let hostComponent: TestHostComponent; + let hostFixture: ComponentFixture; + + const shouldHaveInputs: string = "should have inputs set"; + const marginBottom: string = "margin-bottom"; + const classTokenFunction: string = 'class="token function"'; + const classTokenKeyword: string = 'class="token keyword"'; + const classTokenProperty: string = 'class="token property"'; + const classTokenSelector: string = 'class="token selector"'; + + const rawXmlData: string = [ + '', + '', + '' + ].join(""); + + const formattedXmlData: string = [ + '', + ' ', + ' ', + ' ', + "" + ].join("\n"); // should contain line breaks + + const rawCssData: string = [ + "body{background: #D2DA9C url(leftcolbg.jpg)repeat-y left top;color: #FFF;}", + "p{margin-bottom:1em}ul{margin-left:20px;margin-bottom:1em}" + ].join(""); + + const formattedCssData: string = [ + "body{", + " background: #D2DA9C url(leftcolbg.jpg)repeat-y left top;", + " color: #FFF;", + "}", + "p{", + " margin-bottom:1em", + "}", + "ul{", + " margin-left:20px;", + " margin-bottom:1em", + "}\n" // an extra line break is added at the end + ].join("\n"); // should contain line breaks + + const rawScssData: string = [ + "$font-stack: Helvetica, sans-serif; $primary-color: #333; body { font: 100% $font-stack; color: $primary-color; }" + ].join(""); + + const formattedScssData: string = [ + "$font-stack: Helvetica, sans-serif;", + " $primary-color: #333;", + " body {", + " font: 100% $font-stack;", + " color: $primary-color;", + "}\n" // an extra line break is added at the end + ].join("\n"); // should contain line breaks + + const rawSqlData: string = [ + "SELECT DISTINCT Name FROM Production.Product AS p WHERE EXISTS (SELECT * ", + "FROM Production.ProductModel AS pm WHERE p.ProductModelID = pm.ProductModelID ", + "AND pm.Name LIKE 'Long-Sleeve Logo Jersey%')" + ].join(""); + + /* tslint:disable: no-duplicate-string */ + const formattedSqlData: string = [ + "SELECT DISTINCT Name", + "FROM Production.Product AS p", + "WHERE EXISTS ", + " (SELECT *", + " FROM Production.ProductModel AS pm", + " WHERE p.ProductModelID = pm.ProductModelID", + " AND pm.Name LIKE 'Long-Sleeve Logo Jersey%')" + ].join("\n"); // should contain line breaks + /* tslint:enable: no-duplicate-string */ + + const rawJsonData: string = [ + '{"menu": { "id": "file", "value": "File",', + '"menuitem": [{"value": "New", "onclick": "CreateNewDoc()"},', + '{"value": "Open", "onclick": "OpenDoc()"},', + '{"value": "Close", "onclick": "CloseDoc()"}]}}' + ].join(""); + + const formattedJsonData: string = [ + "{", + ' "menu": {', + ' "id": "file",', + ' "value": "File",', + ' "menuitem": [', + " {", + ' "value": "New",', + ' "onclick": "CreateNewDoc()"', + " },", + " {", + ' "value": "Open",', + ' "onclick": "OpenDoc()"', + " },", + " {", + ' "value": "Close",', + ' "onclick": "CloseDoc()"', + " }", + " ]", + " }", + "}" + ].join("\n"); // should contain line breaks + + const rawJavaScriptData: string = [ + "function calculateData(seed, operationFn) {" + + "var data = operationFn(seed);" + + "if (!data){" + + "data = 'could not calculate data';" + + "}" + + "return data;" + + "}" + ].join(""); + + const rawTypeScriptData: string = [ + "function calculateData(seed:any, operationFn:Function):any {" + + "var data:any = operationFn(seed);" + + "if (!data){" + + "data = 'could not calculate data';" + + "}" + + "return data;" + + "}" + ].join(""); + + /** + * async beforeEach + */ + beforeEach(async(() => { + return TestBed.configureTestingModule({ + declarations: [StarkPrettyPrintComponent, TestHostComponent], + providers: [ + { provide: STARK_LOGGING_SERVICE, useValue: new MockStarkLoggingService() }, + ] + }).compileComponents(); + })); + + /** + * Synchronous beforeEach + */ + beforeEach(() => { + hostFixture = TestBed.createComponent(TestHostComponent); + hostComponent = hostFixture.componentInstance; + hostFixture.detectChanges(); // trigger initial data binding + + component = hostComponent.prettyPrintComponent; + }); + + describe("on initialization", () => { + it("should set internal component properties", () => { + expect(hostFixture).toBeDefined(); + expect(component).toBeDefined(); + + expect(component.logger).not.toBeNull(); + expect(component.logger).toBeDefined(); + }); + + it("should NOT have any inputs set", () => { + expect(component.data).toBeUndefined(); + expect(component.format).toBeUndefined(); + expect(component.enableHighlighting).toBeUndefined(); + }); + }); + + describe("Formatting", () => { + describe("XML", () => { + beforeEach(() => { + hostComponent.data = rawXmlData; + hostComponent.format = "xml"; + hostFixture.detectChanges(); + }); + + it(shouldHaveInputs, () => { + expect(component.data).toBe(rawXmlData); + expect(component.format).toBe("xml"); + expect(component.enableHighlighting).toBeUndefined(); + }); + + it("should nicely format raw XML data", () => { + let formattedData: string = component.prettyString; + + const regExLessThan: RegExp = /</gi; + const regExGreaterThan: RegExp = />/gi; + const regExQuote: RegExp = /"/gi; + + formattedData = formattedData + .replace(regExLessThan, "<") + .replace(regExGreaterThan, ">") + .replace(regExQuote, '"'); + + expect(formattedData).toBe(formattedXmlData); + + const preElement: HTMLPreElement | null = hostFixture.nativeElement.querySelector("pre"); + expect(preElement).not.toBeNull(); + expect(preElement.innerHTML).toContain('<menu id="file" value="File">'); + expect(preElement.innerHTML).toContain('<menuitem value="New" onclick="CreateNewDoc()" />'); + expect(preElement.innerHTML).toContain("</menu>"); + }); + }); + + describe("CSS", () => { + beforeEach(() => { + hostComponent.data = rawCssData; + hostComponent.format = "css"; + hostFixture.detectChanges(); + }); + + it(shouldHaveInputs, () => { + expect(component.data).toBe(rawCssData); + expect(component.format).toBe("css"); + expect(component.enableHighlighting).toBeUndefined(); + }); + + it("should nicely format raw CSS data", () => { + expect(component.prettyString).toBe(formattedCssData); + + const preElement: HTMLPreElement | null = hostFixture.nativeElement.querySelector("pre"); + expect(preElement).not.toBeNull(); + expect(preElement.innerHTML).toContain("body"); + expect(preElement.innerHTML).toContain("color: #FFF;"); + expect(preElement.innerHTML).toContain("margin-left:20px;"); + }); + }); + + describe("SCSS", () => { + beforeEach(() => { + hostComponent.data = rawScssData; + hostComponent.format = "scss"; + hostFixture.detectChanges(); + }); + + it(shouldHaveInputs, () => { + expect(component.data).toBe(rawScssData); + expect(component.format).toBe("scss"); + expect(component.enableHighlighting).toBeUndefined(); + }); + + it("should nicely format raw SCSS data", () => { + expect(component.prettyString).toBe(formattedScssData); + + const preElement: HTMLPreElement | null = hostFixture.nativeElement.querySelector("pre"); + expect(preElement).not.toBeNull(); + expect(preElement.innerHTML).toContain("$font-stack: Helvetica, sans-serif;"); + expect(preElement.innerHTML).toContain("$primary-color: #333;"); + expect(preElement.innerHTML).toContain("font: 100% $font-stack;"); + }); + }); + + describe("SQL", () => { + beforeEach(() => { + hostComponent.data = rawSqlData; + hostComponent.format = "sql"; + hostFixture.detectChanges(); + }); + + it(shouldHaveInputs, () => { + expect(component.data).toBe(rawSqlData); + expect(component.format).toBe("sql"); + expect(component.enableHighlighting).toBeUndefined(); + }); + + it("should nicely format raw SQL data", () => { + expect(component.prettyString).toBe(formattedSqlData); + + const preElement: HTMLPreElement | null = hostFixture.nativeElement.querySelector("pre"); + expect(preElement).not.toBeNull(); + /* tslint:disable: no-duplicate-string */ + expect(preElement.innerHTML).toContain("FROM Production.Product AS p"); + expect(preElement.innerHTML).toContain("FROM Production.ProductModel AS pm"); + expect(preElement.innerHTML).toContain("AND pm.Name LIKE 'Long-Sleeve Logo Jersey%'"); + /* tslint:enable: no-duplicate-string */ + }); + }); + + describe("JSON", () => { + beforeEach(() => { + hostComponent.data = rawJsonData; + hostComponent.format = "json"; + hostFixture.detectChanges(); + }); + + it(shouldHaveInputs, () => { + expect(component.data).toBe(rawJsonData); + expect(component.format).toBe("json"); + expect(component.enableHighlighting).toBeUndefined(); + }); + + it("should nicely format raw JSON data", () => { + expect(component.prettyString).toBe(formattedJsonData); + + const preElement: HTMLPreElement | null = hostFixture.nativeElement.querySelector("pre"); + expect(preElement).not.toBeNull(); + expect(preElement.innerHTML).toContain('"id": "file",'); + expect(preElement.innerHTML).toContain('"menuitem": ['); + expect(preElement.innerHTML).toContain('"onclick": "CreateNewDoc()"'); + }); + + it("should simply display the unformatted raw JSON data in case it is not valid JSON", () => { + const invalidRawJsonData: string = rawJsonData.replace(":", "oops"); + + hostComponent.data = invalidRawJsonData; + hostComponent.format = "json"; + hostFixture.detectChanges(); + + expect(component.prettyString).toBe(invalidRawJsonData); + + const preElement: HTMLPreElement | null = hostFixture.nativeElement.querySelector("pre"); + expect(preElement).not.toBeNull(); + expect(preElement.innerHTML).toContain('{"menu"oops { "id": "file",'); + expect(preElement.innerHTML).toContain('"menuitem": [{"value": "New", "onclick": "CreateNewDoc()"}'); + }); + }); + + describe("JavaScript", () => { + beforeEach(() => { + hostComponent.data = rawJavaScriptData; + hostComponent.format = "javascript"; + hostFixture.detectChanges(); + }); + + it(shouldHaveInputs, () => { + expect(component.data).toBe(rawJavaScriptData); + expect(component.format).toBe("javascript"); + expect(component.enableHighlighting).toBeUndefined(); + }); + + it("should leave the raw JavaScript data unformatted (JS formatting not yet supported)", () => { + expect(component.prettyString).toBe(rawJavaScriptData); + + const preElement: HTMLPreElement | null = hostFixture.nativeElement.querySelector("pre"); + expect(preElement).not.toBeNull(); + expect(preElement.innerHTML).toContain("function calculateData(seed, operationFn) {var data = operationFn(seed);"); + }); + }); + + describe("TypeScript", () => { + beforeEach(() => { + hostComponent.data = rawTypeScriptData; + hostComponent.format = "typescript"; + hostFixture.detectChanges(); + }); + + it(shouldHaveInputs, () => { + expect(component.data).toBe(rawTypeScriptData); + expect(component.format).toBe("typescript"); + expect(component.enableHighlighting).toBeUndefined(); + }); + + it("should leave the raw TypeScript data unformatted (TS formatting not yet supported)", () => { + expect(component.prettyString).toBe(rawTypeScriptData); + + const preElement: HTMLPreElement | null = hostFixture.nativeElement.querySelector("pre"); + expect(preElement).not.toBeNull(); + /* tslint:disable: no-duplicate-string */ + expect(preElement.innerHTML).toContain("function calculateData(seed:any, operationFn:Function):any"); + /* tslint:enable: no-duplicate-string */ + }); + }); + + describe("Undefined format", () => { + beforeEach(() => { + hostComponent.data = rawTypeScriptData; + hostComponent.format = undefined; + hostFixture.detectChanges(); + }); + + it(shouldHaveInputs, () => { + expect(component.data).toBe(rawTypeScriptData); + expect(component.format).toBeUndefined(); + expect(component.enableHighlighting).toBeUndefined(); + }); + + /* tslint:disable: no-identical-functions */ // the same test is performed an unkowm format + it("should leave the raw data unformatted when the format is not defined", () => { + expect(component.prettyString).toBe(rawTypeScriptData); + + const preElement: HTMLPreElement | null = hostFixture.nativeElement.querySelector("pre"); + expect(preElement).not.toBeNull(); + /* tslint:disable: no-duplicate-string */ + expect(preElement.innerHTML).toContain("function calculateData(seed:any, operationFn:Function):any"); + /* tslint:enable: no-duplicate-string */ + }); + /* tslint:enable: no-identical-functions */ + }); + + describe("Unknown Format string", () => { + beforeEach(() => { + hostComponent.data = rawTypeScriptData; + hostComponent.format = "UnknownFormat"; + hostFixture.detectChanges(); + }); + + it(shouldHaveInputs, () => { + expect(component.data).toBe(rawTypeScriptData); + expect(component.format).toBe("unknownformat"); + expect(component.enableHighlighting).toBeUndefined(); + }); + + /* tslint:disable: no-identical-functions */ // the same test is performed an undefined format + it("should leave the raw data unformatted when the format is an unrecognised string", () => { + expect(component.prettyString).toBe(rawTypeScriptData); + + const preElement: HTMLPreElement | null = hostFixture.nativeElement.querySelector("pre"); + expect(preElement).not.toBeNull(); + /* tslint:disable: no-duplicate-string */ + expect(preElement.innerHTML).toContain("function calculateData(seed:any, operationFn:Function):any"); + /* tslint:enable: no-duplicate-string */ + }); + /* tslint:enable: no-identical-functions */ + }); + }); + + describe("Highlighting", () => { + describe("XML", () => { + beforeEach(() => { + hostComponent.data = rawXmlData; + hostComponent.format = "xml"; + hostComponent.enableHighlighting = true; + hostFixture.detectChanges(); + }); + + it(shouldHaveInputs, () => { + expect(component.data).toBe(rawXmlData); + expect(component.format).toBe("xml"); + expect(component.enableHighlighting).toBe(true); + }); + + it("should highlight the formatted XML data", () => { + let formattedData: string = component.prettyString; + + const regExLessThan: RegExp = /</gi; + const regExGreaterThan: RegExp = />/gi; + const regExQuote: RegExp = /"/gi; + + formattedData = formattedData + .replace(regExLessThan, "<") + .replace(regExGreaterThan, ">") + .replace(regExQuote, '"'); + + expect(formattedData).toContain("class='language-markup'"); + expect(formattedData).toContain('class="token tag"'); + expect(formattedData).toContain("hostFixture.nativeElement.querySelector("pre"); + expect(preElement).not.toBeNull(); + expect(preElement.innerHTML).toContain(''); + expect(preElement.innerHTML).toContain('<menu'); + expect(preElement.innerHTML).toContain('value'); + }); + + it("should remove highlighting from the already highlighted XML data when the enableHighlighting is set to FALSE", () => { + hostComponent.enableHighlighting = false; + hostFixture.detectChanges(); + + let formattedData: string = component.prettyString; + + const regExLessThan: RegExp = /</gi; + const regExGreaterThan: RegExp = />/gi; + const regExQuote: RegExp = /"/gi; + + formattedData = formattedData + .replace(regExLessThan, "<") + .replace(regExGreaterThan, ">") + .replace(regExQuote, '"'); + + expect(formattedData).not.toContain("class='language-markup'"); + expect(formattedData).not.toContain('class="token tag"'); + expect(formattedData).not.toContain("hostFixture.nativeElement.querySelector("pre"); + expect(preElement).not.toBeNull(); + expect(preElement.innerHTML).not.toContain(''); + expect(preElement.innerHTML).toContain('<menu id="file" value="File">'); + expect(preElement.innerHTML).toContain('<menuitem value="New" onclick="CreateNewDoc()" />'); + expect(preElement.innerHTML).toContain("</menu>"); + }); + }); + + describe("CSS", () => { + beforeEach(() => { + hostComponent.data = rawCssData; + hostComponent.format = "css"; + hostComponent.enableHighlighting = true; + hostFixture.detectChanges(); + }); + + it(shouldHaveInputs, () => { + expect(component.data).toBe(rawCssData); + expect(component.format).toBe("css"); + expect(component.enableHighlighting).toBe(true); + }); + + it("should highlight the formatted CSS data", () => { + const formattedData: string = component.prettyString; + + expect(formattedData).toContain("class='language-css'"); + expect(formattedData).toContain(classTokenSelector); + expect(formattedData).toContain("hostFixture.nativeElement.querySelector("pre"); + expect(preElement).not.toBeNull(); + expect(preElement.innerHTML).toContain(''); + expect(preElement.innerHTML).toContain('body'); + expect(preElement.innerHTML).toContain(':'); + }); + + it("should remove highlighting from the already highlighted CSS data when the enableHighlighting is set to FALSE", () => { + hostComponent.enableHighlighting = false; + hostFixture.detectChanges(); + + const formattedData: string = component.prettyString; + + expect(formattedData).not.toContain("class='language-css'"); + expect(formattedData).not.toContain(classTokenSelector); + expect(formattedData).not.toContain("hostFixture.nativeElement.querySelector("pre"); + expect(preElement).not.toBeNull(); + expect(preElement.innerHTML).not.toContain(''); + expect(preElement.innerHTML).toContain("color: #FFF;"); + expect(preElement.innerHTML).toContain("margin-bottom:1em"); + expect(preElement.innerHTML).toContain("margin-left:20px;"); + }); + }); + + describe("SCSS", () => { + beforeEach(() => { + hostComponent.data = rawScssData; + hostComponent.format = "scss"; + hostComponent.enableHighlighting = true; + hostFixture.detectChanges(); + }); + + it(shouldHaveInputs, () => { + expect(component.data).toBe(rawScssData); + expect(component.format).toBe("scss"); + expect(component.enableHighlighting).toBe(true); + }); + + it("should highlight the formatted SCSS data", () => { + const formattedData: string = component.prettyString; + + expect(formattedData).toContain("class='language-scss'"); + expect(formattedData).toContain(classTokenSelector); + expect(formattedData).toContain("hostFixture.nativeElement.querySelector("pre"); + expect(preElement).not.toBeNull(); + expect(preElement.innerHTML).toContain(''); + expect(preElement.innerHTML).toContain('$font-stack'); + expect(preElement.innerHTML).toContain(':'); + }); + + it("should remove highlighting from the already highlighted SCSS data when the enableHighlighting is set to FALSE", () => { + hostComponent.enableHighlighting = false; + hostFixture.detectChanges(); + + const formattedData: string = component.prettyString; + + expect(formattedData).not.toContain("class='language-scss'"); + expect(formattedData).not.toContain(classTokenSelector); + expect(formattedData).not.toContain("hostFixture.nativeElement.querySelector("pre"); + expect(preElement).not.toBeNull(); + expect(preElement.innerHTML).not.toContain(''); + expect(preElement.innerHTML).toContain("font: 100% $font-stack;"); + expect(preElement.innerHTML).toContain("color: $primary-color;"); + }); + }); + + describe("SQL", () => { + beforeEach(() => { + hostComponent.data = rawSqlData; + hostComponent.format = "sql"; + hostComponent.enableHighlighting = true; + hostFixture.detectChanges(); + }); + + it(shouldHaveInputs, () => { + expect(component.data).toBe(rawSqlData); + expect(component.format).toBe("sql"); + expect(component.enableHighlighting).toBe(true); + }); + + it("should highlight the formatted SQL data", () => { + const formattedData: string = component.prettyString; + + expect(formattedData).toContain("class='language-sql'"); + expect(formattedData).toContain(classTokenKeyword); + expect(formattedData).toContain("hostFixture.nativeElement.querySelector("pre"); + expect(preElement).not.toBeNull(); + expect(preElement.innerHTML).toContain(''); + expect(preElement.innerHTML).toContain('WHERE'); + expect(preElement.innerHTML).toContain('='); + }); + + it("should remove highlighting from the already highlighted SQL data when the enableHighlighting is set to FALSE", () => { + hostComponent.enableHighlighting = false; + hostFixture.detectChanges(); + + const formattedData: string = component.prettyString; + + expect(formattedData).not.toContain("class='language-sql'"); + expect(formattedData).not.toContain(classTokenKeyword); + expect(formattedData).not.toContain("hostFixture.nativeElement.querySelector("pre"); + expect(preElement).not.toBeNull(); + /* tslint:disable: no-duplicate-string */ + expect(preElement.innerHTML).not.toContain(''); + expect(preElement.innerHTML).toContain("SELECT DISTINCT Name"); + expect(preElement.innerHTML).toContain("FROM Production.Product AS p"); + /* tslint:enable: no-duplicate-string */ + }); + }); + + describe("JSON", () => { + beforeEach(() => { + hostComponent.data = rawJsonData; + hostComponent.format = "json"; + hostComponent.enableHighlighting = true; + hostFixture.detectChanges(); + }); + + it(shouldHaveInputs, () => { + expect(component.data).toBe(rawJsonData); + expect(component.format).toBe("json"); + expect(component.enableHighlighting).toBe(true); + }); + + it("should highlight the formatted JSON data", () => { + const formattedData: string = component.prettyString; + + expect(formattedData).toContain("class='language-json'"); + expect(formattedData).toContain(classTokenProperty); + expect(formattedData).toContain("hostFixture.nativeElement.querySelector("pre"); + expect(preElement).not.toBeNull(); + expect(preElement.innerHTML).toContain(''); + expect(preElement.innerHTML).toContain('"file"'); + expect(preElement.innerHTML).toContain('"onclick"'); + }); + + it("should remove highlighting from the already highlighted JSON data when the enableHighlighting is set to FALSE", () => { + hostComponent.enableHighlighting = false; + hostFixture.detectChanges(); + + const formattedData: string = component.prettyString; + + expect(formattedData).not.toContain("class='language-json'"); + expect(formattedData).not.toContain(classTokenProperty); + expect(formattedData).not.toContain("hostFixture.nativeElement.querySelector("pre"); + expect(preElement).not.toBeNull(); + expect(preElement.innerHTML).not.toContain(''); + expect(preElement.innerHTML).toContain('"menuitem": ['); + expect(preElement.innerHTML).toContain('"onclick": "CreateNewDoc()"'); + }); + }); + + describe("JavaScript", () => { + beforeEach(() => { + hostComponent.data = rawJavaScriptData; + hostComponent.format = "javascript"; + hostComponent.enableHighlighting = true; + hostFixture.detectChanges(); + }); + + it(shouldHaveInputs, () => { + expect(component.data).toBe(rawJavaScriptData); + expect(component.format).toBe("javascript"); + expect(component.enableHighlighting).toBe(true); + }); + + it("should highlight the formatted JavaScript data", () => { + const formattedData: string = component.prettyString; + + expect(formattedData).toContain("class='language-javascript'"); + expect(formattedData).toContain(classTokenKeyword); + expect(formattedData).toContain("hostFixture.nativeElement.querySelector("pre"); + expect(preElement).not.toBeNull(); + expect(preElement.innerHTML).toContain(''); + expect(preElement.innerHTML).toContain('function'); + expect(preElement.innerHTML).toContain(')'); + }); + + it("should remove highlighting from the already highlighted JavaScript data when the enableHighlighting is set to FALSE", () => { + hostComponent.enableHighlighting = false; + hostFixture.detectChanges(); + + const formattedData: string = component.prettyString; + + expect(formattedData).not.toContain("class='language-javascript'"); + expect(formattedData).not.toContain(classTokenKeyword); + expect(formattedData).not.toContain("hostFixture.nativeElement.querySelector("pre"); + expect(preElement).not.toBeNull(); + expect(preElement.innerHTML).not.toContain(''); + expect(preElement.innerHTML).toContain("function calculateData(seed, operationFn)"); + expect(preElement.innerHTML).toContain("if (!data){data = 'could not calculate data';}"); + }); + }); + + describe("TypeScript", () => { + beforeEach(() => { + hostComponent.data = rawTypeScriptData; + hostComponent.format = "typescript"; + hostComponent.enableHighlighting = true; + hostFixture.detectChanges(); + }); + + it(shouldHaveInputs, () => { + expect(component.data).toBe(rawTypeScriptData); + expect(component.format).toBe("typescript"); + expect(component.enableHighlighting).toBe(true); + }); + + it("should highlight the formatted TypeScript data", () => { + const formattedData: string = component.prettyString; + + expect(formattedData).toContain("class='language-typescript'"); + expect(formattedData).toContain(classTokenKeyword); + expect(formattedData).toContain("hostFixture.nativeElement.querySelector("pre"); + expect(preElement).not.toBeNull(); + expect(preElement.innerHTML).toContain(''); + expect(preElement.innerHTML).toContain('function'); + expect(preElement.innerHTML).toContain(')'); + }); + + it("should remove highlighting from the already highlighted TypeScript data when the enableHighlighting is set to FALSE", () => { + hostComponent.enableHighlighting = false; + hostFixture.detectChanges(); + + const formattedData: string = component.prettyString; + + expect(formattedData).not.toContain("class='language-typescript'"); + expect(formattedData).not.toContain(classTokenKeyword); + expect(formattedData).not.toContain("hostFixture.nativeElement.querySelector("pre"); + expect(preElement).not.toBeNull(); + /* tslint:disable: no-duplicate-string */ + expect(preElement.innerHTML).not.toContain(''); + expect(preElement.innerHTML).toContain("function calculateData(seed:any, operationFn:Function):any"); + expect(preElement.innerHTML).toContain("{data = 'could not calculate data';}"); + /* tslint:enable: no-duplicate-string */ + }); + }); + }); +}); diff --git a/packages/stark-ui/src/modules/pretty-print/components/pretty-print.component.ts b/packages/stark-ui/src/modules/pretty-print/components/pretty-print.component.ts new file mode 100644 index 0000000000..5f974e2eff --- /dev/null +++ b/packages/stark-ui/src/modules/pretty-print/components/pretty-print.component.ts @@ -0,0 +1,167 @@ +import { Component, HostBinding, Inject, Input, OnChanges, OnInit, SimpleChanges, ViewEncapsulation } from "@angular/core"; + +/* tslint:disable:no-duplicate-imports no-import-side-effect */ +import * as Prism from "prismjs"; +import { LanguageDefinition } from "prismjs"; +import "prismjs/components/prism-typescript.min.js"; +import "prismjs/components/prism-sql.min.js"; +import "prismjs/components/prism-json.min.js"; +import "prismjs/components/prism-scss.min.js"; +/* tslint:enable */ + +import { STARK_LOGGING_SERVICE, StarkLoggingService } from "@nationalbankbelgium/stark-core"; + +/** + * Name of the component + */ +const componentName: string = "stark-pretty-print"; + +/** + * The prefix used in the PrismJs css classes + */ +const prismClassPrefix: string = "language-"; + +/** + * A reference to the prettyData library + */ +const prettyData: any = require("pretty-data").pd; + +/** + * The code languages that are supported by the Stark-Pretty-Print component + */ +export type StarkPrettyPrintFormat = "css" | "scss" | "html" | "xml" | "json" | "sql" | "javascript" | "typescript"; + +/** + * Component to format and highlight code like HTML, CSS, Typescript... + * Can be used to display code examples + */ +@Component({ + selector: "stark-pretty-print", + templateUrl: "./pretty-print.component.html", + encapsulation: ViewEncapsulation.None +}) +export class StarkPrettyPrintComponent implements OnChanges, OnInit { + /** + * Adds class="stark-pretty-print" attribute on the host component + */ + @HostBinding("class") public class: string = componentName; + /** + * The text to be pretty printed + */ + @Input() public data: string; + + /** + * The format to be used to pretty print the data string + */ + @Input() public format: StarkPrettyPrintFormat; + + /** + * If true, also highlight the pretty printed string + */ + @Input() public enableHighlighting?: boolean; + + public prettyString: string; + public highlightingEnabled: boolean; + + /** + * Class constructor + * @param logger : the logger of the application + */ + public constructor( + @Inject(STARK_LOGGING_SERVICE) public logger: StarkLoggingService + ) {} + + /** + * Component lifecycle hook that is called after data-bound properties of a directive are initialized. + */ + public ngOnInit(): void { + this.logger.debug(componentName + ": controller initialized"); + } + + /** + * Component lifecycle hook that is called when any data-bound property of a directive changes. + * @param Contains the changed properties + */ + public ngOnChanges(_onChangesObj: SimpleChanges): void { + if (!this.data || !this.format) { + this.prettyString = this.data; + return; + } + + this.format = this.format.toLowerCase(); + this.highlightingEnabled = !!this.enableHighlighting; + this.prettyString = ""; + + if (this.data && this.data.length > 0) { + let prismGrammar: LanguageDefinition = ""; + let prismClass: string = ""; + + switch (this.format) { + case "xml": + case "html": + prismGrammar = Prism.languages.markup; + prismClass = prismClassPrefix + "markup"; + this.prettyString = prettyData.xml(this.data); + break; + + case "json": + try { + prismGrammar = Prism.languages.json; + prismClass = prismClassPrefix + this.format; + JSON.parse(this.data); + this.prettyString = prettyData.json(this.data); + } catch (e) { + this.logger.warn(componentName + ": Invalid JSON data"); + // the json string might not be valid so it should be in a try-catch clause + // otherwise the pretty-data throws an error and it stops working :s + // in this case we just show the raw data + this.prettyString = this.data; + this.highlightingEnabled = false; + } + break; + + case "css": + prismGrammar = Prism.languages.css; + prismClass = prismClassPrefix + this.format; + this.prettyString = prettyData.css(this.data); + break; + + case "scss": + prismGrammar = Prism.languages.scss; + prismClass = prismClassPrefix + this.format; + this.prettyString = prettyData.css(this.data); + break; + + case "sql": + prismGrammar = Prism.languages.sql; + prismClass = prismClassPrefix + this.format; + this.prettyString = prettyData.sql(this.data); + break; + + case "javascript": + prismGrammar = Prism.languages.javascript; + prismClass = prismClassPrefix + this.format; + this.prettyString = this.data; + break; + + case "typescript": + prismGrammar = Prism.languages.typescript; + prismClass = prismClassPrefix + this.format; + this.prettyString = this.data; + break; + + default: + this.logger.warn(componentName + ": Unknown format -> ", this.format); + this.highlightingEnabled = false; + this.prettyString = this.data; + break; + } + + if (this.highlightingEnabled) { + this.prettyString = Prism.highlight(this.prettyString, prismGrammar); + this.prettyString = + "
" + this.prettyString + "
"; + } + } + } +} diff --git a/packages/stark-ui/src/modules/pretty-print/pretty-print.module.ts b/packages/stark-ui/src/modules/pretty-print/pretty-print.module.ts new file mode 100644 index 0000000000..1dfdb9d838 --- /dev/null +++ b/packages/stark-ui/src/modules/pretty-print/pretty-print.module.ts @@ -0,0 +1,10 @@ +import { NgModule } from "@angular/core"; +import { CommonModule } from "@angular/common"; +import { StarkPrettyPrintComponent } from "./components"; + +@NgModule({ + declarations: [StarkPrettyPrintComponent], + imports: [CommonModule], + exports: [StarkPrettyPrintComponent] +}) +export class StarkPrettyPrintModule {} diff --git a/showcase/package.json b/showcase/package.json index 0253c7f3b7..aaf5edc295 100644 --- a/showcase/package.json +++ b/showcase/package.json @@ -121,15 +121,13 @@ "@angular/platform-browser-dynamic": "6.0.7", "@angular/platform-server": "6.0.7", "@angular/router": "6.0.7", - "@nationalbankbelgium/stark-core": "file:../dist/packages-dist/stark-core/nationalbankbelgium-stark-core-10.0.0-alpha.3-6796ad0.tgz", - "@nationalbankbelgium/stark-ui": "file:../dist/packages-dist/stark-ui/nationalbankbelgium-stark-ui-10.0.0-alpha.3-6796ad0.tgz", + "@nationalbankbelgium/stark-core": "file:../dist/packages-dist/stark-core/nationalbankbelgium-stark-core-10.0.0-alpha.3-e591ed6.tgz", + "@nationalbankbelgium/stark-ui": "file:../dist/packages-dist/stark-ui/nationalbankbelgium-stark-ui-10.0.0-alpha.3-e591ed6.tgz", "@uirouter/visualizer": "6.0.0", - "angular-highlight-js": "^2.0.1", "basscss": "^8.0.2", "core-js": "2.5.7", "eligrey-classlist-js-polyfill": "1.2.20180112", "event-source-polyfill": "0.0.12", - "highlight.js": "^9.12.0", "http-server": "0.11.1", "ngrx-store-freeze": "0.2.4", "ngrx-store-logger": "0.2.2", @@ -140,8 +138,8 @@ "zone.js": "0.8.26" }, "devDependencies": { - "@nationalbankbelgium/stark-build": "file:../dist/packages-dist/stark-build/nationalbankbelgium-stark-build-10.0.0-alpha.3-6796ad0.tgz", - "@nationalbankbelgium/stark-testing": "file:../dist/packages-dist/stark-testing/nationalbankbelgium-stark-testing-10.0.0-alpha.3-6796ad0.tgz", + "@nationalbankbelgium/stark-build": "file:../dist/packages-dist/stark-build/nationalbankbelgium-stark-build-10.0.0-alpha.3-e591ed6.tgz", + "@nationalbankbelgium/stark-testing": "file:../dist/packages-dist/stark-testing/nationalbankbelgium-stark-testing-10.0.0-alpha.3-e591ed6.tgz", "@types/core-js": "2.5.0", "@types/hammerjs": "2.0.35", "@types/node": "8.10.15", diff --git a/showcase/src/app/shared/example-viewer/example-viewer.component.html b/showcase/src/app/shared/example-viewer/example-viewer.component.html index 9c12c5f42d..06dc04eef4 100644 --- a/showcase/src/app/shared/example-viewer/example-viewer.component.html +++ b/showcase/src/app/shared/example-viewer/example-viewer.component.html @@ -1,23 +1,25 @@
-
-

{{ title }}

- - -
+
+

{{ title }}

+ + +
-
- - -
-
-
-
-
-
+
+ + +
+ +
+
+
+
-
- -
-
\ No newline at end of file +
+ +
+ diff --git a/showcase/src/app/shared/example-viewer/example-viewer.component.scss b/showcase/src/app/shared/example-viewer/example-viewer.component.scss index 89a32d9e80..2fcc7b8c78 100644 --- a/showcase/src/app/shared/example-viewer/example-viewer.component.scss +++ b/showcase/src/app/shared/example-viewer/example-viewer.component.scss @@ -4,7 +4,7 @@ } .example-viewer-wrapper { - h3 { + h2 { margin-top: 10px; } } @@ -21,11 +21,6 @@ flex: 1 1 auto; } -.example-source { - padding: 0 30px 10px 30px; - min-height: 150px; -} - .example-viewer-body { padding: 30px; } diff --git a/showcase/src/app/shared/example-viewer/example-viewer.component.spec.ts b/showcase/src/app/shared/example-viewer/example-viewer.component.spec.ts index 60308ad4e3..d21627a699 100644 --- a/showcase/src/app/shared/example-viewer/example-viewer.component.spec.ts +++ b/showcase/src/app/shared/example-viewer/example-viewer.component.spec.ts @@ -1,9 +1,7 @@ import { HttpClientModule } from "@angular/common/http"; import { BrowserAnimationsModule } from "@angular/platform-browser/animations"; import { async, ComponentFixtureAutoDetect, ComponentFixture, TestBed } from "@angular/core/testing"; -import { MatButtonModule, MatIconModule, MatTabsModule, MatTooltipModule } from "@angular/material"; -import * as hljs from "highlight.js"; -import { HighlightJsModule, HIGHLIGHT_JS } from "angular-highlight-js"; +import { MatButtonModule, MatTabsModule, MatTooltipModule } from "@angular/material"; import { ExampleViewerComponent } from "./example-viewer.component"; import { FileService } from "./file.service"; @@ -11,10 +9,9 @@ import { throwError, of } from "rxjs"; import { MockStarkLoggingService } from "@nationalbankbelgium/stark-core/testing"; import { STARK_LOGGING_SERVICE, StarkLoggingService } from "@nationalbankbelgium/stark-core"; import SpyObj = jasmine.SpyObj; +import { StarkPrettyPrintModule } from "@nationalbankbelgium/stark-ui"; +import { NO_ERRORS_SCHEMA } from "@angular/core"; -export function highlightJsFactory(): any { - return hljs; -} describe("ExampleViewerComponent", () => { let component: ExampleViewerComponent; let fileService: FileService; @@ -24,23 +21,13 @@ describe("ExampleViewerComponent", () => { beforeEach(async(() => { return TestBed.configureTestingModule({ declarations: [ExampleViewerComponent], - imports: [ - BrowserAnimationsModule, - HttpClientModule, - HighlightJsModule.forRoot({ - provide: HIGHLIGHT_JS, - useFactory: highlightJsFactory - }), - MatButtonModule, - MatIconModule, - MatTabsModule, - MatTooltipModule - ], + imports: [BrowserAnimationsModule, HttpClientModule, MatButtonModule, MatTabsModule, MatTooltipModule, StarkPrettyPrintModule], providers: [ { provide: ComponentFixtureAutoDetect, useValue: true }, { provide: STARK_LOGGING_SERVICE, useValue: new MockStarkLoggingService() }, FileService - ] + ], + schemas: [NO_ERRORS_SCHEMA] // tells the Angular compiler to ignore unrecognized elements and attributes: mat-icon }).compileComponents(); })); diff --git a/showcase/src/app/shared/example-viewer/example-viewer.component.ts b/showcase/src/app/shared/example-viewer/example-viewer.component.ts index cc252c9bf6..75e6b52c16 100644 --- a/showcase/src/app/shared/example-viewer/example-viewer.component.ts +++ b/showcase/src/app/shared/example-viewer/example-viewer.component.ts @@ -25,12 +25,15 @@ export class ExampleViewerComponent implements OnInit { public fetchFiles(): void { this.extensions.forEach((extension: string) => { - this.service - .fetchFile("/assets/examples/" + this.filesPath + "." + extension.toLowerCase()) - .subscribe( - (data: string) => this.addFileContent({ extension: extension, file: data }), - (error: HttpErrorResponse) => this.loggingService.error("Error while fetching files", new StarkErrorImpl(error)) - ); + this.service.fetchFile("/assets/examples/" + this.filesPath + "." + extension.toLowerCase()).subscribe( + (data: string) => + this.addFileContent({ + extension: extension, + data: data, + format: this.translateExtentionToFormat(extension) + }), + (error: HttpErrorResponse) => this.loggingService.error("Error while fetching files", new StarkErrorImpl(error)) + ); }); } @@ -45,4 +48,17 @@ export class ExampleViewerComponent implements OnInit { public trackFilesContent(_index: number, file: any): string { return file.extension; } + + private translateExtentionToFormat(extension: string): string { + switch (extension.toLowerCase()) { + case "js": + return "javascript"; + + case "ts": + return "typescript"; + + default: + return extension; + } + } } diff --git a/showcase/src/app/shared/shared.module.ts b/showcase/src/app/shared/shared.module.ts index 3a293b35ce..ae96fdb124 100644 --- a/showcase/src/app/shared/shared.module.ts +++ b/showcase/src/app/shared/shared.module.ts @@ -1,28 +1,20 @@ import { ExampleViewerComponent } from "./example-viewer/example-viewer.component"; import { FileService } from "./example-viewer/file.service"; -import * as hljs from "highlight.js"; -import { HighlightJsModule, HIGHLIGHT_JS } from "angular-highlight-js"; import { MatButtonModule, MatIconModule, MatTabsModule, MatTooltipModule, MatSnackBarModule } from "@angular/material"; import { CommonModule } from "@angular/common"; import { NgModule } from "@angular/core"; +import { StarkPrettyPrintModule } from "@nationalbankbelgium/stark-ui"; import { TranslateModule } from "@ngx-translate/core"; -export function highlightJsFactory(): any { - return hljs; -} - @NgModule({ imports: [ - HighlightJsModule.forRoot({ - provide: HIGHLIGHT_JS, - useFactory: highlightJsFactory - }), MatButtonModule, MatIconModule, MatTooltipModule, MatSnackBarModule, MatTabsModule, CommonModule, + StarkPrettyPrintModule, TranslateModule ], providers: [FileService], diff --git a/showcase/src/styles/_theme.scss b/showcase/src/styles/_theme.scss index b8432329b5..51a9b047d3 100644 --- a/showcase/src/styles/_theme.scss +++ b/showcase/src/styles/_theme.scss @@ -14,3 +14,4 @@ /* Stark components */ @import "~@nationalbankbelgium/stark-ui/src/modules/slider/components/slider-theme"; +@import "~@nationalbankbelgium/stark-ui/src/modules/pretty-print/components/pretty-print.component"; diff --git a/showcase/src/styles/styles.scss b/showcase/src/styles/styles.scss index 29d3d35328..634938d2af 100644 --- a/showcase/src/styles/styles.scss +++ b/showcase/src/styles/styles.scss @@ -2,3 +2,4 @@ @import "sidenav_temp"; @import "~basscss/css/basscss.css"; @import "~normalize.css/normalize.css"; +@import "~prismjs/themes/prism-okaidia.css"; diff --git a/starter/package.json b/starter/package.json index a30890df0c..53b2edca6b 100644 --- a/starter/package.json +++ b/starter/package.json @@ -124,8 +124,8 @@ "@angular/platform-browser-dynamic": "6.0.7", "@angular/platform-server": "6.0.7", "@angular/router": "6.0.7", - "@nationalbankbelgium/stark-core": "file:../dist/packages-dist/stark-core/nationalbankbelgium-stark-core-10.0.0-alpha.3-6796ad0.tgz", - "@nationalbankbelgium/stark-ui": "file:../dist/packages-dist/stark-ui/nationalbankbelgium-stark-ui-10.0.0-alpha.3-6796ad0.tgz", + "@nationalbankbelgium/stark-core": "file:../dist/packages-dist/stark-core/nationalbankbelgium-stark-core-10.0.0-alpha.3-e591ed6.tgz", + "@nationalbankbelgium/stark-ui": "file:../dist/packages-dist/stark-ui/nationalbankbelgium-stark-ui-10.0.0-alpha.3-e591ed6.tgz", "@uirouter/visualizer": "6.0.0", "core-js": "2.5.7", "eligrey-classlist-js-polyfill": "1.2.20180112", @@ -139,8 +139,8 @@ "zone.js": "0.8.26" }, "devDependencies": { - "@nationalbankbelgium/stark-build": "file:../dist/packages-dist/stark-build/nationalbankbelgium-stark-build-10.0.0-alpha.3-6796ad0.tgz", - "@nationalbankbelgium/stark-testing": "file:../dist/packages-dist/stark-testing/nationalbankbelgium-stark-testing-10.0.0-alpha.3-6796ad0.tgz", + "@nationalbankbelgium/stark-build": "file:../dist/packages-dist/stark-build/nationalbankbelgium-stark-build-10.0.0-alpha.3-e591ed6.tgz", + "@nationalbankbelgium/stark-testing": "file:../dist/packages-dist/stark-testing/nationalbankbelgium-stark-testing-10.0.0-alpha.3-e591ed6.tgz", "@types/core-js": "2.5.0", "@types/hammerjs": "2.0.35", "@types/node": "8.10.15",