v0.9.0
This commit is contained in:
5
extension/CHANGELOG.md
Normal file
5
extension/CHANGELOG.md
Normal file
@@ -0,0 +1,5 @@
|
||||
# Change Log
|
||||
|
||||
## 0.9.0
|
||||
|
||||
- Initial release
|
||||
@@ -1,62 +1,171 @@
|
||||
# Debug Visualizer (Preview)
|
||||
# Debug Visualizer
|
||||
|
||||
[](https://twitter.com/intent/follow?screen_name=hediet_dev)
|
||||
|
||||
Provides infrastructure to visualize values while debugging.
|
||||
A VS Code extension for visualizing data structures during debugging.
|
||||
|
||||
This extension is still in development.
|
||||
|
||||

|
||||

|
||||
|
||||
## Usage
|
||||
|
||||
After installing this extension, use the command `Open a new Debug Visualizer View` to open a new visualizer view.
|
||||
There you can enter an expression that is visualized when debugging your application.
|
||||
In this view you can enter an expression that is visualized when debugging your application, e.g.
|
||||
|
||||
```ts
|
||||
{ kind: { graph: true }, nodes: [ { id: "1", label: "1" }, { id: "2", label: "2" } ], edges: [{ from: "1", to: "2", label: "edge" }]}
|
||||
```
|
||||
|
||||
## Integrated Visualizers
|
||||
|
||||
Visualizers present preprocessed data.
|
||||
They are mostly React-Components and live in the webview.
|
||||
Visualizers present data extracted by a _Data Extractor_.
|
||||
Visualizers are (mostly) React components and live in the webview of the extension.
|
||||
|
||||
These visualizers are integrated:
|
||||
### Tree Visualization
|
||||
|
||||
- Tree Viewer
|
||||

|
||||
The tree visualizer renders data that matches the `Tree` interface.
|
||||
|
||||
- Svg Viewer
|
||||
- Dot Graph Viewer
|
||||
- VisJs Graph Viewer
|
||||

|
||||
```ts
|
||||
interface Tree<TData = unknown> {
|
||||
kind: { tree: true };
|
||||
root: TreeNode<TData>;
|
||||
}
|
||||
interface TreeNode<TExtraData> {
|
||||
id: string | undefined;
|
||||
name: string;
|
||||
value: string | undefined;
|
||||
emphasizedValue: string | undefined;
|
||||
children: TreeNode<TExtraData>[];
|
||||
data: TExtraData;
|
||||
isMarked: boolean;
|
||||
}
|
||||
```
|
||||
|
||||
- Graph Viewer
|
||||

|
||||

|
||||
|
||||
- Text Viewer
|
||||
- Plain Text
|
||||
- Monaco
|
||||
### AST Visualization
|
||||
|
||||
The AST visualizer renders data that matches the `Ast` interface.
|
||||
|
||||
```ts
|
||||
interface Ast
|
||||
extends Tree<{
|
||||
position: number;
|
||||
length: number;
|
||||
}>,
|
||||
Text {
|
||||
kind: { text: true; tree: true; ast: true };
|
||||
}
|
||||
```
|
||||
|
||||
In addition to the tree view, the text representation is shown.
|
||||
|
||||

|
||||
|
||||
### Text Visualization
|
||||
|
||||
The text visualizer renders data that matches the `Text` interface.
|
||||
|
||||
```ts
|
||||
interface Text {
|
||||
kind: { text: true };
|
||||
text: string;
|
||||
mimeType?: string;
|
||||
fileName?: string;
|
||||
}
|
||||
```
|
||||
|
||||
The `mimeType` and the file extension of `fileName` are used for syntax highlighting.
|
||||
|
||||
### SVG Visualization
|
||||
|
||||
The SVG visualizer renders data that matches the `Svg` interface.
|
||||
The actual SVG data must be stored in `text`.
|
||||
|
||||
```ts
|
||||
interface Svg extends Text {
|
||||
kind: { text: true; svg: true };
|
||||
}
|
||||
```
|
||||
|
||||
### Dot Graph Visualization
|
||||
|
||||
The Graphviz Dot visualizer renders data that matches the `DotGraph` interface.
|
||||
|
||||
```ts
|
||||
interface DotGraph extends Text {
|
||||
kind: { text: true; dotGraph: true };
|
||||
}
|
||||
```
|
||||
|
||||
`Viz.js` (Graphviz) is used for rendering.
|
||||
|
||||
### Graph Visualization
|
||||
|
||||
The Graphviz and vis.js visualizers render data that matches the `Graph` interface.
|
||||
|
||||
```ts
|
||||
interface Graph {
|
||||
kind: { graph: true };
|
||||
nodes: NodeGraphData[];
|
||||
edges: EdgeGraphData[];
|
||||
}
|
||||
|
||||
interface NodeGraphData {
|
||||
id: string;
|
||||
label: string;
|
||||
color?: string;
|
||||
}
|
||||
|
||||
interface EdgeGraphData {
|
||||
from: string;
|
||||
to: string;
|
||||
label: string;
|
||||
id?: string;
|
||||
color?: string;
|
||||
weight?: number;
|
||||
}
|
||||
```
|
||||
|
||||
The graphviz visualizer uses the SVG viewer to render the SVG created by `viz.js`.
|
||||
|
||||

|
||||

|
||||
|
||||
## Integrated Data Extractors
|
||||
|
||||
Data extractors convert arbitrary values into visualizable data.
|
||||
They live in the debugee.
|
||||
They live in the debugee. The following data extractors are injected automatically by this extension.
|
||||
Custom data extractors can be registered too.
|
||||
|
||||
These visualizers are integrated:
|
||||
### ToString
|
||||
|
||||
- ToString Text Extractor
|
||||
- TypeScript Ast to Tree Data
|
||||
- Direct Visualization of `ts.Node`s
|
||||
- Visualization of `Record<string, ts.Node>` and `[ts.Node]`. If the record contains a key `fn`, its value is displayed for each node.
|
||||
Just calls `.toString()` on values and treats the result as text.
|
||||
|
||||

|
||||
### TypeScript AST
|
||||
|
||||
- As Is Data Extractor. Allows to return data directly.
|
||||
- Direct Visualization of `ts.Node`s
|
||||
- Visualization of `Record<string, ts.Node>` and `[ts.Node]`. If the record contains a key `fn`, its value is displayed for each node.
|
||||
|
||||
### As Is Data Extractor
|
||||
|
||||
Treats the data as direct input to the visualizer.
|
||||
|
||||
### Use Method 'getDebugVisualization'
|
||||
|
||||
Calls `.getDebugVisualization()` on values and treats the result as direct input to the visualizer.
|
||||
|
||||
## Limitations
|
||||
|
||||
Currently, only JavaScript (and thus TypeScript) values can be visualized and only a few visualizations are supported.
|
||||
The architecture is solid enough to support other languages in the future.
|
||||
|
||||
# See Also
|
||||
|
||||
This extension works very well together with my library [`@hediet/node-reload`](https://github.com/hediet/node-reload):
|
||||
This extension works very well together with my library [`@hediet/node-reload`](https://github.com/hediet/node-reload).
|
||||
Together, they provide an interactive typescript playground.
|
||||
|
||||

|
||||

|
||||
|
||||
```
|
||||
|
||||
```
|
||||
|
||||
BIN
extension/docs/logo.png
Normal file
BIN
extension/docs/logo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.8 KiB |
@@ -2,7 +2,8 @@
|
||||
"name": "debug-visualizer",
|
||||
"displayName": "Debug Visualizer",
|
||||
"description": "An extension that visualizes values while debugging.",
|
||||
"version": "0.2.1",
|
||||
"icon": "docs/logo.png",
|
||||
"version": "0.9.0",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"vscode": "^1.35.0"
|
||||
@@ -20,7 +21,6 @@
|
||||
"categories": [
|
||||
"Other"
|
||||
],
|
||||
"preview": true,
|
||||
"activationEvents": [
|
||||
"onCommand:vscode-debug-visualizer.new-visualizer",
|
||||
"onDebug",
|
||||
@@ -46,6 +46,7 @@
|
||||
}
|
||||
},
|
||||
"scripts": {
|
||||
"pub": "vsce publish --baseImagesUrl https://github.com/hediet/vscode-debug-visualizer/raw/master/extension",
|
||||
"vscode:prepublish": "yarn build",
|
||||
"build": "webpack --mode production",
|
||||
"dev": "tsc -watch -p ./"
|
||||
@@ -59,7 +60,8 @@
|
||||
"express": "^4.17.1",
|
||||
"open": "^6.4.0",
|
||||
"serve-static": "^1.14.1",
|
||||
"ws": "^7.1.1"
|
||||
"ws": "^7.1.1",
|
||||
"crypto-random-string": "^3.1.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/express": "^4.17.0",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Disposable } from "@hediet/std/disposable";
|
||||
import { debugVisualizerUIContract } from "@hediet/debug-visualizer-vscode-shared";
|
||||
import { ConsoleRpcLogger } from "@hediet/typed-json-rpc";
|
||||
import { ConsoleRpcLogger, RequestHandlingError } from "@hediet/typed-json-rpc";
|
||||
import { EvaluationWatcher } from "./DataSource/DataSource";
|
||||
import { WebSocketStream } from "@hediet/typed-json-rpc-websocket";
|
||||
import { observable, autorun } from "mobx";
|
||||
@@ -19,8 +19,17 @@ export class ConnectionHandler {
|
||||
sources: Sources,
|
||||
stream: WebSocketStream,
|
||||
server: Server,
|
||||
config: Config
|
||||
config: Config,
|
||||
serverSecret: string
|
||||
) {
|
||||
let authenticated = false;
|
||||
|
||||
function throwIfNotAuthenticated() {
|
||||
if (!authenticated) {
|
||||
throw new RequestHandlingError("Not authenticated");
|
||||
}
|
||||
}
|
||||
|
||||
const {
|
||||
client,
|
||||
channel,
|
||||
@@ -28,12 +37,23 @@ export class ConnectionHandler {
|
||||
stream,
|
||||
new ConsoleRpcLogger(),
|
||||
{
|
||||
authenticate: async ({ secret }, { newErr }) => {
|
||||
if (secret !== serverSecret) {
|
||||
return newErr({ errorMessage: "Invalid Secret" });
|
||||
} else {
|
||||
authenticated = true;
|
||||
}
|
||||
},
|
||||
refresh: async () => {
|
||||
throwIfNotAuthenticated();
|
||||
|
||||
if (this.watcher) {
|
||||
this.watcher.refresh();
|
||||
}
|
||||
},
|
||||
setExpression: async ({ newExpression }) => {
|
||||
throwIfNotAuthenticated();
|
||||
|
||||
let oldPreferredDataExtractor: EvaluationWatcher["preferredDataExtractor"];
|
||||
if (this.watcher) {
|
||||
oldPreferredDataExtractor = this.watcher
|
||||
@@ -50,6 +70,8 @@ export class ConnectionHandler {
|
||||
);
|
||||
},
|
||||
openInBrowser: async ({}) => {
|
||||
throwIfNotAuthenticated();
|
||||
|
||||
let opened = false;
|
||||
if (config.useChromeKioskMode()) {
|
||||
opened = await launchChrome(server.indexUrl);
|
||||
@@ -59,11 +81,15 @@ export class ConnectionHandler {
|
||||
}
|
||||
},
|
||||
setPreferredDataExtractor: async ({ dataExtractorId }) => {
|
||||
throwIfNotAuthenticated();
|
||||
|
||||
if (this.watcher) {
|
||||
this.watcher.setPreferredDataExtractor(dataExtractorId);
|
||||
}
|
||||
},
|
||||
getCompletions: async ({ text, column }) => {
|
||||
throwIfNotAuthenticated();
|
||||
|
||||
const completions = await sources.jsSource.getCompletions(
|
||||
text,
|
||||
column
|
||||
|
||||
@@ -8,9 +8,11 @@ import * as express from "express";
|
||||
import * as http from "http";
|
||||
import * as serveStatic from "serve-static";
|
||||
import { Config } from "./Config";
|
||||
import cryptoRandomString = require("crypto-random-string");
|
||||
|
||||
export class Server {
|
||||
private server: http.Server;
|
||||
public readonly secret = cryptoRandomString({ length: 20 });
|
||||
|
||||
constructor(sources: Sources, config: Config) {
|
||||
const app = express();
|
||||
@@ -22,13 +24,13 @@ export class Server {
|
||||
const wss = new WebSocket.Server({ server: this.server });
|
||||
wss.on("connection", ws => {
|
||||
const stream = new WebSocketStream(ws);
|
||||
new ConnectionHandler(sources, stream, this, config);
|
||||
new ConnectionHandler(sources, stream, this, config, this.secret);
|
||||
});
|
||||
}
|
||||
|
||||
public get indexUrl(): string {
|
||||
const port = process.env.USE_DEV_UI ? 8080 : this.port;
|
||||
return `http://localhost:${port}/index.html?serverPort=${this.port}`;
|
||||
return `http://localhost:${port}/index.html?serverPort=${this.port}&serverSecret=${this.secret}`;
|
||||
}
|
||||
|
||||
public get mainBundleUrl(): string {
|
||||
|
||||
@@ -72,6 +72,7 @@ export function getHtml(server: Server) {
|
||||
<body>
|
||||
<script>
|
||||
window.serverPort = ${server.port};
|
||||
window.serverSecret = ${JSON.stringify(server.secret)};
|
||||
const api = window.VsCodeApi = acquireVsCodeApi();
|
||||
window.addEventListener('message', event => {
|
||||
if (event.source === window.frames[0]) {
|
||||
|
||||
@@ -1,90 +1,42 @@
|
||||
import React = require("react");
|
||||
import { Model } from "../Model/Model";
|
||||
import { observer } from "mobx-react";
|
||||
import { observable, computed } from "mobx";
|
||||
import { Popover, Button, Spinner } from "@blueprintjs/core";
|
||||
import classnames = require("classnames");
|
||||
import { Popover } from "@blueprintjs/core";
|
||||
import { DataExtractorInfo } from "@hediet/debug-visualizer-data-extraction";
|
||||
import { Visualizer } from "./Visualizer";
|
||||
import { ExpressionInput } from "./ExpressionInput";
|
||||
|
||||
@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">
|
||||
<div className="part-expression-input ">
|
||||
<ExpressionInput model={m} />
|
||||
</div>
|
||||
<div style={{ width: 4 }} />
|
||||
{m.loading ? (
|
||||
<div style={{ padding: "0 4px" }}>
|
||||
<Spinner size={Spinner.SIZE_SMALL} />
|
||||
</div>
|
||||
) : (
|
||||
<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 && <ExpandedOptions model={m} />}
|
||||
</div>
|
||||
</div>
|
||||
<div className="part-visualizer">
|
||||
<Visualizer model={m} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
renderValue(value: string) {
|
||||
return <div>{`${value.length}`}</div>;
|
||||
}
|
||||
}
|
||||
import { computed } from "mobx";
|
||||
import { observer } from "mobx-react";
|
||||
import { Model } from "../Model/Model";
|
||||
import { VisualizationId } from "../Visualizers/Visualizer";
|
||||
|
||||
@observer
|
||||
export class ExpandedOptions extends React.Component<{ model: Model }> {
|
||||
render() {
|
||||
const m = this.props.model;
|
||||
let availableExtractors = new Array<DataExtractorInfo>();
|
||||
let selected = -1;
|
||||
let selectedExtractor = -1;
|
||||
if (m.state.kind === "data") {
|
||||
const result = m.state.result;
|
||||
availableExtractors = result.availableExtractors;
|
||||
selected = result.availableExtractors.findIndex(
|
||||
selectedExtractor = result.availableExtractors.findIndex(
|
||||
i => i.id === result.usedExtractor.id
|
||||
);
|
||||
}
|
||||
|
||||
let visualizations = new Array<{
|
||||
label: string;
|
||||
id: VisualizationId;
|
||||
}>();
|
||||
let selectedVisualization = -1;
|
||||
if (m.visualizations) {
|
||||
visualizations = m.visualizations.allVisualizations.map(v => ({
|
||||
label: v.name,
|
||||
id: v.id,
|
||||
}));
|
||||
if (m.visualizations.visualization) {
|
||||
selectedVisualization = visualizations.findIndex(
|
||||
v => v.id === m.visualizations!.visualization!.id
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<div style={{ height: 6 }} />
|
||||
@@ -99,7 +51,7 @@ export class ExpandedOptions extends React.Component<{ model: Model }> {
|
||||
|
||||
<NamedSelect
|
||||
name="Extractor"
|
||||
selected={selected}
|
||||
selected={selectedExtractor}
|
||||
options={availableExtractors.map(e => ({
|
||||
label: e.name,
|
||||
id: e.id,
|
||||
@@ -112,7 +64,7 @@ export class ExpandedOptions extends React.Component<{ model: Model }> {
|
||||
|
||||
<NamedSelect
|
||||
name="Visualizer"
|
||||
selected={0}
|
||||
selected={selectedVisualization}
|
||||
onSelected={item => {
|
||||
m.setPreferredVisualizationId(item.id);
|
||||
}}
|
||||
@@ -131,23 +83,6 @@ export class ExpandedOptions extends React.Component<{ model: Model }> {
|
||||
}
|
||||
}
|
||||
|
||||
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<T extends SelectableItem>(props: {
|
||||
name: string;
|
||||
selected: number;
|
||||
@@ -169,7 +104,7 @@ interface SelectableItem {
|
||||
}
|
||||
|
||||
@observer
|
||||
export class Select<T extends SelectableItem> extends React.Component<{
|
||||
class Select<T extends SelectableItem> extends React.Component<{
|
||||
selected: number;
|
||||
onSelected: (item: T) => void;
|
||||
options: T[];
|
||||
93
extension/ui/src/Components/GUI.tsx
Normal file
93
extension/ui/src/Components/GUI.tsx
Normal file
@@ -0,0 +1,93 @@
|
||||
import React = require("react");
|
||||
import { Button, Spinner } from "@blueprintjs/core";
|
||||
import { observable } from "mobx";
|
||||
import { observer } from "mobx-react";
|
||||
import { Model } from "../Model/Model";
|
||||
import { ExpressionInput } from "./ExpressionInput";
|
||||
import { Visualizer } from "./Visualizer";
|
||||
import classnames = require("classnames");
|
||||
import { ExpandedOptions } from "./ExpandedOptions";
|
||||
|
||||
@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">
|
||||
<div className="part-expression-input ">
|
||||
<ExpressionInput model={m} />
|
||||
</div>
|
||||
<div style={{ width: 4 }} />
|
||||
{m.loading ? (
|
||||
<div style={{ padding: "0 4px" }}>
|
||||
<Spinner size={Spinner.SIZE_SMALL} />
|
||||
</div>
|
||||
) : (
|
||||
<Button
|
||||
minimal
|
||||
small
|
||||
className="part-icon"
|
||||
icon="refresh"
|
||||
onClick={() => m.refresh()}
|
||||
/>
|
||||
)}
|
||||
<Button
|
||||
minimal
|
||||
small
|
||||
className="part-icon"
|
||||
icon="log-in"
|
||||
onClick={() => m.openBrowser()}
|
||||
/>
|
||||
{/*
|
||||
TODO
|
||||
<Button
|
||||
minimal
|
||||
small
|
||||
className="part-icon"
|
||||
icon="locate"
|
||||
onClick={() => m.useSelectionAsExpression()}
|
||||
/>
|
||||
*/}
|
||||
</div>
|
||||
{this.expanded && <ExpandedOptions model={m} />}
|
||||
</div>
|
||||
</div>
|
||||
<div className="part-visualizer">
|
||||
<Visualizer model={m} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
renderValue(value: string) {
|
||||
return <div>{`${value.length}`}</div>;
|
||||
}
|
||||
}
|
||||
|
||||
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"
|
||||
)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -13,10 +13,12 @@ import { MonacoBridge } from "./MonacoBridge";
|
||||
|
||||
declare const window: Window & {
|
||||
serverPort?: number;
|
||||
serverSecret?: string;
|
||||
};
|
||||
|
||||
export class Model {
|
||||
port: number;
|
||||
private port: number;
|
||||
private serverSecret: string;
|
||||
|
||||
@observable expression: string = "";
|
||||
@observable state: DataExtractionState | { kind: "noExpression" } = {
|
||||
@@ -67,6 +69,7 @@ export class Model {
|
||||
constructor() {
|
||||
if (window.serverPort) {
|
||||
this.port = window.serverPort;
|
||||
this.serverSecret = window.serverSecret!;
|
||||
} else {
|
||||
const url = new URL(window.location.href);
|
||||
const portStr = url.searchParams.get("serverPort");
|
||||
@@ -79,6 +82,12 @@ export class Model {
|
||||
if (expr) {
|
||||
this.setExpression(expr);
|
||||
}
|
||||
|
||||
const secret = url.searchParams.get("serverSecret");
|
||||
if (!secret) {
|
||||
throw new Error("Server secret not set.");
|
||||
}
|
||||
this.serverSecret = secret;
|
||||
}
|
||||
|
||||
this.stayConnected();
|
||||
@@ -141,6 +150,11 @@ export class Model {
|
||||
},
|
||||
}
|
||||
);
|
||||
try {
|
||||
await server.authenticate({ secret: this.serverSecret });
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
this.server = server;
|
||||
|
||||
await stream.onClosed;
|
||||
|
||||
@@ -9,16 +9,12 @@ import {
|
||||
CommonDataTypes,
|
||||
} from "@hediet/debug-visualizer-data-extraction";
|
||||
import { createTreeViewModelFromTreeNodeData } from "./TreeVisualizer/Visualizer";
|
||||
import {
|
||||
TreeWithPathView,
|
||||
TreeViewModel,
|
||||
TreeView,
|
||||
} from "./TreeVisualizer/Views";
|
||||
import { TreeWithPathView, TreeViewModel } from "./TreeVisualizer/Views";
|
||||
import React = require("react");
|
||||
import { observer, disposeOnUnmount } from "mobx-react";
|
||||
import { observable, autorun, trace } from "mobx";
|
||||
import * as monaco from "monaco-editor";
|
||||
import { getLanguageId } from "./MonacoTextVisualizer";
|
||||
import { getLanguageId } from "./TextVisualizer/MonacoTextVisualizer";
|
||||
import LineColumn = require("line-column");
|
||||
|
||||
export class AstVisualizer extends VisualizationProvider {
|
||||
@@ -70,7 +66,7 @@ interface NodeInfo {
|
||||
@observer
|
||||
export class AstTree extends React.Component<{
|
||||
model: TreeViewModel<NodeInfo>;
|
||||
data: CommonDataTypes.AstData;
|
||||
data: CommonDataTypes.Ast;
|
||||
nodeInfoToRange: (info: NodeInfo) => monaco.IRange;
|
||||
}> {
|
||||
render() {
|
||||
|
||||
@@ -3,12 +3,12 @@ import * as React from "react";
|
||||
import { observable, autorun, runInAction } from "mobx";
|
||||
import Viz from "viz.js";
|
||||
import { Module, render } from "viz.js/full.render.js";
|
||||
import { SvgViewer } from "./SvgVisualizer";
|
||||
import { SvgViewer } from "../SvgVisualizer";
|
||||
import {
|
||||
VisualizationProvider,
|
||||
VisualizationCollector,
|
||||
asVisualizationId,
|
||||
} from "./Visualizer";
|
||||
} from "../Visualizer";
|
||||
import {
|
||||
ExtractedData,
|
||||
isCommonDataType,
|
||||
@@ -19,18 +19,18 @@ const viz: any = new Viz({
|
||||
render,
|
||||
});
|
||||
|
||||
export class DotVisualizer extends VisualizationProvider {
|
||||
export class GraphvizDotVisualizer extends VisualizationProvider {
|
||||
getVisualizations(
|
||||
data: ExtractedData,
|
||||
collector: VisualizationCollector
|
||||
): void {
|
||||
if (isCommonDataType(data, { dotGraph: true })) {
|
||||
collector.addVisualization({
|
||||
id: asVisualizationId("dot-graph"),
|
||||
name: "Dot Graph",
|
||||
id: asVisualizationId("graphviz-dot"),
|
||||
name: "Graphviz (Dot Data)",
|
||||
priority: 100,
|
||||
render() {
|
||||
return <DotViewer dotCode={data.text} />;
|
||||
return <GraphvizDotViewer dotCode={data.text} />;
|
||||
},
|
||||
});
|
||||
}
|
||||
@@ -38,7 +38,7 @@ export class DotVisualizer extends VisualizationProvider {
|
||||
}
|
||||
|
||||
@observer
|
||||
export class DotViewer extends React.Component<{
|
||||
export class GraphvizDotViewer extends React.Component<{
|
||||
dotCode: string;
|
||||
svgRef?: (element: SVGSVGElement | null) => void;
|
||||
}> {
|
||||
@@ -1,29 +1,32 @@
|
||||
import { observer } from "mobx-react";
|
||||
import * as React from "react";
|
||||
import { DotViewer } from "./DotVisualizer";
|
||||
import { GraphvizDotViewer } from "./GraphvizDotVisualizer";
|
||||
import {
|
||||
VisualizationProvider,
|
||||
VisualizationCollector,
|
||||
asVisualizationId,
|
||||
} from "./Visualizer";
|
||||
} from "../Visualizer";
|
||||
import {
|
||||
ExtractedData,
|
||||
isCommonDataType,
|
||||
} from "@hediet/debug-visualizer-data-extraction";
|
||||
|
||||
export class DotGraphVisualizer extends VisualizationProvider {
|
||||
export class GraphvizGraphVisualizer extends VisualizationProvider {
|
||||
getVisualizations(
|
||||
data: ExtractedData,
|
||||
collector: VisualizationCollector
|
||||
): void {
|
||||
if (isCommonDataType(data, { graph: true })) {
|
||||
collector.addVisualization({
|
||||
id: asVisualizationId("dot-graph"),
|
||||
name: "Dot Graph",
|
||||
id: asVisualizationId("graphviz-graph"),
|
||||
name: "Graphviz",
|
||||
priority: 100,
|
||||
render() {
|
||||
return (
|
||||
<DotGraphViewer edges={data.edges} nodes={data.nodes} />
|
||||
<GraphvizGraphViewer
|
||||
edges={data.edges}
|
||||
nodes={data.nodes}
|
||||
/>
|
||||
);
|
||||
},
|
||||
});
|
||||
@@ -32,7 +35,7 @@ export class DotGraphVisualizer extends VisualizationProvider {
|
||||
}
|
||||
|
||||
@observer
|
||||
export class DotGraphViewer extends React.Component<{
|
||||
export class GraphvizGraphViewer extends React.Component<{
|
||||
nodes: { id: string; label: string }[];
|
||||
edges: { from: string; to: string; label: string }[];
|
||||
}> {
|
||||
@@ -53,6 +56,6 @@ export class DotGraphViewer extends React.Component<{
|
||||
.join("\n")}
|
||||
}
|
||||
`;
|
||||
return <DotViewer dotCode={dotContent} />;
|
||||
return <GraphvizDotViewer dotCode={dotContent} />;
|
||||
}
|
||||
}
|
||||
@@ -4,10 +4,12 @@ import {
|
||||
VisualizationProvider,
|
||||
VisualizationCollector,
|
||||
asVisualizationId,
|
||||
} from "./Visualizer";
|
||||
} from "../Visualizer";
|
||||
import {
|
||||
ExtractedData,
|
||||
isCommonDataType,
|
||||
NodeGraphData,
|
||||
EdgeGraphData,
|
||||
} from "@hediet/debug-visualizer-data-extraction";
|
||||
import { DataSet, Network } from "vis-network";
|
||||
|
||||
@@ -19,7 +21,7 @@ export class VisJsGraphVisualizer extends VisualizationProvider {
|
||||
if (isCommonDataType(data, { graph: true })) {
|
||||
collector.addVisualization({
|
||||
id: asVisualizationId("vis-js-graph"),
|
||||
name: "VisJs Graph",
|
||||
name: "vis.js",
|
||||
priority: 1001,
|
||||
render() {
|
||||
return (
|
||||
@@ -34,34 +36,26 @@ export class VisJsGraphVisualizer extends VisualizationProvider {
|
||||
}
|
||||
}
|
||||
|
||||
interface GraphNode {
|
||||
id: string;
|
||||
label: string;
|
||||
}
|
||||
|
||||
interface GraphEdge {
|
||||
id?: string;
|
||||
label: string;
|
||||
from: string;
|
||||
to: string;
|
||||
}
|
||||
|
||||
@observer
|
||||
export class VisJsGraphViewer extends React.Component<{
|
||||
nodes: GraphNode[];
|
||||
edges: GraphEdge[];
|
||||
nodes: NodeGraphData[];
|
||||
edges: EdgeGraphData[];
|
||||
}> {
|
||||
private readonly divRef = React.createRef<HTMLDivElement>();
|
||||
private readonly nodes = new DataSet<{ id: string; label: string }>();
|
||||
private readonly nodes = new DataSet<{
|
||||
id: string;
|
||||
label: string;
|
||||
color?: string;
|
||||
}>();
|
||||
private readonly edges = new DataSet<{
|
||||
id: string;
|
||||
label: string;
|
||||
from: string;
|
||||
to: string;
|
||||
color?: string;
|
||||
}>();
|
||||
|
||||
render() {
|
||||
//const { nodes, edges } = this.props;
|
||||
return <div style={{ height: "100%" }} ref={this.divRef} />;
|
||||
}
|
||||
|
||||
@@ -69,7 +63,11 @@ export class VisJsGraphViewer extends React.Component<{
|
||||
const newNodes = new Set<string>();
|
||||
for (const n of this.props.nodes) {
|
||||
newNodes.add(n.id);
|
||||
this.nodes.update({ id: n.id, label: n.label });
|
||||
this.nodes.update({
|
||||
id: n.id,
|
||||
label: n.label,
|
||||
color: n.color,
|
||||
});
|
||||
}
|
||||
this.nodes.forEach(item => {
|
||||
if (!newNodes.has(item.id)) {
|
||||
@@ -77,11 +75,11 @@ export class VisJsGraphViewer extends React.Component<{
|
||||
}
|
||||
});
|
||||
|
||||
function getIdOfEdge(e: GraphEdge): string {
|
||||
function getIdOfEdge(e: EdgeGraphData): string {
|
||||
if (e.id) {
|
||||
return e.id;
|
||||
}
|
||||
return e.from + "####" + e.to;
|
||||
return e.from + "####" + e.to + "|" + e.label;
|
||||
}
|
||||
|
||||
const newEdges = new Set<string>();
|
||||
@@ -93,6 +91,7 @@ export class VisJsGraphViewer extends React.Component<{
|
||||
label: n.label,
|
||||
from: n.from,
|
||||
to: n.to,
|
||||
color: n.color,
|
||||
});
|
||||
}
|
||||
this.edges.forEach(item => {
|
||||
@@ -121,6 +120,5 @@ export class VisJsGraphViewer extends React.Component<{
|
||||
},
|
||||
};
|
||||
const network = new Network(this.divRef.current!, data, options);
|
||||
//network.stabilize();
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,7 @@ import {
|
||||
VisualizationProvider,
|
||||
VisualizationCollector,
|
||||
asVisualizationId,
|
||||
} from "./Visualizer";
|
||||
} from "../Visualizer";
|
||||
import {
|
||||
ExtractedData,
|
||||
isCommonDataType,
|
||||
@@ -2,7 +2,7 @@ import {
|
||||
VisualizationProvider,
|
||||
VisualizationCollector,
|
||||
asVisualizationId,
|
||||
} from "./Visualizer";
|
||||
} from "../Visualizer";
|
||||
import {
|
||||
ExtractedData,
|
||||
isCommonDataType,
|
||||
@@ -5,7 +5,6 @@ import {
|
||||
TreeWithPathView,
|
||||
} from "./Views";
|
||||
import {
|
||||
CommonDataTypes,
|
||||
ExtractedData,
|
||||
isCommonDataType,
|
||||
TreeNode,
|
||||
@@ -40,7 +39,7 @@ export function createTreeViewModelFromTreeNodeData<TData>(
|
||||
children,
|
||||
node.data
|
||||
);
|
||||
model.isMarked = node.isMarked;
|
||||
model.isMarked = !!node.isMarked;
|
||||
for (const c of children) {
|
||||
c.parent = model;
|
||||
}
|
||||
|
||||
@@ -1,20 +1,20 @@
|
||||
import "./style.scss";
|
||||
import { ComposedVisualizationProvider } from "./Visualizer";
|
||||
import { TreeVisualizer } from "./TreeVisualizer/Visualizer";
|
||||
import { DotGraphVisualizer } from "./DotGraphVisualizer";
|
||||
import { GraphvizGraphVisualizer } from "./GraphVisualizer/GraphvizGraphVisualizer";
|
||||
import { SvgVisualizer } from "./SvgVisualizer";
|
||||
import { DotVisualizer } from "./DotVisualizer";
|
||||
import { TextVisualizer } from "./TextVisualizer";
|
||||
import { MonacoTextVisualizer } from "./MonacoTextVisualizer";
|
||||
import { GraphvizDotVisualizer } from "./GraphVisualizer/GraphvizDotVisualizer";
|
||||
import { TextVisualizer } from "./TextVisualizer/TextVisualizer";
|
||||
import { MonacoTextVisualizer } from "./TextVisualizer/MonacoTextVisualizer";
|
||||
import { AstVisualizer } from "./AstViewer";
|
||||
import { VisJsGraphVisualizer } from "./VisJsGraphVisualizer";
|
||||
import { VisJsGraphVisualizer } from "./GraphVisualizer/VisJsGraphVisualizer";
|
||||
|
||||
export const knownVisualizations = new ComposedVisualizationProvider([
|
||||
new TreeVisualizer(),
|
||||
new DotGraphVisualizer(),
|
||||
new GraphvizGraphVisualizer(),
|
||||
new VisJsGraphVisualizer(),
|
||||
new SvgVisualizer(),
|
||||
new DotVisualizer(),
|
||||
new GraphvizDotVisualizer(),
|
||||
new TextVisualizer(),
|
||||
new MonacoTextVisualizer(),
|
||||
new AstVisualizer(),
|
||||
|
||||
@@ -2,12 +2,12 @@ import * as React from "react";
|
||||
import * as ReactDOM from "react-dom";
|
||||
import "./style.scss";
|
||||
import { Model } from "./Model/Model";
|
||||
import Components = require("./Components");
|
||||
import Components = require("./Components/GUI");
|
||||
|
||||
const model = new Model();
|
||||
|
||||
function render(target: HTMLDivElement) {
|
||||
const c = require("./Components") as typeof Components;
|
||||
const c = require("./Components/GUI") as typeof Components;
|
||||
ReactDOM.render(<c.GUI model={model} />, target);
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@ declare var module: {
|
||||
declare var require: (name: string) => any;
|
||||
|
||||
if (module.hot) {
|
||||
module.hot.accept("./Components", () => {
|
||||
module.hot.accept("./Components/GUI", () => {
|
||||
render(target);
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user