Skip to content
440 changes: 184 additions & 256 deletions src/App.tsx

Large diffs are not rendered by default.

16 changes: 16 additions & 0 deletions src/Connection.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
export enum ConnectionState {
Disconnected,
Connecting,
Connected,
}

export function toString(state: ConnectionState) {
switch (state) {
case ConnectionState.Disconnected:
return 'Disconnected';
case ConnectionState.Connecting:
return 'Connecting';
case ConnectionState.Connected:
return 'Connected';
}
}
Empty file.
103 changes: 103 additions & 0 deletions src/components/SettingsPanel/SettingsPanel.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
import React from 'react';

import {
PrimaryButton,
Toggle,
TextField,
ITextField,
Stack as Stacker,
IStackTokens,
Pivot,
PivotItem,
IRefObject,
} from '@fluentui/react';
import { ConnectionState, toString } from '../../Connection';

import styles from "./SettingsPanel.module.scss";

const stackWithVerticalGap: IStackTokens = {
childrenGap: 10,
padding: "10px 0",
};


const defaultAddress = 'ws://localhost:9002';

class MainControlsProps {
connected: ConnectionState = ConnectionState.Disconnected;
addressRef?: IRefObject<ITextField>;
onReconnect: () => void = () => { };
onAutoReconnectChanged: (autoReconnect: boolean) => void = () => { };
}

function MainControls(props: MainControlsProps) {

const [autoReconnect, setAutoReconnect] = React.useState(true);

return (
<Stacker className="app-window">
<p>Connection status: <span>{toString(props.connected)}</span></p>
<Toggle label="Auto reconnect" defaultChecked onText="On" offText="Off"
onChange={(e) => {
props.onAutoReconnectChanged(!autoReconnect);
setAutoReconnect(!autoReconnect);
}}
/>
<TextField componentRef={props.addressRef} label="VM Address" required defaultValue={defaultAddress} placeholder="example: ws://localhost:9002" />
<Stacker.Item align="start" tokens={stackWithVerticalGap}>
<PrimaryButton text="Reconnect" onClick={props.onReconnect} />
</Stacker.Item>
</Stacker>
);
}

class ThemeSettingsProps {
onToggledDarkTheme: (isDarkTheme: boolean) => void = () => { };
defaultTheme: 'dark' | 'light' = 'dark';
};

function ThemeSettings(props: ThemeSettingsProps) {
const [useDarkTheme, setDarkTheme] = React.useState(props.defaultTheme === 'dark');

return (
<Stacker className={styles.splitterPanel}>
<Toggle label="Use dark mode" defaultChecked onText="On" offText="Off"
onChange={(e) => {
props.onToggledDarkTheme?.(!useDarkTheme);
setDarkTheme(!useDarkTheme);
}}
/>
</Stacker>
);
}

export function SettingsPanel({connected, reconnect, setUseDarkTheme, serverAddressRef, onAutoReconnectChanged}: any) {
return (
<Pivot aria-label="Settings panel">
<PivotItem
headerText="Main controls"
headerButtonProps={{
'data-order': 1,
'data-title': 'Main controls',
}}
itemIcon="FabricMDL2Icons"
>
<MainControls
connected={connected}
onReconnect={reconnect}
addressRef={serverAddressRef}
onAutoReconnectChanged={onAutoReconnectChanged}
/>
</PivotItem>
<PivotItem
headerText="Theme"
headerButtonProps={{
'data-order': 2,
'data-title': 'Theme',
}}
>
<ThemeSettings onToggledDarkTheme={(isDarkTheme) => setUseDarkTheme(isDarkTheme)} defaultTheme='dark' />
</PivotItem>
</Pivot>
);
}
68 changes: 68 additions & 0 deletions src/components/SplitPane/SplitPane.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@

import React from 'react';
import Splitter, { SplitDirection } from '@devbookhq/splitter';

// import cookies
import {
setCookie,
getCookie,
} from "../../helper/Cookies";


export interface SizedSplitPaneProps {
children: React.ReactNode;
initialSize: [number, number];
minimalSize: [number, number];
direction: "x" | "y";
cookieName?: string;
}


function translateDirection(direction: "x" | "y"): SplitDirection {
if (direction === "x") {
return SplitDirection.Horizontal;
}
return SplitDirection.Vertical;
}

export default function SplitPane({
children,
initialSize,
minimalSize,
direction,
cookieName
}: SizedSplitPaneProps)
{
const [sizes, setSizes] = React.useState<[number, number]>(initialSize);

React.useEffect(() => {
if (cookieName) {
const cookie = getCookie(cookieName);
if (cookie) {
setSizes(JSON.parse(cookie));
}
}
}, [cookieName]);

const handleResize = (sizes: [number, number]) => {
setSizes(sizes);
if (cookieName) {
setCookie(cookieName, JSON.stringify(sizes), 365);
}
};

return (
<Splitter direction={translateDirection(direction)}
initialSizes={sizes}
onResizeFinished={(_, s) => handleResize(s as [number, number])}
{...(direction === "x"
?
{ minWidths: minimalSize }
:
{ minHeights: minimalSize }
)}
>
{children}
</Splitter>
);
}
1 change: 1 addition & 0 deletions src/components/SplitPane/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default } from './SplitPane';
16 changes: 7 additions & 9 deletions src/components/Stack/Window.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import React from 'react';
import { SplitPane } from '../../components';
import styles from '../../App.module.scss';

import Splitter, { SplitDirection } from '@devbookhq/splitter';

import StackMemoryView from './MemoryView';
import StackWatchWindow, { Stack } from './Watch';

Expand All @@ -13,24 +12,23 @@ interface StackWindowProps {
export default function StackWindow(props: StackWindowProps) {

const [highlightedAddresses, setHighlightedAddresses] = React.useState<[number, number]>();
const [sizes, setSizes] = React.useState<[number, number]>([70, 30]);

return (
<Splitter direction={SplitDirection.Horizontal}
initialSizes={sizes}
minWidths={[100, 100]}
onResizeFinished={(idx, s) => {setSizes([ s[0], s[1] ])}}
<SplitPane direction="x"
cookieName="SplitPaneSize_StackWindow"
initialSize={[50, 50]}
minimalSize={[100, 100]}
>
<div className={styles.splitterPanel}>
{<StackMemoryView highlightRange={highlightedAddresses}/>}
</div>
<div className={styles.splitterPanel}>
<StackWatchWindow
values={props.stackValues}
values={props.stackValues}
onValueHovered={(_, addr) => setHighlightedAddresses([addr.address, addr.size])}
onValueUnhovered={() => setHighlightedAddresses(undefined)}
/>
</div>
</Splitter>
</SplitPane>
);
}
1 change: 1 addition & 0 deletions src/components/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default as SplitPane } from './SplitPane';
24 changes: 24 additions & 0 deletions src/helper/Cookies.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
export function setCookie(name: string, value: any, days: number): void {
let expires = "";
if (days) {
let date = new Date();
date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000));
expires = `; expires=${date.toUTCString()}`;
}
document.cookie = `${name}=${value || ""}${expires}; path=/`;
}

export function getCookie(name: string): string | null {
var nameEQ = name + "=";
var ca = document.cookie.split(';');
for (var i = 0; i < ca.length; i++) {
var c = ca[i];
while (c.charAt(0) === ' ') c = c.substring(1, c.length);
if (c.indexOf(nameEQ) === 0) return c.substring(nameEQ.length, c.length);
}
return null;
}

export function eraseCookie(name: string): void {
document.cookie = `${name}=; Path=/; Expires=Thu, 01 Jan 1970 00:00:01 GMT;`;
}
24 changes: 14 additions & 10 deletions src/index.css
Original file line number Diff line number Diff line change
@@ -1,15 +1,19 @@
body {
margin: 0;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
background-color: rgb(44, 44, 44);
color: white;
margin: 0;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
background-color: rgb(44, 44, 44);
color: white;
}

code {
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
monospace;
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
monospace;
}

.app-window {
padding: 10px;
}