From 69f06aadd546afd2fd89de02f6c81d00ac3d37af Mon Sep 17 00:00:00 2001 From: awesson Date: Sun, 14 May 2017 18:02:41 -0400 Subject: [PATCH] Setup the C# client to startup the node server The C# app now runs npm to run the start.js script which starts the webserver. Also started some work on recieving the initial state and saving that state off. Updated some modules which caused a typing issue in connect(). Had to move some interface definitions around to get it working. Ideally it would be fixed with https://github.com/Microsoft/TypeScript/issues/15059 --- .../AsyncNetworkStreamReader.cs | 12 +--- .../HieroglyphBackend/ByteArrayExtension.cs | 2 - backend/HieroglyphBackend/FrontEndListener.cs | 5 -- backend/HieroglyphBackend/Program.cs | 47 +++++++++++---- .../src/ClientConnection/ClientConnection.ts | 13 ++-- frontend/src/ContextMenus/StatementPicker.tsx | 6 -- .../Editors/Inspectors/InspectorContainer.tsx | 17 +++--- .../src/Editors/Inspectors/InspectorView.tsx | 10 +++- frontend/src/RootState.ts | 4 +- .../Functions/FunctionCallInspectorView.tsx | 3 +- .../Functions/FunctionStatement.tsx | 5 +- .../Functions/FunctionStatementContainer.tsx | 7 +-- .../src/Statements/StatementListContainer.tsx | 12 +--- frontend/src/Statements/StatementListView.tsx | 13 +++- frontend/src/index.tsx | 60 ++++++++++++++----- 15 files changed, 129 insertions(+), 87 deletions(-) diff --git a/backend/HieroglyphBackend/AsyncNetworkStreamReader.cs b/backend/HieroglyphBackend/AsyncNetworkStreamReader.cs index e74d0f0..573eb83 100644 --- a/backend/HieroglyphBackend/AsyncNetworkStreamReader.cs +++ b/backend/HieroglyphBackend/AsyncNetworkStreamReader.cs @@ -1,12 +1,7 @@ using System; -using System.Collections.Concurrent; -using System.Collections.Generic; using System.IO; -using System.Linq; using System.Net; using System.Net.Sockets; -using System.Text; -using System.Timers; namespace HieroglyphBackend { @@ -17,8 +12,7 @@ public class AsyncNetworkStreamReader public delegate void ConnectionClosedDelegate(); private readonly ConnectionClosedDelegate m_ConnectionClosedDelegate; - - //private TcpListener m_TcpListener; + private TcpClient m_ConnectedClient; private NetworkStream m_NetworkStream; @@ -59,10 +53,6 @@ public void Connect(string ipAddress, int port, byte[] messageDelimeter) throw new ObjectDisposedException("Tried to connect, but the underlying stream is already closed."); } - //m_TcpListener = new TcpListener(ipAddress, port); - //m_TcpListener.Start(); - //m_TcpListener.AcceptTcpClientAsync(); - m_ConnectionIp = ipAddress; m_ConnectionPort = port; m_MessageDelimeter = messageDelimeter; diff --git a/backend/HieroglyphBackend/ByteArrayExtension.cs b/backend/HieroglyphBackend/ByteArrayExtension.cs index bcad5dc..7ca7c01 100644 --- a/backend/HieroglyphBackend/ByteArrayExtension.cs +++ b/backend/HieroglyphBackend/ByteArrayExtension.cs @@ -1,6 +1,4 @@ using System; -using System.Linq; -using System.Threading; namespace HieroglyphBackend { diff --git a/backend/HieroglyphBackend/FrontEndListener.cs b/backend/HieroglyphBackend/FrontEndListener.cs index bc17805..2472859 100644 --- a/backend/HieroglyphBackend/FrontEndListener.cs +++ b/backend/HieroglyphBackend/FrontEndListener.cs @@ -1,13 +1,8 @@ using System; using System.Collections.Concurrent; using System.Collections.Generic; -using System.Linq; using System.Text; -using System.Threading.Tasks; using System.Net; -using System.Net.Sockets; -using System.Threading; -using System.Timers; namespace HieroglyphBackend { diff --git a/backend/HieroglyphBackend/Program.cs b/backend/HieroglyphBackend/Program.cs index a3620a8..b2581b8 100644 --- a/backend/HieroglyphBackend/Program.cs +++ b/backend/HieroglyphBackend/Program.cs @@ -1,8 +1,5 @@ using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; +using System.Diagnostics; namespace HieroglyphBackend { @@ -10,21 +7,49 @@ static class Program { public static void Main(string[] args) { - // TODO(awesson): Startup the front-end - - FrontEndListener.Start(); + var process = RunServer(); - // Block and wait for a new state, and then process it - // When there are no more states (because the stream closed) the app will close. - foreach (var state in FrontEndListener.RecieveStates()) + try { - ExportState(state); + FrontEndListener.Start(); + + // Block and wait for a new state, and then process it + // When there are no more states (because the stream closed) the app will close. + foreach (var state in FrontEndListener.RecieveStates()) + { + ExportState(state); + } + } + finally + { + if (process != null) + { + // TODO: This doesn't actually stop the node server. + if (!process.HasExited) + { + process.Kill(); + } + process.Close(); + } } } + private static Process RunServer() + { + var process = new Process(); + var info = new ProcessStartInfo(@"C:\Program Files\nodejs\npm.cmd", "run start") + { + WorkingDirectory = @"..\..\..\..\frontend" + }; + process.StartInfo = info; + process.Start(); + return process; + } + private static void ExportState(string state) { Console.WriteLine("Got new state:\n{0}", state); + // TODO(awesson): Save state to disk for starting up the front-end next time. // TODO(awesson): Parse json state into code and save code to disk. diff --git a/frontend/src/ClientConnection/ClientConnection.ts b/frontend/src/ClientConnection/ClientConnection.ts index ae65ea4..3b0894c 100644 --- a/frontend/src/ClientConnection/ClientConnection.ts +++ b/frontend/src/ClientConnection/ClientConnection.ts @@ -7,25 +7,20 @@ function testConnection(socket: SocketIOClient.Socket) console.log('CONNECTED!'); } -export function start(): SocketIOClient.Socket +export function start(initFunc:(initialStateJson: string) => void): SocketIOClient.Socket { const socket = Socket(); socket.on('connect', testConnection); - socket.on('message', receivedState); + socket.on('message', initFunc); return socket; } -export function receivedState(stateJson: string) -{ - console.log("received state:", stateJson); -} - function save(socket: SocketIOClient.Socket, state: RootState) { - // TODO(aweson): Send the json + socket.send(JSON.stringify(state)); } -export function getSaveFunc(socket: SocketIOClient.Socket, state: RootState) +export function getSaveFunc(socket: SocketIOClient.Socket) { return (state: RootState) => { diff --git a/frontend/src/ContextMenus/StatementPicker.tsx b/frontend/src/ContextMenus/StatementPicker.tsx index d791f99..2b005ff 100644 --- a/frontend/src/ContextMenus/StatementPicker.tsx +++ b/frontend/src/ContextMenus/StatementPicker.tsx @@ -8,12 +8,6 @@ import getAllFuncDefs = FunctionState.getAllFuncDefs; import DropDownSelector, { IItemInfo } from './DropDownSelector' -interface IStatementPickerProps -{ - rootState: RootState; - dispatch: Dispatch; -} - const mapStateToProps = (rootState: RootState) => { // TODO: Should eventually be more than just function defs. diff --git a/frontend/src/Editors/Inspectors/InspectorContainer.tsx b/frontend/src/Editors/Inspectors/InspectorContainer.tsx index 6baf6c9..88e179b 100644 --- a/frontend/src/Editors/Inspectors/InspectorContainer.tsx +++ b/frontend/src/Editors/Inspectors/InspectorContainer.tsx @@ -3,26 +3,25 @@ import { connect } from 'react-redux'; import { StatementState } from '../../Statements'; import RootState from '../../RootState'; -import InspectorView, { IInspectorViewProps } from './InspectorView'; +import InspectorView, { IInspectorViewConnectedProps, IInspectorViewOwnProps } from './InspectorView'; import getStatement = StatementState.getStatement; -interface IInspectorContainerProps -{ - statementId: number; -} - -const mapStateToProps = (rootState: RootState, myProps: IInspectorContainerProps) => +const mapStateToProps = (rootState: RootState, myProps: IInspectorViewOwnProps) => { // Check if there isn't a selected statement if (myProps.statementId < 0) { - return {comp: null, viewProps: {concreteStatementId: myProps.statementId}}; + return { comp: null, viewProps: { concreteStatementId: myProps.statementId } }; } const statement = getStatement(rootState, myProps.statementId); const jsxType = StatementState.getInspectorContainerComponent(statement); - const viewProps:IInspectorViewProps = {comp: jsxType, viewProps: {concreteStatementId: statement.concreteStatementId}}; + const viewProps:IInspectorViewConnectedProps = + { + comp: jsxType, + viewProps: { concreteStatementId: statement.concreteStatementId } + }; return viewProps; } diff --git a/frontend/src/Editors/Inspectors/InspectorView.tsx b/frontend/src/Editors/Inspectors/InspectorView.tsx index 9ed3bc6..c4ebd08 100644 --- a/frontend/src/Editors/Inspectors/InspectorView.tsx +++ b/frontend/src/Editors/Inspectors/InspectorView.tsx @@ -9,12 +9,20 @@ export interface IInspectorCompProps concreteStatementId: number; } -export interface IInspectorViewProps +export interface IInspectorViewConnectedProps { comp: React.ComponentClass; viewProps: IInspectorCompProps; } +// Without subtraction types, this needs to be defined here for the rest of the typechecking to work :( +export interface IInspectorViewOwnProps +{ + statementId: number; +} + +type IInspectorViewProps = IInspectorViewConnectedProps & IInspectorViewOwnProps; + class InspectorView extends React.Component { public render(): JSX.Element diff --git a/frontend/src/RootState.ts b/frontend/src/RootState.ts index 413b8b4..6a22664 100644 --- a/frontend/src/RootState.ts +++ b/frontend/src/RootState.ts @@ -10,7 +10,7 @@ import newArgumentDefState = Functions.Arguments.ArgumentState.newArgumentDefSta import StatementsState = StatementState.StatementsState; import newStatementsState = StatementState.newStatementsState; -interface RootState extends StatementsState, FunctionsState +export interface RootState extends StatementsState, FunctionsState { } @@ -47,7 +47,7 @@ export function initRootState() } export function newRootState(functionsState : FunctionsState = newFunctionsState(), - statementsState : StatementsState = newStatementsState()) + statementsState : StatementsState = newStatementsState()): RootState { return { ...functionsState, ...statementsState }; } diff --git a/frontend/src/Statements/Functions/FunctionCallInspectorView.tsx b/frontend/src/Statements/Functions/FunctionCallInspectorView.tsx index fdaa4ab..a824ea5 100644 --- a/frontend/src/Statements/Functions/FunctionCallInspectorView.tsx +++ b/frontend/src/Statements/Functions/FunctionCallInspectorView.tsx @@ -6,13 +6,14 @@ import RootState from '../../RootState'; import { Type } from '../../Types'; import { FunctionCallState, FunctionDefState, getFuncArgTypes } from './FunctionState'; import { ArgumentInputView, OnArgValueChangeCallback } from './Arguments'; +import { IInspectorCompProps } from '../../Editors/Inspectors'; import '../../index.css'; export type SetArgValueCallback = (funcCallId: number, argIndex: number, argValue: string) => void; -interface IFunctionCallInspectorViewProps +interface IFunctionCallInspectorViewProps extends IInspectorCompProps { funcCallId: number; funcArgumentsCurValues: string[]; diff --git a/frontend/src/Statements/Functions/FunctionStatement.tsx b/frontend/src/Statements/Functions/FunctionStatement.tsx index d5432c0..7b88c80 100644 --- a/frontend/src/Statements/Functions/FunctionStatement.tsx +++ b/frontend/src/Statements/Functions/FunctionStatement.tsx @@ -1,7 +1,10 @@ import * as React from "react"; +import { IStatementCompProps } from '../StatementListContainer'; -interface IFunctionStatementProps + + +export interface IFunctionStatementProps extends IStatementCompProps { isSelected: boolean; name: string; diff --git a/frontend/src/Statements/Functions/FunctionStatementContainer.tsx b/frontend/src/Statements/Functions/FunctionStatementContainer.tsx index 3fe4770..f13e40d 100644 --- a/frontend/src/Statements/Functions/FunctionStatementContainer.tsx +++ b/frontend/src/Statements/Functions/FunctionStatementContainer.tsx @@ -1,3 +1,4 @@ +import { IFunctionStatementProps } from './FunctionStatement'; import { Dispatch } from 'redux'; import { connect } from "react-redux"; @@ -9,13 +10,11 @@ import { IStatementCompProps } from '../StatementListContainer'; const mapStateToProps = (rootState: RootState, myProps: IStatementCompProps) => { - const isSelected = myProps.isSelected; - const functionCall = getFuncCall(rootState, myProps.concreteStatementId); const func = getFuncDef(rootState, functionCall.funcDefId); const name = func.name; - return { isSelected, name }; + return { name }; } -export default connect(mapStateToProps, null)(FunctionStatement); +export default connect(mapStateToProps)(FunctionStatement); diff --git a/frontend/src/Statements/StatementListContainer.tsx b/frontend/src/Statements/StatementListContainer.tsx index 11fb328..cbd714a 100644 --- a/frontend/src/Statements/StatementListContainer.tsx +++ b/frontend/src/Statements/StatementListContainer.tsx @@ -3,18 +3,10 @@ import React from 'react'; import { connect } from "react-redux"; import { getStatementContainerComponent, getStatement } from './StatementState'; -import StatementListView from './StatementListView'; +import StatementListView, { IStatementListViewConnectedProps } from './StatementListView'; import RootState from '../RootState'; -export type StatementSelectedCallback = (statementId: number, event: React.MouseEvent) => void; - -interface IStatementListContainerProps -{ - statements: number[]; - selectedStatementId: number; - selectedCallback: StatementSelectedCallback; -} export interface IStatementCompProps { @@ -29,7 +21,7 @@ export interface IStatementElement viewProps: IStatementCompProps; } -const mapStateToProps = (rootState: RootState, myProps: IStatementListContainerProps) => +const mapStateToProps = (rootState: RootState, myProps: IStatementListViewConnectedProps) => { const statementToElement = (statementId: number) : IStatementElement => { diff --git a/frontend/src/Statements/StatementListView.tsx b/frontend/src/Statements/StatementListView.tsx index 2c299b4..0b37c90 100644 --- a/frontend/src/Statements/StatementListView.tsx +++ b/frontend/src/Statements/StatementListView.tsx @@ -6,11 +6,22 @@ import { IStatementElement } from './StatementListContainer'; import '../index.css'; -interface IStatementListViewProps +type StatementSelectedCallback = (statementId: number, event: React.MouseEvent) => void; + +export interface IStatementListViewConnectedProps +{ + statements: number[]; + selectedStatementId: number; + selectedCallback: StatementSelectedCallback; +} + +interface IStatementListViewOwnProps { listItems: IStatementElement[]; } +type IStatementListViewProps = IStatementListViewOwnProps & IStatementListViewConnectedProps; + class StatementListView extends Component { render() diff --git a/frontend/src/index.tsx b/frontend/src/index.tsx index 587e475..5c0a68f 100644 --- a/frontend/src/index.tsx +++ b/frontend/src/index.tsx @@ -1,26 +1,58 @@ import React from 'react'; import ReactDOM from 'react-dom'; -import { createStore } from 'redux'; +import { createStore, Store } from 'redux'; import { Provider } from 'react-redux'; import 'bootstrap/dist/css/bootstrap.css'; +//import throttle from 'lodash/throttle'; -import { initRootState } from './RootState'; +import { initRootState, RootState } from './RootState'; import rootReducer from './RootReducer'; import ClientConnection from './ClientConnection'; import App from './App'; -const socket = ClientConnection.start(); +const socket = ClientConnection.start(init); -const castWindow = window as any; -const store = createStore(rootReducer, - initRootState(), - // Needed for the redux firefox plugin to work - castWindow.devToolsExtension && castWindow.devToolsExtension()); +function init(initialStateJson: string) +{ + // const obj = JSON.parse(initialStateJson); + // startRendering(obj); +} -ReactDOM.render( - - - , - document.getElementById('root') -); +function getSaveFunc(store: Store) +{ + const saveFunc = ClientConnection.getSaveFunc(socket); + return () => + { + saveFunc(store.getState()); + } +} + +function startRendering(initialState: RootState) +{ + if (initialState == null) + { + initialState = initRootState(); + } + + const castWindow = window as any; + const store = createStore(rootReducer, + initialState, + // Needed for the redux firefox plugin to work + castWindow.devToolsExtension && castWindow.devToolsExtension()); + +// store.subscribe(throttle(getSaveFunc(store), 1000)); + + ReactDOM.render( + + + , + document.getElementById('root') + ); +} + +// TODO: Should be getting this from the client, but first I need to figure +// out how to add built-in functions to the state object in such a way that +// the client doesn't need to duplicate logic for updating the state. +// For now start with an empty state every time. +startRendering(null);