Skip to content

Commit

Permalink
feat(app): allow custom labware dir to be opened and reset to default (
Browse files Browse the repository at this point in the history
…#4918)

Closes #4878, closes #4879
  • Loading branch information
mcous authored Feb 7, 2020
1 parent dbc90cb commit 03c438a
Show file tree
Hide file tree
Showing 38 changed files with 662 additions and 264 deletions.
1 change: 1 addition & 0 deletions __mocks__/electron.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,5 +19,6 @@ module.exports = {

shell: {
moveItemToTrash: jest.fn(),
openItem: jest.fn(),
},
}
25 changes: 15 additions & 10 deletions app-shell/src/config.js
Original file line number Diff line number Diff line change
@@ -1,17 +1,18 @@
// @flow
// app configuration and settings
// TODO(mc, 2020-01-31): this module is high-importance and needs unit tests
import path from 'path'
import { app } from 'electron'
import Store from 'electron-store'
import mergeOptions from 'merge-options'
import { getIn } from '@thi.ng/paths'
import { getIn, exists } from '@thi.ng/paths'
import uuid from 'uuid/v4'
import yargsParser from 'yargs-parser'

import pkg from '../package.json'
import { createLogger } from './log'
import * as Cfg from '@opentrons/app/src/config'

// TODO(mc, 2018-08-08): figure out type exports from app
import type { Config } from '@opentrons/app/src/config/types'
import type { Action, Dispatch } from './types'

Expand Down Expand Up @@ -104,17 +105,21 @@ const log = () => _log || (_log = createLogger('config'))
// initialize and register the config module with dispatches from the UI
export function registerConfig(dispatch: Dispatch) {
return function handleIncomingAction(action: Action) {
if (action.type === 'config:UPDATE') {
const { payload } = action
if (action.type === Cfg.UPDATE || action.type === Cfg.RESET) {
const { path } = action.payload
const value =
action.type === Cfg.UPDATE
? action.payload.value
: getIn(DEFAULTS, path)

log().debug('Handling config:UPDATE', payload)
log().debug('Handling config update', { path, value })

if (getIn(overrides(), payload.path) != null) {
log().debug(`${payload.path} in overrides; not updating`)
if (exists(overrides(), path)) {
log().debug(`${path} in overrides; not updating`)
} else {
log().debug(`Updating "${payload.path}" to ${payload.value}`)
store().set(payload.path, payload.value)
dispatch({ type: 'config:SET', payload })
log().debug(`Updating "${path}" to ${value}`)
store().set(path, value)
dispatch({ type: 'config:SET', payload: { path, value } })
}
}
}
Expand Down
9 changes: 9 additions & 0 deletions app-shell/src/labware/__tests__/dispatch.test.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// @flow

import fse from 'fs-extra'
import electron from 'electron'
import * as Cfg from '../../config'
import * as Dialogs from '../../dialogs'
import * as Defs from '../definitions'
Expand Down Expand Up @@ -374,4 +375,12 @@ describe('labware module dispatches', () => {
expect(dispatch).toHaveBeenCalledWith(expectedAction)
})
})

test('opens custom labware directory on OPEN_CUSTOM_LABWARE_DIRECTORY', () => {
handleAction(CustomLabware.openCustomLabwareDirectory())

return flush().then(() => {
expect(electron.shell.openItem).toHaveBeenCalledWith(labwareDir)
})
})
})
11 changes: 9 additions & 2 deletions app-shell/src/labware/index.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// @flow
import fse from 'fs-extra'
import { app } from 'electron'
import { app, shell } from 'electron'
import { getFullConfig, handleConfigChange } from '../config'
import { showOpenDirectoryDialog, showOpenFileDialog } from '../dialogs'
import * as Definitions from './definitions'
Expand All @@ -14,6 +14,7 @@ import type {
DuplicateLabwareFile,
CustomLabwareListActionSource as ListSource,
} from '@opentrons/app/src/custom-labware/types'

import type { Action, Dispatch } from '../types'

const ensureDir: (dir: string) => Promise<void> = fse.ensureDir
Expand Down Expand Up @@ -86,7 +87,7 @@ const copyLabware = (
}

export function registerLabware(dispatch: Dispatch, mainWindow: {}) {
handleConfigChange('labware.directory', () => {
handleConfigChange(CustomLabware.LABWARE_DIRECTORY_CONFIG_PATH, () => {
fetchAndValidateCustomLabware(dispatch, CustomLabware.CHANGE_DIRECTORY)
})

Expand Down Expand Up @@ -143,6 +144,12 @@ export function registerLabware(dispatch: Dispatch, mainWindow: {}) {

break
}

case CustomLabware.OPEN_CUSTOM_LABWARE_DIRECTORY: {
const dir = getFullConfig().labware.directory
shell.openItem(dir)
break
}
}
}
}
8 changes: 7 additions & 1 deletion app/src/components/AddLabwareCard/AddLabware.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ const TO_GENERATE_NEW_LABWARE_DEFINITIONS =

const LABWARE_CREATOR_HREF = 'https://labware.opentrons.com/create'

export const ADD_LABWARE_NAME = 'add-labware'

export type AddLabwareProps = {|
onAddLabware: () => mixed,
|}
Expand All @@ -26,7 +28,11 @@ export function AddLabware(props: AddLabwareProps) {
return (
<LabeledButton
label={ADD_NEW_LABWARE_DEFINITIONS}
buttonProps={{ onClick: onAddLabware, children: ADD_LABWARE }}
buttonProps={{
name: ADD_LABWARE_NAME,
children: ADD_LABWARE,
onClick: onAddLabware,
}}
>
<p>{ADD_LABWARE_DESCRIPTION}</p>
<p>
Expand Down
10 changes: 5 additions & 5 deletions app/src/components/AddLabwareCard/AddLabwareFailureModal.js
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,9 @@ const renderDetails = file => (
</>
)

export function AddLabwareFailureModal(props: AddLabwareFailureModalProps) {
export function AddLabwareFailureModalTemplate(
props: AddLabwareFailureModalProps
) {
const { file, errorMessage, directory, onCancel, onOverwrite } = props
let buttons = [
{ onClick: onCancel, children: CANCEL, className: styles.button },
Expand Down Expand Up @@ -140,12 +142,10 @@ export function AddLabwareFailureModal(props: AddLabwareFailureModalProps) {
)
}

export function PortaledAddLabwareFailureModal(
props: AddLabwareFailureModalProps
) {
export function AddLabwareFailureModal(props: AddLabwareFailureModalProps) {
return (
<Portal>
<AddLabwareFailureModal {...props} />
<AddLabwareFailureModalTemplate {...props} />
</Portal>
)
}
52 changes: 52 additions & 0 deletions app/src/components/AddLabwareCard/ConfirmResetPathModal.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
// @flow
import * as React from 'react'

import { AlertModal } from '@opentrons/components'
import { Portal } from '../portal'

// TODO(mc, 2019-11-20): i18n
// buttons
const CANCEL = 'cancel'
const RESET_SOURCE = 'reset source'

// headings
const RESET_CUSTOM_LABWARE_SOURCE = 'Reset Custom Labware source directory?'

// copy
const CLICK_RESET_SOURCE_TO_RESET_YOUR_CUSTOM_LABWARE_DIRECTORY =
'Click "Reset Source" to reset your custom labware directory to its default location.'

const LABWARE_FILES_IN_YOUR_CURRENT_DIRECTORY_WILL_NOT_BE_MOVED =
'Labware in your current source directory will not be moved nor deleted.'

// button names
export const CANCEL_NAME = 'cancel'
export const RESET_SOURCE_NAME = 'reset-source'

export type ConfirmResetPathModalProps = {|
onCancel: () => mixed,
onConfirm: () => mixed,
|}

export const ConfirmResetPathModalTemplate = ({
onCancel,
onConfirm,
}: ConfirmResetPathModalProps) => (
<AlertModal
alertOverlay
heading={RESET_CUSTOM_LABWARE_SOURCE}
buttons={[
{ name: CANCEL_NAME, children: CANCEL, onClick: onCancel },
{ name: RESET_SOURCE_NAME, children: RESET_SOURCE, onClick: onConfirm },
]}
>
<p>{CLICK_RESET_SOURCE_TO_RESET_YOUR_CUSTOM_LABWARE_DIRECTORY}</p>
<p>{LABWARE_FILES_IN_YOUR_CURRENT_DIRECTORY_WILL_NOT_BE_MOVED}</p>
</AlertModal>
)

export const ConfirmResetPathModal = (props: ConfirmResetPathModalProps) => (
<Portal>
<ConfirmResetPathModalTemplate {...props} />
</Portal>
)
81 changes: 62 additions & 19 deletions app/src/components/AddLabwareCard/ManagePath.js
Original file line number Diff line number Diff line change
@@ -1,40 +1,83 @@
// @flow

import * as React from 'react'
import React, { useReducer } from 'react'

import { LabeledButton, InputField } from '@opentrons/components'
import { IconCta } from '../IconCta'
import { ConfirmResetPathModal } from './ConfirmResetPathModal'
import styles from './styles.css'

// TODO(mc, 2019-11-18): i18n
const CUSTOM_LABWARE_DEFINITIONS_FOLDER = 'Custom Labware Definitions Folder'
const CHANGE_SOURCE = 'Change Source'
const RESET_SOURCE = 'Reset Source'
const OPEN_SOURCE = 'Open Source'

// button names
export const OPEN_SOURCE_NAME = 'open-source'
export const CHANGE_SOURCE_NAME = 'change-source'
export const RESET_SOURCE_NAME = 'reset-source'

export type ManagePathProps = {|
path: string,
onChangePath: () => mixed,
onResetPath: () => mixed,
onOpenPath: () => mixed,
|}

const handleFocus = (e: SyntheticFocusEvent<HTMLInputElement>) => {
e.currentTarget.select()
}

export function ManagePath(props: ManagePathProps) {
const { path, onChangePath } = props
const handleFocus = (e: SyntheticFocusEvent<HTMLInputElement>) => {
e.currentTarget.select()
const { path, onChangePath, onResetPath, onOpenPath } = props
const [confirmResetIsOpen, toggleConfirmResetIsOpen] = useReducer(
open => !open,
false
)
const handleResetPath = () => {
toggleConfirmResetIsOpen()
onResetPath()
}

return (
<LabeledButton
label={CUSTOM_LABWARE_DEFINITIONS_FOLDER}
buttonProps={{
onClick: onChangePath,
children: CHANGE_SOURCE,
}}
>
<InputField
readOnly
value={path}
type="text"
onFocus={handleFocus}
className={styles.labeled_value}
/>
</LabeledButton>
<>
<LabeledButton
label={CUSTOM_LABWARE_DEFINITIONS_FOLDER}
buttonProps={{
name: OPEN_SOURCE_NAME,
onClick: onOpenPath,
children: OPEN_SOURCE,
}}
>
<InputField
readOnly
value={path}
type="text"
onFocus={handleFocus}
className={styles.labeled_value}
/>
<div className={styles.flex_bar}>
<IconCta
name={CHANGE_SOURCE_NAME}
iconName="folder-open"
text={CHANGE_SOURCE}
onClick={onChangePath}
/>
<IconCta
name={RESET_SOURCE_NAME}
iconName="refresh"
text={RESET_SOURCE}
onClick={toggleConfirmResetIsOpen}
/>
</div>
</LabeledButton>
{confirmResetIsOpen && (
<ConfirmResetPathModal
onCancel={toggleConfirmResetIsOpen}
onConfirm={handleResetPath}
/>
)}
</>
)
}
12 changes: 3 additions & 9 deletions app/src/components/AddLabwareCard/__tests__/AddLabware.test.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
// @flow
import * as React from 'react'
import { shallow, mount } from 'enzyme'
import { mount } from 'enzyme'

import { AddLabware } from '../AddLabware'
import { AddLabware, ADD_LABWARE_NAME } from '../AddLabware'

describe('AddLabware', () => {
const mockOnAddLabware = jest.fn()
Expand All @@ -15,13 +15,7 @@ describe('AddLabware', () => {
const wrapper = mount(<AddLabware onAddLabware={mockOnAddLabware} />)

expect(mockOnAddLabware).toHaveBeenCalledTimes(0)
wrapper.find('button').invoke('onClick')()
wrapper.find(`button[name="${ADD_LABWARE_NAME}"]`).invoke('onClick')()
expect(mockOnAddLabware).toHaveBeenCalledTimes(1)
})

test('component renders', () => {
const wrapper = shallow(<AddLabware onAddLabware={mockOnAddLabware} />)

expect(wrapper).toMatchSnapshot()
})
})
Loading

0 comments on commit 03c438a

Please sign in to comment.