"]
+}
diff --git a/projects/chrome-plugin/src/assets/.gitkeep b/projects/chrome-plugin/src/assets/.gitkeep
new file mode 100644
index 0000000..e69de29
diff --git a/projects/chrome-plugin/src/environments/environment.prod.ts b/projects/chrome-plugin/src/environments/environment.prod.ts
new file mode 100644
index 0000000..3612073
--- /dev/null
+++ b/projects/chrome-plugin/src/environments/environment.prod.ts
@@ -0,0 +1,3 @@
+export const environment = {
+ production: true
+};
diff --git a/projects/chrome-plugin/src/environments/environment.ts b/projects/chrome-plugin/src/environments/environment.ts
new file mode 100644
index 0000000..7b4f817
--- /dev/null
+++ b/projects/chrome-plugin/src/environments/environment.ts
@@ -0,0 +1,16 @@
+// This file can be replaced during build by using the `fileReplacements` array.
+// `ng build --prod` replaces `environment.ts` with `environment.prod.ts`.
+// The list of file replacements can be found in `angular.json`.
+
+export const environment = {
+ production: false
+};
+
+/*
+ * For easier debugging in development mode, you can import the following file
+ * to ignore zone related error stack frames such as `zone.run`, `zoneDelegate.invokeTask`.
+ *
+ * This import should be commented out in production mode because it will have a negative impact
+ * on performance if an error is thrown.
+ */
+// import 'zone.js/dist/zone-error'; // Included with Angular CLI.
diff --git a/projects/chrome-plugin/src/favicon.ico b/projects/chrome-plugin/src/favicon.ico
new file mode 100644
index 0000000..8081c7c
Binary files /dev/null and b/projects/chrome-plugin/src/favicon.ico differ
diff --git a/projects/chrome-plugin/src/index.html b/projects/chrome-plugin/src/index.html
new file mode 100644
index 0000000..0f5fe53
--- /dev/null
+++ b/projects/chrome-plugin/src/index.html
@@ -0,0 +1,14 @@
+
+
+
+
+ XNovo
+
+
+
+
+
+
+
+
+
diff --git a/projects/chrome-plugin/src/main.ts b/projects/chrome-plugin/src/main.ts
new file mode 100644
index 0000000..7f0b29e
--- /dev/null
+++ b/projects/chrome-plugin/src/main.ts
@@ -0,0 +1,70 @@
+import 'core-js/es6/reflect';
+import 'core-js/es7/reflect';
+
+import { enableProdMode } from '@angular/core';
+import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
+
+import { AppModule } from './app/app.module';
+import { environment } from './environments/environment';
+
+document.addEventListener('DOMContentLoaded', () => {
+
+ if (halJsonPage()) {
+ const json = getJson();
+ if (json) {
+ console.log(json);
+ bootstrapComponent();
+ }
+ }
+
+}, false);
+
+
+function bootstrapComponent() {
+
+ if (environment.production) {
+ enableProdMode();
+ }
+ platformBrowserDynamic().bootstrapModule(AppModule).catch((err) => { console.log('error bootstraping plugin:' + err); });
+
+}
+
+function halJsonPage() {
+ if (isJsonPage()) {
+ const json = getJson();
+ if (json && json['_links']) {
+ return true;
+ }
+ }
+ return false;
+}
+
+function isJsonPage() {
+
+ return documentHasOnlyHtmlWithHeadAndBody() && bodyHasOnlyPre();
+
+}
+
+function documentHasOnlyHtmlWithHeadAndBody() {
+
+ return document.children.length === 1 && document.childNodes[0].nodeName.toUpperCase() === 'HTML' &&
+ document.children[0].children.length === 2 &&
+ document.children[0].childNodes[0].nodeName.toUpperCase() === 'HEAD' &&
+ document.children[0].childNodes[1].nodeName.toUpperCase() === 'BODY';
+
+}
+
+function bodyHasOnlyPre() {
+ return document.body.children.length === 1 &&
+ document.body.childNodes[0].nodeName.toLocaleUpperCase() === 'PRE';
+}
+
+function getJson() {
+
+ try {
+ return JSON.parse(document.body.firstChild.textContent);
+ } catch (err) {
+ return null;
+ }
+
+}
diff --git a/projects/chrome-plugin/src/polyfills.ts b/projects/chrome-plugin/src/polyfills.ts
new file mode 100644
index 0000000..afe4cd5
--- /dev/null
+++ b/projects/chrome-plugin/src/polyfills.ts
@@ -0,0 +1,85 @@
+/**
+ * This file includes polyfills needed by Angular and is loaded before the app.
+ * You can add your own extra polyfills to this file.
+ *
+ * This file is divided into 2 sections:
+ * 1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers.
+ * 2. Application imports. Files imported after ZoneJS that should be loaded before your main
+ * file.
+ *
+ * The current setup is for so-called "evergreen" browsers; the last versions of browsers that
+ * automatically update themselves. This includes Safari >= 10, Chrome >= 55 (including Opera),
+ * Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile.
+ *
+ * Learn more in https://angular.io/guide/browser-support
+ */
+
+/***************************************************************************************************
+ * BROWSER POLYFILLS
+ */
+
+/** IE9, IE10, IE11, and Chrome <55 requires all of the following polyfills.
+ * This also includes Android Emulators with older versions of Chrome and Google Search/Googlebot
+ */
+
+// import 'core-js/es6/symbol';
+// import 'core-js/es6/object';
+// import 'core-js/es6/function';
+// import 'core-js/es6/parse-int';
+// import 'core-js/es6/parse-float';
+// import 'core-js/es6/number';
+// import 'core-js/es6/math';
+// import 'core-js/es6/string';
+// import 'core-js/es6/date';
+// import 'core-js/es6/array';
+// import 'core-js/es6/regexp';
+// import 'core-js/es6/map';
+// import 'core-js/es6/weak-map';
+// import 'core-js/es6/set';
+
+/** IE10 and IE11 requires the following for NgClass support on SVG elements */
+// import 'classlist.js'; // Run `npm install --save classlist.js`.
+
+/** IE10 and IE11 requires the following for the Reflect API. */
+// import 'core-js/es6/reflect';
+
+/**
+ * Web Animations `@angular/platform-browser/animations`
+ * Only required if AnimationBuilder is used within the application and using IE/Edge or Safari.
+ * Standard animation support in Angular DOES NOT require any polyfills (as of Angular 6.0).
+ */
+// import 'web-animations-js'; // Run `npm install --save web-animations-js`.
+
+/**
+ * By default, zone.js will patch all possible macroTask and DomEvents
+ * user can disable parts of macroTask/DomEvents patch by setting following flags
+ * because those flags need to be set before `zone.js` being loaded, and webpack
+ * will put import in the top of bundle, so user need to create a separate file
+ * in this directory (for example: zone-flags.ts), and put the following flags
+ * into that file, and then add the following code before importing zone.js.
+ * import './zone-flags.ts';
+ *
+ * The flags allowed in zone-flags.ts are listed here.
+ *
+ * The following flags will work for all browsers.
+ *
+ * (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame
+ * (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick
+ * (window as any).__zone_symbol__BLACK_LISTED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames
+ *
+ * in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js
+ * with the following flag, it will bypass `zone.js` patch for IE/Edge
+ *
+ * (window as any).__Zone_enable_cross_context_check = true;
+ *
+ */
+
+/***************************************************************************************************
+ * Zone JS is required by default for Angular itself.
+ */
+import 'zone.js/dist/zone'; // Included with Angular CLI.
+import 'core-js/es7/reflect';
+
+/***************************************************************************************************
+ * APPLICATION IMPORTS
+ */
diff --git a/projects/chrome-plugin/src/styles.css b/projects/chrome-plugin/src/styles.css
new file mode 100644
index 0000000..90d4ee0
--- /dev/null
+++ b/projects/chrome-plugin/src/styles.css
@@ -0,0 +1 @@
+/* You can add global styles to this file, and also import other style files */
diff --git a/projects/chrome-plugin/src/test.ts b/projects/chrome-plugin/src/test.ts
new file mode 100644
index 0000000..1631789
--- /dev/null
+++ b/projects/chrome-plugin/src/test.ts
@@ -0,0 +1,20 @@
+// This file is required by karma.conf.js and loads recursively all the .spec and framework files
+
+import 'zone.js/dist/zone-testing';
+import { getTestBed } from '@angular/core/testing';
+import {
+ BrowserDynamicTestingModule,
+ platformBrowserDynamicTesting
+} from '@angular/platform-browser-dynamic/testing';
+
+declare const require: any;
+
+// First, initialize the Angular testing environment.
+getTestBed().initTestEnvironment(
+ BrowserDynamicTestingModule,
+ platformBrowserDynamicTesting()
+);
+// Then we find all the tests.
+const context = require.context('./', true, /\.spec\.ts$/);
+// And load the modules.
+context.keys().map(context);
diff --git a/projects/chrome-plugin/tsconfig.app.json b/projects/chrome-plugin/tsconfig.app.json
new file mode 100644
index 0000000..bb16c46
--- /dev/null
+++ b/projects/chrome-plugin/tsconfig.app.json
@@ -0,0 +1,11 @@
+{
+ "extends": "../../tsconfig.json",
+ "compilerOptions": {
+ "outDir": "../../out-tsc/app",
+ "types": []
+ },
+ "exclude": [
+ "test.ts",
+ "**/*.spec.ts"
+ ]
+}
diff --git a/projects/chrome-plugin/tsconfig.spec.json b/projects/chrome-plugin/tsconfig.spec.json
new file mode 100644
index 0000000..a809b0a
--- /dev/null
+++ b/projects/chrome-plugin/tsconfig.spec.json
@@ -0,0 +1,18 @@
+{
+ "extends": "../../tsconfig.json",
+ "compilerOptions": {
+ "outDir": "../../out-tsc/spec",
+ "types": [
+ "jasmine",
+ "node"
+ ]
+ },
+ "files": [
+ "src/test.ts",
+ "src/polyfills.ts"
+ ],
+ "include": [
+ "**/*.spec.ts",
+ "**/*.d.ts"
+ ]
+}
diff --git a/projects/chrome-plugin/tslint.json b/projects/chrome-plugin/tslint.json
new file mode 100644
index 0000000..0ad3cf4
--- /dev/null
+++ b/projects/chrome-plugin/tslint.json
@@ -0,0 +1,17 @@
+{
+ "extends": "../../tslint.json",
+ "rules": {
+ "directive-selector": [
+ true,
+ "attribute",
+ "app",
+ "camelCase"
+ ],
+ "component-selector": [
+ true,
+ "element",
+ "app",
+ "kebab-case"
+ ]
+ }
+}
diff --git a/projects/debug.log b/projects/debug.log
new file mode 100644
index 0000000..3632d94
--- /dev/null
+++ b/projects/debug.log
@@ -0,0 +1 @@
+[1227/162313.310:ERROR:crash_report_database_win.cc(569)] CreateDirectory C:\Users\DEINF~1.OSV\AppData\Local\Temp\karma-15724199\Crashpad: O sistema não pode encontrar o caminho especificado. (0x3)
diff --git a/projects/hal-render-component-library/karma.conf.js b/projects/hal-render-component-library/karma.conf.js
new file mode 100644
index 0000000..4c5f8d0
--- /dev/null
+++ b/projects/hal-render-component-library/karma.conf.js
@@ -0,0 +1,31 @@
+// Karma configuration file, see link for more information
+// https://karma-runner.github.io/1.0/config/configuration-file.html
+
+module.exports = function (config) {
+ config.set({
+ basePath: '',
+ frameworks: ['jasmine', '@angular-devkit/build-angular'],
+ plugins: [
+ require('karma-jasmine'),
+ require('karma-chrome-launcher'),
+ require('karma-jasmine-html-reporter'),
+ require('karma-coverage-istanbul-reporter'),
+ require('@angular-devkit/build-angular/plugins/karma')
+ ],
+ client: {
+ clearContext: false // leave Jasmine Spec Runner output visible in browser
+ },
+ coverageIstanbulReporter: {
+ dir: require('path').join(__dirname, '../../coverage'),
+ reports: ['html', 'lcovonly'],
+ fixWebpackSourcePaths: true
+ },
+ reporters: ['progress', 'kjhtml'],
+ port: 9876,
+ colors: true,
+ logLevel: config.LOG_INFO,
+ autoWatch: true,
+ browsers: ['Chrome'],
+ singleRun: false
+ });
+};
diff --git a/projects/hal-render-component-library/ng-package.json b/projects/hal-render-component-library/ng-package.json
new file mode 100644
index 0000000..3d5979d
--- /dev/null
+++ b/projects/hal-render-component-library/ng-package.json
@@ -0,0 +1,7 @@
+{
+ "$schema": "../../node_modules/ng-packagr/ng-package.schema.json",
+ "dest": "../../dist/hal-render-component-library",
+ "lib": {
+ "entryFile": "src/projects.ts"
+ }
+}
\ No newline at end of file
diff --git a/projects/hal-render-component-library/package.json b/projects/hal-render-component-library/package.json
new file mode 100644
index 0000000..f4544de
--- /dev/null
+++ b/projects/hal-render-component-library/package.json
@@ -0,0 +1,9 @@
+{
+ "name": "hal-render-component-library",
+ "version": "0.0.1",
+ "peerDependencies": {
+ "@angular/common": "^7.1.0",
+ "@angular/core": "^7.1.0",
+ "urijs": "^1.19.1"
+ }
+}
diff --git a/projects/hal-render-component-library/src/lib/hal-render-component.module.ts b/projects/hal-render-component-library/src/lib/hal-render-component.module.ts
new file mode 100644
index 0000000..8197e8a
--- /dev/null
+++ b/projects/hal-render-component-library/src/lib/hal-render-component.module.ts
@@ -0,0 +1,23 @@
+import { JsonObjectBranchNodeComponent } from './json-object-node/json-object-node-branch/json-object-branch-node.component';
+
+import { NgModule } from '@angular/core';
+import { CommonModule } from '@angular/common';
+import { HalRenderComponent } from './hal-render.component';
+import { JsonObjectNodeComponent } from './json-object-node/json-object-node.component';
+import { JsonObjectLeafNodeComponent } from './json-object-node/json-object-node-leaf/json-object-leaf-node.component';
+import { JsonPropertyValueComponent } from './json-object-node/json-object-node-leaf/json-property-value/json-property-value.component';
+import { JsonPropertyNameComponent } from './json-object-node/json-object-node-leaf/json-property-name/json-property-name.component';
+import { UriTemplateEditorComponent } from './uri-template-editor/uri-template-editor.component';
+
+@NgModule({
+ declarations: [
+ HalRenderComponent, JsonObjectNodeComponent, JsonObjectBranchNodeComponent,
+ JsonObjectLeafNodeComponent, JsonPropertyValueComponent, JsonPropertyNameComponent,
+ UriTemplateEditorComponent
+ ],
+ imports: [
+ CommonModule
+ ],
+ exports: [HalRenderComponent]
+})
+export class HalRenderComponentModule { }
diff --git a/projects/hal-render-component-library/src/lib/hal-render.component.html b/projects/hal-render-component-library/src/lib/hal-render.component.html
new file mode 100644
index 0000000..7efa2de
--- /dev/null
+++ b/projects/hal-render-component-library/src/lib/hal-render.component.html
@@ -0,0 +1,16 @@
+
diff --git a/projects/hal-render-component-library/src/lib/hal-render.component.ts b/projects/hal-render-component-library/src/lib/hal-render.component.ts
new file mode 100644
index 0000000..9699755
--- /dev/null
+++ b/projects/hal-render-component-library/src/lib/hal-render.component.ts
@@ -0,0 +1,69 @@
+import { Component, Input } from '@angular/core';
+import { buildHalJsonTree, JsonElementNode } from './tree-model/tree-model';
+
+enum CurrentView {
+ RAW,
+ TREE
+}
+
+@Component({
+ selector: 'hrc-hal-render-component',
+ templateUrl: './hal-render.component.html',
+ styles: []
+})
+export class HalRenderComponent {
+
+ root: JsonElementNode;
+
+ json: String;
+
+ currentView = CurrentView.TREE;
+
+ @Input()
+ set hal(value: string) {
+
+ if (value && value.length > 0) {
+ this.json = value;
+ this.root = buildHalJsonTree(JSON.parse(value));
+ this.expandAll();
+ }
+ }
+
+ public expandAll() {
+ if (this.root) {
+ this.expand(this.root);
+ }
+ }
+
+ public colapseAll() {
+ if (this.root) {
+ this.colapse(this.root);
+ }
+ }
+
+ public viewTree() {
+ this.currentView = CurrentView.TREE;
+ }
+
+ public viewRaw() {
+ this.currentView = CurrentView.RAW;
+ }
+
+ public isCurrentViewTree() {
+ return this.currentView === CurrentView.TREE;
+ }
+
+ public isCurrentViewRaw() {
+ return this.currentView === CurrentView.RAW;
+ }
+
+ expand(node: JsonElementNode) {
+ node.expanded = true;
+ node.children.forEach((item) => { this.expand(item); });
+ }
+
+ colapse(node: JsonElementNode) {
+ node.expanded = false;
+ node.children.forEach((item) => { this.colapse(item); });
+ }
+}
diff --git a/projects/hal-render-component-library/src/lib/json-object-node/json-object-node-branch/json-object-branch-node.component.css b/projects/hal-render-component-library/src/lib/json-object-node/json-object-node-branch/json-object-branch-node.component.css
new file mode 100644
index 0000000..f2dc0ac
--- /dev/null
+++ b/projects/hal-render-component-library/src/lib/json-object-node/json-object-node-branch/json-object-branch-node.component.css
@@ -0,0 +1,21 @@
+.expand-simbol {
+ cursor: pointer;
+ user-select: none;
+ display: inline;
+ font-weight : bold;
+}
+
+.branch-property-name {
+ user-select: none;
+ display: inline;
+}
+
+.branch-end {
+ display: inline;
+}
+
+.link {
+ display: inline;
+ font-family: monospace;
+ color: black;
+}
diff --git a/projects/hal-render-component-library/src/lib/json-object-node/json-object-node-branch/json-object-branch-node.component.html b/projects/hal-render-component-library/src/lib/json-object-node/json-object-node-branch/json-object-branch-node.component.html
new file mode 100644
index 0000000..73615bd
--- /dev/null
+++ b/projects/hal-render-component-library/src/lib/json-object-node/json-object-node-branch/json-object-branch-node.component.html
@@ -0,0 +1,15 @@
+
+ {{expandSymbol()}}
+
+
+ {{branchEnd()}}
+
diff --git a/projects/hal-render-component-library/src/lib/json-object-node/json-object-node-branch/json-object-branch-node.component.ts b/projects/hal-render-component-library/src/lib/json-object-node/json-object-node-branch/json-object-branch-node.component.ts
new file mode 100644
index 0000000..97bd6e0
--- /dev/null
+++ b/projects/hal-render-component-library/src/lib/json-object-node/json-object-node-branch/json-object-branch-node.component.ts
@@ -0,0 +1,91 @@
+import { Component, OnInit, Input } from '@angular/core';
+import { JsonElementNode, JsonElementType, HalElementType } from '../../tree-model/tree-model';
+import * as URITemplate from 'urijs/src/URITemplate';
+
+@Component({
+ selector: 'hrc-json-object-branch-node',
+ templateUrl: './json-object-branch-node.component.html',
+ styleUrls: ['./json-object-branch-node.component.css']
+})
+export class JsonObjectBranchNodeComponent {
+
+ @Input()
+ node: JsonElementNode;
+
+ branchStart() {
+ if (this.node.expanded) {
+ return this.branchNodeDelimiterStart();
+ } else {
+ return this.branchNodeDelimiterStart() + '...' + this.branchNodeDelimiterEnd() + (this.node.lastItem ? '' : ',');
+ }
+ }
+
+ branchEnd() {
+ if (this.node.expanded) {
+ return this.branchNodeDelimiterEnd() + (this.node.lastItem ? '' : ',');
+ } else {
+ return '';
+ }
+ }
+
+ branchNodeDelimiterStart() {
+ return this.node.type === JsonElementType.Array ? '[' : '{';
+ }
+
+ branchNodeDelimiterEnd() {
+ return this.node.type === JsonElementType.Array ? ']' : '}';
+ }
+
+ clickExpand() {
+ this.node.expanded = !this.node.expanded;
+ }
+
+ expandSymbol() {
+ return this.node.expanded ? '-' : '+';
+ }
+
+ isSimpleBranchProperty() {
+ return this.node.halElementType !== HalElementType.LinkRel ||
+ (this.node.halElementType === HalElementType.LinkRel && (!this.node.curie) && !this.node.name.startsWith('http:'));
+ }
+
+ isLinkRel() {
+ return this.node.halElementType === HalElementType.LinkRel && (!this.node.curie) && this.node.name.startsWith('http:');
+ }
+
+ isCuriedLinkRel() {
+ return this.node.halElementType === HalElementType.LinkRel && this.node.curie;
+ }
+
+ getLinkRel() {
+ return this.node.name;
+
+ }
+
+ getNodeName() {
+ if (this.node.type === JsonElementType.Root) {
+ return '';
+ } else {
+ return this.node.name;
+ }
+ }
+
+ getKeyValueDelimiter() {
+ if (this.node.name && this.node.type !== JsonElementType.Root) {
+ return ':';
+ } else {
+ return '';
+ }
+ }
+
+ getCurieRelValue() {
+ return this.node.name.split(':')[1];
+ }
+
+ getCuriedLinkHref() {
+ const curieTemplate = new URITemplate(this.node.curie.href);
+ return curieTemplate.expand({
+ rel: this.getCurieRelValue()
+ });
+ }
+}
diff --git a/projects/hal-render-component-library/src/lib/json-object-node/json-object-node-leaf/json-object-leaf-node.component.html b/projects/hal-render-component-library/src/lib/json-object-node/json-object-node-leaf/json-object-leaf-node.component.html
new file mode 100644
index 0000000..b17d33d
--- /dev/null
+++ b/projects/hal-render-component-library/src/lib/json-object-node/json-object-node-leaf/json-object-leaf-node.component.html
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
diff --git a/projects/hal-render-component-library/src/lib/json-object-node/json-object-node-leaf/json-object-leaf-node.component.ts b/projects/hal-render-component-library/src/lib/json-object-node/json-object-node-leaf/json-object-leaf-node.component.ts
new file mode 100644
index 0000000..b20b051
--- /dev/null
+++ b/projects/hal-render-component-library/src/lib/json-object-node/json-object-node-leaf/json-object-leaf-node.component.ts
@@ -0,0 +1,41 @@
+import { Component, Input } from '@angular/core';
+import { JsonElementNode, JsonElementType , HalElementType } from '../../tree-model/tree-model';
+
+@Component({
+ selector: 'hrc-json-object-leaf-node',
+ templateUrl: './json-object-leaf-node.component.html',
+ // styleUrls: ['./json-object-leaf-node.component.css']
+})
+export class JsonObjectLeafNodeComponent {
+ HalElementEnumType;
+
+ constructor() {
+ this.HalElementEnumType = HalElementType;
+ }
+
+ @Input()
+ node: JsonElementNode;
+
+ leafProperty() {
+ return this.leafPropertyNoDelimiter() + (this.node.lastItem ? '' : ',');
+ }
+
+ leafPropertyNoDelimiter() {
+ return this.node.name ? ('"' + this.node.name + '": ' + this.leafPropertyValue()) : (this.leafPropertyValue());
+ }
+
+ leafPropertyValue() {
+ switch (this.node.type) {
+ case JsonElementType.Null: {
+ return 'null';
+ }
+ case JsonElementType.String: {
+ return '"' + this.node.value + '"';
+ }
+ default: {
+ return this.node.value;
+ }
+ }
+ }
+
+}
diff --git a/projects/hal-render-component-library/src/lib/json-object-node/json-object-node-leaf/json-property-name/json-property-name.component.ts b/projects/hal-render-component-library/src/lib/json-object-node/json-object-node-leaf/json-property-name/json-property-name.component.ts
new file mode 100644
index 0000000..f6bcd6d
--- /dev/null
+++ b/projects/hal-render-component-library/src/lib/json-object-node/json-object-node-leaf/json-property-name/json-property-name.component.ts
@@ -0,0 +1,16 @@
+import { Component, Input } from '@angular/core';
+import { JsonElementNode, JsonElementType , HalElementType } from '../../../tree-model/tree-model';
+
+@Component({
+ selector: 'hrc-json-property-name',
+ template: `{{name()}}
`
+})
+export class JsonPropertyNameComponent {
+
+ @Input()
+ node: JsonElementNode;
+
+ name() {
+ return this.node.name ? ('"' + this.node.name + '":') : ('');
+ }
+}
diff --git a/projects/hal-render-component-library/src/lib/json-object-node/json-object-node-leaf/json-property-value/json-property-value.component.css b/projects/hal-render-component-library/src/lib/json-object-node/json-object-node-leaf/json-property-value/json-property-value.component.css
new file mode 100644
index 0000000..979fdd9
--- /dev/null
+++ b/projects/hal-render-component-library/src/lib/json-object-node/json-object-node-leaf/json-property-value/json-property-value.component.css
@@ -0,0 +1,29 @@
+.leaf-boolean-property-value {
+ display: inline;
+ color: brown;
+}
+
+.leaf-string-property-value {
+ display: inline;
+ color: green;
+}
+
+.leaf-string-property-link-value {
+ display: inline;
+ font-family: monospace;
+ color: green;
+}
+
+.leaf-number-property-value {
+ display: inline;
+ color: blue;
+}
+
+.leaf-boolean-property-value {
+ display: inline;
+ color: brown;
+}
+
+.leaf-property-separator {
+ display: inline;
+}
diff --git a/projects/hal-render-component-library/src/lib/json-object-node/json-object-node-leaf/json-property-value/json-property-value.component.html b/projects/hal-render-component-library/src/lib/json-object-node/json-object-node-leaf/json-property-value/json-property-value.component.html
new file mode 100644
index 0000000..c7c9fec
--- /dev/null
+++ b/projects/hal-render-component-library/src/lib/json-object-node/json-object-node-leaf/json-property-value/json-property-value.component.html
@@ -0,0 +1,4 @@
+{{value()}}
+{{value()}}
+
+,
diff --git a/projects/hal-render-component-library/src/lib/json-object-node/json-object-node-leaf/json-property-value/json-property-value.component.ts b/projects/hal-render-component-library/src/lib/json-object-node/json-object-node-leaf/json-property-value/json-property-value.component.ts
new file mode 100644
index 0000000..6c4268d
--- /dev/null
+++ b/projects/hal-render-component-library/src/lib/json-object-node/json-object-node-leaf/json-property-value/json-property-value.component.ts
@@ -0,0 +1,82 @@
+import { Component, Input } from '@angular/core';
+import { JsonElementNode, JsonElementType, HalElementType } from '../../../tree-model/tree-model';
+
+@Component({
+ selector: 'hrc-json-property-value',
+ templateUrl: './json-property-value.component.html',
+ styleUrls: ['./json-property-value.component.css']
+
+})
+export class JsonPropertyValueComponent {
+
+ @Input()
+ node: JsonElementNode;
+
+
+ value() {
+ switch (this.node.type) {
+ case JsonElementType.Null: {
+ return 'null';
+ }
+ case JsonElementType.String: {
+ return '"' + this.node.value + '"';
+ }
+ default: {
+ return this.node.value;
+ }
+ }
+ }
+
+ getColor() {
+ switch (this.node.type) {
+ case JsonElementType.String: {
+ return 'green';
+ }
+ case JsonElementType.Number: {
+ return 'blue';
+ }
+ case JsonElementType.Boolean: {
+ return 'brown';
+ }
+ default: {
+ return 'black';
+ }
+ }
+
+ }
+
+ propValueStyleClass() {
+ switch (this.node.type) {
+ case JsonElementType.String: {
+ return 'leaf-string-property-value';
+ }
+ case JsonElementType.Number: {
+ return 'leaf-number-property-value';
+ }
+ case JsonElementType.Boolean: {
+ return 'leaf-boolean-property-value';
+ }
+ default: {
+ return '';
+ }
+ }
+ }
+
+ linkHref() {
+ return this.value().substring(1, this.value().length - 1);
+ }
+
+ isSimpleProperty() {
+ return this.node.halElementType !== HalElementType.LinkHref &&
+ this.node.halElementType !== HalElementType.TemplatedLinkHref &&
+ this.node.halElementType !== HalElementType.PropertyUrl;
+ }
+
+ isLinkProperty() {
+ return this.node.halElementType === HalElementType.LinkHref || this.node.halElementType === HalElementType.PropertyUrl;
+ }
+
+ isTemplatedLink() {
+ return this.node.halElementType === HalElementType.TemplatedLinkHref;
+ }
+}
diff --git a/projects/hal-render-component-library/src/lib/json-object-node/json-object-node.component.html b/projects/hal-render-component-library/src/lib/json-object-node/json-object-node.component.html
new file mode 100644
index 0000000..b2179e9
--- /dev/null
+++ b/projects/hal-render-component-library/src/lib/json-object-node/json-object-node.component.html
@@ -0,0 +1,6 @@
+
+
+
+
+
+
diff --git a/projects/hal-render-component-library/src/lib/json-object-node/json-object-node.component.ts b/projects/hal-render-component-library/src/lib/json-object-node/json-object-node.component.ts
new file mode 100644
index 0000000..254a391
--- /dev/null
+++ b/projects/hal-render-component-library/src/lib/json-object-node/json-object-node.component.ts
@@ -0,0 +1,13 @@
+import { Component, Input } from '@angular/core';
+import { JsonElementNode } from '../tree-model/tree-model';
+
+@Component({
+ selector: 'hrc-json-object-node',
+ templateUrl: './json-object-node.component.html',
+})
+export class JsonObjectNodeComponent {
+
+ @Input()
+ node: JsonElementNode;
+
+}
diff --git a/projects/hal-render-component-library/src/lib/tree-model/tree-model.spec.ts b/projects/hal-render-component-library/src/lib/tree-model/tree-model.spec.ts
new file mode 100644
index 0000000..1ffe9f3
--- /dev/null
+++ b/projects/hal-render-component-library/src/lib/tree-model/tree-model.spec.ts
@@ -0,0 +1,444 @@
+import {
+ buildJsonTree,
+ JsonElementType,
+ jsonNodeElementFactory,
+ buildHalLinksJsonTree,
+ HalElementType,
+ halNodeElementFactory,
+ JsonElementNode,
+ buildHalLinkProperty,
+ buildHalLinkProperties,
+ buildHalJsonTree,
+ buildHalEmbeddedJsonTree,
+ getCurieName,
+ Curie
+} from './tree-model';
+
+describe('buildJsonTree', () => {
+ it('buildJsonTree for a null value', () => {
+
+ expect(
+ buildJsonTree(null)
+ ).toEqual(
+ jsonNodeElementFactory('root', JsonElementType.Null, null, [], true)
+ );
+
+ });
+ it('buildJsonTree for a string', () => {
+
+ expect(
+ buildJsonTree('a single string')
+ ).toEqual(
+ jsonNodeElementFactory('root', JsonElementType.String, 'a single string', [], true)
+ );
+
+ });
+ it('buildJsonTree for a number', () => {
+
+ expect(
+ buildJsonTree(123)
+ ).toEqual(
+ jsonNodeElementFactory('root', JsonElementType.Number, '123', [], true)
+ );
+
+ });
+ it('buildJsonTree for a boolean', () => {
+
+ expect(
+ buildJsonTree(false)
+ ).toEqual(
+ jsonNodeElementFactory('root', JsonElementType.Boolean, 'false', [], true)
+ );
+
+ });
+ it('buildJsonTree for a empty object', () => {
+
+ expect(
+ buildJsonTree({})
+ ).toEqual(
+ jsonNodeElementFactory('root', JsonElementType.Object, null, [], true)
+ );
+
+
+ });
+ it('buildJsonTree for an object with some properties', () => {
+
+ expect(
+ buildJsonTree({
+ aStringProperty: 'a string value',
+ aNumberProperty: 123,
+ aBooleanProperty: true,
+ aNullProperty: null,
+ aSubObject: {
+ aSubObjectStringProperty: 'a sub object string property'
+ },
+ aArray: [
+ 'a array item'
+ ]
+ })
+ ).toEqual(
+ jsonNodeElementFactory('root', JsonElementType.Object, null, [
+ jsonNodeElementFactory('aStringProperty', JsonElementType.String, 'a string value', [], false),
+ jsonNodeElementFactory('aNumberProperty', JsonElementType.Number, '123', [], false),
+ jsonNodeElementFactory('aBooleanProperty', JsonElementType.Boolean, 'true', [], false),
+ jsonNodeElementFactory('aNullProperty', JsonElementType.Null, null, [], false),
+ jsonNodeElementFactory('aSubObject', JsonElementType.Object, null, [
+ jsonNodeElementFactory('aSubObjectStringProperty', JsonElementType.String, 'a sub object string property', [], true)
+ ], false),
+ jsonNodeElementFactory('aArray', JsonElementType.Array, null, [
+ jsonNodeElementFactory(null, JsonElementType.String, 'a array item', [], true)
+ ], true)
+ ], true
+ )
+ );
+ });
+ it('buildJsonTree for an empty array', () => {
+
+ expect(
+ buildJsonTree([])
+ ).toEqual(
+ jsonNodeElementFactory('root', JsonElementType.Array, null, [], true)
+ );
+
+ });
+ it('buildJsonTree for an array with some elements', () => {
+
+ expect(
+ buildJsonTree([
+ 'a string',
+ 123,
+ true,
+ null,
+ {
+ aStringProperty: 'a string property value'
+ }
+ ])
+ ).toEqual(
+ jsonNodeElementFactory('root', JsonElementType.Array, null, [
+ jsonNodeElementFactory(null, JsonElementType.String, 'a string', [], false),
+ jsonNodeElementFactory(null, JsonElementType.Number, '123', [], false),
+ jsonNodeElementFactory(null, JsonElementType.Boolean, 'true', [], false),
+ jsonNodeElementFactory(null, JsonElementType.Null, null, [], false),
+ jsonNodeElementFactory(null, JsonElementType.Object, null, [
+ jsonNodeElementFactory('aStringProperty', JsonElementType.String, 'a string property value', [], true)
+ ], true)
+ ], true
+ )
+ );
+ });
+});
+describe('buildHalLinkProperties', () => {
+ it('buildHalLinkProperties for an single link with a object no templated link property', () => {
+ expect(
+ buildHalLinkProperties({
+ 'href': 'http://href-url',
+ 'templated': false,
+ 'type': 'link type',
+ 'deprecation': 'http://deprecation-url',
+ 'name': 'link name',
+ 'profile': 'http://profile-url',
+ 'title': 'link title',
+ 'hreflang': 'link hreflang'
+ })
+ ).toEqual(
+ [
+ halNodeElementFactory('href', JsonElementType.String, HalElementType.LinkHref, 'http://href-url', [], false),
+ jsonNodeElementFactory('templated', JsonElementType.Boolean, 'false', [], false),
+ jsonNodeElementFactory('type', JsonElementType.String, 'link type', [], false),
+ halNodeElementFactory('deprecation', JsonElementType.String, HalElementType.PropertyUrl, 'http://deprecation-url', [], false),
+ jsonNodeElementFactory('name', JsonElementType.String, 'link name', [], false),
+ halNodeElementFactory('profile', JsonElementType.String, HalElementType.PropertyUrl, 'http://profile-url', [], false),
+ jsonNodeElementFactory('title', JsonElementType.String, 'link title', [], false),
+ jsonNodeElementFactory('hreflang', JsonElementType.String, 'link hreflang', [], true)
+ ]
+ );
+ });
+ it('buildHalLinksJsonTree for an single link with a object with templated link property', () => {
+ expect(
+ buildHalLinkProperties({
+ 'href': 'http://href-url',
+ 'templated': true,
+ 'type': 'link type',
+ 'deprecation': 'http://deprecation-url',
+ 'name': 'link name',
+ 'profile': 'http://profile-url',
+ 'title': 'link title',
+ 'hreflang': 'link hreflang'
+ })
+ ).toEqual(
+ [
+ halNodeElementFactory('href', JsonElementType.String, HalElementType.TemplatedLinkHref, 'http://href-url', [], false),
+ jsonNodeElementFactory('templated', JsonElementType.Boolean, 'true', [], false),
+ jsonNodeElementFactory('type', JsonElementType.String, 'link type', [], false),
+ halNodeElementFactory('deprecation', JsonElementType.String, HalElementType.PropertyUrl, 'http://deprecation-url', [], false),
+ jsonNodeElementFactory('name', JsonElementType.String, 'link name', [], false),
+ halNodeElementFactory('profile', JsonElementType.String, HalElementType.PropertyUrl, 'http://profile-url', [], false),
+ jsonNodeElementFactory('title', JsonElementType.String, 'link title', [], false),
+ jsonNodeElementFactory('hreflang', JsonElementType.String, 'link hreflang', [], true)
+ ]
+ );
+ });
+});
+describe('buildHalLinkProperty', () => {
+ it('buildHalLinkProperty for a non existing property', () => {
+ const jsonElementNodes: JsonElementNode[] = [];
+ const linkProperties = {
+ 'a-property': 'a property value'
+ };
+
+ buildHalLinkProperty(jsonElementNodes, linkProperties, 'a-no-existing-property', JsonElementType.String, HalElementType.PropertyUrl);
+
+ expect(jsonElementNodes).toEqual([]);
+
+ });
+ it('buildHalLinkProperty for a valid string property', () => {
+ const jsonElementNodes: JsonElementNode[] = [];
+ const linkProperties = {
+ 'a-property': 'a property value'
+ };
+
+ buildHalLinkProperty(jsonElementNodes, linkProperties, 'a-property', JsonElementType.String, HalElementType.PropertyUrl);
+
+ expect(jsonElementNodes).
+ toEqual([halNodeElementFactory('a-property', JsonElementType.String, HalElementType.PropertyUrl, 'a property value', [], false)]);
+
+ });
+ it('buildHalLinkProperty for a valid boolean property', () => {
+ const jsonElementNodes: JsonElementNode[] = [];
+ const linkProperties = {
+ 'a-property': true
+ };
+
+ buildHalLinkProperty(jsonElementNodes, linkProperties, 'a-property', JsonElementType.Boolean, HalElementType.PropertyUrl);
+
+ expect(jsonElementNodes).
+ toEqual([halNodeElementFactory('a-property', JsonElementType.Boolean, HalElementType.PropertyUrl, 'true', [], false)]);
+
+ });
+});
+describe('buildHalLinksJsonTree', () => {
+ it('buildHalLinksJsonTree for an object links rel ', () => {
+ expect(
+ buildHalLinksJsonTree({
+ '_links': {
+ 'first-rel': {
+ 'href': 'first href'
+ },
+ 'second-rel': {
+ 'href': 'second href'
+ }
+ }
+ })
+ ).toEqual(
+ jsonNodeElementFactory('_links', JsonElementType.Object, null, [
+ halNodeElementFactory('first-rel', JsonElementType.Object, HalElementType.LinkRel, null, [
+ halNodeElementFactory('href', JsonElementType.String, HalElementType.LinkHref, 'first href', [], true)
+ ], false),
+ halNodeElementFactory('second-rel', JsonElementType.Object, HalElementType.LinkRel, null, [
+ halNodeElementFactory('href', JsonElementType.String, HalElementType.LinkHref, 'second href', [], true)
+ ], true)
+ ], false));
+ });
+ it('buildHalLinksJsonTree for an object links rel with curies', () => {
+ expect(
+ buildHalLinksJsonTree({
+ '_links': {
+ 'first-rel': {
+ 'href': 'first href'
+ },
+ 'second-rel': {
+ 'href': 'second href'
+ }
+ }
+ })
+ ).toEqual(
+ jsonNodeElementFactory('_links', JsonElementType.Object, null, [
+ halNodeElementFactory('first-rel', JsonElementType.Object, HalElementType.LinkRel, null, [
+ halNodeElementFactory('href', JsonElementType.String, HalElementType.LinkHref, 'first href', [], true)
+ ], false),
+ halNodeElementFactory('second-rel', JsonElementType.Object, HalElementType.LinkRel, null, [
+ halNodeElementFactory('href', JsonElementType.String, HalElementType.LinkHref, 'second href', [], true)
+ ], true)
+ ], false));
+ });
+ it('buildHalLinksJsonTree for an array links rel ', () => {
+
+ expect(buildHalLinksJsonTree({
+ '_links': {
+ 'first-rel': [
+ {
+ 'href': 'first href'
+ },
+ {
+ 'href': 'second href'
+ }
+ ]
+ }
+ })).toEqual(
+ jsonNodeElementFactory('_links', JsonElementType.Object, null, [
+ halNodeElementFactory('first-rel', JsonElementType.Array, HalElementType.LinkRel, null, [
+ jsonNodeElementFactory(null, JsonElementType.Object, null, [
+ halNodeElementFactory('href', JsonElementType.String, HalElementType.LinkHref, 'first href', [], true)
+ ], false),
+ jsonNodeElementFactory(null, JsonElementType.Object, null, [
+ halNodeElementFactory('href', JsonElementType.String, HalElementType.LinkHref, 'second href', [], true)
+ ], true),
+ ], true),
+ ], false)
+ );
+ });
+});
+describe('buildHalEmbeddedJsonTree', () => {
+ it('buildHalEmbeddedJsonTree for an object links rel ', () => {
+
+ expect(
+ buildHalEmbeddedJsonTree({
+ '_embedded': {
+ 'first-rel': {
+ 'a-property': 'a property value'
+ },
+ 'second-rel': {
+ 'other-property': 'other property value'
+ }
+
+ }
+ })
+ ).toEqual(
+ jsonNodeElementFactory('_embedded', JsonElementType.Object, null, [
+ halNodeElementFactory('first-rel', JsonElementType.Object, HalElementType.LinkRel, null, [
+ jsonNodeElementFactory('a-property', JsonElementType.String, 'a property value', [], true)
+ ], false),
+ halNodeElementFactory('second-rel', JsonElementType.Object, HalElementType.LinkRel, null, [
+ jsonNodeElementFactory('other-property', JsonElementType.String, 'other property value', [], true)
+ ], true),
+ ], false)
+ );
+ });
+ it('buildHalEmbeddedJsonTree for an array links rel ', () => {
+
+ expect(
+ buildHalEmbeddedJsonTree({
+ '_embedded': {
+ 'first-rel': [
+ {
+ 'first-property': 'first property value'
+ },
+ {
+ 'second-property': 'second property value'
+ }
+ ]
+ }
+ })
+ ).toEqual(
+ jsonNodeElementFactory('_embedded', JsonElementType.Object, null, [
+ halNodeElementFactory('first-rel', JsonElementType.Array, HalElementType.LinkRel, null, [
+ jsonNodeElementFactory(null, JsonElementType.Object, null, [
+ jsonNodeElementFactory('first-property', JsonElementType.String, 'first property value', [], true)
+ ], false),
+ jsonNodeElementFactory(null, JsonElementType.Object, null, [
+ jsonNodeElementFactory('second-property', JsonElementType.String, 'second property value', [], true)
+ ], true),
+ ], true),
+ ], false));
+ });
+});
+describe('buildHalJsonTree', () => {
+ it('buildHalJsonTree for an complete hal object', () => {
+
+ expect(
+ buildHalJsonTree({
+ '_links': {
+ 'curies': [{
+ 'name': 'curie-name',
+ 'href': 'http://curie-href',
+ 'templated': true
+ }],
+ 'curie-name:curie-parameter': {
+ 'href': 'http://self-url'
+ },
+ 'an-array-link-rel': [
+ {
+ 'href': 'http://one-array-link-rel-url'
+ },
+ {
+ 'href': 'http://other-array-link-rel-url'
+ }
+ ]
+ },
+ '_embedded': {
+ 'a-embedded-rel': {
+ 'a-property': 'a property value'
+ },
+ 'curie-name:other-embedded-rel': [
+ {
+ 'first-property': 'first property value'
+ },
+ {
+ 'second-property': 'second property value'
+ }
+ ]
+ },
+ 'string-property': 'string property value',
+ 'boolean-property': true,
+ 'array-property': [
+ 'first-array-item',
+ false,
+ 123
+ ],
+ 'a-sub-object': {
+ 'a-sub-object-property': 'a sub object property value'
+ }
+ })
+ ).toEqual(
+ jsonNodeElementFactory('root', JsonElementType.Object, null, [
+ jsonNodeElementFactory('string-property', JsonElementType.String, 'string property value', [], false),
+ jsonNodeElementFactory('boolean-property', JsonElementType.Boolean, 'true', [], false),
+ jsonNodeElementFactory('array-property', JsonElementType.Array, null, [
+ jsonNodeElementFactory(null, JsonElementType.String, 'first-array-item', [], false),
+ jsonNodeElementFactory(null, JsonElementType.Boolean, 'false', [], false),
+ jsonNodeElementFactory(null, JsonElementType.Number, '123', [], true),
+ ], false),
+ jsonNodeElementFactory('a-sub-object', JsonElementType.Object, null, [
+ jsonNodeElementFactory('a-sub-object-property', JsonElementType.String, 'a sub object property value', [], true),
+ ], false),
+ jsonNodeElementFactory('_links', JsonElementType.Object, null, [
+ halNodeElementFactory('curie-name:curie-parameter', JsonElementType.Object, HalElementType.LinkRel, null, [
+ halNodeElementFactory('href', JsonElementType.String, HalElementType.LinkHref, 'http://self-url', [], true)
+ ], false, new Curie('curie-name', 'http://curie-href', true)),
+ halNodeElementFactory('an-array-link-rel', JsonElementType.Array, HalElementType.LinkRel, null, [
+ jsonNodeElementFactory(null, JsonElementType.Object, null, [
+ halNodeElementFactory('href', JsonElementType.String, HalElementType.LinkHref, 'http://one-array-link-rel-url', [], true)
+ ], false),
+ jsonNodeElementFactory(null, JsonElementType.Object, null, [
+ halNodeElementFactory('href', JsonElementType.String, HalElementType.LinkHref, 'http://other-array-link-rel-url', [], true)
+ ], true)
+ ], true)
+ ], false),
+ jsonNodeElementFactory('_embedded', JsonElementType.Object, null, [
+ halNodeElementFactory('a-embedded-rel', JsonElementType.Object, HalElementType.LinkRel, null, [
+ jsonNodeElementFactory('a-property', JsonElementType.String, 'a property value', [], true),
+ ], false),
+ halNodeElementFactory('curie-name:other-embedded-rel', JsonElementType.Array, HalElementType.LinkRel, null, [
+ jsonNodeElementFactory(null, JsonElementType.Object, null, [
+ jsonNodeElementFactory('first-property', JsonElementType.String, 'first property value', [], true)
+ ], false),
+ jsonNodeElementFactory(null, JsonElementType.Object, null, [
+ jsonNodeElementFactory('second-property', JsonElementType.String, 'second property value', [], true)
+ ], true),
+ ], true, new Curie('curie-name', 'http://curie-href', true))
+ ], true)
+ ], true));
+ });
+});
+describe('isCuriedRel', () => {
+ it('isCuriedRel for a rel without curie', () => {
+ expect(
+ getCurieName('no-curied-rel')
+ ).toBeNull();
+ });
+ it('isCuriedRel for a rel with curie', () => {
+ expect(
+ getCurieName('curie:parameter')
+ ).toBe('curie');
+ });
+});
diff --git a/projects/hal-render-component-library/src/lib/tree-model/tree-model.ts b/projects/hal-render-component-library/src/lib/tree-model/tree-model.ts
new file mode 100644
index 0000000..b2a023b
--- /dev/null
+++ b/projects/hal-render-component-library/src/lib/tree-model/tree-model.ts
@@ -0,0 +1,285 @@
+export enum JsonElementType {
+ Root,
+ Object,
+ Array,
+ String,
+ Number,
+ Boolean,
+ Null,
+ LinkRel
+}
+
+export enum HalElementType {
+ LinkRel,
+ LinkHref,
+ TemplatedLinkHref,
+ PropertyUrl
+}
+
+export class JsonElementNode {
+ name: string;
+ type: JsonElementType;
+ halElementType: HalElementType;
+ curie: Curie;
+ value: string;
+ children: JsonElementNode[];
+ expanded: boolean;
+ lastItem: boolean;
+}
+
+export class Curie {
+
+ constructor(public name: string, public href: string, public templated: boolean) { }
+
+}
+
+export class Curies {
+ curies: Curie[] = [];
+
+ add(curie: Curie) {
+ this.curies.push(curie);
+ }
+
+ getCuriesNames() {
+ return this.curies.reduce((acum, curie, i) => {
+ return acum + (i === 0 ? '' : ', ') + curie.name;
+ }, '');
+ }
+
+ get(name: string) {
+ for (let i = 0; i <= this.curies.length - 1; i++) {
+ if (this.curies[i].name === name) {
+ return this.curies[i];
+ }
+ }
+ throw new Error(`Could not find curie named ${name}. availabe curies are: ${this.getCuriesNames()}`);
+ }
+
+}
+
+
+export function jsonNodeElementFactory(name: string, type: JsonElementType, value: string, children: JsonElementNode[], lastItem: boolean) {
+ const result = new JsonElementNode();
+ result.name = name;
+ result.type = type;
+ result.value = value;
+ result.children = children;
+ result.expanded = false;
+ result.lastItem = lastItem;
+
+ return result;
+}
+
+export function halNodeElementFactory(name: string, type: JsonElementType, halElementType: HalElementType,
+ value: string, children: JsonElementNode[], lastItem: boolean, curie: Curie = null) {
+
+ const result = new JsonElementNode();
+ result.name = name;
+ result.type = type;
+ if (halElementType != null) {
+ result.halElementType = halElementType;
+ }
+ result.value = value;
+ result.children = children;
+ result.expanded = false;
+ result.lastItem = lastItem;
+ if (curie) {
+ result.curie = curie;
+ }
+
+ return result;
+}
+
+export function buildJsonTree(value: any): JsonElementNode {
+ return buildJsonNode(value, true, 'root');
+}
+
+
+export function buildHalJsonTree(value: object): JsonElementNode {
+ const _links = value['_links'];
+ const _embedded = value['_embedded'];
+ delete value['_links'];
+ delete value['_embedded'];
+ const resourceTree = buildJsonTree(value);
+
+ resourceTree.type = JsonElementType.Root;
+
+ if (resourceTree.children.length > 0) {
+ resourceTree.children[resourceTree.children.length - 1].lastItem = false;
+ }
+ let curies;
+ if (_links) {
+ curies = extractCuries(_links);
+
+ delete _links['curies'];
+ resourceTree.children.push(buildHalLinksJsonTree({
+ '_links': _links
+ }, curies));
+ }
+ if (_embedded) {
+ resourceTree.children.push(buildHalEmbeddedJsonTree({
+ '_embedded': _embedded
+ }, curies));
+ }
+ resourceTree.children[resourceTree.children.length - 1].lastItem = true;
+ return resourceTree;
+}
+
+export function buildHalLinksJsonTree(links: object, curies: Curies = new Curies()): JsonElementNode {
+ const linksRoot = jsonNodeElementFactory('_links', JsonElementType.Object, null, [], false);
+ const linksRels = links['_links'];
+
+ Object.keys(linksRels).forEach((linkRel) => {
+ const curieName = getCurieName(linkRel);
+
+ if (Array.isArray(linksRels[linkRel])) {
+ const linkRelArray = halNodeElementFactory(linkRel, JsonElementType.Array, HalElementType.LinkRel, null, [], false);
+ if (curieName) {
+ linkRelArray.curie = curies.get(curieName);
+ }
+ linksRoot.children.push(linkRelArray);
+ linksRels[linkRel].forEach((linkRelItem) => {
+ linkRelArray.children.push(
+ jsonNodeElementFactory(null, JsonElementType.Object, null, buildHalLinkProperties(linkRelItem), false)
+ );
+ });
+ linkRelArray.children[linkRelArray.children.length - 1].lastItem = true;
+ } else if (typeof linksRels[linkRel] === 'object') {
+ const linkRelObject = halNodeElementFactory(
+ linkRel,
+ JsonElementType.Object,
+ HalElementType.LinkRel,
+ null,
+ buildHalLinkProperties(linksRels[linkRel]),
+ false
+ );
+ if (curieName) {
+ linkRelObject.curie = curies.get(curieName);
+ }
+ linksRoot.children.push(linkRelObject);
+ }
+ });
+ linksRoot.children[linksRoot.children.length - 1].lastItem = true;
+ return linksRoot;
+}
+
+export function extractCuries(links: object) {
+ const curies = new Curies();
+ if (links['curies']) {
+ links['curies'].forEach((curie) => {
+ curies.add(new Curie(curie.name, curie.href, curie.templated));
+ });
+ }
+ return curies;
+}
+
+export function getCurieName(rel: string) {
+ const curieMarkPosition = rel.indexOf(':');
+
+ if ((!rel.startsWith('http:')) && curieMarkPosition > -1) {
+ return rel.substring(0, curieMarkPosition);
+ }
+ return null;
+}
+
+export function buildHalEmbeddedJsonTree(links: object, curies: Curies = new Curies()): JsonElementNode {
+ const embeddedRoot = jsonNodeElementFactory('_embedded', JsonElementType.Object, null, [], false);
+ const embeddedRels = links['_embedded'];
+
+ Object.keys(embeddedRels).forEach((embeddedRel) => {
+ const curieName = getCurieName(embeddedRel);
+
+ if (Array.isArray(embeddedRels[embeddedRel])) {
+ const embeddedRelArray = halNodeElementFactory(embeddedRel, JsonElementType.Array, HalElementType.LinkRel, null, [], false);
+ if (curieName) {
+ embeddedRelArray.curie = curies.get(curieName);
+ }
+ embeddedRoot.children.push(embeddedRelArray);
+ embeddedRels[embeddedRel].forEach((embeddedItem) => {
+ embeddedRelArray.children.push(buildJsonNode(embeddedItem, false));
+ });
+ embeddedRelArray.children[embeddedRelArray.children.length - 1].lastItem = true;
+ } else if (typeof embeddedRels[embeddedRel] === 'object') {
+ const embeddedResourceTree = buildHalJsonTree(embeddedRels[embeddedRel]);
+ if (curieName) {
+ embeddedResourceTree.curie = curies.get(curieName);
+ }
+ embeddedRoot.children.push(
+ halNodeElementFactory(embeddedRel, JsonElementType.Object, HalElementType.LinkRel, null, [
+ embeddedResourceTree.children[0]
+ ], false)
+ );
+ }
+ });
+ embeddedRoot.children[embeddedRoot.children.length - 1].lastItem = true;
+ return embeddedRoot;
+}
+
+export function buildHalLinkProperties(linkProperties: object): JsonElementNode[] {
+ const jsonElementNodes: JsonElementNode[] = [];
+
+ if (!linkProperties['href']) {
+ throw new Error('Link must have a href property');
+ }
+
+ buildHalLinkProperty(jsonElementNodes, linkProperties, 'href', JsonElementType.String, HalElementType.LinkHref);
+ if (linkProperties['templated']) {
+ jsonElementNodes[0].halElementType = HalElementType.TemplatedLinkHref;
+ }
+
+ buildHalLinkProperty(jsonElementNodes, linkProperties, 'templated', JsonElementType.Boolean, null);
+ buildHalLinkProperty(jsonElementNodes, linkProperties, 'type', JsonElementType.String, null);
+ buildHalLinkProperty(jsonElementNodes, linkProperties, 'deprecation', JsonElementType.String, HalElementType.PropertyUrl);
+ buildHalLinkProperty(jsonElementNodes, linkProperties, 'name', JsonElementType.String, null);
+ buildHalLinkProperty(jsonElementNodes, linkProperties, 'profile', JsonElementType.String, HalElementType.PropertyUrl);
+ buildHalLinkProperty(jsonElementNodes, linkProperties, 'title', JsonElementType.String, null);
+ buildHalLinkProperty(jsonElementNodes, linkProperties, 'hreflang', JsonElementType.String, null);
+
+ if (jsonElementNodes.length > 0) {
+ jsonElementNodes[jsonElementNodes.length - 1].lastItem = true;
+ }
+
+ return jsonElementNodes;
+}
+
+export function buildHalLinkProperty(jsonElementNodes: JsonElementNode[], linkProperties: object, propertyName: string,
+ type: JsonElementType, halElementType: HalElementType) {
+ if (linkProperties.hasOwnProperty(propertyName)) {
+ jsonElementNodes.push(halNodeElementFactory(propertyName, type, halElementType, linkProperties[propertyName].toString(), [], false));
+ }
+}
+
+function buildJsonNode(value: any, last: boolean, name = null): JsonElementNode {
+ const node = new JsonElementNode();
+ node.children = [];
+ node.value = null;
+ node.name = name;
+ node.lastItem = last;
+ node.expanded = false;
+
+
+ if (value == null) {
+ node.type = JsonElementType.Null;
+ } else if (typeof value === 'string') {
+ node.type = JsonElementType.String;
+ node.value = value;
+ } else if (typeof value === 'number') {
+ node.type = JsonElementType.Number;
+ node.value = value.toString();
+ } else if (typeof value === 'boolean') {
+ node.type = JsonElementType.Boolean;
+ node.value = value.toString();
+ } else if (!Array.isArray(value) && typeof value === 'object') {
+ node.type = JsonElementType.Object;
+ Object.keys(value).forEach((key, index, keysArray) => {
+ node.children.push(buildJsonNode(value[key], index === keysArray.length - 1, key));
+ });
+ } else if (Array.isArray(value)) {
+ node.type = JsonElementType.Array;
+ value.forEach((arrayItem, index, array) => {
+ node.children.push(buildJsonNode(arrayItem, index === array.length - 1));
+ });
+ }
+ return node;
+}
+
diff --git a/projects/hal-render-component-library/src/lib/uri-template-editor/template-parser.spec.ts b/projects/hal-render-component-library/src/lib/uri-template-editor/template-parser.spec.ts
new file mode 100644
index 0000000..4d25b61
--- /dev/null
+++ b/projects/hal-render-component-library/src/lib/uri-template-editor/template-parser.spec.ts
@@ -0,0 +1,68 @@
+import * as URITemplate from 'urijs/src/URITemplate';
+import { parse, UriText, UriVariable, groupConsecutiveTexts, getTemplateValuesAsObject } from './template-parser';
+
+
+describe('template parser', () => {
+ it('groupConsecutiveTexts without consecutive texts', () => {
+ expect(
+ groupConsecutiveTexts([new UriText('a'), new UriVariable('b'), new UriText('c')])
+ ).
+ toEqual(
+ [new UriText('a'), new UriVariable('b'), new UriText('c')]
+ );
+ });
+ it('groupConsecutiveTexts with consecutive texts', () => {
+ expect(
+ groupConsecutiveTexts([new UriText('a'), new UriText('b'), new UriText('c'), new UriVariable('d'), new UriText('e'), new UriText('f')])
+ ).
+ toEqual(
+ [new UriText('abc'), new UriVariable('d'), new UriText('ef')]
+ );
+ });
+});
+describe('template parser', () => {
+ it('simple template no operators', () => {
+ expect(
+ parse(new URITemplate('/order/any?a=1&b=2'))
+ ).
+ toEqual(
+ [new UriText('/order/any?a=1&b=2')]
+ );
+ });
+ it('template with variable without operator', () => {
+ expect(
+ parse(new URITemplate('/order/{id}/any?a=1&b=2'))
+ ).
+ toEqual(
+ [new UriText('/order/'), new UriVariable('id'), new UriText('/any?a=1&b=2')]
+ );
+ });
+ it('template with ? variable operator', () => {
+ expect(
+ parse(new URITemplate('/order/{id}/any{?a,b}'))
+ ).
+ toEqual(
+ [new UriText('/order/'), new UriVariable('id'), new UriText('/any?'), new UriVariable('a'), new UriText('&'), new UriVariable('b')]
+ );
+ });
+ it('template with & variable operator', () => {
+ expect(
+ parse(new URITemplate('/order/{id}/any?a=1{&b,c}'))
+ ).
+ toEqual(
+ [new UriText('/order/'), new UriVariable('id'), new UriText('/any?a=1&'), new UriVariable('b'), new UriText('&'), new UriVariable('c')]
+ );
+ });
+});
+describe('template parser', () => {
+ it('getTemplateValuesAsObject', () => {
+ expect(
+ getTemplateValuesAsObject([new UriText('a'), new UriVariable('b', 'value of b'), new UriVariable('c')])
+ ).
+ toEqual(
+ {
+ 'b' : 'value of b'
+ }
+ );
+ });
+});
diff --git a/projects/hal-render-component-library/src/lib/uri-template-editor/template-parser.ts b/projects/hal-render-component-library/src/lib/uri-template-editor/template-parser.ts
new file mode 100644
index 0000000..07072ce
--- /dev/null
+++ b/projects/hal-render-component-library/src/lib/uri-template-editor/template-parser.ts
@@ -0,0 +1,61 @@
+export class UriVariable {
+ constructor(public name: string, public value: string = '') {
+ }
+}
+
+export class UriText {
+ constructor(public text: string) { }
+}
+
+export function parse(template: any): any[] {
+ const parts = template.parse().parts;
+ const parsedParts = [];
+ parts.forEach((element) => {
+ if (typeof element === 'object') {
+ switch (element.operator) {
+ case '?': {
+ parsedParts.push(new UriText('?'));
+ break;
+ }
+ }
+ element.variables.forEach((variable, index) => {
+ if (element.operator === '?' && index > 0) {
+ parsedParts.push(new UriText('&'));
+ } else if (element.operator === '&') {
+ parsedParts.push(new UriText('&'));
+ }
+ parsedParts.push(new UriVariable(variable.name));
+ });
+ } else {
+ if (typeof element === 'string' && element.trim().length > 0) {
+ parsedParts.push(new UriText(element));
+ }
+ }
+ });
+ return groupConsecutiveTexts(parsedParts);
+}
+
+export function groupConsecutiveTexts(templateParts: any[]) {
+ const resultTemplateParts = [];
+ templateParts.forEach((templatePart, index, templatePartsInternal) => {
+ if (index > 0 && templatePart instanceof UriText && templatePartsInternal[index - 1] instanceof UriText) {
+ resultTemplateParts[resultTemplateParts.length - 1] =
+ new UriText(resultTemplateParts[resultTemplateParts.length - 1].text + templatePart.text);
+ } else {
+ resultTemplateParts.push(templatePart);
+ }
+ });
+
+ return resultTemplateParts;
+
+}
+
+export function getTemplateValuesAsObject(templateParts: any[]): object {
+ const values = {};
+ templateParts.forEach((templatePart) => {
+ if (templatePart instanceof UriVariable && templatePart.value.length > 0) {
+ values[templatePart.name] = templatePart.value;
+ }
+ });
+ return values;
+}
diff --git a/projects/hal-render-component-library/src/lib/uri-template-editor/uri-template-editor.component.css b/projects/hal-render-component-library/src/lib/uri-template-editor/uri-template-editor.component.css
new file mode 100644
index 0000000..1cf97c0
--- /dev/null
+++ b/projects/hal-render-component-library/src/lib/uri-template-editor/uri-template-editor.component.css
@@ -0,0 +1,17 @@
+.uriTemplateArea {
+ display: inline;
+ font-family: monospace;
+}
+
+.uriTemplateEdit {
+ display: inline;
+ font-family: monospace;
+ border: 0;
+ outline: 0;
+ background: transparent;
+ border-bottom: 1px solid black;
+}
+
+.uriTemplateText {
+ display: inline;
+}
diff --git a/projects/hal-render-component-library/src/lib/uri-template-editor/uri-template-editor.component.html b/projects/hal-render-component-library/src/lib/uri-template-editor/uri-template-editor.component.html
new file mode 100644
index 0000000..1501fae
--- /dev/null
+++ b/projects/hal-render-component-library/src/lib/uri-template-editor/uri-template-editor.component.html
@@ -0,0 +1,8 @@
+
diff --git a/projects/hal-render-component-library/src/lib/uri-template-editor/uri-template-editor.component.ts b/projects/hal-render-component-library/src/lib/uri-template-editor/uri-template-editor.component.ts
new file mode 100644
index 0000000..adfd138
--- /dev/null
+++ b/projects/hal-render-component-library/src/lib/uri-template-editor/uri-template-editor.component.ts
@@ -0,0 +1,41 @@
+import { Component, Input, Output } from '@angular/core';
+import * as URITemplate from 'urijs/src/URITemplate';
+import { parse, UriVariable, UriText, getTemplateValuesAsObject } from './template-parser';
+
+@Component({
+ selector: 'hrc-uri-template-editor',
+ templateUrl: './uri-template-editor.component.html',
+ styleUrls: ['./uri-template-editor.component.css']
+})
+export class UriTemplateEditorComponent {
+
+ templateParts: any[];
+ template: any;
+
+ @Input()
+ set uriTemplate(template: string) {
+ this.template = new URITemplate(template);
+ this.templateParts = parse(this.template);
+ }
+
+ isVariable(templatePart: any) {
+ return templatePart instanceof UriVariable;
+ }
+
+ isText(templatePart: any) {
+ return templatePart instanceof UriText;
+ }
+
+ keyUp(templatePart: any, event: any) {
+ templatePart.value = event.target.value;
+ }
+
+ getInputLength(templatePart: any) {
+ return templatePart.value.length === 0 ? 1 : templatePart.value.length;
+ }
+
+ evaluatedTemplate() {
+ return this.template.expand(getTemplateValuesAsObject(this.templateParts));
+ }
+
+}
diff --git a/projects/hal-render-component-library/src/projects.ts b/projects/hal-render-component-library/src/projects.ts
new file mode 100644
index 0000000..22130f1
--- /dev/null
+++ b/projects/hal-render-component-library/src/projects.ts
@@ -0,0 +1,7 @@
+/*
+ * Public API Surface of hal-render-component-library
+ */
+
+// export * from './lib/hal-render-component-library.service';
+export * from './lib/hal-render.component';
+export * from './lib/hal-render-component.module';
diff --git a/projects/hal-render-component-library/styles.css b/projects/hal-render-component-library/styles.css
new file mode 100644
index 0000000..584837e
--- /dev/null
+++ b/projects/hal-render-component-library/styles.css
@@ -0,0 +1,81 @@
+ul {
+ list-style-type: none;
+}
+
+.expander {
+ display: inline;
+}
+
+.leaf-property-name {
+ display: inline;
+}
+
+.leaf-boolean-property-value {
+ display: inline;
+ color: brown;
+}
+
+.leaf-string-property-value {
+ display: inline;
+ color: green;
+}
+
+.leaf-string-property-link-value {
+ display: inline;
+ font-family: monospace;
+ color: green;
+}
+
+.leaf-number-property-value {
+ display: inline;
+ color: blue;
+}
+
+.leaf-boolean-property-value {
+ display: inline;
+ color: brown;
+}
+
+.leaf-property-separator {
+ display: inline;
+}
+
+.link {
+ display: inline;
+ font-family: monospace;
+ color: black;
+}
+
+.expand-simbol {
+ cursor: pointer;
+ user-select: none;
+ display: inline;
+ font-weight : bold;
+}
+
+.branch-end {
+ display: inline;
+}
+
+.branch-property-name {
+ user-select: none;
+ display: inline;
+}
+
+.uriTemplateArea {
+ display: inline;
+ font-family: monospace;
+}
+
+.uriTemplateEdit {
+ display: inline;
+ font-family: monospace;
+ border: 0;
+ outline: 0;
+ background: transparent;
+ border-bottom: 1px solid black;
+}
+
+.uriTemplateText {
+ display: inline;
+}
diff --git a/projects/hal-render-component-library/tsconfig.lib.json b/projects/hal-render-component-library/tsconfig.lib.json
new file mode 100644
index 0000000..3fe337f
--- /dev/null
+++ b/projects/hal-render-component-library/tsconfig.lib.json
@@ -0,0 +1,32 @@
+{
+ "extends": "../../tsconfig.json",
+ "compilerOptions": {
+ "outDir": "../../out-tsc/lib",
+ "target": "es2015",
+ "module": "es2015",
+ "moduleResolution": "node",
+ "declaration": true,
+ "sourceMap": true,
+ "inlineSources": true,
+ "emitDecoratorMetadata": true,
+ "experimentalDecorators": true,
+ "importHelpers": true,
+ "types": [],
+ "lib": [
+ "dom",
+ "es2018"
+ ]
+ },
+ "angularCompilerOptions": {
+ "annotateForClosureCompiler": true,
+ "skipTemplateCodegen": true,
+ "strictMetadataEmit": true,
+ "fullTemplateTypeCheck": true,
+ "strictInjectionParameters": true,
+ "enableResourceInlining": true
+ },
+ "exclude": [
+ "src/test.ts",
+ "**/*.spec.ts"
+ ]
+}
diff --git a/projects/hal-render-component-library/tsconfig.spec.json b/projects/hal-render-component-library/tsconfig.spec.json
new file mode 100644
index 0000000..4e03dc6
--- /dev/null
+++ b/projects/hal-render-component-library/tsconfig.spec.json
@@ -0,0 +1,17 @@
+{
+ "extends": "../../tsconfig.json",
+ "compilerOptions": {
+ "outDir": "../../out-tsc/spec",
+ "types": [
+ "jasmine",
+ "node"
+ ]
+ },
+ "files": [
+ "src/lib/tree-model/first-tree-model-bug.spec.ts"
+ ],
+ "include": [
+ "**/*.spec.ts",
+ "**/*.d.ts"
+ ]
+}
diff --git a/projects/hal-render-component-library/tslint.json b/projects/hal-render-component-library/tslint.json
new file mode 100644
index 0000000..26bac5b
--- /dev/null
+++ b/projects/hal-render-component-library/tslint.json
@@ -0,0 +1,17 @@
+{
+ "extends": "../../tslint.json",
+ "rules": {
+ "directive-selector": [
+ true,
+ "attribute",
+ "hrc",
+ "camelCase"
+ ],
+ "component-selector": [
+ true,
+ "element",
+ "hrc",
+ "kebab-case"
+ ]
+ }
+}
diff --git a/src/app/app.component.css b/src/app/app.component.css
new file mode 100644
index 0000000..ccd7d76
--- /dev/null
+++ b/src/app/app.component.css
@@ -0,0 +1,5 @@
+.boxsizingBorder {
+ -webkit-box-sizing: border-box;
+ -moz-box-sizing: border-box;
+ box-sizing: border-box;
+}
diff --git a/src/app/app.component.html b/src/app/app.component.html
new file mode 100644
index 0000000..9eb053d
--- /dev/null
+++ b/src/app/app.component.html
@@ -0,0 +1,57 @@
+
+
+
+
+
diff --git a/src/app/app.component.ts b/src/app/app.component.ts
new file mode 100644
index 0000000..6feee7a
--- /dev/null
+++ b/src/app/app.component.ts
@@ -0,0 +1,19 @@
+import { Component, ViewChild, ElementRef } from '@angular/core';
+
+@Component({
+ selector: 'app-root',
+ templateUrl: './app.component.html',
+ styleUrls: ['./app.component.css']
+})
+export class AppComponent {
+
+ @ViewChild('jsonValueTextArea')
+ jsonValueTextArea: ElementRef;
+
+ hal: string;
+
+ renderHAL() {
+ this.hal = this.jsonValueTextArea.nativeElement.value;
+ }
+
+}
diff --git a/src/app/app.module.ts b/src/app/app.module.ts
new file mode 100644
index 0000000..93a0ae2
--- /dev/null
+++ b/src/app/app.module.ts
@@ -0,0 +1,20 @@
+import { BrowserModule } from '@angular/platform-browser';
+import { NgModule } from '@angular/core';
+
+import { AppComponent } from './app.component';
+import { HalRenderComponentModule } from '../../projects/hal-render-component-library/src/lib/hal-render-component.module';
+import { CommonModule } from '@angular/common';
+
+@NgModule({
+ declarations: [
+ AppComponent
+ ],
+ imports: [
+ BrowserModule,
+ CommonModule,
+ HalRenderComponentModule
+ ],
+ providers: [],
+ bootstrap: [AppComponent]
+})
+export class AppModule { }
diff --git a/src/assets/.gitkeep b/src/assets/.gitkeep
new file mode 100644
index 0000000..e69de29
diff --git a/src/browserslist b/src/browserslist
new file mode 100644
index 0000000..37371cb
--- /dev/null
+++ b/src/browserslist
@@ -0,0 +1,11 @@
+# This file is currently used by autoprefixer to adjust CSS to support the below specified browsers
+# For additional information regarding the format and rule options, please see:
+# https://github.com/browserslist/browserslist#queries
+#
+# For IE 9-11 support, please remove 'not' from the last line of the file and adjust as needed
+
+> 0.5%
+last 2 versions
+Firefox ESR
+not dead
+not IE 9-11
\ No newline at end of file
diff --git a/src/environments/environment.prod.ts b/src/environments/environment.prod.ts
new file mode 100644
index 0000000..3612073
--- /dev/null
+++ b/src/environments/environment.prod.ts
@@ -0,0 +1,3 @@
+export const environment = {
+ production: true
+};
diff --git a/src/environments/environment.ts b/src/environments/environment.ts
new file mode 100644
index 0000000..7b4f817
--- /dev/null
+++ b/src/environments/environment.ts
@@ -0,0 +1,16 @@
+// This file can be replaced during build by using the `fileReplacements` array.
+// `ng build --prod` replaces `environment.ts` with `environment.prod.ts`.
+// The list of file replacements can be found in `angular.json`.
+
+export const environment = {
+ production: false
+};
+
+/*
+ * For easier debugging in development mode, you can import the following file
+ * to ignore zone related error stack frames such as `zone.run`, `zoneDelegate.invokeTask`.
+ *
+ * This import should be commented out in production mode because it will have a negative impact
+ * on performance if an error is thrown.
+ */
+// import 'zone.js/dist/zone-error'; // Included with Angular CLI.
diff --git a/src/favicon.ico b/src/favicon.ico
new file mode 100644
index 0000000..8081c7c
Binary files /dev/null and b/src/favicon.ico differ
diff --git a/src/index.html b/src/index.html
new file mode 100644
index 0000000..6ba637a
--- /dev/null
+++ b/src/index.html
@@ -0,0 +1,14 @@
+
+
+
+
+ HalRenderComponent
+
+
+
+
+
+
+
+
+
diff --git a/src/karma.conf.js b/src/karma.conf.js
new file mode 100644
index 0000000..ee9caa1
--- /dev/null
+++ b/src/karma.conf.js
@@ -0,0 +1,31 @@
+// Karma configuration file, see link for more information
+// https://karma-runner.github.io/1.0/config/configuration-file.html
+
+module.exports = function (config) {
+ config.set({
+ basePath: '',
+ frameworks: ['jasmine', '@angular-devkit/build-angular'],
+ plugins: [
+ require('karma-jasmine'),
+ require('karma-chrome-launcher'),
+ require('karma-jasmine-html-reporter'),
+ require('karma-coverage-istanbul-reporter'),
+ require('@angular-devkit/build-angular/plugins/karma')
+ ],
+ client: {
+ clearContext: false // leave Jasmine Spec Runner output visible in browser
+ },
+ coverageIstanbulReporter: {
+ dir: require('path').join(__dirname, '../coverage'),
+ reports: ['html', 'lcovonly', 'text-summary'],
+ fixWebpackSourcePaths: true
+ },
+ reporters: ['progress', 'kjhtml'],
+ port: 9876,
+ colors: true,
+ logLevel: config.LOG_INFO,
+ autoWatch: true,
+ browsers: ['Chrome'],
+ singleRun: false
+ });
+};
\ No newline at end of file
diff --git a/src/main.ts b/src/main.ts
new file mode 100644
index 0000000..c7b673c
--- /dev/null
+++ b/src/main.ts
@@ -0,0 +1,12 @@
+import { enableProdMode } from '@angular/core';
+import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
+
+import { AppModule } from './app/app.module';
+import { environment } from './environments/environment';
+
+if (environment.production) {
+ enableProdMode();
+}
+
+platformBrowserDynamic().bootstrapModule(AppModule)
+ .catch(err => console.error(err));
diff --git a/src/polyfills.ts b/src/polyfills.ts
new file mode 100644
index 0000000..a6d34ea
--- /dev/null
+++ b/src/polyfills.ts
@@ -0,0 +1,85 @@
+/**
+ * This file includes polyfills needed by Angular and is loaded before the app.
+ * You can add your own extra polyfills to this file.
+ *
+ * This file is divided into 2 sections:
+ * 1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers.
+ * 2. Application imports. Files imported after ZoneJS that should be loaded before your main
+ * file.
+ *
+ * The current setup is for so-called "evergreen" browsers; the last versions of browsers that
+ * automatically update themselves. This includes Safari >= 10, Chrome >= 55 (including Opera),
+ * Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile.
+ *
+ * Learn more in https://angular.io/guide/browser-support
+ */
+
+/***************************************************************************************************
+ * BROWSER POLYFILLS
+ */
+
+/** IE9, IE10, IE11, and Chrome <55 requires all of the following polyfills.
+ * This also includes Android Emulators with older versions of Chrome and Google Search/Googlebot
+ */
+
+// import 'core-js/es6/symbol';
+// import 'core-js/es6/object';
+// import 'core-js/es6/function';
+// import 'core-js/es6/parse-int';
+// import 'core-js/es6/parse-float';
+// import 'core-js/es6/number';
+// import 'core-js/es6/math';
+// import 'core-js/es6/string';
+// import 'core-js/es6/date';
+// import 'core-js/es6/array';
+// import 'core-js/es6/regexp';
+// import 'core-js/es6/map';
+// import 'core-js/es6/weak-map';
+// import 'core-js/es6/set';
+
+/** IE10 and IE11 requires the following for NgClass support on SVG elements */
+// import 'classlist.js'; // Run `npm install --save classlist.js`.
+
+/** IE10 and IE11 requires the following for the Reflect API. */
+// import 'core-js/es6/reflect';
+
+/**
+ * Web Animations `@angular/platform-browser/animations`
+ * Only required if AnimationBuilder is used within the application and using IE/Edge or Safari.
+ * Standard animation support in Angular DOES NOT require any polyfills (as of Angular 6.0).
+ */
+// import 'web-animations-js'; // Run `npm install --save web-animations-js`.
+
+/**
+ * By default, zone.js will patch all possible macroTask and DomEvents
+ * user can disable parts of macroTask/DomEvents patch by setting following flags
+ * because those flags need to be set before `zone.js` being loaded, and webpack
+ * will put import in the top of bundle, so user need to create a separate file
+ * in this directory (for example: zone-flags.ts), and put the following flags
+ * into that file, and then add the following code before importing zone.js.
+ * import './zone-flags.ts';
+ *
+ * The flags allowed in zone-flags.ts are listed here.
+ *
+ * The following flags will work for all browsers.
+ *
+ * (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame
+ * (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick
+ * (window as any).__zone_symbol__BLACK_LISTED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames
+ *
+ * in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js
+ * with the following flag, it will bypass `zone.js` patch for IE/Edge
+ *
+ * (window as any).__Zone_enable_cross_context_check = true;
+ *
+ */
+
+/***************************************************************************************************
+ * Zone JS is required by default for Angular itself.
+ */
+import 'zone.js/dist/zone'; // Included with Angular CLI.
+
+
+/***************************************************************************************************
+ * APPLICATION IMPORTS
+ */
diff --git a/src/styles.css b/src/styles.css
new file mode 100644
index 0000000..90d4ee0
--- /dev/null
+++ b/src/styles.css
@@ -0,0 +1 @@
+/* You can add global styles to this file, and also import other style files */
diff --git a/src/tsconfig.app.json b/src/tsconfig.app.json
new file mode 100644
index 0000000..190fd30
--- /dev/null
+++ b/src/tsconfig.app.json
@@ -0,0 +1,11 @@
+{
+ "extends": "../tsconfig.json",
+ "compilerOptions": {
+ "outDir": "../out-tsc/app",
+ "types": []
+ },
+ "exclude": [
+ "test.ts",
+ "**/*.spec.ts"
+ ]
+}
diff --git a/src/tsconfig.spec.json b/src/tsconfig.spec.json
new file mode 100644
index 0000000..de77336
--- /dev/null
+++ b/src/tsconfig.spec.json
@@ -0,0 +1,18 @@
+{
+ "extends": "../tsconfig.json",
+ "compilerOptions": {
+ "outDir": "../out-tsc/spec",
+ "types": [
+ "jasmine",
+ "node"
+ ]
+ },
+ "files": [
+ "test.ts",
+ "polyfills.ts"
+ ],
+ "include": [
+ "**/*.spec.ts",
+ "**/*.d.ts"
+ ]
+}
diff --git a/src/tslint.json b/src/tslint.json
new file mode 100644
index 0000000..52e2c1a
--- /dev/null
+++ b/src/tslint.json
@@ -0,0 +1,17 @@
+{
+ "extends": "../tslint.json",
+ "rules": {
+ "directive-selector": [
+ true,
+ "attribute",
+ "app",
+ "camelCase"
+ ],
+ "component-selector": [
+ true,
+ "element",
+ "app",
+ "kebab-case"
+ ]
+ }
+}
diff --git a/tsconfig.json b/tsconfig.json
new file mode 100644
index 0000000..9f27b8d
--- /dev/null
+++ b/tsconfig.json
@@ -0,0 +1,40 @@
+{
+ "compileOnSave": false,
+ "compilerOptions": {
+ "baseUrl": "./",
+ "outDir": "./dist/out-tsc",
+ "sourceMap": true,
+ "declaration": false,
+ "module": "es2015",
+ "moduleResolution": "node",
+ "emitDecoratorMetadata": true,
+ "experimentalDecorators": true,
+ "importHelpers": true,
+ "target": "es5",
+ "typeRoots": [
+ "node_modules/@types"
+ ],
+ "lib": [
+ "es2018",
+ "dom"
+ ],
+ "paths": {
+ "hal-render-component-library": [
+ "dist/hal-render-component-library",
+ "dist/hal-render-component-library",
+ "dist/hal-render-component-library"
+ ],
+ "hal-render-component-library/*": [
+ "dist/hal-render-component-library/*",
+ "dist/hal-render-component-library/*",
+ "dist/hal-render-component-library/*"
+ ],
+ "chrome-plugin": [
+ "dist/chrome-plugin"
+ ],
+ "chrome-plugin/*": [
+ "dist/chrome-plugin/*"
+ ]
+ }
+ }
+}
diff --git a/tslint.json b/tslint.json
new file mode 100644
index 0000000..c740a7b
--- /dev/null
+++ b/tslint.json
@@ -0,0 +1,131 @@
+{
+ "rulesDirectory": [
+ "codelyzer"
+ ],
+ "rules": {
+ "arrow-return-shorthand": true,
+ "callable-types": true,
+ "class-name": true,
+ "comment-format": [
+ true,
+ "check-space"
+ ],
+ "curly": true,
+ "deprecation": {
+ "severity": "warn"
+ },
+ "eofline": true,
+ "forin": true,
+ "import-blacklist": [
+ true,
+ "rxjs/Rx"
+ ],
+ "import-spacing": true,
+ "indent": [
+ true,
+ "spaces"
+ ],
+ "interface-over-type-literal": true,
+ "label-position": true,
+ "max-line-length": [
+ true,
+ 140
+ ],
+ "member-access": false,
+ "member-ordering": [
+ true,
+ {
+ "order": [
+ "static-field",
+ "instance-field",
+ "static-method",
+ "instance-method"
+ ]
+ }
+ ],
+ "no-arg": true,
+ "no-bitwise": true,
+ "no-console": [
+ true,
+ "debug",
+ "info",
+ "time",
+ "timeEnd",
+ "trace"
+ ],
+ "no-construct": true,
+ "no-debugger": true,
+ "no-duplicate-super": true,
+ "no-empty": false,
+ "no-empty-interface": true,
+ "no-eval": true,
+ "no-inferrable-types": [
+ true,
+ "ignore-params"
+ ],
+ "no-misused-new": true,
+ "no-non-null-assertion": true,
+ "no-redundant-jsdoc": true,
+ "no-shadowed-variable": true,
+ "no-string-literal": false,
+ "no-string-throw": true,
+ "no-switch-case-fall-through": true,
+ "no-trailing-whitespace": true,
+ "no-unnecessary-initializer": true,
+ "no-unused-expression": true,
+ "no-use-before-declare": true,
+ "no-var-keyword": true,
+ "object-literal-sort-keys": false,
+ "one-line": [
+ true,
+ "check-open-brace",
+ "check-catch",
+ "check-else",
+ "check-whitespace"
+ ],
+ "prefer-const": true,
+ "quotemark": [
+ true,
+ "single"
+ ],
+ "radix": true,
+ "semicolon": [
+ true,
+ "always"
+ ],
+ "triple-equals": [
+ true,
+ "allow-null-check"
+ ],
+ "typedef-whitespace": [
+ true,
+ {
+ "call-signature": "nospace",
+ "index-signature": "nospace",
+ "parameter": "nospace",
+ "property-declaration": "nospace",
+ "variable-declaration": "nospace"
+ }
+ ],
+ "unified-signatures": true,
+ "variable-name": false,
+ "whitespace": [
+ true,
+ "check-branch",
+ "check-decl",
+ "check-operator",
+ "check-separator",
+ "check-type"
+ ],
+ "no-output-on-prefix": true,
+ "use-input-property-decorator": true,
+ "use-output-property-decorator": true,
+ "use-host-property-decorator": true,
+ "no-input-rename": true,
+ "no-output-rename": true,
+ "use-life-cycle-interface": true,
+ "use-pipe-transform-interface": true,
+ "component-class-suffix": true,
+ "directive-class-suffix": true
+ }
+}