-
Notifications
You must be signed in to change notification settings - Fork 5
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
UI rewrite #117
base: master
Are you sure you want to change the base?
UI rewrite #117
Changes from 1 commit
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, | ||
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 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,68 @@ | ||
{ | ||
"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", | ||
"test": "echo \"Error: no test specified\" && exit 1" | ||
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. Is this "coming soon" ? 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. :) Yes WIP |
||
}, | ||
"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/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", | ||
"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" | ||
}, | ||
"dependencies": { | ||
"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-css": "^2.4.1", | ||
"semantic-ui-react": "^0.88.2", | ||
"styled-components": "^4.4.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
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,25 @@ | ||
import * as React from 'react'; | ||
|
||
import { Responsive, Segment } from 'semantic-ui-react'; | ||
|
||
import { TraceItem } from './TraceSelectorRow'; | ||
import Sidebar from './Sidebar'; | ||
import Window from './Window'; | ||
|
||
interface Props {} | ||
|
||
export default function App(_: Props): React.ReactElement { | ||
const [currentTrace, setCurrentTrace] = React.useState<TraceItem | null>( | ||
null, | ||
); | ||
return ( | ||
<Segment.Group horizontal> | ||
<Responsive | ||
as={Sidebar} | ||
currentTrace={currentTrace} | ||
setCurrentTrace={setCurrentTrace} | ||
/> | ||
<Responsive as={Window} currentTrace={currentTrace} /> | ||
</Segment.Group> | ||
); | ||
} |
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,53 @@ | ||
import * as React from 'react'; | ||
|
||
import { AnnotationData } from 'react-image-annotation'; | ||
import { SemanticCOLORS } from 'semantic-ui-react'; | ||
import styled from 'styled-components'; | ||
|
||
interface Props { | ||
annotation: AnnotationData<{}, HasXY, HasColor>; | ||
active: boolean; | ||
} | ||
|
||
interface HasColor { | ||
color: SemanticCOLORS; | ||
} | ||
|
||
interface HasXY { | ||
x: number; | ||
y: number; | ||
} | ||
|
||
// prettier-ignore | ||
const Container = styled.div` | ||
border: solid 3px white; | ||
border-radius: 50%; | ||
box-sizing: border-box; | ||
box-shadow: | ||
0 0 0 1px rgba(0,0,0,0.3), | ||
0 0 0 2px rgba(0,0,0,0.2), | ||
0 5px 4px rgba(0,0,0,0.4); | ||
height: 16px; | ||
position: absolute; | ||
transform: translate3d(-50%, -50%, 0); | ||
width: 16px; | ||
` | ||
|
||
export default function Point({ | ||
annotation, | ||
}: Props): React.ReactElement | null { | ||
const { geometry } = annotation; | ||
if (geometry == null) { | ||
return null; | ||
} | ||
|
||
return ( | ||
<Container | ||
style={{ | ||
top: `${geometry.y}%`, | ||
left: `${geometry.x}%`, | ||
'background-color': annotation.data?.color, | ||
}} | ||
/> | ||
); | ||
} |
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'; | ||
|
||
interface Props { | ||
placeholder: string; | ||
} | ||
|
||
export default function SeekableSelector({ | ||
placeholder, | ||
}: Props): React.ReactElement { | ||
return ( | ||
<div> | ||
<Button icon> | ||
<Icon name="fast backward" /> | ||
</Button> | ||
<Button icon> | ||
<Icon name="backward" /> | ||
</Button> | ||
<Input placeholder={placeholder} /> | ||
<Button icon> | ||
<Icon name="forward" /> | ||
</Button> | ||
<Button icon> | ||
<Icon name="fast forward" /> | ||
</Button> | ||
</div> | ||
); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
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'; | ||
|
||
interface 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> | ||
<SeekableSelector placeholder="Bundle..." /> | ||
<SeekableSelector placeholder="Frame..." /> | ||
<TraceSelector traces={traceElements} onAddTrace={upsertTrace} /> | ||
<PlaybackControls /> | ||
</Segment> | ||
); | ||
} |
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.