Skip to content

Commit

Permalink
feat: add remove dataset modal
Browse files Browse the repository at this point in the history
  • Loading branch information
chriswhong committed Sep 13, 2019
1 parent c289c46 commit bdab991
Show file tree
Hide file tree
Showing 11 changed files with 206 additions and 28 deletions.
2 changes: 1 addition & 1 deletion app/actions/mappingFuncs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ export function mapDatasetSummary (data: any[]): DatasetSummary[] {
peername: ref.peername,
name: ref.name,
path: ref.path,
isLinked: !!ref.fsiPath,
fsipath: ref.fsiPath,
published: ref.published
}))
}
Expand Down
20 changes: 19 additions & 1 deletion app/components/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import AppError from './AppError'
import AppLoading from './AppLoading'
import AddDataset from './modals/AddDataset'
import LinkDataset from './modals/LinkDataset'
import RemoveDataset from './modals/RemoveDataset'
import CreateDataset from './modals/CreateDataset'
import RoutesContainer from '../containers/RoutesContainer'

Expand Down Expand Up @@ -48,6 +49,8 @@ export interface AppProps {
setApiConnection: (status: number) => Action
pingApi: () => Promise<ApiAction>
setModal: (modal: Modal) => Action
removeDataset: (peername: string, name: string, dir: string) => Promise<ApiAction>

}

interface AppState {
Expand Down Expand Up @@ -109,7 +112,7 @@ class App extends React.Component<AppProps, AppState> {

private renderModal (): JSX.Element | null {
const { modal, setModal, workingDataset } = this.props
const { peername, name } = workingDataset
const { peername, name, linkpath } = workingDataset
const Modal = modal

if (!Modal) return null
Expand Down Expand Up @@ -153,6 +156,21 @@ class App extends React.Component<AppProps, AppState> {
onDismissed={async () => setModal(NoModal)}
/>
</CSSTransition>
<CSSTransition
in={ModalType.RemoveDataset === Modal.type}
classNames='fade'
component='div'
timeout={300}
unmountOnExit
>
<RemoveDataset
peername={peername}
name={name}
linkpath={linkpath}
onSubmit={this.props.removeDataset}
onDismissed={async () => setModal(NoModal)}
/>
</CSSTransition>
</div>
)
}
Expand Down
74 changes: 52 additions & 22 deletions app/components/DatasetList.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import * as React from 'react'
import { Action, AnyAction } from 'redux'
import classNames from 'classnames'
import ContextMenuArea from 'react-electron-contextmenu'
import { shell, MenuItemConstructorOptions } from 'electron'

import { MyDatasets, WorkingDataset } from '../models/store'
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
Expand Down Expand Up @@ -57,9 +59,13 @@ export default class DatasetList extends React.Component<DatasetListProps> {
}

render () {
const { workingDataset, setModal } = this.props
const { setWorkingDataset } = this.props
const { filter, value: datasets } = this.props.myDatasets
const {
workingDataset,
setModal,
setWorkingDataset,
myDatasets
} = this.props
const { filter, value: datasets } = myDatasets

const filteredDatasets = datasets.filter(({ peername, name, title }) => {
// if there's a non-empty filter string, only show matches on peername, name, and title
Expand All @@ -76,26 +82,50 @@ export default class DatasetList extends React.Component<DatasetListProps> {
})

const listContent = filteredDatasets.length > 0
? filteredDatasets.map(({ peername, name, title, isLinked, published }) => (
<div
key={`${peername}/${name}`}
className={classNames('sidebar-list-item', 'sidebar-list-item-text', {
'selected': (peername === workingDataset.peername) && (name === workingDataset.name)
})}
onClick={() => setWorkingDataset(peername, name, isLinked, published)}
>
<div className='text-column'>
<div className='text'>{peername}/{name}</div>
<div className='subtext'>{title || <br/>}</div>
</div>
<div className='status-column' data-tip='unlinked'>
{!isLinked && (
<FontAwesomeIcon icon={faUnlink} size='sm'/>
)}
</div>
? filteredDatasets.map(({ peername, name, title, fsipath, published }) => {
const menuItems: MenuItemConstructorOptions[] = fsipath
? [
{
label: 'Reveal in Finder',
click: () => { shell.showItemInFolder(fsipath) }
},
{
type: 'separator'
},
{
label: 'Remove...',
click: () => { setModal({ type: ModalType.RemoveDataset }) }
}
]
: [
{
label: 'Remove...',
click: () => { setModal({ type: ModalType.RemoveDataset }) }
}
]

return (<ContextMenuArea menuItems={menuItems} key={`${peername}/${name}`}>
<div
key={`${peername}/${name}`}
className={classNames('sidebar-list-item', 'sidebar-list-item-text', {
'selected': (peername === workingDataset.peername) && (name === workingDataset.name)
})}
onClick={() => setWorkingDataset(peername, name, !!fsipath, published)}
>
<div className='text-column'>
<div className='text'>{peername}/{name}</div>
<div className='subtext'>{title || <br/>}</div>
</div>
<div className='status-column' data-tip='unlinked'>
{!!fsipath && (
<FontAwesomeIcon icon={faUnlink} size='sm'/>
)}
</div>

</div>
))
</div>
</ContextMenuArea>)
}
)
: <div className='sidebar-list-item-text'>Oops, no matches found for <strong>&apos;{filter}&apos;</strong></div>

const countMessage = filteredDatasets.length !== datasets.length
Expand Down
29 changes: 29 additions & 0 deletions app/components/form/CheckboxInput.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import * as React from 'react'

export interface CheckboxInputProps {
label?: string
name: string
checked: boolean
onChange: (name: string, checked: any) => void
}

const CheckboxInput: React.FunctionComponent<CheckboxInputProps> = ({ label, name, checked, onChange }) => {
const labelColor = 'primary'
return (
<>
<div className='checkbox-input-container'>
<input
id={name}
name={name}
type='checkbox'
className='checkbox-input'
checked={checked}
onChange={ () => { onChange(name, !checked) }}
/>
{label && <span className={labelColor}>{label}</span>}
</div>
</>
)
}

export default CheckboxInput
81 changes: 81 additions & 0 deletions app/components/modals/RemoveDataset.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import * as React from 'react'

import CheckboxInput from '../form/CheckboxInput'
import Modal from './Modal'
import { ApiAction } from '../../store/api'
import Error from './Error'
import Buttons from './Buttons'

interface RemoveDatasetProps {
peername: string
name: string
linkpath: string
onDismissed: () => void
onSubmit: (peername: string, name: string, dir: string) => Promise<ApiAction>
}

const RemoveDataset: React.FunctionComponent<RemoveDatasetProps> = ({ peername, name, onDismissed, onSubmit, linkpath }) => {
const [shouldRemoveFiles, setShouldRemoveFiles] = React.useState(false)

const [dismissable, setDismissable] = React.useState(true)

const [error, setError] = React.useState('')
const [loading, setLoading] = React.useState(false)

const handleChanges = (name: string, value: any) => {
setShouldRemoveFiles(value)
}

const handleSubmit = () => {
setDismissable(false)
setLoading(true)
error && setError('')
if (!onSubmit) return
onSubmit(peername, name, linkpath)
.then(onDismissed)
.catch((action) => {
setLoading(false)
setDismissable(true)
setError(action.payload.err.message)
})
}

return (
<Modal
id='RemoveDataset'
title={`Remove Dataset`}
onDismissed={onDismissed}
onSubmit={() => {}}
dismissable={dismissable}
setDismissable={setDismissable}
>
<div className='content-wrap'>
<div className='content'>
<p>Are you sure you want to remove the dataset &quot;{peername}/{name}&quot;?</p>
{ linkpath.length > 0 &&
<CheckboxInput
name='shouldRemoveFiles'
checked={shouldRemoveFiles}
onChange={handleChanges}
label={'Also remove the dataset\'s files'}
/>
}
</div>
</div>
<p className='submit-message'>
{ linkpath.length > 0 && shouldRemoveFiles && `Qri will delete dataset files in "${linkpath}"`}
</p>
<Error text={error} />
<Buttons
cancelText='cancel'
onCancel={onDismissed}
submitText='Remove'
onSubmit={handleSubmit}
disabled={false}
loading={loading}
/>
</Modal>
)
}

export default RemoveDataset
6 changes: 4 additions & 2 deletions app/containers/AppContainer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ import {
addDatasetAndFetch,
initDatasetAndFetch,
linkDataset,
pingApi
pingApi,
removeDataset
} from '../actions/api'

import {
Expand Down Expand Up @@ -62,7 +63,8 @@ const AppContainer = connect(
pingApi,
setApiConnection,
setWorkingDataset,
setModal
setModal,
removeDataset
},
mergeProps
)(App)
Expand Down
7 changes: 6 additions & 1 deletion app/models/modals.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@ export enum ModalType {
NoModal,
CreateDataset,
AddDataset,
LinkDataset
LinkDataset,
RemoveDataset
}

export const NoModal = { type: ModalType.NoModal }
Expand All @@ -21,4 +22,8 @@ export type Modal =
type: ModalType.LinkDataset
dirPath?: string
}
| {
type: ModalType.RemoveDataset
dirPath?: string
}
| { type: ModalType.NoModal }
2 changes: 1 addition & 1 deletion app/models/store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ export interface DatasetSummary {
peername: string
name: string
path: string
isLinked: boolean
fsipath: string
published: boolean
}

Expand Down
1 change: 1 addition & 0 deletions app/scss/_dataset.scss
Original file line number Diff line number Diff line change
Expand Up @@ -424,6 +424,7 @@ $header-font-size: .9rem;
border-bottom: $list-item-bottom-border;
display: flex;
align-items: center;
user-select: none;
}

.sidebar-list-header {
Expand Down
1 change: 1 addition & 0 deletions app/scss/_modal.scss
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
}

.submit-message {
min-height: 35px;
font-size: .8rem;
color: #777;
}
11 changes: 11 additions & 0 deletions app/scss/_welcome.scss
Original file line number Diff line number Diff line change
Expand Up @@ -99,3 +99,14 @@
}
}
}

.checkbox-input-container {
display: flex;

input {
margin-right: 5px;
}

span {
font-size: 0.8rem;
}

0 comments on commit bdab991

Please sign in to comment.