Skip to content
Next Next commit
Fix rebase onto master and add shortcut features - minor refactoring
  • Loading branch information
seveibar committed Mar 19, 2020
commit 2a764cb930081dc00efb985e000137d95cd8f00d
9 changes: 5 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
"react-use": "^13.27.0",
"react-use-measure": "^2.0.0",
"seamless-immutable": "^7.1.4",
"storybook": "^5.3.14",
"transformation-matrix-js": "^2.7.6",
"use-event-callback": "^0.1.0",
"use-key-hook": "^1.3.0"
Expand Down Expand Up @@ -58,10 +59,10 @@
"devDependencies": {
"@babel/cli": "^7.2.3",
"@babel/core": "^7.2.2",
"@storybook/addon-actions": "^5.1.11",
"@storybook/addon-links": "^5.1.11",
"@storybook/addons": "^5.1.11",
"@storybook/react": "^5.1.11",
"@storybook/addon-actions": "^5.3.14",
"@storybook/addon-links": "^5.3.14",
"@storybook/addons": "^5.3.14",
"@storybook/react": "^5.3.14",
"babel-loader": "^8.0.5",
"babel-preset-react-app": "^7.0.0",
"gh-pages": "^2.0.1",
Expand Down
33 changes: 23 additions & 10 deletions src/Annotator/index.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// @flow

import React, { useReducer } from "react"
import React, { useReducer, useEffect } from "react"
import MainLayout from "../MainLayout"
import type {
ToolEnum,
Expand All @@ -16,8 +16,8 @@ import generalReducer from "./reducers/general-reducer.js"
import imageReducer from "./reducers/image-reducer.js"
import videoReducer from "./reducers/video-reducer.js"
import historyHandler from "./reducers/history-handler.js"
import useEventCallback from "use-event-callback"

import useEventCallback from "use-event-callback"
import makeImmutable, { without } from "seamless-immutable"

type Props = {
Expand Down Expand Up @@ -58,6 +58,8 @@ export const Annotator = ({
videoTime = 0,
videoName,
onExit
onNextImage,
onPrevImage
}: Props) => {
if (!images && !videoSrc)
return 'Missing required prop "images" or "videoSrc"'
Expand Down Expand Up @@ -102,19 +104,30 @@ export const Annotator = ({
)

const dispatch = useEventCallback((action: Action) => {
if (
action.type === "HEADER_BUTTON_CLICKED" &&
["Exit", "Done", "Save", "Complete"].includes(action.buttonName)
) {
onExit(without(state, "history"))
} else {
dispatchToReducer(action)
if (action.type === "HEADER_BUTTON_CLICKED") {
if (["Exit", "Done", "Save", "Complete"].includes(action.buttonName)) {
return onExit(without(state, "history"))
} else if (action.buttonName === "Next") {
return onNextImage(without(state, "history"))
} else if (action.buttonName === "Prev") {
return onPrevImage(without(state, "history"))
}
}
dispatchToReducer(action)
})

useEffect(() => {
dispatchToReducer({ type: "SELECT_IMAGE", image: state.images.find(img => img.src === selectedImage) })
}, [selectedImage])

return (
<SettingsProvider>
<MainLayout state={state} dispatch={dispatch} />
<MainLayout
alwaysShowNextButton={Boolean(onNextImage)}
alwaysShowPrevButton={Boolean(onPrevImage)}
state={state}
dispatch={dispatch}
/>
</SettingsProvider>
)
}
Expand Down
42 changes: 41 additions & 1 deletion src/Annotator/index.story.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// @flow

import React from "react"
import React, { useState } from "react"

import { storiesOf } from "@storybook/react"
import { action as actionAddon } from "@storybook/addon-actions"
Expand Down Expand Up @@ -467,3 +467,43 @@ storiesOf("Annotator", module)
/>
</div>
))
.add("Override Next/Prev Button Handling", () => {
const images = [
exampleImage,
"https://loremflickr.com/100/100/cars?lock=1",
"https://loremflickr.com/100/100/cars?lock=2"
]
const [selectedImageIndex, changeSelectedImageIndex] = useState(0)

return (
<Annotator
onExit={actionAddon("onExit")}
onNextImage={() => {
actionAddon("onNextImage")()
changeSelectedImageIndex((selectedImageIndex + 1)%3)
}}
onPrevImage={() => {
actionAddon("onPrevImage")()
changeSelectedImageIndex((selectedImageIndex - 1 + 3)%3)
}}
labelImages
selectedImage={images[selectedImageIndex]}
regionClsList={["Alpha", "Beta", "Charlie", "Delta"]}
imageClsList={["Alpha", "Beta", "Charlie", "Delta"]}
images={[
{
src: exampleImage,
name: "Seve's Desk",
},
{
src: images[1],
name: "Frame 0036"
},
{
src: images[2],
name: "Frame 0037"
}
]}
/>
)
})
13 changes: 7 additions & 6 deletions src/DemoSite/Editor.js
Original file line number Diff line number Diff line change
Expand Up @@ -123,12 +123,13 @@ const Editor = ({ onOpenAnnotator, lastOutput }: any) => {
className="button"
variant="outlined"
disabled={Boolean(currentError)}
onClick={() =>
onOpenAnnotator(
selectedExample === "Custom"
? loadSavedInput()
: examples[selectedExample]
)
onClick={() =>{
onOpenAnnotator(
selectedExample === "Custom"
? loadSavedInput()
: examples[selectedExample]
)
}
}
>
Open Annotator
Expand Down
10 changes: 7 additions & 3 deletions src/Header/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,9 @@ export const Header = ({
multipleImages,
videoPlaying,
onChangeCurrentTime,
keyframes
keyframes,
alwaysShowPrevButton,
alwaysShowNextButton
}: Props) => {
const classes = useStyles()
return (
Expand All @@ -65,9 +67,11 @@ export const Header = ({
)}
<div className={classes.headerActions}>
<HeaderButtonContext.Provider value={{ onHeaderButtonClick }}>
{multipleImages && (
{(multipleImages || alwaysShowPrevButton) && (
<HeaderButton name="Prev" Icon={BackIcon} />
)}
{(multipleImages || alwaysShowNextButton) && (
<>
<HeaderButton name="Prev" Icon={BackIcon} />
<HeaderButton name="Next" Icon={NextIcon} />
<HeaderButton
name="Clone"
Expand Down
9 changes: 7 additions & 2 deletions src/MainLayout/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,12 @@ const useStyles = makeStyles(styles)

type Props = {
state: MainLayoutState,
dispatch: Action => any
dispatch: Action => any,
alwaysShowNextButton: boolean,
alwaysShowPrevButton: boolean
}

export const MainLayout = ({ state, dispatch }: Props) => {
export default ({ state, dispatch, alwaysShowNextButton = false, alwaysShowPrevButton = false }: Props) => {
const classes = useStyles()
const settings = useSettings()

Expand Down Expand Up @@ -80,6 +82,8 @@ export const MainLayout = ({ state, dispatch }: Props) => {
<Header
onHeaderButtonClick={action("HEADER_BUTTON_CLICKED", "buttonName")}
videoMode={state.annotationType === "video"}
alwaysShowNextButton={alwaysShowNextButton}
alwaysShowPrevButton={alwaysShowPrevButton}
inFullScreen={state.fullScreen}
isAVideoFrame={isAVideoFrame}
nextVideoFrameHasRegions={
Expand Down Expand Up @@ -206,6 +210,7 @@ export const MainLayout = ({ state, dispatch }: Props) => {
onRestoreHistory={action("RESTORE_HISTORY")}
onChangeVideoTime={action("CHANGE_VIDEO_TIME", "newTime")}
onDeleteKeyframe={action("DELETE_KEYFRAME", "time")}
onShortcutActionDispatched={dispatch}
/>
</div>
</div>
Expand Down
2 changes: 1 addition & 1 deletion src/MainLayout/types.js
Original file line number Diff line number Diff line change
Expand Up @@ -127,5 +127,5 @@ export type Action =
| {| type: "CLOSE_REGION_EDITOR", region: Region |}
| {| type: "DELETE_REGION", region: Region |}
| {| type: "HEADER_BUTTON_CLICKED", buttonName: string |}
| {| type: "SELECT_TOOL", selectedTool: string |}
| {| type: "SELECT_TOOL", selectedTool: ToolEnum |}
| {| type: "CANCEL" |}
Copy link
Contributor Author

Choose a reason for hiding this comment

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

need to add new actions here...

{| type: "GO_TO_NEXT_IMAGE" |}

{| type: "GO_TO_PREV_IMAGE" |}

9 changes: 9 additions & 0 deletions src/RegionLabel/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,15 @@ export const RegionLabel = ({

return (
<Paper
onKeyPress={e => {
e.stopPropagation()
}}
onKeyDown={e => {
e.stopPropagation()
}}
onKeyUp={e => {
e.stopPropagation()
}}
onClick={() => (!editing ? onOpen(region) : null)}
className={classnames(classes.regionInfo, {
highlighted: region.highlighted
Expand Down
42 changes: 42 additions & 0 deletions src/Shortcuts/ShortcutField.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import React from "react"
import TextField from '@material-ui/core/TextField';
import { makeStyles } from '@material-ui/core/styles';

const useStyles = makeStyles({
shortcutKeyFieldWrapper: {
paddingTop: 8,
display: "inline-flex",
width: '100%'
},
shortcutKeyText: {
lineHeight: 0
},
shortcutTextfield: {
width: "100%",
boxSizing: "border-box",
textAlign: 'center'
}
})

const ShortcutField = ({actionId, actionName, keyName, onChangeShortcut}) =>{
const classes = useStyles()

return(
<div
className={classes.shortcutKeyFieldWrapper}
>
<TextField
variant="outlined"
label={actionName}
className={classes.shortcutTextfield}
value={keyName}
onKeyPress={e => {
onChangeShortcut(actionId, e.key)
e.stopPropagation()
}}
/>
</div>
)
}

export default ShortcutField
99 changes: 99 additions & 0 deletions src/Shortcuts/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
import React, { useState, useReducer, useEffect, useCallback } from "react"
import SidebarBoxContainer from "../SidebarBoxContainer"
import { setIn } from "seamless-immutable"
import ShortcutField from "./ShortcutField"

const defaultShortcuts = {
select: {
name: "Select Region",
key: "Escape"
},
zoom: {
name: "Zoom In/Out",
key: "z"
},
"create-point": {
name: "Create Point"
},
"create-box": {
name: "Add Bounding Box",
key: "b"
},
pan: {
name: "Pan"
},
"create-polygon": {
name: "Create Polygon"
},
"create-pixel": {
name: "Create Pixel"
},
"prev-image": {
//TODO: { type: "GO_TO_PREV_IMAGE" }
name: "Previous Image"
},
"next-image": {
//TODO: { type: "GO_TO_NEXT_IMAGE" }
name: "Next Image"
Copy link
Contributor Author

Choose a reason for hiding this comment

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

each of these should have static actions defined. e.g.

// ... "next-image": { "name": "Next Image", "action": { type: "GO_TO_NEXT_IMAGE" } } // ... 
}
}

export default ({ onShortcutActionDispatched }) => {
const [shortcuts, setShortcuts] = useState({}) // useLocalStorage

useEffect(() => {
const newShortcuts = { ...shortcuts }
for (const actionId of Object.keys(defaultShortcuts)) {
if (!newShortcuts[actionId]) {
newShortcuts[actionId] = defaultShortcuts[actionId]
}
}
setShortcuts(newShortcuts)
}, [])

const onChangeShortcut = (actionId, keyName) => {
setShortcuts(setIn(shortcuts, [actionId, "key"], keyName))
}

useEffect(() => {
const handleKeyPress = e => {
for (const actionId in shortcuts) {
const shortcut = shortcuts[actionId]
if (!shortcut || !shortcut.key) {
continue
}
if (e.key === shortcut.key) {
onShortcutActionDispatched({
type: "SELECT_TOOL",
selectedTool: actionId
})
Copy link
Contributor Author

Choose a reason for hiding this comment

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

change this to...

onShortcutActionDispatched(shortcut.action)
}
}
}

window.addEventListener("keypress", handleKeyPress)

return () => {
window.removeEventListener("keypress", handleKeyPress)
}
}, [shortcuts])

return (
<SidebarBoxContainer title="Shortcuts">
{Object.keys(shortcuts)
.map((actionId, index) => {
if (!shortcuts[actionId]) return null
return (
<ShortcutField
key={actionId}
actionId={actionId}
actionName={shortcuts[actionId].name}
keyName={shortcuts[actionId].key || ""}
onChangeShortcut={onChangeShortcut}
/>
)
})
.filter(Boolean)}
</SidebarBoxContainer>
)
}
Loading