1
0
mirror of https://github.com/BoostIo/Boostnote synced 2025-12-12 17:26:17 +00:00

renew SideNav

add contextmenu
fix MutableSet bug
This commit is contained in:
Dick Choi
2016-10-13 15:51:33 +09:00
parent 2fdea6cd61
commit 8f0789bc6d
10 changed files with 569 additions and 41 deletions

View File

@@ -61,7 +61,7 @@ class MutableSet {
constructor (iterable) {
this._set = new Set(iterable)
Object.defineProperty(this, 'size', {
get: () => this._map.size,
get: () => this._set.size,
set: function (value) {
this['size'] = value
}

View File

@@ -38,7 +38,6 @@ class NoteList extends React.Component {
}
this.state = {
range: 0
}
}
@@ -57,19 +56,6 @@ class NoteList extends React.Component {
resetScroll () {
this.refs.list.scrollTop = 0
this.setState({
range: 0
})
}
handleScroll (e) {
let notes = this.notes
if (e.target.offsetHeight + e.target.scrollTop > e.target.scrollHeight - 250 && notes.length > this.state.range * 20 + 20) {
this.setState({
range: this.state.range + 1
})
}
}
componentWillUnmount () {
@@ -82,6 +68,7 @@ class NoteList extends React.Component {
componentDidUpdate (prevProps) {
let { location } = this.props
if (this.notes.length > 0 && location.query.key == null) {
let { router } = this.context
router.replace({
@@ -325,7 +312,7 @@ class NoteList extends React.Component {
this.notes = notes = this.getNotes()
.sort(sortFunc)
let noteList = notes.slice(0, 20 + 20 * this.state.range)
let noteList = notes
.map((note) => {
if (note == null) return null
let tagElements = _.isArray(note.tags)
@@ -424,7 +411,6 @@ class NoteList extends React.Component {
ref='list'
tabIndex='-1'
onKeyDown={(e) => this.handleNoteListKeyDown(e)}
onScroll={(e) => this.handleScroll(e)}
>
{noteList}
</div>

View File

@@ -2,6 +2,13 @@ import React, { PropTypes } from 'react'
import CSSModules from 'browser/lib/CSSModules'
import styles from './StorageItem.styl'
import { hashHistory } from 'react-router'
import modal from 'browser/main/lib/modal'
import CreateFolderModal from 'browser/main/modals/CreateFolderModal'
import RenameFolderModal from 'browser/main/modals/RenameFolderModal'
import dataApi from 'browser/main/lib/dataApi'
const { remote } = require('electron')
const { Menu, MenuItem, dialog } = remote
class StorageItem extends React.Component {
constructor (props) {
@@ -12,12 +19,59 @@ class StorageItem extends React.Component {
}
}
handleHeaderContextMenu (e) {
let menu = new Menu()
menu.append(new MenuItem({
label: 'Add Folder',
click: (e) => this.handleAddFolderButtonClick(e)
}))
menu.append(new MenuItem({
type: 'separator'
}))
menu.append(new MenuItem({
label: 'Unlink Storage',
click: (e) => this.handleUnlinkStorageClick(e)
}))
menu.popup()
}
handleUnlinkStorageClick (e) {
let index = dialog.showMessageBox(remote.getCurrentWindow(), {
type: 'warning',
message: 'Unlink Storage',
detail: 'This work will just detatches a storage from Boostnote. (Any data won\'t be deleted.)',
buttons: ['Confirm', 'Cancel']
})
if (index === 0) {
let { storage, dispatch } = this.props
dataApi.removeStorage(storage.key)
.then(() => {
dispatch({
type: 'REMOVE_STORAGE',
storageKey: storage.key
})
})
.catch((err) => {
throw err
})
}
}
handleToggleButtonClick (e) {
this.setState({
isOpen: !this.state.isOpen
})
}
handleAddFolderButtonClick (e) {
let { storage } = this.props
modal.open(CreateFolderModal, {
storage
})
}
handleHeaderInfoClick (e) {
let { storage } = this.props
hashHistory.push('/storages/' + storage.key)
@@ -30,22 +84,78 @@ class StorageItem extends React.Component {
}
}
handleFolderButtonContextMenu (e, folder) {
let menu = new Menu()
menu.append(new MenuItem({
label: 'Rename Folder',
click: (e) => this.handleRenameFolderClick(e, folder)
}))
menu.append(new MenuItem({
type: 'separator'
}))
menu.append(new MenuItem({
label: 'Delete Folder',
click: (e) => this.handleFolderDeleteClick(e, folder)
}))
menu.popup()
}
handleRenameFolderClick (e, folder) {
let { storage } = this.props
modal.open(RenameFolderModal, {
storage,
folder
})
}
handleFolderDeleteClick (e, folder) {
let index = dialog.showMessageBox(remote.getCurrentWindow(), {
type: 'warning',
message: 'Delete Folder',
detail: 'This work will deletes all notes in the folder and can not be undone.',
buttons: ['Confirm', 'Cancel']
})
if (index === 0) {
let { storage, dispatch } = this.props
dataApi
.deleteFolder(storage.key, folder.key)
.then((data) => {
dispatch({
type: 'DELETE_FOLDER',
storage: data.storage,
folderKey: data.folderKey
})
})
}
}
render () {
let { storage, location, isFolded } = this.props
let { storage, location, isFolded, data } = this.props
let { folderNoteMap } = data
let folderList = storage.folders.map((folder) => {
let isActive = location.pathname.match(new RegExp('\/storages\/' + storage.key + '\/folders\/' + folder.key))
let noteSet = folderNoteMap.get(storage.key + '-' + folder.key)
let noteCount = noteSet != null
? noteSet.size
: 0
return <button styleName={isActive
? 'folderList-item--active'
: 'folderList-item'
}
key={folder.key}
onClick={(e) => this.handleFolderButtonClick(folder.key)(e)}
onContextMenu={(e) => this.handleFolderButtonContextMenu(e, folder)}
>
<span styleName='folderList-item-name'
style={{borderColor: folder.color}}
>
{isFolded ? folder.name.substring(0, 1) : folder.name}
</span>
{!isFolded &&
<span styleName='folderList-item-noteCount'>{noteCount}</span>
}
{isFolded &&
<span styleName='folderList-item-tooltip'>
{folder.name}
@@ -61,9 +171,11 @@ class StorageItem extends React.Component {
key={storage.key}
>
<div styleName={isActive
? 'header--active'
: 'header'
}>
? 'header--active'
: 'header'
}
onContextMenu={(e) => this.handleHeaderContextMenu(e)}
>
<button styleName='header-toggleButton'
onMouseDown={(e) => this.handleToggleButtonClick(e)}
>
@@ -73,21 +185,24 @@ class StorageItem extends React.Component {
}
/>
</button>
{!isFolded &&
<button styleName='header-addFolderButton'
onClick={(e) => this.handleAddFolderButtonClick(e)}
>
<i className='fa fa-plus'/>
</button>
}
<button styleName='header-info'
onClick={(e) => this.handleHeaderInfoClick(e)}
>
<span styleName='header-info-name'>
{isFolded ? storage.name.substring(0, 1) : storage.name}
</span>
<span styleName='header-info-path'>
({storage.path})
</span>
{isFolded &&
<span styleName='header-info--folded-tooltip'>
{storage.name}
<span styleName='header-info--folded-tooltip-path'>
({storage.path})
</span>
</span>
}
</button>

View File

@@ -9,15 +9,22 @@
background-color $ui-button--hover-backgroundColor
&:active
.header-toggleButton
.header-addFolderButton
color white
&:active
color $ui-active-color
.header--active
@extend .header
.header-info
color $ui-button--active-color
background-color $ui-button--active-backgroundColor
.header-toggleButton
.header-addFolderButton
color white
&:active
&:hover
&:hover:active
color white
.header-toggleButton
@@ -55,8 +62,23 @@
.header-info-path
font-size 10px
margin 0 5px
.header-addFolderButton
position absolute
right 0
width 25px
height 26px
padding 0
border none
color $ui-inactive-text-color
background-color transparent
&:hover
color $ui-text-color
&:active
color $ui-active-color
.folderList-item
display block
display flex
width 100%
height 26px
background-color transparent
@@ -74,6 +96,7 @@
&:active
color $ui-button--active-color
background-color $ui-button--active-backgroundColor
.folderList-item--active
@extend .folderList-item
color $ui-button--active-color
@@ -81,14 +104,23 @@
&:hover
color $ui-button--active-color
background-color $ui-button--active-backgroundColor
.folderList-item-name
display block
flex 1
padding 0 10px
height 26px
line-height 26px
border-width 0 0 0 6px
border-style solid
border-color transparent
.folderList-item-noteCount
float right
line-height 26px
padding-right 5px
font-size 12px
.folderList-item-tooltip
tooltip()
position fixed
@@ -102,6 +134,7 @@
height 26px
margin-top -26px
line-height 26px
.root--folded
@extend .root
.header
@@ -133,3 +166,17 @@
opacity 1
.folderList-item-name
padding-left 14px
body[data-theme="dark"]
.header-toggleButton
.header-addFolderButton
color $ui-dark-inactive-text-color
&:hover
color $ui-dark-text-color
&:active
color $ui-dark-active-color
.header--active
.header-toggleButton
.header-addFolderButton
color white
&:active
color white

View File

@@ -36,7 +36,7 @@ class SideNav extends React.Component {
}
render () {
let { data, location, config } = this.props
let { data, location, config, dispatch } = this.props
let isFolded = config.isSideNavFolded
let isHomeActive = location.pathname.match(/^\/home$/)
@@ -46,8 +46,10 @@ class SideNav extends React.Component {
return <StorageItem
key={storage.key}
storage={storage}
data={data}
location={location}
isFolded={isFolded}
dispatch={dispatch}
/>
})
let style = {}

View File

@@ -0,0 +1,109 @@
import React, { PropTypes } from 'react'
import CSSModules from 'browser/lib/CSSModules'
import styles from './CreateFolderModal.styl'
import dataApi from 'browser/main/lib/dataApi'
import store from 'browser/main/store'
import consts from 'browser/lib/consts'
class CreateFolderModal extends React.Component {
constructor (props) {
super(props)
this.state = {
name: ''
}
}
componentDidMount () {
this.refs.name.focus()
this.refs.name.select()
}
handleCloseButtonClick (e) {
this.props.close()
}
handleChange (e) {
this.setState({
name: this.refs.name.value
})
}
handleKeyDown (e) {
if (e.keyCode === 27) {
this.props.close()
}
}
handleInputKeyDown (e) {
switch (e.keyCode) {
case 13:
this.confirm()
}
}
handleConfirmButtonClick (e) {
this.confirm()
}
confirm () {
if (this.state.name.trim().length > 0) {
let { storage } = this.props
let input = {
name: this.state.name.trim(),
color: consts.FOLDER_COLORS[Math.floor(Math.random() * 7) % 7]
}
dataApi.createFolder(storage.key, input)
.then((data) => {
store.dispatch({
type: 'UPDATE_FOLDER',
storage: data.storage
})
this.props.close()
})
.catch((err) => {
console.error(err)
})
}
}
render () {
return (
<div styleName='root'
tabIndex='-1'
onKeyDown={(e) => this.handleKeyDown(e)}
>
<div styleName='header'>
<div styleName='title'>New Folder</div>
</div>
<button styleName='closeButton'
onClick={(e) => this.handleCloseButtonClick(e)}
>Close</button>
<div styleName='control'>
<input styleName='control-input'
placeholder='Folder Name'
ref='name'
value={this.state.name}
onChange={(e) => this.handleChange(e)}
onKeyDown={(e) => this.handleInputKeyDown(e)}
/>
<button styleName='control-confirmButton'
onClick={(e) => this.handleConfirmButtonClick(e)}
>
Confirm
</button>
</div>
</div>
)
}
}
CreateFolderModal.propTypes = {
storage: PropTypes.shape({
key: PropTypes.string
})
}
export default CSSModules(CreateFolderModal, styles)

View File

@@ -0,0 +1,76 @@
.root
modal()
max-width 340px
overflow hidden
position relative
.header
height 50px
font-size 18px
line-height 50px
padding 0 15px
background-color $ui-backgroundColor
border-bottom solid 1px $ui-borderColor
color $ui-text-color
.closeButton
position absolute
top 10px
right 10px
height 30px
width 0 25px
border $ui-border
border-radius 2px
color $ui-text-color
colorDefaultButton()
.control
padding 25px 15px 15px
text-align center
.control-input
display block
height 30px
width 240px
padding 0 5px
margin 0 auto 15px
border none
border-bottom solid 1px $border-color
border-radius 2px
outline none
vertical-align middle
font-size 18px
text-align center
&:disabled
background-color $ui-input--disabled-backgroundColor
&:focus, &:active
border-color $ui-active-color
.control-confirmButton
display block
height 30px
border none
border-radius 2px
padding 0 25px
margin 0 auto
colorPrimaryButton()
body[data-theme="dark"]
.root
modalDark()
.header
background-color $ui-dark-button--hover-backgroundColor
border-color $ui-dark-borderColor
color $ui-dark-text-color
.closeButton
border-color $ui-dark-borderColor
color $ui-dark-text-color
colorDarkDefaultButton()
.description
color $ui-inactive-text-color
.control-input
border-color $ui-dark-borderColor

View File

@@ -6,8 +6,8 @@ import consts from 'browser/lib/consts'
import dataApi from 'browser/main/lib/dataApi'
import store from 'browser/main/store'
const electron = require('electron')
const { shell } = electron
const { shell, remote } = require('electron')
const { dialog } = remote
import { SketchPicker } from 'react-color'
class UnstyledFolderItem extends React.Component {
@@ -292,17 +292,26 @@ class StorageItem extends React.Component {
}
handleUnlinkButtonClick (e) {
let { storage } = this.props
dataApi.removeStorage(storage.key)
.then(() => {
store.dispatch({
type: 'REMOVE_STORAGE',
storageKey: storage.key
let index = dialog.showMessageBox(remote.getCurrentWindow(), {
type: 'warning',
message: 'Unlink Storage',
detail: 'This work just detatches a storage from Boostnote. (Any data won\'t be deleted.)',
buttons: ['Confirm', 'Cancel']
})
if (index === 0) {
let { storage, dispatch } = this.props
dataApi.removeStorage(storage.key)
.then(() => {
dispatch({
type: 'REMOVE_STORAGE',
storageKey: storage.key
})
})
})
.catch((err) => {
throw err
})
.catch((err) => {
throw err
})
}
}
handleLabelClick (e) {

View File

@@ -0,0 +1,108 @@
import React, { PropTypes } from 'react'
import CSSModules from 'browser/lib/CSSModules'
import styles from './RenameFolderModal.styl'
import dataApi from 'browser/main/lib/dataApi'
import store from 'browser/main/store'
class RenameFolderModal extends React.Component {
constructor (props) {
super(props)
this.state = {
name: props.folder.name
}
}
componentDidMount () {
this.refs.name.focus()
this.refs.name.select()
}
handleCloseButtonClick (e) {
this.props.close()
}
handleChange (e) {
this.setState({
name: this.refs.name.value
})
}
handleKeyDown (e) {
if (e.keyCode === 27) {
this.props.close()
}
}
handleInputKeyDown (e) {
switch (e.keyCode) {
case 13:
this.confirm()
}
}
handleConfirmButtonClick (e) {
this.confirm()
}
confirm () {
if (this.state.name.trim().length > 0) {
let { storage, folder } = this.props
dataApi
.updateFolder(storage.key, folder.key, {
name: this.state.name,
color: folder.color
})
.then((data) => {
store.dispatch({
type: 'UPDATE_FOLDER',
storage: data.storage
})
this.props.close()
})
}
}
render () {
return (
<div styleName='root'
tabIndex='-1'
onKeyDown={(e) => this.handleKeyDown(e)}
>
<div styleName='header'>
<div styleName='title'>Rename Folder</div>
</div>
<button styleName='closeButton'
onClick={(e) => this.handleCloseButtonClick(e)}
>Close</button>
<div styleName='control'>
<input styleName='control-input'
placeholder='Folder Name'
ref='name'
value={this.state.name}
onChange={(e) => this.handleChange(e)}
onKeyDown={(e) => this.handleInputKeyDown(e)}
/>
<button styleName='control-confirmButton'
onClick={(e) => this.handleConfirmButtonClick(e)}
>
Confirm
</button>
</div>
</div>
)
}
}
RenameFolderModal.propTypes = {
storage: PropTypes.shape({
key: PropTypes.string
}),
folder: PropTypes.shape({
key: PropTypes.string,
name: PropTypes.string
})
}
export default CSSModules(RenameFolderModal, styles)

View File

@@ -0,0 +1,76 @@
.root
modal()
max-width 340px
overflow hidden
position relative
.header
height 50px
font-size 18px
line-height 50px
padding 0 15px
background-color $ui-backgroundColor
border-bottom solid 1px $ui-borderColor
color $ui-text-color
.closeButton
position absolute
top 10px
right 10px
height 30px
width 0 25px
border $ui-border
border-radius 2px
color $ui-text-color
colorDefaultButton()
.control
padding 25px 15px 15px
text-align center
.control-input
display block
height 30px
width 240px
padding 0 5px
margin 0 auto 15px
border none
border-bottom solid 1px $border-color
border-radius 2px
outline none
vertical-align middle
font-size 18px
text-align center
&:disabled
background-color $ui-input--disabled-backgroundColor
&:focus, &:active
border-color $ui-active-color
.control-confirmButton
display block
height 30px
border none
border-radius 2px
padding 0 25px
margin 0 auto
colorPrimaryButton()
body[data-theme="dark"]
.root
modalDark()
.header
background-color $ui-dark-button--hover-backgroundColor
border-color $ui-dark-borderColor
color $ui-dark-text-color
.closeButton
border-color $ui-dark-borderColor
color $ui-dark-text-color
colorDarkDefaultButton()
.description
color $ui-inactive-text-color
.control-input
border-color $ui-dark-borderColor