Skip to content

Commit bdab991

Browse files
committed
feat: add remove dataset modal
1 parent c289c46 commit bdab991

File tree

11 files changed

+206
-28
lines changed

11 files changed

+206
-28
lines changed

app/actions/mappingFuncs.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ export function mapDatasetSummary (data: any[]): DatasetSummary[] {
1717
peername: ref.peername,
1818
name: ref.name,
1919
path: ref.path,
20-
isLinked: !!ref.fsiPath,
20+
fsipath: ref.fsiPath,
2121
published: ref.published
2222
}))
2323
}

app/components/App.tsx

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import AppError from './AppError'
1111
import AppLoading from './AppLoading'
1212
import AddDataset from './modals/AddDataset'
1313
import LinkDataset from './modals/LinkDataset'
14+
import RemoveDataset from './modals/RemoveDataset'
1415
import CreateDataset from './modals/CreateDataset'
1516
import RoutesContainer from '../containers/RoutesContainer'
1617

@@ -48,6 +49,8 @@ export interface AppProps {
4849
setApiConnection: (status: number) => Action
4950
pingApi: () => Promise<ApiAction>
5051
setModal: (modal: Modal) => Action
52+
removeDataset: (peername: string, name: string, dir: string) => Promise<ApiAction>
53+
5154
}
5255

5356
interface AppState {
@@ -109,7 +112,7 @@ class App extends React.Component<AppProps, AppState> {
109112

110113
private renderModal (): JSX.Element | null {
111114
const { modal, setModal, workingDataset } = this.props
112-
const { peername, name } = workingDataset
115+
const { peername, name, linkpath } = workingDataset
113116
const Modal = modal
114117

115118
if (!Modal) return null
@@ -153,6 +156,21 @@ class App extends React.Component<AppProps, AppState> {
153156
onDismissed={async () => setModal(NoModal)}
154157
/>
155158
</CSSTransition>
159+
<CSSTransition
160+
in={ModalType.RemoveDataset === Modal.type}
161+
classNames='fade'
162+
component='div'
163+
timeout={300}
164+
unmountOnExit
165+
>
166+
<RemoveDataset
167+
peername={peername}
168+
name={name}
169+
linkpath={linkpath}
170+
onSubmit={this.props.removeDataset}
171+
onDismissed={async () => setModal(NoModal)}
172+
/>
173+
</CSSTransition>
156174
</div>
157175
)
158176
}

app/components/DatasetList.tsx

Lines changed: 52 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import * as React from 'react'
22
import { Action, AnyAction } from 'redux'
33
import classNames from 'classnames'
4+
import ContextMenuArea from 'react-electron-contextmenu'
5+
import { shell, MenuItemConstructorOptions } from 'electron'
46

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

5961
render () {
60-
const { workingDataset, setModal } = this.props
61-
const { setWorkingDataset } = this.props
62-
const { filter, value: datasets } = this.props.myDatasets
62+
const {
63+
workingDataset,
64+
setModal,
65+
setWorkingDataset,
66+
myDatasets
67+
} = this.props
68+
const { filter, value: datasets } = myDatasets
6369

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

7884
const listContent = filteredDatasets.length > 0
79-
? filteredDatasets.map(({ peername, name, title, isLinked, published }) => (
80-
<div
81-
key={`${peername}/${name}`}
82-
className={classNames('sidebar-list-item', 'sidebar-list-item-text', {
83-
'selected': (peername === workingDataset.peername) && (name === workingDataset.name)
84-
})}
85-
onClick={() => setWorkingDataset(peername, name, isLinked, published)}
86-
>
87-
<div className='text-column'>
88-
<div className='text'>{peername}/{name}</div>
89-
<div className='subtext'>{title || <br/>}</div>
90-
</div>
91-
<div className='status-column' data-tip='unlinked'>
92-
{!isLinked && (
93-
<FontAwesomeIcon icon={faUnlink} size='sm'/>
94-
)}
95-
</div>
85+
? filteredDatasets.map(({ peername, name, title, fsipath, published }) => {
86+
const menuItems: MenuItemConstructorOptions[] = fsipath
87+
? [
88+
{
89+
label: 'Reveal in Finder',
90+
click: () => { shell.showItemInFolder(fsipath) }
91+
},
92+
{
93+
type: 'separator'
94+
},
95+
{
96+
label: 'Remove...',
97+
click: () => { setModal({ type: ModalType.RemoveDataset }) }
98+
}
99+
]
100+
: [
101+
{
102+
label: 'Remove...',
103+
click: () => { setModal({ type: ModalType.RemoveDataset }) }
104+
}
105+
]
106+
107+
return (<ContextMenuArea menuItems={menuItems} key={`${peername}/${name}`}>
108+
<div
109+
key={`${peername}/${name}`}
110+
className={classNames('sidebar-list-item', 'sidebar-list-item-text', {
111+
'selected': (peername === workingDataset.peername) && (name === workingDataset.name)
112+
})}
113+
onClick={() => setWorkingDataset(peername, name, !!fsipath, published)}
114+
>
115+
<div className='text-column'>
116+
<div className='text'>{peername}/{name}</div>
117+
<div className='subtext'>{title || <br/>}</div>
118+
</div>
119+
<div className='status-column' data-tip='unlinked'>
120+
{!!fsipath && (
121+
<FontAwesomeIcon icon={faUnlink} size='sm'/>
122+
)}
123+
</div>
96124

97-
</div>
98-
))
125+
</div>
126+
</ContextMenuArea>)
127+
}
128+
)
99129
: <div className='sidebar-list-item-text'>Oops, no matches found for <strong>&apos;{filter}&apos;</strong></div>
100130

101131
const countMessage = filteredDatasets.length !== datasets.length

app/components/form/CheckboxInput.tsx

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import * as React from 'react'
2+
3+
export interface CheckboxInputProps {
4+
label?: string
5+
name: string
6+
checked: boolean
7+
onChange: (name: string, checked: any) => void
8+
}
9+
10+
const CheckboxInput: React.FunctionComponent<CheckboxInputProps> = ({ label, name, checked, onChange }) => {
11+
const labelColor = 'primary'
12+
return (
13+
<>
14+
<div className='checkbox-input-container'>
15+
<input
16+
id={name}
17+
name={name}
18+
type='checkbox'
19+
className='checkbox-input'
20+
checked={checked}
21+
onChange={ () => { onChange(name, !checked) }}
22+
/>
23+
{label && <span className={labelColor}>{label}</span>}
24+
</div>
25+
</>
26+
)
27+
}
28+
29+
export default CheckboxInput
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
import * as React from 'react'
2+
3+
import CheckboxInput from '../form/CheckboxInput'
4+
import Modal from './Modal'
5+
import { ApiAction } from '../../store/api'
6+
import Error from './Error'
7+
import Buttons from './Buttons'
8+
9+
interface RemoveDatasetProps {
10+
peername: string
11+
name: string
12+
linkpath: string
13+
onDismissed: () => void
14+
onSubmit: (peername: string, name: string, dir: string) => Promise<ApiAction>
15+
}
16+
17+
const RemoveDataset: React.FunctionComponent<RemoveDatasetProps> = ({ peername, name, onDismissed, onSubmit, linkpath }) => {
18+
const [shouldRemoveFiles, setShouldRemoveFiles] = React.useState(false)
19+
20+
const [dismissable, setDismissable] = React.useState(true)
21+
22+
const [error, setError] = React.useState('')
23+
const [loading, setLoading] = React.useState(false)
24+
25+
const handleChanges = (name: string, value: any) => {
26+
setShouldRemoveFiles(value)
27+
}
28+
29+
const handleSubmit = () => {
30+
setDismissable(false)
31+
setLoading(true)
32+
error && setError('')
33+
if (!onSubmit) return
34+
onSubmit(peername, name, linkpath)
35+
.then(onDismissed)
36+
.catch((action) => {
37+
setLoading(false)
38+
setDismissable(true)
39+
setError(action.payload.err.message)
40+
})
41+
}
42+
43+
return (
44+
<Modal
45+
id='RemoveDataset'
46+
title={`Remove Dataset`}
47+
onDismissed={onDismissed}
48+
onSubmit={() => {}}
49+
dismissable={dismissable}
50+
setDismissable={setDismissable}
51+
>
52+
<div className='content-wrap'>
53+
<div className='content'>
54+
<p>Are you sure you want to remove the dataset &quot;{peername}/{name}&quot;?</p>
55+
{ linkpath.length > 0 &&
56+
<CheckboxInput
57+
name='shouldRemoveFiles'
58+
checked={shouldRemoveFiles}
59+
onChange={handleChanges}
60+
label={'Also remove the dataset\'s files'}
61+
/>
62+
}
63+
</div>
64+
</div>
65+
<p className='submit-message'>
66+
{ linkpath.length > 0 && shouldRemoveFiles && `Qri will delete dataset files in "${linkpath}"`}
67+
</p>
68+
<Error text={error} />
69+
<Buttons
70+
cancelText='cancel'
71+
onCancel={onDismissed}
72+
submitText='Remove'
73+
onSubmit={handleSubmit}
74+
disabled={false}
75+
loading={loading}
76+
/>
77+
</Modal>
78+
)
79+
}
80+
81+
export default RemoveDataset

app/containers/AppContainer.tsx

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@ import {
77
addDatasetAndFetch,
88
initDatasetAndFetch,
99
linkDataset,
10-
pingApi
10+
pingApi,
11+
removeDataset
1112
} from '../actions/api'
1213

1314
import {
@@ -62,7 +63,8 @@ const AppContainer = connect(
6263
pingApi,
6364
setApiConnection,
6465
setWorkingDataset,
65-
setModal
66+
setModal,
67+
removeDataset
6668
},
6769
mergeProps
6870
)(App)

app/models/modals.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,8 @@ export enum ModalType {
22
NoModal,
33
CreateDataset,
44
AddDataset,
5-
LinkDataset
5+
LinkDataset,
6+
RemoveDataset
67
}
78

89
export const NoModal = { type: ModalType.NoModal }
@@ -21,4 +22,8 @@ export type Modal =
2122
type: ModalType.LinkDataset
2223
dirPath?: string
2324
}
25+
| {
26+
type: ModalType.RemoveDataset
27+
dirPath?: string
28+
}
2429
| { type: ModalType.NoModal }

app/models/store.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@ export interface DatasetSummary {
8989
peername: string
9090
name: string
9191
path: string
92-
isLinked: boolean
92+
fsipath: string
9393
published: boolean
9494
}
9595

app/scss/_dataset.scss

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -424,6 +424,7 @@ $header-font-size: .9rem;
424424
border-bottom: $list-item-bottom-border;
425425
display: flex;
426426
align-items: center;
427+
user-select: none;
427428
}
428429

429430
.sidebar-list-header {

app/scss/_modal.scss

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
}
4040

4141
.submit-message {
42+
min-height: 35px;
4243
font-size: .8rem;
4344
color: #777;
4445
}

app/scss/_welcome.scss

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,3 +99,14 @@
9999
}
100100
}
101101
}
102+
103+
.checkbox-input-container {
104+
display: flex;
105+
106+
input {
107+
margin-right: 5px;
108+
}
109+
110+
span {
111+
font-size: 0.8rem;
112+
}

0 commit comments

Comments
 (0)