Merged PR 208: Merge dev/samershi/fixes to master

This commit is contained in:
Saleema Amershi 2017-02-23 22:52:09 +00:00
Родитель d4e687bbdb 81d438d3e8
Коммит 43f21812e8
28 изменённых файлов: 263 добавлений и 569 удалений

Просмотреть файл

@ -1,14 +1,28 @@
README
======
Requires NodeJS
-----------------------------------
Windows:
- node.js 4.3.2
Download from [https://nodejs.org]. Node.js installs npm, the node package manager
OS X:
- node.js
homebrew install node (got version 5.0.0) --- this installs node and
npm (node package manager)
Linux:
- node.js
apt-get node
Installation
-----------------------------------
To initialize the project and install node packages, run `npm install`
To build the project, run `npm run build`; you can also build individual parts only:
To build the project, run `npm run build`;
- Web worker: `npm run build:worker`
- TypeScript code: `npm run build:ts`
- Less CSS code: `npm run build:less`
To debug the project, run `npm run debug`;
To start the project, run `npm start`
To create a package of the project, run `npm run package`
To run the project, run `npm start`

Просмотреть файл

@ -78,6 +78,15 @@
.tbtn-style-mixin(#d9534f, white);
}
.disabled {
cursor: not-allowed;
opacity: .5;
}
.flipped-icon {
transform: scaleX(-1);
-webkit-transform: scaleX(-1);
}
// Button groups.
.tbtn-group {

Просмотреть файл

@ -86,7 +86,7 @@
.labeling-detailed-view {
position: absolute;
top: 50px; left: 0;
.time-cursor { // FIXME: seems to only be used in referenceTrackOverview (why is this a class of labeling-detailed-view?
.time-cursor {
line {
stroke: black;
fill: none;
@ -257,7 +257,6 @@
}
}
// FIXME (remove when we remove slider)
.confidence-slider {
rect.histogram-bin {
fill: #CCC;

Просмотреть файл

@ -1,20 +1,13 @@
// The main view for the app.
import { TabID } from '../stores/dataStructures/types';
import * as stores from '../stores/stores';
import { NavigationColumn, NavigationColumnItem } from './common/NavigationColumn';
import { SharedAlignmentLabelingPane } from './common/SharedAlignmentLabelingPane';
import { DeploymentPanel } from './deployment/DeploymentPanel';
import { HomeMenu } from './menus/HomeMenu';
import { TabPane } from './TabPane';
import { HomeMenu } from './home/HomeMenu';
import { remote } from 'electron';
import { observer } from 'mobx-react';
// tslint:disable-next-line:import-name
import DevTools from 'mobx-react-devtools';
import * as React from 'react';
// Labeling app has some configuration code, then it calls LabelingView.
@observer
export class App extends React.Component<{}, {}> {
constructor(props: {}, context: any) {
@ -64,11 +57,17 @@ export class App extends React.Component<{}, {}> {
}
}
// FIXME: TabPane just refers to alignment/labeling relocate or split like other tabs
public render(): JSX.Element {
const debugging = remote.getGlobal('debugging');
return (
<div className='app-container container-fluid'>
<DevTools />
{debugging ?
// The following weird construct is to ensure that the devtools
// module is only loaded when the debugging flag is true.
// tslint:disable-next-line:no-require-imports
(DevTools => <DevTools />)(require('mobx-react-devtools').default) : null}
<NavigationColumn selected={stores.projectUiStore.currentTab} onSelect={
tab => {
stores.projectUiStore.currentTab = tab as TabID;
@ -78,13 +77,13 @@ export class App extends React.Component<{}, {}> {
<HomeMenu />
</NavigationColumnItem>
<NavigationColumnItem title='Alignment' name='alignment' showButton={true} iconClass={'glyphicon glyphicon-time'}>
<TabPane mode='alignment' toolbarHeight={40}/>
<SharedAlignmentLabelingPane mode='alignment' toolbarHeight={40} />
</NavigationColumnItem>
<NavigationColumnItem title='Labeling' name='labeling' showButton={true} iconClass={'glyphicon glyphicon-tags'}>
<TabPane mode='labeling' toolbarHeight={40}/>
<SharedAlignmentLabelingPane mode='labeling' toolbarHeight={40} />
</NavigationColumnItem>
<NavigationColumnItem title='Deployment' name='deploying' iconClass='glyphicon glyphicon-export'>
<DeploymentPanel toolbarHeight={40}/>
<DeploymentPanel toolbarHeight={40} />
</NavigationColumnItem>
</NavigationColumn>
</div>

Просмотреть файл

@ -1,6 +1,4 @@
// Alignment toolbar view.
// - Toolbar buttons for alignment.
import { alignmentStore, projectStore } from '../../stores/stores';
import { projectStore } from '../../stores/stores';
import { OptionsToolbar } from '../common/OptionsToolbar';
import { remote } from 'electron';
import { observer } from 'mobx-react';
@ -29,7 +27,7 @@ export class AlignmentToolbar extends React.Component<AlignmentToolbarProps, {}>
{
properties: ['openFile'],
filters: [
{ name: 'Videos', extensions: ['mov', 'mp4', 'webm'] },
{ name: 'Videos', extensions: ['mov', 'mp4', 'webm'] }
]
},
(fileNames: string[]) => {
@ -76,9 +74,9 @@ export class AlignmentToolbar extends React.Component<AlignmentToolbarProps, {}>
<span className='glyphicon glyphicon-folder-open'></span>Load Reference Video...
</button>
<button className='tbtn tbtn-l1'
title='Load a sensor track from collected data'
title='Load collected sensor data'
onClick={this.loadDataOrVideo}>
<span className='glyphicon glyphicon-folder-open'></span>Load Video or Sensor...
<span className='glyphicon glyphicon-folder-open'></span>Load Sensor Data...
</button>
<OptionsToolbar />
</div>

Просмотреть файл

@ -72,12 +72,6 @@ export class AlignmentView extends React.Component<AlignmentViewProps, Alignment
}
}
}
if (event.ctrlKey && event.keyCode === 'Z'.charCodeAt(0)) {
stores.projectStore.alignmentUndo();
}
if (event.ctrlKey && event.keyCode === 'Y'.charCodeAt(0)) {
stores.projectStore.alignmentRedo();
}
}
private onTrackMouseDown(
@ -227,7 +221,7 @@ export class AlignmentView extends React.Component<AlignmentViewProps, Alignment
const map = new Map<string, TrackLayout>();
let trackYCurrent = 50;
const trackMinimizedHeight = 40; // FIXME: I don't think minimized is every used
const trackMinimizedHeight = 40;
const referenceTrack = stores.projectStore.referenceTrack;
if (referenceTrack) {

Просмотреть файл

@ -1,4 +1,5 @@
import { SignalsViewMode } from '../../stores/dataStructures/labeling';
import { KeyCode } from '../../stores/dataStructures/types';
import * as stores from '../../stores/stores';
import { projectStore, projectUiStore } from '../../stores/stores';
import { observer } from 'mobx-react';
@ -7,22 +8,48 @@ import * as React from 'react';
@observer
export class OptionsToolbar extends React.Component<{}, {}> {
private setViewModeThunk: { [viewMode: number]: () => void } = {};
private changeFadeVideo(): void {
projectStore.fadeBackground(!projectStore.shouldFadeVideoBackground);
}
constructor(props: {}, context: any) {
super(props, context);
this.onKeyDown = this.onKeyDown.bind(this);
Object.keys(SignalsViewMode).forEach(name => {
const val = SignalsViewMode[name];
this.setViewModeThunk[val] = this.setViewMode.bind(this, val);
});
}
private setViewModeThunk: { [viewMode: number]: () => void } = {};
private changeFadeVideo(): void {
projectStore.fadeBackground(!projectStore.shouldFadeVideoBackground);
}
private changeTimeSeriesColor(): void {
projectUiStore.timeSeriesGrayscale = !projectUiStore.timeSeriesGrayscale;
}
private onKeyDown(event: KeyboardEvent): void {
if (event.keyCode === KeyCode.LEFT) {
stores.projectUiStore.zoomReferenceTrackByPercentage(-0.6);
}
if (event.keyCode === KeyCode.RIGHT) {
stores.projectUiStore.zoomReferenceTrackByPercentage(+0.6);
}
if (event.ctrlKey && event.keyCode === 'Z'.charCodeAt(0)) { // Ctrl-Z
stores.projectStore.undo();
}
if (event.ctrlKey && event.keyCode === 'Y'.charCodeAt(0)) { // Ctrl-Y
stores.projectStore.redo();
}
}
public componentDidMount(): void {
window.addEventListener('keydown', this.onKeyDown);
}
public componentWillUnmount(): void {
window.removeEventListener('keydown', this.onKeyDown);
}
private setViewMode(viewMode: SignalsViewMode): void {
stores.labelingUiStore.setSignalsViewMode(viewMode);
stores.labelingUiStore.signalsViewMode = viewMode;
}
public render(): JSX.Element {
@ -31,17 +58,35 @@ export class OptionsToolbar extends React.Component<{}, {}> {
};
return (
<div className='pull-right'>
<button className='tbtn tbtn-l3'
onClick={stores.projectStore.labelingUndo}>
Undo
<button className={'tbtn tbtn-l3' + (stores.projectStore.canUndo ? '' : ' disabled')}
onClick={stores.projectStore.undo} title='Undo' >
<span className='glyphicon icon-only glyphicon-share-alt flipped-icon'></span>
</button>
<button className={'tbtn tbtn-l3' + (stores.projectStore.canRedo ? '' : ' disabled')}
onClick={stores.projectStore.redo} title='Redo'>
<span className='glyphicon icon-only glyphicon-share-alt'></span>
</button>
<span className='sep' />
<span className='message' style={{marginRight: '5pt'}}>{projectStore.statusMessage}</span>
<div className='btn-group'>
<button className='tbtn tbtn-l3 dropdown-toggle' data-toggle='dropdown' title='Options'>
<span className='glyphicon icon-only glyphicon-cog'></span>
</button>
<ul className='dropdown-menu options-menu'>
<li className='dropdown-header'>Signals Display</li>
<li className='dropdown-header'>Time Series Color</li>
<li className='option-item'
role='button'
onClick={this.changeTimeSeriesColor}>
<span className='glyphicon icon-only glyphicon-ok'
style={{
visibility: projectUiStore.timeSeriesGrayscale ? 'visible' : 'hidden',
marginRight: '5pt'}
}>
</span>
Grayscale
</li>
<li role='separator' className='divider'></li>
<li className='dropdown-header'>Signals Display Type</li>
<li className='option-item'
role='button'
onClick={this.setViewModeThunk[SignalsViewMode.TIMESERIES]}>

Просмотреть файл

@ -1,8 +1,5 @@
// The 'Overview' view that is shared by both alignment and labeling.
import { getLabelKey } from '../../stores/dataStructures/labeling';
import { PanZoomParameters } from '../../stores/dataStructures/PanZoomParameters';
import { KeyCode } from '../../stores/dataStructures/types';
import * as stores from '../../stores/stores';
import { makePathDFromPoints, startDragging } from '../../stores/utils';
import { LabelType, LabelView } from '../labeling/LabelView';
@ -20,7 +17,7 @@ export interface ReferenceTrackOverviewProps {
downReach: number;
}
// The 'Overview' view that is shared by both alignment and labeling.
@observer
export class ReferenceTrackOverview extends React.Component<ReferenceTrackOverviewProps, {}> {
public refs: {
@ -30,18 +27,7 @@ export class ReferenceTrackOverview extends React.Component<ReferenceTrackOvervi
constructor(props: ReferenceTrackOverviewProps, context: any) {
super(props, context);
this.onKeyDown = this.onKeyDown.bind(this);
}
private onKeyDown(event: KeyboardEvent): void {
if (event.srcElement === document.body) {
if (event.keyCode === KeyCode.LEFT) {
stores.projectUiStore.zoomReferenceTrackByPercentage(-0.6);
}
if (event.keyCode === KeyCode.RIGHT) {
stores.projectUiStore.zoomReferenceTrackByPercentage(+0.6);
}
}
// this.onKeyDown = this.onKeyDown.bind(this);
}
private onMouseWheel(event: React.WheelEvent<Element>): void {
@ -58,14 +44,6 @@ export class ReferenceTrackOverview extends React.Component<ReferenceTrackOvervi
new PanZoomParameters(t - timeWindow / 2, null), true);
}
public componentDidMount(): void {
window.addEventListener('keydown', this.onKeyDown);
}
public componentWillUnmount(): void {
window.removeEventListener('keydown', this.onKeyDown);
}
private raiseOnDrag(mode: string, t0: number, t1: number): void {
const newStart = Math.min(t0, t1);
const newEnd = Math.max(t0, t1);
@ -154,17 +132,17 @@ export class ReferenceTrackOverview extends React.Component<ReferenceTrackOvervi
if (this.props.mode === 'labeling') {
labelsView = (
<g transform={`translate(${-globalPanZoom.pixelsPerSecond * globalPanZoom.rangeStart},0)`}>
{labels.map(label =>
<LabelView
key={getLabelKey(label)}
label={label}
pixelsPerSecond={globalPanZoom.pixelsPerSecond}
height={labelsY1 - labelsY0}
classColormap={stores.labelingStore.classColormap}
labelType={LabelType.Overview}
/>
)}
</g>
{labels.map(label =>
<LabelView
key={getLabelKey(label)}
label={label}
pixelsPerSecond={globalPanZoom.pixelsPerSecond}
height={labelsY1 - labelsY0}
classColormap={stores.labelingStore.classColormap}
labelType={LabelType.Overview}
/>
)}
</g>
);
}
@ -222,10 +200,11 @@ export class ReferenceTrackOverview extends React.Component<ReferenceTrackOvervi
</g>
</g>
<g className='time-cursor' transform='translate(0, 0)'>
<line className='bg' x1={cursorX} y1={0} x2={cursorX} y2={this.props.viewHeight} />
<line x1={cursorX} y1={0} x2={cursorX} y2={this.props.viewHeight} />
</g>
{!isNaN(cursorX) ?
<g className='time-cursor' transform='translate(0, 0)'>
<line className='bg' x1={cursorX} y1={0} x2={cursorX} y2={this.props.viewHeight} />
<line x1={cursorX} y1={0} x2={cursorX} y2={this.props.viewHeight} />
</g> : null}
<g className='brackets' transform='translate(0, 0)'>
<rect x={rangeX0} y={0} width={rangeX1 - rangeX0} height={this.props.viewHeight}

Просмотреть файл

@ -14,7 +14,8 @@ const mipmapCache = new MipmapCache();
export interface SensorPlotProps {
timeSeries: SensorTimeSeries; // The timeseries object to show, replace it with a NEW object if you need to update its contents.
dimensionVisibility?: boolean[]; // boolean array to show/hide individual dimensions.
colorScale?: d3.ScaleOrdinal<number, string>; // Colors to use, if null, use d3.category10 or 20.
grayscale: boolean;
//colorScale?: d3.ScaleOrdinal<number, string>; // Colors to use, if null, use d3.category10 or 20.
pixelsPerSecond: number; // Scaling factor (assume the dataset's timeunit is seconds).
plotHeight: number; // The height of the plot.
yDomain?: number[]; // The y domain: [ min, max ], similar to D3's scale.domain([min, max]).
@ -28,7 +29,7 @@ export class SensorPlot extends React.Component<SensorPlotProps, {}> {
// We consider the timeSeries object and colorScale constant, so any change INSIDE these objects will not trigger an update.
// To change the timeSeries, replace it with another object, don't update it directly.
return nextProps.timeSeries !== this.props.timeSeries ||
nextProps.colorScale !== this.props.colorScale ||
nextProps.grayscale !== this.props.grayscale ||
nextProps.pixelsPerSecond !== this.props.pixelsPerSecond ||
nextProps.plotHeight !== this.props.plotHeight ||
nextProps.alternateDimensions !== this.props.alternateDimensions ||
@ -61,8 +62,7 @@ export class SensorPlot extends React.Component<SensorPlotProps, {}> {
const yScale = 1 / (y0 - y1) * this.props.plotHeight;
// Determine color scale.
const colors = this.props.colorScale ||
(numSeries <= 10 ?
const colors = (numSeries <= 10 ?
d3.scaleOrdinal<number, string>(d3.schemeCategory10) :
d3.scaleOrdinal<number, string>(d3.schemeCategory20));
@ -87,16 +87,19 @@ export class SensorPlot extends React.Component<SensorPlotProps, {}> {
}
}
const d: string = 'M' + positions.join('L');
const style = {
fill: 'none',
stroke: colors(dimIndex)
};
// const grayscale = .6 - dimIndex * ((.6 - .2) / dimensions.length); //grayscale range from .6-.2 (dark to light)
// const style = {
// fill: 'none',
// stroke: 'rgba(0, 0, 0, ' + grayscale + ')'
// };
const grayscale = .6 - dimIndex * ((.6 - .2) / dimensions.length); //grayscale range from .6-.2 (dark to light)
let style = {
fill: 'none',
stroke: 'rgba(0, 0, 0, ' + grayscale + ')'
};
if ( !this.props.grayscale) {
style = {
fill: 'none',
stroke: colors(dimIndex)
};
}
return (
<path
d={d} style={style} key={dimIndex}
@ -118,7 +121,7 @@ export class SensorPlot extends React.Component<SensorPlotProps, {}> {
export interface SensorRangePlotProps {
timeSeries: SensorTimeSeries; // The timeseries object to show, replace it with a NEW object if you need to update its contents.
dimensionVisibility?: boolean[]; // boolean array to show/hide individual dimensions.
colorScale?: any; // Colors to use, if null, use d3.category10 or 20.
grayscale: boolean; // Colors to use, if null, use d3.category10 or 20.
pixelsPerSecond: number; // Scaling factor (assume the dataset's timeunit is seconds).
plotWidth: number; // The width of the plot.
plotHeight: number; // The height of the plot.
@ -135,7 +138,7 @@ export class SensorRangePlot extends React.Component<SensorRangePlotProps, {}> {
public shouldComponentUpdate(nextProps: SensorRangePlotProps): boolean {
return nextProps.timeSeries !== this.props.timeSeries ||
nextProps.colorScale !== this.props.colorScale ||
nextProps.grayscale !== this.props.grayscale ||
nextProps.pixelsPerSecond !== this.props.pixelsPerSecond ||
nextProps.plotHeight !== this.props.plotHeight ||
!isSameArray(nextProps.yDomain, this.props.yDomain) ||
@ -225,7 +228,7 @@ export class SensorRangePlot extends React.Component<SensorRangePlotProps, {}> {
<SensorPlot
key={`slice-${slice[0]}`}
timeSeries={timeseries}
colorScale={this.props.colorScale}
grayscale={this.props.grayscale}
pixelsPerSecond={this.props.pixelsPerSecond}
plotHeight={this.props.plotHeight}
yDomain={this.props.yDomain}

Просмотреть файл

@ -1,11 +1,11 @@
import {KeyCode} from '../stores/dataStructures/types';
import * as stores from '../stores/stores';
import {AlignmentToolbar} from './alignment/AlignmentToolbar';
import {AlignmentView} from './alignment/AlignmentView';
import {LabelingToolbar} from './labeling/LabelingToolbar';
import {LabelingView} from './labeling/LabelingView';
import {ReferenceTrackDetail} from './common/ReferenceTrackDetail';
import {ReferenceTrackOverview} from './common/ReferenceTrackOverview';
import {KeyCode} from '../../stores/dataStructures/types';
import * as stores from '../../stores/stores';
import {AlignmentToolbar} from '../alignment/AlignmentToolbar';
import {AlignmentView} from '../alignment/AlignmentView';
import {ReferenceTrackDetail} from '../common/ReferenceTrackDetail';
import {ReferenceTrackOverview} from '../common/ReferenceTrackOverview';
import {LabelingToolbar} from '../labeling/LabelingToolbar';
import {LabelingView} from '../labeling/LabelingView';
import * as React from 'react';
@ -18,7 +18,6 @@ interface AlignmentLabelingViewState {
layout: AlignmentLabelingViewLayout;
}
// FIXME: why are these not state variables?
interface AlignmentLabelingViewLayout {
viewWidth: number;
viewHeight: number;
@ -46,8 +45,7 @@ interface AlignmentLabelingViewLayout {
detailedViewY1: number;
}
// FIXME: this is not really a tabpane, its the tab for both alignment and labeling (see props/state names)
export class TabPane extends React.Component<AlignmentLabelingViewProps, AlignmentLabelingViewState> {
export class SharedAlignmentLabelingPane extends React.Component<AlignmentLabelingViewProps, AlignmentLabelingViewState> {
public refs: {
[key: string]: Element,
container: Element,
@ -106,7 +104,6 @@ export class TabPane extends React.Component<AlignmentLabelingViewProps, Alignme
}
public computeLayoutAttributes(viewWidth: number, viewHeight: number, props: AlignmentLabelingViewProps): AlignmentLabelingViewLayout {
// FIXME: rename svgs
// Layout parameters.
const overviewDetailsSVGXPadding1 = 8;
const overviewDetailsSVGXPadding2 = 8 + 20;
@ -233,8 +230,8 @@ export class TabPane extends React.Component<AlignmentLabelingViewProps, Alignme
<AlignmentView ref='alignmentView'
viewWidth={layout.detailedViewX1 - layout.detailedViewX0}
viewHeight={layout.detailedViewY1 - layout.detailedViewY0}
trackHeight={250} // FIXME: should this go in App?
trackGap={40} // FIXME: should this go in App?
trackHeight={250}
trackGap={40}
referenceDetailedViewHeight={layout.referenceDetailedViewHeight}
timeAxisHeight={layout.timeAxisHeight}
/>

Просмотреть файл

@ -20,7 +20,6 @@ export interface TrackViewProps {
viewHeight: number;
zoomTransform: PanZoomParameters;
signalsViewMode?: SignalsViewMode;
colorScale?: any;
useMipmap?: boolean;
videoDetail?: boolean;
@ -70,7 +69,6 @@ export class TrackView extends React.Component<TrackViewProps, {}> {
startX={startX} endX={endX}
height={seriesHeight}
startTime={startTime} endTime={endTime}
colorScale={this.props.colorScale}
useMipmap={this.props.useMipmap}
videoDetail={this.props.videoDetail}
onMouseDown={this.props.onMouseDown}
@ -97,7 +95,6 @@ export interface TimeSeriesViewProps {
height: number;
startTime: number;
endTime: number;
colorScale?: any;
useMipmap?: boolean;
videoDetail?: boolean;
signalsViewMode?: SignalsViewMode;
@ -231,7 +228,7 @@ export class TimeSeriesView extends React.Component<TimeSeriesViewProps, {}> {
rangeStart={this.props.startTime} pixelsPerSecond={this.pixelsPerSecond}
plotWidth={this.width} plotHeight={this.props.height}
useMipmap={this.props.useMipmap}
colorScale={this.props.colorScale}
grayscale={stores.projectUiStore.timeSeriesGrayscale}
/>
);
}
@ -281,7 +278,7 @@ export class TimeSeriesView extends React.Component<TimeSeriesViewProps, {}> {
onWheel={this.onWheel}
/>
{
timeCursor != null ? (
timeCursor != null && !isNaN(timeCursor) ? (
<line
x1={timeCursorX} y1={0}
x2={timeCursorX} y2={this.props.height}

Просмотреть файл

@ -4,7 +4,6 @@ import { observer } from 'mobx-react';
import * as path from 'path';
import * as React from 'react';
// FIXME: rename menus to home
// The 'Home' menu.
@observer
export class HomeMenu extends React.Component<{}, {}> {

Просмотреть файл

@ -123,7 +123,6 @@ export class LabelView extends React.Component<LabelViewProps, LabelViewState> {
this.props.label.state === LabelConfirmationState.CONFIRMED_BOTH;
}
// FIXME: what does this do?
private getSuggestionConfidenceOrOne(): number {
const suggestionConfidence = this.props.label.suggestionConfidence;
if (suggestionConfidence && this.props.label.state !== LabelConfirmationState.CONFIRMED_BOTH) {

Просмотреть файл

@ -46,12 +46,6 @@ export class LabelingView extends React.Component<LabelingViewProps, LabelingVie
}
}
}
if (event.ctrlKey && event.keyCode === 'Z'.charCodeAt(0)) { // Ctrl-Z
stores.projectStore.labelingUndo();
}
if (event.ctrlKey && event.keyCode === 'Y'.charCodeAt(0)) { // Ctrl-Y
stores.projectStore.labelingRedo();
}
}
private getRelativePosition(event: { clientX: number; clientY: number }): number[] {

Просмотреть файл

@ -24,13 +24,13 @@ export class AlignmentStore {
}
@action public addMarker(marker: Marker): void {
projectStore.alignmentHistoryRecord();
projectStore.recordAlignmentSnapshot();
this.markers.push(marker);
}
@action public updateMarker(marker: Marker, newLocalTimestamp: number, recompute: boolean = true, recordState: boolean = true): void {
if (recordState) {
projectStore.alignmentHistoryRecord();
projectStore.recordAlignmentSnapshot();
}
marker.localTimestamp = newLocalTimestamp;
if (recompute) {
@ -39,7 +39,7 @@ export class AlignmentStore {
}
@action public deleteMarker(marker: Marker): void {
projectStore.alignmentHistoryRecord();
projectStore.recordAlignmentSnapshot();
const index = this.markers.indexOf(marker);
if (index >= 0) {
this.markers.splice(index, 1);
@ -50,7 +50,7 @@ export class AlignmentStore {
}
@action public addMarkerCorrespondence(marker1: Marker, marker2: Marker): MarkerCorrespondence {
projectStore.alignmentHistoryRecord();
projectStore.recordAlignmentSnapshot();
const newCorr = new MarkerCorrespondence(marker1, marker2);
// Remove all conflicting correspondence.
this.correspondences = this.correspondences.filter(c => c.compatibleWith(newCorr));
@ -60,7 +60,7 @@ export class AlignmentStore {
}
@action public deleteMarkerCorrespondence(correspondence: MarkerCorrespondence): void {
projectStore.alignmentHistoryRecord();
projectStore.recordAlignmentSnapshot();
const index = this.correspondences.indexOf(correspondence);
if (index >= 0) {
this.correspondences.splice(index, 1);
@ -113,7 +113,6 @@ export class AlignmentStore {
return blocks;
}
public alignAllTracks(animate: boolean = false): void {
if (this.correspondences.length === 0) { return; }
projectStore.tracks.forEach(track => {
@ -172,7 +171,7 @@ export class AlignmentStore {
track.referenceStart = tsState.referenceStart;
track.referenceEnd = tsState.referenceEnd;
});
this.alignAllTracks(false);
//this.alignAllTracks(false);
}
public reset(): void {

Просмотреть файл

@ -53,10 +53,6 @@ export class LabelingStore {
return this._labelsIndex.items;
}
@computed public get suggestions(): Label[] {
return this._suggestedLabelsIndex.items;
}
public getLabelsInRange(timeRange: TimeRange): Label[] {
return mergeTimeRangeArrays(
this._labelsIndex.getRangesInRange(timeRange),
@ -65,12 +61,12 @@ export class LabelingStore {
@action public addLabel(label: Label): void {
projectStore.recordLabelingSnapshot();
this._labelsIndex.add(label);
projectStore.labelingHistoryRecord();
}
@action public removeLabel(label: Label): void {
projectStore.labelingHistoryRecord();
projectStore.recordLabelingSnapshot();
if (this._labelsIndex.has(label)) {
this._labelsIndex.remove(label);
}
@ -80,7 +76,7 @@ export class LabelingStore {
}
@action public updateLabel(label: Label, newLabel: PartialLabel): void {
projectStore.labelingHistoryRecord();
projectStore.recordLabelingSnapshot();
// Update the label info.
if (newLabel.timestampStart !== undefined) { label.timestampStart = newLabel.timestampStart; }
if (newLabel.timestampEnd !== undefined) { label.timestampEnd = newLabel.timestampEnd; }
@ -169,13 +165,13 @@ export class LabelingStore {
}
@action public removeAllLabels(): void {
projectStore.labelingHistoryRecord();
projectStore.recordLabelingSnapshot();
this._labelsIndex.clear();
this._suggestedLabelsIndex.clear();
}
@action public addClass(className: string): void {
projectStore.labelingHistoryRecord();
projectStore.recordLabelingSnapshot();
if (this.classes.indexOf(className) < 0) {
this.classes.push(className);
this.updateColors();
@ -183,7 +179,7 @@ export class LabelingStore {
}
@action public removeClass(className: string): void {
projectStore.labelingHistoryRecord();
projectStore.recordLabelingSnapshot();
// Remove the labels of that class.
const toRemove = [];
this._labelsIndex.forEach(label => {
@ -204,7 +200,7 @@ export class LabelingStore {
}
@action public renameClass(oldClassName: string, newClassName: string): void {
projectStore.labelingHistoryRecord();
projectStore.recordLabelingSnapshot();
if (this.classes.indexOf(newClassName) < 0) {
let renamed = false;
this._labelsIndex.forEach(label => {
@ -230,7 +226,7 @@ export class LabelingStore {
}
@action public confirmVisibleSuggestions(): void {
projectStore.labelingHistoryRecord();
projectStore.recordLabelingSnapshot();
// Get visible suggestions.
let visibleSuggestions = this._suggestedLabelsIndex.getRangesInRange(
projectUiStore.referenceTrackTimeRange);

Просмотреть файл

@ -3,7 +3,7 @@ import { Label, SignalsViewMode, TimeRange } from './dataStructures/labeling';
import { ObservableSet } from './dataStructures/ObservableSet';
import { LabelingStore } from './LabelingStore';
import { labelingStore } from './stores';
import { action, computed, observable } from 'mobx';
import { action, observable } from 'mobx';
export class LabelingUiStore {
@ -90,10 +90,6 @@ export class LabelingUiStore {
this.suggestionLogic = getLabelingSuggestionLogic(logic);
}
@action public setSignalsViewMode(mode: SignalsViewMode): void {
this.signalsViewMode = mode;
}
public getLabelsInRange(timeRange: TimeRange): Label[] {
const labels = labelingStore.getLabelsInRange(timeRange);
return labels.filter(l => !this.selectedLabels.has(l)).concat(

Просмотреть файл

@ -5,10 +5,10 @@ import { Track } from './dataStructures/alignment';
import { loadMultipleSensorTimeSeriesFromFile, loadRawSensorTimeSeriesFromFile, loadVideoTimeSeriesFromFile }
from './dataStructures/dataset';
import { DeferredCallbacks } from './dataStructures/DeferredCallbacks';
import { HistoryTracker } from './dataStructures/HistoryTracker';
import { PanZoomParameters } from './dataStructures/PanZoomParameters';
import { SavedAlignmentSnapshot, SavedLabelingSnapshot, SavedProject, SavedTrack }
from './dataStructures/project';
import { UndoRedoHistory } from './dataStructures/UndoRedoHistory';
import { convertToWebm, fadeBackground, isWebm } from './dataStructures/video';
import { alignmentStore, labelingStore, projectUiStore } from './stores';
import * as fs from 'fs';
@ -49,16 +49,19 @@ export class ProjectStore {
// Stores alignment and labeling history (undo is implemented separately, you can't undo alignment from labeling or vice versa).
private _alignmentHistory: HistoryTracker<SavedAlignmentSnapshot>;
private _labelingHistory: HistoryTracker<SavedLabelingSnapshot>;
private _alignmentUndoRedoHistory: UndoRedoHistory<SavedAlignmentSnapshot>;
private _labelingUndoRedoHistory: UndoRedoHistory<SavedLabelingSnapshot>;
constructor() {
this._alignmentHistory = new HistoryTracker<SavedAlignmentSnapshot>();
this._labelingHistory = new HistoryTracker<SavedLabelingSnapshot>();
this._alignmentUndoRedoHistory = new UndoRedoHistory<SavedAlignmentSnapshot>();
this._labelingUndoRedoHistory = new UndoRedoHistory<SavedLabelingSnapshot>();
this.referenceTrack = null;
this.tracks = [];
this.projectFileLocation = null;
this.statusMessage = '';
this.undo = this.undo.bind(this);
this.redo = this.redo.bind(this);
}
@ -84,15 +87,28 @@ export class ProjectStore {
return this.referenceTimestampEnd - this.referenceTimestampStart;
}
@computed public get canUndo(): boolean {
const tab = projectUiStore.currentTab;
const canUndoAlignment = this._alignmentUndoRedoHistory.canUndo;
const canUndoLabeling = this._labelingUndoRedoHistory.canUndo;
return tab === 'alignment' && canUndoAlignment || tab === 'labeling' && canUndoLabeling;
}
@computed public get canRedo(): boolean {
const tab = projectUiStore.currentTab;
const canRedoAlignment = this._alignmentUndoRedoHistory.canRedo;
const canRedoLabeling = this._labelingUndoRedoHistory.canRedo;
return tab === 'alignment' && canRedoAlignment || tab === 'labeling' && canRedoLabeling;
}
@action public loadReferenceTrack(path: string): void {
this.alignmentHistoryRecord();
this.recordAlignmentSnapshot();
loadVideoTimeSeriesFromFile(path, video => {
if (!isWebm(path)) {
const newPath = convertToWebm(
convertToWebm(
path, video.videoDuration,
pctDone => {
this.statusMessage = 'converting video: ' + (pctDone * 100).toFixed(0) + '%';
this.statusMessage = 'converting video: ' + (pctDone * 100).toFixed(0) + '%';
},
webmVideo => {
this.referenceTrack = Track.fromFile(webmVideo.filename, [webmVideo]);
@ -108,19 +124,19 @@ export class ProjectStore {
}
@action public loadVideoTrack(fileName: string): void {
this.alignmentHistoryRecord();
this.recordAlignmentSnapshot();
loadVideoTimeSeriesFromFile(fileName, video => {
this.tracks.push(Track.fromFile(fileName, [video]));
});
}
@action public loadSensorTrack(fileName: string): void {
this.alignmentHistoryRecord();
this.recordAlignmentSnapshot();
const sensors = loadMultipleSensorTimeSeriesFromFile(fileName);
this.tracks.push(Track.fromFile(fileName, sensors));
}
@action public fadeBackground(userChoice: boolean): void {
@action public fadeBackground(userChoice: boolean): void {
this.shouldFadeVideoBackground = userChoice;
if (this.shouldFadeVideoBackground) {
this.originalReferenceTrackFilename = this.referenceTrack.source;
@ -142,7 +158,7 @@ export class ProjectStore {
}
@action public deleteTrack(track: Track): void {
this.alignmentHistoryRecord();
this.recordAlignmentSnapshot();
const index = this.tracks.map(t => t.id).indexOf(track.id);
this.tracks.splice(index, 1);
}
@ -169,8 +185,8 @@ export class ProjectStore {
const json = fs.readFileSync(fileName, 'utf-8');
const project = JSON.parse(json);
this.projectFileLocation = null;
this.alignmentHistoryReset();
this.labelingHistoryReset();
this.resetAlignmentUndoRedoHistory();
this.resetLabelingUndoRedoHistory();
this.loadProjectHelper(project as SavedProject, () => {
this.projectFileLocation = fileName;
this.addToRecentProjects(fileName);
@ -354,6 +370,14 @@ export class ProjectStore {
alignmentStore.loadState(snapshot.alignment);
}
@action public recordAlignmentSnapshot(): void {
this._alignmentUndoRedoHistory.add(this.getAlignmentSnapshot());
}
@action private resetAlignmentUndoRedoHistory(): void {
this._alignmentUndoRedoHistory.reset();
}
private getLabelingSnapshot(): SavedLabelingSnapshot {
return { labeling: deepClone(labelingStore.saveState()) };
}
@ -362,47 +386,39 @@ export class ProjectStore {
labelingStore.loadState(snapshot.labeling);
}
public alignmentHistoryRecord(): void {
this._alignmentHistory.add(this.getAlignmentSnapshot());
@action public recordLabelingSnapshot(): void {
this._labelingUndoRedoHistory.add(this.getLabelingSnapshot());
}
private alignmentHistoryReset(): void {
this._alignmentHistory.reset();
@action private resetLabelingUndoRedoHistory(): void {
this._labelingUndoRedoHistory.reset();
}
public labelingHistoryRecord(): void {
this._labelingHistory.add(this.getLabelingSnapshot());
}
private labelingHistoryReset(): void {
this._labelingHistory.reset();
}
public alignmentUndo(): void {
const snapshot = this._alignmentHistory.undo(this.getAlignmentSnapshot());
if (snapshot) {
this.loadAlignmentSnapshot(snapshot);
@action public undo(): void {
if (projectUiStore.currentTab === 'alignment') {
const snapshot = this._alignmentUndoRedoHistory.undo(this.getAlignmentSnapshot());
if (snapshot) {
this.loadAlignmentSnapshot(snapshot);
}
} else {
const snapshot = this._labelingUndoRedoHistory.undo(this.getLabelingSnapshot());
if (snapshot) {
this.loadLabelingSnapshot(snapshot);
}
}
}
public alignmentRedo(): void {
const snapshot = this._alignmentHistory.redo(this.getAlignmentSnapshot());
if (snapshot) {
this.loadAlignmentSnapshot(snapshot);
}
}
public labelingUndo(): void {
const snapshot = this._labelingHistory.undo(this.getLabelingSnapshot());
if (snapshot) {
this.loadLabelingSnapshot(snapshot);
}
}
public labelingRedo(): void {
const snapshot = this._labelingHistory.redo(this.getLabelingSnapshot());
if (snapshot) {
this.loadLabelingSnapshot(snapshot);
@action public redo(): void {
if (projectUiStore.currentTab === 'alignment') {
const snapshot = this._alignmentUndoRedoHistory.redo(this.getAlignmentSnapshot());
if (snapshot) {
this.loadAlignmentSnapshot(snapshot);
}
} else {
const snapshot = this._labelingUndoRedoHistory.redo(this.getLabelingSnapshot());
if (snapshot) {
this.loadLabelingSnapshot(snapshot);
}
}
}

Просмотреть файл

@ -15,6 +15,8 @@ export class ProjectUiStore {
@observable public viewWidth: number;
@observable public currentTab: TabID;
@observable public timeSeriesGrayscale: boolean;
// Current transition.
private _referenceViewTransition: TransitionController;
@ -37,8 +39,8 @@ export class ProjectUiStore {
this._panZoomParameterMap = observable.map<PanZoomParameters>();
this.selectedMarker = null;
this.selectedCorrespondence = null;
this.getTimeCursor = this.getTimeCursor.bind(this);
this.timeSeriesGrayscale = false;
autorun('ProjectUiStore.onTracksChanged', () => this.onTracksChanged());
reaction(

Просмотреть файл

@ -1,9 +1,9 @@
import { action, computed, observable, ObservableMap } from 'mobx';
import * as Map from 'es6-map';
import { action, computed, observable, ObservableMap } from 'mobx';
// FIXME: this class requires the es6-map polyfill for Map. Consider changing the implementation to avoid this.
// This class requires the es6-map polyfill for Map.
export class ObservableSet<T> {
private map: ObservableMap<boolean>; //Observable<string,boolean>
private map: ObservableMap<boolean>;
private keyMap: Map<string, T>;
constructor(private getKey: (item: T) => string) {

Просмотреть файл

@ -22,21 +22,23 @@
// Snapshots need to be decoupled
// - They shouldn't reference to the same object which can be updated by the app.
// - Referencing to the same object is okay (and save space) if the object never changes.
import { action, computed, observable } from 'mobx';
export class UndoRedoHistory<TSnapshot> {
@observable private _undoHistory: TSnapshot[];
@observable private _redoHistory: TSnapshot[];
export class HistoryTracker<Snapshot> {
private _undoHistory: Snapshot[];
private _redoHistory: Snapshot[];
constructor() {
this._undoHistory = [];
this._redoHistory = [];
}
public add(item: Snapshot): void {
@action public add(item: TSnapshot): void {
this._undoHistory.push(item);
this._redoHistory = [];
}
public undo(current: Snapshot): Snapshot {
@action public undo(current: TSnapshot): TSnapshot {
const lastIndex = this._undoHistory.length - 1;
if (lastIndex >= 0) {
const [lastItem] = this._undoHistory.splice(lastIndex, 1);
@ -47,7 +49,7 @@ export class HistoryTracker<Snapshot> {
}
}
public redo(current: Snapshot): Snapshot {
@action public redo(current: TSnapshot): TSnapshot {
const lastIndex = this._redoHistory.length - 1;
if (lastIndex >= 0) {
const [lastItem] = this._redoHistory.splice(lastIndex, 1);
@ -58,15 +60,15 @@ export class HistoryTracker<Snapshot> {
}
}
public canUndo(): boolean {
@computed public get canUndo(): boolean {
return this._undoHistory.length > 0;
}
public canRndo(): boolean {
@computed public get canRedo(): boolean {
return this._redoHistory.length > 0;
}
public reset(): void {
@action public reset(): void {
this._undoHistory = [];
this._redoHistory = [];
}

Просмотреть файл

@ -130,6 +130,7 @@ export class Track {
});
// Find the translation and scale for correspondences.
if (tCorrespondences.length === 0) { return; } // The correspondences don't involve this track.
let [k, b] = leastSquares(tCorrespondences);
if (isNaN(k) || isNaN(b)) { k = 1; b = 0; } // Is this the right thing to do?
const project = x => k * x + b;
@ -140,10 +141,6 @@ export class Track {
}
// leastSquares([[yi, xi], ... ]) => [ k, b ] such that sum(k xi + b - yi)^2 is minimized.
function leastSquares(correspondences: [number, number][]): [number, number] {
if (correspondences.length === 0) { throw 'leastSquares empty array'; }

Просмотреть файл

@ -1,10 +1,3 @@
import { autocorrelogram } from '../suggestion/algorithms/Autocorrelation';
import { Dataset, SensorTimeSeries, TimeSeriesKind } from './dataStructures/dataset';
import { Label, LabelConfirmationState } from './dataStructures/labeling';
import * as d3 from 'd3';
export function startDragging(
move?: (e: MouseEvent) => void,
up?: (e: MouseEvent) => void,
@ -24,8 +17,6 @@ export function startDragging(
window.addEventListener('mouseup', handler_up, useCapture);
}
export function isSameArray<T>(arr1?: T[], arr2?: T[]): boolean {
return arr1 === arr2 || arr1 && arr2 && arr1.length === arr2.length && arr1.every((d, i) => d === arr2[i]);
}
@ -35,31 +26,6 @@ export function makePathDFromPoints(points: number[][]): string {
return 'M' + points.map(([x, y]) => x + ',' + y).join('L');
}
export function updateLabelConfirmationState(label: Label, endpoint: string): LabelConfirmationState {
let newState = label.state;
if (endpoint === 'start') {
if (label.state === LabelConfirmationState.UNCONFIRMED) {
newState = LabelConfirmationState.CONFIRMED_START;
} else if (label.state === LabelConfirmationState.CONFIRMED_END) {
newState = LabelConfirmationState.CONFIRMED_BOTH;
}
}
if (endpoint === 'end') {
if (label.state === LabelConfirmationState.UNCONFIRMED) {
newState = LabelConfirmationState.CONFIRMED_END;
} else if (label.state === LabelConfirmationState.CONFIRMED_START) {
newState = LabelConfirmationState.CONFIRMED_BOTH;
}
}
if (endpoint === 'both') {
newState = LabelConfirmationState.CONFIRMED_BOTH;
}
return newState;
}
export class TransitionController {
private _timer: number;
private _onProgress: (t: number, finish?: boolean) => void;
@ -92,9 +58,6 @@ export class TransitionController {
}
}
export class ArrayThrottler<ItemType, StationaryType> {
private _minInterval: number;
private _callback: (items: ItemType[], stationary: StationaryType) => void;
@ -146,82 +109,3 @@ export class ArrayThrottler<ItemType, StationaryType> {
}
}
export interface DatasetMetadata {
name: string;
sensors: {
name: string;
path: string;
timestampStart?: number;
timestampEnd?: number;
alignmentFix?: [number, number];
}[];
videos: {
name: string;
path: string;
timestampStart?: number;
timestampEnd?: number;
alignmentFix?: [number, number];
}[];
}
const autocorrelogramCache = new WeakMap<SensorTimeSeries, SensorTimeSeries>();
export function computeSensorTimeSeriesAutocorrelogram(timeSeries: SensorTimeSeries): SensorTimeSeries {
if (autocorrelogramCache.has(timeSeries)) { return autocorrelogramCache.get(timeSeries); }
const sampleRate = (timeSeries.dimensions[0].length - 1) / (timeSeries.timestampEnd - timeSeries.timestampStart);
const windowSize = Math.ceil(sampleRate * 4);
const sliceSize = Math.ceil(windowSize / 4);
const dimension = new Float32Array(timeSeries.dimensions[0].length);
for (let i = 0; i < timeSeries.dimensions[0].length; i++) {
dimension[i] = 0;
for (let j = 0; j < timeSeries.dimensions.length; j++) {
if (timeSeries.dimensions[j][i] === timeSeries.dimensions[j][i]) {
dimension[i] += timeSeries.dimensions[j][i];
}
}
}
const result = autocorrelogram(dimension, windowSize, sliceSize);
const sliceCount = result.length / windowSize;
const dimensions: Float32Array[] = [];
const samplesScale = d3.scaleLinear() // sample index <> sample timestamp
.domain([0, dimension.length - 1])
.range([timeSeries.timestampStart, timeSeries.timestampEnd]);
const sliceScale = d3.scaleLinear() // slice index <> slice timestamp
.domain([0, sliceCount - 1])
.range([samplesScale(0 + windowSize / 2), samplesScale(sliceSize * (sliceCount - 1) + windowSize / 2)]);
for (let i = 0; i < windowSize; i++) {
const t = dimensions[i] = new Float32Array(sliceCount);
for (let j = 0; j < sliceCount; j++) {
t[j] = result[j * windowSize + i];
if (t[j] !== t[j]) { t[j] = 0; }
}
}
const r = {
name: timeSeries.name + '.autocorrelogram',
kind: timeSeries.kind,
timestampStart: sliceScale.range()[0],
timestampEnd: sliceScale.range()[1],
dimensions: dimensions,
sampleRate: (sliceScale.range()[1] - sliceScale.range()[0]) / (dimension.length - 1),
scales: [[-1, 1]]
};
autocorrelogramCache.set(timeSeries, r);
return r;
}
export function computeDatasetAutocorrelogram(dataset: Dataset): Dataset {
const datasetOut = new Dataset();
datasetOut.name = dataset.name;
datasetOut.timestampStart = dataset.timestampStart;
datasetOut.timestampEnd = dataset.timestampEnd;
for (const series of dataset.timeSeries) {
if (series.kind === TimeSeriesKind.VIDEO) {
datasetOut.timeSeries.push(series);
} else {
datasetOut.timeSeries.push(computeSensorTimeSeriesAutocorrelogram(series as SensorTimeSeries));
}
}
return datasetOut;
}

Просмотреть файл

@ -1,88 +0,0 @@
Prerequisites for using Element with native modules
===================================================
- SWIG
- node.js
- node-gyp
- electron
- electron-rebuild
- d3 node.js module
- serial node.js module (modified for electron)
- the FeatureExtraction library
How to install requirements on Windows
--------------------------------------
- SWIG
Download the Windows zip file from swig.org's [download section](http://www.swig.org/download.html). Unzip this directory
and put it somewhere convenient (I used C:\swigwin-3.0.8). Make sure you set your PATH environment
variable to include the swigwin directory.
- node.js 4.3.2
Download from [https://nodejs.org]. Node.js installs npm, the node package manager
- node-gyp
npm install -g node-gyp
- electron
npm install electron-prebuilt -g
- electron-rebuild
npm install --save-dev electron-rebuild (note to self: what is save-dev?)
- d3
npm install d3
- serial
???
How to install requirements on OS X
-----------------------------------
- SWIG
homebrew install swig
- node.js
homebrew install node (got version 5.0.0) --- this installs node and
npm (node package manager)
- node-gyp
npm install -g node-gyp
- electron
npm install electron-prebuilt -g
- electron-rebuild
npm install --save-dev electron-rebuild (what is save-dev?)
- d3
npm install d3
- serial
???
How to install requirements on Linux
------------------------------------
- SWIG
apt-get swig
- node.js
apt-get node
- node-gyp
npm install -g node-gyp
- electron
npm install electron-prebuilt -g
- electron-rebuild
npm install --save-dev electron-rebuild (what is save-dev?)
- d3
npm install d3
- serial
???

Просмотреть файл

@ -1,125 +0,0 @@
Intelligent Devices UX style guide
====================================================
This is the coding style guide for UX projects.
Directory hierarchy
--------------
TODO: after setting up intern project spaces. Include build scripts?
File structure
--------------
Use one file per logical component (one main class per file plus any helpers).
Files should be organized in the following way:
* import statements
* props interface
* state interface
* class definition
* refs
* private fields
* public fields
* constructors
* getters/setters
* lifecycle methods (componentsWillMount, componentsDidMount...)
* event handlers
* other methods
* render
Naming
--------------
All names should be as descriptive as possible. Prefer whole words to abbreviations, especially if those abbreviations could be ambiguous, e.g., prefer `CurrentMousePosition` to `CurrMousePos`
Filenames:
Use camelCase, e.g., `dataStore.ts`
Component filenames should match the main component class, e.g., the `dataView.tsx` file should contain a `class DataView`.
Classes, interfaces, and enums:
Use PascalCase, e.g., `class DataView`.
Don't use "I" as a prefix for interface names.
Component props and state interfaces:
Use the same name as the corresponding component followed by "Props" or "State", e.g., `DataViewProps` and `DataViewState` for the `DataView` component.
Methods:
Use camelCase, e.g., `onDragStart()`, `zoomLevel()`
Member variables:
Use `_` (underscore) followed by camelCase, e.g., `_zoomLevel`
Getter and setter methods:
Use the same name as the corresponding member variable, minus the underscore, e.g., `public get zoomLevel()`
Enum values (TODO: double check this):
Use all caps with `_` (underscore) between words, e.g., `INPUT_DATA_CHANGED`
Event emitters, addListeners and removeListeners:
Use `private emit<event name>`, `public add<event name>Listener`, `public remove<event name>Listener`
Import statements:
Only import what you need. If you only need to import one class from a file, use `import {ClassName} from 'path'`. If you need to import more than one class, use `import * as className from 'path'`. Do not import using requires because these will not pick up the interface description from typings.
Types and modifiers
--------------
All methods and member variables should have access modifiers. Always opt for `private` where possible and `public` when necessary.
Always specify types for member variables, e.g., prefer `private _dataFileName : string = "";` to `private _dataFileName = "";`.
There is no need to specify types for local variables.
`any` types should be avoided.
`refs` should always start with `[key: string]: React.ReactInstance`. All additional refs should also be of type `React.ReactInstance`. For example:
refs: {
[key: string]: React.ReactInstance,
dataView: React.ReactInstance,
}
Always do type checks and set default values on state variables. e.g., TODO
Miscellaneous styling
-------
Keep lambdas simple e.g., prefer `x => x + x` to `(x) => x + x`.
Open curly braces always go on the same line as whatever necessitates them.
`else` and `else if` goes on a separate line from the closing curly brace.
Use fat arrow syntax `() => {` over `function() {`. The word function shouldn't appear in your code.
Use `let` not `var` for local variables.
Do not export types/functions unless you need to share them across components.
For anything not documented here, consult this guide: https://github.com/Microsoft/TypeScript/wiki/Coding-guidelines
Comments
-------
Only add comments for anything that is not obvious.
TODO statements can be used before checking in to master, e.g., for communicating to code reviewers. No TODOs should be checked in to master.
Interfacing with EMLL
-------
TODO
HTML and CSS
--------
Use bootstrap css as much as possible: http://getbootstrap.com/css/
Add custom styles to `styles.css`
Note that Typescript html code uses `className` not `class` whereas .html files use `class`

Просмотреть файл

@ -21,7 +21,7 @@
</script>
<script src="./node_modules/bootstrap/dist/js/bootstrap.js"></script>
<title>Gesture Builder - Intelligent Device Project</title>
<title>Embedded Learning Toolkit</title>
</head>
<!--

Просмотреть файл

@ -9,6 +9,7 @@ interface ExpectedEnv {
const debugging = process.argv.some(a => a === '--debugging');
if (!debugging) { (<ExpectedEnv>process.env).NODE_ENV = 'production'; }
(global as any).debugging = debugging;
// Module to create native browser window.
const window = electron.BrowserWindow;

Просмотреть файл

@ -1,67 +1,55 @@
{
"name": "integrated-app",
"name": "elt",
"productName": "Embedded Learning Toolkit",
"version": "0.0.1",
"description": "An application to create labels",
"main": "main.js",
"scripts": {
"start": "cross-env NODE_ENV=production electron main.js",
"run": "cross-env NODE_ENV=production electron main.js --debugging",
"run": "cross-env NODE_ENV=production electron main.js",
"debug": "cross-env NODE_ENV=debug electron main.js --debugging --remote-debugging-port=9222 ",
"build": "npm run build:ts && npm run build:worker && npm run build:less",
"build:ts": "tsc && tsc -p app",
"build:less": "lessc app/less/styles.less app/css/styles.compiled.css",
"build:worker": "browserify app/js/suggestion/worker/dtwsuggestionWorker.js -o app/js/suggestion/worker/suggestionWorker.browserified.js",
"prepublish": "npm run build",
"package:win32": "electron-packager . --platform=win32 --arch=x64 --ignore=\"/data($|/)\" --asar --out=../packaged",
"package:darwin": "electron-packager . --platform=darwin --arch=x64 --ignore=\"/data($|/)\" --asar --out=../packaged"
"package:win32": "electron-packager . --overwrite --platform=win32 --arch=x64 --ignore=\"/data($|/)\" --asar --out=../packaged",
"package:darwin": "electron-packager . --overwrite --platform=darwin --arch=x64 --ignore=\"/data($|/)\" --asar --out=../packaged"
},
"license": "MIT",
"repository": {
"type": "git",
"url": "git+https://cjacobs.visualstudio.com/DefaultCollection/_git/ElectronApp"
},
"keywords": [],
"author": "cjacobs",
"devDependencies": {
"cross-env": "^2.0.1",
"electron-packager": "^8.1.0",
"electron": "^1.4.4",
"electron-rebuild": "^1.1.5",
"mobx-react-devtools": "^4.2.11",
"tslint": "^3.15.1"
"tslint": "^3.15.1",
"tslint-microsoft-contrib": "^2.0.13"
},
"dependencies": {
"@types/d3": "^4.4.0",
"@types/electron": "^1.4.29",
"@types/es6-promise": "0.0.32",
"@types/flux": "0.0.33",
"@types/es6-promise": "0.0.32",
"@types/node": "^6.0.52",
"@types/react": "^0.14.55",
"@types/react-bootstrap": "0.0.37",
"@types/react-dom": "^0.14.19",
"bootstrap": "^3.3.6",
"browserify": "^13.1.1",
"d3": "^4.4.0",
"digitalsignals": "^1.0.0",
"electron": "^1.4.12",
"electron-packager": "^8.4.0",
"emll": "file:../../emllModule",
"es6-map": "^0.1.4",
"es6-promise": "^3.2.1",
"flux": "^2.1.1",
"jquery": "^2.2.2",
"less": "^2.7.1",
"mobx": "^3.0.0",
"mobx-react": "^4.1.0",
"monaco-editor": "^0.7.0",
"react": "^15.4.1",
"react-bootstrap": "^0.30.3",
"react-dom": "^15.1.0",
"requirejs": "^2.2.0",
"serialport": "^2.1.2",
"tslint-microsoft-contrib": "^2.0.13",
"typescript": "^2.1.6",
"typescript-require": "^0.2.9-1",
"underscore": "^1.8.3"
"typescript-require": "^0.2.9-1"
}
}