Skip to content
This repository was archived by the owner on Jul 3, 2023. It is now read-only.
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,6 @@ build

# Jest coverage report
/coverage

# NBS recordings
/recordings
43 changes: 1 addition & 42 deletions jest.config.js
Original file line number Diff line number Diff line change
@@ -1,43 +1,2 @@
module.exports = {
coverageDirectory: 'coverage',
coveragePathIgnorePatterns: ['/node_modules/', 'src/global.d.ts'],
collectCoverageFrom: [
'**/*.{ts,tsx}',
'!src/shared/proto/**',
'!**/node_modules/**',
'!**/tests/**',
],
globals: {
'ts-jest': {
'tsConfigFile': './tsconfig.test.json',
},
},
mapCoverage: true,
moduleDirectories: [
'node_modules',
'<rootDir>/src',
],
moduleFileExtensions: [
'js',
'ts',
'tsx',
],
moduleNameMapper: {
'\\.(css)$': 'identity-obj-proxy',
'\\.(vert)$': '<rootDir>/__mocks__/mock.vert',
'\\.(frag)$': '<rootDir>/__mocks__/mock.frag',
},
roots: [
'<rootDir>/src',
],
modulePaths: [
'<rootDir>/src',
],
testMatch: [
'**/tests/**/*.tests.{ts,tsx}',
],
transform: {
'.(ts|tsx)': '<rootDir>/node_modules/ts-jest/preprocessor.js',
},
}
module.exports = require('./jestconfig.json')

45 changes: 45 additions & 0 deletions jestconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
{
"coverageDirectory": "coverage",
"coveragePathIgnorePatterns": [
"/node_modules/",
"src/global.d.ts"
],
"collectCoverageFrom": [
"**/*.{ts,tsx}",
"!src/shared/proto/**",
"!**/node_modules/**",
"!**/tests/**"
],
"globals": {
"ts-jest": {
"tsConfigFile": "./tsconfig.test.json"
}
},
"mapCoverage": true,
"moduleDirectories": [
"node_modules",
"<rootDir>/src"
],
"moduleFileExtensions": [
"js",
"ts",
"tsx"
],
"moduleNameMapper": {
"\\.(css)$": "identity-obj-proxy",
"\\.(vert)$": "<rootDir>/__mocks__/mock.vert",
"\\.(frag)$": "<rootDir>/__mocks__/mock.frag"
},
"roots": [
"<rootDir>/src"
],
"modulePaths": [
"<rootDir>/src"
],
"testMatch": [
"**/tests/**/*.tests.{ts,tsx}"
],
"transform": {
".(ts|tsx)": "<rootDir>/node_modules/ts-jest/preprocessor.js"
}
}
5 changes: 4 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
"start": "nodemon ./src/server/dev.ts",
"start:sim": "nodemon ./src/server/dev.ts --with-simulators",
"simulate": "nodemon ./src/simulators/simulate.ts",
"validate": "ts-node ./src/validator/validate.ts",
"prod": "ts-node -F ./src/server/prod.ts",
"prod:sim": "ts-node -F ./src/server/prod.ts --with-simulators",
"build": "yarn clean:build && webpack -p --progress --colors",
Expand All @@ -29,6 +30,7 @@
},
"license": "MIT",
"devDependencies": {
"@types/buffers": "^0.1.30",
"@types/classnames": "^2.2.0",
"@types/compression": "^0.0.33",
"@types/copy-webpack-plugin": "^4.0.0",
Expand Down Expand Up @@ -88,14 +90,15 @@
"webpack-hot-middleware": "^2.18.2"
},
"dependencies": {
"buffers": "^0.1.1",
"classnames": "^2.2.5",
"compression": "^1.7.0",
"connect-history-api-fallback": "^1.3.0",
"express": "^4.15.3",
"minimist": "^1.2.0",
"mobx": "^3.2.0",
"mobx-react": "^4.2.2",
"nuclearnet.js": "^1.2.0",
"nuclearnet.js": "^1.4.2",
"protobufjs": "^6.7.3",
"react": "^15.6.1",
"react-dom": "^15.6.1",
Expand Down
8 changes: 4 additions & 4 deletions src/client/base/memoize.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
/**
* Given a function that takes an object A and returns a B, create a new function which memoizes that A -> B transform.
*
* i.e. The first time the memoized function called with an A, it calculates B using fn(A) and stores B in its internal
* map. The second time it is called with the same A, it will not call fn(A) and instead just return the B that was
* created the previous time. Internally the function uses a WeakMap, so B will be automatically garbage collected when
* its corresponding A no longer exists in memory.
* i.e. The first time the memoized function is called with an A, it calculates B using fn(A) and stores B in its
* internal map. The second time it is called with the same A, it will not call fn(A) and instead just return the B that
* was created the previous time. Internally the function uses a WeakMap, so B will be automatically garbage collected
* when its corresponding A no longer exists in memory.
*
* e.g.
* const a = { name: 'Foo' }
Expand Down
7 changes: 7 additions & 0 deletions src/client/components/navigation/icons/record.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 2 additions & 0 deletions src/client/components/navigation/view.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import EyeIcon from './icons/eye.svg'
import MapIcon from './icons/map.svg'
import NUClearIcon from './icons/nuclear.svg'
import OrderingIcon from './icons/ordering.svg'
import RecordIcon from './icons/record.svg'
import ScatterIcon from './icons/scatter.svg'
import SpeedometerIcon from './icons/speedometer.svg'
import * as style from './style.css'
Expand Down Expand Up @@ -40,6 +41,7 @@ export const NavigationView = () => (
<NavigationItemView url='/classifier' Icon={CubeIcon}>Classifier</NavigationItemView>
<NavigationItemView url='/subsumption' Icon={OrderingIcon}>Subsumption</NavigationItemView>
<NavigationItemView url='/gamestate' Icon={ControllerIcon}>GameState</NavigationItemView>
<NavigationItemView url='/record' Icon={RecordIcon}>Record</NavigationItemView>
</ul>
</header>
)
27 changes: 27 additions & 0 deletions src/client/components/record/controller.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { action } from 'mobx'
import { NUsightNetwork } from '../../network/nusight_network'
import { RecordRobotModel } from './model'

export class RecordController {
public constructor(private nusightNetwork: NUsightNetwork) {
}

public static of(nusightNetwork: NUsightNetwork): RecordController {
return new RecordController(nusightNetwork)
}

@action
public onStartRecordingClick(robot: RecordRobotModel) {
const peer = { name: robot.name, address: robot.address, port: robot.port }
robot.stopRecording = this.nusightNetwork.record(peer)
robot.recording = true
}

@action
public onStopRecordingClick(robot: RecordRobotModel) {
if (robot.stopRecording) {
robot.stopRecording()
}
robot.recording = false
}
}
58 changes: 58 additions & 0 deletions src/client/components/record/model.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import { observable } from 'mobx'
import { computed } from 'mobx'
import { memoize } from '../../base/memoize'
import { AppModel } from '../app/model'
import { RobotModel } from '../robot/model'

export class RecordModel {
@observable private appModel: AppModel

public constructor(appModel: AppModel) {
this.appModel = appModel
}

public static of(appModel: AppModel) {
return new RecordModel(appModel)
}

@computed
public get robots(): RecordRobotModel[] {
return this.appModel.robots.map(robot => RecordRobotModel.of(robot))
}
}

type RecordRobotModelOpts = {
recording: boolean
}

export class RecordRobotModel {
@observable private robotModel: RobotModel
@observable public recording: boolean
public stopRecording?: () => void

public constructor(robotModel: RobotModel, opts: RecordRobotModelOpts) {
this.robotModel = robotModel
this.recording = opts.recording
}

public static of = memoize((robot: RobotModel): RecordRobotModel => {
return new RecordRobotModel(robot, {
recording: false // TODO (Annable): get from server?
})
})

@computed
public get name(): string {
return this.robotModel.name
}

@computed
public get address(): string {
return this.robotModel.address
}

@computed
public get port(): number {
return this.robotModel.port
}
}
6 changes: 6 additions & 0 deletions src/client/components/record/styles.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
.record {
flex-grow: 1;
}
.recordMenuBar {
flex: 1;
}
54 changes: 54 additions & 0 deletions src/client/components/record/view.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import { observer } from 'mobx-react'
import * as React from 'react'
import { ComponentType } from 'react'
import { Component } from 'react'
import { NUsightNetwork } from '../../network/nusight_network'
import { RecordController } from './controller'
import { RecordModel } from './model'
import * as styles from './styles.css'

type Props = {
menu: ComponentType<{}>
controller: RecordController
model: RecordModel
}

@observer
export class RecordView extends Component<Props> {
public static of(menu: ComponentType<{}>, nusightNetwork: NUsightNetwork, model: RecordModel) {
const controller = RecordController.of(nusightNetwork)
return <RecordView menu={menu} controller={controller} model={model}/>
}

public render() {
const { menu, controller, model } = this.props
const { robots } = model
return (
<div className={styles.record}>
<RecordMenuBar menu={menu}/>
<div>
{robots.map(robot => (
<div key={robot.name}>
<div>Name: {robot.name}</div>
<div>Record: {robot.recording
? <button style={{ border: 'none', backgroundColor: 'red' }}
onClick={() => controller.onStopRecordingClick(robot)}>Stop recording</button>
: <button style={{ border: 'none', backgroundColor: 'white' }}
onClick={() => controller.onStartRecordingClick(robot)}>Start recording</button>}
</div>
</div>
))}
</div>
</div>
)
}
}

type RecordMenuBarProps = {
menu: ComponentType<{}>
}

const RecordMenuBar = observer((props: RecordMenuBarProps) => {
const { menu: Menu } = props
return <Menu/>
})
6 changes: 6 additions & 0 deletions src/client/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,12 @@ import { LocalisationNetwork } from './components/localisation/network'
import { LocalisationView } from './components/localisation/view'
import { withRobotSelectorMenuBar } from './components/menu_bar/view'
import { NUClear } from './components/nuclear/view'
import { RecordView } from './components/record/view'
import { Scatter } from './components/scatter_plot/view'
import { Subsumption } from './components/subsumption/view'
import { Vision } from './components/vision/view'
import { NUsightNetwork } from './network/nusight_network'
import { RecordModel } from './components/record/model'

// enable MobX strict mode
useStrict(true)
Expand All @@ -36,6 +38,9 @@ const appController = AppController.of()
AppNetwork.of(nusightNetwork, appModel)
const menu = withRobotSelectorMenuBar(appModel.robots, appController.toggleRobotEnabled)


const recordModel = RecordModel.of(appModel)

ReactDOM.render(
<BrowserRouter>
<AppView>
Expand All @@ -54,6 +59,7 @@ ReactDOM.render(
<Route path='/classifier' component={Classifier}/>
<Route path='/subsumption' component={Subsumption}/>
<Route path='/gamestate' component={GameState}/>
<Route path='/record' render={() => RecordView.of(menu, nusightNetwork, recordModel)}/>
</Switch>
</AppView>
</BrowserRouter>,
Expand Down
Loading