Progress.

This commit is contained in:
Henning Dieterichs
2019-08-01 20:18:36 +02:00
parent 52db41558e
commit 99a0f854a3
9 changed files with 249 additions and 53 deletions

View File

@@ -37,6 +37,11 @@ export class ConnectionHandler {
openInBrowser: async ({}) => {
open(server.indexUrl);
},
setPreferredDataExtractor: async ({ dataExtractorId }) => {
if (this.watcher) {
this.watcher.setPreferredDataExtractor(dataExtractorId);
}
},
}
);

View File

@@ -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,

View File

@@ -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>
`;

View File

@@ -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>

View File

@@ -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;
}
},
}
);

View File

@@ -25,6 +25,7 @@ export function SampleTreeVisualizer() {
viewModel,
root.parentPropertyName,
root.name,
undefined,
children
);
for (const c of children) {

View File

@@ -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() {

View File

@@ -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 {

View File

@@ -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,