Skip to content
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 @@ -15,3 +15,6 @@ __pycache__
.mypy_cache
.nox
.coverage

# NPM build artifacts
app/dist/
55 changes: 55 additions & 0 deletions app/.eslintrc.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
env:
browser: true
es6: true
node: true
extends:
- 'plugin:react/recommended'
- 'plugin:@typescript-eslint/recommended'
- airbnb
- 'plugin:prettier/recommended'
globals:
Atomics: readonly
SharedArrayBuffer: readonly
parser: '@typescript-eslint/parser'
parserOptions:
ecmaFeatures:
jsx: true
ecmaVersion: 2018
sourceType: module
plugins:
- react
- '@typescript-eslint'
rules:
sort-imports: off

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why off?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm running into an annoying issue where imports of the form

import Default, { something } from 'foo';

are a lint error because braced imports are supposed to occur before default imports. After fiddling around with the settings a bit, I gave up because it wasn't worth the effort. If you have any ideas I'm all ears.

react/jsx-filename-extension:
- error
- extensions:
- ".jsx"
- ".tsx"
import/extensions:
- error
- ignorePackages
- tsx: never
ts: never
js: never
jsx: never
no-unused-vars: off
'@typescript-eslint/no-unused-vars':
- error
- argsIgnorePattern: "^_"
'@typescript-eslint/no-empty-interface': off
'@typescript-eslint/explicit-function-return-type':
- error
- allowExpressions: true
'@typescript-eslint/camelcase': off
settings:
import/resolver:
node:
paths:
- src
extensions:
- .js
- .jsx
- .ts
- .tsx
- .jpg
6 changes: 6 additions & 0 deletions app/.prettierrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"tabWidth": 2,

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know that 2 is the default for javascript, but how would you feel about using 4 here to be more consistent with the rest of the repo?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have a weak preference for 2. It's pretty common for multi-language projects to have separate styles for each language

"useTabs": false,
"singleQuote": true,
"trailingComma": "all"
}
75 changes: 75 additions & 0 deletions app/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
{
"name": "ultratrace",
"version": "1.0.0",
"description": "A tool for manually annotating ultrasound tongue imaging data",
"main": "index.js",
"scripts": {
"build": "webpack --config ./webpack.config.js",
"start": "npm run build && electron ./dist/main.js",
"electron": "electron ./dist/main.js",
"watch": "webpack --watch"
},
"repository": {
"type": "git",
"url": "git+https://github.com/SwatPhonLab/UltraTrace.git"
},
"author": "Jonathan Washington",
"license": "GPL-3.0",
"bugs": {
"url": "https://github.com/SwatPhonLab/UltraTrace/issues"
},
"homepage": "https://github.com/SwatPhonLab/UltraTrace#readme",
"devDependencies": {
"@interactjs/types": "^1.8.0-rc.1",
"@types/classnames": "^2.2.9",
"@types/node": "^12.12.21",
"@types/ramda": "^0.26.38",
"@types/react": "^16.9.17",
"@types/react-calendar-timeline": "^0.26.0",
"@types/react-dom": "^16.9.4",
"@types/styled-components": "^4.4.1",
"@typescript-eslint/eslint-plugin": "^2.12.0",
"@typescript-eslint/parser": "^2.12.0",
"css-loader": "^3.4.0",
"eslint": "^6.8.0",
"eslint-config-airbnb": "^18.0.1",
"eslint-config-prettier": "^6.7.0",
"eslint-plugin-import": "^2.19.1",
"eslint-plugin-jsx-a11y": "^6.2.3",
"eslint-plugin-prettier": "^3.1.2",
"eslint-plugin-react": "^7.17.0",
"eslint-plugin-react-hooks": "^1.7.0",
"eslint-plugin-sort-imports-es6-autofix": "^0.5.0",
"file-loader": "^5.0.2",
"html-webpack-plugin": "^3.2.0",
"less": "^3.10.3",
"less-loader": "^5.0.0",
"prettier": "^1.19.1",
"source-map-loader": "^0.2.4",
"style-loader": "^1.1.1",
"ts-loader": "^6.2.1",
"typescript": "^3.7.4",
"url-loader": "^3.0.0",
"webpack": "^4.41.4",
"webpack-cli": "^3.3.10",
"webpack-dev-server": "^3.10.1"
},
"dependencies": {
"classnames": "^2.2.6",
"electron": "^7.1.7",
"immutable": "^4.0.0-rc.12",
"interactjs": "^1.7.3",
"moment": "^2.24.0",
"ramda": "^0.26.1",
"react": "^16.12.0",
"react-calendar-timeline": "^0.26.7",
"react-dom": "^16.12.0",
"react-image-annotation": "^0.9.10",
"react-pinch-and-zoom": "^1.2.4",
"semantic-ui-less": "^2.4.1",
"semantic-ui-react": "^0.88.2",
"styled-components": "^4.4.1",
"tachyons": "^4.11.1",
"uuidv4": "^6.0.0"
}
}
Binary file added app/src/img/img_grid_square.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
17 changes: 17 additions & 0 deletions app/src/main/main.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { BrowserWindow, app } from 'electron';

function createWindow(): void {
// Create the browser window.
const win = new BrowserWindow({
width: 800,
height: 600,
Comment on lines +6 to +7

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The default window size needs to be bumped up a bit. This was the first render when I ran the app:
Screenshot_2019-12-26_09-46-47

webPreferences: {
nodeIntegration: true,
},
});

// and load the index.html of the app.
win.loadFile('index.html');
}

app.on('ready', createWindow);
18 changes: 18 additions & 0 deletions app/src/renderer/components/App.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import * as React from 'react';
import { TraceItem } from './TraceSelectorRow';
import Sidebar from './Sidebar';
import Window from './Window';

type Props = {};

export default function App(_: Props): React.ReactElement {
const [currentTrace, setCurrentTrace] = React.useState<TraceItem | null>(
null,
);
return (
<div className="flex items-start">
<Sidebar currentTrace={currentTrace} setCurrentTrace={setCurrentTrace} />
<Window currentTrace={currentTrace} />
</div>
);
}
26 changes: 26 additions & 0 deletions app/src/renderer/components/PlaybackControls.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import * as React from 'react';
import { Button, Icon } from 'semantic-ui-react';

interface Props {}

export default function PlaybackControls(_: Props): React.ReactElement {
const [isPlaying, setIsPlaying] = React.useState(false);

const toggleIsPlaying = React.useCallback(() => setIsPlaying(!isPlaying), [
isPlaying
]);

return (
<Button.Group>
<Button icon>
<Icon name="backward" />
</Button>
<Button icon onClick={toggleIsPlaying}>
<Icon name={isPlaying ? 'pause' : 'play'} />
</Button>
<Button icon>
<Icon name="forward" />
</Button>
</Button.Group>
);
}
37 changes: 37 additions & 0 deletions app/src/renderer/components/Point.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import * as React from 'react';
import { AnnotationData } from 'react-image-annotation';
import { SemanticCOLORS } from 'semantic-ui-react';

interface Props {
annotation: AnnotationData<{}, HasXY, HasColor>;
active: boolean;
}

interface HasColor {
color: SemanticCOLORS;
}

interface HasXY {
x: number;
y: number;
}

export default function Point({
annotation,
}: Props): React.ReactElement | null {
const { geometry } = annotation;
if (geometry == null) {
return null;
}

return (
<div
className="br-100 w1 h1 bg-blue absolute border-box ba bw1 b--white"
style={{
top: `${geometry.y}%`,
left: `${geometry.x}%`,
transform: 'translate(-50%, -50%)',
}}
/>
);
}
29 changes: 29 additions & 0 deletions app/src/renderer/components/SeekableSelector.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import * as React from 'react';

import { Button, Icon, Input } from 'semantic-ui-react';

type Props = {
placeholder: string;
};

export default function SeekableSelector({
placeholder,
}: Props): React.ReactElement {
return (
<div className="flex">
<Button className="ph3" icon>
<Icon name="fast backward" />
</Button>
<Button className="ph3" icon>
<Icon name="backward" />
</Button>
<Input className="ph3" placeholder={placeholder} />
<Button className="ph3" icon>
<Icon name="forward" />
</Button>
<Button className="ph3" icon>
<Icon name="fast forward" />
</Button>
</div>
);
}
66 changes: 66 additions & 0 deletions app/src/renderer/components/Sidebar.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import * as React from 'react';

import { OrderedMap } from 'immutable';
import { CheckboxProps, Segment } from 'semantic-ui-react';

import TraceSelectorRow, { TraceItem } from './TraceSelectorRow';

import PlaybackControls from './PlaybackControls';
import SeekableSelector from './SeekableSelector';
import TraceSelector from './TraceSelector';

type Props = {
currentTrace: TraceItem | null;
setCurrentTrace: (item: TraceItem | null) => void;
};

/**
* The application sidebar. Provides an interface to select frames, modify
* traces, and control playback.
*/
export default function Sidebar({
currentTrace,
setCurrentTrace,
}: Props): React.ReactElement {
const [traces, setTraces] = React.useState<OrderedMap<string, TraceItem>>(
OrderedMap(),
);
const upsertTrace = React.useCallback(
(item: TraceItem) => setTraces(traces.set(item.id, item)),
[traces],
);
const lookupTraceByKeyAndSetCurrent = React.useCallback(
(data: CheckboxProps) => {
const { value } = data;
if (value == null || typeof value === 'number') {
throw new Error(`Expected a string but got '${value}'`);
}
setCurrentTrace(traces.get(value, null));
},
[traces],
);

const traceElements = traces
.map((item: TraceItem) => (
<TraceSelectorRow
key={item.id}
item={item}
onEditItem={upsertTrace}
isSelected={currentTrace?.id === item.id}
onSelect={lookupTraceByKeyAndSetCurrent}
/>
))
.valueSeq();
return (
<Segment className="h-100">
<div className="pb3">
<SeekableSelector placeholder="Bundle..." />
</div>
<div className="pb3">
<SeekableSelector placeholder="Frame..." />
</div>
<TraceSelector traces={traceElements} onAddTrace={upsertTrace} />
<PlaybackControls />
</Segment>
);
}
47 changes: 47 additions & 0 deletions app/src/renderer/components/TraceSelector.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import * as React from 'react';

import { Button, Icon, Table } from 'semantic-ui-react';
import { Seq } from 'immutable';
import { uuid } from 'uuidv4';
import TraceSelectorRow, { TraceItem } from './TraceSelectorRow';

type Props = {
traces: Seq.Indexed<
React.ReactElement<
React.ComponentProps<typeof TraceSelectorRow>,
typeof TraceSelectorRow
>
>;
onAddTrace: (item: TraceItem) => void;
};

/**
* The list of traces. Provides an interface to modify the trace list.
*/
export default function TraceSelector({
traces,
onAddTrace,
}: Props): React.ReactElement {
const addTrace = React.useCallback(() => {
onAddTrace({ id: uuid(), name: '', color: 'blue' });
}, [onAddTrace]);
return (
<Table selectable>
<Table.Body>{traces}</Table.Body>
<Table.Footer>
<Table.Row>
<Table.HeaderCell colSpan="3">
<Button.Group>
<Button icon>
<Icon name="minus" />
</Button>
<Button icon>
<Icon name="plus" onClick={addTrace} />
</Button>
</Button.Group>
</Table.HeaderCell>
</Table.Row>
</Table.Footer>
</Table>
);
}
Loading