From 25317a66f9eca55b42da843cc8375a92dd8b59b2 Mon Sep 17 00:00:00 2001 From: PoetaKodu Date: Fri, 9 Sep 2022 18:27:18 +0200 Subject: [PATCH 01/10] fix: out-of-bounds related crashes --- src/components/Stack/MemoryView.tsx | 4 ++-- src/components/Stack/Watch.tsx | 3 +++ 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/components/Stack/MemoryView.tsx b/src/components/Stack/MemoryView.tsx index de35ae0..682d5ce 100644 --- a/src/components/Stack/MemoryView.tsx +++ b/src/components/Stack/MemoryView.tsx @@ -60,7 +60,7 @@ class StackView extends React.Component console.log("Highlighted elem key: ", highlightedElem.key, typeof +highlightedElem.key); const len = this.props.highlightRange ? this.props.highlightRange[1] : 0; - if (len > 0) { + if (len > 0 && stackContent.length > +highlightedElem.key + len) { const view = viewBytes(stackContent, +highlightedElem.key, len); if (len === 1) { console.log("Reading bytes: ", view.getInt8(0)); @@ -81,4 +81,4 @@ class StackView extends React.Component ); } -} \ No newline at end of file +} diff --git a/src/components/Stack/Watch.tsx b/src/components/Stack/Watch.tsx index ece645f..731da28 100644 --- a/src/components/Stack/Watch.tsx +++ b/src/components/Stack/Watch.tsx @@ -49,6 +49,9 @@ class WatchWindow extends React.Component render() { const viewValue = (value: any) => { + if (this.context.memory.length <= value.address + value.size) + return "?"; + if (value.size === 1) { const val = this.context.memory[value.address]; From ac4843a6abd8286f33e0dd9138bee387962da394 Mon Sep 17 00:00:00 2001 From: PoetaKodu Date: Fri, 9 Sep 2022 18:29:46 +0200 Subject: [PATCH 02/10] refactor: `StackWindow` uses a non-default context provider (from app state) --- src/App.tsx | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/src/App.tsx b/src/App.tsx index e9d36bb..067b46a 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -26,6 +26,7 @@ import StackWindow from './components/Stack/Window'; import LogWindow, { ILogEntry } from './components/Log/Window'; import LogData, { logMockupData } from './components/Log/Context'; +import StackData from './components/Stack/Context'; enum ConnectionState { Disconnected, @@ -50,6 +51,7 @@ type AppState = { useDarkTheme: boolean; highlightedAddresses?: [number, number]; callStack: CallstackItem[]; + memory: Int8Array; } const stackWithVerticalGap : IStackTokens = { @@ -116,7 +118,14 @@ export default class App extends React.Component { this.reconnect = this.reconnect.bind(this); this.state = { - callStack: [], + callStack: [], + memory: new Int8Array([ + 53, 0, 0, 0, + 12, 237, 0, 255, + 0, 11, 40, 0, + 19, 0, 8, 0, + + ]), connected: ConnectionState.Disconnected, useDarkTheme: true, } @@ -226,7 +235,9 @@ export default class App extends React.Component { Learn React - + + + Date: Sat, 17 Sep 2022 23:32:51 +0200 Subject: [PATCH 03/10] feat: save split-pane sizes --- src/App.tsx | 51 +++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 39 insertions(+), 12 deletions(-) diff --git a/src/App.tsx b/src/App.tsx index 17ac0c3..3eb07a1 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -47,15 +47,6 @@ function toString(state: ConnectionState) { } -type AppState = { - connected: ConnectionState; - useDarkTheme: boolean; - highlightedAddresses?: [number, number]; - callStack: CallstackItem[]; - stack: any[]; - memory: Int8Array; -} - const stackWithVerticalGap: IStackTokens = { childrenGap: 10, padding: "10px 0", @@ -102,6 +93,21 @@ function ThemeSettings(props: ThemeSettingsProps) { ); } +type AppState = { + connected: ConnectionState; + useDarkTheme: boolean; + highlightedAddresses?: [number, number]; + callStack: CallstackItem[]; + stack: any[]; + memory: Int8Array; + + splitPanes: { + rootHorizontal: [number, number], + mainVertical: [number, number], + mainTopHorizontal: [number, number], + } +} + export default class App extends React.Component { ws: WebSocket | null; @@ -120,6 +126,11 @@ export default class App extends React.Component { this.reconnect = this.reconnect.bind(this); this.state = { + splitPanes: { + rootHorizontal: [20, 80], + mainTopHorizontal: [70, 30], + mainVertical: [70, 30], + }, stack: [], callStack: [], memory: new Int8Array([ @@ -241,20 +252,36 @@ export default class App extends React.Component { } render() { + type SplitPaneType = keyof AppState['splitPanes']; + const updatePanelSizes = (name: SplitPaneType, prevState: any, newSizes: number[]) => + ({ + ...prevState, + splitPanes: { + ...prevState.splitPanes, + [name]: newSizes as [number, number] + }, + }); + const resizeFinishedHandler = (name: SplitPaneType) => { + return (_: any, newSizes: number[]) => this.setState(prevState => updatePanelSizes(name, prevState, newSizes)); + } + return (
{this.leftPanel()}
From 0566d0e09f6b080b658e63ba6648007a388ee141 Mon Sep 17 00:00:00 2001 From: PoetaKodu Date: Thu, 6 Oct 2022 22:24:07 +0200 Subject: [PATCH 04/10] fix: invalid prop handling --- src/App.tsx | 396 +++++++++++++++++++++++++--------------------------- 1 file changed, 193 insertions(+), 203 deletions(-) diff --git a/src/App.tsx b/src/App.tsx index 3eb07a1..4aa9efe 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -93,6 +93,12 @@ function ThemeSettings(props: ThemeSettingsProps) { ); } +type AppPanes = { + rootHorizontal: [number, number]; + mainVertical: [number, number]; + mainTopHorizontal: [number, number]; +} + type AppState = { connected: ConnectionState; useDarkTheme: boolean; @@ -100,144 +106,185 @@ type AppState = { callStack: CallstackItem[]; stack: any[]; memory: Int8Array; +} - splitPanes: { - rootHorizontal: [number, number], - mainVertical: [number, number], - mainTopHorizontal: [number, number], - } +export function LeftPanel({connected, reconnect, setUseDarkTheme, serverAddressRef}: any) { + return ( + + + + + + setUseDarkTheme(isDarkTheme)} defaultTheme='dark' /> + + + ); } -export default class App extends React.Component { +export interface SizedSplitPaneProps { + children: React.ReactNode; + initialSize: [number, number]; + minimalSize: [number, number]; + direction: SplitDirection; +} - ws: WebSocket | null; - serverAddressRef: React.RefObject; +export interface SplitPaneProps { + children: React.ReactNode; +} - logList: React.RefObject; - logEntries: ILogEntry[] = []; +export function SizedSplitPane({children, initialSize, minimalSize, direction}: SizedSplitPaneProps) { + const [sizes, setSizes] = React.useState<[number, number]>(initialSize); + return ( + setSizes(s as [number, number])} + {...(direction === SplitDirection.Horizontal + ? + { minWidths: minimalSize } + : + { minHeights: minimalSize } + )} + > + {children} + + ); +} - constructor(props: any) { - super(props); +export function RootSplit({children}: SplitPaneProps) { + return ( + + {children} + + ); +} - this.ws = null; - this.serverAddressRef = React.createRef(); - this.logList = React.createRef(); +export function MainVerticalSplit({children}: SplitPaneProps) { + return ( + + {children} + + ); +} - this.reconnect = this.reconnect.bind(this); +export function MainTopHorizontalSplit({children}: SplitPaneProps) { + return ( + + {children} + + ); +} - this.state = { - splitPanes: { - rootHorizontal: [20, 80], - mainTopHorizontal: [70, 30], - mainVertical: [70, 30], - }, - stack: [], - callStack: [], - memory: new Int8Array([ - 53, 0, 0, 0, - 12, 237, 0, 255, - 0, 11, 40, 0, - 19, 0, 8, 0, - ]), - connected: ConnectionState.Disconnected, - useDarkTheme: true, - } - } +export default function App() { - setHoveredAddress(address?: [number, number]) { - this.setState({ - highlightedAddresses: address - }); - } - pushToLog(entry: ILogEntry) { - this.logEntries.push(entry); - this.logList.current?.forceUpdate(); + let logEntries: ILogEntry[] = []; + + let ws: WebSocket | null = null; + let serverAddressRef = React.createRef(); + let logList = React.createRef(); + + const [stack, setStack] = React.useState([]); + const [callStack, setCallStack] = React.useState([]); + const [memory, setMemory] = React.useState(new Int8Array([ + 53, 0, 0, 0, + 12, 237, 0, 255, + 0, 11, 40, 0, + 19, 0, 8, 0, + ])); + const [highlightedAddresses, setHighlightedAddresses] = React.useState<[number, number] | null>(null); + + const [connected, setConnected] = React.useState(ConnectionState.Disconnected); + const [useDarkTheme, setUseDarkTheme] = React.useState(true); + + const pushToLog = (entry: ILogEntry) => { + logEntries.push(entry); + logList.current?.forceUpdate(); } - handleStackRequest(json: any) { + const handleStackRequest = (json: any) => { if (json.action === 'pushFrame') { - this.setState(prev => { - const data = { - kind: 'frame', - ...(json.data as StackFrame) - } - return { ...prev, stack: [...prev.stack, data] } - }) + const data = { + kind: 'frame', + ...(json.data as StackFrame) + } + setStack([...stack, data]); } else if (json.action === 'popFrame') { - this.setState(prev => { - // FIXME: seems like this doesnt work - const isStackFrame = (item: any) => item.action === 'pushFrame'; - const lastStackFrameIndex = prev.stack.slice().reverse().findIndex(isStackFrame) - return { ...prev, stack: prev.stack.slice(0, lastStackFrameIndex) } - }) + // FIXME: seems like this doesnt work + const isStackFrame = (item: any) => item.action === 'pushFrame'; + const lastStackFrameIndex = stack.slice().reverse().findIndex(isStackFrame); + setStack(stack.slice(0, lastStackFrameIndex)); } else if (json.action === 'allocate') { - this.setState(prev => { - const data = { - kind: 'allocation', - ...(json.data as StackAllocation) - } - return { ...prev, stack: [...prev.stack, data] } - }) + const data = { + kind: 'allocation', + ...(json.data as StackAllocation) + } + setStack([...stack, data]); } } - reconnect() { + const reconnect = () => { - if (this.ws && this.ws.readyState === WebSocket.OPEN) { - this.ws.close(); + if (ws && ws.readyState === WebSocket.OPEN) { + ws.close(); } - this.setState({ - connected: ConnectionState.Connecting - }); + setConnected(ConnectionState.Connecting); const tryConnect = () => { - if (!this.serverAddressRef.current) + if (!serverAddressRef.current) return false; - const addr = this.serverAddressRef.current.value || ""; + const addr = serverAddressRef.current.value || ""; if (addr === "") return false; - this.ws = new WebSocket(addr); + ws = new WebSocket(addr); - this.ws.onopen = () => { - this.setState({ - connected: ConnectionState.Connected - }); - } + ws.onopen = () => { setConnected(ConnectionState.Connected); } + ws.onclose = () => { setConnected(ConnectionState.Disconnected); } - this.ws.onclose = () => { - this.setState({ - connected: ConnectionState.Disconnected - }); - } - this.ws.onmessage = (event: MessageEvent) => { + ws.onmessage = (event: MessageEvent) => { const json = JSON.parse(event.data); if (json.type === 'callstack') { if (json.action === 'push') { - this.setState(prev => { - return { ...prev, callStack: [...prev.callStack, json.data as CallstackItem] } - }) + setCallStack([...callStack, json.data as CallstackItem]); } else if (json.action === 'pop') { - this.setState(prev => { - return { ...prev, callStack: prev.callStack.slice(0, -1) } - }) + setCallStack(callStack.slice(0, -1)); } } else if (json.type === 'stack') { - this.handleStackRequest(json); + handleStackRequest(json); } else if (json.type === 'log') { - this.pushToLog(json.data as ILogEntry); - + pushToLog(json.data as ILogEntry); } } return true; @@ -245,119 +292,62 @@ export default class App extends React.Component { if (!tryConnect()) { - this.setState({ - connected: ConnectionState.Disconnected - }); + setConnected(ConnectionState.Disconnected); } } - render() { - type SplitPaneType = keyof AppState['splitPanes']; - const updatePanelSizes = (name: SplitPaneType, prevState: any, newSizes: number[]) => - ({ - ...prevState, - splitPanes: { - ...prevState.splitPanes, - [name]: newSizes as [number, number] - }, - }); - const resizeFinishedHandler = (name: SplitPaneType) => { - return (_: any, newSizes: number[]) => this.setState(prevState => updatePanelSizes(name, prevState, newSizes)); - } - - return ( - -
- - {this.leftPanel()} - - -
- logo -

- Edit src/App.js and save to reload. -

- - Learn React - -
- - - -
- - - - - - - + + + + + + + + + + + + + + + + + +
+
+ ); } From a43eab3ec51ca308ea28e6e00ff515f19ad7fab3 Mon Sep 17 00:00:00 2001 From: PoetaKodu Date: Fri, 7 Oct 2022 00:30:16 +0200 Subject: [PATCH 05/10] fix: `StackValuesView` works properly --- src/App.tsx | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/src/App.tsx b/src/App.tsx index 4aa9efe..ed3fa9a 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -219,6 +219,12 @@ export default function App() { const [connected, setConnected] = React.useState(ConnectionState.Disconnected); const [useDarkTheme, setUseDarkTheme] = React.useState(true); + React.useEffect(() => { + if (connected === ConnectionState.Connected) { + setStack([]); + } + }, [connected]); + const pushToLog = (entry: ILogEntry) => { logEntries.push(entry); logList.current?.forceUpdate(); @@ -230,21 +236,23 @@ export default function App() { kind: 'frame', ...(json.data as StackFrame) } - setStack([...stack, data]); + setStack(s => [...s, data]); } else if (json.action === 'popFrame') { // FIXME: seems like this doesnt work const isStackFrame = (item: any) => item.action === 'pushFrame'; - const lastStackFrameIndex = stack.slice().reverse().findIndex(isStackFrame); - setStack(stack.slice(0, lastStackFrameIndex)); + setStack(s => { + const lastStackFrameIndex = s.slice().reverse().findIndex(isStackFrame); + return s.slice(0, lastStackFrameIndex) + }); } else if (json.action === 'allocate') { const data = { kind: 'allocation', ...(json.data as StackAllocation) } - setStack([...stack, data]); + setStack(s => [...s, data]); } } From 3dde6277816445587b3967349c43c5de086663c8 Mon Sep 17 00:00:00 2001 From: PoetaKodu Date: Fri, 7 Oct 2022 00:51:50 +0200 Subject: [PATCH 06/10] feat: customized `SplitPane` component --- src/components/SplitPane/SplitPane.tsx | 68 ++++++++++++++++++++++++++ src/components/SplitPane/index.ts | 1 + src/components/index.ts | 1 + 3 files changed, 70 insertions(+) create mode 100644 src/components/SplitPane/SplitPane.tsx create mode 100644 src/components/SplitPane/index.ts create mode 100644 src/components/index.ts diff --git a/src/components/SplitPane/SplitPane.tsx b/src/components/SplitPane/SplitPane.tsx new file mode 100644 index 0000000..d1e40b1 --- /dev/null +++ b/src/components/SplitPane/SplitPane.tsx @@ -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 onResize = (sizes: [number, number]) => { + setSizes(sizes); + if (cookieName) { + setCookie(cookieName, JSON.stringify(sizes), 365); + } + }; + + return ( + onResize(s as [number, number])} + {...(direction === "x" + ? + { minWidths: minimalSize } + : + { minHeights: minimalSize } + )} + > + {children} + + ); +} \ No newline at end of file diff --git a/src/components/SplitPane/index.ts b/src/components/SplitPane/index.ts new file mode 100644 index 0000000..7f2531b --- /dev/null +++ b/src/components/SplitPane/index.ts @@ -0,0 +1 @@ +export { default } from './SplitPane'; \ No newline at end of file diff --git a/src/components/index.ts b/src/components/index.ts new file mode 100644 index 0000000..f500519 --- /dev/null +++ b/src/components/index.ts @@ -0,0 +1 @@ +export { default as SplitPane } from './SplitPane'; \ No newline at end of file From cb534d347378bbaebdf45f073ec9b3fb3e69fa94 Mon Sep 17 00:00:00 2001 From: PoetaKodu Date: Fri, 7 Oct 2022 00:51:58 +0200 Subject: [PATCH 07/10] feat: cookie handling --- src/helper/Cookies.ts | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 src/helper/Cookies.ts diff --git a/src/helper/Cookies.ts b/src/helper/Cookies.ts new file mode 100644 index 0000000..3a0b0c4 --- /dev/null +++ b/src/helper/Cookies.ts @@ -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;`; +} \ No newline at end of file From 9625917dc51144f3237ff9376ea04e80760b49c1 Mon Sep 17 00:00:00 2001 From: PoetaKodu Date: Fri, 7 Oct 2022 00:52:34 +0200 Subject: [PATCH 08/10] refactor: working on stack allocation --- src/App.tsx | 53 +++++++++++---------------------- src/components/Stack/Watch.tsx | 52 ++++++++++++++++---------------- src/components/Stack/Window.tsx | 25 +++++++++------- 3 files changed, 56 insertions(+), 74 deletions(-) diff --git a/src/App.tsx b/src/App.tsx index ed3fa9a..058958e 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,10 +1,8 @@ +import React from 'react'; import logo from './logo.svg'; import styles from './App.module.scss'; -import React from 'react'; import { CallstackItem, CallstackWindow } from './components/Callstack'; -import Splitter, { SplitDirection } from '@devbookhq/splitter'; - import { PrimaryButton, ThemeProvider, @@ -28,6 +26,7 @@ import LogWindow, { ILogEntry } from './components/Log/Window'; import LogData, { logMockupData } from './components/Log/Context'; import StackData from './components/Stack/Context'; +import { SplitPane } from './components'; enum ConnectionState { Disconnected, @@ -134,65 +133,47 @@ export function LeftPanel({connected, reconnect, setUseDarkTheme, serverAddressR ); } -export interface SizedSplitPaneProps { - children: React.ReactNode; - initialSize: [number, number]; - minimalSize: [number, number]; - direction: SplitDirection; -} - export interface SplitPaneProps { children: React.ReactNode; } -export function SizedSplitPane({children, initialSize, minimalSize, direction}: SizedSplitPaneProps) { - const [sizes, setSizes] = React.useState<[number, number]>(initialSize); - return ( - setSizes(s as [number, number])} - {...(direction === SplitDirection.Horizontal - ? - { minWidths: minimalSize } - : - { minHeights: minimalSize } - )} - > - {children} - - ); -} export function RootSplit({children}: SplitPaneProps) { return ( - + direction="x" + cookieName="SplitPaneSize_RootHorizontal" + > {children} - + ); } export function MainVerticalSplit({children}: SplitPaneProps) { return ( - + direction="y" + cookieName="SplitPaneSize_MainVertical" + > {children} - + ); } export function MainTopHorizontalSplit({children}: SplitPaneProps) { return ( - + direction="x" + cookieName="SplitPaneSize_MainTopHorizontal" + > {children} - + ); } diff --git a/src/components/Stack/Watch.tsx b/src/components/Stack/Watch.tsx index 731da28..bedb98d 100644 --- a/src/components/Stack/Watch.tsx +++ b/src/components/Stack/Watch.tsx @@ -6,11 +6,23 @@ import StackData, { viewBytes } from "./Context"; type MouseHoverCallback = (index: number, value: any) => void; interface StackValuesViewProps { - values?: any[]; + values: any[]; onValueHovered?: MouseHoverCallback; onValueUnhovered?: MouseHoverCallback; } +export type Stack = any[]; +export type StackFrame = { + initialSize: number; +} + +export type StackAllocation = { + name: string; + type: string; + size: number; + address: number; +} + export default class WatchWindow extends React.Component { @@ -20,31 +32,15 @@ class WatchWindow extends React.Component super(props); this.handleMouseEnter = this.handleMouseEnter.bind(this); - - this.state = { - values: props.values ?? [ - { kind: 'stackFrame', size: 0 }, - { kind: 'var', name: '', type: 'Char', size: 1, address: 0 }, - { kind: 'var', name: 'i', type: 'Int32', size: 4, address: 1 }, - { kind: 'stackFrame', size: 5 }, - { kind: 'var', name: 'c', type: 'Int32', size: 4, address: 5 }, - { kind: 'var', name: 'x', type: 'Int32', size: 4, address: 9 }, - { kind: 'var', name: 'z', type: 'Int32', size: 4, address: 13 }, - { kind: 'stackFrame', size: 17 }, - { kind: 'var', name: 'c', type: 'Int32', size: 4, address: 17 }, - { kind: 'var', name: 'x', type: 'Int32', size: 4, address: 21 }, - { kind: 'var', name: 'z', type: 'Int32', size: 4, address: 25 }, - ] - } } handleMouseEnter(index: number) { if (this.props.onValueHovered) - this.props.onValueHovered(index, this.state.values[index]); + this.props.onValueHovered(index, this.props.values[index]); } handleMouseLeave(index: number) { if (this.props.onValueUnhovered) - this.props.onValueUnhovered(index, this.state.values[index]); + this.props.onValueUnhovered(index, this.props.values[index]); } render() { @@ -63,9 +59,9 @@ class WatchWindow extends React.Component return val; } else if (value.size === 2) - return viewBytes(this.context.memory, value.address, value.size).getInt16(0); + return; else if (value.size === 4) - return viewBytes(this.context.memory, value.address, value.size).getInt32(0); + return; else return ""; } @@ -73,12 +69,14 @@ class WatchWindow extends React.Component return ( <> Stack Values View - {this.state.values.map((value: any, index: number) => -
this.handleMouseEnter(index)} - > + {this.props.values.map((value: any, index: number) => +
this.handleMouseEnter(index)} + > { - value.kind === 'var' ? + value.kind === 'allocation' ?
{value.name || '?'} @@ -92,7 +90,7 @@ class WatchWindow extends React.Component
:
- Stack Frame (initial size: {value.size}) + Stack Frame (initial size: {value.initialSize})
}
diff --git a/src/components/Stack/Window.tsx b/src/components/Stack/Window.tsx index 8143b46..c77eca0 100644 --- a/src/components/Stack/Window.tsx +++ b/src/components/Stack/Window.tsx @@ -1,31 +1,34 @@ 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 from './Watch'; +import StackWatchWindow, { Stack } from './Watch'; + +interface StackWindowProps { + stackValues: Stack; +} -export default function StackWindow() { +export default function StackWindow(props: StackWindowProps) { const [highlightedAddresses, setHighlightedAddresses] = React.useState<[number, number]>(); - const [sizes, setSizes] = React.useState<[number, number]>([70, 30]); return ( - {setSizes([ s[0], s[1] ])}} +
{}
setHighlightedAddresses([addr.address, addr.size])} onValueUnhovered={() => setHighlightedAddresses(undefined)} />
-
+ ); -} \ No newline at end of file +} From 73bc20b9c859a2d94ce755b6413bb1415f1dafb8 Mon Sep 17 00:00:00 2001 From: PoetaKodu Date: Fri, 7 Oct 2022 00:53:30 +0200 Subject: [PATCH 09/10] refactor: change `onResize` to `handleResize` --- src/components/SplitPane/SplitPane.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/SplitPane/SplitPane.tsx b/src/components/SplitPane/SplitPane.tsx index d1e40b1..a17dcf8 100644 --- a/src/components/SplitPane/SplitPane.tsx +++ b/src/components/SplitPane/SplitPane.tsx @@ -44,7 +44,7 @@ export default function SplitPane({ } }, [cookieName]); - const onResize = (sizes: [number, number]) => { + const handleResize = (sizes: [number, number]) => { setSizes(sizes); if (cookieName) { setCookie(cookieName, JSON.stringify(sizes), 365); @@ -54,7 +54,7 @@ export default function SplitPane({ return ( onResize(s as [number, number])} + onResizeFinished={(_, s) => handleResize(s as [number, number])} {...(direction === "x" ? { minWidths: minimalSize } From 5030418ce57bb043e512d88ac7766927df1f349d Mon Sep 17 00:00:00 2001 From: PoetaKodu Date: Fri, 7 Oct 2022 01:15:26 +0200 Subject: [PATCH 10/10] feat: autoreconnect fix: missing `useCallback`, `useState` --- src/App.tsx | 139 ++++-------------- src/Connection.ts | 16 ++ .../SettingsPanel/SettingsPanel.module.scss | 0 .../SettingsPanel/SettingsPanel.tsx | 103 +++++++++++++ src/index.css | 24 +-- 5 files changed, 159 insertions(+), 123 deletions(-) create mode 100644 src/Connection.ts create mode 100644 src/components/SettingsPanel/SettingsPanel.module.scss create mode 100644 src/components/SettingsPanel/SettingsPanel.tsx diff --git a/src/App.tsx b/src/App.tsx index 058958e..9ec1717 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -27,111 +27,9 @@ import LogWindow, { ILogEntry } from './components/Log/Window'; import LogData, { logMockupData } from './components/Log/Context'; import StackData from './components/Stack/Context'; import { SplitPane } from './components'; +import { ConnectionState } from './Connection'; +import { SettingsPanel } from './components/SettingsPanel/SettingsPanel'; -enum ConnectionState { - Disconnected, - Connecting, - Connected, -} - -function toString(state: ConnectionState) { - switch (state) { - case ConnectionState.Disconnected: - return 'Disconnected'; - case ConnectionState.Connecting: - return 'Connecting'; - case ConnectionState.Connected: - return 'Connected'; - } -} - - -const stackWithVerticalGap: IStackTokens = { - childrenGap: 10, - padding: "10px 0", -}; - - -const defaultAddress = 'ws://localhost:9002'; - -class MainControlsProps { - connected: ConnectionState = ConnectionState.Disconnected; - addressRef?: IRefObject; - onReconnect: () => void = () => { }; -} - -function MainControls(props: MainControlsProps) { - return ( - -

Connection status: {toString(props.connected)}

- - - - -
- ); -} - -class ThemeSettingsProps { - onToggledDarkTheme: (isDarkTheme: boolean) => void = () => { }; - defaultTheme: 'dark' | 'light' = 'dark'; -}; - -function ThemeSettings(props: ThemeSettingsProps) { - const [useDarkTheme, setDarkTheme] = React.useState(props.defaultTheme === 'dark'); - - return ( - - { - props.onToggledDarkTheme?.(!useDarkTheme); - setDarkTheme(!useDarkTheme); - }} - /> - - ); -} - -type AppPanes = { - rootHorizontal: [number, number]; - mainVertical: [number, number]; - mainTopHorizontal: [number, number]; -} - -type AppState = { - connected: ConnectionState; - useDarkTheme: boolean; - highlightedAddresses?: [number, number]; - callStack: CallstackItem[]; - stack: any[]; - memory: Int8Array; -} - -export function LeftPanel({connected, reconnect, setUseDarkTheme, serverAddressRef}: any) { - return ( - - - - - - setUseDarkTheme(isDarkTheme)} defaultTheme='dark' /> - - - ); -} export interface SplitPaneProps { children: React.ReactNode; @@ -183,12 +81,13 @@ export default function App() { let logEntries: ILogEntry[] = []; - let ws: WebSocket | null = null; + const [ws, setWebSocket] = React.useState(null); let serverAddressRef = React.createRef(); let logList = React.createRef(); const [stack, setStack] = React.useState([]); const [callStack, setCallStack] = React.useState([]); + const [autoReconnect, setAutoReconnect] = React.useState(true); const [memory, setMemory] = React.useState(new Int8Array([ 53, 0, 0, 0, 12, 237, 0, 255, @@ -206,10 +105,10 @@ export default function App() { } }, [connected]); - const pushToLog = (entry: ILogEntry) => { + const pushToLog = React.useCallback((entry: ILogEntry) => { logEntries.push(entry); logList.current?.forceUpdate(); - } + }, [logEntries, logList]); const handleStackRequest = (json: any) => { if (json.action === 'pushFrame') { @@ -237,10 +136,11 @@ export default function App() { } } - const reconnect = () => { + const reconnect = React.useCallback(() => { - if (ws && ws.readyState === WebSocket.OPEN) { + if (ws) { ws.close(); + setWebSocket(null); } setConnected(ConnectionState.Connecting); @@ -253,8 +153,7 @@ export default function App() { if (addr === "") return false; - - ws = new WebSocket(addr); + let ws = new WebSocket(addr); ws.onopen = () => { setConnected(ConnectionState.Connected); } ws.onclose = () => { setConnected(ConnectionState.Disconnected); } @@ -276,6 +175,8 @@ export default function App() { pushToLog(json.data as ILogEntry); } } + setWebSocket(ws); + return true; }; @@ -283,13 +184,25 @@ export default function App() { if (!tryConnect()) { setConnected(ConnectionState.Disconnected); } - } + }, [callStack, pushToLog, serverAddressRef, ws]); + + React.useEffect(() => { + if (autoReconnect && connected === ConnectionState.Disconnected) { + reconnect(); + } + }, [reconnect, connected, autoReconnect, ws]); return (
- + setAutoReconnect(ar)} + />
diff --git a/src/Connection.ts b/src/Connection.ts new file mode 100644 index 0000000..18f3455 --- /dev/null +++ b/src/Connection.ts @@ -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'; + } +} diff --git a/src/components/SettingsPanel/SettingsPanel.module.scss b/src/components/SettingsPanel/SettingsPanel.module.scss new file mode 100644 index 0000000..e69de29 diff --git a/src/components/SettingsPanel/SettingsPanel.tsx b/src/components/SettingsPanel/SettingsPanel.tsx new file mode 100644 index 0000000..2bc61d8 --- /dev/null +++ b/src/components/SettingsPanel/SettingsPanel.tsx @@ -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; + onReconnect: () => void = () => { }; + onAutoReconnectChanged: (autoReconnect: boolean) => void = () => { }; +} + +function MainControls(props: MainControlsProps) { + + const [autoReconnect, setAutoReconnect] = React.useState(true); + + return ( + +

Connection status: {toString(props.connected)}

+ { + props.onAutoReconnectChanged(!autoReconnect); + setAutoReconnect(!autoReconnect); + }} + /> + + + + +
+ ); +} + +class ThemeSettingsProps { + onToggledDarkTheme: (isDarkTheme: boolean) => void = () => { }; + defaultTheme: 'dark' | 'light' = 'dark'; +}; + +function ThemeSettings(props: ThemeSettingsProps) { + const [useDarkTheme, setDarkTheme] = React.useState(props.defaultTheme === 'dark'); + + return ( + + { + props.onToggledDarkTheme?.(!useDarkTheme); + setDarkTheme(!useDarkTheme); + }} + /> + + ); +} + +export function SettingsPanel({connected, reconnect, setUseDarkTheme, serverAddressRef, onAutoReconnectChanged}: any) { + return ( + + + + + + setUseDarkTheme(isDarkTheme)} defaultTheme='dark' /> + + + ); +} diff --git a/src/index.css b/src/index.css index 6c95821..0ed1388 100644 --- a/src/index.css +++ b/src/index.css @@ -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; +} \ No newline at end of file