-
Notifications
You must be signed in to change notification settings - Fork 5
UI rewrite #117
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
UI rewrite #117
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -15,3 +15,6 @@ __pycache__ | |
| .mypy_cache | ||
| .nox | ||
| .coverage | ||
|
|
||
| # NPM build artifacts | ||
| app/dist/ | ||
| 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 | ||
| 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 | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| { | ||
| "tabWidth": 2, | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I know that
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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" | ||
| } | ||
| 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" | ||
| } | ||
| } |
| 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
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
| webPreferences: { | ||
| nodeIntegration: true, | ||
| }, | ||
| }); | ||
|
|
||
| // and load the index.html of the app. | ||
| win.loadFile('index.html'); | ||
| } | ||
|
|
||
| app.on('ready', createWindow); | ||
| 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> | ||
| ); | ||
| } |
| 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> | ||
| ); | ||
| } |
| 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%)', | ||
| }} | ||
| /> | ||
| ); | ||
| } |
| 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> | ||
| ); | ||
| } |
| 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> | ||
| ); | ||
| } |
| 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> | ||
| ); | ||
| } |

There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why off?
There was a problem hiding this comment.
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
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.