@cosla/sensemaking-web-ui 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +35 -0
- package/angular.json +116 -0
- package/health_check.js +68 -0
- package/karma.conf.js +43 -0
- package/package.json +93 -0
- package/public/favicon.ico +0 -0
- package/server.ts +60 -0
- package/single-html-build.js +100 -0
- package/site-build.ts +54 -0
- package/src/app/app.component.html +1 -0
- package/src/app/app.component.scss +0 -0
- package/src/app/app.component.spec.ts +32 -0
- package/src/app/app.component.ts +21 -0
- package/src/app/app.config.server.ts +9 -0
- package/src/app/app.config.ts +19 -0
- package/src/app/app.routes.ts +8 -0
- package/src/app/components/dialog/dialog.component.html +15 -0
- package/src/app/components/dialog/dialog.component.scss +42 -0
- package/src/app/components/dialog/dialog.component.spec.ts +27 -0
- package/src/app/components/dialog/dialog.component.ts +40 -0
- package/src/app/components/sensemaking-chart-wrapper/sensemaking-chart-wrapper.component.ts +66 -0
- package/src/app/components/statement-card/statement-card.component.html +51 -0
- package/src/app/components/statement-card/statement-card.component.scss +134 -0
- package/src/app/components/statement-card/statement-card.component.spec.ts +23 -0
- package/src/app/components/statement-card/statement-card.component.ts +50 -0
- package/src/app/directives/custom-tooltip/custom-tooltip.directive.spec.ts +39 -0
- package/src/app/directives/custom-tooltip/custom-tooltip.directive.ts +89 -0
- package/src/app/models/report.model.ts +48 -0
- package/src/app/pages/report/report.component.html +363 -0
- package/src/app/pages/report/report.component.scss +600 -0
- package/src/app/pages/report/report.component.spec.ts +29 -0
- package/src/app/pages/report/report.component.ts +276 -0
- package/src/environments/environment.ts +5 -0
- package/src/index.html +17 -0
- package/src/main.server.ts +7 -0
- package/src/main.ts +5 -0
- package/src/style-vars.scss +40 -0
- package/src/styles.scss +23 -0
- package/tsconfig.app.json +19 -0
- package/tsconfig.json +32 -0
- package/tsconfig.spec.json +15 -0
package/README.md
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# web-ui
|
|
2
|
+
|
|
3
|
+
This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 18.1.1.
|
|
4
|
+
|
|
5
|
+
For setup and basic usage, see the `README.md` file in the root of this repository.
|
|
6
|
+
What follows is web-ui-side developer-specific information, autogenerated from Angular.
|
|
7
|
+
|
|
8
|
+
## Running
|
|
9
|
+
|
|
10
|
+
```bash
|
|
11
|
+
cd web-ui
|
|
12
|
+
npm run dev
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
Note that this will run both the web-ui build & serve process, and the backend server.
|
|
16
|
+
|
|
17
|
+
## Code scaffolding
|
|
18
|
+
|
|
19
|
+
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`.
|
|
20
|
+
|
|
21
|
+
## Build
|
|
22
|
+
|
|
23
|
+
Run `ng build` to build the project. The build artifacts will be stored in the `dist/` directory.
|
|
24
|
+
|
|
25
|
+
## Running unit tests
|
|
26
|
+
|
|
27
|
+
Run `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io).
|
|
28
|
+
|
|
29
|
+
## Running end-to-end tests
|
|
30
|
+
|
|
31
|
+
Run `ng e2e` to execute the end-to-end tests via a platform of your choice. To use this command, you need to first add a package that implements end-to-end testing capabilities.
|
|
32
|
+
|
|
33
|
+
## Further help
|
|
34
|
+
|
|
35
|
+
To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI Overview and Command Reference](https://angular.dev/tools/cli) page.
|
package/angular.json
ADDED
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
|
|
3
|
+
"version": 1,
|
|
4
|
+
"newProjectRoot": "projects",
|
|
5
|
+
"projects": {
|
|
6
|
+
"web-ui": {
|
|
7
|
+
"projectType": "application",
|
|
8
|
+
"schematics": {
|
|
9
|
+
"@schematics/angular:component": {
|
|
10
|
+
"style": "scss"
|
|
11
|
+
}
|
|
12
|
+
},
|
|
13
|
+
"root": "",
|
|
14
|
+
"sourceRoot": "src",
|
|
15
|
+
"prefix": "app",
|
|
16
|
+
"architect": {
|
|
17
|
+
"build": {
|
|
18
|
+
"builder": "@angular-devkit/build-angular:application",
|
|
19
|
+
"options": {
|
|
20
|
+
"outputPath": "dist/web-ui",
|
|
21
|
+
"index": "src/index.html",
|
|
22
|
+
"browser": "src/main.ts",
|
|
23
|
+
"polyfills": [
|
|
24
|
+
"zone.js"
|
|
25
|
+
],
|
|
26
|
+
"tsConfig": "tsconfig.app.json",
|
|
27
|
+
"inlineStyleLanguage": "scss",
|
|
28
|
+
"assets": [
|
|
29
|
+
{
|
|
30
|
+
"glob": "**/*",
|
|
31
|
+
"input": "public"
|
|
32
|
+
}
|
|
33
|
+
],
|
|
34
|
+
"styles": [
|
|
35
|
+
"@angular/material/prebuilt-themes/azure-blue.css",
|
|
36
|
+
"src/styles.scss"
|
|
37
|
+
],
|
|
38
|
+
"scripts": [],
|
|
39
|
+
"server": "src/main.server.ts",
|
|
40
|
+
"prerender": true,
|
|
41
|
+
"ssr": {
|
|
42
|
+
"entry": "server.ts"
|
|
43
|
+
}
|
|
44
|
+
},
|
|
45
|
+
"configurations": {
|
|
46
|
+
"production": {
|
|
47
|
+
"budgets": [
|
|
48
|
+
{
|
|
49
|
+
"type": "initial",
|
|
50
|
+
"maximumWarning": "20MB",
|
|
51
|
+
"maximumError": "20MB"
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
"type": "anyComponentStyle",
|
|
55
|
+
"maximumWarning": "100kB",
|
|
56
|
+
"maximumError": "100kB"
|
|
57
|
+
}
|
|
58
|
+
],
|
|
59
|
+
"outputHashing": "all"
|
|
60
|
+
},
|
|
61
|
+
"development": {
|
|
62
|
+
"optimization": false,
|
|
63
|
+
"extractLicenses": false,
|
|
64
|
+
"sourceMap": true
|
|
65
|
+
}
|
|
66
|
+
},
|
|
67
|
+
"defaultConfiguration": "production"
|
|
68
|
+
},
|
|
69
|
+
"serve": {
|
|
70
|
+
"builder": "@angular-devkit/build-angular:dev-server",
|
|
71
|
+
"configurations": {
|
|
72
|
+
"production": {
|
|
73
|
+
"buildTarget": "web-ui:build:production"
|
|
74
|
+
},
|
|
75
|
+
"development": {
|
|
76
|
+
"buildTarget": "web-ui:build:development"
|
|
77
|
+
}
|
|
78
|
+
},
|
|
79
|
+
"defaultConfiguration": "development",
|
|
80
|
+
"options": {
|
|
81
|
+
"host": "0.0.0.0",
|
|
82
|
+
"port": 4200
|
|
83
|
+
}
|
|
84
|
+
},
|
|
85
|
+
"extract-i18n": {
|
|
86
|
+
"builder": "@angular-devkit/build-angular:extract-i18n"
|
|
87
|
+
},
|
|
88
|
+
"test": {
|
|
89
|
+
"builder": "@angular-devkit/build-angular:karma",
|
|
90
|
+
"options": {
|
|
91
|
+
"polyfills": [
|
|
92
|
+
"zone.js",
|
|
93
|
+
"zone.js/testing"
|
|
94
|
+
],
|
|
95
|
+
"tsConfig": "tsconfig.spec.json",
|
|
96
|
+
"inlineStyleLanguage": "scss",
|
|
97
|
+
"assets": [
|
|
98
|
+
{
|
|
99
|
+
"glob": "**/*",
|
|
100
|
+
"input": "public"
|
|
101
|
+
}
|
|
102
|
+
],
|
|
103
|
+
"styles": [
|
|
104
|
+
"@angular/material/prebuilt-themes/azure-blue.css",
|
|
105
|
+
"src/styles.scss"
|
|
106
|
+
],
|
|
107
|
+
"scripts": []
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
},
|
|
113
|
+
"cli": {
|
|
114
|
+
"analytics": "6ff4f325-17ba-4fbb-ae7e-d93e98737dc5"
|
|
115
|
+
}
|
|
116
|
+
}
|
package/health_check.js
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
const esbuild = require("esbuild");
|
|
2
|
+
const jsdom = require("jsdom");
|
|
3
|
+
const fs = require("fs");
|
|
4
|
+
const path = require("path");
|
|
5
|
+
|
|
6
|
+
const { JSDOM } = jsdom;
|
|
7
|
+
|
|
8
|
+
const argv = process.argv.slice(2);
|
|
9
|
+
function getArgValue(names) {
|
|
10
|
+
for (let i = 0; i < argv.length; i++) {
|
|
11
|
+
const a = argv[i];
|
|
12
|
+
for (const name of names) {
|
|
13
|
+
if (a === name && argv[i + 1]) return argv[i + 1];
|
|
14
|
+
if (a.startsWith(name + "=")) return a.split("=", 2)[1];
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
return undefined;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const outputFilePath = getArgValue(["-o", "--outputFile"]) || "dist/health-check.html";
|
|
21
|
+
const outputDir = path.dirname(outputFilePath);
|
|
22
|
+
|
|
23
|
+
fs.mkdirSync(outputDir, { recursive: true });
|
|
24
|
+
|
|
25
|
+
const dom = new JSDOM(`<!DOCTYPE html>
|
|
26
|
+
<html>
|
|
27
|
+
<head>
|
|
28
|
+
<title>Web-UI Health Check</title>
|
|
29
|
+
</head>
|
|
30
|
+
<body>
|
|
31
|
+
<h1>Hello World</h1>
|
|
32
|
+
<p>Web-UI health check passed!</p>
|
|
33
|
+
</body>
|
|
34
|
+
</html>`);
|
|
35
|
+
|
|
36
|
+
const simpleScript = `
|
|
37
|
+
console.log('Health check: Web-UI dependencies are working!');
|
|
38
|
+
`;
|
|
39
|
+
|
|
40
|
+
esbuild
|
|
41
|
+
.build({
|
|
42
|
+
stdin: {
|
|
43
|
+
contents: simpleScript,
|
|
44
|
+
loader: "js",
|
|
45
|
+
},
|
|
46
|
+
bundle: true,
|
|
47
|
+
minify: true,
|
|
48
|
+
format: "iife",
|
|
49
|
+
write: false,
|
|
50
|
+
})
|
|
51
|
+
.then(
|
|
52
|
+
(result) => {
|
|
53
|
+
const scriptSrc = result.outputFiles[0].text;
|
|
54
|
+
const script = dom.window.document.createElement("script");
|
|
55
|
+
script.innerHTML = scriptSrc;
|
|
56
|
+
dom.window.document.body.appendChild(script);
|
|
57
|
+
|
|
58
|
+
fs.writeFileSync(outputFilePath, dom.serialize());
|
|
59
|
+
console.log(`Health check HTML file created at ${path.resolve(outputFilePath)}`);
|
|
60
|
+
console.log("✓ Web-UI health check passed");
|
|
61
|
+
process.exit(0);
|
|
62
|
+
},
|
|
63
|
+
(error) => {
|
|
64
|
+
console.error("Health check failed:", error);
|
|
65
|
+
process.exit(1);
|
|
66
|
+
}
|
|
67
|
+
);
|
|
68
|
+
|
package/karma.conf.js
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
// karma.conf.js
|
|
2
|
+
module.exports = function (config) {
|
|
3
|
+
config.set({
|
|
4
|
+
basePath: "",
|
|
5
|
+
frameworks: ["jasmine", "@angular-devkit/build-angular"],
|
|
6
|
+
plugins: [
|
|
7
|
+
require("karma-jasmine"),
|
|
8
|
+
require("karma-chrome-launcher"),
|
|
9
|
+
require("karma-jasmine-html-reporter"),
|
|
10
|
+
require("karma-coverage"),
|
|
11
|
+
require("@angular-devkit/build-angular/plugins/karma"),
|
|
12
|
+
],
|
|
13
|
+
client: {
|
|
14
|
+
jasmine: {
|
|
15
|
+
// you can add configuration options for Jasmine here
|
|
16
|
+
// the possible options are listed at https://jasmine.github.io/api/edge/Jasmine/Jasmine
|
|
17
|
+
},
|
|
18
|
+
clearContext: false, // leave Jasmine Spec Runner output visible in browser
|
|
19
|
+
},
|
|
20
|
+
jasmineHtmlReporter: {
|
|
21
|
+
suppressAll: true, // removes the duplicated traces
|
|
22
|
+
},
|
|
23
|
+
coverageReporter: {
|
|
24
|
+
dir: require("path").join(__dirname, "./coverage/web-ui"),
|
|
25
|
+
subdir: ".",
|
|
26
|
+
reporters: [{ type: "html" }, { type: "text-summary" }],
|
|
27
|
+
},
|
|
28
|
+
reporters: ["progress", "kjhtml"],
|
|
29
|
+
port: 9876,
|
|
30
|
+
colors: true,
|
|
31
|
+
logLevel: config.LOG_INFO,
|
|
32
|
+
autoWatch: true, //this will be overriden by the test command
|
|
33
|
+
customLaunchers: {
|
|
34
|
+
ChromeHeadlessNoSandbox: {
|
|
35
|
+
base: "ChromeHeadless",
|
|
36
|
+
flags: ["--no-sandbox"],
|
|
37
|
+
},
|
|
38
|
+
},
|
|
39
|
+
browsers: ["ChromeHeadlessNoSandbox"], // Run in Headless Chrome
|
|
40
|
+
singleRun: false, // Set to false for continuous testing, true to run once,
|
|
41
|
+
restartOnFileChange: true,
|
|
42
|
+
});
|
|
43
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@cosla/sensemaking-web-ui",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Provides interactive visualizations and report from sensemaking-tools output. Fork of Jigsaw's sensemaking-tools web-ui (https://github.com/Jigsaw-Code/sensemaking-tools).",
|
|
5
|
+
"homepage": "https://github.com/CoslaDigital/sensemaking-tools#readme",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"ng": "ng",
|
|
8
|
+
"build": "ng build",
|
|
9
|
+
"watch": "ng build --watch --configuration development",
|
|
10
|
+
"test": "ng test --watch=false --browsers=ChromeHeadlessNoSandbox --code-coverage",
|
|
11
|
+
"serve:ssr:web-ui": "node dist/web-ui/server/server.mjs",
|
|
12
|
+
"start:api-server": "nodemon --watch ../api-server --watch ../library ../api-server/index.ts",
|
|
13
|
+
"start:web-ui": "ng serve",
|
|
14
|
+
"dev": "concurrently --kill-others \"npm run start:api-server\" \"npm run start:web-ui\"",
|
|
15
|
+
"prepublishOnly": "echo 'Publishing @cosla/sensemaking-web-ui'"
|
|
16
|
+
},
|
|
17
|
+
"repository": {
|
|
18
|
+
"type": "git",
|
|
19
|
+
"url": "git+https://github.com/CoslaDigital/sensemaking-tools.git"
|
|
20
|
+
},
|
|
21
|
+
"keywords": [
|
|
22
|
+
"sensemaking",
|
|
23
|
+
"angular",
|
|
24
|
+
"visualization",
|
|
25
|
+
"civic-tech",
|
|
26
|
+
"public-input",
|
|
27
|
+
"data-visualization",
|
|
28
|
+
"web-ui",
|
|
29
|
+
"dashboard"
|
|
30
|
+
],
|
|
31
|
+
"author": "CoslaDigital",
|
|
32
|
+
"license": "ISC",
|
|
33
|
+
"files": [
|
|
34
|
+
"src/**/*",
|
|
35
|
+
"public/**/*",
|
|
36
|
+
"angular.json",
|
|
37
|
+
"tsconfig.json",
|
|
38
|
+
"tsconfig.app.json",
|
|
39
|
+
"tsconfig.spec.json",
|
|
40
|
+
"karma.conf.js",
|
|
41
|
+
"server.ts",
|
|
42
|
+
"site-build.ts",
|
|
43
|
+
"single-html-build.js",
|
|
44
|
+
"health_check.js",
|
|
45
|
+
"package.json",
|
|
46
|
+
"README.md"
|
|
47
|
+
],
|
|
48
|
+
"publishConfig": {
|
|
49
|
+
"access": "public"
|
|
50
|
+
},
|
|
51
|
+
"dependencies": {
|
|
52
|
+
"@angular/animations": "18.2.13",
|
|
53
|
+
"@angular/cdk": "18.2.13",
|
|
54
|
+
"@angular/common": "18.2.13",
|
|
55
|
+
"@angular/compiler": "18.2.13",
|
|
56
|
+
"@angular/core": "18.2.13",
|
|
57
|
+
"@angular/forms": "18.2.13",
|
|
58
|
+
"@angular/material": "18.2.13",
|
|
59
|
+
"@angular/platform-browser": "18.2.13",
|
|
60
|
+
"@angular/platform-browser-dynamic": "18.2.13",
|
|
61
|
+
"@angular/platform-server": "18.2.13",
|
|
62
|
+
"@angular/router": "18.2.13",
|
|
63
|
+
"@angular/ssr": "18.2.13",
|
|
64
|
+
"@conversationai/sensemaker-visualizations": "^1.0.46",
|
|
65
|
+
"@sinclair/typebox": "^0.34.27",
|
|
66
|
+
"express": "^4.18.2",
|
|
67
|
+
"ngx-markdown": "^18.0.0",
|
|
68
|
+
"papaparse": "^5.4.1",
|
|
69
|
+
"rxjs": "~7.8.0",
|
|
70
|
+
"tslib": "^2.3.0",
|
|
71
|
+
"zone.js": "~0.14.3"
|
|
72
|
+
},
|
|
73
|
+
"devDependencies": {
|
|
74
|
+
"@angular-devkit/build-angular": "18.2.13",
|
|
75
|
+
"@angular/cli": "18.2.13",
|
|
76
|
+
"@angular/compiler-cli": "18.2.13",
|
|
77
|
+
"@types/express": "^4.17.17",
|
|
78
|
+
"@types/jasmine": "~5.1.0",
|
|
79
|
+
"@types/node": "^18.18.0",
|
|
80
|
+
"@types/papaparse": "^5.3.14",
|
|
81
|
+
"concurrently": "^9.0.1",
|
|
82
|
+
"jasmine-core": "~5.1.0",
|
|
83
|
+
"jsdom": "^26.1.0",
|
|
84
|
+
"karma": "~6.4.0",
|
|
85
|
+
"karma-chrome-launcher": "~3.2.0",
|
|
86
|
+
"karma-coverage": "~2.2.0",
|
|
87
|
+
"karma-jasmine": "~5.1.0",
|
|
88
|
+
"karma-jasmine-html-reporter": "~2.1.0",
|
|
89
|
+
"nodemon": "^3.1.4",
|
|
90
|
+
"ts-node": "^10.9.2",
|
|
91
|
+
"typescript": "~5.5.2"
|
|
92
|
+
}
|
|
93
|
+
}
|
|
Binary file
|
package/server.ts
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { APP_BASE_HREF } from '@angular/common';
|
|
2
|
+
import { CommonEngine } from '@angular/ssr';
|
|
3
|
+
import express from 'express';
|
|
4
|
+
import { fileURLToPath } from 'node:url';
|
|
5
|
+
import { dirname, join, resolve } from 'node:path';
|
|
6
|
+
import bootstrap from './src/main.server';
|
|
7
|
+
|
|
8
|
+
// The Express app is exported so that it can be used by serverless Functions.
|
|
9
|
+
export function app(): express.Express {
|
|
10
|
+
const server = express();
|
|
11
|
+
const serverDistFolder = dirname(fileURLToPath(import.meta.url));
|
|
12
|
+
const browserDistFolder = resolve(serverDistFolder, '../browser');
|
|
13
|
+
const indexHtml = join(serverDistFolder, 'index.server.html');
|
|
14
|
+
|
|
15
|
+
const commonEngine = new CommonEngine();
|
|
16
|
+
|
|
17
|
+
server.set('view engine', 'html');
|
|
18
|
+
server.set('views', browserDistFolder);
|
|
19
|
+
|
|
20
|
+
// Example Express Rest API endpoints
|
|
21
|
+
// server.get('/api/**', (req, res) => { });
|
|
22
|
+
// Serve static files from /browser
|
|
23
|
+
server.get(
|
|
24
|
+
'**',
|
|
25
|
+
express.static(browserDistFolder, {
|
|
26
|
+
maxAge: '1y',
|
|
27
|
+
index: 'index.html',
|
|
28
|
+
})
|
|
29
|
+
);
|
|
30
|
+
|
|
31
|
+
// All regular routes use the Angular engine
|
|
32
|
+
server.get('**', (req, res, next) => {
|
|
33
|
+
const { protocol, originalUrl, baseUrl, headers } = req;
|
|
34
|
+
|
|
35
|
+
commonEngine
|
|
36
|
+
.render({
|
|
37
|
+
bootstrap,
|
|
38
|
+
documentFilePath: indexHtml,
|
|
39
|
+
url: `${protocol}://${headers.host}${originalUrl}`,
|
|
40
|
+
publicPath: browserDistFolder,
|
|
41
|
+
providers: [{ provide: APP_BASE_HREF, useValue: baseUrl }],
|
|
42
|
+
})
|
|
43
|
+
.then((html) => res.send(html))
|
|
44
|
+
.catch((err) => next(err));
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
return server;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function run(): void {
|
|
51
|
+
const port = process.env['PORT'] || 4200;
|
|
52
|
+
|
|
53
|
+
// Start up the Node server
|
|
54
|
+
const server = app();
|
|
55
|
+
server.listen(port, () => {
|
|
56
|
+
console.log(`Node Express server listening on http://localhost:${port}`);
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
run();
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
const esbuild = require("esbuild");
|
|
2
|
+
const jsdom = require("jsdom");
|
|
3
|
+
const fs = require("fs");
|
|
4
|
+
const path = require("path");
|
|
5
|
+
|
|
6
|
+
const { JSDOM } = jsdom;
|
|
7
|
+
const srcDir = "dist/web-ui/browser";
|
|
8
|
+
const defaultDestDir = "dist/bundled";
|
|
9
|
+
|
|
10
|
+
// Parse CLI args for output file path (supports -o value, --outputFile value, and --outputFile=value)
|
|
11
|
+
const argv = process.argv.slice(2);
|
|
12
|
+
function getArgValue(names) {
|
|
13
|
+
for (let i = 0; i < argv.length; i++) {
|
|
14
|
+
const a = argv[i];
|
|
15
|
+
for (const name of names) {
|
|
16
|
+
if (a === name && argv[i + 1]) return argv[i + 1];
|
|
17
|
+
if (a.startsWith(name + "=")) return a.split("=", 2)[1];
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
return undefined;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const outputFilePath = getArgValue(["-o", "--outputFile"]) || `${defaultDestDir}/report.html`;
|
|
24
|
+
const outputDir = path.dirname(outputFilePath);
|
|
25
|
+
|
|
26
|
+
fs.mkdirSync(outputDir, { recursive: true });
|
|
27
|
+
|
|
28
|
+
const indexFilePath = srcDir + "/index.csr.html"; // this is the root html file from the build
|
|
29
|
+
const htmlSource = fs.readFileSync(indexFilePath);
|
|
30
|
+
const dom = new JSDOM(htmlSource.toString());
|
|
31
|
+
|
|
32
|
+
let mainScript;
|
|
33
|
+
let scriptsToInject = [];
|
|
34
|
+
|
|
35
|
+
const isUrl = string => {
|
|
36
|
+
try {
|
|
37
|
+
return Boolean(new URL(string));
|
|
38
|
+
} catch (e) {
|
|
39
|
+
return false;
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
const scriptTags = Array.from(dom.window.document.getElementsByTagName("script"));
|
|
44
|
+
scriptTags.forEach((e) => {
|
|
45
|
+
let fileName = e.getAttribute("src");
|
|
46
|
+
if(fileName && !isUrl(fileName)) {
|
|
47
|
+
let scriptPath = srcDir + "/" + fileName;
|
|
48
|
+
if(fileName.includes("main-")) {
|
|
49
|
+
mainScript = scriptPath;
|
|
50
|
+
} else {
|
|
51
|
+
scriptsToInject.push(scriptPath);
|
|
52
|
+
}
|
|
53
|
+
e.remove();
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
const linkTags = Array.from(dom.window.document.getElementsByTagName("link"));
|
|
58
|
+
linkTags.forEach((e) => {
|
|
59
|
+
const rel = e.getAttribute("rel");
|
|
60
|
+
const href = e.getAttribute("href");
|
|
61
|
+
const media = e.getAttribute("media");
|
|
62
|
+
if(rel === "stylesheet" && !isUrl(href) && media === "print") {
|
|
63
|
+
e.remove();
|
|
64
|
+
let styleFile = srcDir + "/" + href;
|
|
65
|
+
// Add the stylesheet to dom as inline.
|
|
66
|
+
const style = dom.window.document.createElement("style");
|
|
67
|
+
style.innerHTML = fs.readFileSync(styleFile).toString();
|
|
68
|
+
dom.window.document.body.appendChild(style);
|
|
69
|
+
}
|
|
70
|
+
// find and remove unused resources to prevent console errors
|
|
71
|
+
if(rel === "modulepreload" || rel === "icon") {
|
|
72
|
+
e.remove();
|
|
73
|
+
}
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
// write JS to memory, then add to DOM, then write HTML file
|
|
77
|
+
esbuild
|
|
78
|
+
.build({
|
|
79
|
+
entryPoints: [mainScript],
|
|
80
|
+
inject: scriptsToInject,
|
|
81
|
+
bundle: true,
|
|
82
|
+
minify: true,
|
|
83
|
+
sourcemap: false,
|
|
84
|
+
outfile: path.join(outputDir, "bundled.js"), // file won't be created, but property is still required when writing to memory
|
|
85
|
+
format: "esm",
|
|
86
|
+
write: false, // places JS in memory instead of writing to file
|
|
87
|
+
})
|
|
88
|
+
.then(
|
|
89
|
+
(result) => {
|
|
90
|
+
const scriptSrc = result.outputFiles[0].text;
|
|
91
|
+
const script = dom.window.document.createElement("script");
|
|
92
|
+
script.innerHTML = scriptSrc;
|
|
93
|
+
dom.window.document.body.appendChild(script);
|
|
94
|
+
|
|
95
|
+
// do not rename file to "index.html"; causes a break
|
|
96
|
+
fs.writeFileSync(outputFilePath, dom.serialize());
|
|
97
|
+
console.log(`Report HTML file created at ${path.resolve(outputFilePath)}`);
|
|
98
|
+
},
|
|
99
|
+
(error) => console.error(error),
|
|
100
|
+
);
|
package/site-build.ts
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
const { Command } = require("commander");
|
|
2
|
+
const { copyFileSync, writeFileSync } = require("fs");
|
|
3
|
+
const path = require("path");
|
|
4
|
+
const { exec, ExecException } = require("child_process");
|
|
5
|
+
|
|
6
|
+
async function main(): Promise<void> {
|
|
7
|
+
// Parse command line arguments.
|
|
8
|
+
const program = new Command();
|
|
9
|
+
program.option("-t, --topics <file>", "The topics file location.");
|
|
10
|
+
program.option("-s, --summary <file>", "The summary file location.");
|
|
11
|
+
program.option("-c, --comments <file>", "The comments file location.");
|
|
12
|
+
program.option("-r, --reportTitle <title>", "The title of the report.");
|
|
13
|
+
program.parse(process.argv);
|
|
14
|
+
const options = program.opts();
|
|
15
|
+
|
|
16
|
+
if(!options["topics"]) {
|
|
17
|
+
throw Error("Topics file path not specified");
|
|
18
|
+
}
|
|
19
|
+
if(!options["summary"]) {
|
|
20
|
+
throw Error("Summary file path not specified");
|
|
21
|
+
}
|
|
22
|
+
if(!options["comments"]) {
|
|
23
|
+
throw Error("Comments file path not specified");
|
|
24
|
+
}
|
|
25
|
+
if(!options["reportTitle"]) {
|
|
26
|
+
throw Error("Report title not specified");
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const reportMetadata = {
|
|
30
|
+
title: options["reportTitle"],
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
// path to "data" folder
|
|
34
|
+
const baseOutputPath = path.join(__dirname, "./data");
|
|
35
|
+
|
|
36
|
+
copyFileSync(options["topics"], path.join(baseOutputPath, "/topic-stats.json"));
|
|
37
|
+
copyFileSync(options["summary"], path.join(baseOutputPath, "/summary.json"));
|
|
38
|
+
copyFileSync(options["comments"], path.join(baseOutputPath, "/comments.json"));
|
|
39
|
+
writeFileSync(path.join(baseOutputPath, "/metadata.json"), JSON.stringify(reportMetadata, null, 2));
|
|
40
|
+
|
|
41
|
+
await exec("npm run build", (error: typeof ExecException | null, stdout: string, stderr: string) => {
|
|
42
|
+
if(error) {
|
|
43
|
+
console.error(`Build failed: ${error.message}`);
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
if(stderr) {
|
|
47
|
+
console.error(`Build errors/warnings: ${stderr}`);
|
|
48
|
+
}
|
|
49
|
+
console.log(`Build output: ${stdout}`);
|
|
50
|
+
console.log("Build complete");
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
main();
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<router-outlet></router-outlet>
|
|
File without changes
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
|
2
|
+
import { AppComponent } from './app.component';
|
|
3
|
+
import { CommonModule } from '@angular/common';
|
|
4
|
+
|
|
5
|
+
describe('AppComponent', () => {
|
|
6
|
+
let component: AppComponent;
|
|
7
|
+
let fixture: ComponentFixture<AppComponent>;
|
|
8
|
+
|
|
9
|
+
beforeEach(async () => {
|
|
10
|
+
await TestBed.configureTestingModule({
|
|
11
|
+
imports: [
|
|
12
|
+
CommonModule,
|
|
13
|
+
AppComponent,
|
|
14
|
+
],
|
|
15
|
+
declarations: [],
|
|
16
|
+
providers: [],
|
|
17
|
+
})
|
|
18
|
+
.overrideComponent(AppComponent, {
|
|
19
|
+
remove: { imports: [] },
|
|
20
|
+
add: { imports: [] },
|
|
21
|
+
})
|
|
22
|
+
.compileComponents();
|
|
23
|
+
|
|
24
|
+
fixture = TestBed.createComponent(AppComponent);
|
|
25
|
+
component = fixture.componentInstance;
|
|
26
|
+
fixture.detectChanges();
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it('should create the app', () => {
|
|
30
|
+
expect(component).toBeTruthy();
|
|
31
|
+
});
|
|
32
|
+
});
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { Component } from '@angular/core';
|
|
2
|
+
import { MatIconRegistry } from "@angular/material/icon";
|
|
3
|
+
import { RouterOutlet } from '@angular/router';
|
|
4
|
+
|
|
5
|
+
@Component({
|
|
6
|
+
selector: 'app-root',
|
|
7
|
+
standalone: true,
|
|
8
|
+
imports: [
|
|
9
|
+
RouterOutlet,
|
|
10
|
+
],
|
|
11
|
+
templateUrl: './app.component.html',
|
|
12
|
+
styleUrl: './app.component.scss',
|
|
13
|
+
})
|
|
14
|
+
export class AppComponent {
|
|
15
|
+
constructor(private matIconRegistry: MatIconRegistry) {
|
|
16
|
+
// set defaults for all material icons:
|
|
17
|
+
// use symbols (instead of icons)
|
|
18
|
+
// use "outlined" variation
|
|
19
|
+
this.matIconRegistry.setDefaultFontSetClass("material-symbols-outlined");
|
|
20
|
+
}
|
|
21
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { mergeApplicationConfig, ApplicationConfig } from '@angular/core';
|
|
2
|
+
import { provideServerRendering } from '@angular/platform-server';
|
|
3
|
+
import { appConfig } from './app.config';
|
|
4
|
+
|
|
5
|
+
const serverConfig: ApplicationConfig = {
|
|
6
|
+
providers: [provideServerRendering()],
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
export const config = mergeApplicationConfig(appConfig, serverConfig);
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { ApplicationConfig, provideZoneChangeDetection } from '@angular/core';
|
|
2
|
+
import { provideRouter } from '@angular/router';
|
|
3
|
+
|
|
4
|
+
import { routes } from './app.routes';
|
|
5
|
+
import { provideClientHydration } from '@angular/platform-browser';
|
|
6
|
+
import { provideAnimations } from '@angular/platform-browser/animations';
|
|
7
|
+
import { provideAnimationsAsync } from '@angular/platform-browser/animations/async';
|
|
8
|
+
import { provideMarkdown } from 'ngx-markdown';
|
|
9
|
+
export const appConfig: ApplicationConfig = {
|
|
10
|
+
providers: [
|
|
11
|
+
provideMarkdown(),
|
|
12
|
+
provideAnimations(),
|
|
13
|
+
provideZoneChangeDetection({ eventCoalescing: true }),
|
|
14
|
+
provideRouter(routes),
|
|
15
|
+
provideClientHydration(),
|
|
16
|
+
provideAnimationsAsync(),
|
|
17
|
+
provideAnimationsAsync(),
|
|
18
|
+
],
|
|
19
|
+
};
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { Routes } from '@angular/router';
|
|
2
|
+
import { ReportComponent } from './pages/report/report.component';
|
|
3
|
+
|
|
4
|
+
export const routes: Routes = [
|
|
5
|
+
// Wildcard route is necessary for building "single HTML file" report.
|
|
6
|
+
// Do not change path to anything else.
|
|
7
|
+
{ path: "**", component: ReportComponent },
|
|
8
|
+
];
|