Adds support for dark themes.

This commit is contained in:
Henning Dieterichs
2020-02-20 13:51:22 +01:00
parent dcdb1c43d5
commit 4f121b6fde
12 changed files with 395 additions and 287 deletions

View File

@@ -4,6 +4,12 @@
// For further information visit https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md
"version": "0.2.0",
"configurations": [
{
"type": "java",
"name": "CodeLens (Launch) - DemoLinkedLists",
"request": "launch",
"mainClass": "DemoLinkedLists"
},
{
"type": "node",
"request": "launch",
@@ -16,7 +22,7 @@
"type": "node",
"request": "launch",
"name": "TypeScript - Demo: Doubly Linked List",
"program": "${workspaceFolder}/js/dist/demo-doubly-linked-list",
"program": "${workspaceFolder}/js/dist/demo_doubly-linked-list",
"skipFiles": ["<node_internals>/**"],
"preLaunchTask": "typescript-watch"
},
@@ -64,6 +70,15 @@
"xdebugSettings": {
"max_data": -1
}
},
{
"name": "Java - Demo",
"type": "java",
"request": "launch",
"mainClass": "DemoLinkedLists.java",
"classPaths": ["${workspaceFolder}/java/json-simple-1.1.1.jar"],
"sourcePaths": ["${workspaceFolder}/java/"],
"cwd": "${workspaceFolder}/java"
}
]
}

View File

@@ -69,6 +69,7 @@ export class ConnectionHandler {
throwIfNotAuthenticated();
const url = server.getIndexUrl({
mode: "standalone",
expression: this.watcher
? this.watcher.expression
: undefined,

View File

@@ -34,13 +34,18 @@ export class Server {
});
}
public getIndexUrl(args: { expression?: string } = {}): string {
public getIndexUrl(args: {
expression?: string;
mode: "standalone" | "webViewIFrame";
}): string {
const port = process.env.USE_DEV_UI ? 8080 : this.port;
const expr =
args.expression !== undefined
? `&expression=${encodeURIComponent(args.expression)}`
: "";
return `http://localhost:${port}/index.html?serverPort=${this.port}&serverSecret=${this.secret}${expr}`;
const inWebView =
args.mode === "standalone" ? "" : "&mode=webViewIFrame";
return `http://localhost:${port}/index.html?serverPort=${this.port}&serverSecret=${this.secret}${inWebView}${expr}`;
}
public get mainBundleUrl(): string {

View File

@@ -71,8 +71,10 @@ export function getHtml(server: Server) {
</head>
<body>
<script>
window.serverPort = ${server.port};
window.serverSecret = ${JSON.stringify(server.secret)};
window.webViewData = ${JSON.stringify({
serverSecret: server.secret,
serverPort: server.port,
})};
const api = window.VsCodeApi = acquireVsCodeApi();
window.addEventListener('message', event => {
if (event.source === window.frames[0]) {
@@ -89,7 +91,9 @@ export function getHtml(server: Server) {
</script>
${
isDev
? `<iframe sandbox="allow-top-navigation allow-scripts allow-same-origin allow-popups allow-pointer-lock allow-forms" src="${server.getIndexUrl()}"></iframe>`
? `<iframe sandbox="allow-top-navigation allow-scripts allow-same-origin allow-popups allow-pointer-lock allow-forms" src="${server.getIndexUrl(
{ mode: "webViewIFrame" }
)}"></iframe>`
: `<script type="text/javascript" src="${server.mainBundleUrl}"></script>`
}
</body>

View File

@@ -4,26 +4,34 @@ import { observer, disposeOnUnmount } from "mobx-react";
import { observable, action, autorun } from "mobx";
import * as monaco from "monaco-editor";
export function getModel(): monaco.editor.ITextModel {
return monaco.editor.createModel(
"",
"javascript",
monaco.Uri.parse(`file:///main.ts`)
);
}
@observer
export class ExpressionInput extends React.Component<{ model: Model }> {
@observable private editor: monaco.editor.IStandaloneCodeEditor | undefined;
@observable private contentHeight: number | undefined = undefined;
private model = getModel();
private model = monaco.editor.createModel(
"",
"javascript",
monaco.Uri.parse(`file:///main.ts`)
);
render() {
return (
<div className="component-monaco-editor">
<div
className="component-monaco-editor"
style={{
// The monaco editor does not have a padding, so we add our own.
// We have to match the colors of monaco's background.
backgroundColor:
this.props.model.theme === "light"
? "white"
: "#263238",
}}
>
<div
style={{ height: this.contentHeight }}
className="part-editor"
style={{
height: this.contentHeight,
}}
ref={this.setEditorDiv}
/>
</div>
@@ -68,8 +76,11 @@ export class ExpressionInput extends React.Component<{ model: Model }> {
horizontalScrollbarSize: 0,
verticalScrollbarSize: 0,
},
/*theme: { dark: "vs-dark", light: "vs-light" }[
this.props.model.theme
],*/
});
//
this.editor.onDidContentSizeChange(e => {
this.contentHeight = e.contentHeight;
});

View File

@@ -1,93 +1,138 @@
import React = require("react");
import { Button, Spinner } from "@blueprintjs/core";
import { observable } from "mobx";
import { observable, action } 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";
import { VisualizerHeaderDetails } from "./VisualizerHeaderDetails";
@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
className="component-GUI"
tabIndex={0}
style={{
display: "flex",
flexDirection: "column",
height: "100%",
}}
>
<div className="part-Header">
<VisualizerHeader model={m} />
</div>
<div className="part-visualizer">
<div
className="part-Visualizer"
style={{ flex: 1, minHeight: 0 }}
>
<Visualizer model={m} />
</div>
</div>
);
}
}
renderValue(value: string) {
return <div>{`${value.length}`}</div>;
@observer
export class VisualizerHeader extends React.Component<{ model: Model }> {
@observable expanded = false;
render() {
const m = this.props.model;
return (
<div
className="component-VisualizerHeader"
style={{ display: "flex", flexDirection: "row" }}
>
<div
className={classnames(
"part-ExpandButton",
this.expanded && "expanded"
)}
onClick={this.toggleExpanded}
/>
<div
className="part-HeaderContent"
style={{
flex: 1,
minWidth: 0,
display: "flex",
flexDirection: "column",
}}
>
<div className="part-HeaderMain">
<VisualizerHeaderMain model={m} />
</div>
{this.expanded && (
<>
<div style={{ height: 6 }} />
<VisualizerHeaderDetails model={m} />
</>
)}
</div>
</div>
);
}
@action.bound
toggleExpanded() {
this.expanded = !this.expanded;
}
}
export class ExpandButton extends React.Component<{
expanded: boolean;
setExpanded: (val: boolean) => void;
}> {
export class VisualizerHeaderMain extends React.Component<{ model: Model }> {
render() {
const m = this.props.model;
return (
<div
onClick={() => this.props.setExpanded(!this.props.expanded)}
className={classnames(
"component-ExpandButton",
this.props.expanded && "expanded"
className="component-VisualizerHeaderMain"
style={{
display: "flex",
alignItems: "center",
}}
>
<div
className="part-ExpressionInput "
style={{ flex: 1, minWidth: 0 }}
>
<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>
);
}
}

View File

@@ -7,7 +7,7 @@ import { Model } from "../Model/Model";
import { VisualizationId } from "../Visualizers/Visualizer";
@observer
export class ExpandedOptions extends React.Component<{ model: Model }> {
export class VisualizerHeaderDetails extends React.Component<{ model: Model }> {
render() {
const m = this.props.model;
let availableExtractors = new Array<DataExtractorInfo>();
@@ -38,47 +38,47 @@ export class ExpandedOptions extends React.Component<{ model: Model }> {
}
return (
<>
<div style={{ height: 6 }} />
<div className="part-header-options">
<NamedSelect
name="Source"
selected={0}
options={[{ label: "js" }]}
onSelected={item => {}}
/>
<div style={{ width: 10 }} />
<div
className="component-VisualizerHeaderDetails"
style={{ display: "flex", alignItems: "center" }}
>
<NamedSelect
name="Source"
selected={0}
options={[{ label: "js" }]}
onSelected={item => {}}
/>
<div style={{ width: 10 }} />
<NamedSelect
name="Extractor"
selected={selectedExtractor}
options={availableExtractors.map(e => ({
label: e.name,
id: e.id,
}))}
onSelected={item => {
m.setPreferredExtractorId(item.id);
}}
/>
<div style={{ width: 10 }} />
<NamedSelect
name="Extractor"
selected={selectedExtractor}
options={availableExtractors.map(e => ({
label: e.name,
id: e.id,
}))}
onSelected={item => {
m.setPreferredExtractorId(item.id);
}}
/>
<div style={{ width: 10 }} />
<NamedSelect
name="Visualizer"
selected={selectedVisualization}
onSelected={item => {
m.setPreferredVisualizationId(item.id);
}}
options={
m.visualizations
? m.visualizations.allVisualizations.map(v => ({
label: v.name,
id: v.id,
}))
: []
}
/>
</div>
</>
<NamedSelect
name="Visualizer"
selected={selectedVisualization}
onSelected={item => {
m.setPreferredVisualizationId(item.id);
}}
options={
m.visualizations
? m.visualizations.allVisualizations.map(v => ({
label: v.name,
id: v.id,
}))
: []
}
/>
</div>
);
}
}
@@ -90,11 +90,18 @@ function NamedSelect<T extends SelectableItem>(props: {
options: T[];
}) {
return (
<div className="component-NamedSelect">
<div className="part-Select">
<div
className="component-NamedSelect"
style={{
display: "flex",
flexDirection: "column",
alignItems: "center",
}}
>
<div className="part-Select" style={{ minWidth: 100 }}>
<Select {...props} />
</div>
<div>{props.name}</div>
<div className="part-Name">{props.name}</div>
</div>
);
}
@@ -121,14 +128,14 @@ class Select<T extends SelectableItem> extends React.Component<{
modifiers={{ arrow: { enabled: false } }}
usePortal={false}
>
<button className="part-button">
<button className="part-Button">
{this.selected ? this.selected.label : "(none)"}
</button>
<div className={"part-content"}>
<div className="part-Items">
{this.props.options.map((o, idx) => (
<div
key={idx}
className="part-option"
className="part-Item"
onClick={() => this.props.onSelected(o)}
>
{o.label}

View File

@@ -4,19 +4,27 @@ import {
} from "@hediet/debug-visualizer-vscode-shared";
import { WebSocketStream } from "@hediet/typed-json-rpc-websocket";
import { ConsoleRpcLogger } from "@hediet/typed-json-rpc";
import { observable, action, computed, when } from "mobx";
import { observable, action, computed, when, autorun } from "mobx";
import { DataExtractorId } from "@hediet/debug-visualizer-data-extraction";
import { Visualization, VisualizationId } from "../Visualizers/Visualizer";
import { knownVisualizations } from "../Visualizers";
import { getApi as getVsCodeApi } from "./VsCodeApi";
import { MonacoBridge } from "./MonacoBridge";
import { Disposable } from "@hediet/std/disposable";
import { startInterval } from "@hediet/std/timer";
declare const window: Window & {
serverPort?: number;
serverSecret?: string;
webViewData?: { serverSecret: string; serverPort: number };
};
export class Model {
public readonly dispose = Disposable.fn();
public readonly runningMode: "standalone" | "webView" | "webViewIFrame" =
"webView";
@observable
public theme: "dark" | "light" = "light";
private port: number;
private serverSecret: string;
@@ -67,9 +75,25 @@ export class Model {
private readonly _bridge = new MonacoBridge(this);
constructor() {
if (window.serverPort) {
this.port = window.serverPort;
this.serverSecret = window.serverSecret!;
if (window.webViewData) {
const data = window.webViewData;
this.port = data.serverPort;
this.serverSecret = data.serverSecret;
this.runningMode = "webView";
const updateTheme = () => {
const isLight = document.body.classList.contains(
"vscode-light"
);
this.theme = isLight ? "light" : "dark";
};
updateTheme();
this.dispose.track(
startInterval(1000, () => {
updateTheme();
})
);
} else {
const url = new URL(window.location.href);
const portStr = url.searchParams.get("serverPort");
@@ -88,6 +112,20 @@ export class Model {
throw new Error("Server secret not set.");
}
this.serverSecret = secret;
const theme = url.searchParams.get("theme");
if (theme && theme === "dark") {
this.theme = "dark";
} else {
this.theme = "light";
}
const mode = url.searchParams.get("mode");
if (mode) {
this.runningMode = mode as any;
} else {
this.runningMode = "standalone";
}
}
this.stayConnected();

View File

@@ -1,6 +1,7 @@
import * as monaco from "monaco-editor";
import { Model } from "./Model";
import { Disposable } from "@hediet/std/disposable";
import { autorun } from "mobx";
export class MonacoBridge {
public readonly dispose = Disposable.fn();
@@ -41,6 +42,14 @@ export class MonacoBridge {
new DebugSessionCompletionProvider(this.model)
)
);
this.dispose.track({
dispose: autorun(() => {
monaco.editor.setTheme(
model.theme === "light" ? "vs-light" : "vs-dark"
);
}),
});
}
}
@@ -82,10 +91,12 @@ class DebugSessionCompletionProvider
if (!this.model.server) {
return { suggestions: [] };
}
const completions = (await this.model.server.getCompletions({
text: expression,
column: position.column,
})).completions;
const completions = (
await this.model.server.getCompletions({
text: expression,
column: position.column,
})
).completions;
const p = model.getWordAtPosition(position);

View File

@@ -126,3 +126,16 @@
flex: 1;
}
}
.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");
}
}

View File

@@ -6,6 +6,14 @@ import Components = require("./Components/GUI");
const model = new Model();
if (model.runningMode !== "webView") {
if (model.theme === "light") {
require("./vscode-light.scss");
} else {
require("./vscode-dark.scss");
}
}
function render(target: HTMLDivElement) {
const c = require("./Components/GUI") as typeof Components;
ReactDOM.render(<c.GUI model={model} />, target);

View File

@@ -1,9 +1,13 @@
@import "./vscode-light.scss";
@import "~normalize.css";
@import "~@blueprintjs/core/lib/css/blueprint.css";
@import "~@blueprintjs/icons/lib/css/blueprint-icons.css";
html,
body,
.target {
height: 100%;
}
body {
font-family: "Segoe UI", Tahoma, Geneva, Verdana, sans-serif;
margin: 0px;
@@ -12,6 +16,8 @@ body {
margin: 0;
padding: 0;
background-color: var(--vscode-editor-background);
}
.bp3-input,
@@ -26,10 +32,95 @@ body {
}
}
html,
body,
.target {
height: 100%;
.component-GUI {
& > .part-Header {
padding: 6px 20px 6px 0;
background-color: var(--vscode-sideBar-background);
// border-top: solid 1px var(--vscode-button-background);
// rgba(128, 128, 128, 0.15)
}
}
.component-VisualizerHeader {
.part-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-VisualizerHeaderMain {
.part-Icon {
margin-left: 4px;
svg {
fill: var(--color);
}
}
}
.component-NamedSelect {
.part-Name {
color: var(--color);
}
}
.component-Select {
.part-Button {
background-color: var(--vscode-button-background);
color: var(--vscode-button-foreground);
padding: 4px 10px;
width: 100%;
border: none;
cursor: pointer;
&:hover,
&:focus {
background-color: var(--vscode-button-hoverBackground);
}
}
.part-Items {
background-color: var(--vscode-sideBar-background);
.part-Item {
//color: black;
color: var(--color);
padding: 12px 16px;
text-decoration: none;
display: block;
cursor: pointer;
&:hover {
background-color: var(
--vscode-titleBar-activeBackground
); // #ddd;
}
}
}
.bp3-popover-wrapper {
width: 100%;
}
.bp3-popover-target {
width: 100%;
}
}
.component-Visualizer {
@@ -49,7 +140,7 @@ body,
height: 100%;
rect {
fill: white;
fill: var(--vscode-editor-background);
}
line {
@@ -57,76 +148,19 @@ body,
}
text {
stroke: black;
fill: var(--vscode-editor-foreground);
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;
min-width: 0;
transition: flex 0.3s ease-out;
.part-header-main,
.part-header-options {
display: flex;
align-items: center;
.part-expression-input {
min-width: 0;
//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%;
}
}
.component-monaco-editor {
width: 100%;
min-width: 0;
box-sizing: border-box;
display: flex;
padding: 2px;
background-color: white;
border: 0.5px solid gray;
.part-editor {
min-width: 0;
@@ -142,90 +176,6 @@ body,
}
}
.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);
.part-option {
color: black;
padding: 12px 16px;
text-decoration: none;
display: block;
cursor: pointer;
&:hover {
background-color: #ddd;
}
}
}
.bp3-popover-wrapper {
width: 100%;
}
.bp3-popover-target {
width: 100%;
}
}
.svgViewer {
width: 100%;
height: 100%;