Progress.
This commit is contained in:
@@ -37,6 +37,11 @@ export class ConnectionHandler {
|
||||
openInBrowser: async ({}) => {
|
||||
open(server.indexUrl);
|
||||
},
|
||||
setPreferredDataExtractor: async ({ dataExtractorId }) => {
|
||||
if (this.watcher) {
|
||||
this.watcher.setPreferredDataExtractor(dataExtractorId);
|
||||
}
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
@@ -66,10 +66,16 @@ class JsDebuggerSourceImplementation implements JsDataSource {
|
||||
|
||||
try {
|
||||
w._state = { kind: "loading" };
|
||||
const expression = `(${selfContainedGetInitializedDataExtractorApi.toString()})().getData(${
|
||||
w.expression
|
||||
}, expr => eval(expr), undefined)`;
|
||||
const fnSrc = selfContainedGetInitializedDataExtractorApi.toString();
|
||||
|
||||
const preferredExtractor = w.preferredDataExtractor
|
||||
? `"${w.preferredDataExtractor}"`
|
||||
: "undefined";
|
||||
|
||||
const expression = `(${fnSrc})().getData(${
|
||||
w.expression
|
||||
}, expr => eval(expr), ${preferredExtractor})`;
|
||||
// uia
|
||||
const reply = await session.customRequest("evaluate", {
|
||||
expression,
|
||||
frameId: this.lastFrameId,
|
||||
|
||||
@@ -1,12 +1,31 @@
|
||||
import { window, ViewColumn, WebviewPanel } from "vscode";
|
||||
import { Server } from "./Server";
|
||||
import { Disposable } from "@hediet/std/disposable";
|
||||
|
||||
export const debugVisualizer = "debugVisualizer";
|
||||
|
||||
export class WebViews {
|
||||
private readonly debugVisualizations = new Map<WebviewPanel, WebView>();
|
||||
|
||||
constructor(private readonly server: Server) {}
|
||||
public readonly dispose = Disposable.fn();
|
||||
|
||||
constructor(private readonly server: Server) {
|
||||
this.dispose.track(
|
||||
window.registerWebviewPanelSerializer(debugVisualizer, {
|
||||
deserializeWebviewPanel: async (panel, state) => {
|
||||
this.restore(panel);
|
||||
},
|
||||
})
|
||||
);
|
||||
|
||||
this.dispose.track({
|
||||
dispose: () => {
|
||||
for (const panel of this.debugVisualizations.keys()) {
|
||||
panel.dispose();
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
public createNew() {
|
||||
const panel = window.createWebviewPanel(
|
||||
@@ -31,12 +50,6 @@ export class WebViews {
|
||||
this.debugVisualizations.delete(webviewPanel);
|
||||
});
|
||||
}
|
||||
|
||||
public dispose() {
|
||||
for (const panel of this.debugVisualizations.keys()) {
|
||||
panel.dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class WebView {
|
||||
@@ -48,6 +61,7 @@ export class WebView {
|
||||
|
||||
export function getHtml(server: Server) {
|
||||
const isDev = true;
|
||||
// uiae
|
||||
return `
|
||||
<html>
|
||||
<head>
|
||||
@@ -70,6 +84,22 @@ export function getHtml(server: Server) {
|
||||
server.mainBundle
|
||||
}"></script>`
|
||||
}
|
||||
<script>
|
||||
const api = window.api = acquireVsCodeApi();
|
||||
window.addEventListener('message', event => {
|
||||
debugger;
|
||||
if (event.source === window.frames[0]) {
|
||||
if (event.data.command === "setState") {
|
||||
console.log("setState", event.data.state);
|
||||
api.setState(event.data.state);
|
||||
}
|
||||
if (event.data.command === "getState") {
|
||||
console.log("getState, sent ", api.getState());
|
||||
window.frames[0].postMessage({ command: "getStateResult", state: api.getState() }, "*");
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
`;
|
||||
|
||||
@@ -2,13 +2,16 @@ 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 { Popover, Icon, Button, Spinner, InputGroup } 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";
|
||||
import {
|
||||
CommonDataTypes,
|
||||
DataExtractorInfo,
|
||||
} from "@hediet/debug-visualizer-data-extraction";
|
||||
@observer
|
||||
export class GUI extends React.Component<{ model: Model }> {
|
||||
@observable text: string = "node";
|
||||
@@ -27,6 +30,7 @@ export class GUI extends React.Component<{ model: Model }> {
|
||||
<div className="part-header-content">
|
||||
<div className="part-header-main">
|
||||
<ExpressionInput model={m} />
|
||||
|
||||
<Button
|
||||
minimal
|
||||
small
|
||||
@@ -49,32 +53,7 @@ export class GUI extends React.Component<{ model: Model }> {
|
||||
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>
|
||||
</>
|
||||
)}
|
||||
{this.expanded && <ExpandedOptions model={m} />}
|
||||
</div>
|
||||
</div>
|
||||
<div className="part-visualizer">
|
||||
@@ -89,6 +68,54 @@ export class GUI extends React.Component<{ model: Model }> {
|
||||
}
|
||||
}
|
||||
|
||||
@observer
|
||||
export class ExpandedOptions extends React.Component<{ model: Model }> {
|
||||
render() {
|
||||
const m = this.props.model;
|
||||
let availableExtractors = new Array<DataExtractorInfo>();
|
||||
let selected = -1;
|
||||
if (m.state.kind === "data") {
|
||||
const result = m.state.result;
|
||||
availableExtractors = result.availableExtractors;
|
||||
selected = result.availableExtractors.findIndex(
|
||||
i => i.id === result.usedExtractor.id
|
||||
);
|
||||
}
|
||||
return (
|
||||
<>
|
||||
<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={selected}
|
||||
options={availableExtractors.map(e => ({
|
||||
label: e.name,
|
||||
id: e.id,
|
||||
}))}
|
||||
onSelected={item => {
|
||||
m.setPreferredExtractorId(item.id);
|
||||
}}
|
||||
/>
|
||||
<div style={{ width: 10 }} />
|
||||
|
||||
<NamedSelect
|
||||
name="Visualizer"
|
||||
selected={0}
|
||||
options={[{ label: "literal" }]}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@observer
|
||||
export class ExpressionInput extends React.Component<{ model: Model }> {
|
||||
@observable editedExpression: string | undefined;
|
||||
@@ -113,11 +140,23 @@ export class ExpressionInput extends React.Component<{ model: Model }> {
|
||||
}
|
||||
|
||||
render() {
|
||||
const model = this.props.model;
|
||||
return (
|
||||
<input
|
||||
className="part-expression-input"
|
||||
<InputGroup
|
||||
small
|
||||
fill
|
||||
rightElement={
|
||||
model.loading ? (
|
||||
<Spinner size={Icon.SIZE_STANDARD} />
|
||||
) : (
|
||||
undefined
|
||||
)
|
||||
}
|
||||
value={this.currentExpression}
|
||||
onChange={e => this.updateCurrentExpression(e.target.value)}
|
||||
onChange={
|
||||
((e: { target: { value: string } }) =>
|
||||
this.updateCurrentExpression(e.target.value)) as any
|
||||
}
|
||||
onBlur={this.submit}
|
||||
onKeyPress={e => {
|
||||
if (e.charCode === 13) {
|
||||
@@ -127,6 +166,12 @@ export class ExpressionInput extends React.Component<{ model: Model }> {
|
||||
}}
|
||||
/>
|
||||
);
|
||||
/*
|
||||
<input
|
||||
className="part-expression-input"
|
||||
|
||||
/>
|
||||
*/
|
||||
}
|
||||
}
|
||||
|
||||
@@ -137,7 +182,7 @@ export class Visualizer extends React.Component<{ model: Model }> {
|
||||
<div className="component-Visualizer">{this.renderContent()}</div>
|
||||
);
|
||||
/*
|
||||
|
||||
|
||||
|
||||
<DotGraphViewer
|
||||
data={{
|
||||
@@ -245,10 +290,11 @@ export class ExpandButton extends React.Component<{
|
||||
}
|
||||
}
|
||||
|
||||
function NamedSelect(props: {
|
||||
function NamedSelect<T extends SelectableItem>(props: {
|
||||
name: string;
|
||||
selected: number;
|
||||
options: SelectableItem[];
|
||||
onSelected: (item: T) => void;
|
||||
options: T[];
|
||||
}) {
|
||||
return (
|
||||
<div className="component-NamedSelect">
|
||||
@@ -265,11 +311,12 @@ interface SelectableItem {
|
||||
}
|
||||
|
||||
@observer
|
||||
export class Select extends React.Component<{
|
||||
export class Select<T extends SelectableItem> extends React.Component<{
|
||||
selected: number;
|
||||
options: SelectableItem[];
|
||||
onSelected: (item: T) => void;
|
||||
options: T[];
|
||||
}> {
|
||||
@computed get selected(): SelectableItem {
|
||||
@computed get selected(): T {
|
||||
return this.props.options[this.props.selected];
|
||||
}
|
||||
|
||||
@@ -286,7 +333,12 @@ export class Select extends React.Component<{
|
||||
</button>
|
||||
<div className={"part-content"}>
|
||||
{this.props.options.map((o, idx) => (
|
||||
<div key={idx}>{o.label}</div>
|
||||
<div
|
||||
key={idx}
|
||||
onClick={() => this.props.onSelected(o)}
|
||||
>
|
||||
{o.label}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</Popover>
|
||||
|
||||
@@ -5,11 +5,60 @@ import {
|
||||
import { WebSocketStream } from "@hediet/typed-json-rpc-websocket";
|
||||
import { ConsoleRpcLogger } from "@hediet/typed-json-rpc";
|
||||
import { observable, action } from "mobx";
|
||||
import { Barrier } from "@hediet/std/synchronization";
|
||||
import { DataExtractorId } from "@hediet/debug-visualizer-data-extraction";
|
||||
|
||||
declare const window: Window & {
|
||||
serverPort?: number;
|
||||
};
|
||||
|
||||
interface VSCodeApi<TState> {
|
||||
getState(): Promise<TState | undefined>;
|
||||
setState(state: TState): Promise<void>;
|
||||
}
|
||||
|
||||
type IncomingCommand = {
|
||||
command: "getStateResult";
|
||||
state: unknown;
|
||||
};
|
||||
|
||||
type OutgoingCommand =
|
||||
| {
|
||||
command: "setState";
|
||||
state: unknown;
|
||||
}
|
||||
| { command: "getState" };
|
||||
|
||||
class IFrameVSCodeApi<TState> implements VSCodeApi<TState> {
|
||||
private currentGetStatePromise: Barrier<TState> | undefined = undefined;
|
||||
|
||||
constructor() {
|
||||
window.addEventListener("message", event => {
|
||||
const data = event.data as IncomingCommand;
|
||||
if (data.command === "getStateResult") {
|
||||
this.currentGetStatePromise!.unlock(data.state as any);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private sendCommand(command: OutgoingCommand) {
|
||||
window.parent.postMessage(command, "*");
|
||||
}
|
||||
|
||||
getState(): Promise<TState | undefined> {
|
||||
this.sendCommand({ command: "getState" });
|
||||
if (this.currentGetStatePromise) {
|
||||
throw new Error("Get state already in progress.");
|
||||
}
|
||||
this.currentGetStatePromise = new Barrier();
|
||||
return this.currentGetStatePromise.onUnlocked;
|
||||
}
|
||||
|
||||
async setState(state: TState): Promise<void> {
|
||||
this.sendCommand({ command: "setState", state });
|
||||
}
|
||||
}
|
||||
|
||||
export class Model {
|
||||
port: number;
|
||||
|
||||
@@ -22,6 +71,14 @@ export class Model {
|
||||
| typeof debugVisualizerUIContract.TServerInterface
|
||||
| undefined = undefined;
|
||||
|
||||
private readonly vsCodeApi = new IFrameVSCodeApi<{ expression: string }>();
|
||||
|
||||
@observable private _loading = false;
|
||||
|
||||
public get loading(): boolean {
|
||||
return this._loading;
|
||||
}
|
||||
|
||||
constructor() {
|
||||
if (window.serverPort) {
|
||||
this.port = window.serverPort;
|
||||
@@ -35,6 +92,12 @@ export class Model {
|
||||
}
|
||||
|
||||
this.stayConnected();
|
||||
|
||||
this.vsCodeApi.getState().then(state => {
|
||||
if (state) {
|
||||
this.setExpression(state.expression);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@action
|
||||
@@ -43,6 +106,19 @@ export class Model {
|
||||
if (this.server) {
|
||||
this.server.setExpression({ newExpression });
|
||||
}
|
||||
|
||||
this.vsCodeApi.setState({
|
||||
expression: newExpression,
|
||||
});
|
||||
}
|
||||
|
||||
setPreferredExtractorId(id: DataExtractorId) {
|
||||
console.log(id);
|
||||
if (this.server) {
|
||||
this.server.setPreferredDataExtractor({
|
||||
dataExtractorId: id,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
refresh() {
|
||||
@@ -65,7 +141,10 @@ export class Model {
|
||||
new ConsoleRpcLogger(),
|
||||
{
|
||||
updateState: ({ newState }) => {
|
||||
this.state = newState;
|
||||
this._loading = newState.kind === "loading";
|
||||
if (!this._loading) {
|
||||
this.state = newState;
|
||||
}
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
@@ -25,6 +25,7 @@ export function SampleTreeVisualizer() {
|
||||
viewModel,
|
||||
root.parentPropertyName,
|
||||
root.name,
|
||||
undefined,
|
||||
children
|
||||
);
|
||||
for (const c of children) {
|
||||
|
||||
@@ -36,8 +36,9 @@ export class TreeViewModel {
|
||||
if (this.lastUpdateVersion === this.version) {
|
||||
return;
|
||||
}
|
||||
this.lastUpdateVersion = this.version;
|
||||
|
||||
this.lastUpdateVersion = this.version;
|
||||
console.log("update");
|
||||
this.root.updateOffsets();
|
||||
}
|
||||
|
||||
@@ -81,6 +82,8 @@ export class TreeViewModel {
|
||||
}
|
||||
}
|
||||
|
||||
let i = 0;
|
||||
|
||||
export class TreeNodeViewModel {
|
||||
public parent: TreeNodeViewModel | null = null;
|
||||
|
||||
@@ -191,8 +194,8 @@ export class TreeWithPathView extends React.Component<{
|
||||
|
||||
@observer
|
||||
export class TreeView extends React.Component<{ model: TreeViewModel }> {
|
||||
componentWillUpdate() {
|
||||
this.props.model.stop();
|
||||
componentWillUpdate(newProps: this["props"]) {
|
||||
newProps.model.stop();
|
||||
}
|
||||
|
||||
componentWillMount() {
|
||||
|
||||
@@ -14,6 +14,18 @@ body {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.bp3-input,
|
||||
.bp3-button {
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
.bp3-icon {
|
||||
svg {
|
||||
color: lighten(black, 20);
|
||||
width: 14px;
|
||||
}
|
||||
}
|
||||
|
||||
html,
|
||||
body,
|
||||
.target {
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
import { contract, notificationContract, types } from "@hediet/typed-json-rpc";
|
||||
import { DataExtractionResult } from "@hediet/debug-visualizer-data-extraction";
|
||||
import {
|
||||
DataExtractionResult,
|
||||
DataExtractorId,
|
||||
} from "@hediet/debug-visualizer-data-extraction";
|
||||
|
||||
function unchecked<T>(): types.Type<T, T, unknown> {
|
||||
return new types.Type<T, T, unknown>(
|
||||
@@ -28,6 +31,11 @@ export const debugVisualizerUIContract = contract({
|
||||
}),
|
||||
},
|
||||
server: {
|
||||
setPreferredDataExtractor: notificationContract({
|
||||
params: types.type({
|
||||
dataExtractorId: unchecked<DataExtractorId>(),
|
||||
}),
|
||||
}),
|
||||
setExpression: notificationContract({
|
||||
params: types.type({
|
||||
newExpression: types.string,
|
||||
|
||||
Reference in New Issue
Block a user