From 5384551d1081b9fc39cd83e0f38df1254705cf58 Mon Sep 17 00:00:00 2001 From: seveibar Date: Thu, 21 May 2020 14:26:41 -0400 Subject: [PATCH 01/10] boilerplate storybook for keyboard shortcut manager --- .../KeyboardShortcutManagerDialog/index.js | 65 +++++++++++++++++++ .../index.story.js | 9 +++ 2 files changed, 74 insertions(+) create mode 100644 src/components/KeyboardShortcutManagerDialog/index.js create mode 100644 src/components/KeyboardShortcutManagerDialog/index.story.js diff --git a/src/components/KeyboardShortcutManagerDialog/index.js b/src/components/KeyboardShortcutManagerDialog/index.js new file mode 100644 index 00000000..b0ab5d11 --- /dev/null +++ b/src/components/KeyboardShortcutManagerDialog/index.js @@ -0,0 +1,65 @@ +import React from "react" +import { makeStyles } from '@material-ui/core/styles'; +import SimpleDialog from "../SimpleDialog" +import Table from '@material-ui/core/Table'; +import TableBody from '@material-ui/core/TableBody'; +import TableCell from '@material-ui/core/TableCell'; +import TableContainer from '@material-ui/core/TableContainer'; +import TableHead from '@material-ui/core/TableHead'; +import TableRow from '@material-ui/core/TableRow'; +import Paper from "@material-ui/core/Paper" + +const useStyles = makeStyles({ + table: { minWidth: 650 } +}) + +function createData(name, calories, fat, carbs, protein) { + return { name, calories, fat, carbs, protein }; +} + +const rows = [ + createData('Frozen yoghurt', 159, 6.0, 24, 4.0), + createData('Ice cream sandwich', 237, 9.0, 37, 4.3), + createData('Eclair', 262, 16.0, 24, 6.0), + createData('Cupcake', 305, 3.7, 67, 4.3), + createData('Gingerbread', 356, 16.0, 49, 3.9), +]; + + +export default ({ open, onClose }) => { + const classes = useStyles() + return ( + + + + + + Dessert (100g serving) + Calories + Fat (g) + Carbs (g) + Protein (g) + + + + {rows.map((row) => ( + + + {row.name} + + {row.calories} + {row.fat} + {row.carbs} + {row.protein} + + ))} + +
+
+
+ ) +} diff --git a/src/components/KeyboardShortcutManagerDialog/index.story.js b/src/components/KeyboardShortcutManagerDialog/index.story.js new file mode 100644 index 00000000..e1e95dc9 --- /dev/null +++ b/src/components/KeyboardShortcutManagerDialog/index.story.js @@ -0,0 +1,9 @@ +import React from "react" + +import { storiesOf } from "@storybook/react" + +import KeyboardShortcutManagerDialog from "./index.js" + +storiesOf("KeyboardShortcutManagerDialog", module).add("Basic", () => { + return +}) From abf6f5e50284b9bd86491e1e1598588da0138722 Mon Sep 17 00:00:00 2001 From: seveibar Date: Sat, 23 May 2020 17:08:25 -0400 Subject: [PATCH 02/10] keyboard shortcut manager dialog component complete --- .../KeyboardShortcutManagerDialog/index.js | 130 +++++++++++------- .../index.story.js | 14 +- 2 files changed, 96 insertions(+), 48 deletions(-) diff --git a/src/components/KeyboardShortcutManagerDialog/index.js b/src/components/KeyboardShortcutManagerDialog/index.js index b0ab5d11..b766c7a0 100644 --- a/src/components/KeyboardShortcutManagerDialog/index.js +++ b/src/components/KeyboardShortcutManagerDialog/index.js @@ -1,65 +1,101 @@ -import React from "react" -import { makeStyles } from '@material-ui/core/styles'; +import React, { useState, useEffect, useMemo } from "react" +import { makeStyles } from "@material-ui/core/styles" import SimpleDialog from "../SimpleDialog" -import Table from '@material-ui/core/Table'; -import TableBody from '@material-ui/core/TableBody'; -import TableCell from '@material-ui/core/TableCell'; -import TableContainer from '@material-ui/core/TableContainer'; -import TableHead from '@material-ui/core/TableHead'; -import TableRow from '@material-ui/core/TableRow'; -import Paper from "@material-ui/core/Paper" +import Table from "@material-ui/core/Table" +import TableBody from "@material-ui/core/TableBody" +import TableCell from "@material-ui/core/TableCell" +import TableContainer from "@material-ui/core/TableContainer" +import TableHead from "@material-ui/core/TableHead" +import TableRow from "@material-ui/core/TableRow" +import Button from "@material-ui/core/Button" +import EditIcon from "@material-ui/icons/Edit" const useStyles = makeStyles({ - table: { minWidth: 650 } + table: {}, }) -function createData(name, calories, fat, carbs, protein) { - return { name, calories, fat, carbs, protein }; -} +const hotkeyIconStyle = { marginLeft: 8, width: 16, height: 16 } + +export default ({ open, onClose, onChangeHotkey, hotkeyList }) => { + const classes = useStyles() + const [selectedHotKey, setSelectedHotKey] = useState() + const [{ modifier, key }, setCurrentKeyCombo] = useState({}) -const rows = [ - createData('Frozen yoghurt', 159, 6.0, 24, 4.0), - createData('Ice cream sandwich', 237, 9.0, 37, 4.3), - createData('Eclair', 262, 16.0, 24, 6.0), - createData('Cupcake', 305, 3.7, 67, 4.3), - createData('Gingerbread', 356, 16.0, 49, 3.9), -]; + useEffect(() => { + if (!selectedHotKey) return + function keyListener(e) { + if (["shift", "ctrl", "cmd", "alt"].includes(e.key.toLowerCase())) { + setCurrentKeyCombo({ modifier: e.key.toLowerCase(), key }) + } + if (e.key.length === 1) { + setCurrentKeyCombo({ modifier, key: e.key.toLowerCase() }) + } + } + window.addEventListener("keydown", keyListener) + return () => { + window.removeEventListener("keydown", keyListener) + } + }, [selectedHotKey, key, modifier, setCurrentKeyCombo]) + useEffect(() => { + if (!key) return + if (modifier) { + onChangeHotkey(selectedHotKey, `${modifier} + ${key}`) + } else { + onChangeHotkey(selectedHotKey, key) + } + setCurrentKeyCombo({}) + setSelectedHotKey(null) + }, [modifier, key, onChangeHotkey, selectedHotKey]) -export default ({ open, onClose }) => { - const classes = useStyles() return ( - - - - - Dessert (100g serving) - Calories - Fat (g) - Carbs (g) - Protein (g) - - - - {rows.map((row) => ( - - - {row.name} - - {row.calories} - {row.fat} - {row.carbs} - {row.protein} + +
+ + + Action + Keyboard Shortcut - ))} - -
-
+ + + {hotkeyList.map((hotkey) => ( + + {hotkey.description} + + + + + ))} + + +
) } diff --git a/src/components/KeyboardShortcutManagerDialog/index.story.js b/src/components/KeyboardShortcutManagerDialog/index.story.js index e1e95dc9..578ef68c 100644 --- a/src/components/KeyboardShortcutManagerDialog/index.story.js +++ b/src/components/KeyboardShortcutManagerDialog/index.story.js @@ -1,9 +1,21 @@ import React from "react" import { storiesOf } from "@storybook/react" +import { action } from "@storybook/addon-actions" import KeyboardShortcutManagerDialog from "./index.js" storiesOf("KeyboardShortcutManagerDialog", module).add("Basic", () => { - return + return ( + + ) }) From caa25501f41be502d9c69ae2daf7a1022a670f9d Mon Sep 17 00:00:00 2001 From: seveibar Date: Sat, 23 May 2020 18:04:32 -0400 Subject: [PATCH 03/10] hotkey storage context --- package-lock.json | 1 - package.json | 1 + src/App.js | 5 ++- src/components/AppConfig/index.js | 2 ++ src/components/HotkeyStorage/index.js | 46 +++++++++++++++++++++++++++ 5 files changed, 53 insertions(+), 2 deletions(-) create mode 100644 src/components/HotkeyStorage/index.js diff --git a/package-lock.json b/package-lock.json index 6cfed631..9272e565 100644 --- a/package-lock.json +++ b/package-lock.json @@ -25897,7 +25897,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/react-hotkeys/-/react-hotkeys-2.0.0.tgz", "integrity": "sha512-3n3OU8vLX/pfcJrR3xJ1zlww6KS1kEJt0Whxc4FiGV+MJrQ1mYSYI3qS/11d2MJDFm8IhOXMTFQirfu6AVOF6Q==", - "dev": true, "requires": { "prop-types": "^15.6.1" } diff --git a/package.json b/package.json index cd7be445..9963582f 100644 --- a/package.json +++ b/package.json @@ -96,6 +96,7 @@ "react-ace": "^7.0.4", "react-data-table-component": "^6.2.2", "react-dropzone": "^10.1.8", + "react-hotkeys": "^2.0.0", "react-icons": "^3.9.0", "react-image-annotate": "^1.0.6", "react-scripts": "^3.4.1", diff --git a/src/App.js b/src/App.js index 661fbbea..6cbe5b3c 100644 --- a/src/App.js +++ b/src/App.js @@ -7,6 +7,7 @@ import useElectron from "./utils/use-electron.js" import { AppConfigProvider } from "./components/AppConfig" import { AuthProvider } from "./utils/auth-handlers/use-auth.js" import { LabelHelpProvider } from "./components/LabelHelpView" +import { HotkeyStorageProvider } from "./components/HotkeyStorage" import "./App.css" export const App = () => { @@ -17,7 +18,9 @@ export const App = () => { - {Boolean(electron) ? : } + + {Boolean(electron) ? : } + diff --git a/src/components/AppConfig/index.js b/src/components/AppConfig/index.js index 8ba50b66..d9fab316 100644 --- a/src/components/AppConfig/index.js +++ b/src/components/AppConfig/index.js @@ -1,5 +1,6 @@ import React, { useMemo, useContext, createContext } from "react" import { useLocalStorage } from "react-use" +import { defaultHotkeys } from "../HotkeyStorage" const configKeyNames = [ "auth.provider", @@ -13,6 +14,7 @@ const configKeyNames = [ "auth.cognito.storage.aws_s3.region", "labelhelp.disabled", "labelhelp.apikey", + ...defaultHotkeys.map(({ id }) => `hotkeys.${id}`), ] // NOTE: appConfig should not allow any nested values diff --git a/src/components/HotkeyStorage/index.js b/src/components/HotkeyStorage/index.js new file mode 100644 index 00000000..377d7b1b --- /dev/null +++ b/src/components/HotkeyStorage/index.js @@ -0,0 +1,46 @@ +import React, { createContext, useContext, useMemo } from "react" +import { useAppConfig } from "../AppConfig" + +export const defaultHotkeys = [ + { + id: "save_and_next_sample", + description: "Save and go to next sample", + hotkey: "shift+n", + }, +] + +export const HotkeyContext = createContext({ + hotkeys: defaultHotkeys, + changeHotkey: (id, newBinding) => null, +}) + +export const HotkeyStorageProvider = ({ children }) => { + const { fromConfig, setInConfig } = useAppConfig() + + const hotkeys = useMemo( + () => + defaultHotkeys.map((item) => { + if (fromConfig(`hotkeys.${item.id}`)) { + return { ...item, hotkey: fromConfig(`hotkeys.${item.id}`) } + } else { + return item + } + }), + [fromConfig] + ) + + const contextValue = useMemo( + () => ({ + hotkeys, + changeHotkey: (id, newBinding) => + setInConfig(`hotkeys.${id}`, newBinding), + }), + [setInConfig, hotkeys] + ) + + return ( + + {children} + + ) +} From d3b20768115b79f03698b16c7d39672e66618126 Mon Sep 17 00:00:00 2001 From: seveibar Date: Sat, 23 May 2020 18:04:41 -0400 Subject: [PATCH 04/10] fix configure interface empty state --- src/components/ConfigureInterface/index.js | 58 +++++++++++++++++++++- 1 file changed, 57 insertions(+), 1 deletion(-) diff --git a/src/components/ConfigureInterface/index.js b/src/components/ConfigureInterface/index.js index f1548ec3..06240f8e 100644 --- a/src/components/ConfigureInterface/index.js +++ b/src/components/ConfigureInterface/index.js @@ -22,7 +22,39 @@ import useEventCallback from "use-event-callback" const noop = () => {} -const Container = styled("div")({ padding: 24 }) +const Container = styled("div")({ + padding: 24, + "&.emptyState": { + textAlign: "center", + backgroundColor: colors.blue[800], + minHeight: "70vh", + padding: 64, + "& .bigText": { + textAlign: "left", + fontSize: 48, + color: "#fff", + fontWeight: "bold", + marginBottom: 48, + }, + }, +}) + +const BigButton = styled(Button)({ + padding: 10, + width: 200, + height: 150, + boxShadow: "0px 3px 5px rgba(0,0,0,0.3)", + margin: 12, + backgroundColor: "#fff", + "& .bigIcon": { + marginTop: 16, + width: 64, + height: 64, + }, + "&:hover": { + backgroundColor: "#fff", + }, +}) const NoOptions = styled("div")({ fontSize: 18, @@ -114,6 +146,30 @@ export const ConfigureInterface = ({ clearTimeout(timeout) } }, [previewChangedTime]) + + if (!iface.type || iface.type === "empty") { + return ( + +
Choose an Interface:
+ {templates + .filter((t) => t.name !== "Empty") + .map((template) => ( + onChange(template.dataset.interface)} + > +
+
{template.name}
+
+ +
+
+
+ ))} +
+ ) + } + return ( Interface Type From 887a6a270d6dd3538e6c8e50a4e8f7b722fcc76a Mon Sep 17 00:00:00 2001 From: seveibar Date: Sat, 23 May 2020 18:16:11 -0400 Subject: [PATCH 05/10] integrate dialog for managing hotkeys --- src/components/AdvancedOptionsView/index.js | 22 ++++++++++++++++++++- src/components/HotkeyStorage/index.js | 2 ++ 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/src/components/AdvancedOptionsView/index.js b/src/components/AdvancedOptionsView/index.js index 4aab9a4e..10265245 100644 --- a/src/components/AdvancedOptionsView/index.js +++ b/src/components/AdvancedOptionsView/index.js @@ -1,10 +1,12 @@ -import React from "react" +import React, { useState } from "react" import Box from "@material-ui/core/Box" import MuiButton from "@material-ui/core/Button" import { useUpdate } from "react-use" import { styled } from "@material-ui/core/styles" import usePosthog from "../../utils/use-posthog" import { useAppConfig } from "../AppConfig" +import { useHotkeyStorage } from "../HotkeyStorage" +import KeyboardShortcutManagerDialog from "../KeyboardShortcutManagerDialog" const Button = styled(MuiButton)({ margin: 8, @@ -14,6 +16,8 @@ export const AdvancedOptionsView = ({ onClickEditJSON, onClearLabelData }) => { const forceUpdate = useUpdate() const posthog = usePosthog() const { fromConfig, setInConfig } = useAppConfig() + const { hotkeys, changeHotkey } = useHotkeyStorage() + const [hotkeyDialogOpen, setHotkeyDialogOpen] = useState(false) return ( @@ -89,6 +93,22 @@ export const AdvancedOptionsView = ({ onClickEditJSON, onClearLabelData }) => { Label Help API Key )} + + setHotkeyDialogOpen(false)} + onChangeHotkey={(hotkey, newBinding) => + changeHotkey(hotkey.id, newBinding) + } + /> ) } diff --git a/src/components/HotkeyStorage/index.js b/src/components/HotkeyStorage/index.js index 377d7b1b..dc45ed99 100644 --- a/src/components/HotkeyStorage/index.js +++ b/src/components/HotkeyStorage/index.js @@ -14,6 +14,8 @@ export const HotkeyContext = createContext({ changeHotkey: (id, newBinding) => null, }) +export const useHotkeyStorage = () => useContext(HotkeyContext) + export const HotkeyStorageProvider = ({ children }) => { const { fromConfig, setInConfig } = useAppConfig() From 5b5d64a5f325787bba919e5eb624dcb7e5eadb94 Mon Sep 17 00:00:00 2001 From: seveibar Date: Sat, 23 May 2020 19:58:01 -0400 Subject: [PATCH 06/10] placeholder for #175, pass hotkeys with react-hotkeys --- src/components/HotkeyStorage/index.js | 25 +++++++++++++++---- .../KeyboardShortcutManagerDialog/index.js | 11 +++++++- 2 files changed, 30 insertions(+), 6 deletions(-) diff --git a/src/components/HotkeyStorage/index.js b/src/components/HotkeyStorage/index.js index dc45ed99..bcff9eca 100644 --- a/src/components/HotkeyStorage/index.js +++ b/src/components/HotkeyStorage/index.js @@ -1,11 +1,20 @@ import React, { createContext, useContext, useMemo } from "react" import { useAppConfig } from "../AppConfig" +import { HotKeys } from "react-hotkeys" export const defaultHotkeys = [ { - id: "save_and_next_sample", - description: "Save and go to next sample", - hotkey: "shift+n", + id: "switch_to_label", + description: "Go to Labels Tab", + binding: "shift+l", + }, + { + id: "switch_to_setup", + description: "Go to Setup Tab", + }, + { + id: "switch_to_samples", + description: "Go to Samples Tab", }, ] @@ -23,7 +32,7 @@ export const HotkeyStorageProvider = ({ children }) => { () => defaultHotkeys.map((item) => { if (fromConfig(`hotkeys.${item.id}`)) { - return { ...item, hotkey: fromConfig(`hotkeys.${item.id}`) } + return { ...item, binding: fromConfig(`hotkeys.${item.id}`) } } else { return item } @@ -40,9 +49,15 @@ export const HotkeyStorageProvider = ({ children }) => { [setInConfig, hotkeys] ) + const keyMap = useMemo(() => { + const keyMap = {} + for (const { id, binding } of hotkeys) keyMap[id] = binding + return keyMap + }, [hotkeys]) + return ( - {children} + {children} ) } diff --git a/src/components/KeyboardShortcutManagerDialog/index.js b/src/components/KeyboardShortcutManagerDialog/index.js index b766c7a0..7f11ca59 100644 --- a/src/components/KeyboardShortcutManagerDialog/index.js +++ b/src/components/KeyboardShortcutManagerDialog/index.js @@ -51,6 +51,15 @@ export default ({ open, onClose, onChangeHotkey, hotkeyList }) => { return ( { + // // TODO + // } + // } + // ]} open={open} onClose={onClose} > @@ -82,7 +91,7 @@ export default ({ open, onClose, onChangeHotkey, hotkeyList }) => { ? modifier || key ? `<${modifier ? modifier + "+" : ""}${key || "???"}>` : "" - : hotkey.hotkey} + : hotkey.binding} {selectedHotKey === hotkey ? ( From f90812c17ffa1611541175bd7dd9366b646b365c Mon Sep 17 00:00:00 2001 From: seveibar Date: Sat, 23 May 2020 20:13:05 -0400 Subject: [PATCH 07/10] add react warnings to ci, fix existingwarnings --- .github/workflows/test.yml | 10 +- package.json | 4 +- .../AddAuthFromTemplateDialog/index.js | 2 +- src/components/DatasetEditor/index.js | 276 +++++++++--------- src/components/InterfacePage/index.js | 2 - .../KeyboardShortcutManagerDialog/index.js | 2 +- .../LabelHelpView/LabelHelpProvider.js | 1 + src/components/LabelHelpView/api-key-entry.js | 2 +- src/components/LabelHelpView/index.js | 4 +- .../label-help-dialog-content.js | 7 +- .../LabelHelpView/use-label-help.js | 7 +- src/components/LabelView/index.js | 2 - src/components/LocalStorageApp/index.js | 2 +- src/components/LoginDrawer/CompleteSignUp.js | 4 +- src/utils/auth-handlers/cognito-handler.js | 1 - src/utils/auth-handlers/use-auth.js | 6 +- src/utils/compute-dataset-variable.js | 25 +- src/utils/file-handlers/use-local-storage.js | 4 +- 18 files changed, 185 insertions(+), 176 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 3f243c9f..996b1a3b 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -16,12 +16,10 @@ jobs: run: npx prettier --check "src/**/*.js" - name: Run Lint Test run: | - rm package.json - rm package-lock.json - npm init -y - npm install react-scripts - echo '{"extends": "react-app"}' > .eslintrc - npx eslint src + # TODO this takes 1-2 mins to install, run eslint w/o installing + # everything + npm install + npm run test:list cypress-run: needs: iscodeclean runs-on: ubuntu-16.04 diff --git a/package.json b/package.json index 9963582f..a52e095b 100644 --- a/package.json +++ b/package.json @@ -12,8 +12,8 @@ "build:babel": "cross-env NODE_ENV=production babel ./src --out-dir=./lib && cp ./package.json ./lib/package.json && node ./lib/lib/fix-deps.js", "build:vanilla": "parcel build -d ./lib -o vanilla.js ./src/vanilla/index.js", "build:vanilla:dev": "parcel ./src/vanilla/index.js", - "build:web": "CI=false react-scripts build", - "build:desktop": "cross-env CI=false REACT_APP_DESKTOP=true PUBLIC_URL=./ react-scripts build && electron-builder build && cp ./desktop/entitlements.mac.plist ./build/entitlements.mac.plist", + "build:web": "react-scripts build", + "build:desktop": "cross-env REACT_APP_DESKTOP=true PUBLIC_URL=./ react-scripts build && electron-builder build && cp ./desktop/entitlements.mac.plist ./build/entitlements.mac.plist", "start:desktop:dev": "USE_DEV_SERVER=yes electron ./desktop", "start:desktop": "electron ./desktop", "release:lib": "npm run build && cd lib && npm publish", diff --git a/src/components/AddAuthFromTemplateDialog/index.js b/src/components/AddAuthFromTemplateDialog/index.js index adcc7310..8aa0694f 100644 --- a/src/components/AddAuthFromTemplateDialog/index.js +++ b/src/components/AddAuthFromTemplateDialog/index.js @@ -9,7 +9,7 @@ import isEmpty from "lodash/isEmpty" import Survey from "material-survey/components/Survey" import ErrorToasts from "../ErrorToasts" import useErrors from "../../utils/use-errors.js" -import Amplify, { Auth as AWSAmplifyAuth } from "aws-amplify" +import Amplify from "aws-amplify" import { useAppConfig } from "../AppConfig" import * as colors from "@material-ui/core/colors" diff --git a/src/components/DatasetEditor/index.js b/src/components/DatasetEditor/index.js index 51296c4f..7b7024f5 100644 --- a/src/components/DatasetEditor/index.js +++ b/src/components/DatasetEditor/index.js @@ -1,6 +1,6 @@ // @flow -import React, { useState, useEffect } from "react" +import React, { useState, useEffect, useMemo } from "react" import { makeStyles } from "@material-ui/core/styles" import Header from "../Header" @@ -19,6 +19,7 @@ import usePosthog from "../../utils/use-posthog" import classnames from "classnames" import LabelView from "../LabelView" import useIsLabelOnlyMode from "../../utils/use-is-label-only-mode" +import { HotKeys } from "react-hotkeys" import "brace/mode/javascript" import "brace/theme/github" @@ -111,140 +112,155 @@ export default ({ const onChangeTab = useEventCallback((tab) => changeMode(tab.toLowerCase())) + const shortcutHandlers = useMemo( + () => ({ + switch_to_label: () => changeMode("label"), + switch_to_setup: () => changeMode("setup"), + switch_to_samples: () => changeMode("samples"), + }), + [changeMode] + ) + return ( -
-
+
+
+ ) : ( + { + onChangeFile(setIn(file, ["fileName"], newName)) + setValueDisplay(newName) + }} + value={valueDisplay || ""} + /> + ) + } + onChangeTab={onChangeTab} + currentTab={mode} + tabs={headerTabs} + /> +
+ {mode === "json" && ( + changeJSONText(t)} /> - ) : ( - { - onChangeFile(setIn(file, ["fileName"], newName)) - setValueDisplay(newName) + )} + {mode === "setup" && ( + changeMode("json")} + dataset={dataset} + onClearLabelData={() => { + onChangeDataset( + setIn( + dataset, + ["samples"], + dataset.samples.map((s) => without(s, "annotation")) + ) + ) + }} + onChange={(iface) => { + if ( + iface.type !== dataset.interface.type && + dataset.interface.type !== "empty" && + dataset.samples.map((s) => s.annotation).some(Boolean) + ) { + addToast( + "Changing label types can cause label data issues. You must clear all label data first.", + "error" + ) + return + } + onChangeDataset({ + ...dataset, + interface: iface, + }) }} - value={valueDisplay || ""} /> - ) - } - onChangeTab={onChangeTab} - currentTab={mode} - tabs={headerTabs} - /> -
- {mode === "json" && ( - changeJSONText(t)} - /> - )} - {mode === "setup" && ( - changeMode("json")} - dataset={dataset} - onClearLabelData={() => { - onChangeDataset( - setIn( - dataset, - ["samples"], - dataset.samples.map((s) => without(s, "annotation")) - ) + )} + {mode === "samples" && ( + { + setSingleSampleDataset({ + ...dataset, + samples: [dataset.samples[sampleIndex]], + sampleIndex, + annotationStartTime: Date.now(), + }) + posthog.capture("open_sample", { + interface_type: dataset.interface.type, + }) + changeMode("label") + }} + openSampleInputEditor={(sampleIndex) => { + changeSampleInputEditor({ open: true, sampleIndex }) + }} + deleteSample={(sampleIndex) => { + const newSamples = [...dataset.samples] + newSamples.splice(sampleIndex, 1) + onChangeDataset({ + ...dataset, + samples: newSamples, + }) + }} + onChangeFile={(file) => { + onChangeFile(file) + setValueDisplay(file.fileName) + }} + onChangeDataset={onChangeDataset} + authConfig={authConfig} + user={user} + /> + )} + {mode === "label" && ( + changeMode("setup")} + /> + )} +
+ { + changeSampleInputEditor({ open: false }) + }} + onChange={(newInput) => { + onChangeDataset( + setIn( + dataset, + ["samples", sampleInputEditor.sampleIndex], + newInput ) - }} - onChange={(iface) => { - if ( - iface.type !== dataset.interface.type && - dataset.interface.type !== "empty" && - dataset.samples.map((s) => s.annotation).some(Boolean) - ) { - addToast( - "Changing label types can cause label data issues. You must clear all label data first.", - "error" - ) - return - } - onChangeDataset({ - ...dataset, - interface: iface, - }) - }} - /> - )} - {mode === "samples" && ( - { - setSingleSampleDataset({ - ...dataset, - samples: [dataset.samples[sampleIndex]], - sampleIndex, - annotationStartTime: Date.now(), - }) - posthog.capture("open_sample", { - interface_type: dataset.interface.type, - }) - changeMode("label") - }} - openSampleInputEditor={(sampleIndex) => { - changeSampleInputEditor({ open: true, sampleIndex }) - }} - deleteSample={(sampleIndex) => { - const newSamples = [...dataset.samples] - newSamples.splice(sampleIndex, 1) - onChangeDataset({ - ...dataset, - samples: newSamples, - }) - }} - onChangeFile={(file) => { - onChangeFile(file) - setValueDisplay(file.fileName) - }} - onChangeDataset={onChangeDataset} - authConfig={authConfig} - user={user} - /> - )} - {mode === "label" && ( - changeMode("setup")} - /> - )} + ) + }} + />
- { - changeSampleInputEditor({ open: false }) - }} - onChange={(newInput) => { - onChangeDataset( - setIn(dataset, ["samples", sampleInputEditor.sampleIndex], newInput) - ) - }} - /> -
+ ) } diff --git a/src/components/InterfacePage/index.js b/src/components/InterfacePage/index.js index f263dcec..a3ef1d64 100644 --- a/src/components/InterfacePage/index.js +++ b/src/components/InterfacePage/index.js @@ -3,8 +3,6 @@ import React from "react" import ConfigureInterface, { Heading } from "../ConfigureInterface" import PaperContainer from "../PaperContainer" -import MuiButton from "@material-ui/core/Button" -import { styled } from "@material-ui/core/styles" import AdvancedOptionsView from "../AdvancedOptionsView" export default ({ dataset, onChange, onClickEditJSON, onClearLabelData }) => { diff --git a/src/components/KeyboardShortcutManagerDialog/index.js b/src/components/KeyboardShortcutManagerDialog/index.js index 7f11ca59..04bb2ceb 100644 --- a/src/components/KeyboardShortcutManagerDialog/index.js +++ b/src/components/KeyboardShortcutManagerDialog/index.js @@ -1,4 +1,4 @@ -import React, { useState, useEffect, useMemo } from "react" +import React, { useState, useEffect } from "react" import { makeStyles } from "@material-ui/core/styles" import SimpleDialog from "../SimpleDialog" import Table from "@material-ui/core/Table" diff --git a/src/components/LabelHelpView/LabelHelpProvider.js b/src/components/LabelHelpView/LabelHelpProvider.js index b1545d69..e6105c01 100644 --- a/src/components/LabelHelpView/LabelHelpProvider.js +++ b/src/components/LabelHelpView/LabelHelpProvider.js @@ -57,6 +57,7 @@ export const LabelHelpProvider = ({ children }) => { loadingMyTeam, myCredits, fromConfig, + setInConfig, ] ) return ( diff --git a/src/components/LabelHelpView/api-key-entry.js b/src/components/LabelHelpView/api-key-entry.js index a68c795f..c507142c 100644 --- a/src/components/LabelHelpView/api-key-entry.js +++ b/src/components/LabelHelpView/api-key-entry.js @@ -22,7 +22,7 @@ const ErrorText = styled("div")({ }) export default () => { - const { fromConfig, setInConfig } = useAppConfig() + const { setInConfig } = useAppConfig() const [verifying, setVerifying] = useState(false) const [textFieldValue, setTextFieldValue] = useState("") const [error, setError] = useState("") diff --git a/src/components/LabelHelpView/index.js b/src/components/LabelHelpView/index.js index bf825edb..5f8ede38 100644 --- a/src/components/LabelHelpView/index.js +++ b/src/components/LabelHelpView/index.js @@ -1,7 +1,5 @@ -import React, { createContext, useContext, useMemo, useState } from "react" +import React from "react" import { styled } from "@material-ui/core/styles" -import Box from "@material-ui/core/Box" -import Grid from "@material-ui/core/Grid" import APIKeyEntry from "./api-key-entry.js" import PaperContainer from "../PaperContainer" import LabelHelpDialogContent from "./label-help-dialog-content" diff --git a/src/components/LabelHelpView/label-help-dialog-content.js b/src/components/LabelHelpView/label-help-dialog-content.js index 35d1a53e..66e9dc3f 100644 --- a/src/components/LabelHelpView/label-help-dialog-content.js +++ b/src/components/LabelHelpView/label-help-dialog-content.js @@ -2,7 +2,6 @@ import React, { useState, useEffect } from "react" import { styled } from "@material-ui/core/styles" -import Grid from "@material-ui/core/Grid" import Box from "@material-ui/core/Box" import Button from "@material-ui/core/Button" import Stepper from "@material-ui/core/Stepper" @@ -11,10 +10,8 @@ import StepLabel from "@material-ui/core/StepLabel" import Table from "@material-ui/core/Table" import TableBody from "@material-ui/core/TableBody" import TableCell from "@material-ui/core/TableCell" -import TableContainer from "@material-ui/core/TableContainer" import TableHead from "@material-ui/core/TableHead" import TableRow from "@material-ui/core/TableRow" -import Divider from "@material-ui/core/Divider" import { useAppConfig } from "../AppConfig" import { useLabelHelp } from "./" import * as colors from "@material-ui/core/colors" @@ -64,7 +61,7 @@ export default () => { if (myCredits === null || myCredits === undefined) { loadMyCredits(dataset) } - }, [myCredits]) + }, [myCredits, dataset, loadMyCredits]) useEffect(() => { if (!collabUrl) return @@ -83,7 +80,7 @@ export default () => { }) } loadJob() - }, [collabUrl]) + }, [collabUrl, dataset.labelHelp, fromConfig]) if (!labelHelpEnabled) return ( diff --git a/src/components/LabelHelpView/use-label-help.js b/src/components/LabelHelpView/use-label-help.js index d596a4f6..0fc32c2c 100644 --- a/src/components/LabelHelpView/use-label-help.js +++ b/src/components/LabelHelpView/use-label-help.js @@ -1,15 +1,14 @@ -import { useContext, useMemo, useState, useEffect } from "react" +import { useContext, useEffect } from "react" import useIsLabelOnlyMode from "../../utils/use-is-label-only-mode" import { useActiveDataset } from "../FileContext" import { useAppConfig } from "../AppConfig" import { LabelHelpContext } from "./LabelHelpProvider.js" -import { setIn } from "seamless-immutable" import computeDatasetVariable from "../../utils/compute-dataset-variable" export const useLabelHelp = () => { const isLabelOnlyMode = useIsLabelOnlyMode() - const { dataset, setDataset } = useActiveDataset() + const { dataset } = useActiveDataset() const { pricingConfig, myCredits, @@ -24,7 +23,7 @@ export const useLabelHelp = () => { useEffect(() => { if (labelHelpDisabled || isLabelOnlyMode) return if (!pricingConfig) loadPricingConfig() - }, [pricingConfig, isLabelOnlyMode, labelHelpDisabled]) + }, [pricingConfig, isLabelOnlyMode, labelHelpDisabled, loadPricingConfig]) if (labelHelpDisabled) return { labelHelpEnabled: false, labelHelpError: "Disabled in config" } diff --git a/src/components/LabelView/index.js b/src/components/LabelView/index.js index de005bd2..3568395b 100644 --- a/src/components/LabelView/index.js +++ b/src/components/LabelView/index.js @@ -16,7 +16,6 @@ import DataUsageIcon from "@material-ui/icons/DataUsage" import LabelHelpView, { useLabelHelp } from "../LabelHelpView" import ActiveLearningView from "../ActiveLearningView" import useIsLabelOnlyMode from "../../utils/use-is-label-only-mode" -import { useAppConfig } from "../AppConfig" const OverviewContainer = styled("div")({ padding: 16, @@ -38,7 +37,6 @@ export default ({ }) => { const [currentTab, setTab] = useState("label") const posthog = usePosthog() - const { fromConfig, setInConfig } = useAppConfig() const { labelHelpEnabled } = useLabelHelp() const labelOnlyMode = useIsLabelOnlyMode() let percentComplete = 0 diff --git a/src/components/LocalStorageApp/index.js b/src/components/LocalStorageApp/index.js index a7bb2796..5de87152 100644 --- a/src/components/LocalStorageApp/index.js +++ b/src/components/LocalStorageApp/index.js @@ -1,5 +1,5 @@ // @flow -import React, { useState, useRef, useEffect, useCallback } from "react" +import React, { useState, useCallback } from "react" import { HeaderContext } from "../Header" import StartingPage from "../StartingPage" import DatasetEditor from "../DatasetEditor" diff --git a/src/components/LoginDrawer/CompleteSignUp.js b/src/components/LoginDrawer/CompleteSignUp.js index 85201627..9d82ff8f 100644 --- a/src/components/LoginDrawer/CompleteSignUp.js +++ b/src/components/LoginDrawer/CompleteSignUp.js @@ -1,7 +1,7 @@ import React, { Fragment, useState } from "react" import { Typography, TextField, Button } from "@material-ui/core" import { makeStyles } from "@material-ui/core/styles" -import Amplify, { Auth } from "aws-amplify" +import { Auth } from "aws-amplify" import { useAuth } from "../../utils/auth-handlers/use-auth.js" const useStyles = makeStyles((theme) => ({ @@ -15,7 +15,7 @@ const useStyles = makeStyles((theme) => ({ })) export default ({ requiredAttributes, onUserChange, onClose }) => { - const { user, auth } = useAuth() + const { user } = useAuth() const requiredAttributesDict = {} const requiredAttributesErrorDict = {} requiredAttributes.forEach((requiredAttribute) => { diff --git a/src/utils/auth-handlers/cognito-handler.js b/src/utils/auth-handlers/cognito-handler.js index 4e65a776..920a53fe 100644 --- a/src/utils/auth-handlers/cognito-handler.js +++ b/src/utils/auth-handlers/cognito-handler.js @@ -1,4 +1,3 @@ -import isEmpty from "lodash/isEmpty" import Amplify, { Auth } from "aws-amplify" class CognitoHandler { diff --git a/src/utils/auth-handlers/use-auth.js b/src/utils/auth-handlers/use-auth.js index 212932bf..543af6d5 100644 --- a/src/utils/auth-handlers/use-auth.js +++ b/src/utils/auth-handlers/use-auth.js @@ -9,7 +9,7 @@ import { useAppConfig } from "../../components/AppConfig" import CognitoHandler from "./cognito-handler.js" import { useUpdate } from "react-use" -const authProviders = ["cognito"] +export const authProviders = ["cognito"] const AuthContext = createContext({ authProvider: "none" }) @@ -37,7 +37,7 @@ export const AuthProvider = ({ children }) => { return () => { clearInterval(interval) } - }, [handler]) + }, [handler, forceUpdate]) const contextValue = useMemo( () => ({ @@ -48,7 +48,7 @@ export const AuthProvider = ({ children }) => { logout: handler.logout, login: handler.login, }), - [handler, handler.hasChanged, handler.isLoggedIn] + [handler] ) return ( diff --git a/src/utils/compute-dataset-variable.js b/src/utils/compute-dataset-variable.js index 66c7375b..fa345792 100644 --- a/src/utils/compute-dataset-variable.js +++ b/src/utils/compute-dataset-variable.js @@ -1,15 +1,15 @@ export default (dataset, varName) => { let fields = (dataset.interface || {}).fields || [] - const totalLabels = fields.reduce( - (acc, f) => - f.interface.type !== "data_entry" - ? acc - : acc + (f.interface.surveyjs.questions[0].choices || []).length, - 0 - ) - const totalBoundingBoxes = fields.filter( - (f) => f.interface.type === "image_segmentation" - ).length + // const totalLabels = fields.reduce( + // (acc, f) => + // f.interface.type !== "data_entry" + // ? acc + // : acc + (f.interface.surveyjs.questions[0].choices || []).length, + // 0 + // ) + // const totalBoundingBoxes = fields.filter( + // (f) => f.interface.type === "image_segmentation" + // ).length switch (varName) { case "sample_count": @@ -28,6 +28,11 @@ export default (dataset, varName) => { f.interface.type === "data_entry" && f.interface.surveyjs.questions[0].type === "text" ).length + } else { + throw new Error( + "Couldn't compute text_field_count for interface: " + + dataset.interface.type + ) } case "number_of_classifications": return dataset.interface.labels.length diff --git a/src/utils/file-handlers/use-local-storage.js b/src/utils/file-handlers/use-local-storage.js index c7de3db9..f859ab48 100644 --- a/src/utils/file-handlers/use-local-storage.js +++ b/src/utils/file-handlers/use-local-storage.js @@ -1,7 +1,7 @@ // @flow -import { useRef, useEffect } from "react" +import { useEffect } from "react" import { useLocalStorage } from "react-use" -import isEmpty from "lodash/isEmpty" + export default (file, changeFile) => { let [recentItems, changeRecentItems] = useLocalStorage("recentItems", []) From d01747806ea25f4c297615c15b31703db8ed90d5 Mon Sep 17 00:00:00 2001 From: seveibar Date: Sat, 23 May 2020 20:19:03 -0400 Subject: [PATCH 08/10] fix typo --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 996b1a3b..a22407c3 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -19,7 +19,7 @@ jobs: # TODO this takes 1-2 mins to install, run eslint w/o installing # everything npm install - npm run test:list + npm run test:lint cypress-run: needs: iscodeclean runs-on: ubuntu-16.04 From 0c4f01cfe647b2a85b59ee3cb09c4ac9182f6e8b Mon Sep 17 00:00:00 2001 From: seveibar Date: Sat, 23 May 2020 20:26:15 -0400 Subject: [PATCH 09/10] placeholder bindings for image annotation --- src/components/HotkeyStorage/index.js | 42 +++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/src/components/HotkeyStorage/index.js b/src/components/HotkeyStorage/index.js index bcff9eca..6652a332 100644 --- a/src/components/HotkeyStorage/index.js +++ b/src/components/HotkeyStorage/index.js @@ -16,6 +16,48 @@ export const defaultHotkeys = [ id: "switch_to_samples", description: "Go to Samples Tab", }, + { + id: "select_tool", + description: "Switch to the Select Tool", + binding: "escape", + }, + { + id: "zoom_tool", + description: "Select the Zoom Tool", + binding: "z", + }, + { + id: "create_point", + description: "Create a point", + }, + { + id: "pan_tool", + description: "Select the Pan Tool", + }, + { + id: "create_polygon", + description: "Create a Polygon", + }, + { + id: "create_pixel", + description: "Create a Pixel Mask", + }, + { + id: "save_and_previous_sample", + description: "Save and go to previous sample", + }, + { + id: "save_and_next_sample", + description: "Save and go to next sample", + }, + { + id: "save_and_exit_sample", + description: "Save and exit current sample", + }, + { + id: "exit_sample", + description: "Exit sample without saving", + }, ] export const HotkeyContext = createContext({ From 6a7d874c744723335d4d5033439f4af73b465c30 Mon Sep 17 00:00:00 2001 From: seveibar Date: Sat, 23 May 2020 21:16:07 -0400 Subject: [PATCH 10/10] placeholder cypress test, default tab switch hotkeys shift+numeric --- .../integration/keyboard-shortcuts.spec.js | 22 +++++++++++++++++++ src/components/HotkeyStorage/index.js | 4 +++- 2 files changed, 25 insertions(+), 1 deletion(-) create mode 100644 cypress/integration/keyboard-shortcuts.spec.js diff --git a/cypress/integration/keyboard-shortcuts.spec.js b/cypress/integration/keyboard-shortcuts.spec.js new file mode 100644 index 00000000..14ae4866 --- /dev/null +++ b/cypress/integration/keyboard-shortcuts.spec.js @@ -0,0 +1,22 @@ +describe("Test default keyboard shortcuts", () => { + it.skip("should be able to navigate to label tab with default shortcut", () => { + cy.visit("http://localhost:6001") + + cy.contains("New File").click() + + cy.wait(500) + + // TODO this doesn't trigger hot keys for some reason, I'm not sure how + // good the support for testing keyboard shortcuts in cypress is + cy.get("body").trigger("keydown", { + keyCode: 51, + release: false, + location: 0, + which: 51, + key: "3", + code: "Digit3", + }) + + cy.contains("Percent Complete") + }) +}) diff --git a/src/components/HotkeyStorage/index.js b/src/components/HotkeyStorage/index.js index 6652a332..6bb3a9cf 100644 --- a/src/components/HotkeyStorage/index.js +++ b/src/components/HotkeyStorage/index.js @@ -6,15 +6,17 @@ export const defaultHotkeys = [ { id: "switch_to_label", description: "Go to Labels Tab", - binding: "shift+l", + binding: "shift+3", }, { id: "switch_to_setup", description: "Go to Setup Tab", + binding: "shift+1", }, { id: "switch_to_samples", description: "Go to Samples Tab", + binding: "shift+2", }, { id: "select_tool",