-
-
Notifications
You must be signed in to change notification settings - Fork 1.5k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #853 from PrimaMateria/feature-rearrange-storage
Feature rearrange storage
- Loading branch information
Showing
14 changed files
with
632 additions
and
371 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
const _ = require('lodash') | ||
_.move = require('lodash-move').default | ||
const path = require('path') | ||
const resolveStorageData = require('./resolveStorageData') | ||
const CSON = require('@rokt33r/season') | ||
const { findStorage } = require('browser/lib/findStorage') | ||
|
||
/** | ||
* @param {String} storageKey | ||
* @param {number} oldIndex | ||
* @param {number} newIndex | ||
* | ||
* @return {Object} | ||
* ``` | ||
* { | ||
* storage: Object | ||
* } | ||
* ``` | ||
*/ | ||
function reorderFolder (storageKey, oldIndex, newIndex) { | ||
let rawStorages | ||
let targetStorage | ||
try { | ||
if (!_.isNumber(oldIndex)) throw new Error('oldIndex must be a number.') | ||
if (!_.isNumber(newIndex)) throw new Error('newIndex must be a number.') | ||
|
||
targetStorage = findStorage(storageKey) | ||
} catch (e) { | ||
return Promise.reject(e) | ||
} | ||
|
||
return resolveStorageData(targetStorage) | ||
.then(function reorderFolder (storage) { | ||
storage.folders = _.move(storage.folders, oldIndex, newIndex) | ||
CSON.writeFileSync(path.join(storage.path, 'boostnote.json'), _.pick(storage, ['folders', 'version'])) | ||
|
||
return { | ||
storage | ||
} | ||
}) | ||
} | ||
|
||
module.exports = reorderFolder |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,303 @@ | ||
import React, { PropTypes } from 'react' | ||
import CSSModules from 'browser/lib/CSSModules' | ||
import ReactDOM from 'react-dom' | ||
import styles from './FolderItem.styl' | ||
import dataApi from 'browser/main/lib/dataApi' | ||
import store from 'browser/main/store' | ||
import { SketchPicker } from 'react-color' | ||
import { SortableElement, SortableHandle } from 'react-sortable-hoc' | ||
|
||
class FolderItem extends React.Component { | ||
constructor (props) { | ||
super(props) | ||
|
||
this.state = { | ||
status: 'IDLE', | ||
folder: { | ||
showColumnPicker: false, | ||
colorPickerPos: { left: 0, top: 0 }, | ||
color: props.color, | ||
name: props.name | ||
} | ||
} | ||
} | ||
|
||
handleEditChange (e) { | ||
let { folder } = this.state | ||
|
||
folder.name = this.refs.nameInput.value | ||
this.setState({ | ||
folder | ||
}) | ||
} | ||
|
||
handleConfirmButtonClick (e) { | ||
this.confirm() | ||
} | ||
|
||
confirm () { | ||
let { storage, folder } = this.props | ||
dataApi | ||
.updateFolder(storage.key, folder.key, { | ||
color: this.state.folder.color, | ||
name: this.state.folder.name | ||
}) | ||
.then((data) => { | ||
store.dispatch({ | ||
type: 'UPDATE_FOLDER', | ||
storage: data.storage | ||
}) | ||
this.setState({ | ||
status: 'IDLE' | ||
}) | ||
}) | ||
} | ||
|
||
handleColorButtonClick (e) { | ||
const folder = Object.assign({}, this.state.folder, { showColumnPicker: true, colorPickerPos: { left: 0, top: 0 } }) | ||
this.setState({ folder }, function () { | ||
// After the color picker has been painted, re-calculate its position | ||
// by comparing its dimensions to the host dimensions. | ||
const { hostBoundingBox } = this.props | ||
const colorPickerNode = ReactDOM.findDOMNode(this.refs.colorPicker) | ||
const colorPickerBox = colorPickerNode.getBoundingClientRect() | ||
const offsetTop = hostBoundingBox.bottom - colorPickerBox.bottom | ||
const folder = Object.assign({}, this.state.folder, { | ||
colorPickerPos: { | ||
left: 25, | ||
top: offsetTop < 0 ? offsetTop - 5 : 0 // subtract 5px for aestetics | ||
} | ||
}) | ||
this.setState({ folder }) | ||
}) | ||
} | ||
|
||
handleColorChange (color) { | ||
const folder = Object.assign({}, this.state.folder, { color: color.hex }) | ||
this.setState({ folder }) | ||
} | ||
|
||
handleColorPickerClose (event) { | ||
const folder = Object.assign({}, this.state.folder, { showColumnPicker: false }) | ||
this.setState({ folder }) | ||
} | ||
|
||
handleCancelButtonClick (e) { | ||
this.setState({ | ||
status: 'IDLE' | ||
}) | ||
} | ||
|
||
handleFolderItemBlur (e) { | ||
let el = e.relatedTarget | ||
while (el != null) { | ||
if (el === this.refs.root) { | ||
return false | ||
} | ||
el = el.parentNode | ||
} | ||
this.confirm() | ||
} | ||
|
||
renderEdit (e) { | ||
const popover = { position: 'absolute', zIndex: 2 } | ||
const cover = { | ||
position: 'fixed', | ||
top: 0, | ||
right: 0, | ||
bottom: 0, | ||
left: 0 | ||
} | ||
const pickerStyle = Object.assign({}, { | ||
position: 'absolute' | ||
}, this.state.folder.colorPickerPos) | ||
return ( | ||
<div styleName='folderItem' | ||
onBlur={(e) => this.handleFolderItemBlur(e)} | ||
tabIndex='-1' | ||
ref='root' | ||
> | ||
<div styleName='folderItem-left'> | ||
<button styleName='folderItem-left-colorButton' style={{color: this.state.folder.color}} | ||
onClick={(e) => !this.state.folder.showColumnPicker && this.handleColorButtonClick(e)} | ||
> | ||
{this.state.folder.showColumnPicker | ||
? <div style={popover}> | ||
<div style={cover} | ||
onClick={() => this.handleColorPickerClose()} | ||
/> | ||
<div style={pickerStyle}> | ||
<SketchPicker | ||
ref='colorPicker' | ||
color={this.state.folder.color} | ||
onChange={(color) => this.handleColorChange(color)} | ||
onChangeComplete={(color) => this.handleColorChange(color)} | ||
/> | ||
</div> | ||
</div> | ||
: null | ||
} | ||
<i className='fa fa-square' /> | ||
</button> | ||
<input styleName='folderItem-left-nameInput' | ||
value={this.state.folder.name} | ||
ref='nameInput' | ||
onChange={(e) => this.handleEditChange(e)} | ||
/> | ||
</div> | ||
<div styleName='folderItem-right'> | ||
<button styleName='folderItem-right-confirmButton' | ||
onClick={(e) => this.handleConfirmButtonClick(e)} | ||
> | ||
Confirm | ||
</button> | ||
<button styleName='folderItem-right-button' | ||
onClick={(e) => this.handleCancelButtonClick(e)} | ||
> | ||
Cancel | ||
</button> | ||
</div> | ||
</div> | ||
) | ||
} | ||
|
||
handleDeleteConfirmButtonClick (e) { | ||
let { storage, folder } = this.props | ||
dataApi | ||
.deleteFolder(storage.key, folder.key) | ||
.then((data) => { | ||
store.dispatch({ | ||
type: 'DELETE_FOLDER', | ||
storage: data.storage, | ||
folderKey: data.folderKey | ||
}) | ||
}) | ||
} | ||
|
||
renderDelete () { | ||
return ( | ||
<div styleName='folderItem'> | ||
<div styleName='folderItem-left'> | ||
Are you sure to <span styleName='folderItem-left-danger'>delete</span> this folder? | ||
</div> | ||
<div styleName='folderItem-right'> | ||
<button styleName='folderItem-right-dangerButton' | ||
onClick={(e) => this.handleDeleteConfirmButtonClick(e)} | ||
> | ||
Confirm | ||
</button> | ||
<button styleName='folderItem-right-button' | ||
onClick={(e) => this.handleCancelButtonClick(e)} | ||
> | ||
Cancel | ||
</button> | ||
</div> | ||
</div> | ||
) | ||
} | ||
|
||
handleEditButtonClick (e) { | ||
let { folder: propsFolder } = this.props | ||
let { folder: stateFolder } = this.state | ||
const folder = Object.assign({}, stateFolder, propsFolder) | ||
this.setState({ | ||
status: 'EDIT', | ||
folder | ||
}, () => { | ||
this.refs.nameInput.select() | ||
}) | ||
} | ||
|
||
handleDeleteButtonClick (e) { | ||
this.setState({ | ||
status: 'DELETE' | ||
}) | ||
} | ||
|
||
renderIdle () { | ||
let { folder } = this.props | ||
return ( | ||
<div styleName='folderItem' | ||
onDoubleClick={(e) => this.handleEditButtonClick(e)} | ||
> | ||
<div styleName='folderItem-left' | ||
style={{borderColor: folder.color}} | ||
> | ||
<span styleName='folderItem-left-name'>{folder.name}</span> | ||
<span styleName='folderItem-left-key'>({folder.key})</span> | ||
</div> | ||
<div styleName='folderItem-right'> | ||
<button styleName='folderItem-right-button' | ||
onClick={(e) => this.handleEditButtonClick(e)} | ||
> | ||
Edit | ||
</button> | ||
<button styleName='folderItem-right-button' | ||
onClick={(e) => this.handleDeleteButtonClick(e)} | ||
> | ||
Delete | ||
</button> | ||
</div> | ||
</div> | ||
) | ||
} | ||
|
||
render () { | ||
switch (this.state.status) { | ||
case 'DELETE': | ||
return this.renderDelete() | ||
case 'EDIT': | ||
return this.renderEdit() | ||
case 'IDLE': | ||
default: | ||
return this.renderIdle() | ||
} | ||
} | ||
} | ||
|
||
FolderItem.propTypes = { | ||
hostBoundingBox: PropTypes.shape({ | ||
bottom: PropTypes.number, | ||
height: PropTypes.number, | ||
left: PropTypes.number, | ||
right: PropTypes.number, | ||
top: PropTypes.number, | ||
width: PropTypes.number | ||
}), | ||
storage: PropTypes.shape({ | ||
key: PropTypes.string | ||
}), | ||
folder: PropTypes.shape({ | ||
key: PropTypes.string, | ||
color: PropTypes.string, | ||
name: PropTypes.string | ||
}) | ||
} | ||
|
||
class Handle extends React.Component { | ||
render () { | ||
return ( | ||
<div styleName='folderItem-drag-handle'> | ||
<i className='fa fa-reorder' /> | ||
</div> | ||
) | ||
} | ||
} | ||
|
||
class SortableFolderItemComponent extends React.Component { | ||
render () { | ||
const StyledHandle = CSSModules(Handle, this.props.styles) | ||
const DragHandle = SortableHandle(StyledHandle) | ||
|
||
const StyledFolderItem = CSSModules(FolderItem, this.props.styles) | ||
|
||
return ( | ||
<div> | ||
<DragHandle /> | ||
<StyledFolderItem {...this.props} /> | ||
</div> | ||
) | ||
} | ||
} | ||
|
||
export default CSSModules(SortableElement(SortableFolderItemComponent), styles) |
Oops, something went wrong.