Skip to content

Commit

Permalink
Add an experimental browser-based GUI
Browse files Browse the repository at this point in the history
  • Loading branch information
nickg committed Nov 6, 2023
1 parent 9de0748 commit d143c1a
Show file tree
Hide file tree
Showing 39 changed files with 10,746 additions and 23 deletions.
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,11 @@ compile_commands.json
www/*.html
/debian

# Generated GUI files
contrib/gui/node_modules
contrib/gui/dist/
contrib/gui/.vite

# VI test suite also cannot be distributed
test/vi_suite*

Expand Down
1 change: 1 addition & 0 deletions Makefile.am
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ include lib/ieee.19/Makemodule.am
include lib/vital/Makemodule.am
include lib/synopsys/Makemodule.am
include contrib/Makemodule.am
include contrib/gui/Makemodule.am

compile-commands:
$(MAKE) -C $(top_builddir) clean
Expand Down
19 changes: 17 additions & 2 deletions configure.ac
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,7 @@ AX_PROG_FLEX([], [AC_MSG_ERROR(Flex not found)])

case $host_os in
*cygwin*|msys*|mingw32*)
LIBS="$LIBS -ldbghelp" # For StackTrace64
LIBS="$LIBS -ldbghelp -lwsock32" # For StackTrace64 and sockets
DIR_SEP=\\\\
pathprog="cygpath -m"
;;
Expand Down Expand Up @@ -220,16 +220,31 @@ PKG_CHECK_MODULES([libffi], [libffi >= 3.0])

PKG_CHECK_MODULES([libzstd], [libzstd >= 1.4])

AC_ARG_ENABLE([gui],
[AS_HELP_STRING([--enable-gui], [Build browser-based GUI (WIP)])],
[enable_gui=$enableval],
[enable_gui=no])

AS_IF([test x$enable_gui = xyes],
[AC_DEFINE_UNQUOTED([ENABLE_GUI], [1], [Browser-based GUI enabled])
AC_CHECK_PROGS([NPM], [npm])
AS_IF([test x$NPM = x], [AC_MSG_ERROR([please install NodeJS])])])

AM_CONDITIONAL([ENABLE_GUI], [test x$enable_gui = xyes])

AC_ARG_ENABLE([server],
[AS_HELP_STRING([--enable-server], [Build WebSocket interface])],
[enable_server=$enableval],
[enable_server=no])
[enable_server=$enable_gui])

AS_IF([test x$enable_server = xyes],
[AC_DEFINE_UNQUOTED([ENABLE_SERVER], [1], [WebSocket server enabled])])

AM_CONDITIONAL([ENABLE_SERVER], [test x$enable_server = xyes])

AS_IF([test x$enable_gui$enable_server = xyesno],
[AC_MSG_ERROR([GUI supports requires --enable-server])])

AC_ARG_ENABLE([tcl],
[AS_HELP_STRING([--enable-tcl], [Build TCL interface])],
[enable_tcl=$enableval],
Expand Down
2 changes: 2 additions & 0 deletions contrib/gui/.eslintignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
node_modules/
dist/
54 changes: 54 additions & 0 deletions contrib/gui/.eslintrc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
module.exports = {
"env": {
"browser": true,
"es2021": true
},
"extends": [
"eslint:recommended",
"plugin:@typescript-eslint/recommended",
],
"overrides": [
{
"env": {
"node": true
},
"files": [
".eslintrc.{js,cjs}",
"*.config.js",
],
"parserOptions": {
"sourceType": "script"
}
}
],
"parser": "@typescript-eslint/parser",
"parserOptions": {
"ecmaVersion": "latest",
"sourceType": "module"
},
"plugins": [
"@typescript-eslint",
"@stylistic",
],
"rules": {
"linebreak-style": [ "error", "unix"],
"@typescript-eslint/no-explicit-any": [ "off" ],
"@stylistic/indent": [
"error", 2, {
"ignoredNodes": ["JSXElement *", "JSXElement"],
"SwitchCase": 1 }
],
"@stylistic/jsx-indent-props": [ "warn", "first" ],
"@stylistic/jsx-closing-bracket-location": [ "warn", "after-props" ],
"@stylistic/jsx-first-prop-new-line": [ "warn", "never" ],
"@stylistic/quotes": [ "warn", "double" ],
"@stylistic/semi": [ "error", "always" ],
"@stylistic/brace-style": [
"error", "stroustrup", { "allowSingleLine": true }
],
"@stylistic/type-annotation-spacing": [ "error" ],
"@stylistic/max-len": ["error", { "code": 80 }],
"@stylistic/no-tabs": ["error"],
"@stylistic/eol-last": ["error", "always"],
}
};
58 changes: 58 additions & 0 deletions contrib/gui/Makemodule.am
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
NPM = $(AM_V_GEN)npm --prefix $(top_srcdir)/contrib/gui

BUNDLE_ASSETS = \
gui/assets/index.css \
gui/assets/allPaths.js \
gui/assets/allPathsLoader.js \
gui/assets/splitPathsBySizeLoader.js \
gui/assets/index2.js \
gui/assets/index3.js \
gui/assets/index.js

if ENABLE_GUI
guidir = $(pkgdatadir)/gui
assetsdir = $(guidir)/assets

gui_DATA = gui/index.html
assets_DATA = $(BUNDLE_ASSETS)

CLEANFILES += gui/index.html $(BUNDLE_ASSETS) .stamp-npm

gui/index.html: $(GUI_FILES) .stamp-npm
$(AM_V_GEN)( \
cd $(top_srcdir)/contrib/gui; \
npx tsc -noEmit && \
npm run lint && \
npx vite build --outDir $(abs_builddir)/gui --emptyOutDir; \
)

gui/assets/index.css: gui/index.html
gui/assets/allPaths.js: gui/index.html
gui/assets/allPathsLoader.js: gui/index.html
gui/assets/splitPathsBySizeLoader.js: gui/index.html
gui/assets/index2.js: gui/index.html
gui/assets/index3.js: gui/index.html
gui/assets/index.js: gui/index.html

.stamp-npm: $(srcdir)/contrib/gui/package-lock.json
$(NPM) ci
$(AM_V_AT)touch .stamp-npm
endif

EXTRA_DIST += $(GUI_FILES)

if MAINTAINER_MODE
update-js-dist:
$(AM_V_GEN)( \
echo "# Generated automatically, do not edit." ; \
echo "GUI_FILES = \\" ; \
cd $(top_srcdir) ; \
git ls-files -- 'contrib/gui/*.ts' 'contrib/gui/*.tsx' 'contrib/gui/*.json' \
'contrib/gui/*.css' 'contrib/gui/*.html' \
| sort | sed -e 's/^/\t/' -e '$$ ! s/$$/ \\/' ; \
) > $(top_srcdir)/contrib/gui/dist.mk
endif

.PHONY: update-js-dist

include contrib/gui/dist.mk
10 changes: 10 additions & 0 deletions contrib/gui/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
## Hacking

NVC always serves the static HTML and JS files from the install prefix.
Development using `make`, `make install`, and then reloading is way too
slow so use the [https://vitejs.dev/](Vite) development server instead
by running `nvc --gui` and then `npm start` in this directory in another
window. The open [http://localhost:5173] in your web browser (note the
different port number). This will automatically reload when any of the
source files changes.

6 changes: 6 additions & 0 deletions contrib/gui/babel.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
module.exports = {
presets: [
["@babel/preset-env", {targets: {node: "current"}}],
"@babel/preset-typescript",
],
};
77 changes: 77 additions & 0 deletions contrib/gui/component/App.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
//
// Copyright (C) 2023 Nick Gasson
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//

import type { Toaster } from "@blueprintjs/core";
import { FocusStyleManager, OverlayToaster } from "@blueprintjs/core";
import "@blueprintjs/core/lib/css/blueprint.css";
import React from "react";
import Split from "react-split";
import Conduit from "../lib/conduit";
import { Model } from "../lib/model";
import ClosedAlert from "./ClosedAlert";
import Transcript from "./Transcript";
import WaveView from "./WaveView";
import Sidebar from "./Sidebar";
import { bindEvents } from "../lib/react-util";

function makeWebSocket(url: string) {
const ws = new WebSocket(url);
ws.binaryType = "arraybuffer";
return ws;
}

const conduit = new Conduit(makeWebSocket);
const model = new Model(conduit);
const toaster: Toaster = OverlayToaster.create({ position: "top-right" });

function showRestartToast() {
toaster.show({ message: "simulation restarted" });
}

function setWindowTitle(top: string) {
toaster.show({ message: `simulation ${top} loaded` });
document.title = `${top.toLowerCase()} - NVC`;
}

function showQuitToast() {
toaster.show({ message: "simulation unloaded" });
}

FocusStyleManager.onlyShowFocusOnTabs();

function App() {
bindEvents(model, {
restartSim: showRestartToast,
quitSim: showQuitToast,
startSim: setWindowTitle,
});

return (
<div className="component-app">
<Split className="app-split" direction="vertical" gutterSize={8}>
<Split className="app-hsplit" gutterSize={8} sizes={[20, 80]}>
<Sidebar />
<WaveView model={model}/>
</Split>
<Transcript conduit={conduit} />
</Split>
<ClosedAlert conduit={conduit} />
</div>
);
}

export default App;
46 changes: 46 additions & 0 deletions contrib/gui/component/ClosedAlert.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
//
// Copyright (C) 2023 Nick Gasson
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//

import { Alert, Intent } from "@blueprintjs/core";
import React, { useEffect, useState } from "react";
import type Conduit from "../lib/conduit";

interface IProps {
conduit: Conduit;
}

function ClosedAlert(props: IProps) {
const [closed, setClosed] = useState(false);

useEffect(() => {
props.conduit.onClose = () => { setClosed(true); };
}, []);

const onConfirm = () => { location.reload(); };

return (
<Alert confirmButtonText="Refresh"
onConfirm={onConfirm}
isOpen={closed}
intent={Intent.DANGER}
loading={false}>
<p>Lost connection to server</p>
</Alert>
);
}

export default ClosedAlert;
31 changes: 31 additions & 0 deletions contrib/gui/component/Sidebar.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
//
// Copyright (C) 2023 Nick Gasson
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//

import React from "react";
import { Tab, Tabs } from "@blueprintjs/core";
import SignalPanel from "./SignalPanel";

function Sidebar() {
return (
<Tabs id="TabsExample" selectedTabId="signals">
<Tab id="signals" title="Signals" panel={<SignalPanel/>} />
<Tab id="bb" disabled title="Other" />
</Tabs>
);
}

export default Sidebar;
30 changes: 30 additions & 0 deletions contrib/gui/component/SignalPanel.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
//
// Copyright (C) 2023 Nick Gasson
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//

import React from "react";

function SignalPanel() {
return (
<div>
<h1>Example panel</h1>
<p>
text
</p>
</div>);
}

export default SignalPanel;
Loading

0 comments on commit d143c1a

Please sign in to comment.