diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..e89330a --- /dev/null +++ b/.editorconfig @@ -0,0 +1,13 @@ +# Editor configuration, see https://editorconfig.org +root = true + +[*] +charset = utf-8 +indent_style = space +indent_size = 2 +insert_final_newline = true +trim_trailing_whitespace = true + +[*.md] +max_line_length = off +trim_trailing_whitespace = false diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..85158b7 --- /dev/null +++ b/.gitignore @@ -0,0 +1,43 @@ +# See http://help.github.com/ignore-files/ for more about ignoring files. + +# compiled output +/dist +/tmp +/out-tsc + +# dependencies +/node_modules + +# profiling files +chrome-profiler-events.json +speed-measure-plugin.json + +# IDEs and editors +/.idea +.project +.classpath +.c9/ +*.launch +.settings/ +*.sublime-workspace + +# IDE - VSCode +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json + +# misc +/.sass-cache +/connect.lock +/coverage +/libpeerconnection.log +npm-debug.log +yarn-error.log +testem.log +/typings + +# System Files +.DS_Store +Thumbs.db diff --git a/README.md b/README.md new file mode 100644 index 0000000..120375a --- /dev/null +++ b/README.md @@ -0,0 +1,27 @@ +# HalRenderComponent + +This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 7.1.4. + +## Development server + +Run `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The app will automatically reload if you change any of the source files. + +## Code scaffolding + +Run `ng generate component component-name` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module`. + +## Build + +Run `ng build` to build the project. The build artifacts will be stored in the `dist/` directory. Use the `--prod` flag for a production build. + +## Running unit tests + +Run `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io). + +## Running end-to-end tests + +Run `ng e2e` to execute the end-to-end tests via [Protractor](http://www.protractortest.org/). + +## Further help + +To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI README](https://github.com/angular/angular-cli/blob/master/README.md). diff --git a/angular.json b/angular.json new file mode 100644 index 0000000..87a3103 --- /dev/null +++ b/angular.json @@ -0,0 +1,238 @@ +{ + "$schema": "./node_modules/@angular/cli/lib/config/schema.json", + "version": 1, + "newProjectRoot": "projects", + "projects": { + "hal-render-component-library": { + "root": "projects/hal-render-component-library", + "sourceRoot": "projects/hal-render-component-library/src", + "projectType": "library", + "prefix": "hrc", + "architect": { + "build": { + "builder": "@angular-devkit/build-ng-packagr:build", + "options": { + "tsConfig": "projects/hal-render-component-library/tsconfig.lib.json", + "project": "projects/hal-render-component-library/ng-package.json" + } + }, + "test": { + "builder": "@angular-devkit/build-angular:karma", + "options": { + "main": "projects/hal-render-component-library/src/lib/tree-model/first-tree-model-bug.spec.ts", + "tsConfig": "projects/hal-render-component-library/tsconfig.spec.json", + "karmaConfig": "projects/hal-render-component-library/karma.conf.js" + } + }, + "lint": { + "builder": "@angular-devkit/build-angular:tslint", + "options": { + "tsConfig": [ + "projects/hal-render-component-library/tsconfig.lib.json", + "projects/hal-render-component-library/tsconfig.spec.json" + ], + "exclude": [ + "**/node_modules/**" + ] + } + } + } + }, + "chrome-plugin": { + "root": "projects/chrome-plugin/", + "sourceRoot": "projects/chrome-plugin/src", + "projectType": "application", + "prefix": "app", + "schematics": {}, + "architect": { + "build": { + "builder": "@angular-devkit/build-angular:browser", + "options": { + "outputPath": "dist/chrome-plugin", + "index": "src/index.html", + "main": "projects/chrome-plugin/src/main.ts", + "polyfills": "projects/chrome-plugin/src/polyfills.ts", + "tsConfig": "projects/chrome-plugin/tsconfig.app.json", + "assets": [ + "projects/chrome-plugin/src/favicon.ico", + "projects/chrome-plugin/src/assets" + ], + "scripts": [] + }, + "configurations": { + "production": { + "fileReplacements": [ + { + "replace": "projects/chrome-plugin/src/environments/environment.ts", + "with": "projects/chrome-plugin/src/environments/environment.prod.ts" + } + ], + "optimization": true, + "outputHashing": "all", + "sourceMap": false, + "extractCss": true, + "namedChunks": false, + "aot": true, + "extractLicenses": true, + "vendorChunk": false, + "buildOptimizer": true, + "budgets": [ + { + "type": "initial", + "maximumWarning": "2mb", + "maximumError": "5mb" + } + ] + } + } + }, + "serve": { + "builder": "@angular-devkit/build-angular:dev-server", + "options": { + "browserTarget": "chrome-plugin:build" + }, + "configurations": { + "production": { + "browserTarget": "chrome-plugin:build:production" + } + } + }, + "extract-i18n": { + "builder": "@angular-devkit/build-angular:extract-i18n", + "options": { + "browserTarget": "chrome-plugin:build" + } + }, + "test": { + "builder": "@angular-devkit/build-angular:karma", + "options": { + "main": "projects/chrome-plugin/src/test.ts", + "polyfills": "projects/chrome-plugin/src/polyfills.ts", + "tsConfig": "projects/chrome-plugin/tsconfig.spec.json", + "karmaConfig": "projects/chrome-plugin/karma.conf.js", + "styles": [ + "projects/chrome-plugin/src/styles.css" + ], + "scripts": [], + "assets": [ + "projects/chrome-plugin/src/favicon.ico", + "projects/chrome-plugin/src/assets" + ] + } + }, + "lint": { + "builder": "@angular-devkit/build-angular:tslint", + "options": { + "tsConfig": [ + "projects/chrome-plugin/tsconfig.app.json", + "projects/chrome-plugin/tsconfig.spec.json" + ], + "exclude": [ + "**/node_modules/**" + ] + } + } + } + }, + "hal-render-application": { + "root": "", + "sourceRoot": "src", + "projectType": "application", + "prefix": "app", + "schematics": {}, + "architect": { + "build": { + "builder": "@angular-devkit/build-angular:browser", + "options": { + "outputPath": "dist/hal-render-application", + "index": "src/index.html", + "main": "src/main.ts", + "polyfills": "src/polyfills.ts", + "tsConfig": "src/tsconfig.app.json", + "assets": [ + "src/favicon.ico", + "src/assets" + ], + "styles": [ + "src/styles.css" + ], + "scripts": [] + }, + "configurations": { + "production": { + "fileReplacements": [ + { + "replace": "src/environments/environment.ts", + "with": "src/environments/environment.prod.ts" + } + ], + "optimization": true, + "outputHashing": "all", + "sourceMap": false, + "extractCss": true, + "namedChunks": false, + "aot": true, + "extractLicenses": true, + "vendorChunk": false, + "buildOptimizer": true, + "budgets": [ + { + "type": "initial", + "maximumWarning": "2mb", + "maximumError": "5mb" + } + ] + } + } + }, + "serve": { + "builder": "@angular-devkit/build-angular:dev-server", + "options": { + "browserTarget": "hal-render-application:build" + }, + "configurations": { + "production": { + "browserTarget": "hal-render-application:build:production" + } + } + }, + "extract-i18n": { + "builder": "@angular-devkit/build-angular:extract-i18n", + "options": { + "browserTarget": "hal-render-application:build" + } + }, + "test": { + "builder": "@angular-devkit/build-angular:karma", + "options": { + "main": "src/test.ts", + "polyfills": "src/polyfills.ts", + "tsConfig": "src/tsconfig.spec.json", + "karmaConfig": "src/karma.conf.js", + "styles": [ + "src/styles.css" + ], + "scripts": [], + "assets": [ + "src/favicon.ico", + "src/assets" + ] + } + }, + "lint": { + "builder": "@angular-devkit/build-angular:tslint", + "options": { + "tsConfig": [ + "src/tsconfig.app.json", + "src/tsconfig.spec.json" + ], + "exclude": [ + "**/node_modules/**" + ] + } + } + } + } + }, + "defaultProject": "hal-render-application" +} diff --git a/e2e/protractor.conf.js b/e2e/protractor.conf.js new file mode 100644 index 0000000..86776a3 --- /dev/null +++ b/e2e/protractor.conf.js @@ -0,0 +1,28 @@ +// Protractor configuration file, see link for more information +// https://github.com/angular/protractor/blob/master/lib/config.ts + +const { SpecReporter } = require('jasmine-spec-reporter'); + +exports.config = { + allScriptsTimeout: 11000, + specs: [ + './src/**/*.e2e-spec.ts' + ], + capabilities: { + 'browserName': 'chrome' + }, + directConnect: true, + baseUrl: 'http://localhost:4200/', + framework: 'jasmine', + jasmineNodeOpts: { + showColors: true, + defaultTimeoutInterval: 30000, + print: function() {} + }, + onPrepare() { + require('ts-node').register({ + project: require('path').join(__dirname, './tsconfig.e2e.json') + }); + jasmine.getEnv().addReporter(new SpecReporter({ spec: { displayStacktrace: true } })); + } +}; \ No newline at end of file diff --git a/e2e/src/app.e2e-spec.ts b/e2e/src/app.e2e-spec.ts new file mode 100644 index 0000000..b3f0933 --- /dev/null +++ b/e2e/src/app.e2e-spec.ts @@ -0,0 +1,14 @@ +import { AppPage } from './app.po'; + +describe('workspace-project App', () => { + let page: AppPage; + + beforeEach(() => { + page = new AppPage(); + }); + + it('should display welcome message', () => { + page.navigateTo(); + expect(page.getTitleText()).toEqual('Welcome to hal-render-component!'); + }); +}); diff --git a/e2e/src/app.po.ts b/e2e/src/app.po.ts new file mode 100644 index 0000000..72e463a --- /dev/null +++ b/e2e/src/app.po.ts @@ -0,0 +1,11 @@ +import { browser, by, element } from 'protractor'; + +export class AppPage { + navigateTo() { + return browser.get('/'); + } + + getTitleText() { + return element(by.css('app-root h1')).getText(); + } +} diff --git a/e2e/tsconfig.e2e.json b/e2e/tsconfig.e2e.json new file mode 100644 index 0000000..a6dd622 --- /dev/null +++ b/e2e/tsconfig.e2e.json @@ -0,0 +1,13 @@ +{ + "extends": "../tsconfig.json", + "compilerOptions": { + "outDir": "../out-tsc/app", + "module": "commonjs", + "target": "es5", + "types": [ + "jasmine", + "jasminewd2", + "node" + ] + } +} \ No newline at end of file diff --git a/package.json b/package.json new file mode 100644 index 0000000..5a631dc --- /dev/null +++ b/package.json @@ -0,0 +1,56 @@ +{ + "name": "hal-render-component", + "version": "0.0.0", + "scripts": { + "ng": "ng", + "start": "ng serve", + "build": "ng build", + "build-chrome-plugin": "ng build chrome-plugin --main projects/chrome-plugin/src/main.ts && copyfiles -f projects/chrome-plugin/src/app/manifest.json projects/chrome-plugin/src/app/icon.png dist/chrome-plugin", + "test": "ng test", + "lint": "ng lint", + "e2e": "ng e2e" + }, + "private": true, + "dependencies": { + "@angular/animations": "~7.1.0", + "@angular/common": "~7.1.0", + "@angular/compiler": "~7.1.0", + "@angular/core": "~7.1.0", + "@angular/forms": "~7.1.0", + "@angular/platform-browser": "~7.1.0", + "@angular/platform-browser-dynamic": "~7.1.0", + "@angular/router": "~7.1.0", + "core-js": "^2.5.4", + "rxjs": "~6.3.3", + "tslib": "^1.9.0", + "urijs": "^1.19.1", + "zone.js": "^0.8.16" + }, + "devDependencies": { + "@types/chrome": "0.0.60", + "@angular-devkit/build-angular": "~0.11.0", + "@angular-devkit/build-ng-packagr": "~0.11.0", + "@angular/cli": "~7.1.4", + "@angular/compiler-cli": "~7.1.0", + "@angular/language-service": "~7.1.0", + "@types/jasmine": "~2.8.8", + "@types/jasminewd2": "~2.0.3", + "@types/node": "~8.9.4", + "codelyzer": "~4.5.0", + "copyfiles": "^2.1.0", + "jasmine-core": "~2.99.1", + "jasmine-spec-reporter": "~4.2.1", + "karma": "~3.1.1", + "karma-chrome-launcher": "~2.2.0", + "karma-coverage-istanbul-reporter": "~2.0.1", + "karma-jasmine": "~1.1.2", + "karma-jasmine-html-reporter": "^0.2.2", + "ng-packagr": "^4.2.0", + "protractor": "~5.4.0", + "ts-node": "~7.0.0", + "tsickle": ">=0.29.0", + "tslib": "^1.9.0", + "tslint": "~5.11.0", + "typescript": "~3.1.6" + } +} diff --git a/projects/chrome-plugin/browserslist b/projects/chrome-plugin/browserslist new file mode 100644 index 0000000..37371cb --- /dev/null +++ b/projects/chrome-plugin/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/projects/chrome-plugin/karma.conf.js b/projects/chrome-plugin/karma.conf.js new file mode 100644 index 0000000..c57e806 --- /dev/null +++ b/projects/chrome-plugin/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/projects/chrome-plugin/src/app/app.component.ts b/projects/chrome-plugin/src/app/app.component.ts new file mode 100644 index 0000000..50db536 --- /dev/null +++ b/projects/chrome-plugin/src/app/app.component.ts @@ -0,0 +1,22 @@ +import { Component, OnInit } from '@angular/core'; + +@Component({ + selector: 'app-root', + template: '', +}) +export class AppComponent { + + jsonValue = this.getPreJsonElement().textContent; + + getPreJsonElement(): ChildNode { + let preElement: ChildNode; + + document.body.childNodes.forEach((element) => { + if (element.nodeName.toUpperCase() === 'PRE') { + preElement = element; + } + }); + + return preElement; + } +} diff --git a/projects/chrome-plugin/src/app/app.module.ts b/projects/chrome-plugin/src/app/app.module.ts new file mode 100644 index 0000000..92149b0 --- /dev/null +++ b/projects/chrome-plugin/src/app/app.module.ts @@ -0,0 +1,26 @@ +import { BrowserModule } from '@angular/platform-browser'; +import { NgModule } from '@angular/core'; +import { AppComponent } from './app.component'; +import { HalRenderComponent } from 'projects/hal-render-component-library/src/projects'; +import { HalRenderComponentModule } from 'projects/hal-render-component-library/src/lib/hal-render-component.module'; +import { AngularWaitBarrier } from 'blocking-proxy/built/lib/angular_wait_barrier'; +// import { HalRenderComponent } from 'projects/hal-render-component-library/src/lib/hal-render.component'; + +@NgModule({ + declarations: [ + AppComponent + ], + imports: [BrowserModule, HalRenderComponentModule], + entryComponents: [AppComponent] +}) +export class AppModule { + + ngDoBootstrap(app) { + const componentElement = document.createElement('app-root'); + const preElement = document.body.firstChild; + document.body.insertBefore(componentElement, preElement); + app.bootstrap(AppComponent); + document.body.childNodes[1].remove(); + } + +} diff --git a/projects/chrome-plugin/src/app/icon.png b/projects/chrome-plugin/src/app/icon.png new file mode 100644 index 0000000..5d1b4eb Binary files /dev/null and b/projects/chrome-plugin/src/app/icon.png differ diff --git a/projects/chrome-plugin/src/app/manifest.json b/projects/chrome-plugin/src/app/manifest.json new file mode 100644 index 0000000..dff2255 --- /dev/null +++ b/projects/chrome-plugin/src/app/manifest.json @@ -0,0 +1,15 @@ +{ + "name": "HAL Formatter", + "version": "0.0.1", + "manifest_version": 2, + "description": "Makes HAL easy to read. Open source.", + "homepage_url": "https://github.com/callumlocke/json-formatter", + "minimum_chrome_version": "21", + "icons": { + "32": "icon.png" + }, + "content_scripts": [ + { "matches": [""], "js": ["main.js", "polyfills.js", "runtime.js", "vendor.js"], "run_at": "document_start" } + ], + "permissions":["*://*/*", ""] +} 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()}} 
    +
    +
    {{getNodeName()}}
    + {{getLinkRel()}} + {{getLinkRel()}} +
    {{getKeyValueDelimiter()}} {{branchStart()}}
    +
    +
    +
      + +
    +
    +
    {{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 @@ +
    + +
    {{templatePart.name}}=
    + +
    {{templatePart.text}}
    +
    + ({{evaluatedTemplate()}}) +
    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 + } +}