Skip to content
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

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
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
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
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
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
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"
}
68 changes: 68 additions & 0 deletions app/package.json
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"
Copy link
Collaborator

Choose a reason for hiding this comment

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

Is this "coming soon" ?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The 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"
}
}
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
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);
25 changes: 25 additions & 0 deletions app/src/renderer/components/App.tsx
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>
);
}
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>
);
}
53 changes: 53 additions & 0 deletions app/src/renderer/components/Point.tsx
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,
}}
/>
);
}
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';

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>
);
}
62 changes: 62 additions & 0 deletions app/src/renderer/components/Sidebar.tsx
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>
);
}
Loading