Skip to content

Commit

Permalink
feat: add dataset rename
Browse files Browse the repository at this point in the history
  • Loading branch information
chriswhong committed Dec 9, 2019
1 parent fc8371a commit a505582
Show file tree
Hide file tree
Showing 8 changed files with 201 additions and 12 deletions.
30 changes: 30 additions & 0 deletions app/actions/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -737,3 +737,33 @@ export function fetchReadmePreview (peername: string, name: string): ApiActionTh
return dispatch(action)
}
}

// peername and name are the dataset to be renamed
// newName is the new dataset's name, which will be in the user's namespace
export function renameDataset (peername: string, name: string, newName: string): ApiActionThunk {
return async (dispatch, getState) => {
const { peername: newPeername } = getState().session
const whenOk = chainSuccess(dispatch, getState)
const action = {
type: 'rename',
[CALL_API]: {
endpoint: 'rename',
method: 'POST',
body: {
current: `${peername}/${name}`,
new: `${newPeername}/${newName}`
}
}
}
let response: Action
try {
response = await dispatch(action)
response = await whenOk(fetchMyDatasets(-1))(response)
dispatch(openToast('success', `Dataset renamed`))
} catch (action) {
dispatch(openToast('error', action.payload.err.message))
throw action
}
return response
}
}
7 changes: 1 addition & 6 deletions app/components/Dataset.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -96,11 +96,6 @@ class Dataset extends React.Component<DatasetProps> {
const { status } = workingDataset
const { status: prevStatus } = prevProps.workingDataset

if ((this.props.selections.peername !== prevProps.selections.peername) || (this.props.selections.name !== prevProps.selections.name)) {
this.props.fetchWorkingDatasetDetails()
return
}

if (status) {
// create an array of components that need updating
const componentsToReset: SelectedComponent[] = []
Expand Down Expand Up @@ -321,7 +316,7 @@ class Dataset extends React.Component<DatasetProps> {
sidebarContent={sidebarContent}
sidebarWidth={datasetSidebarWidth}
onSidebarResize={(width) => { setSidebarWidth('dataset', width) }}
maximumSidebarWidth={300}
maximumSidebarWidth={495}
mainContent={mainContent}
/>
</>
Expand Down
112 changes: 112 additions & 0 deletions app/components/DatasetReference.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
// a component that displays the dataset reference including edit-in-place UI
// for dataset rename
import * as React from 'react'
import classNames from 'classnames'

import { ApiActionThunk } from '../store/api'
import { validateDatasetName } from '../utils/formValidation'

interface DatasetReferenceProps {
peername: string
name: string
renameDataset: (peername: string, name: string, newName: string) => ApiActionThunk
}

const DatasetReference: React.FunctionComponent<DatasetReferenceProps> = (props) => {
const { peername, name, renameDataset } = props
const [ nameEditing, setNameEditing ] = React.useState(false)
const [ newName, setNewName ] = React.useState(name)
const [ inValid, setInvalid ] = React.useState(null)

const commitRename = (peername: string, name: string, newName: string) => {
// cancel if no change, change invalid, or empty
if ((name === newName) || inValid || newName === '') {
cancelRename()
} else {
renameDataset(peername, name, newName)
.then(() => {
setNameEditing(false)
})
}
}

const cancelRename = () => {
setNewName(name)
setInvalid(null)
setNameEditing(false)
}

const handleKeyDown = (e: any) => {
// cancel on esc
if (e.keyCode === 27) {
cancelRename()
}

// submit on enter or tab
if ((e.keyCode === 13) || (e.keyCode === 9)) {
commitRename(peername, name, newName)
}
}

// use a ref so we can set up a click handler
const nameRef: any = React.useRef(null)

const handleMousedown = (e: MouseEvent) => {
const { target } = e
// allows the user to resize the sidebar when editing the dataset name
if (target.classList.contains('resize-handle')) return

if (nameRef.current.isSameNode(target)) {
setNameEditing(true)
return
}

if (!nameRef.current.contains(target)) {
commitRename(peername, name, newName)
}
}

React.useEffect(() => {
document.addEventListener('keydown', handleKeyDown, false)
document.addEventListener('mousedown', handleMousedown, false)

return () => {
document.removeEventListener('keydown', handleKeyDown, false)
document.removeEventListener('mousedown', handleMousedown, false)
}
}, [ name, newName ])

const handleInputChange = (e: any) => {
let { value } = e.target
setInvalid(validateDatasetName(value))
setNewName(value)
}

// when the input is focused, scroll all the way to the left
const onFocus = () => {
const el = document.getElementById('dataset-name-input') as HTMLInputElement
el.scrollLeft = 0
}

return (
<div className='dataset-reference'>
<div className='dataset-peername'>{peername}/</div>
<div className='dataset-name' ref={nameRef}>
{ nameEditing && <input
id='dataset-name-input'
className={classNames({ invalid: inValid })}
type='text'
value={newName}
maxLength={150}
onChange={handleInputChange}
autoFocus
onFocus={onFocus}
pattern='^[a-z0-9_]+$'
/> }
{ !nameEditing && (<>{name}</>)}
</div>
</div>
)
}

export default DatasetReference
7 changes: 5 additions & 2 deletions app/components/DatasetSidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { faClock } from '@fortawesome/free-regular-svg-icons'

import { ApiActionThunk } from '../store/api'
import ComponentList from './ComponentList'
import DatasetReference from './DatasetReference'

import classNames from 'classnames'
import Spinner from './chrome/Spinner'
Expand Down Expand Up @@ -71,6 +72,7 @@ export interface DatasetSidebarProps {
fetchWorkingHistory: (page?: number, pageSize?: number) => ApiActionThunk
discardChanges: (component: ComponentType) => ApiActionThunk
setHideCommitNudge: () => Action
renameDataset: (peername: string, name: string, newName: string) => ApiActionThunk
}

const DatasetSidebar: React.FunctionComponent<DatasetSidebarProps> = (props) => {
Expand All @@ -81,7 +83,8 @@ const DatasetSidebar: React.FunctionComponent<DatasetSidebarProps> = (props) =>
setSelectedListItem,
fetchWorkingHistory,
discardChanges,
setModal
setModal,
renameDataset
} = props

const { fsiPath, history, status, structure } = workingDataset
Expand Down Expand Up @@ -118,7 +121,7 @@ const DatasetSidebar: React.FunctionComponent<DatasetSidebarProps> = (props) =>
<div className='dataset-sidebar'>
<div className='dataset-sidebar-header sidebar-padded-container'>
<p className='pane-title'>Dataset</p>
<p className='dataset-name'>{peername}/{name}</p>
<DatasetReference peername={peername} name={name} renameDataset={renameDataset}/>
<DatasetDetailsSubtext format={format} lastCommit={lastCommit} commitCount={commitCount} />
</div>
<div id='tabs' className='sidebar-padded-container'>
Expand Down
6 changes: 4 additions & 2 deletions app/containers/DatasetSidebarContainer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ import {

import {
fetchWorkingHistory,
discardChanges
discardChanges,
renameDataset
} from '../actions/api'

import {
Expand Down Expand Up @@ -49,7 +50,8 @@ const DatasetSidebarContainer = connect(
setSelectedListItem,
fetchWorkingHistory,
discardChanges,
setHideCommitNudge
setHideCommitNudge,
renameDataset
},
mergeProps
)(DatasetSidebar)
Expand Down
8 changes: 8 additions & 0 deletions app/reducers/selections.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ export const [, UNPUBLISH_SUCC] = apiActionTypes('unpublish')
export const [, SIGNIN_SUCC] = apiActionTypes('signin')
export const [, SIGNUP_SUCC] = apiActionTypes('signup')
export const [, , HISTORY_FAIL] = apiActionTypes('history')
export const [, RENAME_SUCC] = apiActionTypes('rename')

export default (state = initialState, action: AnyAction) => {
switch (action.type) {
Expand Down Expand Up @@ -170,6 +171,13 @@ export default (state = initialState, action: AnyAction) => {
activeTab: 'status'
}

case RENAME_SUCC:
localStore().setItem('name', action.payload.data.name)
return {
...state,
name: action.payload.data.name
}

default:
return state
}
Expand Down
7 changes: 7 additions & 0 deletions app/reducers/workingDataset.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ export const [DATASET_STATUS_REQ, DATASET_STATUS_SUCC, DATASET_STATUS_FAIL] = ap
const [DATASET_BODY_REQ, DATASET_BODY_SUCC, DATASET_BODY_FAIL] = apiActionTypes('body')
const [, RESETOTHERCOMPONENTS_SUCC, RESETOTHERCOMPONENTS_FAIL] = apiActionTypes('resetOtherComponents')
const [STATS_REQ, STATS_SUCC, STATS_FAIL] = apiActionTypes('stats')
export const [, RENAME_SUCC] = apiActionTypes('rename')

export const RESET_BODY = 'RESET_BODY'

Expand Down Expand Up @@ -274,6 +275,12 @@ const workingDatasetsReducer: Reducer = (state = initialState, action: AnyAction
case SELECTIONS_SET_WORKING_DATASET:
return initialState

case RENAME_SUCC:
return {
...state,
name: action.payload.data.name
}

default:
return state
}
Expand Down
36 changes: 34 additions & 2 deletions app/scss/_dataset.scss
Original file line number Diff line number Diff line change
Expand Up @@ -208,12 +208,37 @@ $header-font-size: .9rem;
font-weight: 900;
}

.dataset-name {
.dataset-reference {
font-size: 1.05rem;
display: flex;
}

.dataset-name {
flex: 1;
white-space: nowrap;
text-overflow: ellipsis;
display: block;
overflow: hidden;

input {
background-color: #2f578c;
border: none;
border-radius: 5px;
color: #fff;
padding: 0 5px;
outline: none;
width: 100%;
transition: background 0.2s;

&.invalid {
background: #F43535;
}

}

&:hover {
cursor: pointer;
text-decoration: underline;
}
}
}

Expand Down Expand Up @@ -320,6 +345,13 @@ $header-font-size: .9rem;
background-color: #38C629;
}

.edit-icon {
font-size: 0.75rem;
opacity: 0;
transition: opacity 0.1s;
color: $light-blue-accent;
}


////////////////////////////
// Commit Details //
Expand Down

0 comments on commit a505582

Please sign in to comment.