Next Iteration
This commit is contained in:
87
.gitignore
vendored
87
.gitignore
vendored
@@ -1,4 +1,83 @@
|
||||
out
|
||||
node_modules
|
||||
.vscode-test/
|
||||
*.vsix
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
|
||||
# Runtime data
|
||||
pids
|
||||
*.pid
|
||||
*.seed
|
||||
*.pid.lock
|
||||
|
||||
# Directory for instrumented libs generated by jscoverage/JSCover
|
||||
lib-cov
|
||||
|
||||
# Coverage directory used by tools like istanbul
|
||||
coverage
|
||||
|
||||
# nyc test coverage
|
||||
.nyc_output
|
||||
|
||||
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
|
||||
.grunt
|
||||
|
||||
# Bower dependency directory (https://bower.io/)
|
||||
bower_components
|
||||
|
||||
# node-waf configuration
|
||||
.lock-wscript
|
||||
|
||||
# Compiled binary addons (https://nodejs.org/api/addons.html)
|
||||
build/Release
|
||||
|
||||
# Dependency directories
|
||||
node_modules/
|
||||
jspm_packages/
|
||||
|
||||
# TypeScript v1 declaration files
|
||||
typings/
|
||||
|
||||
# Optional npm cache directory
|
||||
.npm
|
||||
|
||||
# Optional eslint cache
|
||||
.eslintcache
|
||||
|
||||
# Optional REPL history
|
||||
.node_repl_history
|
||||
|
||||
# Output of 'npm pack'
|
||||
*.tgz
|
||||
|
||||
# Yarn Integrity file
|
||||
.yarn-integrity
|
||||
|
||||
# dotenv environment variables file
|
||||
.env
|
||||
.env.test
|
||||
|
||||
# parcel-bundler cache (https://parceljs.org/)
|
||||
.cache
|
||||
|
||||
# next.js build output
|
||||
.next
|
||||
|
||||
# nuxt.js build output
|
||||
.nuxt
|
||||
|
||||
# vuepress build output
|
||||
.vuepress/dist
|
||||
|
||||
# Serverless directories
|
||||
.serverless/
|
||||
|
||||
# FuseBox cache
|
||||
.fusebox/
|
||||
|
||||
# DynamoDB Local files
|
||||
.dynamodb/
|
||||
|
||||
dist/
|
||||
out/
|
||||
6
.prettierrc.json
Normal file
6
.prettierrc.json
Normal file
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"trailingComma": "es5",
|
||||
"tabWidth": 4,
|
||||
"semi": true,
|
||||
"useTabs": true
|
||||
}
|
||||
7
.vscode/extensions.json
vendored
7
.vscode/extensions.json
vendored
@@ -1,7 +0,0 @@
|
||||
{
|
||||
// See http://go.microsoft.com/fwlink/?LinkId=827846
|
||||
// for the documentation about the extensions.json format
|
||||
"recommendations": [
|
||||
"eg2.tslint"
|
||||
]
|
||||
}
|
||||
47
.vscode/launch.json
vendored
47
.vscode/launch.json
vendored
@@ -1,36 +1,17 @@
|
||||
// A launch configuration that compiles the extension and then opens it inside a new window
|
||||
// Use IntelliSense to learn about possible attributes.
|
||||
// Hover to view descriptions of existing attributes.
|
||||
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
||||
{
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"name": "Extension",
|
||||
"type": "extensionHost",
|
||||
"request": "launch",
|
||||
"runtimeExecutable": "${execPath}",
|
||||
"args": [
|
||||
"--extensionDevelopmentPath=${workspaceFolder}"
|
||||
],
|
||||
"outFiles": [
|
||||
"${workspaceFolder}/out/**/*.js"
|
||||
],
|
||||
"preLaunchTask": "npm: watch"
|
||||
},
|
||||
{
|
||||
"name": "Extension Tests",
|
||||
"type": "extensionHost",
|
||||
"request": "launch",
|
||||
"runtimeExecutable": "${execPath}",
|
||||
"args": [
|
||||
"--extensionDevelopmentPath=${workspaceFolder}",
|
||||
"--extensionTestsPath=${workspaceFolder}/out/test"
|
||||
],
|
||||
"outFiles": [
|
||||
"${workspaceFolder}/out/test/**/*.js"
|
||||
],
|
||||
"preLaunchTask": "npm: watch"
|
||||
}
|
||||
]
|
||||
"configurations": [
|
||||
{
|
||||
"name": "Run Extension",
|
||||
"type": "extensionHost",
|
||||
"request": "launch",
|
||||
"runtimeExecutable": "${execPath}",
|
||||
"args": [
|
||||
"--extensionDevelopmentPath=${workspaceFolder}/extension",
|
||||
"S:\\dev\\vscode\\vscode-debug-visualizer2\\example-project"
|
||||
],
|
||||
"outFiles": ["${workspaceFolder}/extension/out/**/*.js"],
|
||||
"preLaunchTask": "npm: dev - extension"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
17
.vscode/settings.json
vendored
17
.vscode/settings.json
vendored
@@ -1,11 +1,8 @@
|
||||
// Place your settings in this file to overwrite default and user settings.
|
||||
{
|
||||
"files.exclude": {
|
||||
"out": false // set this to true to hide the "out" folder with the compiled JS files
|
||||
},
|
||||
"search.exclude": {
|
||||
"out": true // set this to false to include "out" folder in search results
|
||||
},
|
||||
// Turn off tsc task auto detection since we have the necessary tasks as npm scripts
|
||||
"typescript.tsc.autoDetect": "off"
|
||||
}
|
||||
"search.exclude": {
|
||||
"extension/out": true
|
||||
},
|
||||
"plantuml.diagramsRoot": "docs",
|
||||
"plantuml.exportFormat": "png",
|
||||
"plantuml.exportOutDir": "docs/exported"
|
||||
}
|
||||
|
||||
38
.vscode/tasks.json
vendored
38
.vscode/tasks.json
vendored
@@ -1,20 +1,20 @@
|
||||
// See https://go.microsoft.com/fwlink/?LinkId=733558
|
||||
// for the documentation about the tasks.json format
|
||||
{
|
||||
"version": "2.0.0",
|
||||
"tasks": [
|
||||
{
|
||||
"type": "npm",
|
||||
"script": "watch",
|
||||
"problemMatcher": "$tsc-watch",
|
||||
"isBackground": true,
|
||||
"presentation": {
|
||||
"reveal": "never"
|
||||
},
|
||||
"group": {
|
||||
"kind": "build",
|
||||
"isDefault": true
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
"version": "2.0.0",
|
||||
"tasks": [
|
||||
{
|
||||
"type": "npm",
|
||||
"label": "npm: dev - extension",
|
||||
"script": "dev",
|
||||
"problemMatcher": "$tsc-watch",
|
||||
"isBackground": true,
|
||||
"presentation": {
|
||||
"reveal": "never"
|
||||
},
|
||||
"path": "extension/",
|
||||
"group": {
|
||||
"kind": "build",
|
||||
"isDefault": true
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
20
data-extraction/package.json
Normal file
20
data-extraction/package.json
Normal file
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"name": "@hediet/debug-visualizer-data-extraction",
|
||||
"private": true,
|
||||
"version": "0.1.0",
|
||||
"main": "dist/index.js",
|
||||
"types": "dist/index.d.ts",
|
||||
"author": {
|
||||
"name": "Henning Dieterichs",
|
||||
"email": "henning.dieterichs@live.de"
|
||||
},
|
||||
"license": "MIT",
|
||||
"files": [
|
||||
"dist",
|
||||
"src"
|
||||
],
|
||||
"scripts": {
|
||||
"dev": "tsc --watch"
|
||||
},
|
||||
"dependencies": {}
|
||||
}
|
||||
46
data-extraction/src/CommonDataTypes.ts
Normal file
46
data-extraction/src/CommonDataTypes.ts
Normal file
@@ -0,0 +1,46 @@
|
||||
import { ExtractedData } from "./DataExtractionResult";
|
||||
|
||||
export module CommonDataTypes {
|
||||
export interface Text extends ExtractedData {
|
||||
kind: { text: true };
|
||||
text: string;
|
||||
// what should be used here? File type (.ts, .txt) or mime type?
|
||||
fileType?: string;
|
||||
}
|
||||
|
||||
export interface Svg extends Text {
|
||||
kind: { text: true; svg: true };
|
||||
text: string;
|
||||
}
|
||||
|
||||
export interface Html extends Text {
|
||||
kind: { text: true; html: true };
|
||||
text: string;
|
||||
}
|
||||
|
||||
export interface DotGraph extends Text {
|
||||
kind: { text: true; dotGraph: true };
|
||||
text: string;
|
||||
}
|
||||
|
||||
export interface TreeNodeData extends ExtractedData {
|
||||
kind: { tree: true };
|
||||
root: TreeNode<{}>;
|
||||
}
|
||||
|
||||
export interface AstData extends TreeNodeData, Text {
|
||||
kind: { text: true; tree: true; ast: true };
|
||||
root: TreeNode<{
|
||||
position: number;
|
||||
length: number;
|
||||
}>;
|
||||
}
|
||||
}
|
||||
|
||||
export interface TreeNode<TExtraData> {
|
||||
id: string | undefined;
|
||||
name: string;
|
||||
children: TreeNode<TExtraData>[];
|
||||
data: TExtraData;
|
||||
isSelected: boolean;
|
||||
}
|
||||
20
data-extraction/src/DataExtractionResult.ts
Normal file
20
data-extraction/src/DataExtractionResult.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
export type DataExtractionResult = {
|
||||
data: ExtractedData;
|
||||
usedExtractor: DataExtractorInfo;
|
||||
availableExtractors: DataExtractorInfo[];
|
||||
};
|
||||
|
||||
// is json
|
||||
export type ExtractedData = {
|
||||
kind: Record<string, true>;
|
||||
};
|
||||
|
||||
export type DataExtractorInfo = {
|
||||
id: DataExtractorId;
|
||||
name: string;
|
||||
priority: number;
|
||||
};
|
||||
|
||||
export type DataExtractorId = {
|
||||
__brand: "DataExtractorId";
|
||||
};
|
||||
3
data-extraction/src/index.ts
Normal file
3
data-extraction/src/index.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
export * from "./js";
|
||||
export * from "./CommonDataTypes";
|
||||
export * from "./DataExtractionResult";
|
||||
21
data-extraction/src/js/DataExtractor.ts
Normal file
21
data-extraction/src/js/DataExtractor.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
import { ExtractedData } from "../DataExtractionResult";
|
||||
|
||||
export interface DataExtractor<T extends ExtractedData> {
|
||||
id: string;
|
||||
getExtractions(
|
||||
data: any,
|
||||
extractionCollector: ExtractionCollector<T>
|
||||
): void;
|
||||
}
|
||||
|
||||
export interface ExtractionCollector<T extends ExtractedData> {
|
||||
addExtraction(extraction: DataExtraction<T>): void;
|
||||
}
|
||||
|
||||
export interface DataExtraction<TData extends ExtractedData> {
|
||||
// the higher the better
|
||||
priority: number;
|
||||
id: string;
|
||||
name: string;
|
||||
extractData(): TData;
|
||||
}
|
||||
144
data-extraction/src/js/DataExtractorApi.ts
Normal file
144
data-extraction/src/js/DataExtractorApi.ts
Normal file
@@ -0,0 +1,144 @@
|
||||
import {
|
||||
ExtractedData,
|
||||
DataExtractionResult,
|
||||
DataExtractorInfo,
|
||||
} from "../DataExtractionResult";
|
||||
import {
|
||||
DataExtractor,
|
||||
DataExtraction,
|
||||
ExtractionCollector,
|
||||
} from "./DataExtractor";
|
||||
|
||||
export type DataResult =
|
||||
| {
|
||||
kind: "Data";
|
||||
extractionResult: DataExtractionResult;
|
||||
}
|
||||
| { kind: "NoExtractors" };
|
||||
|
||||
export interface JSONString<T> extends String {
|
||||
__brand: { json: T };
|
||||
}
|
||||
|
||||
export interface DataExtractorApi {
|
||||
registerExtractor<TExtractedData extends ExtractedData>(
|
||||
extractor: DataExtractor<TExtractedData>
|
||||
): void;
|
||||
getData(
|
||||
value: unknown,
|
||||
preferredDataExtractorId: string | undefined
|
||||
): JSONString<DataResult>;
|
||||
}
|
||||
|
||||
declare const window: any;
|
||||
|
||||
export function selfContainedInitDataExtractorApi(): boolean {
|
||||
const obj = typeof window === "object" ? (window as any) : (global as any);
|
||||
const key = "@hediet/data-extractor";
|
||||
const prefix = key + "::";
|
||||
|
||||
let api: DataExtractorApi | undefined = obj[key];
|
||||
if (api) {
|
||||
return false;
|
||||
}
|
||||
|
||||
function toJson<TData>(data: TData): JSONString<TData> {
|
||||
return JSON.stringify(data) as any;
|
||||
}
|
||||
function getExtractors(): DataExtractor<ExtractedData>[] {
|
||||
return Object.entries(obj)
|
||||
.filter(([key, value]) => key.startsWith(prefix))
|
||||
.map(([key, value]) => value as DataExtractor<ExtractedData>);
|
||||
}
|
||||
obj[key] = api = {
|
||||
registerExtractor(extractor) {
|
||||
obj[prefix + extractor.id] = extractor;
|
||||
},
|
||||
getData(value, preferredDataExtractorId) {
|
||||
const extractions = new Array<DataExtraction<ExtractedData>>();
|
||||
const collector: ExtractionCollector<ExtractedData> = {
|
||||
addExtraction(extraction) {
|
||||
extractions.push(extraction);
|
||||
},
|
||||
};
|
||||
for (const e of getExtractors()) {
|
||||
e.getExtractions(value, collector);
|
||||
}
|
||||
extractions.sort((a, b) => b.priority - a.priority);
|
||||
let usedExtraction = extractions[0];
|
||||
if (!usedExtraction) {
|
||||
return toJson({ kind: "NoExtractors" } as DataResult);
|
||||
}
|
||||
|
||||
if (preferredDataExtractorId) {
|
||||
const preferred = extractions.find(
|
||||
e => e.id === preferredDataExtractorId
|
||||
);
|
||||
if (preferred) {
|
||||
usedExtraction = preferred;
|
||||
}
|
||||
}
|
||||
|
||||
function mapExtractor(
|
||||
e: DataExtraction<ExtractedData>
|
||||
): DataExtractorInfo {
|
||||
return {
|
||||
id: e.id as any,
|
||||
name: e.name,
|
||||
priority: e.priority,
|
||||
};
|
||||
}
|
||||
|
||||
const data = usedExtraction.extractData();
|
||||
return toJson({
|
||||
kind: "Data",
|
||||
extractionResult: {
|
||||
data,
|
||||
usedExtractor: mapExtractor(usedExtraction),
|
||||
availableExtractors: extractions.map(mapExtractor),
|
||||
},
|
||||
} as DataResult);
|
||||
},
|
||||
};
|
||||
|
||||
api.registerExtractor({
|
||||
id: "toString",
|
||||
getExtractions(data, collector) {
|
||||
collector.addExtraction({
|
||||
id: "toString",
|
||||
name: "To String",
|
||||
priority: 100,
|
||||
extractData() {
|
||||
return {
|
||||
kind: {
|
||||
text: true,
|
||||
},
|
||||
text: data.toString(),
|
||||
};
|
||||
},
|
||||
});
|
||||
},
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
export const ApiHasNotBeenInitializedCode = "EgH0cybXij1jYUozyakO" as const;
|
||||
|
||||
export function selfContainedGetInitializedDataExtractorApi(): DataExtractorApi {
|
||||
const obj = typeof window === "object" ? (window as any) : (global as any);
|
||||
const key = "@hediet/data-extractor";
|
||||
let api: DataExtractorApi | undefined = obj[key];
|
||||
if (!api) {
|
||||
const code: typeof ApiHasNotBeenInitializedCode =
|
||||
"EgH0cybXij1jYUozyakO";
|
||||
throw new Error(
|
||||
`Data Extractor API has not been initialized. Code: ${code}`
|
||||
);
|
||||
}
|
||||
return api;
|
||||
}
|
||||
|
||||
export function getDataExtractorApi(): DataExtractorApi {
|
||||
selfContainedInitDataExtractorApi();
|
||||
return selfContainedGetInitializedDataExtractorApi();
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
import * as ts from "typescript";
|
||||
import { DataExtractor, ExtractionCollector } from "../DataExtractor";
|
||||
import { CommonDataTypes } from "../../CommonDataTypes";
|
||||
|
||||
export class TypeScriptAstDataExtractor
|
||||
implements DataExtractor<CommonDataTypes.AstData> {
|
||||
readonly id = "TypeScriptAst";
|
||||
|
||||
getExtractions(
|
||||
data: any,
|
||||
collector: ExtractionCollector<CommonDataTypes.AstData>
|
||||
): void {
|
||||
if (typeof data !== "object" || data === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
const tsApi = require("typescript") as typeof ts | undefined;
|
||||
if (!tsApi) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ((tsApi as any).isNode(data)) {
|
||||
const n = data as ts.Node;
|
||||
collector.addExtraction({
|
||||
id: "ts-ast",
|
||||
name: "TypeScript AST",
|
||||
priority: 1000,
|
||||
extractData() {
|
||||
return {
|
||||
kind: { text: true, tree: true, ast: true },
|
||||
root: toTreeNode(data, "root"),
|
||||
text: n.getSourceFile().text,
|
||||
fileType: "ts",
|
||||
};
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function findKey(value: any, object: any): string | null {
|
||||
for (var key in object) {
|
||||
if (key.startsWith("_")) continue;
|
||||
|
||||
var member = object[key];
|
||||
if (member === value) return key;
|
||||
|
||||
if (Array.isArray(member) && member.indexOf(value) !== -1) return key;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function toTreeNode(
|
||||
node: ts.Node,
|
||||
memberName: string
|
||||
): CommonDataTypes.AstData["root"] {
|
||||
const name = ts.SyntaxKind[node.kind];
|
||||
const children = node
|
||||
.getChildren()
|
||||
.map((childNode, idx) => {
|
||||
let parentPropertyName = findKey(childNode, node) || "";
|
||||
|
||||
if (childNode.kind == ts.SyntaxKind.SyntaxList) {
|
||||
childNode.getChildren().some(c => {
|
||||
parentPropertyName = findKey(c, node) || "";
|
||||
return !!parentPropertyName;
|
||||
});
|
||||
|
||||
if (childNode.getChildren().length === 0) return null!;
|
||||
}
|
||||
|
||||
if (node.kind == ts.SyntaxKind.SyntaxList) {
|
||||
parentPropertyName = "" + idx;
|
||||
}
|
||||
|
||||
return toTreeNode(childNode, parentPropertyName);
|
||||
})
|
||||
.filter(c => c !== null);
|
||||
|
||||
return {
|
||||
name: name,
|
||||
id: memberName,
|
||||
children: children,
|
||||
data: {
|
||||
length: 0,
|
||||
position: 0,
|
||||
},
|
||||
isSelected: false,
|
||||
// startPos: node.pos,
|
||||
// endPos: node.end
|
||||
};
|
||||
}
|
||||
3
data-extraction/src/js/index.ts
Normal file
3
data-extraction/src/js/index.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
export * from "./Extractors/TypeScriptDataExtractors";
|
||||
export * from "./DataExtractor";
|
||||
export * from "./DataExtractorApi";
|
||||
16
data-extraction/tsconfig.json
Normal file
16
data-extraction/tsconfig.json
Normal file
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "esnext",
|
||||
"module": "commonjs",
|
||||
"strict": true,
|
||||
"outDir": "dist",
|
||||
"skipLibCheck": true,
|
||||
"rootDir": "./src",
|
||||
"resolveJsonModule": true,
|
||||
"declaration": true,
|
||||
"declarationMap": true,
|
||||
"newLine": "LF",
|
||||
"sourceMap": true
|
||||
},
|
||||
"include": ["./src/**/*"]
|
||||
}
|
||||
16
example-project/.vscode/launch.json
vendored
Normal file
16
example-project/.vscode/launch.json
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
{
|
||||
// Use IntelliSense to learn about possible attributes.
|
||||
// Hover to view descriptions of existing attributes.
|
||||
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"type": "node",
|
||||
"request": "launch",
|
||||
"name": "Launch Program",
|
||||
"program": "${workspaceFolder}\\src\\index.ts",
|
||||
"outFiles": ["${workspaceFolder}/dist/**/*.js"],
|
||||
"preLaunchTask": "npm: dev"
|
||||
}
|
||||
]
|
||||
}
|
||||
19
example-project/.vscode/tasks.json
vendored
Normal file
19
example-project/.vscode/tasks.json
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"version": "2.0.0",
|
||||
"tasks": [
|
||||
{
|
||||
"type": "npm",
|
||||
"label": "npm: dev",
|
||||
"script": "dev",
|
||||
"problemMatcher": "$tsc-watch",
|
||||
"isBackground": true,
|
||||
"presentation": {
|
||||
"reveal": "never"
|
||||
},
|
||||
"group": {
|
||||
"kind": "build",
|
||||
"isDefault": true
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
13
example-project/package.json
Normal file
13
example-project/package.json
Normal file
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"name": "demo",
|
||||
"version": "1.0.0",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@hediet/std": "^0.4.0",
|
||||
"@hediet/node-reload": "0.7.0",
|
||||
"typescript": "^3.5.3"
|
||||
},
|
||||
"scripts": {
|
||||
"dev": "tsc --watch"
|
||||
}
|
||||
}
|
||||
45
example-project/src/index.ts
Normal file
45
example-project/src/index.ts
Normal file
@@ -0,0 +1,45 @@
|
||||
import {
|
||||
enableHotReload,
|
||||
registerUpdateReconciler,
|
||||
getReloadCount,
|
||||
hotClass,
|
||||
} from "@hediet/node-reload";
|
||||
import {
|
||||
getDataExtractorApi,
|
||||
TypeScriptAstDataExtractor,
|
||||
CommonDataTypes,
|
||||
} from "@hediet/debug-visualizer-data-extraction";
|
||||
enableHotReload();
|
||||
import * as ts from "typescript";
|
||||
|
||||
registerUpdateReconciler(module);
|
||||
|
||||
getDataExtractorApi().registerExtractor(new TypeScriptAstDataExtractor());
|
||||
|
||||
@hotClass(module)
|
||||
class Main {
|
||||
run() {
|
||||
const sf = ts.createSourceFile(
|
||||
"test",
|
||||
`
|
||||
class Test {
|
||||
public test() {
|
||||
console.log("aaa");
|
||||
const x = 4 + 1;
|
||||
}
|
||||
}
|
||||
`,
|
||||
ts.ScriptTarget.Latest,
|
||||
true
|
||||
);
|
||||
|
||||
console.log(sf);
|
||||
//new TypeScriptAstDataExtractor().getExtractions(sf, );
|
||||
|
||||
debugger;
|
||||
}
|
||||
}
|
||||
|
||||
if (getReloadCount(module) === 0) {
|
||||
new Main().run();
|
||||
}
|
||||
17
example-project/tsconfig.json
Normal file
17
example-project/tsconfig.json
Normal file
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "esnext",
|
||||
"module": "commonjs",
|
||||
"strict": true,
|
||||
"outDir": "dist",
|
||||
"skipLibCheck": true,
|
||||
"rootDir": "./src",
|
||||
"resolveJsonModule": true,
|
||||
"declaration": true,
|
||||
"declarationMap": true,
|
||||
"newLine": "LF",
|
||||
"sourceMap": true,
|
||||
"experimentalDecorators": true
|
||||
},
|
||||
"include": ["src/**/*"]
|
||||
}
|
||||
436
example-project/yarn.lock
Normal file
436
example-project/yarn.lock
Normal file
@@ -0,0 +1,436 @@
|
||||
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
|
||||
# yarn lockfile v1
|
||||
|
||||
|
||||
"@hediet/live-debug@^0.5.1":
|
||||
version "0.5.2"
|
||||
resolved "https://registry.yarnpkg.com/@hediet/live-debug/-/live-debug-0.5.2.tgz#f2ee0034f014836bfd68e26c35a168ab42b15486"
|
||||
integrity sha512-HBqVbyFez1oQlIPO9dzkFi7/9Gvt6ZpssgURzYaVFREl9Oq03nPd6V7Ay4bW03xFHPo3YT2Yff9mFmGQUX7JEg==
|
||||
dependencies:
|
||||
"@hediet/std" "^0.6.0"
|
||||
"@hediet/typed-json-rpc" "^0.7.6"
|
||||
"@hediet/typed-json-rpc-websocket" "^0.7.7"
|
||||
stacktracey "^1.2.112"
|
||||
ws "^7.0.0"
|
||||
|
||||
"@hediet/node-reload@0.7.0":
|
||||
version "0.7.0"
|
||||
resolved "https://registry.yarnpkg.com/@hediet/node-reload/-/node-reload-0.7.0.tgz#ec50160ce26203079f18e7024cd58025fa79652a"
|
||||
integrity sha512-gWFbq2tOThSV9GT+pFx1JJzFuOV7l40IsseOFHMJGyvLmmAdgbJPV3OZuV9gcsXIGocOtbfEyky9S4tQELLLxA==
|
||||
dependencies:
|
||||
"@hediet/live-debug" "^0.5.1"
|
||||
"@hediet/std" "^0.6.0"
|
||||
"@hediet/typed-json-rpc" "^0.7.6"
|
||||
"@types/node" "^12.0.7"
|
||||
"@types/stack-trace" "^0.0.29"
|
||||
chokidar "^3.0.1"
|
||||
stack-trace "^0.0.10"
|
||||
ws "^7.0.0"
|
||||
|
||||
"@hediet/std@^0.4.0":
|
||||
version "0.4.0"
|
||||
resolved "https://registry.yarnpkg.com/@hediet/std/-/std-0.4.0.tgz#95dfd6e9d62f8bf9d91f3925d37b7b359b2af047"
|
||||
integrity sha512-2kw4vSOprLchTjhcg8qQo6fdbBxJicSUrzZXgL0KzgMv18hjVmpm57NPLCPfI6115kAaMIWvMnlHFLYbx7poQQ==
|
||||
dependencies:
|
||||
"@types/chai" "^4.1.7"
|
||||
"@types/sinon" "^7.0.10"
|
||||
chai "^4.2.0"
|
||||
sinon "^7.2.7"
|
||||
|
||||
"@hediet/std@^0.6.0":
|
||||
version "0.6.0"
|
||||
resolved "https://registry.yarnpkg.com/@hediet/std/-/std-0.6.0.tgz#f7d2ffc1180ecef6ff272d0d6780f0c0d9337e4e"
|
||||
integrity sha512-XzBiZo6pnt01k266lSbXOBZd7jrQQSutoH3IpI/1GuL09zxuDX+c0XersEbMCGS45S2l/TSlISyV3MH1lbYJEg==
|
||||
dependencies:
|
||||
"@types/chai" "^4.1.7"
|
||||
"@types/sinon" "^7.0.10"
|
||||
chai "^4.2.0"
|
||||
mobx "^5.10.0"
|
||||
sinon "^7.2.7"
|
||||
|
||||
"@hediet/typed-json-rpc-websocket@^0.7.7":
|
||||
version "0.7.7"
|
||||
resolved "https://registry.yarnpkg.com/@hediet/typed-json-rpc-websocket/-/typed-json-rpc-websocket-0.7.7.tgz#b1555528226e2bf3202d1d0e1870d04b59c14048"
|
||||
integrity sha512-0Aakr7ktqjR9paPRHM0fhePZDA8kKWn2t2Al3ta9nXSsY4ec/HknEwR9NMhXD3lATa/wvvKVIymalzKuRoV82A==
|
||||
dependencies:
|
||||
"@hediet/typed-json-rpc" "^0.7.5"
|
||||
"@types/ws" "^6.0.1"
|
||||
isomorphic-ws "^4.0.1"
|
||||
|
||||
"@hediet/typed-json-rpc@^0.7.5", "@hediet/typed-json-rpc@^0.7.6":
|
||||
version "0.7.7"
|
||||
resolved "https://registry.yarnpkg.com/@hediet/typed-json-rpc/-/typed-json-rpc-0.7.7.tgz#bd7e586ad5c0480f7bd6bc40d6ad202e0a80b54d"
|
||||
integrity sha512-lyjbVqt+mFrRS0+2cOVDithtfG9gfRV5CJMVDwV+F2gfCUjTe1aZu4xObpDDLfxfoZOGgtPNNg6jCNhvXSxQHg==
|
||||
dependencies:
|
||||
"@hediet/std" "^0.6.0"
|
||||
io-ts "^1.8.6"
|
||||
|
||||
"@sinonjs/commons@^1", "@sinonjs/commons@^1.0.2", "@sinonjs/commons@^1.4.0":
|
||||
version "1.4.0"
|
||||
resolved "https://registry.yarnpkg.com/@sinonjs/commons/-/commons-1.4.0.tgz#7b3ec2d96af481d7a0321252e7b1c94724ec5a78"
|
||||
integrity sha512-9jHK3YF/8HtJ9wCAbG+j8cD0i0+ATS9A7gXFqS36TblLPNy6rEEc+SB0imo91eCboGaBYGV/MT1/br/J+EE7Tw==
|
||||
dependencies:
|
||||
type-detect "4.0.8"
|
||||
|
||||
"@sinonjs/formatio@^3.1.0", "@sinonjs/formatio@^3.2.1":
|
||||
version "3.2.1"
|
||||
resolved "https://registry.yarnpkg.com/@sinonjs/formatio/-/formatio-3.2.1.tgz#52310f2f9bcbc67bdac18c94ad4901b95fde267e"
|
||||
integrity sha512-tsHvOB24rvyvV2+zKMmPkZ7dXX6LSLKZ7aOtXY6Edklp0uRcgGpOsQTTGTcWViFyx4uhWc6GV8QdnALbIbIdeQ==
|
||||
dependencies:
|
||||
"@sinonjs/commons" "^1"
|
||||
"@sinonjs/samsam" "^3.1.0"
|
||||
|
||||
"@sinonjs/samsam@^3.1.0", "@sinonjs/samsam@^3.3.1":
|
||||
version "3.3.2"
|
||||
resolved "https://registry.yarnpkg.com/@sinonjs/samsam/-/samsam-3.3.2.tgz#63942e3d5eb0b79f6de3bef9abfad15fb4b6401b"
|
||||
integrity sha512-ILO/rR8LfAb60Y1Yfp9vxfYAASK43NFC2mLzpvLUbCQY/Qu8YwReboseu8aheCEkyElZF2L2T9mHcR2bgdvZyA==
|
||||
dependencies:
|
||||
"@sinonjs/commons" "^1.0.2"
|
||||
array-from "^2.1.1"
|
||||
lodash "^4.17.11"
|
||||
|
||||
"@sinonjs/text-encoding@^0.7.1":
|
||||
version "0.7.1"
|
||||
resolved "https://registry.yarnpkg.com/@sinonjs/text-encoding/-/text-encoding-0.7.1.tgz#8da5c6530915653f3a1f38fd5f101d8c3f8079c5"
|
||||
integrity sha512-+iTbntw2IZPb/anVDbypzfQa+ay64MW0Zo8aJ8gZPWMMK6/OubMVb6lUPMagqjOPnmtauXnFCACVl3O7ogjeqQ==
|
||||
|
||||
"@types/chai@^4.1.7":
|
||||
version "4.1.7"
|
||||
resolved "https://registry.yarnpkg.com/@types/chai/-/chai-4.1.7.tgz#1b8e33b61a8c09cbe1f85133071baa0dbf9fa71a"
|
||||
integrity sha512-2Y8uPt0/jwjhQ6EiluT0XCri1Dbplr0ZxfFXUz+ye13gaqE8u5gL5ppao1JrUYr9cIip5S6MvQzBS7Kke7U9VA==
|
||||
|
||||
"@types/events@*":
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@types/events/-/events-3.0.0.tgz#2862f3f58a9a7f7c3e78d79f130dd4d71c25c2a7"
|
||||
integrity sha512-EaObqwIvayI5a8dCzhFrjKzVwKLxjoG9T6Ppd5CEo07LRKfQ8Yokw54r5+Wq7FaBQ+yXRvQAYPrHwya1/UFt9g==
|
||||
|
||||
"@types/node@*", "@types/node@^12.0.7":
|
||||
version "12.6.8"
|
||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-12.6.8.tgz#e469b4bf9d1c9832aee4907ba8a051494357c12c"
|
||||
integrity sha512-aX+gFgA5GHcDi89KG5keey2zf0WfZk/HAQotEamsK2kbey+8yGKcson0hbK8E+v0NArlCJQCqMP161YhV6ZXLg==
|
||||
|
||||
"@types/node@^8.0.7":
|
||||
version "8.10.51"
|
||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-8.10.51.tgz#80600857c0a47a8e8bafc2dae6daed6db58e3627"
|
||||
integrity sha512-cArrlJp3Yv6IyFT/DYe+rlO8o3SIHraALbBW/+CcCYW/a9QucpLI+n2p4sRxAvl2O35TiecpX2heSZtJjvEO+Q==
|
||||
|
||||
"@types/sinon@^7.0.10":
|
||||
version "7.0.13"
|
||||
resolved "https://registry.yarnpkg.com/@types/sinon/-/sinon-7.0.13.tgz#ca039c23a9e27ebea53e0901ef928ea2a1a6d313"
|
||||
integrity sha512-d7c/C/+H/knZ3L8/cxhicHUiTDxdgap0b/aNJfsmLwFu/iOP17mdgbQsbHA3SJmrzsjD0l3UEE5SN4xxuz5ung==
|
||||
|
||||
"@types/stack-trace@^0.0.29":
|
||||
version "0.0.29"
|
||||
resolved "https://registry.yarnpkg.com/@types/stack-trace/-/stack-trace-0.0.29.tgz#eb7a7c60098edb35630ed900742a5ecb20cfcb4d"
|
||||
integrity sha512-TgfOX+mGY/NyNxJLIbDWrO9DjGoVSW9+aB8H2yy1fy32jsvxijhmyJI9fDFgvz3YP4lvJaq9DzdR/M1bOgVc9g==
|
||||
|
||||
"@types/ws@^6.0.1":
|
||||
version "6.0.1"
|
||||
resolved "https://registry.yarnpkg.com/@types/ws/-/ws-6.0.1.tgz#ca7a3f3756aa12f62a0a62145ed14c6db25d5a28"
|
||||
integrity sha512-EzH8k1gyZ4xih/MaZTXwT2xOkPiIMSrhQ9b8wrlX88L0T02eYsddatQlwVFlEPyEqV0ChpdpNnE51QPH6NVT4Q==
|
||||
dependencies:
|
||||
"@types/events" "*"
|
||||
"@types/node" "*"
|
||||
|
||||
anymatch@^3.0.1:
|
||||
version "3.0.3"
|
||||
resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.0.3.tgz#2fb624fe0e84bccab00afee3d0006ed310f22f09"
|
||||
integrity sha512-c6IvoeBECQlMVuYUjSwimnhmztImpErfxJzWZhIQinIvQWoGOnB0dLIgifbPHQt5heS6mNlaZG16f06H3C8t1g==
|
||||
dependencies:
|
||||
normalize-path "^3.0.0"
|
||||
picomatch "^2.0.4"
|
||||
|
||||
array-from@^2.1.1:
|
||||
version "2.1.1"
|
||||
resolved "https://registry.yarnpkg.com/array-from/-/array-from-2.1.1.tgz#cfe9d8c26628b9dc5aecc62a9f5d8f1f352c1195"
|
||||
integrity sha1-z+nYwmYoudxa7MYqn12PHzUsEZU=
|
||||
|
||||
as-table@^1.0.36:
|
||||
version "1.0.52"
|
||||
resolved "https://registry.yarnpkg.com/as-table/-/as-table-1.0.52.tgz#537fb0cfcc6d04b498c8922df5f75ed357ec91e6"
|
||||
integrity sha512-Lw3OjVL9BDGSrU6RoK5aTm79SnrDY0Zvucxc+oOTqZrC2Sw575IhkOTPtvpzviliL/WJaCSWzJdKFdYeI+8wFg==
|
||||
dependencies:
|
||||
printable-characters "^1.0.42"
|
||||
|
||||
assertion-error@^1.1.0:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/assertion-error/-/assertion-error-1.1.0.tgz#e60b6b0e8f301bd97e5375215bda406c85118c0b"
|
||||
integrity sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==
|
||||
|
||||
async-limiter@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/async-limiter/-/async-limiter-1.0.0.tgz#78faed8c3d074ab81f22b4e985d79e8738f720f8"
|
||||
integrity sha512-jp/uFnooOiO+L211eZOoSyzpOITMXx1rBITauYykG3BRYPu8h0UcxsPNB04RR5vo4Tyz3+ay17tR6JVf9qzYWg==
|
||||
|
||||
binary-extensions@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.0.0.tgz#23c0df14f6a88077f5f986c0d167ec03c3d5537c"
|
||||
integrity sha512-Phlt0plgpIIBOGTT/ehfFnbNlfsDEiqmzE2KRXoX1bLIlir4X/MR+zSyBEkL05ffWgnRSf/DXv+WrUAVr93/ow==
|
||||
|
||||
braces@^3.0.2:
|
||||
version "3.0.2"
|
||||
resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107"
|
||||
integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==
|
||||
dependencies:
|
||||
fill-range "^7.0.1"
|
||||
|
||||
chai@^4.2.0:
|
||||
version "4.2.0"
|
||||
resolved "https://registry.yarnpkg.com/chai/-/chai-4.2.0.tgz#760aa72cf20e3795e84b12877ce0e83737aa29e5"
|
||||
integrity sha512-XQU3bhBukrOsQCuwZndwGcCVQHyZi53fQ6Ys1Fym7E4olpIqqZZhhoFJoaKVvV17lWQoXYwgWN2nF5crA8J2jw==
|
||||
dependencies:
|
||||
assertion-error "^1.1.0"
|
||||
check-error "^1.0.2"
|
||||
deep-eql "^3.0.1"
|
||||
get-func-name "^2.0.0"
|
||||
pathval "^1.1.0"
|
||||
type-detect "^4.0.5"
|
||||
|
||||
check-error@^1.0.2:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/check-error/-/check-error-1.0.2.tgz#574d312edd88bb5dd8912e9286dd6c0aed4aac82"
|
||||
integrity sha1-V00xLt2Iu13YkS6Sht1sCu1KrII=
|
||||
|
||||
chokidar@^3.0.1:
|
||||
version "3.0.2"
|
||||
resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.0.2.tgz#0d1cd6d04eb2df0327446188cd13736a3367d681"
|
||||
integrity sha512-c4PR2egjNjI1um6bamCQ6bUNPDiyofNQruHvKgHQ4gDUP/ITSVSzNsiI5OWtHOsX323i5ha/kk4YmOZ1Ktg7KA==
|
||||
dependencies:
|
||||
anymatch "^3.0.1"
|
||||
braces "^3.0.2"
|
||||
glob-parent "^5.0.0"
|
||||
is-binary-path "^2.1.0"
|
||||
is-glob "^4.0.1"
|
||||
normalize-path "^3.0.0"
|
||||
readdirp "^3.1.1"
|
||||
optionalDependencies:
|
||||
fsevents "^2.0.6"
|
||||
|
||||
data-uri-to-buffer@^2.0.0:
|
||||
version "2.0.1"
|
||||
resolved "https://registry.yarnpkg.com/data-uri-to-buffer/-/data-uri-to-buffer-2.0.1.tgz#ca8f56fe38b1fd329473e9d1b4a9afcd8ce1c045"
|
||||
integrity sha512-OkVVLrerfAKZlW2ZZ3Ve2y65jgiWqBKsTfUIAFbn8nVbPcCZg6l6gikKlEYv0kXcmzqGm6mFq/Jf2vriuEkv8A==
|
||||
dependencies:
|
||||
"@types/node" "^8.0.7"
|
||||
|
||||
deep-eql@^3.0.1:
|
||||
version "3.0.1"
|
||||
resolved "https://registry.yarnpkg.com/deep-eql/-/deep-eql-3.0.1.tgz#dfc9404400ad1c8fe023e7da1df1c147c4b444df"
|
||||
integrity sha512-+QeIQyN5ZuO+3Uk5DYh6/1eKO0m0YmJFGNmFHGACpf1ClL1nmlV/p4gNgbl2pJGxgXb4faqo6UE+M5ACEMyVcw==
|
||||
dependencies:
|
||||
type-detect "^4.0.0"
|
||||
|
||||
diff@^3.5.0:
|
||||
version "3.5.0"
|
||||
resolved "https://registry.yarnpkg.com/diff/-/diff-3.5.0.tgz#800c0dd1e0a8bfbc95835c202ad220fe317e5a12"
|
||||
integrity sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==
|
||||
|
||||
fill-range@^7.0.1:
|
||||
version "7.0.1"
|
||||
resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40"
|
||||
integrity sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==
|
||||
dependencies:
|
||||
to-regex-range "^5.0.1"
|
||||
|
||||
fp-ts@^1.0.0:
|
||||
version "1.19.5"
|
||||
resolved "https://registry.yarnpkg.com/fp-ts/-/fp-ts-1.19.5.tgz#3da865e585dfa1fdfd51785417357ac50afc520a"
|
||||
integrity sha512-wDNqTimnzs8QqpldiId9OavWK2NptormjXnRJTQecNjzwfyp6P/8s/zG8e4h3ja3oqkKaY72UlTjQYt/1yXf9A==
|
||||
|
||||
fsevents@^2.0.6:
|
||||
version "2.0.7"
|
||||
resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.0.7.tgz#382c9b443c6cbac4c57187cdda23aa3bf1ccfc2a"
|
||||
integrity sha512-a7YT0SV3RB+DjYcppwVDLtn13UQnmg0SWZS7ezZD0UjnLwXmy8Zm21GMVGLaFGimIqcvyMQaOJBrop8MyOp1kQ==
|
||||
|
||||
get-func-name@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/get-func-name/-/get-func-name-2.0.0.tgz#ead774abee72e20409433a066366023dd6887a41"
|
||||
integrity sha1-6td0q+5y4gQJQzoGY2YCPdaIekE=
|
||||
|
||||
get-source@^1.0.34:
|
||||
version "1.0.37"
|
||||
resolved "https://registry.yarnpkg.com/get-source/-/get-source-1.0.37.tgz#f1f3f799abd2a196de73ba8b853c38cd76c04583"
|
||||
integrity sha512-Q0YbhnCgvqFe3OTuxkNvWCM+22O4hkx7D0g8em7iFW4vmw/TiX8dhsFIaP6yRkyN9ETHgzU190L3keCg2MADtg==
|
||||
dependencies:
|
||||
data-uri-to-buffer "^2.0.0"
|
||||
source-map "^0.6.1"
|
||||
|
||||
glob-parent@^5.0.0:
|
||||
version "5.0.0"
|
||||
resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.0.0.tgz#1dc99f0f39b006d3e92c2c284068382f0c20e954"
|
||||
integrity sha512-Z2RwiujPRGluePM6j699ktJYxmPpJKCfpGA13jz2hmFZC7gKetzrWvg5KN3+OsIFmydGyZ1AVwERCq1w/ZZwRg==
|
||||
dependencies:
|
||||
is-glob "^4.0.1"
|
||||
|
||||
has-flag@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd"
|
||||
integrity sha1-tdRU3CGZriJWmfNGfloH87lVuv0=
|
||||
|
||||
io-ts@^1.8.6:
|
||||
version "1.10.4"
|
||||
resolved "https://registry.yarnpkg.com/io-ts/-/io-ts-1.10.4.tgz#cd5401b138de88e4f920adbcb7026e2d1967e6e2"
|
||||
integrity sha512-b23PteSnYXSONJ6JQXRAlvJhuw8KOtkqa87W4wDtvMrud/DTJd5X+NpOOI+O/zZwVq6v0VLAaJ+1EDViKEuN9g==
|
||||
dependencies:
|
||||
fp-ts "^1.0.0"
|
||||
|
||||
is-binary-path@^2.1.0:
|
||||
version "2.1.0"
|
||||
resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-2.1.0.tgz#ea1f7f3b80f064236e83470f86c09c254fb45b09"
|
||||
integrity sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==
|
||||
dependencies:
|
||||
binary-extensions "^2.0.0"
|
||||
|
||||
is-extglob@^2.1.1:
|
||||
version "2.1.1"
|
||||
resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2"
|
||||
integrity sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=
|
||||
|
||||
is-glob@^4.0.1:
|
||||
version "4.0.1"
|
||||
resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.1.tgz#7567dbe9f2f5e2467bc77ab83c4a29482407a5dc"
|
||||
integrity sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==
|
||||
dependencies:
|
||||
is-extglob "^2.1.1"
|
||||
|
||||
is-number@^7.0.0:
|
||||
version "7.0.0"
|
||||
resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b"
|
||||
integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==
|
||||
|
||||
isarray@0.0.1:
|
||||
version "0.0.1"
|
||||
resolved "https://registry.yarnpkg.com/isarray/-/isarray-0.0.1.tgz#8a18acfca9a8f4177e09abfc6038939b05d1eedf"
|
||||
integrity sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=
|
||||
|
||||
isomorphic-ws@^4.0.1:
|
||||
version "4.0.1"
|
||||
resolved "https://registry.yarnpkg.com/isomorphic-ws/-/isomorphic-ws-4.0.1.tgz#55fd4cd6c5e6491e76dc125938dd863f5cd4f2dc"
|
||||
integrity sha512-BhBvN2MBpWTaSHdWRb/bwdZJ1WaehQ2L1KngkCkfLUGF0mAWAT1sQUQacEmQ0jXkFw/czDXPNQSL5u2/Krsz1w==
|
||||
|
||||
just-extend@^4.0.2:
|
||||
version "4.0.2"
|
||||
resolved "https://registry.yarnpkg.com/just-extend/-/just-extend-4.0.2.tgz#f3f47f7dfca0f989c55410a7ebc8854b07108afc"
|
||||
integrity sha512-FrLwOgm+iXrPV+5zDU6Jqu4gCRXbWEQg2O3SKONsWE4w7AXFRkryS53bpWdaL9cNol+AmR3AEYz6kn+o0fCPnw==
|
||||
|
||||
lodash@^4.17.11:
|
||||
version "4.17.15"
|
||||
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.15.tgz#b447f6670a0455bbfeedd11392eff330ea097548"
|
||||
integrity sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==
|
||||
|
||||
lolex@^4.0.1, lolex@^4.1.0:
|
||||
version "4.1.0"
|
||||
resolved "https://registry.yarnpkg.com/lolex/-/lolex-4.1.0.tgz#ecdd7b86539391d8237947a3419aa8ac975f0fe1"
|
||||
integrity sha512-BYxIEXiVq5lGIXeVHnsFzqa1TxN5acnKnPCdlZSpzm8viNEOhiigupA4vTQ9HEFQ6nLTQ9wQOgBknJgzUYQ9Aw==
|
||||
|
||||
mobx@^5.10.0:
|
||||
version "5.13.0"
|
||||
resolved "https://registry.yarnpkg.com/mobx/-/mobx-5.13.0.tgz#0fd68f10aa5ff2d146a4ed9e145b53337cfbca59"
|
||||
integrity sha512-eSAntMSMNj0PFL705rgv+aB/z1RjNqDnFEpBe18yQVreXTWiVgIrmBUXzjnJfuba+eo4eAk6zi+/gXQkSUea8A==
|
||||
|
||||
nise@^1.4.10:
|
||||
version "1.5.0"
|
||||
resolved "https://registry.yarnpkg.com/nise/-/nise-1.5.0.tgz#d03ea0e6c1b75c638015aa3585eddc132949a50d"
|
||||
integrity sha512-Z3sfYEkLFzFmL8KY6xnSJLRxwQwYBjOXi/24lb62ZnZiGA0JUzGGTI6TBIgfCSMIDl9Jlu8SRmHNACLTemDHww==
|
||||
dependencies:
|
||||
"@sinonjs/formatio" "^3.1.0"
|
||||
"@sinonjs/text-encoding" "^0.7.1"
|
||||
just-extend "^4.0.2"
|
||||
lolex "^4.1.0"
|
||||
path-to-regexp "^1.7.0"
|
||||
|
||||
normalize-path@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65"
|
||||
integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==
|
||||
|
||||
path-to-regexp@^1.7.0:
|
||||
version "1.7.0"
|
||||
resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-1.7.0.tgz#59fde0f435badacba103a84e9d3bc64e96b9937d"
|
||||
integrity sha1-Wf3g9DW62suhA6hOnTvGTpa5k30=
|
||||
dependencies:
|
||||
isarray "0.0.1"
|
||||
|
||||
pathval@^1.1.0:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/pathval/-/pathval-1.1.0.tgz#b942e6d4bde653005ef6b71361def8727d0645e0"
|
||||
integrity sha1-uULm1L3mUwBe9rcTYd74cn0GReA=
|
||||
|
||||
picomatch@^2.0.4:
|
||||
version "2.0.7"
|
||||
resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.0.7.tgz#514169d8c7cd0bdbeecc8a2609e34a7163de69f6"
|
||||
integrity sha512-oLHIdio3tZ0qH76NybpeneBhYVj0QFTfXEFTc/B3zKQspYfYYkWYgFsmzo+4kvId/bQRcNkVeguI3y+CD22BtA==
|
||||
|
||||
printable-characters@^1.0.42:
|
||||
version "1.0.42"
|
||||
resolved "https://registry.yarnpkg.com/printable-characters/-/printable-characters-1.0.42.tgz#3f18e977a9bd8eb37fcc4ff5659d7be90868b3d8"
|
||||
integrity sha1-Pxjpd6m9jrN/zE/1ZZ176Qhos9g=
|
||||
|
||||
readdirp@^3.1.1:
|
||||
version "3.1.1"
|
||||
resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.1.1.tgz#b158123ac343c8b0f31d65680269cc0fc1025db1"
|
||||
integrity sha512-XXdSXZrQuvqoETj50+JAitxz1UPdt5dupjT6T5nVB+WvjMv2XKYj+s7hPeAVCXvmJrL36O4YYyWlIC3an2ePiQ==
|
||||
dependencies:
|
||||
picomatch "^2.0.4"
|
||||
|
||||
sinon@^7.2.7:
|
||||
version "7.3.2"
|
||||
resolved "https://registry.yarnpkg.com/sinon/-/sinon-7.3.2.tgz#82dba3a6d85f6d2181e1eca2c10d8657c2161f28"
|
||||
integrity sha512-thErC1z64BeyGiPvF8aoSg0LEnptSaWE7YhdWWbWXgelOyThent7uKOnnEh9zBxDbKixtr5dEko+ws1sZMuFMA==
|
||||
dependencies:
|
||||
"@sinonjs/commons" "^1.4.0"
|
||||
"@sinonjs/formatio" "^3.2.1"
|
||||
"@sinonjs/samsam" "^3.3.1"
|
||||
diff "^3.5.0"
|
||||
lolex "^4.0.1"
|
||||
nise "^1.4.10"
|
||||
supports-color "^5.5.0"
|
||||
|
||||
source-map@^0.6.1:
|
||||
version "0.6.1"
|
||||
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263"
|
||||
integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==
|
||||
|
||||
stack-trace@^0.0.10:
|
||||
version "0.0.10"
|
||||
resolved "https://registry.yarnpkg.com/stack-trace/-/stack-trace-0.0.10.tgz#547c70b347e8d32b4e108ea1a2a159e5fdde19c0"
|
||||
integrity sha1-VHxws0fo0ytOEI6hoqFZ5f3eGcA=
|
||||
|
||||
stacktracey@^1.2.112:
|
||||
version "1.2.117"
|
||||
resolved "https://registry.yarnpkg.com/stacktracey/-/stacktracey-1.2.117.tgz#7249d41937c37af29011173131d6b9aa62e6ec05"
|
||||
integrity sha512-sj9olGCco8jxMw3fcmhL5er1yuVL2qSMhNfycwx+iIWz6uVpG+bQ2YNpVSlWscGmSskt61gSmUBjZ0Mb5jYBLw==
|
||||
dependencies:
|
||||
as-table "^1.0.36"
|
||||
get-source "^1.0.34"
|
||||
|
||||
supports-color@^5.5.0:
|
||||
version "5.5.0"
|
||||
resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f"
|
||||
integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==
|
||||
dependencies:
|
||||
has-flag "^3.0.0"
|
||||
|
||||
to-regex-range@^5.0.1:
|
||||
version "5.0.1"
|
||||
resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4"
|
||||
integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==
|
||||
dependencies:
|
||||
is-number "^7.0.0"
|
||||
|
||||
type-detect@4.0.8, type-detect@^4.0.0, type-detect@^4.0.5:
|
||||
version "4.0.8"
|
||||
resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-4.0.8.tgz#7646fb5f18871cfbb7749e69bd39a6388eb7450c"
|
||||
integrity sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==
|
||||
|
||||
typescript@^3.5.3:
|
||||
version "3.5.3"
|
||||
resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.5.3.tgz#c830f657f93f1ea846819e929092f5fe5983e977"
|
||||
integrity sha512-ACzBtm/PhXBDId6a6sDJfroT2pOWt/oOnk4/dElG5G33ZL776N3Y6/6bKZJBFpd+b05F3Ct9qDjMeJmRWtE2/g==
|
||||
|
||||
ws@^7.0.0:
|
||||
version "7.1.1"
|
||||
resolved "https://registry.yarnpkg.com/ws/-/ws-7.1.1.tgz#f9942dc868b6dffb72c14fd8f2ba05f77a4d5983"
|
||||
integrity sha512-o41D/WmDeca0BqYhsr3nJzQyg9NF5X8l/UdnFNux9cS3lwB+swm8qGWX5rn+aD6xfBU3rGmtHij7g7x6LxFU3A==
|
||||
dependencies:
|
||||
async-limiter "^1.0.0"
|
||||
@@ -1,30 +0,0 @@
|
||||
|
||||
const matrix = [
|
||||
[1, 2, 3],
|
||||
[4, 5, 6],
|
||||
[7, 8, 9],
|
||||
];
|
||||
|
||||
let str = "test1";
|
||||
for (let i = 0; i < 100000; i++) {
|
||||
str += i;
|
||||
}
|
||||
|
||||
function randInt(max) {
|
||||
return Math.floor(Math.random() * max);
|
||||
}
|
||||
|
||||
const nodes = [];
|
||||
for (let i = 0; i < 10; i++) {
|
||||
nodes.push(i);
|
||||
}
|
||||
|
||||
const edges = [];
|
||||
for (let j = 0; j < 15; j++) {
|
||||
edges.push([ randInt(nodes.length - 1), randInt(nodes.length - 1) ]);
|
||||
}
|
||||
|
||||
console.log(matrix, str);
|
||||
|
||||
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
.vscode/**
|
||||
.vscode-test/**
|
||||
out/test/**
|
||||
out/**/*.map
|
||||
src/**
|
||||
.gitignore
|
||||
tsconfig.json
|
||||
vsc-extension-quickstart.md
|
||||
tslint.json
|
||||
**/tsconfig.json
|
||||
**/tslint.json
|
||||
**/*.map
|
||||
**/*.ts
|
||||
57
extension/package.json
Normal file
57
extension/package.json
Normal file
@@ -0,0 +1,57 @@
|
||||
{
|
||||
"name": "vscode-debug-visualizer",
|
||||
"displayName": "VS Code Debug Visualizer",
|
||||
"description": "",
|
||||
"icon": "docs/logo.png",
|
||||
"version": "0.1.0",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"vscode": "^1.35.0"
|
||||
},
|
||||
"publisher": "hediet",
|
||||
"author": {
|
||||
"email": "henning.dieterichs@live.de",
|
||||
"name": "Henning Dieterichs"
|
||||
},
|
||||
"readme": "./README.md",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/hediet/vscode-rpc-server.git"
|
||||
},
|
||||
"categories": [
|
||||
"Other"
|
||||
],
|
||||
"activationEvents": [
|
||||
"*"
|
||||
],
|
||||
"main": "./out/extension.js",
|
||||
"contributes": {
|
||||
"commands": [],
|
||||
"configuration": {}
|
||||
},
|
||||
"scripts": {
|
||||
"vscode:prepublish": "yarn run build",
|
||||
"build": "tsc -p ./",
|
||||
"dev": "tsc -watch -p ./"
|
||||
},
|
||||
"dependencies": {
|
||||
"@hediet/std": "^0.6.0",
|
||||
"@hediet/typed-json-rpc": "^0.7.7",
|
||||
"@hediet/typed-json-rpc-websocket-server": "^0.7.7",
|
||||
"@types/express": "^4.17.0",
|
||||
"@types/serve-static": "^1.13.2",
|
||||
"express": "^4.17.1",
|
||||
"open": "^6.4.0",
|
||||
"serve-static": "^1.14.1",
|
||||
"ws": "^7.1.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@hediet/node-reload": "^0.7.2",
|
||||
"@types/node": "^12.6.8",
|
||||
"@types/vscode": "1.36.0",
|
||||
"tslint": "^5.18.0",
|
||||
"typescript": "^3.5.3",
|
||||
"webpack": "^4.38.0",
|
||||
"webpack-cli": "^3.3.6"
|
||||
}
|
||||
}
|
||||
22
extension/src/DataSource/DataSource.ts
Normal file
22
extension/src/DataSource/DataSource.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
import { DataExtractorId } from "@hediet/debug-visualizer-data-extraction";
|
||||
import { DataExtractionState } from "@hediet/debug-visualizer-vscode-shared";
|
||||
|
||||
export interface DataSource {
|
||||
createEvaluationWatcher(expression: string): EvaluationWatcher;
|
||||
}
|
||||
|
||||
export interface EvaluationWatcher {
|
||||
readonly expression: string;
|
||||
|
||||
// is observable
|
||||
readonly preferredDataExtractor: DataExtractorId | undefined;
|
||||
|
||||
setPreferredDataExtractor(id: DataExtractorId): void;
|
||||
|
||||
refresh(): void;
|
||||
|
||||
// is observable
|
||||
state: DataExtractionState;
|
||||
|
||||
dispose(): void;
|
||||
}
|
||||
157
extension/src/DataSource/JsDebuggerSource.ts
Normal file
157
extension/src/DataSource/JsDebuggerSource.ts
Normal file
@@ -0,0 +1,157 @@
|
||||
import { DataSource, EvaluationWatcher } from "./DataSource";
|
||||
import { observable } from "mobx";
|
||||
import * as vscode from "vscode";
|
||||
import { Disposable } from "@hediet/std/disposable";
|
||||
import {
|
||||
selfContainedGetInitializedDataExtractorApi,
|
||||
DataResult,
|
||||
DataExtractorId,
|
||||
ApiHasNotBeenInitializedCode,
|
||||
selfContainedInitDataExtractorApi,
|
||||
} from "@hediet/debug-visualizer-data-extraction";
|
||||
import { DataExtractionState } from "@hediet/debug-visualizer-vscode-shared";
|
||||
|
||||
export function createJsDebuggerSource(): JsDataSource {
|
||||
return new JsDebuggerSourceImplementation();
|
||||
}
|
||||
|
||||
export interface JsCode extends String {
|
||||
__brand: "JsCode";
|
||||
}
|
||||
|
||||
export interface JsDataSource extends DataSource {
|
||||
registerDataExtractor(classExpression: JsCode): void;
|
||||
}
|
||||
|
||||
class JsDebuggerSourceImplementation implements JsDataSource {
|
||||
private lastFrameId: number | null = null;
|
||||
private readonly watchers = new Set<ObservableEvaluationWatcher>();
|
||||
|
||||
public readonly dispose = Disposable.fn();
|
||||
|
||||
constructor() {
|
||||
this.dispose.track(
|
||||
vscode.debug.onDidReceiveDebugSessionCustomEvent(e => {
|
||||
if (e.event === "paused") {
|
||||
const frameId = e.body.currentFrameId;
|
||||
this.lastFrameId = frameId;
|
||||
|
||||
for (const w of this.watchers) {
|
||||
w.refresh();
|
||||
}
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
registerDataExtractor(classExpression: JsCode): void {}
|
||||
|
||||
public createEvaluationWatcher(expression: string): EvaluationWatcher {
|
||||
const w = new ObservableEvaluationWatcher(expression, this);
|
||||
this.watchers.add(w);
|
||||
this.refresh(w);
|
||||
return w;
|
||||
}
|
||||
|
||||
public removeWatcher(w: ObservableEvaluationWatcher) {
|
||||
this.watchers.delete(w);
|
||||
}
|
||||
|
||||
public async refresh(w: ObservableEvaluationWatcher): Promise<void> {
|
||||
const session = vscode.debug.activeDebugSession;
|
||||
if (!session) {
|
||||
w._state = { kind: "noDebugSession" };
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
w._state = { kind: "loading" };
|
||||
const expression = `(${selfContainedGetInitializedDataExtractorApi.toString()})().getData(${
|
||||
w.expression
|
||||
})`;
|
||||
|
||||
const reply = await session.customRequest("evaluate", {
|
||||
expression,
|
||||
frameId: this.lastFrameId,
|
||||
context: "watch",
|
||||
});
|
||||
const resultStr = reply.result as string;
|
||||
const jsonData = resultStr.substr(1, resultStr.length - 2);
|
||||
const result = JSON.parse(jsonData) as DataResult;
|
||||
if (result.kind === "NoExtractors") {
|
||||
throw new Error("No extractors");
|
||||
}
|
||||
|
||||
w._state = {
|
||||
kind: "data",
|
||||
result: result.extractionResult,
|
||||
};
|
||||
} catch (error) {
|
||||
const msg = error.message as string | undefined;
|
||||
if (msg && msg.includes(ApiHasNotBeenInitializedCode)) {
|
||||
if (await this.initializeApi(session)) {
|
||||
await this.refresh(w);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
w._state = {
|
||||
kind: "error",
|
||||
message: error.message,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
private async initializeApi(
|
||||
session: vscode.DebugSession
|
||||
): Promise<boolean> {
|
||||
try {
|
||||
const expression = `(${selfContainedInitDataExtractorApi.toString()})()`;
|
||||
|
||||
const reply = await session.customRequest("evaluate", {
|
||||
expression,
|
||||
frameId: this.lastFrameId,
|
||||
context: "watch",
|
||||
});
|
||||
if (reply.result === "true") {
|
||||
// register extractors
|
||||
}
|
||||
return true;
|
||||
} catch (error) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class ObservableEvaluationWatcher implements EvaluationWatcher {
|
||||
constructor(
|
||||
public readonly expression: string,
|
||||
private readonly source: JsDebuggerSourceImplementation
|
||||
) {}
|
||||
|
||||
@observable
|
||||
_preferredDataExtractor: DataExtractorId | undefined = undefined;
|
||||
|
||||
get preferredDataExtractor(): DataExtractorId | undefined {
|
||||
return this._preferredDataExtractor;
|
||||
}
|
||||
|
||||
public setPreferredDataExtractor(id: DataExtractorId): void {
|
||||
this._preferredDataExtractor = id;
|
||||
this.refresh();
|
||||
}
|
||||
|
||||
public refresh(): void {
|
||||
this.source.refresh(this);
|
||||
}
|
||||
|
||||
@observable
|
||||
public _state: DataExtractionState = { kind: "loading" };
|
||||
public get state(): DataExtractionState {
|
||||
return this._state;
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
this.source.removeWatcher(this);
|
||||
}
|
||||
}
|
||||
224
extension/src/extension.ts
Normal file
224
extension/src/extension.ts
Normal file
@@ -0,0 +1,224 @@
|
||||
import {
|
||||
window,
|
||||
OutputChannel,
|
||||
ExtensionContext,
|
||||
StatusBarAlignment,
|
||||
commands,
|
||||
workspace,
|
||||
ViewColumn,
|
||||
WebviewPanel,
|
||||
} from "vscode";
|
||||
import {
|
||||
enableHotReload,
|
||||
hotRequireExportedFn,
|
||||
registerUpdateReconciler,
|
||||
getReloadCount,
|
||||
} from "@hediet/node-reload";
|
||||
enableHotReload({ entryModule: module });
|
||||
import { Disposable } from "@hediet/std/disposable";
|
||||
|
||||
import { createJsDebuggerSource } from "./DataSource/JsDebuggerSource";
|
||||
import { debugVisualizerUIContract } from "@hediet/debug-visualizer-vscode-shared";
|
||||
|
||||
import { ConsoleRpcLogger } from "@hediet/typed-json-rpc";
|
||||
import { EvaluationWatcher } from "./DataSource/DataSource";
|
||||
import { WebSocketStream } from "@hediet/typed-json-rpc-websocket";
|
||||
import { observable, autorun } from "mobx";
|
||||
import * as express from "express";
|
||||
import * as http from "http";
|
||||
import * as serveStatic from "serve-static";
|
||||
import { AddressInfo } from "net";
|
||||
import WebSocket = require("ws");
|
||||
import { join } from "path";
|
||||
|
||||
registerUpdateReconciler(module);
|
||||
|
||||
class WebView {
|
||||
private expression: string | null = null;
|
||||
private lastFrameId: number | null = null;
|
||||
|
||||
constructor(private readonly webviewPanel: WebviewPanel) {}
|
||||
}
|
||||
|
||||
const debugVisualizer = "debugVisualizer";
|
||||
|
||||
function getHtml(server: Server) {
|
||||
const isDev = true;
|
||||
return `
|
||||
<html>
|
||||
<head>
|
||||
<style>
|
||||
html { height: 100%; width: 100%; padding: 0; margin: 0; }
|
||||
body { height: 100%; width: 100%; padding: 0; margin: 0; }
|
||||
iframe { height: 100%; width: 100%; padding: 0; margin: 0; border: 0; display: block; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<script>
|
||||
window.serverPort = ${server.port};
|
||||
</script>
|
||||
${
|
||||
isDev
|
||||
? `<iframe src="http://localhost:8080/?serverPort=${
|
||||
server.port
|
||||
}" tabindex="0"></iframe>`
|
||||
: `<script type="text/javascript" src="${
|
||||
server.mainBundle
|
||||
}"></script>`
|
||||
}
|
||||
</body>
|
||||
</html>
|
||||
`;
|
||||
}
|
||||
|
||||
class WebViews {
|
||||
private readonly debugVisualizations = new Map<WebviewPanel, WebView>();
|
||||
|
||||
constructor(private readonly server: Server) {}
|
||||
|
||||
public createNew() {
|
||||
const panel = window.createWebviewPanel(
|
||||
debugVisualizer,
|
||||
"Debug Visualizer",
|
||||
ViewColumn.Two,
|
||||
{ enableScripts: true }
|
||||
);
|
||||
|
||||
this.setupView(panel);
|
||||
}
|
||||
|
||||
public restore(webviewPanel: WebviewPanel) {
|
||||
this.setupView(webviewPanel);
|
||||
}
|
||||
|
||||
private setupView(webviewPanel: WebviewPanel) {
|
||||
webviewPanel.webview.html = getHtml(this.server);
|
||||
const view = new WebView(webviewPanel);
|
||||
this.debugVisualizations.set(webviewPanel, view);
|
||||
webviewPanel.onDidDispose(() => {
|
||||
this.debugVisualizations.delete(webviewPanel);
|
||||
});
|
||||
}
|
||||
|
||||
public dispose() {
|
||||
for (const panel of this.debugVisualizations.keys()) {
|
||||
panel.dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// js-debug value-printer value-viewer
|
||||
// #src: bla
|
||||
// editor-src typescript-ast-viewer
|
||||
|
||||
import * as open from "open";
|
||||
|
||||
class ConnectionHandler {
|
||||
public readonly dispose = Disposable.fn();
|
||||
@observable
|
||||
private watcher: EvaluationWatcher | undefined = undefined;
|
||||
|
||||
constructor(sources: Sources, stream: WebSocketStream, server: Server) {
|
||||
const {
|
||||
client,
|
||||
channel,
|
||||
} = debugVisualizerUIContract.registerServerToStream(
|
||||
stream,
|
||||
new ConsoleRpcLogger(),
|
||||
{
|
||||
refresh: async () => {
|
||||
if (this.watcher) {
|
||||
this.watcher.refresh();
|
||||
}
|
||||
},
|
||||
setExpression: async ({ newExpression }) => {
|
||||
if (this.watcher) {
|
||||
this.dispose.untrack(this.watcher).dispose();
|
||||
}
|
||||
this.watcher = this.dispose.track(
|
||||
sources.jsSource.createEvaluationWatcher(newExpression)
|
||||
);
|
||||
},
|
||||
openInBrowser: async ({}) => {
|
||||
open(server.indexUrl);
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
this.dispose.track(
|
||||
Disposable.create(
|
||||
autorun(() => {
|
||||
if (this.watcher) {
|
||||
client.updateState({
|
||||
newState: this.watcher.state,
|
||||
});
|
||||
}
|
||||
})
|
||||
)
|
||||
);
|
||||
|
||||
stream.onClosed.then(() => {
|
||||
this.dispose();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
class Server {
|
||||
private server: http.Server;
|
||||
|
||||
constructor(sources: Sources) {
|
||||
const app = express();
|
||||
app.use(serveStatic(join(__dirname, "../../ui/dist")));
|
||||
|
||||
this.server = app.listen();
|
||||
|
||||
const wss = new WebSocket.Server({ server: this.server });
|
||||
wss.on("connection", ws => {
|
||||
const stream = new WebSocketStream(ws);
|
||||
new ConnectionHandler(sources, stream, this);
|
||||
});
|
||||
console.log(this.port);
|
||||
}
|
||||
|
||||
public get indexUrl(): string {
|
||||
return `http://localhost:${this.port}/index.html?serverPort=${
|
||||
this.port
|
||||
}`;
|
||||
}
|
||||
|
||||
public get mainBundle(): string {
|
||||
return `http://localhost:${this.port}/main-efe6bdc4b62fdc7971ee.js`;
|
||||
}
|
||||
|
||||
public get port(): number {
|
||||
const httpPort = (this.server.address() as AddressInfo).port;
|
||||
return httpPort;
|
||||
}
|
||||
}
|
||||
|
||||
export class Sources {
|
||||
public readonly jsSource = createJsDebuggerSource();
|
||||
}
|
||||
|
||||
export class Extension {
|
||||
public dispose = Disposable.fn();
|
||||
|
||||
private readonly sources = new Sources();
|
||||
private readonly server = new Server(this.sources);
|
||||
private readonly views = this.dispose.track(new WebViews(this.server));
|
||||
|
||||
constructor() {
|
||||
const i = this.dispose.track(window.createStatusBarItem());
|
||||
i.text = "reload" + getReloadCount(module);
|
||||
i.show();
|
||||
this.views.createNew();
|
||||
}
|
||||
}
|
||||
|
||||
export function activate(context: ExtensionContext) {
|
||||
context.subscriptions.push(
|
||||
hotRequireExportedFn(module, Extension, Extension => new Extension())
|
||||
);
|
||||
}
|
||||
|
||||
export function deactivate() {}
|
||||
17
extension/tsconfig.json
Normal file
17
extension/tsconfig.json
Normal file
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"module": "commonjs",
|
||||
"target": "es6",
|
||||
"outDir": "out",
|
||||
"lib": ["es6"],
|
||||
"sourceMap": true,
|
||||
"rootDir": "src",
|
||||
"strict": true,
|
||||
"skipLibCheck": true,
|
||||
"experimentalDecorators": true
|
||||
},
|
||||
"include": [
|
||||
"src/**/*",
|
||||
"../js-data-extractors/src/Extractors/TypeScriptDataExtractors.ts"
|
||||
]
|
||||
}
|
||||
2
extension/webpack.config.js
Normal file
2
extension/webpack.config.js
Normal file
@@ -0,0 +1,2 @@
|
||||
require("ts-node").register();
|
||||
module.exports = require("./webpack.config.ts");
|
||||
14
extension/webpack.config.ts
Normal file
14
extension/webpack.config.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
import * as webpack from "webpack";
|
||||
import path = require("path");
|
||||
import HtmlWebpackPlugin = require("html-webpack-plugin");
|
||||
import MonacoWebpackPlugin = require("monaco-editor-webpack-plugin");
|
||||
import ForkTsCheckerWebpackPlugin = require("fork-ts-checker-webpack-plugin");
|
||||
|
||||
const r = (file: string) => path.resolve(__dirname, file);
|
||||
|
||||
module.exports = {
|
||||
target: "node",
|
||||
entry: r("./src/extension"),
|
||||
} as webpack.Configuration;
|
||||
|
||||
// todo
|
||||
3784
extension/yarn.lock
Normal file
3784
extension/yarn.lock
Normal file
File diff suppressed because it is too large
Load Diff
16
notes.md
16
notes.md
@@ -1,16 +0,0 @@
|
||||
|
||||
* Plugins
|
||||
* Hot reloadable, i.e. webframes
|
||||
|
||||
* Visualization of Source and Debug Expressions
|
||||
* Debug Expression: `dbg("foo.bar")`
|
||||
* Source: `src()`
|
||||
|
||||
* Multiple Visualizations
|
||||
* Visualization in new Windows
|
||||
* Mouse Curser Visualization
|
||||
|
||||
* Global/Local Visualizers
|
||||
* Visualization Reduction
|
||||
|
||||
* Visualization History
|
||||
45
package.json
45
package.json
@@ -1,40 +1,9 @@
|
||||
{
|
||||
"name": "debug-visualizer",
|
||||
"displayName": "debug-visualizer",
|
||||
"description": "",
|
||||
"version": "0.0.1",
|
||||
"publisher": "hediet",
|
||||
"engines": {
|
||||
"vscode": "^1.30.0"
|
||||
},
|
||||
"categories": [
|
||||
"Other"
|
||||
],
|
||||
"activationEvents": [
|
||||
"onCommand:extension.sayHello",
|
||||
"onWebviewPanel:debugVisualizer"
|
||||
],
|
||||
"main": "./out/extension",
|
||||
"contributes": {
|
||||
"commands": [
|
||||
{
|
||||
"command": "extension.sayHello",
|
||||
"title": "Hello World"
|
||||
}
|
||||
]
|
||||
},
|
||||
"scripts": {
|
||||
"vscode:prepublish": "npm run compile",
|
||||
"compile": "tsc -p ./",
|
||||
"watch": "tsc -watch -p ./",
|
||||
"postinstall": "node ./node_modules/vscode/bin/install",
|
||||
"test": "npm run compile && node ./node_modules/vscode/bin/test"
|
||||
},
|
||||
"devDependencies": {
|
||||
"typescript": "^2.6.1",
|
||||
"vscode": "^1.1.6",
|
||||
"tslint": "^5.8.0",
|
||||
"@types/node": "^7.0.43",
|
||||
"@types/mocha": "^2.2.42"
|
||||
}
|
||||
"private": true,
|
||||
"workspaces": [
|
||||
"./shared",
|
||||
"./data-extraction",
|
||||
"./extension",
|
||||
"./ui"
|
||||
]
|
||||
}
|
||||
|
||||
23
shared/package.json
Normal file
23
shared/package.json
Normal file
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"name": "@hediet/debug-visualizer-vscode-shared",
|
||||
"private": true,
|
||||
"version": "1.0.0",
|
||||
"main": "dist/index.js",
|
||||
"types": "dist/index.d.ts",
|
||||
"author": {
|
||||
"name": "Henning Dieterichs",
|
||||
"email": "henning.dieterichs@live.de"
|
||||
},
|
||||
"license": "MIT",
|
||||
"files": [
|
||||
"dist",
|
||||
"src"
|
||||
],
|
||||
"scripts": {
|
||||
"dev": "tsc --watch"
|
||||
},
|
||||
"dependencies": {
|
||||
"@hediet/std": "^0.6.0",
|
||||
"@hediet/typed-json-rpc": "^0.7.7"
|
||||
}
|
||||
}
|
||||
39
shared/src/contract.ts
Normal file
39
shared/src/contract.ts
Normal file
@@ -0,0 +1,39 @@
|
||||
import { contract, notificationContract, types } from "@hediet/typed-json-rpc";
|
||||
import { DataExtractionResult } from "@hediet/debug-visualizer-data-extraction";
|
||||
|
||||
function unchecked<T>(): types.Type<T, T, unknown> {
|
||||
return new types.Type<T, T, unknown>(
|
||||
"unchecked",
|
||||
(u): u is T => true,
|
||||
value => types.success(value as T),
|
||||
value => value
|
||||
);
|
||||
}
|
||||
|
||||
export type DataExtractionState =
|
||||
| { kind: "loading" }
|
||||
| { kind: "error"; message: string }
|
||||
| { kind: "noDebugSession" }
|
||||
| {
|
||||
kind: "data";
|
||||
result: DataExtractionResult;
|
||||
};
|
||||
|
||||
export const debugVisualizerUIContract = contract({
|
||||
client: {
|
||||
updateState: notificationContract({
|
||||
params: types.type({
|
||||
newState: unchecked<DataExtractionState>(),
|
||||
}),
|
||||
}),
|
||||
},
|
||||
server: {
|
||||
setExpression: notificationContract({
|
||||
params: types.type({
|
||||
newExpression: types.string,
|
||||
}),
|
||||
}),
|
||||
refresh: notificationContract({}),
|
||||
openInBrowser: notificationContract({}),
|
||||
},
|
||||
});
|
||||
1
shared/src/index.ts
Normal file
1
shared/src/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from "./contract";
|
||||
16
shared/tsconfig.json
Normal file
16
shared/tsconfig.json
Normal file
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "esnext",
|
||||
"module": "commonjs",
|
||||
"strict": true,
|
||||
"outDir": "dist",
|
||||
"skipLibCheck": true,
|
||||
"rootDir": "./src",
|
||||
"resolveJsonModule": true,
|
||||
"declaration": true,
|
||||
"declarationMap": true,
|
||||
"newLine": "LF",
|
||||
"sourceMap": true
|
||||
},
|
||||
"include": ["./src/**/*"]
|
||||
}
|
||||
158
src/extension.ts
158
src/extension.ts
@@ -1,158 +0,0 @@
|
||||
import * as vscode from 'vscode';
|
||||
|
||||
const debugVisualizer = "debugVisualizer";
|
||||
|
||||
function getHtml() {
|
||||
return `
|
||||
<html>
|
||||
<head>
|
||||
<style>
|
||||
html { height: 100%; width: 100%; padding: 0; margin: 0; }
|
||||
body { height: 100%; width: 100%; padding: 0; margin: 0; }
|
||||
iframe { height: 100%; width: 100%; padding: 0; margin: 0; border: 0; display: block; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<iframe src="http://localhost:8080/"></iframe>
|
||||
<script>
|
||||
const api = acquireVsCodeApi();
|
||||
window.addEventListener('message', event => {
|
||||
if (event.source === window.frames[0]) {
|
||||
if (event.data.command === "setExpression") {
|
||||
api.setState({ expression: event.data.expression });
|
||||
}
|
||||
|
||||
if (event.data.command === "initialized") {
|
||||
console.log("initialized, sent ", api.getState());
|
||||
window.frames[0].postMessage({ command: "setExpression", expression: api.getState().expression }, "*");
|
||||
|
||||
} else {
|
||||
console.log("iframe -> vscode", event.data);
|
||||
api.postMessage(event.data);
|
||||
}
|
||||
} else {
|
||||
console.log("vscode -> iframe", event.data);
|
||||
window.frames[0].postMessage(event.data, "*");
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
`;
|
||||
}
|
||||
|
||||
class DebugVisualizerViewSerializer implements vscode.WebviewPanelSerializer {
|
||||
constructor(private readonly views: WebViews) {}
|
||||
|
||||
async deserializeWebviewPanel(webviewPanel: vscode.WebviewPanel, state: any) {
|
||||
this.views.restore(webviewPanel);
|
||||
}
|
||||
}
|
||||
|
||||
class WebView {
|
||||
private expression: string|null = null;
|
||||
private lastFrameId: number|null = null;
|
||||
|
||||
constructor(private readonly webviewPanel: vscode.WebviewPanel) {
|
||||
webviewPanel.webview.onDidReceiveMessage(e => {
|
||||
if (e.command === "setExpression") {
|
||||
this.expression = e.expression;
|
||||
if (this.lastFrameId) {
|
||||
this.reevaluateExpression(this.lastFrameId);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public getExpression(): string|null {
|
||||
return this.expression;
|
||||
}
|
||||
|
||||
public async reevaluateExpression(frameId: number) {
|
||||
this.lastFrameId = frameId;
|
||||
if (!this.expression) { return; }
|
||||
|
||||
const session = vscode.debug.activeDebugSession;
|
||||
if (session) {
|
||||
try {
|
||||
const reply = await session.customRequest("evaluate", { "expression": this.expression, frameId, context: "watch" });
|
||||
this.webviewPanel.webview.postMessage({
|
||||
command: "setValue",
|
||||
value: { data: reply.result }
|
||||
});
|
||||
}
|
||||
catch (error) {
|
||||
vscode.window.showInformationMessage(`error: ${error.message}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class WebViews {
|
||||
private readonly debugVisualizations = new Map<vscode.WebviewPanel, WebView>();
|
||||
|
||||
public createNew() {
|
||||
const panel = vscode.window.createWebviewPanel(
|
||||
debugVisualizer,
|
||||
'Debug Visualizer',
|
||||
vscode.ViewColumn.Two,
|
||||
{ enableScripts: true }
|
||||
);
|
||||
|
||||
this.setupView(panel);
|
||||
}
|
||||
|
||||
public restore(webviewPanel: vscode.WebviewPanel) {
|
||||
this.setupView(webviewPanel);
|
||||
}
|
||||
|
||||
private setupView(webviewPanel: vscode.WebviewPanel) {
|
||||
webviewPanel.webview.html = getHtml();
|
||||
const view = new WebView(webviewPanel);
|
||||
this.debugVisualizations.set(webviewPanel, view);
|
||||
webviewPanel.onDidDispose(() => {
|
||||
this.debugVisualizations.delete(webviewPanel);
|
||||
});
|
||||
}
|
||||
|
||||
public reevaluateExpressions(frameId: number) {
|
||||
for (const vis of this.debugVisualizations.values()) {
|
||||
vis.reevaluateExpression(frameId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export function activate(context: vscode.ExtensionContext) {
|
||||
const views = new WebViews();
|
||||
|
||||
vscode.debug.onDidReceiveDebugSessionCustomEvent(e => {
|
||||
if (e.event === "paused") {
|
||||
const frameId = e.body.currentFrameId;
|
||||
views.reevaluateExpressions(frameId);
|
||||
}
|
||||
})
|
||||
|
||||
vscode.window.registerWebviewPanelSerializer(debugVisualizer,
|
||||
new DebugVisualizerViewSerializer(views));
|
||||
|
||||
let disposable = vscode.commands.registerCommand('extension.sayHello', () => {
|
||||
views.createNew();
|
||||
//const session = vscode.debug.activeDebugSession;
|
||||
|
||||
/*
|
||||
if (session) {
|
||||
session.customRequest("evaluate", { "expression": "Math.sqrt(10)" }).then(reply => {
|
||||
vscode.window.showInformationMessage(`result: ${reply.result}`);
|
||||
}, error => {
|
||||
vscode.window.showInformationMessage(`error: ${error.message}`);
|
||||
});
|
||||
}*/
|
||||
});
|
||||
|
||||
context.subscriptions.push(disposable);
|
||||
}
|
||||
|
||||
// this method is called when your extension is deactivated
|
||||
export function deactivate() {
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
//
|
||||
// Note: This example test is leveraging the Mocha test framework.
|
||||
// Please refer to their documentation on https://mochajs.org/ for help.
|
||||
//
|
||||
|
||||
// The module 'assert' provides assertion methods from node
|
||||
import * as assert from 'assert';
|
||||
|
||||
// You can import and use all API from the 'vscode' module
|
||||
// as well as import your extension to test it
|
||||
// import * as vscode from 'vscode';
|
||||
// import * as myExtension from '../extension';
|
||||
|
||||
// Defines a Mocha test suite to group tests of similar kind together
|
||||
suite("Extension Tests", function () {
|
||||
|
||||
// Defines a Mocha unit test
|
||||
test("Something 1", function() {
|
||||
assert.equal(-1, [1, 2, 3].indexOf(5));
|
||||
assert.equal(-1, [1, 2, 3].indexOf(0));
|
||||
});
|
||||
});
|
||||
@@ -1,22 +0,0 @@
|
||||
//
|
||||
// PLEASE DO NOT MODIFY / DELETE UNLESS YOU KNOW WHAT YOU ARE DOING
|
||||
//
|
||||
// This file is providing the test runner to use when running extension tests.
|
||||
// By default the test runner in use is Mocha based.
|
||||
//
|
||||
// You can provide your own test runner if you want to override it by exporting
|
||||
// a function run(testRoot: string, clb: (error:Error) => void) that the extension
|
||||
// host can call to run the tests. The test runner is expected to use console.log
|
||||
// to report the results back to the caller. When the tests are finished, return
|
||||
// a possible error to the callback or null if none.
|
||||
|
||||
import * as testRunner from 'vscode/lib/testrunner';
|
||||
|
||||
// You can directly control Mocha options by uncommenting the following lines
|
||||
// See https://github.com/mochajs/mocha/wiki/Using-mocha-programmatically#set-options for more info
|
||||
testRunner.configure({
|
||||
ui: 'tdd', // the TDD UI is being used in extension.test.ts (suite, test, etc.)
|
||||
useColors: true // colored output from test results
|
||||
});
|
||||
|
||||
module.exports = testRunner;
|
||||
12
tslint.json
12
tslint.json
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"extends": "recommended",
|
||||
"rules": {
|
||||
"await-promise": true
|
||||
}
|
||||
}
|
||||
{
|
||||
"extends": "recommended",
|
||||
"rules": {
|
||||
"await-promise": true
|
||||
}
|
||||
}
|
||||
|
||||
48
ui/package.json
Normal file
48
ui/package.json
Normal file
@@ -0,0 +1,48 @@
|
||||
{
|
||||
"name": "my-website",
|
||||
"version": "0.0.1",
|
||||
"scripts": {
|
||||
"dev": "webpack-dev-server --hot",
|
||||
"build": "webpack"
|
||||
},
|
||||
"dependencies": {
|
||||
"@blueprintjs/core": "^3.17.1",
|
||||
"@hediet/std": "^0.6.0",
|
||||
"@types/html-webpack-plugin": "^3.2.0",
|
||||
"@types/react-measure": "^2.0.5",
|
||||
"@types/react-svg-pan-zoom": "^2.5.7",
|
||||
"@types/webpack": "^4.32.1",
|
||||
"blueprintjs": "^0.0.8",
|
||||
"classnames": "^2.2.6",
|
||||
"mobx": "^5.10.1",
|
||||
"mobx-react": "^6.1.1",
|
||||
"monaco-editor": "^0.17.1",
|
||||
"popper.js": "^1.15.0",
|
||||
"react": "^16.8.6",
|
||||
"react-dom": "^16.8.6",
|
||||
"react-measure": "^2.3.0",
|
||||
"react-svg-pan-zoom": "^3.1.0",
|
||||
"viz.js": "^2.1.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/classnames": "^2.2.8",
|
||||
"@types/react": "^16.8.21",
|
||||
"@types/react-dom": "^16.8.4",
|
||||
"css-loader": "^3.0.0",
|
||||
"file-loader": "^4.0.0",
|
||||
"fork-ts-checker-webpack-plugin": "^1.4.2",
|
||||
"html-webpack-plugin": "^3.2.0",
|
||||
"monaco-editor-webpack-plugin": "^1.7.0",
|
||||
"node-sass": "^4.12.0",
|
||||
"raw-loader": "^3.0.0",
|
||||
"sass": "^1.20.1",
|
||||
"sass-loader": "^7.1.0",
|
||||
"style-loader": "^0.23.1",
|
||||
"ts-loader": "^6.0.3",
|
||||
"ts-node": "^8.3.0",
|
||||
"typescript": "^3.5.2",
|
||||
"webpack": "^4.35.0",
|
||||
"webpack-cli": "^3.3.4",
|
||||
"webpack-dev-server": "^3.7.2"
|
||||
}
|
||||
}
|
||||
149
ui/src/Components/GraphViz.tsx
Normal file
149
ui/src/Components/GraphViz.tsx
Normal file
@@ -0,0 +1,149 @@
|
||||
import { observer, disposeOnUnmount } from "mobx-react";
|
||||
import * as classNames from "classnames";
|
||||
import * as React from "react";
|
||||
import * as ReactDOM from "react-dom";
|
||||
import { ReactSVGPanZoom, Tool, Value } from "react-svg-pan-zoom";
|
||||
import { computed, observable, autorun, runInAction, action } from "mobx";
|
||||
import Measure, { ContentRect } from "react-measure";
|
||||
import Viz from "viz.js";
|
||||
import { Module, render } from "viz.js/full.render.js";
|
||||
|
||||
export interface GraphData {
|
||||
kind: "GraphData";
|
||||
nodes: { id: string; label: string }[];
|
||||
edges: { from: string; to: string; label: string }[];
|
||||
}
|
||||
|
||||
@observer
|
||||
export class DotGraphViewer extends React.Component<{ data: GraphData }> {
|
||||
render() {
|
||||
const { nodes, edges } = this.props.data;
|
||||
const dotContent = `
|
||||
digraph MyGraph {
|
||||
${nodes
|
||||
.map(n => `${n.id} [ label = ${JSON.stringify(n.label)} ];`)
|
||||
.join("\n ")}
|
||||
${edges
|
||||
.map(
|
||||
e =>
|
||||
`${e.from} -> ${e.to} [ label = ${JSON.stringify(
|
||||
e.label
|
||||
)} ];`
|
||||
)
|
||||
.join("\n")}
|
||||
}
|
||||
`;
|
||||
return <DotViewer dotCode={dotContent} />;
|
||||
}
|
||||
}
|
||||
|
||||
const viz: any = new Viz({
|
||||
Module: () => Module({ TOTAL_MEMORY: 1 << 30 }),
|
||||
render,
|
||||
});
|
||||
|
||||
@observer
|
||||
export class DotViewer extends React.Component<{
|
||||
dotCode: string;
|
||||
svgRef?: (element: SVGSVGElement | null) => void;
|
||||
}> {
|
||||
@observable private svg: string | null = null;
|
||||
|
||||
@disposeOnUnmount
|
||||
// @ts-ignore
|
||||
private readonly _updateSvgAutorun = autorun(async () => {
|
||||
const svg = await viz.renderString(this.props.dotCode);
|
||||
runInAction("Update svg", () => (this.svg = svg));
|
||||
});
|
||||
|
||||
render() {
|
||||
if (!this.svg) {
|
||||
return <div>Loading...</div>;
|
||||
}
|
||||
return <SvgViewer svgRef={this.props.svgRef} svgContent={this.svg} />;
|
||||
}
|
||||
}
|
||||
|
||||
function widthOrDefault(r: ContentRect): number {
|
||||
if (r.bounds && r.bounds.width) {
|
||||
return r.bounds.width;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
function heightOrDefault(r: ContentRect): number {
|
||||
console.log(r);
|
||||
if (r.bounds && r.bounds.height) {
|
||||
return r.bounds.height;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
@observer
|
||||
export class SvgViewer extends React.Component<{
|
||||
svgContent: string;
|
||||
svgRef?: (element: SVGSVGElement | null) => void;
|
||||
}> {
|
||||
@observable tool: Tool = "pan";
|
||||
|
||||
@action.bound
|
||||
private setTool(tool: Tool): void {
|
||||
this.tool = tool;
|
||||
}
|
||||
|
||||
private setRef(element: ReactSVGPanZoom | null) {
|
||||
if (this.props.svgRef) {
|
||||
if (!element) {
|
||||
this.props.svgRef(null);
|
||||
return;
|
||||
}
|
||||
|
||||
const svg = (element as any).ViewerDOM as SVGSVGElement;
|
||||
this.props.svgRef(svg);
|
||||
}
|
||||
}
|
||||
|
||||
@observable value: Value | {} = {};
|
||||
|
||||
render() {
|
||||
let { svgContent } = this.props;
|
||||
let width: number = 0;
|
||||
let height: number = 0;
|
||||
svgContent = svgContent.replace(
|
||||
/viewBox="[0-9\.]+ [0-9\.]+ ([0-9\.]+) ([0-9\.]+)"/,
|
||||
(r, w, h) => {
|
||||
width = parseFloat(w);
|
||||
height = parseFloat(h);
|
||||
return "";
|
||||
}
|
||||
);
|
||||
const tool = this.tool;
|
||||
const val = this.value;
|
||||
|
||||
return (
|
||||
<Measure bounds>
|
||||
{({ measureRef, contentRect }) => (
|
||||
<div ref={measureRef} className="svgViewer">
|
||||
<ReactSVGPanZoom
|
||||
width={widthOrDefault(contentRect)}
|
||||
height={heightOrDefault(contentRect)}
|
||||
tool={tool}
|
||||
value={val}
|
||||
onChangeValue={v => (this.value = v)}
|
||||
onChangeTool={this.setTool}
|
||||
ref={e => this.setRef(e)}
|
||||
>
|
||||
<svg width={width} height={height}>
|
||||
<g
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: svgContent,
|
||||
}}
|
||||
/>
|
||||
</svg>
|
||||
</ReactSVGPanZoom>
|
||||
</div>
|
||||
)}
|
||||
</Measure>
|
||||
);
|
||||
}
|
||||
}
|
||||
256
ui/src/Components/Point.ts
Normal file
256
ui/src/Components/Point.ts
Normal file
@@ -0,0 +1,256 @@
|
||||
function sqr(a: number) {
|
||||
return a * a;
|
||||
}
|
||||
|
||||
export type PointLike =
|
||||
| Point
|
||||
| { x?: number; y: number }
|
||||
| { x: number; y?: number };
|
||||
|
||||
export function point(data: PointLike) {
|
||||
if (data instanceof Point) {
|
||||
return data;
|
||||
}
|
||||
return new Point(data.x || 0, data.y || 0);
|
||||
}
|
||||
|
||||
export class Point {
|
||||
public static readonly Zero = new Point(0, 0);
|
||||
|
||||
constructor(public readonly x: number, public readonly y: number) {}
|
||||
|
||||
public mapXY(fn: (arg: number) => number): Point {
|
||||
return new Point(fn(this.x), fn(this.y));
|
||||
}
|
||||
|
||||
public distance(other: PointLike = Point.Zero): number {
|
||||
const d = this.sub(other);
|
||||
return Math.sqrt(sqr(d.x) + sqr(d.y));
|
||||
}
|
||||
|
||||
public sub(other: PointLike): Point {
|
||||
const o = point(other);
|
||||
return new Point(this.x - o.x, this.y - o.y);
|
||||
}
|
||||
|
||||
public add(other: PointLike): Point {
|
||||
const o = point(other);
|
||||
return new Point(this.x + o.x, this.y + o.y);
|
||||
}
|
||||
|
||||
public mul(scalar: number): Point {
|
||||
return new Point(this.x * scalar, this.y * scalar);
|
||||
}
|
||||
|
||||
public div(scalar: number): Point {
|
||||
return new Point(this.x / scalar, this.y / scalar);
|
||||
}
|
||||
|
||||
public equals(other: PointLike) {
|
||||
const o = point(other);
|
||||
return this.x === o.x && this.y === o.y;
|
||||
}
|
||||
|
||||
public angle(): number {
|
||||
const angle = Math.atan2(this.x, this.y);
|
||||
return angle;
|
||||
}
|
||||
|
||||
public getPointCloserTo(dest: PointLike, dist: number): Point {
|
||||
if (this.equals(dest)) return this;
|
||||
|
||||
const angle = point(dest)
|
||||
.sub(this)
|
||||
.angle();
|
||||
|
||||
const result = new Point(
|
||||
this.x + Math.sin(angle) * dist,
|
||||
this.y + Math.cos(angle) * dist
|
||||
);
|
||||
return result;
|
||||
}
|
||||
|
||||
public toJson(): { x: number; y: number } {
|
||||
return { x: this.x, y: this.y };
|
||||
}
|
||||
}
|
||||
|
||||
function turn(p1: Point, p2: Point, p3: Point): number {
|
||||
const A = (p3.y - p1.y) * (p2.x - p1.x);
|
||||
const B = (p2.y - p1.y) * (p3.x - p1.x);
|
||||
return A > B + Number.MIN_VALUE ? 1 : A + Number.MIN_VALUE < B ? -1 : 0;
|
||||
}
|
||||
|
||||
export function isIntersect(
|
||||
aStart: Point,
|
||||
aEnd: Point,
|
||||
bStart: Point,
|
||||
bEnd: Point
|
||||
): boolean {
|
||||
return (
|
||||
turn(aStart, bStart, bEnd) != turn(aEnd, bStart, bEnd) &&
|
||||
turn(aStart, aEnd, bStart) != turn(aStart, aEnd, bEnd)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Liang-Barsky function by Daniel White
|
||||
*
|
||||
* @link http://www.skytopia.com/project/articles/compsci/clipping.html
|
||||
*
|
||||
* @param {number} x0
|
||||
* @param {number} y0
|
||||
* @param {number} x1
|
||||
* @param {number} y1
|
||||
* @param {array<number>} bbox
|
||||
* @return {array<array<number>>|null}
|
||||
*/
|
||||
function liangBarsky(
|
||||
x0: number,
|
||||
y0: number,
|
||||
x1: number,
|
||||
y1: number,
|
||||
bbox: [number, number, number, number]
|
||||
): [[number, number], [number, number]] | null {
|
||||
let [xmin, xmax, ymin, ymax] = bbox;
|
||||
let t0 = 0,
|
||||
t1 = 1;
|
||||
let dx = x1 - x0,
|
||||
dy = y1 - y0;
|
||||
let p = 0,
|
||||
q = 0,
|
||||
r = 0;
|
||||
|
||||
for (let edge: 0 | 1 | 2 | 3 = 0; edge < 4; edge++) {
|
||||
// Traverse through left, right, bottom, top edges.
|
||||
if (edge === 0) {
|
||||
p = -dx;
|
||||
q = -(xmin - x0);
|
||||
} else if (edge === 1) {
|
||||
p = dx;
|
||||
q = xmax - x0;
|
||||
} else if (edge === 2) {
|
||||
p = -dy;
|
||||
q = -(ymin - y0);
|
||||
} else if (edge === 3) {
|
||||
p = dy;
|
||||
q = ymax - y0;
|
||||
}
|
||||
|
||||
r = q / p;
|
||||
|
||||
if (p === 0 && q < 0) return null; // Don't draw line at all. (parallel line outside)
|
||||
|
||||
if (p < 0) {
|
||||
if (r > t1) return null;
|
||||
// Don't draw line at all.
|
||||
else if (r > t0) t0 = r; // Line is clipped!
|
||||
} else if (p > 0) {
|
||||
if (r < t0) return null;
|
||||
// Don't draw line at all.
|
||||
else if (r < t1) t1 = r; // Line is clipped!
|
||||
}
|
||||
}
|
||||
|
||||
return [[x0 + t0 * dx, y0 + t0 * dy], [x0 + t1 * dx, y0 + t1 * dy]];
|
||||
}
|
||||
|
||||
export function intersectRectWithLine(
|
||||
start: Point,
|
||||
end: Point,
|
||||
rect: Rectangle
|
||||
): { first: Point; second: Point } | undefined {
|
||||
const r = liangBarsky(start.x, start.y, end.x, end.y, [
|
||||
rect.topLeft.x,
|
||||
rect.bottomRight.x,
|
||||
rect.topLeft.y,
|
||||
rect.bottomRight.y,
|
||||
]);
|
||||
if (!r) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return {
|
||||
first: new Point(r[0][0], r[0][1]),
|
||||
second: new Point(r[1][0], r[1][1]),
|
||||
};
|
||||
}
|
||||
|
||||
// first zoom then translate
|
||||
export function scale(
|
||||
clientOffset: Point,
|
||||
clientSize: Point,
|
||||
viewSize: Point
|
||||
): { clientZoom: number; clientOffset: Point } {
|
||||
const clientRatio = clientSize.x / clientSize.y;
|
||||
const viewRatio = viewSize.x / viewSize.y;
|
||||
|
||||
let zoom = 1;
|
||||
|
||||
if (clientRatio < viewRatio) zoom = viewSize.y / clientSize.y;
|
||||
else zoom = viewSize.x / clientSize.x;
|
||||
|
||||
const clientMid = clientOffset.mul(zoom).add(clientSize.mul(zoom / 2));
|
||||
const viewMid = viewSize.div(2);
|
||||
|
||||
const clientOffset2 = viewMid.sub(clientMid);
|
||||
|
||||
return { clientOffset: clientOffset2, clientZoom: zoom };
|
||||
}
|
||||
|
||||
export class Rectangle {
|
||||
public static ofSize(position: PointLike, size: PointLike): Rectangle {
|
||||
const pos = point(position);
|
||||
return new Rectangle(pos, pos.add(size));
|
||||
}
|
||||
|
||||
public static spanning(first: Point, ...rest: Point[]): Rectangle {
|
||||
let min = first.toJson();
|
||||
let max = first.toJson();
|
||||
for (const p of rest) {
|
||||
min.x = Math.min(min.x, p.x);
|
||||
min.y = Math.min(min.y, p.y);
|
||||
max.x = Math.max(max.x, p.x);
|
||||
max.y = Math.max(max.y, p.y);
|
||||
}
|
||||
|
||||
return new Rectangle(point(min), point(max));
|
||||
}
|
||||
|
||||
constructor(
|
||||
public readonly topLeft: Point,
|
||||
public readonly bottomRight: Point
|
||||
) {}
|
||||
|
||||
get center(): Point {
|
||||
return this.bottomRight.add(this.topLeft).div(2);
|
||||
}
|
||||
|
||||
get size(): Point {
|
||||
return this.bottomRight.sub(this.topLeft);
|
||||
}
|
||||
|
||||
get topRight(): Point {
|
||||
return new Point(this.bottomRight.x, this.topLeft.y);
|
||||
}
|
||||
|
||||
get bottomLeft(): Point {
|
||||
return new Point(this.topLeft.x, this.bottomRight.y);
|
||||
}
|
||||
|
||||
public intersects(selectionRect: Rectangle): boolean {
|
||||
return !(
|
||||
selectionRect.topLeft.x > this.bottomRight.x ||
|
||||
selectionRect.bottomRight.x < this.topLeft.x ||
|
||||
selectionRect.topLeft.y > this.bottomRight.y ||
|
||||
selectionRect.bottomRight.y < this.topLeft.y
|
||||
);
|
||||
}
|
||||
|
||||
equals(other: Rectangle) {
|
||||
return (
|
||||
this.topLeft.equals(other.topLeft) &&
|
||||
this.bottomRight.equals(other.bottomRight)
|
||||
);
|
||||
}
|
||||
}
|
||||
102
ui/src/Components/SvgElements.tsx
Normal file
102
ui/src/Components/SvgElements.tsx
Normal file
@@ -0,0 +1,102 @@
|
||||
import { Point, Rectangle } from "./Point";
|
||||
import * as React from "react";
|
||||
import { Properties } from "csstype";
|
||||
|
||||
type Omit<T, K> = Pick<T, Exclude<keyof T, K>>;
|
||||
|
||||
export function omit<T, K extends keyof T>(obj: T, keys: K[]): Omit<T, K> {
|
||||
const newObj: Omit<T, K> = {} as any;
|
||||
for (const [key, val] of Object.entries(obj)) {
|
||||
if (!keys.includes(key as K)) {
|
||||
(newObj as any)[key] = val;
|
||||
}
|
||||
}
|
||||
return newObj;
|
||||
}
|
||||
|
||||
export interface SvgAttributes<T> extends React.DOMAttributes<T> {
|
||||
stroke?: string;
|
||||
className?: string;
|
||||
style?: Properties;
|
||||
pointerEvents?: "none" | "auto" | "initial";
|
||||
}
|
||||
|
||||
export function SvgText(
|
||||
props: {
|
||||
position: Point;
|
||||
children: any;
|
||||
childRef?: React.Ref<SVGTextElement>;
|
||||
textAnchor?: "middle" | "end" | "start";
|
||||
dominantBaseline?: "central" | "middle";
|
||||
} & SvgAttributes<SVGTextElement>
|
||||
) {
|
||||
return (
|
||||
<text
|
||||
x={props.position.x}
|
||||
y={props.position.y}
|
||||
ref={props.childRef}
|
||||
{...omit(props, ["position", "childRef"])}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export function SvgCircle(
|
||||
props: {
|
||||
center: Point;
|
||||
radius: number;
|
||||
childRef?: React.Ref<SVGCircleElement>;
|
||||
} & SvgAttributes<SVGCircleElement>
|
||||
) {
|
||||
return (
|
||||
<circle
|
||||
cx={props.center.x}
|
||||
cy={props.center.y}
|
||||
r={props.radius}
|
||||
ref={props.childRef}
|
||||
{...omit(props, ["center", "radius", "childRef"])}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export function SvgLine(
|
||||
props: { start: Point; end: Point } & SvgAttributes<SVGLineElement>
|
||||
) {
|
||||
return (
|
||||
<line
|
||||
x1={props.start.x}
|
||||
y1={props.start.y}
|
||||
x2={props.end.x}
|
||||
y2={props.end.y}
|
||||
{...omit(props, ["end", "start"])}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export function SvgRect(
|
||||
props: { rectangle: Rectangle; fill?: string } & SvgAttributes<
|
||||
SVGRectElement
|
||||
>
|
||||
) {
|
||||
const r = props.rectangle;
|
||||
return (
|
||||
<rect
|
||||
x={r.topLeft.x}
|
||||
y={r.topLeft.y}
|
||||
width={r.size.x}
|
||||
height={r.size.y}
|
||||
{...omit(props, ["rectangle"])}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
/*
|
||||
export function Svg(props: { position: Point } & SvgAttributes<SVGSVGElement>) {
|
||||
return (
|
||||
<svg
|
||||
x={props.position.x}
|
||||
y={props.position.y}
|
||||
{...omit(props, ["position"])}
|
||||
/>
|
||||
);
|
||||
}
|
||||
*/
|
||||
296
ui/src/Components/index.tsx
Normal file
296
ui/src/Components/index.tsx
Normal file
@@ -0,0 +1,296 @@
|
||||
import React = require("react");
|
||||
import { Model } from "../Model";
|
||||
import { observer } from "mobx-react";
|
||||
import { observable, action, computed } from "mobx";
|
||||
import { Popover, Icon, Button } from "@blueprintjs/core";
|
||||
import classnames = require("classnames");
|
||||
import Measure from "react-measure";
|
||||
import { DotGraphViewer } from "./GraphViz";
|
||||
import { TreeView } from "../Visualizers/TreeVisualizer/Views";
|
||||
import { createTreeViewModelFromTreeNodeData } from "../Visualizers/TreeVisualizer/Visualizers";
|
||||
import { CommonDataTypes } from "@hediet/debug-visualizer-data-extraction";
|
||||
@observer
|
||||
export class GUI extends React.Component<{ model: Model }> {
|
||||
@observable text: string = "node";
|
||||
|
||||
@observable expanded = false;
|
||||
|
||||
render() {
|
||||
const m = this.props.model;
|
||||
return (
|
||||
<div className="component-GUI" tabIndex={0}>
|
||||
<div className="part-header">
|
||||
<ExpandButton
|
||||
expanded={this.expanded}
|
||||
setExpanded={v => (this.expanded = v)}
|
||||
/>
|
||||
<div className="part-header-content">
|
||||
<div className="part-header-main">
|
||||
<ExpressionInput model={m} />
|
||||
<Button
|
||||
minimal
|
||||
small
|
||||
className="part-icon"
|
||||
icon="refresh"
|
||||
onClick={() => m.refresh()}
|
||||
/>
|
||||
<Button
|
||||
minimal
|
||||
small
|
||||
className="part-icon"
|
||||
icon="log-in"
|
||||
onClick={() => m.openBrowser()}
|
||||
/>
|
||||
<Button
|
||||
minimal
|
||||
small
|
||||
className="part-icon"
|
||||
icon="locate"
|
||||
onClick={() => m.useSelectionAsExpression()}
|
||||
/>
|
||||
</div>
|
||||
{this.expanded && (
|
||||
<>
|
||||
<div style={{ height: 6 }} />
|
||||
<div className="part-header-options">
|
||||
<NamedSelect
|
||||
name="Source"
|
||||
selected={0}
|
||||
options={[{ label: "js" }]}
|
||||
/>
|
||||
<div style={{ width: 10 }} />
|
||||
|
||||
<NamedSelect
|
||||
name="Extractor"
|
||||
selected={0}
|
||||
options={[{ label: "literal" }]}
|
||||
/>
|
||||
<div style={{ width: 10 }} />
|
||||
|
||||
<NamedSelect
|
||||
name="Visualizer"
|
||||
selected={0}
|
||||
options={[{ label: "literal" }]}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="part-visualizer">
|
||||
<Visualizer model={m} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
renderValue(value: string) {
|
||||
return <div>{`${value.length}`}</div>;
|
||||
}
|
||||
}
|
||||
|
||||
@observer
|
||||
export class ExpressionInput extends React.Component<{ model: Model }> {
|
||||
@observable editedExpression: string | undefined;
|
||||
@computed get currentExpression(): string {
|
||||
return this.editedExpression !== undefined
|
||||
? this.editedExpression
|
||||
: this.props.model.expression;
|
||||
}
|
||||
|
||||
@action.bound
|
||||
updateCurrentExpression(newValue: string) {
|
||||
this.editedExpression = newValue;
|
||||
}
|
||||
|
||||
@action.bound
|
||||
submit() {
|
||||
if (!this.editedExpression) {
|
||||
return;
|
||||
}
|
||||
this.props.model.setExpression(this.editedExpression);
|
||||
this.editedExpression = undefined;
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<input
|
||||
className="part-expression-input"
|
||||
value={this.currentExpression}
|
||||
onChange={e => this.updateCurrentExpression(e.target.value)}
|
||||
onBlur={this.submit}
|
||||
onKeyPress={e => {
|
||||
if (e.charCode === 13) {
|
||||
// enter
|
||||
this.submit();
|
||||
}
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@observer
|
||||
export class Visualizer extends React.Component<{ model: Model }> {
|
||||
render() {
|
||||
return (
|
||||
<div className="component-Visualizer">{this.renderContent()}</div>
|
||||
);
|
||||
/*
|
||||
|
||||
|
||||
<DotGraphViewer
|
||||
data={{
|
||||
kind: "GraphData",
|
||||
edges: [
|
||||
{
|
||||
from: "1",
|
||||
to: "2",
|
||||
label: "test",
|
||||
},
|
||||
],
|
||||
nodes: [
|
||||
{ id: "1", label: "foo" },
|
||||
{ id: "2", label: "bar" },
|
||||
],
|
||||
}}
|
||||
/>
|
||||
*/
|
||||
}
|
||||
|
||||
renderContent(): JSX.Element {
|
||||
const s = this.props.model.state;
|
||||
if (s.kind === "loading") {
|
||||
return <NoData label="Loading" />;
|
||||
} else if (s.kind === "error") {
|
||||
return <NoData label={"Error: " + s.message} />;
|
||||
} else if (s.kind === "noExpression") {
|
||||
return <NoData label="No Expression" />;
|
||||
} else if (s.kind === "noDebugSession") {
|
||||
return <NoData label="No Debug Session" />;
|
||||
} else if (s.kind === "data") {
|
||||
const data = s.result.data;
|
||||
|
||||
if (data.kind.tree) {
|
||||
const d = data as CommonDataTypes.TreeNodeData;
|
||||
const m = createTreeViewModelFromTreeNodeData(d.root);
|
||||
return <TreeView model={m} />;
|
||||
} else {
|
||||
return <pre>{JSON.stringify(s.result)}</pre>;
|
||||
}
|
||||
} else {
|
||||
const nvr: never = s;
|
||||
return <div />;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@observer
|
||||
class NoData extends React.Component<{ label: string }> {
|
||||
@observable height = 0;
|
||||
@observable width = 0;
|
||||
|
||||
render() {
|
||||
const { width, height } = this;
|
||||
return (
|
||||
<Measure
|
||||
client={true}
|
||||
onResize={e => {
|
||||
if (e.client) {
|
||||
this.height = e.client.height;
|
||||
this.width = e.client.width;
|
||||
}
|
||||
}}
|
||||
>
|
||||
{({ measureRef }) => (
|
||||
<div className="component-NoData" ref={measureRef}>
|
||||
<svg>
|
||||
<line x1={0} y1={0} x2={width} y2={height} />
|
||||
<line x1={width} y1={0} x2={0} y2={height} />
|
||||
<rect
|
||||
x={width / 2 - 50}
|
||||
y={height / 2 - 20}
|
||||
width={100}
|
||||
height={40}
|
||||
/>
|
||||
<text
|
||||
x={width / 2}
|
||||
y={height / 2}
|
||||
textAnchor="middle"
|
||||
alignmentBaseline="central"
|
||||
children={this.props.label}
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
)}
|
||||
</Measure>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export class ExpandButton extends React.Component<{
|
||||
expanded: boolean;
|
||||
setExpanded: (val: boolean) => void;
|
||||
}> {
|
||||
render() {
|
||||
return (
|
||||
<div
|
||||
onClick={() => this.props.setExpanded(!this.props.expanded)}
|
||||
className={classnames(
|
||||
"component-ExpandButton",
|
||||
this.props.expanded && "expanded"
|
||||
)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function NamedSelect(props: {
|
||||
name: string;
|
||||
selected: number;
|
||||
options: SelectableItem[];
|
||||
}) {
|
||||
return (
|
||||
<div className="component-NamedSelect">
|
||||
<div className="part-Select">
|
||||
<Select {...props} />
|
||||
</div>
|
||||
<div>{props.name}</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
interface SelectableItem {
|
||||
label: string;
|
||||
}
|
||||
|
||||
@observer
|
||||
export class Select extends React.Component<{
|
||||
selected: number;
|
||||
options: SelectableItem[];
|
||||
}> {
|
||||
@computed get selected(): SelectableItem {
|
||||
return this.props.options[this.props.selected];
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div className="component-Select">
|
||||
<Popover
|
||||
position="auto-start"
|
||||
modifiers={{ arrow: { enabled: false } }}
|
||||
usePortal={false}
|
||||
>
|
||||
<button className="part-button">
|
||||
{this.selected.label}
|
||||
</button>
|
||||
<div className={"part-content"}>
|
||||
{this.props.options.map((o, idx) => (
|
||||
<div key={idx}>{o.label}</div>
|
||||
))}
|
||||
</div>
|
||||
</Popover>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
88
ui/src/Model.ts
Normal file
88
ui/src/Model.ts
Normal file
@@ -0,0 +1,88 @@
|
||||
import {
|
||||
debugVisualizerUIContract,
|
||||
DataExtractionState,
|
||||
} from "@hediet/debug-visualizer-vscode-shared";
|
||||
import { WebSocketStream } from "@hediet/typed-json-rpc-websocket";
|
||||
import { ConsoleRpcLogger } from "@hediet/typed-json-rpc";
|
||||
import { observable, action } from "mobx";
|
||||
|
||||
declare const window: Window & {
|
||||
serverPort?: number;
|
||||
};
|
||||
|
||||
export class Model {
|
||||
port: number;
|
||||
|
||||
@observable expression: string = "";
|
||||
@observable state: DataExtractionState | { kind: "noExpression" } = {
|
||||
kind: "noExpression",
|
||||
};
|
||||
|
||||
private server:
|
||||
| typeof debugVisualizerUIContract.TServerInterface
|
||||
| undefined = undefined;
|
||||
|
||||
constructor() {
|
||||
if (window.serverPort) {
|
||||
this.port = window.serverPort;
|
||||
} else {
|
||||
const url = new URL(window.location.href);
|
||||
const portStr = url.searchParams.get("serverPort");
|
||||
if (!portStr) {
|
||||
throw new Error("No port given.");
|
||||
}
|
||||
this.port = parseInt(portStr);
|
||||
}
|
||||
|
||||
this.stayConnected();
|
||||
}
|
||||
|
||||
@action
|
||||
setExpression(newExpression: string) {
|
||||
this.expression = newExpression;
|
||||
if (this.server) {
|
||||
this.server.setExpression({ newExpression });
|
||||
}
|
||||
}
|
||||
|
||||
refresh() {
|
||||
if (this.server) {
|
||||
this.server.refresh();
|
||||
}
|
||||
}
|
||||
|
||||
async stayConnected(): Promise<void> {
|
||||
while (true) {
|
||||
try {
|
||||
const stream = await WebSocketStream.connectTo({
|
||||
host: "localhost",
|
||||
port: this.port,
|
||||
});
|
||||
const {
|
||||
server,
|
||||
} = debugVisualizerUIContract.getServerFromStream(
|
||||
stream,
|
||||
new ConsoleRpcLogger(),
|
||||
{
|
||||
updateState: ({ newState }) => {
|
||||
this.state = newState;
|
||||
},
|
||||
}
|
||||
);
|
||||
this.server = server;
|
||||
|
||||
await stream.onClosed;
|
||||
} catch (e) {}
|
||||
}
|
||||
}
|
||||
|
||||
openBrowser() {
|
||||
if (this.server) {
|
||||
this.server.openInBrowser();
|
||||
}
|
||||
}
|
||||
|
||||
useSelectionAsExpression() {
|
||||
throw new Error("Method not implemented.");
|
||||
}
|
||||
}
|
||||
37
ui/src/Visualizers/TreeVisualizer/SampleTreeVisualizer.tsx
Normal file
37
ui/src/Visualizers/TreeVisualizer/SampleTreeVisualizer.tsx
Normal file
@@ -0,0 +1,37 @@
|
||||
import { TreeViewModel, TreeNodeViewModel, TreeWithPathView } from "./Views";
|
||||
import React = require("react");
|
||||
|
||||
export function SampleTreeVisualizer() {
|
||||
interface SampleTreeNode {
|
||||
parentPropertyName: string | undefined;
|
||||
name: string;
|
||||
children: SampleTreeNode[];
|
||||
}
|
||||
|
||||
const data = require("./sample-input.json") as SampleTreeNode;
|
||||
|
||||
const m = new TreeViewModel();
|
||||
m.root = createModel(data, m);
|
||||
m.root.children[0].children[4].children[1].children[0].isMarked = true;
|
||||
|
||||
function createModel(
|
||||
root: SampleTreeNode,
|
||||
viewModel: TreeViewModel
|
||||
): TreeNodeViewModel {
|
||||
const children: TreeNodeViewModel[] = root.children.map(c =>
|
||||
createModel(c, viewModel)
|
||||
);
|
||||
const model = new TreeNodeViewModel(
|
||||
viewModel,
|
||||
root.parentPropertyName,
|
||||
root.name,
|
||||
children
|
||||
);
|
||||
for (const c of children) {
|
||||
c.parent = model;
|
||||
}
|
||||
return model;
|
||||
}
|
||||
|
||||
return <TreeWithPathView model={m} />;
|
||||
}
|
||||
409
ui/src/Visualizers/TreeVisualizer/Views.tsx
Normal file
409
ui/src/Visualizers/TreeVisualizer/Views.tsx
Normal file
@@ -0,0 +1,409 @@
|
||||
import { observer, disposeOnUnmount } from "mobx-react";
|
||||
import { observable, autorun, computed, action, runInAction } from "mobx";
|
||||
import React = require("react");
|
||||
import classNames = require("classnames");
|
||||
import { Rectangle, Point } from "../../Components/Point";
|
||||
import { SvgLine } from "../../Components/SvgElements";
|
||||
import "./style.scss";
|
||||
|
||||
export class TreeViewModel {
|
||||
private version = 0;
|
||||
private lastUpdateVersion = -1;
|
||||
|
||||
@observable public root: TreeNodeViewModel | undefined = undefined;
|
||||
|
||||
public invalidateOffsets() {
|
||||
this.version++;
|
||||
}
|
||||
|
||||
@action
|
||||
public updateOffsets() {
|
||||
if (!this.root) {
|
||||
return;
|
||||
}
|
||||
if (this.lastUpdateVersion === this.version) {
|
||||
return;
|
||||
}
|
||||
this.lastUpdateVersion = this.version;
|
||||
|
||||
this.root.updateOffsets();
|
||||
}
|
||||
|
||||
@observable selected: TreeNodeViewModel | undefined = undefined;
|
||||
|
||||
@action public select(current: TreeNodeViewModel | undefined) {
|
||||
if (this.selected) {
|
||||
this.selected.isSelected = false;
|
||||
}
|
||||
if (current) {
|
||||
current.isSelected = true;
|
||||
}
|
||||
this.selected = current;
|
||||
}
|
||||
|
||||
private selectOnHoverEnabled = false;
|
||||
private selectedBeforeHover: TreeNodeViewModel | undefined = undefined;
|
||||
|
||||
@action public hover(current: TreeNodeViewModel | undefined) {
|
||||
if (this.selectOnHoverEnabled) {
|
||||
if (current) {
|
||||
this.select(current);
|
||||
} else {
|
||||
this.select(this.selectedBeforeHover);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
selectOnHover(enable: boolean) {
|
||||
if (this.selectOnHoverEnabled === enable) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (enable) {
|
||||
this.selectedBeforeHover = this.selected;
|
||||
} else if (!this.selected) {
|
||||
this.select(this.selectedBeforeHover);
|
||||
}
|
||||
|
||||
this.selectOnHoverEnabled = enable;
|
||||
}
|
||||
}
|
||||
|
||||
export class TreeNodeViewModel {
|
||||
public parent: TreeNodeViewModel | null = null;
|
||||
|
||||
constructor(
|
||||
public readonly treeViewModel: TreeViewModel,
|
||||
public readonly id: string | undefined,
|
||||
public readonly name: string,
|
||||
public readonly children: TreeNodeViewModel[]
|
||||
) {}
|
||||
|
||||
@observable expanderOffsetRect: Rectangle | undefined = undefined;
|
||||
private expanderDiv: HTMLDivElement | null = null;
|
||||
|
||||
@action.bound
|
||||
setExpanderDiv(div: HTMLDivElement | null) {
|
||||
this.expanderDiv = div;
|
||||
this.treeViewModel.invalidateOffsets();
|
||||
}
|
||||
|
||||
@observable public expanded: boolean = false;
|
||||
@observable public isMarked: boolean = false;
|
||||
@observable public isSelected: boolean = false;
|
||||
|
||||
@action
|
||||
public toggleExpanded() {
|
||||
this.expanded = !this.expanded;
|
||||
this.treeViewModel.invalidateOffsets();
|
||||
}
|
||||
|
||||
@computed public get isChildOrThisMarked(): boolean {
|
||||
if (this.isMarked) return true;
|
||||
return this.children.some(c => c.isChildOrThisMarked);
|
||||
}
|
||||
|
||||
@computed public get isChildOrThisSelected(): boolean {
|
||||
if (this.isSelected) return true;
|
||||
return this.children.some(c => c.isChildOrThisSelected);
|
||||
}
|
||||
|
||||
@computed public get isParentOrThisSelected(): boolean {
|
||||
if (this.isSelected) return true;
|
||||
if (this.parent) return this.parent.isParentOrThisSelected;
|
||||
return false;
|
||||
}
|
||||
|
||||
get path(): string[] {
|
||||
const result = new Array<string>();
|
||||
let c: TreeNodeViewModel = this;
|
||||
while (c.parent) {
|
||||
result.unshift(c.id || c.name);
|
||||
c = c.parent;
|
||||
}
|
||||
if (result.length === 0) {
|
||||
result.push("root");
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
updateOffsets() {
|
||||
for (const c of this.children) {
|
||||
c.updateOffsets();
|
||||
}
|
||||
|
||||
if (!this.expanderDiv) {
|
||||
return;
|
||||
}
|
||||
const newRect = Rectangle.ofSize(
|
||||
new Point(this.expanderDiv.offsetLeft, this.expanderDiv.offsetTop),
|
||||
new Point(
|
||||
this.expanderDiv.offsetWidth,
|
||||
this.expanderDiv.offsetHeight
|
||||
)
|
||||
);
|
||||
if (
|
||||
!this.expanderOffsetRect ||
|
||||
!newRect.equals(this.expanderOffsetRect)
|
||||
) {
|
||||
this.expanderOffsetRect = newRect;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@observer
|
||||
export class TreeWithPathView extends React.Component<{
|
||||
model: TreeViewModel;
|
||||
}> {
|
||||
render() {
|
||||
const model = this.props.model;
|
||||
return (
|
||||
<div className="component-TreeWithPathView">
|
||||
<div className="part-path">
|
||||
<span>
|
||||
{model.selected ? (
|
||||
<span>{model.selected.path.join(" / ")}</span>
|
||||
) : (
|
||||
"(Nothing Selected)"
|
||||
)}
|
||||
</span>
|
||||
</div>
|
||||
<div className="part-tree">
|
||||
<TreeView model={model} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@observer
|
||||
export class TreeView extends React.Component<{ model: TreeViewModel }> {
|
||||
render() {
|
||||
const model = this.props.model;
|
||||
return (
|
||||
<div
|
||||
tabIndex={0}
|
||||
className="component-TreeView"
|
||||
onKeyDown={e => {
|
||||
if (e.keyCode === 18) {
|
||||
// alt
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
model.selectOnHover(true);
|
||||
}
|
||||
}}
|
||||
onKeyUp={e => {
|
||||
if (e.keyCode === 18) {
|
||||
// alt
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
model.selectOnHover(false);
|
||||
}
|
||||
}}
|
||||
>
|
||||
{model.root && (
|
||||
<>
|
||||
<svg className="part-svg">
|
||||
<TreeNodeSvgView model={model.root} />
|
||||
</svg>
|
||||
<div className="part-node">
|
||||
<TreeNodeView model={model.root} />
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@observer
|
||||
export class TreeNodeSvgView extends React.Component<{
|
||||
model: TreeNodeViewModel;
|
||||
}> {
|
||||
render() {
|
||||
const model = this.props.model;
|
||||
if (
|
||||
!model.expanderOffsetRect ||
|
||||
model.children.length === 0 ||
|
||||
model.expanded
|
||||
) {
|
||||
return <g />;
|
||||
}
|
||||
|
||||
function findLastExpanderOffset(
|
||||
predicate: (v: TreeNodeViewModel) => boolean
|
||||
): Rectangle | undefined {
|
||||
const all = model.children
|
||||
.filter(predicate)
|
||||
.map(c => c.expanderOffsetRect);
|
||||
const last = all[all.length - 1];
|
||||
return last;
|
||||
}
|
||||
|
||||
const lastOffset = findLastExpanderOffset(() => true);
|
||||
const selectedOffset = findLastExpanderOffset(
|
||||
v => v.isChildOrThisSelected
|
||||
);
|
||||
const markedOffset = findLastExpanderOffset(v => v.isChildOrThisMarked);
|
||||
|
||||
if (!lastOffset) {
|
||||
return <g />;
|
||||
}
|
||||
|
||||
const start = model.expanderOffsetRect.center.add({ y: 10 });
|
||||
|
||||
return (
|
||||
<g className="component-TreeNodeSvgView">
|
||||
{markedOffset && (
|
||||
<SvgLine
|
||||
className="path childOrThisMarked"
|
||||
start={start}
|
||||
end={new Point(start.x, markedOffset.center.y)}
|
||||
/>
|
||||
)}
|
||||
{selectedOffset && (
|
||||
<SvgLine
|
||||
className="path childOrThisSelected"
|
||||
start={start}
|
||||
end={new Point(start.x, selectedOffset.center.y)}
|
||||
/>
|
||||
)}
|
||||
<SvgLine
|
||||
className="path"
|
||||
start={start}
|
||||
end={new Point(start.x, lastOffset.center.y)}
|
||||
/>
|
||||
{model.children.map((c, idx) => {
|
||||
const end = c.expanderOffsetRect!.center.sub({ x: 5 });
|
||||
|
||||
return (
|
||||
<g key={idx}>
|
||||
{c.isChildOrThisMarked && (
|
||||
<SvgLine
|
||||
className={classNames(
|
||||
"path",
|
||||
"childOrThisMarked"
|
||||
)}
|
||||
start={new Point(start.x, end.y)}
|
||||
end={end}
|
||||
/>
|
||||
)}
|
||||
<SvgLine
|
||||
className={classNames(
|
||||
"path",
|
||||
c.isChildOrThisSelected &&
|
||||
"childOrThisSelected"
|
||||
)}
|
||||
start={new Point(start.x, end.y)}
|
||||
end={end}
|
||||
/>
|
||||
</g>
|
||||
);
|
||||
})}
|
||||
{model.children.map((c, idx) => (
|
||||
<TreeNodeSvgView key={idx} model={c} />
|
||||
))}
|
||||
</g>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@observer
|
||||
export class TreeNodeView extends React.Component<{
|
||||
model: TreeNodeViewModel;
|
||||
}> {
|
||||
@action.bound
|
||||
private clickHandler() {
|
||||
this.props.model.treeViewModel.select(this.props.model);
|
||||
}
|
||||
|
||||
private mouseEnterOrLeaveHandler(enter: boolean) {
|
||||
this.props.model.treeViewModel.hover(
|
||||
enter ? this.props.model : undefined
|
||||
);
|
||||
}
|
||||
|
||||
@observable
|
||||
private rootDiv: HTMLElement | null = null;
|
||||
@action.bound
|
||||
private setRootDiv(div: HTMLElement | null) {
|
||||
this.rootDiv = div;
|
||||
}
|
||||
|
||||
@disposeOnUnmount
|
||||
private readonly _autorun = autorun(() => {
|
||||
if (this.props.model.isMarked && this.rootDiv) {
|
||||
this.rootDiv.scrollIntoView();
|
||||
}
|
||||
});
|
||||
|
||||
public componentDidMount() {
|
||||
this.props.model.treeViewModel.updateOffsets();
|
||||
}
|
||||
|
||||
public componentDidUpdate() {
|
||||
this.props.model.treeViewModel.updateOffsets();
|
||||
}
|
||||
|
||||
public render(): JSX.Element {
|
||||
const model = this.props.model;
|
||||
const collapsed = model.expanded;
|
||||
|
||||
return (
|
||||
<div
|
||||
className={classNames(
|
||||
"component-TreeNodeView",
|
||||
model.expanded && "collapsed",
|
||||
model.isMarked && "selected",
|
||||
model.isSelected && "hovered",
|
||||
model.isChildOrThisSelected &&
|
||||
!model.isSelected &&
|
||||
"childHovered",
|
||||
model.isParentOrThisSelected &&
|
||||
!model.isSelected &&
|
||||
"parentHovered"
|
||||
)}
|
||||
ref={this.setRootDiv}
|
||||
onMouseOver={e => {
|
||||
this.mouseEnterOrLeaveHandler(true);
|
||||
e.stopPropagation();
|
||||
}}
|
||||
onMouseOut={e => {
|
||||
this.mouseEnterOrLeaveHandler(false);
|
||||
e.stopPropagation();
|
||||
}}
|
||||
>
|
||||
<div className="part-header">
|
||||
<div
|
||||
ref={model.setExpanderDiv}
|
||||
onClick={() => model.toggleExpanded()}
|
||||
className={classNames(
|
||||
"part-collapser",
|
||||
model.children.length > 0 &&
|
||||
"decorator-collapse-icon",
|
||||
!collapsed && "expanded"
|
||||
)}
|
||||
/>
|
||||
|
||||
<span className="part-text" onClick={this.clickHandler}>
|
||||
{model.id && (
|
||||
<>
|
||||
<span className="part-id">{model.id}</span>
|
||||
<span>: </span>
|
||||
</>
|
||||
)}
|
||||
<span className="part-name">{model.name}</span>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{model.children.length > 0 && !model.expanded && (
|
||||
<div className="part-children">
|
||||
{model.children.map((c, idx) => (
|
||||
<TreeNodeView model={c} key={idx} />
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
29
ui/src/Visualizers/TreeVisualizer/Visualizers.ts
Normal file
29
ui/src/Visualizers/TreeVisualizer/Visualizers.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
import { TreeViewModel, TreeNodeViewModel } from "./Views";
|
||||
import { TreeNodeData } from "@hediet/debug-visualizer-data-extraction";
|
||||
|
||||
export function createTreeViewModelFromTreeNodeData(
|
||||
root: TreeNodeData["root"]
|
||||
): TreeViewModel {
|
||||
const m = new TreeViewModel();
|
||||
m.root = recurse(root, m);
|
||||
return m;
|
||||
|
||||
function recurse(
|
||||
root: TreeNodeData["root"],
|
||||
viewModel: TreeViewModel
|
||||
): TreeNodeViewModel {
|
||||
const children: TreeNodeViewModel[] = root.children.map(c =>
|
||||
recurse(c, viewModel)
|
||||
);
|
||||
const model = new TreeNodeViewModel(
|
||||
viewModel,
|
||||
root.id,
|
||||
root.name,
|
||||
children
|
||||
);
|
||||
for (const c of children) {
|
||||
c.parent = model;
|
||||
}
|
||||
return model;
|
||||
}
|
||||
}
|
||||
1
ui/src/Visualizers/TreeVisualizer/sample-input.json
Normal file
1
ui/src/Visualizers/TreeVisualizer/sample-input.json
Normal file
File diff suppressed because one or more lines are too long
100
ui/src/Visualizers/TreeVisualizer/style.scss
Normal file
100
ui/src/Visualizers/TreeVisualizer/style.scss
Normal file
@@ -0,0 +1,100 @@
|
||||
.component-TreeView {
|
||||
overflow: auto;
|
||||
scroll-behavior: smooth;
|
||||
height: 100%;
|
||||
position: relative;
|
||||
display: grid;
|
||||
|
||||
& > .part-svg,
|
||||
& > .part-node {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
grid-column: 1;
|
||||
grid-row: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.component-TreeNodeSvgView {
|
||||
.path {
|
||||
stroke: gray;
|
||||
stroke-dasharray: 1;
|
||||
|
||||
&.childOrThisMarked {
|
||||
stroke: orange;
|
||||
stroke-width: 5;
|
||||
stroke-dasharray: 0;
|
||||
stroke-linecap: round;
|
||||
}
|
||||
|
||||
&.childOrThisSelected {
|
||||
stroke: black;
|
||||
stroke-width: 2;
|
||||
stroke-dasharray: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.component-TreeNodeView {
|
||||
font-family: -apple-system, BlinkMacSystemFont, Segoe WPC, Segoe UI,
|
||||
HelveticaNeue-Light, Ubuntu, Droid Sans, sans-serif;
|
||||
|
||||
.part-header {
|
||||
padding: 2px;
|
||||
padding-left: 5px;
|
||||
margin: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.part-collapser {
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
margin-right: 6px;
|
||||
}
|
||||
|
||||
.part-text {
|
||||
cursor: pointer;
|
||||
.part-id {
|
||||
color: blue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.part-children {
|
||||
margin-left: 0px;
|
||||
margin-top: 0px;
|
||||
|
||||
padding: 5px;
|
||||
padding-left: 15px;
|
||||
}
|
||||
|
||||
&.selected {
|
||||
& > .part-header {
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
&.hovered {
|
||||
& > .part-header > span {
|
||||
font-weight: bold;
|
||||
}
|
||||
& > .part-children {
|
||||
background: transparentize(gray, 0.8);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.component-TreeWithPathView {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
|
||||
.part-path {
|
||||
overflow: hidden;
|
||||
white-space: pre;
|
||||
padding: 5px 10px;
|
||||
border-bottom: solid gray 1px;
|
||||
}
|
||||
.part-tree {
|
||||
overflow: auto;
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
29
ui/src/index.tsx
Normal file
29
ui/src/index.tsx
Normal file
@@ -0,0 +1,29 @@
|
||||
import * as React from "react";
|
||||
import * as ReactDOM from "react-dom";
|
||||
import "./style.scss";
|
||||
import { Model } from "./Model";
|
||||
import Components = require("./Components");
|
||||
|
||||
const model = new Model();
|
||||
|
||||
function render(target: HTMLDivElement) {
|
||||
const c = require("./Components") as typeof Components;
|
||||
ReactDOM.render(<c.GUI model={model} />, target);
|
||||
}
|
||||
|
||||
const target = document.createElement("div");
|
||||
target.className = "target";
|
||||
document.body.appendChild(target);
|
||||
|
||||
render(target);
|
||||
|
||||
declare var module: {
|
||||
hot?: { accept: (componentName: string, callback: () => void) => void };
|
||||
};
|
||||
declare var require: (name: string) => any;
|
||||
|
||||
if (module.hot) {
|
||||
module.hot.accept("./Components", () => {
|
||||
render(target);
|
||||
});
|
||||
}
|
||||
197
ui/src/style.scss
Normal file
197
ui/src/style.scss
Normal file
@@ -0,0 +1,197 @@
|
||||
@import "./vscode-light.scss";
|
||||
|
||||
@import "~normalize.css";
|
||||
@import "~@blueprintjs/core/lib/css/blueprint.css";
|
||||
@import "~@blueprintjs/icons/lib/css/blueprint-icons.css";
|
||||
|
||||
body {
|
||||
font-family: "Segoe UI", Tahoma, Geneva, Verdana, sans-serif;
|
||||
margin: 0px;
|
||||
padding: 0px;
|
||||
border: 0px;
|
||||
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
html,
|
||||
body,
|
||||
.target {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.component-Visualizer {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.component-NoData {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
svg {
|
||||
float: left; // to prevent scrollbar.
|
||||
// I don't know why it works.
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
rect {
|
||||
fill: white;
|
||||
}
|
||||
|
||||
line {
|
||||
stroke: gray;
|
||||
}
|
||||
|
||||
text {
|
||||
stroke: black;
|
||||
stroke-width: 0px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.component-GUI {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
|
||||
& > .part-header {
|
||||
padding: 6px 20px 6px 0;
|
||||
background-color: var(--vscode-sideBar-background);
|
||||
|
||||
//border: solid 8px white;
|
||||
//border-top: solid 1px var(--vscode-button-background);
|
||||
// rgba(128, 128, 128, 0.15)
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
|
||||
.part-header-content {
|
||||
flex: 1;
|
||||
|
||||
transition: flex 0.3s ease-out;
|
||||
|
||||
.part-header-main,
|
||||
.part-header-options {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.part-expression-input {
|
||||
//color: rgb(155, 70, 176);
|
||||
font-family: var(--vscode-editor-font-family);
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.part-header-main {
|
||||
.part-icon {
|
||||
margin-left: 4px;
|
||||
}
|
||||
}
|
||||
|
||||
.part-header-options {
|
||||
.part-select {
|
||||
min-width: 90px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.part-visualizer {
|
||||
//padding: 10px 20px;
|
||||
flex: 1;
|
||||
min-height: 0px;
|
||||
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.decorator-collapse-icon {
|
||||
background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16'%3E%3Cpath fill='%23646465' d='M6 4v8l4-4-4-4zm1 2.414L8.586 8 7 9.586V6.414z'/%3E%3C/svg%3E");
|
||||
|
||||
background-position: 50%;
|
||||
background-position-x: 50%;
|
||||
background-position-y: center;
|
||||
background-repeat: no-repeat;
|
||||
|
||||
&.expanded {
|
||||
background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16'%3E%3Cpath fill='%23646465' d='M11 10.07H5.344L11 4.414v5.656z'/%3E%3C/svg%3E");
|
||||
}
|
||||
}
|
||||
|
||||
.component-ExpandButton {
|
||||
background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16'%3E%3Cpath fill='%23646465' d='M6 4v8l4-4-4-4zm1 2.414L8.586 8 7 9.586V6.414z'/%3E%3C/svg%3E");
|
||||
|
||||
background-position: 50%;
|
||||
background-position-x: 50%;
|
||||
background-position-y: center;
|
||||
background-repeat: no-repeat;
|
||||
|
||||
width: 15px;
|
||||
margin: -6px 6px -6px 0px;
|
||||
|
||||
&.expanded {
|
||||
background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16'%3E%3Cpath fill='%23646465' d='M11 10.07H5.344L11 4.414v5.656z'/%3E%3C/svg%3E");
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: var(--vscode-titleBar-activeBackground);
|
||||
}
|
||||
}
|
||||
|
||||
.component-NamedSelect {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
|
||||
.part-Select {
|
||||
min-width: 100px;
|
||||
}
|
||||
}
|
||||
|
||||
.component-Select {
|
||||
.part-button {
|
||||
background-color: var(--vscode-button-background);
|
||||
color: white;
|
||||
padding: 4px 10px;
|
||||
|
||||
width: 100%;
|
||||
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
&:hover,
|
||||
&:focus {
|
||||
background-color: var(--vscode-button-hoverBackground);
|
||||
}
|
||||
}
|
||||
|
||||
.part-content {
|
||||
background-color: var(--vscode-sideBar-background);
|
||||
|
||||
div {
|
||||
color: black;
|
||||
padding: 12px 16px;
|
||||
text-decoration: none;
|
||||
display: block;
|
||||
|
||||
&:hover {
|
||||
background-color: #ddd;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.bp3-popover-wrapper {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.bp3-popover-target {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.svgViewer {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
307
ui/src/vscode-light.scss
Normal file
307
ui/src/vscode-light.scss
Normal file
@@ -0,0 +1,307 @@
|
||||
:root {
|
||||
--vscode-activityBar-background: #2c2c2c;
|
||||
--vscode-activityBar-dropBackground: rgba(255, 255, 255, 0.12);
|
||||
--vscode-activityBar-foreground: #ffffff;
|
||||
--vscode-activityBar-inactiveForeground: rgba(255, 255, 255, 0.6);
|
||||
--vscode-activityBarBadge-background: #007acc;
|
||||
--vscode-activityBarBadge-foreground: #ffffff;
|
||||
--vscode-badge-background: #c4c4c4;
|
||||
--vscode-badge-foreground: #333333;
|
||||
--vscode-breadcrumb-activeSelectionForeground: #4e4e4e;
|
||||
--vscode-breadcrumb-background: #ffffff;
|
||||
--vscode-breadcrumb-focusForeground: #4e4e4e;
|
||||
--vscode-breadcrumb-foreground: rgba(97, 97, 97, 0.8);
|
||||
--vscode-breadcrumbPicker-background: #f3f3f3;
|
||||
--vscode-button-background: #007acc;
|
||||
--vscode-button-foreground: #ffffff;
|
||||
--vscode-button-hoverBackground: #0062a3;
|
||||
--vscode-debugExceptionWidget-background: #f1dfde;
|
||||
--vscode-debugExceptionWidget-border: #a31515;
|
||||
--vscode-debugToolBar-background: #f3f3f3;
|
||||
--vscode-descriptionForeground: #717171;
|
||||
--vscode-diffEditor-insertedTextBackground: rgba(155, 185, 85, 0.2);
|
||||
--vscode-diffEditor-removedTextBackground: rgba(255, 0, 0, 0.2);
|
||||
--vscode-dropdown-background: #ffffff;
|
||||
--vscode-dropdown-border: #cecece;
|
||||
--vscode-editor-background: #ffffff;
|
||||
--vscode-editor-findMatchBackground: #a8ac94;
|
||||
--vscode-editor-findMatchHighlightBackground: rgba(234, 92, 0, 0.33);
|
||||
--vscode-editor-findRangeHighlightBackground: rgba(180, 180, 180, 0.3);
|
||||
--vscode-editor-focusedStackFrameHighlightBackground: rgba(
|
||||
206,
|
||||
231,
|
||||
206,
|
||||
0.45
|
||||
);
|
||||
--vscode-editor-font-family: Consolas, "Courier New", monospace;
|
||||
--vscode-editor-font-size: 14;
|
||||
--vscode-editor-font-weight: normal;
|
||||
--vscode-editor-foreground: #000000;
|
||||
--vscode-editor-hoverHighlightBackground: rgba(173, 214, 255, 0.15);
|
||||
--vscode-editor-inactiveSelectionBackground: #e5ebf1;
|
||||
--vscode-editor-lineHighlightBorder: #eeeeee;
|
||||
--vscode-editor-rangeHighlightBackground: rgba(253, 255, 0, 0.2);
|
||||
--vscode-editor-selectionBackground: #add6ff;
|
||||
--vscode-editor-selectionHighlightBackground: rgba(173, 214, 255, 0.5);
|
||||
--vscode-editor-snippetFinalTabstopHighlightBorder: rgba(10, 50, 100, 0.5);
|
||||
--vscode-editor-snippetTabstopHighlightBackground: rgba(10, 50, 100, 0.2);
|
||||
--vscode-editor-stackFrameHighlightBackground: rgba(255, 255, 102, 0.45);
|
||||
--vscode-editor-wordHighlightBackground: rgba(87, 87, 87, 0.25);
|
||||
--vscode-editor-wordHighlightStrongBackground: rgba(14, 99, 156, 0.25);
|
||||
--vscode-editorActiveLineNumber-foreground: #0b216f;
|
||||
--vscode-editorBracketMatch-background: rgba(0, 100, 0, 0.1);
|
||||
--vscode-editorBracketMatch-border: #b9b9b9;
|
||||
--vscode-editorCodeLens-foreground: #999999;
|
||||
--vscode-editorCursor-foreground: #000000;
|
||||
--vscode-editorError-foreground: #e51400;
|
||||
--vscode-editorGroup-border: #e7e7e7;
|
||||
--vscode-editorGroup-dropBackground: rgba(38, 119, 203, 0.18);
|
||||
--vscode-editorGroupHeader-noTabsBackground: #ffffff;
|
||||
--vscode-editorGroupHeader-tabsBackground: #f3f3f3;
|
||||
--vscode-editorGutter-addedBackground: #81b88b;
|
||||
--vscode-editorGutter-background: #ffffff;
|
||||
--vscode-editorGutter-commentRangeForeground: #c5c5c5;
|
||||
--vscode-editorGutter-deletedBackground: #ca4b51;
|
||||
--vscode-editorGutter-modifiedBackground: #66afe0;
|
||||
--vscode-editorHint-foreground: #6c6c6c;
|
||||
--vscode-editorHoverWidget-background: #f3f3f3;
|
||||
--vscode-editorHoverWidget-border: #c8c8c8;
|
||||
--vscode-editorHoverWidget-statusBarBackground: #e7e7e7;
|
||||
--vscode-editorIndentGuide-activeBackground: #939393;
|
||||
--vscode-editorIndentGuide-background: #d3d3d3;
|
||||
--vscode-editorInfo-foreground: #008000;
|
||||
--vscode-editorLineNumber-activeForeground: #0b216f;
|
||||
--vscode-editorLineNumber-foreground: #237893;
|
||||
--vscode-editorLink-activeForeground: #0000ff;
|
||||
--vscode-editorMarkerNavigation-background: #ffffff;
|
||||
--vscode-editorMarkerNavigationError-background: #e51400;
|
||||
--vscode-editorMarkerNavigationInfo-background: #008000;
|
||||
--vscode-editorMarkerNavigationWarning-background: #e9a700;
|
||||
--vscode-editorOverviewRuler-addedForeground: rgba(0, 122, 204, 0.6);
|
||||
--vscode-editorOverviewRuler-border: rgba(127, 127, 127, 0.3);
|
||||
--vscode-editorOverviewRuler-bracketMatchForeground: #a0a0a0;
|
||||
--vscode-editorOverviewRuler-commonContentForeground: rgba(96, 96, 96, 0.4);
|
||||
--vscode-editorOverviewRuler-currentContentForeground: rgba(
|
||||
64,
|
||||
200,
|
||||
174,
|
||||
0.5
|
||||
);
|
||||
--vscode-editorOverviewRuler-deletedForeground: rgba(0, 122, 204, 0.6);
|
||||
--vscode-editorOverviewRuler-errorForeground: rgba(255, 18, 18, 0.7);
|
||||
--vscode-editorOverviewRuler-findMatchForeground: rgba(209, 134, 22, 0.49);
|
||||
--vscode-editorOverviewRuler-incomingContentForeground: rgba(
|
||||
64,
|
||||
166,
|
||||
255,
|
||||
0.5
|
||||
);
|
||||
--vscode-editorOverviewRuler-infoForeground: #008000;
|
||||
--vscode-editorOverviewRuler-modifiedForeground: rgba(0, 122, 204, 0.6);
|
||||
--vscode-editorOverviewRuler-rangeHighlightForeground: rgba(
|
||||
0,
|
||||
122,
|
||||
204,
|
||||
0.6
|
||||
);
|
||||
--vscode-editorOverviewRuler-selectionHighlightForeground: rgba(
|
||||
160,
|
||||
160,
|
||||
160,
|
||||
0.8
|
||||
);
|
||||
--vscode-editorOverviewRuler-warningForeground: #e9a700;
|
||||
--vscode-editorOverviewRuler-wordHighlightForeground: rgba(
|
||||
160,
|
||||
160,
|
||||
160,
|
||||
0.8
|
||||
);
|
||||
--vscode-editorOverviewRuler-wordHighlightStrongForeground: rgba(
|
||||
192,
|
||||
160,
|
||||
192,
|
||||
0.8
|
||||
);
|
||||
--vscode-editorPane-background: #ffffff;
|
||||
--vscode-editorRuler-foreground: #d3d3d3;
|
||||
--vscode-editorSuggestWidget-background: #f3f3f3;
|
||||
--vscode-editorSuggestWidget-border: #c8c8c8;
|
||||
--vscode-editorSuggestWidget-foreground: #000000;
|
||||
--vscode-editorSuggestWidget-highlightForeground: #0066bf;
|
||||
--vscode-editorSuggestWidget-selectedBackground: #d6ebff;
|
||||
--vscode-editorUnnecessaryCode-opacity: rgba(0, 0, 0, 0.47);
|
||||
--vscode-editorWarning-foreground: #e9a700;
|
||||
--vscode-editorWhitespace-foreground: rgba(51, 51, 51, 0.2);
|
||||
--vscode-editorWidget-background: #f3f3f3;
|
||||
--vscode-editorWidget-border: #c8c8c8;
|
||||
--vscode-errorForeground: #a1260d;
|
||||
--vscode-extensionBadge-remoteBackground: #007acc;
|
||||
--vscode-extensionBadge-remoteForeground: #ffffff;
|
||||
--vscode-extensionButton-prominentBackground: #327e36;
|
||||
--vscode-extensionButton-prominentForeground: #ffffff;
|
||||
--vscode-extensionButton-prominentHoverBackground: #28632b;
|
||||
--vscode-focusBorder: rgba(0, 122, 204, 0.4);
|
||||
--vscode-font-family: -apple-system, BlinkMacSystemFont, "Segoe WPC",
|
||||
"Segoe UI", "Ubuntu", "Droid Sans", ans-serif;
|
||||
--vscode-font-size: 13px;
|
||||
--vscode-font-weight: normal;
|
||||
--vscode-foreground: #616161;
|
||||
--vscode-gitDecoration-addedResourceForeground: #587c0c;
|
||||
--vscode-gitDecoration-conflictingResourceForeground: #6c6cc4;
|
||||
--vscode-gitDecoration-deletedResourceForeground: #ad0707;
|
||||
--vscode-gitDecoration-ignoredResourceForeground: #8e8e90;
|
||||
--vscode-gitDecoration-modifiedResourceForeground: #895503;
|
||||
--vscode-gitDecoration-submoduleResourceForeground: #1258a7;
|
||||
--vscode-gitDecoration-untrackedResourceForeground: #007100;
|
||||
--vscode-gitlens-gutterBackgroundColor: rgba(0, 0, 0, 0.05);
|
||||
--vscode-gitlens-gutterForegroundColor: #747474;
|
||||
--vscode-gitlens-gutterUncommittedForegroundColor: rgba(0, 188, 242, 0.6);
|
||||
--vscode-gitlens-lineHighlightBackgroundColor: rgba(0, 188, 242, 0.2);
|
||||
--vscode-gitlens-lineHighlightOverviewRulerColor: rgba(0, 188, 242, 0.6);
|
||||
--vscode-gitlens-trailingLineBackgroundColor: rgba(0, 0, 0, 0);
|
||||
--vscode-gitlens-trailingLineForegroundColor: rgba(153, 153, 153, 0.35);
|
||||
--vscode-input-background: #ffffff;
|
||||
--vscode-input-foreground: #616161;
|
||||
--vscode-input-placeholderForeground: #767676;
|
||||
--vscode-inputOption-activeBorder: #007acc;
|
||||
--vscode-inputValidation-errorBackground: #f2dede;
|
||||
--vscode-inputValidation-errorBorder: #be1100;
|
||||
--vscode-inputValidation-infoBackground: #d6ecf2;
|
||||
--vscode-inputValidation-infoBorder: #007acc;
|
||||
--vscode-inputValidation-warningBackground: #f6f5d2;
|
||||
--vscode-inputValidation-warningBorder: #b89500;
|
||||
--vscode-list-activeSelectionBackground: #0074e8;
|
||||
--vscode-list-activeSelectionForeground: #ffffff;
|
||||
--vscode-list-dropBackground: #d6ebff;
|
||||
--vscode-list-errorForeground: #b01011;
|
||||
--vscode-list-focusBackground: #d6ebff;
|
||||
--vscode-list-highlightForeground: #0066bf;
|
||||
--vscode-list-hoverBackground: #e8e8e8;
|
||||
--vscode-list-inactiveSelectionBackground: #e4e6f1;
|
||||
--vscode-list-invalidItemForeground: #b89500;
|
||||
--vscode-list-warningForeground: #855f00;
|
||||
--vscode-listFilterWidget-background: #efc1ad;
|
||||
--vscode-listFilterWidget-noMatchesOutline: #be1100;
|
||||
--vscode-listFilterWidget-outline: rgba(0, 0, 0, 0);
|
||||
--vscode-menu-background: #ffffff;
|
||||
--vscode-menu-foreground: #616161;
|
||||
--vscode-menu-selectionBackground: #0074e8;
|
||||
--vscode-menu-selectionForeground: #ffffff;
|
||||
--vscode-menu-separatorBackground: #888888;
|
||||
--vscode-menubar-selectionBackground: rgba(0, 0, 0, 0.1);
|
||||
--vscode-menubar-selectionForeground: #333333;
|
||||
--vscode-merge-commonContentBackground: rgba(96, 96, 96, 0.16);
|
||||
--vscode-merge-commonHeaderBackground: rgba(96, 96, 96, 0.4);
|
||||
--vscode-merge-currentContentBackground: rgba(64, 200, 174, 0.2);
|
||||
--vscode-merge-currentHeaderBackground: rgba(64, 200, 174, 0.5);
|
||||
--vscode-merge-incomingContentBackground: rgba(64, 166, 255, 0.2);
|
||||
--vscode-merge-incomingHeaderBackground: rgba(64, 166, 255, 0.5);
|
||||
--vscode-notificationCenterHeader-background: #e7e7e7;
|
||||
--vscode-notificationLink-foreground: #006ab1;
|
||||
--vscode-notifications-background: #f3f3f3;
|
||||
--vscode-notifications-border: #e7e7e7;
|
||||
--vscode-panel-background: #ffffff;
|
||||
--vscode-panel-border: rgba(128, 128, 128, 0.35);
|
||||
--vscode-panel-dropBackground: rgba(38, 119, 203, 0.18);
|
||||
--vscode-panelInput-border: #dddddd;
|
||||
--vscode-panelTitle-activeBorder: rgba(128, 128, 128, 0.35);
|
||||
--vscode-panelTitle-activeForeground: #424242;
|
||||
--vscode-panelTitle-inactiveForeground: rgba(66, 66, 66, 0.75);
|
||||
--vscode-peekView-border: #007acc;
|
||||
--vscode-peekViewEditor-background: #f2f8fc;
|
||||
--vscode-peekViewEditor-matchHighlightBackground: rgba(245, 216, 2, 0.87);
|
||||
--vscode-peekViewEditorGutter-background: #f2f8fc;
|
||||
--vscode-peekViewResult-background: #f3f3f3;
|
||||
--vscode-peekViewResult-fileForeground: #1e1e1e;
|
||||
--vscode-peekViewResult-lineForeground: #646465;
|
||||
--vscode-peekViewResult-matchHighlightBackground: rgba(234, 92, 0, 0.3);
|
||||
--vscode-peekViewResult-selectionBackground: rgba(51, 153, 255, 0.2);
|
||||
--vscode-peekViewResult-selectionForeground: #6c6c6c;
|
||||
--vscode-peekViewTitle-background: #ffffff;
|
||||
--vscode-peekViewTitleDescription-foreground: rgba(108, 108, 108, 0.7);
|
||||
--vscode-peekViewTitleLabel-foreground: #333333;
|
||||
--vscode-pickerGroup-border: #cccedb;
|
||||
--vscode-pickerGroup-foreground: #0066bf;
|
||||
--vscode-progressBar-background: #0e70c0;
|
||||
--vscode-quickInput-background: #f3f3f3;
|
||||
--vscode-scrollbar-shadow: #dddddd;
|
||||
--vscode-scrollbarSlider-activeBackground: rgba(0, 0, 0, 0.6);
|
||||
--vscode-scrollbarSlider-background: rgba(100, 100, 100, 0.4);
|
||||
--vscode-scrollbarSlider-hoverBackground: rgba(100, 100, 100, 0.7);
|
||||
--vscode-settings-checkboxBackground: #ffffff;
|
||||
--vscode-settings-checkboxBorder: #cecece;
|
||||
--vscode-settings-dropdownBackground: #ffffff;
|
||||
--vscode-settings-dropdownBorder: #cecece;
|
||||
--vscode-settings-dropdownListBorder: #c8c8c8;
|
||||
--vscode-settings-headerForeground: #444444;
|
||||
--vscode-settings-modifiedItemIndicator: #66afe0;
|
||||
--vscode-settings-numberInputBackground: #ffffff;
|
||||
--vscode-settings-numberInputBorder: #cecece;
|
||||
--vscode-settings-numberInputForeground: #616161;
|
||||
--vscode-settings-textInputBackground: #ffffff;
|
||||
--vscode-settings-textInputBorder: #cecece;
|
||||
--vscode-settings-textInputForeground: #616161;
|
||||
--vscode-sideBar-background: #f3f3f3;
|
||||
--vscode-sideBar-dropBackground: rgba(0, 0, 0, 0.1);
|
||||
--vscode-sideBarSectionHeader-background: rgba(128, 128, 128, 0.2);
|
||||
--vscode-sideBarTitle-foreground: #6f6f6f;
|
||||
--vscode-statusBar-background: #007acc;
|
||||
--vscode-statusBar-debuggingBackground: #cc6633;
|
||||
--vscode-statusBar-debuggingForeground: #ffffff;
|
||||
--vscode-statusBar-foreground: #ffffff;
|
||||
--vscode-statusBar-noFolderBackground: #68217a;
|
||||
--vscode-statusBar-noFolderForeground: #ffffff;
|
||||
--vscode-statusBarItem-activeBackground: rgba(255, 255, 255, 0.18);
|
||||
--vscode-statusBarItem-hoverBackground: rgba(255, 255, 255, 0.12);
|
||||
--vscode-statusBarItem-prominentBackground: rgba(0, 0, 0, 0.5);
|
||||
--vscode-statusBarItem-prominentForeground: #ffffff;
|
||||
--vscode-statusBarItem-prominentHoverBackground: rgba(0, 0, 0, 0.3);
|
||||
--vscode-statusBarItem-remoteBackground: #16825d;
|
||||
--vscode-statusBarItem-remoteForeground: #ffffff;
|
||||
--vscode-tab-activeBackground: #ffffff;
|
||||
--vscode-tab-activeForeground: #333333;
|
||||
--vscode-tab-activeModifiedBorder: #33aaee;
|
||||
--vscode-tab-border: #f3f3f3;
|
||||
--vscode-tab-inactiveBackground: #ececec;
|
||||
--vscode-tab-inactiveForeground: rgba(51, 51, 51, 0.7);
|
||||
--vscode-tab-inactiveModifiedBorder: rgba(51, 170, 238, 0.5);
|
||||
--vscode-tab-unfocusedActiveBackground: #ffffff;
|
||||
--vscode-tab-unfocusedActiveForeground: rgba(51, 51, 51, 0.7);
|
||||
--vscode-tab-unfocusedActiveModifiedBorder: rgba(51, 170, 238, 0.7);
|
||||
--vscode-tab-unfocusedInactiveForeground: rgba(51, 51, 51, 0.35);
|
||||
--vscode-tab-unfocusedInactiveModifiedBorder: rgba(51, 170, 238, 0.25);
|
||||
--vscode-terminal-ansiBlack: #000000;
|
||||
--vscode-terminal-ansiBlue: #0451a5;
|
||||
--vscode-terminal-ansiBrightBlack: #666666;
|
||||
--vscode-terminal-ansiBrightBlue: #0451a5;
|
||||
--vscode-terminal-ansiBrightCyan: #0598bc;
|
||||
--vscode-terminal-ansiBrightGreen: #14ce14;
|
||||
--vscode-terminal-ansiBrightMagenta: #bc05bc;
|
||||
--vscode-terminal-ansiBrightRed: #cd3131;
|
||||
--vscode-terminal-ansiBrightWhite: #a5a5a5;
|
||||
--vscode-terminal-ansiBrightYellow: #b5ba00;
|
||||
--vscode-terminal-ansiCyan: #0598bc;
|
||||
--vscode-terminal-ansiGreen: #00bc00;
|
||||
--vscode-terminal-ansiMagenta: #bc05bc;
|
||||
--vscode-terminal-ansiRed: #cd3131;
|
||||
--vscode-terminal-ansiWhite: #555555;
|
||||
--vscode-terminal-ansiYellow: #949800;
|
||||
--vscode-terminal-background: #ffffff;
|
||||
--vscode-terminal-border: rgba(128, 128, 128, 0.35);
|
||||
--vscode-terminal-foreground: #333333;
|
||||
--vscode-terminal-selectionBackground: rgba(0, 0, 0, 0.25);
|
||||
--vscode-textBlockQuote-background: rgba(127, 127, 127, 0.1);
|
||||
--vscode-textBlockQuote-border: rgba(0, 122, 204, 0.5);
|
||||
--vscode-textCodeBlock-background: rgba(220, 220, 220, 0.4);
|
||||
--vscode-textLink-activeForeground: #006ab1;
|
||||
--vscode-textLink-foreground: #006ab1;
|
||||
--vscode-textPreformat-foreground: #a31515;
|
||||
--vscode-textSeparator-foreground: rgba(0, 0, 0, 0.18);
|
||||
--vscode-titleBar-activeBackground: #dddddd;
|
||||
--vscode-titleBar-activeForeground: #333333;
|
||||
--vscode-titleBar-inactiveBackground: rgba(221, 221, 221, 0.6);
|
||||
--vscode-titleBar-inactiveForeground: rgba(51, 51, 51, 0.6);
|
||||
--vscode-tree-indentGuidesStroke: #a9a9a9;
|
||||
--vscode-widget-shadow: #a8a8a8;
|
||||
}
|
||||
@@ -1,16 +1,16 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "esnext",
|
||||
"module": "commonjs",
|
||||
"strict": true,
|
||||
"outDir": "dist",
|
||||
"skipLibCheck": true,
|
||||
"rootDir": "./src",
|
||||
"resolveJsonModule": true,
|
||||
"newLine": "LF",
|
||||
"sourceMap": true,
|
||||
"jsx": "react",
|
||||
"experimentalDecorators": true
|
||||
},
|
||||
"include": ["src/**/*"]
|
||||
}
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "esnext",
|
||||
"module": "commonjs",
|
||||
"strict": true,
|
||||
"outDir": "dist",
|
||||
"skipLibCheck": true,
|
||||
"rootDir": "./src",
|
||||
"resolveJsonModule": true,
|
||||
"newLine": "LF",
|
||||
"sourceMap": true,
|
||||
"jsx": "react",
|
||||
"experimentalDecorators": true
|
||||
},
|
||||
"include": ["src/**/*"]
|
||||
}
|
||||
2
ui/webpack.config.js
Normal file
2
ui/webpack.config.js
Normal file
@@ -0,0 +1,2 @@
|
||||
require("ts-node").register();
|
||||
module.exports = require("./webpack.config.ts");
|
||||
40
ui/webpack.config.ts
Normal file
40
ui/webpack.config.ts
Normal file
@@ -0,0 +1,40 @@
|
||||
import * as webpack from "webpack";
|
||||
import path = require("path");
|
||||
import HtmlWebpackPlugin = require("html-webpack-plugin");
|
||||
import MonacoWebpackPlugin = require("monaco-editor-webpack-plugin");
|
||||
import ForkTsCheckerWebpackPlugin = require("fork-ts-checker-webpack-plugin");
|
||||
|
||||
const r = (file: string) => path.resolve(__dirname, file);
|
||||
|
||||
module.exports = {
|
||||
entry: [r("src/index.tsx")],
|
||||
output: {
|
||||
path: r("dist"),
|
||||
filename: "[name]-[hash].js",
|
||||
chunkFilename: "[name]-[hash].js",
|
||||
},
|
||||
resolve: {
|
||||
extensions: [".webpack.js", ".web.js", ".ts", ".tsx", ".js"],
|
||||
},
|
||||
devtool: "source-map",
|
||||
module: {
|
||||
rules: [
|
||||
{ test: /\.css$/, loader: "style-loader!css-loader" },
|
||||
{ test: /\.scss$/, loader: "style-loader!css-loader!sass-loader" },
|
||||
{
|
||||
test: /\.(jpe?g|png|gif|eot|ttf|svg|woff|woff2|md)$/i,
|
||||
loader: "file-loader",
|
||||
},
|
||||
{
|
||||
test: /\.tsx?$/,
|
||||
loader: "ts-loader",
|
||||
options: { transpileOnly: true },
|
||||
},
|
||||
],
|
||||
},
|
||||
plugins: [
|
||||
new HtmlWebpackPlugin(),
|
||||
new MonacoWebpackPlugin(),
|
||||
new ForkTsCheckerWebpackPlugin(),
|
||||
],
|
||||
} as webpack.Configuration;
|
||||
File diff suppressed because it is too large
Load Diff
8078
webview/package-lock.json
generated
8078
webview/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -1,47 +0,0 @@
|
||||
{
|
||||
"name": "template",
|
||||
"version": "0.1.0",
|
||||
"scripts": {
|
||||
"dev": "webpack-dev-server -d --hot --inline",
|
||||
"build": "webpack"
|
||||
},
|
||||
"dependencies": {
|
||||
"@hediet/typed-json-rpc": "^0.1.0",
|
||||
"@hediet/typed-json-rpc-websocket": "^0.1.0",
|
||||
"classnames": "^2.2.6",
|
||||
"mobx": "^5.7.0",
|
||||
"mobx-react": "^5.4.2",
|
||||
"react": "^16.6.3",
|
||||
"react-dom": "^16.6.3",
|
||||
"react-measure": "2.1.3",
|
||||
"react-svg-pan-zoom": "^2.18.0",
|
||||
"svg-inline-react": "^3.1.0",
|
||||
"viz.js": "^2.1.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/classnames": "^2.2.6",
|
||||
"@types/react": "^16.7.13",
|
||||
"@types/react-dom": "^16.0.11",
|
||||
"@types/react-measure": "^2.0.4",
|
||||
"@types/react-svg-pan-zoom": "^2.5.6",
|
||||
"clean-webpack-plugin": "^1.0.0",
|
||||
"copy-webpack-plugin": "^4.6.0",
|
||||
"css-loader": "^2.0.0",
|
||||
"file-loader": "^2.0.0",
|
||||
"git-revision-webpack-plugin": "^3.0.3",
|
||||
"html-webpack-plugin": "^3.2.0",
|
||||
"json-loader": "^0.5.7",
|
||||
"mini-css-extract-plugin": "^0.5.0",
|
||||
"node-sass": "^4.11.0",
|
||||
"raw-loader": "^1.0.0",
|
||||
"sass-loader": "^7.1.0",
|
||||
"source-map-loader": "^0.2.4",
|
||||
"style-loader": "^0.23.1",
|
||||
"ts-loader": "^5.3.1",
|
||||
"typescript": "^3.2.2",
|
||||
"webpack": "^4.27.1",
|
||||
"webpack-cli": "^3.1.2",
|
||||
"webpack-dev-server": "^3.1.10",
|
||||
"webpack-split-by-path": "^2.0.0"
|
||||
}
|
||||
}
|
||||
@@ -1,257 +0,0 @@
|
||||
import { observer } from "mobx-react";
|
||||
import * as classNames from "classnames";
|
||||
import * as React from "react";
|
||||
import * as ReactDOM from "react-dom";
|
||||
import { ReactSVGPanZoom, Tool } from "react-svg-pan-zoom";
|
||||
import { computed, observable, runInAction, action } from "mobx";
|
||||
import Measure from "react-measure";
|
||||
import { Model, ExpressionInputModel } from "./Model";
|
||||
import { GraphData, DotGraphViewer, DotViewer } from "./DotGraphViewer";
|
||||
|
||||
const ENTER_KEYCODE = 13;
|
||||
const ESCAPE = 27;
|
||||
|
||||
@observer
|
||||
class ExpressionInput extends React.Component<{
|
||||
rootModel: Model;
|
||||
model: ExpressionInputModel;
|
||||
}> {
|
||||
private ref: HTMLInputElement | null;
|
||||
private readonly inputTextChanged = (
|
||||
event: React.ChangeEvent<HTMLInputElement>
|
||||
) => {
|
||||
this.props.model.setEditedExpression(event.target.value);
|
||||
};
|
||||
|
||||
private readonly setRef = (ref: HTMLInputElement | null) => {
|
||||
if (ref) {
|
||||
ref.select();
|
||||
}
|
||||
};
|
||||
|
||||
private readonly onInputKeyDown = (e: React.KeyboardEvent) => {
|
||||
if (e.keyCode === ENTER_KEYCODE) {
|
||||
e.preventDefault();
|
||||
this.props.model.finishEditing();
|
||||
} else if (e.keyCode === ESCAPE) {
|
||||
e.preventDefault();
|
||||
this.props.model.abortEditing();
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
const model = this.props.model;
|
||||
if (model.editedExpression !== null) {
|
||||
return (
|
||||
<input
|
||||
ref={this.setRef}
|
||||
className="expression-input"
|
||||
value={model.editedExpression}
|
||||
onChange={this.inputTextChanged}
|
||||
onBlur={model.finishEditing}
|
||||
onKeyDown={this.onInputKeyDown}
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<div
|
||||
className="expression"
|
||||
onDoubleClick={model.activateEditing}
|
||||
>
|
||||
{this.props.rootModel.actualExpression}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
import {
|
||||
contract,
|
||||
types as t,
|
||||
requestContract,
|
||||
notificationContract,
|
||||
ConsoleRpcLogger
|
||||
} from "@hediet/typed-json-rpc";
|
||||
import { TypedWebSocketClientChannel } from "@hediet/typed-json-rpc-websocket";
|
||||
|
||||
const editorContract = contract({
|
||||
server: {
|
||||
highlightLine: requestContract({ params: { line: t.Integer } }),
|
||||
highlight: requestContract({
|
||||
params: {
|
||||
startLine: t.Integer,
|
||||
startCol: t.Integer,
|
||||
endLine: t.Integer,
|
||||
endCol: t.Integer
|
||||
}
|
||||
}),
|
||||
decorate: requestContract({
|
||||
params: {
|
||||
decos: t.array(t.type({ line: t.Integer, text: t.string }))
|
||||
}
|
||||
})
|
||||
},
|
||||
client: {}
|
||||
});
|
||||
|
||||
@observer
|
||||
export class HighlightLine extends React.Component<{ content: string }> {
|
||||
@observable localContent: string;
|
||||
|
||||
constructor(props: HighlightLine["props"]) {
|
||||
super(props);
|
||||
this.localContent = props.content;
|
||||
}
|
||||
/*
|
||||
componentDidUpdate(props: this["props"]) {
|
||||
this.localContent = this.props.content;
|
||||
}*/
|
||||
|
||||
async send() {
|
||||
const content = this.localContent.substr("highlight-line:".length);
|
||||
const lines = content.split("\n");
|
||||
|
||||
const parts = lines[0].split(",");
|
||||
const startLine = parseInt(parts[0]) - 1;
|
||||
const startCol = parseInt(parts[1]) - 1;
|
||||
const endLine = parseInt(parts[2]) - 1;
|
||||
const endCol = parseInt(parts[3]);
|
||||
|
||||
let decos = lines.splice(1).map(l => {
|
||||
const p = l.split(":");
|
||||
return {
|
||||
line: parseInt(p[0]) - 1,
|
||||
text: " " + p.splice(1).join(":")
|
||||
};
|
||||
});
|
||||
decos = decos.filter(d => !d.text.startsWith(" @"));
|
||||
|
||||
console.log("send", startLine, decos);
|
||||
|
||||
try {
|
||||
let channel = await TypedWebSocketClientChannel.connectTo(
|
||||
{ address: "ws://localhost:56024" },
|
||||
new ConsoleRpcLogger()
|
||||
);
|
||||
const server = editorContract.getServerInterface(channel, {});
|
||||
channel.startListen();
|
||||
|
||||
await server.highlight({ startLine, startCol, endLine, endCol });
|
||||
await server.decorate({ decos });
|
||||
} catch (exception) {
|
||||
console.log("could not contact vscode: ", exception);
|
||||
}
|
||||
}
|
||||
render() {
|
||||
this.send();
|
||||
return (
|
||||
<textarea
|
||||
value={this.localContent}
|
||||
onChange={e => (this.localContent = "" + e.target.value)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@observer
|
||||
export class GUI extends React.Component<{ model: Model }, {}> {
|
||||
render() {
|
||||
const m = this.props.model;
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
paddingTop: "0.5em",
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
height: "100%",
|
||||
boxSizing: "border-box"
|
||||
}}
|
||||
tabIndex={0}
|
||||
onKeyDown={e => {
|
||||
if (e.keyCode === 39) {
|
||||
m.historyIndex++;
|
||||
} else if ((e.keyCode = 37)) {
|
||||
m.historyIndex--;
|
||||
}
|
||||
}}
|
||||
>
|
||||
<div style={{}}>
|
||||
<ExpressionInput
|
||||
rootModel={this.props.model}
|
||||
model={this.props.model.expressionInput}
|
||||
/>
|
||||
</div>
|
||||
<hr style={{ width: "100%" }} />
|
||||
<div style={{ flex: "1", minHeight: "0px" }}>
|
||||
{/* <input type="checkbox" onChange={e => this.props.model.expressionInput.isEditing = !!e.target.checked} /> */}
|
||||
{m.value ? (
|
||||
this.renderValue(m.value.data)
|
||||
) : (
|
||||
<div>No Data</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
private cachedValue: string;
|
||||
private cachedParts: string[];
|
||||
|
||||
renderValue(value: string) {
|
||||
try {
|
||||
if (value.match(/"(.*)"/)) {
|
||||
value = value.substr(1, value.length - 2);
|
||||
}
|
||||
const data = JSON.parse(value);
|
||||
|
||||
//console.log(value, (data as any).kind);
|
||||
if ((data as any).kind === "GraphData") {
|
||||
const value2 = data as GraphData;
|
||||
return <DotGraphViewer data={value2} />;
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(value, e);
|
||||
}
|
||||
return value;
|
||||
|
||||
try {
|
||||
let parsed = JSON.parse(`${value}`);
|
||||
|
||||
if (parsed === "None") {
|
||||
return <textarea>None</textarea>;
|
||||
}
|
||||
|
||||
if (parsed.startsWith("highlight-line:")) {
|
||||
return <HighlightLine content={parsed} />;
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
|
||||
try {
|
||||
return <DotViewer dotCode={JSON.parse(`${value}`)} />;
|
||||
} catch (e) {
|
||||
console.error(value, e);
|
||||
}
|
||||
|
||||
/*return <DotViewer dotCode={JSON.parse(`${value}`)} />;*/
|
||||
if (value !== this.cachedValue) {
|
||||
this.cachedParts = value
|
||||
.split("---------------------------")
|
||||
.filter(e => !!e);
|
||||
this.cachedValue = value;
|
||||
console.log(`updated ${this.cachedParts.length} parts`);
|
||||
}
|
||||
//console.log(value.substr(0, 10000));
|
||||
//console.log(`${this.cachedParts[0]}`);
|
||||
//console.log(`"${this.cachedParts[0]}"`);
|
||||
const idx = Math.max(
|
||||
1,
|
||||
Math.min(this.cachedParts.length - 2, this.props.model.historyIndex)
|
||||
);
|
||||
console.log(idx);
|
||||
return <DotViewer dotCode={JSON.parse(`"${this.cachedParts[idx]}"`)} />;
|
||||
|
||||
return <div>{`${value.length}`}</div>;
|
||||
}
|
||||
}
|
||||
@@ -1,113 +0,0 @@
|
||||
import { observer } from "mobx-react";
|
||||
import * as classNames from "classnames";
|
||||
import * as React from "react";
|
||||
import * as ReactDOM from "react-dom";
|
||||
import { ReactSVGPanZoom, Tool } from 'react-svg-pan-zoom';
|
||||
import { computed, observable, autorun, runInAction } from "mobx";
|
||||
import Measure, { ContentRect } from 'react-measure';
|
||||
import Viz from 'viz.js';
|
||||
import { Module, render } from 'viz.js/full.render.js';
|
||||
|
||||
export interface GraphData {
|
||||
kind: "GraphData";
|
||||
nodes: { id: string; label: string; }[];
|
||||
edges: { from: string; to: string; label: string; }[];
|
||||
}
|
||||
|
||||
let viz: any = new Viz({Module: () => Module({TOTAL_MEMORY: 1<<30}), render});
|
||||
|
||||
@observer
|
||||
export class DotGraphViewer extends React.Component<{ data: GraphData }> {
|
||||
render() {
|
||||
const { nodes, edges } = this.props.data;
|
||||
const dotContent = `
|
||||
digraph MyGraph {
|
||||
${nodes.map(n => `${n.id} [ label = ${JSON.stringify(n.label)} ];`).join("\n ")}
|
||||
${edges.map(e => `${e.from} -> ${e.to} [ label = ${JSON.stringify(e.label)} ];`).join("\n")}
|
||||
}
|
||||
`;
|
||||
return (
|
||||
<DotViewer dotCode={dotContent} />
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@observer
|
||||
export class DotViewer extends React.Component<{ dotCode: string }> {
|
||||
@observable private svg: string|null = null;
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
autorun(async () => {
|
||||
const svg = await viz.renderString(this.props.dotCode);
|
||||
runInAction("Update svg", () => this.svg = svg);
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
if (!this.svg) {
|
||||
return (<div>Loading...</div>);
|
||||
}
|
||||
return (
|
||||
<SvgViewer svgContent={this.svg} />
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function widthOrDefault(r: ContentRect): number {
|
||||
if (r.bounds && r.bounds.width) {
|
||||
return r.bounds.width;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
function heightOrDefault(r: ContentRect): number {
|
||||
if (r.bounds && r.bounds.height) {
|
||||
return r.bounds.height;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
export class SvgViewer extends React.Component<{ svgContent: string }, { tool: Tool }> {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = { tool: "pan" };
|
||||
}
|
||||
|
||||
private readonly setTool = (tool: Tool) => {
|
||||
this.setState({ tool });
|
||||
};
|
||||
|
||||
render() {
|
||||
let { svgContent } = this.props;
|
||||
let width: number = 0;
|
||||
let height: number = 0;
|
||||
svgContent = svgContent.replace(/viewBox="[0-9\.]+ [0-9\.]+ ([0-9\.]+) ([0-9\.]+)"/, (r, w, h) => {
|
||||
width = parseFloat(w);
|
||||
height = parseFloat(h);
|
||||
return "";
|
||||
});
|
||||
//console.log(svgContent);
|
||||
return (
|
||||
<Measure bounds>
|
||||
{
|
||||
({ measureRef, contentRect }) =>
|
||||
(
|
||||
<div ref={measureRef} className="svgViewer">
|
||||
<ReactSVGPanZoom
|
||||
width={widthOrDefault(contentRect)}
|
||||
height={heightOrDefault(contentRect)}
|
||||
tool={this.state.tool}
|
||||
onChangeTool={this.setTool}
|
||||
>
|
||||
<svg width={width} height={height}>
|
||||
<g dangerouslySetInnerHTML={{ __html: svgContent }} />
|
||||
</svg>
|
||||
</ReactSVGPanZoom>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
</Measure>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,89 +0,0 @@
|
||||
import { observable, computed, action, autorun, intercept, runInAction, values } from "mobx";
|
||||
import Viz from 'viz.js';
|
||||
import { Module, render } from 'viz.js/full.render.js';
|
||||
|
||||
let viz: any = new Viz({ Module, render });
|
||||
|
||||
export class ExpressionInputModel {
|
||||
@observable public editedExpression: string|null = "";
|
||||
@observable public expression: string = "";
|
||||
|
||||
@action
|
||||
public readonly activateEditing = () => {
|
||||
this.editedExpression = this.expression;
|
||||
}
|
||||
|
||||
@action
|
||||
public readonly setEditedExpression = (text: string) => {
|
||||
this.editedExpression = text;
|
||||
|
||||
}
|
||||
|
||||
@action
|
||||
public readonly finishEditing = () => {
|
||||
if (this.editedExpression) {
|
||||
this.expression = this.editedExpression;
|
||||
this.editedExpression = null;
|
||||
}
|
||||
}
|
||||
|
||||
@action
|
||||
public readonly abortEditing = () => {
|
||||
this.editedExpression = null;
|
||||
}
|
||||
}
|
||||
|
||||
interface Value {
|
||||
data: string;
|
||||
}
|
||||
|
||||
type Data = SetValueCommand | SetExpressionCommand | { command: undefined; };
|
||||
|
||||
interface SetValueCommand {
|
||||
command: "setValue";
|
||||
value: Value;
|
||||
}
|
||||
|
||||
interface SetExpressionCommand {
|
||||
command: "setExpression";
|
||||
expression: string;
|
||||
}
|
||||
|
||||
export class Model {
|
||||
public readonly expressionInput = new ExpressionInputModel();
|
||||
@observable public value: Value | undefined = undefined;
|
||||
|
||||
@observable public historyIndex: number = 0;
|
||||
@computed public get actualExpression(): string {
|
||||
return this.expressionInput.expression;
|
||||
//return this.expressionInput.expression.replace(/\$i/g, `${this.historyIndex}`);
|
||||
}
|
||||
|
||||
constructor() {
|
||||
window.addEventListener("message", event => {
|
||||
const data = event.data as Data;
|
||||
if (data.command === "setValue") {
|
||||
runInAction("setValue", () => {
|
||||
this.value = data.value;
|
||||
});
|
||||
}
|
||||
if (data.command === "setExpression") {
|
||||
runInAction("setExpression", () => {
|
||||
this.expressionInput.editedExpression = data.expression ? null : "";
|
||||
this.expressionInput.expression = data.expression;
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
window.parent.postMessage({ command: "initialized" }, "*");
|
||||
|
||||
autorun(() => {
|
||||
setExpression(this.actualExpression);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function setExpression(expression: string) {
|
||||
window.parent.postMessage({ command: "setExpression", expression }, "*");
|
||||
}
|
||||
|
||||
@@ -1,27 +0,0 @@
|
||||
import * as React from "react";
|
||||
import * as ReactDOM from "react-dom";
|
||||
|
||||
import "./style.scss";
|
||||
import App = require("./App");
|
||||
import { Model } from "./Model";
|
||||
|
||||
const model = new Model();
|
||||
|
||||
function render(target: HTMLDivElement, app: typeof App) {
|
||||
ReactDOM.render(<app.GUI model={model} />, target);
|
||||
}
|
||||
|
||||
const target = document.createElement("div");
|
||||
target.className = "root";
|
||||
render(target, App);
|
||||
document.body.appendChild(target);
|
||||
|
||||
declare var module: { hot?: { accept: (componentName: string, callback: () => void) => void } };
|
||||
declare var require: (name: string) => any;
|
||||
|
||||
if (module.hot) {
|
||||
module.hot.accept("./App", () => {
|
||||
console.log("render");
|
||||
render(target, require("./App"));
|
||||
});
|
||||
}
|
||||
@@ -1,105 +0,0 @@
|
||||
@import './vscode-defaults.scss';
|
||||
|
||||
body {
|
||||
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||||
height: 100vh;
|
||||
position: relative;
|
||||
margin: 0px;
|
||||
padding: 0px;
|
||||
border: 0px;
|
||||
|
||||
background-color: var(--vscode-editor-background);
|
||||
color: var(--vscode-editor-foreground);
|
||||
font-family: var(--vscode-editor-font-family);
|
||||
font-weight: var(--vscode-editor-font-weight);
|
||||
font-size: var(--vscode-editor-font-size);
|
||||
margin: 0;
|
||||
padding: 0 20px;
|
||||
}
|
||||
|
||||
html, .root, .gui {
|
||||
height: 100vh;
|
||||
position: relative;
|
||||
margin: 0px;
|
||||
padding: 0px;
|
||||
border: 0px;
|
||||
}
|
||||
|
||||
.gui {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
img {
|
||||
max-width: 100%;
|
||||
max-height: 100%;
|
||||
}
|
||||
|
||||
a {
|
||||
color: var(--vscode-textLink-foreground);
|
||||
}
|
||||
|
||||
a:hover {
|
||||
color: var(--vscode-textLink-activeForeground);
|
||||
}
|
||||
|
||||
a:focus,
|
||||
input:focus,
|
||||
select:focus,
|
||||
textarea:focus {
|
||||
outline: 1px solid -webkit-focus-ring-color;
|
||||
outline-offset: -1px;
|
||||
}
|
||||
|
||||
code {
|
||||
color: var(--vscode-textPreformat-foreground);
|
||||
}
|
||||
|
||||
blockquote {
|
||||
background: var(--vscode-textBlockQuote-background);
|
||||
border-color: var(--vscode-textBlockQuote-border);
|
||||
}
|
||||
|
||||
::-webkit-scrollbar {
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb {
|
||||
background-color: var(--vscode-scrollbarSlider-background);
|
||||
}
|
||||
::-webkit-scrollbar-thumb:hover {
|
||||
background-color: var(--vscode-scrollbarSlider-hoverBackground);
|
||||
}
|
||||
::-webkit-scrollbar-thumb:active {
|
||||
background-color: var(--vscode-scrollbarSlider-activeBackground);
|
||||
}
|
||||
|
||||
.svgViewer {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.expression {
|
||||
color: #c586c0;
|
||||
white-space: pre;
|
||||
font-size: 13px;
|
||||
font-family: Monaco,Menlo,Consolas,Droid Sans Mono,Inconsolata,Courier New,monospace,Droid Sans Fallback;
|
||||
margin-top: 3px;
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
|
||||
.expression-input {
|
||||
width: 100%;
|
||||
box-sizing : border-box;
|
||||
margin-top: 1px;
|
||||
margin-bottom: 0px;
|
||||
font-size: 13px;
|
||||
font-family: Monaco,Menlo,Consolas,Droid Sans Mono,Inconsolata,Courier New,monospace,Droid Sans Fallback;
|
||||
background-color: rgb(48, 60, 65);
|
||||
color: rgb(238, 255, 255);
|
||||
border: 1px black;
|
||||
}
|
||||
|
||||
hr {
|
||||
border: 0.5px solid white;
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"outDir": "./dist/development",
|
||||
"lib": ["es6", "dom"],
|
||||
"module": "commonjs",
|
||||
"target": "es5",
|
||||
"jsx": "react",
|
||||
"allowJs": true,
|
||||
"strictNullChecks": true,
|
||||
"sourceMap": true,
|
||||
"experimentalDecorators": true
|
||||
},
|
||||
"include": [
|
||||
"src/**/*"
|
||||
],
|
||||
"exclude": [
|
||||
"dist",
|
||||
"node_modules"
|
||||
]
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
var webpack = require('webpack');
|
||||
var path = require('path');
|
||||
var HtmlWebpackPlugin = require('html-webpack-plugin');
|
||||
|
||||
var r = file => path.resolve(__dirname, file[0]);
|
||||
|
||||
module.exports = {
|
||||
entry: [ r`src/index.tsx` ],
|
||||
output: {
|
||||
path: r`dist`,
|
||||
filename: "[name]-[hash].js",
|
||||
chunkFilename: "[name]-[hash].js"
|
||||
},
|
||||
resolve: {
|
||||
extensions: ['.webpack.js', '.web.js', '.ts', '.tsx', '.js']
|
||||
},
|
||||
devtool: 'source-map',
|
||||
module: {
|
||||
rules: [
|
||||
{ test: /\.css$/, loader: "style-loader!css-loader" },
|
||||
{ test: /\.scss$/, loader: "style-loader!css-loader!sass-loader" },
|
||||
{ test: /\.(jpe?g|png|gif)$/i, loader: "file-loader" },
|
||||
{
|
||||
test: /\.tsx?$/, loader: 'ts-loader',
|
||||
options: { transpileOnly: true }
|
||||
}
|
||||
]
|
||||
},
|
||||
plugins: [
|
||||
// generates an index.html
|
||||
new HtmlWebpackPlugin()
|
||||
]
|
||||
};
|
||||
Reference in New Issue
Block a user