mirror of
https://github.com/BoostIo/Boostnote
synced 2025-12-13 09:46:22 +00:00
add initialization modal & migration func
This commit is contained in:
@@ -10,6 +10,8 @@ import dataApi from 'browser/main/lib/dataApi'
|
|||||||
import StatusBar from './StatusBar'
|
import StatusBar from './StatusBar'
|
||||||
import _ from 'lodash'
|
import _ from 'lodash'
|
||||||
import ConfigManager from 'browser/main/lib/ConfigManager'
|
import ConfigManager from 'browser/main/lib/ConfigManager'
|
||||||
|
import modal from 'browser/main/lib/modal'
|
||||||
|
import InitModal from 'browser/main/modals/InitModal'
|
||||||
|
|
||||||
class Main extends React.Component {
|
class Main extends React.Component {
|
||||||
constructor (props) {
|
constructor (props) {
|
||||||
@@ -34,6 +36,10 @@ class Main extends React.Component {
|
|||||||
storages: data.storages,
|
storages: data.storages,
|
||||||
notes: data.notes
|
notes: data.notes
|
||||||
})
|
})
|
||||||
|
|
||||||
|
if (data.storages.length < 1) {
|
||||||
|
modal.open(InitModal)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ const defaultConfig = {
|
|||||||
fontFamily: 'Monaco, Consolas',
|
fontFamily: 'Monaco, Consolas',
|
||||||
indentType: 'space',
|
indentType: 'space',
|
||||||
indentSize: '4',
|
indentSize: '4',
|
||||||
switchPreview: 'RIGHTCLICK'
|
switchPreview: 'BLUR' // Available value: RIGHTCLICK, BLUR
|
||||||
},
|
},
|
||||||
preview: {
|
preview: {
|
||||||
fontSize: '14',
|
fontSize: '14',
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ const CSON = require('season')
|
|||||||
const path = require('path')
|
const path = require('path')
|
||||||
const _ = require('lodash')
|
const _ = require('lodash')
|
||||||
const sander = require('sander')
|
const sander = require('sander')
|
||||||
|
const consts = require('browser/lib/consts')
|
||||||
|
|
||||||
let storages = []
|
let storages = []
|
||||||
let notes = []
|
let notes = []
|
||||||
@@ -67,6 +68,7 @@ class Storage {
|
|||||||
let initialStorage = {
|
let initialStorage = {
|
||||||
folders: []
|
folders: []
|
||||||
}
|
}
|
||||||
|
|
||||||
return sander.writeFile(path.join(this.cache.path, 'boostnote.json'), JSON.stringify(initialStorage))
|
return sander.writeFile(path.join(this.cache.path, 'boostnote.json'), JSON.stringify(initialStorage))
|
||||||
} else throw err
|
} else throw err
|
||||||
})
|
})
|
||||||
@@ -127,10 +129,11 @@ function init () {
|
|||||||
let caches
|
let caches
|
||||||
try {
|
try {
|
||||||
caches = JSON.parse(localStorage.getItem('storages'))
|
caches = JSON.parse(localStorage.getItem('storages'))
|
||||||
|
if (!_.isArray(caches)) throw new Error('Cached data is not valid.')
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(e)
|
console.error(e)
|
||||||
caches = []
|
caches = []
|
||||||
localStorage.getItem('storages', JSON.stringify(caches))
|
localStorage.setItem('storages', JSON.stringify(caches))
|
||||||
}
|
}
|
||||||
|
|
||||||
return caches.map((cache) => {
|
return caches.map((cache) => {
|
||||||
@@ -236,15 +239,14 @@ function addStorage (input) {
|
|||||||
note.folder = folder.key
|
note.folder = folder.key
|
||||||
_notes.push(Note.forge(note))
|
_notes.push(Note.forge(note))
|
||||||
})
|
})
|
||||||
|
|
||||||
notes = notes.slice().concat(_notes)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
return Promise.all(notes)
|
return Promise.all(_notes)
|
||||||
.then((notes) => {
|
.then((_notes) => {
|
||||||
|
notes = notes.concat(_notes)
|
||||||
let data = {
|
let data = {
|
||||||
storage: storage,
|
storage: storage,
|
||||||
notes: notes
|
notes: _notes
|
||||||
}
|
}
|
||||||
return isFolderRemoved
|
return isFolderRemoved
|
||||||
? storage.saveData().then(() => data)
|
? storage.saveData().then(() => data)
|
||||||
@@ -255,6 +257,17 @@ function addStorage (input) {
|
|||||||
storages = storages.filter((storage) => storage.key !== data.storage.key)
|
storages = storages.filter((storage) => storage.key !== data.storage.key)
|
||||||
storages.push(data.storage)
|
storages.push(data.storage)
|
||||||
_saveCaches()
|
_saveCaches()
|
||||||
|
|
||||||
|
if (data.storage.data.folders.length < 1) {
|
||||||
|
return createFolder(data.storage.key, {
|
||||||
|
name: 'Default',
|
||||||
|
color: consts.FOLDER_COLORS[0]
|
||||||
|
}).then(() => data)
|
||||||
|
}
|
||||||
|
|
||||||
|
return data
|
||||||
|
})
|
||||||
|
.then((data) => {
|
||||||
return {
|
return {
|
||||||
storage: data.storage.toJSON(),
|
storage: data.storage.toJSON(),
|
||||||
notes: data.notes.map((note) => note.toJSON())
|
notes: data.notes.map((note) => note.toJSON())
|
||||||
@@ -269,6 +282,88 @@ function removeStorage (key) {
|
|||||||
return Promise.resolve(true)
|
return Promise.resolve(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function migrateFromV5 (key, data) {
|
||||||
|
let oldFolders = data.folders
|
||||||
|
let oldArticles = data.articles
|
||||||
|
let storage = _.find(storages, {key: key})
|
||||||
|
if (storage == null) throw new Error('Storage doesn\'t exist.')
|
||||||
|
|
||||||
|
let migrateFolders = oldFolders.map((oldFolder) => {
|
||||||
|
let folderKey = keygen()
|
||||||
|
while (storage.data.folders.some((folder) => folder.key === folderKey)) {
|
||||||
|
folderKey = keygen()
|
||||||
|
}
|
||||||
|
let newFolder = {
|
||||||
|
key: folderKey,
|
||||||
|
name: oldFolder.name,
|
||||||
|
color: consts.FOLDER_COLORS[Math.floor(Math.random() * 7) % 7]
|
||||||
|
}
|
||||||
|
storage.data.folders.push(newFolder)
|
||||||
|
let articles = oldArticles.filter((article) => article.FolderKey === oldFolder.key)
|
||||||
|
let folderNotes = []
|
||||||
|
articles.forEach((article) => {
|
||||||
|
let noteKey = keygen()
|
||||||
|
while (notes.some((note) => note.storage === key && note.folder === folderKey && note.key === noteKey)) {
|
||||||
|
key = keygen()
|
||||||
|
}
|
||||||
|
if (article.mode === 'markdown') {
|
||||||
|
let newNote = new Note({
|
||||||
|
tags: article.tags,
|
||||||
|
createdAt: article.createdAt,
|
||||||
|
updatedAt: article.updatedAt,
|
||||||
|
folder: folderKey,
|
||||||
|
storage: key,
|
||||||
|
type: 'MARKDOWN_NOTE',
|
||||||
|
isStarred: false,
|
||||||
|
title: article.title,
|
||||||
|
content: '# ' + article.title + '\n\n' + article.content,
|
||||||
|
key: noteKey
|
||||||
|
})
|
||||||
|
notes.push(newNote)
|
||||||
|
folderNotes.push(newNote)
|
||||||
|
} else {
|
||||||
|
let newNote = new Note({
|
||||||
|
tags: article.tags,
|
||||||
|
createdAt: article.createdAt,
|
||||||
|
updatedAt: article.updatedAt,
|
||||||
|
folder: folderKey,
|
||||||
|
storage: key,
|
||||||
|
type: 'SNIPPET_NOTE',
|
||||||
|
isStarred: false,
|
||||||
|
title: article.title,
|
||||||
|
description: article.title,
|
||||||
|
key: noteKey,
|
||||||
|
snippets: [{
|
||||||
|
name: article.mode,
|
||||||
|
mode: article.mode,
|
||||||
|
content: article.content
|
||||||
|
}]
|
||||||
|
})
|
||||||
|
notes.push(newNote)
|
||||||
|
folderNotes.push(newNote)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return sander
|
||||||
|
.writeFile(path.join(storage.cache.path, folderKey, 'data.json'), JSON.stringify({
|
||||||
|
notes: folderNotes.map((note) => {
|
||||||
|
let json = note.toJSON()
|
||||||
|
delete json.storage
|
||||||
|
return json
|
||||||
|
})
|
||||||
|
}))
|
||||||
|
})
|
||||||
|
return Promise.all(migrateFolders)
|
||||||
|
.then(() => storage.saveData())
|
||||||
|
.then(() => {
|
||||||
|
return {
|
||||||
|
storage: storage.toJSON(),
|
||||||
|
notes: notes.filter((note) => note.storage === key)
|
||||||
|
.map((note) => note.toJSON())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
function createFolder (key, input) {
|
function createFolder (key, input) {
|
||||||
let storage = _.find(storages, {key: key})
|
let storage = _.find(storages, {key: key})
|
||||||
if (storage == null) throw new Error('Storage doesn\'t exist.')
|
if (storage == null) throw new Error('Storage doesn\'t exist.')
|
||||||
@@ -441,5 +536,6 @@ export default {
|
|||||||
createSnippetNote,
|
createSnippetNote,
|
||||||
updateNote,
|
updateNote,
|
||||||
removeNote,
|
removeNote,
|
||||||
moveNote
|
moveNote,
|
||||||
|
migrateFromV5
|
||||||
}
|
}
|
||||||
|
|||||||
212
browser/main/modals/InitModal.js
Normal file
212
browser/main/modals/InitModal.js
Normal file
@@ -0,0 +1,212 @@
|
|||||||
|
import React, { PropTypes } from 'react'
|
||||||
|
import CSSModules from 'browser/lib/CSSModules'
|
||||||
|
import styles from './InitModal.styl'
|
||||||
|
import dataApi from 'browser/main/lib/dataApi'
|
||||||
|
import store from 'browser/main/store'
|
||||||
|
import { hashHistory } from 'react-router'
|
||||||
|
import _ from 'lodash'
|
||||||
|
|
||||||
|
const CSON = require('season')
|
||||||
|
const path = require('path')
|
||||||
|
const electron = require('electron')
|
||||||
|
const { remote } = electron
|
||||||
|
|
||||||
|
function browseFolder () {
|
||||||
|
let dialog = remote.dialog
|
||||||
|
|
||||||
|
let defaultPath = remote.app.getPath('home')
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
dialog.showOpenDialog({
|
||||||
|
title: 'Select Directory',
|
||||||
|
defaultPath,
|
||||||
|
properties: ['openDirectory', 'createDirectory']
|
||||||
|
}, function (targetPaths) {
|
||||||
|
if (targetPaths == null) return resolve('')
|
||||||
|
resolve(targetPaths[0])
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
class InitModal extends React.Component {
|
||||||
|
constructor (props) {
|
||||||
|
super(props)
|
||||||
|
|
||||||
|
this.state = {
|
||||||
|
path: path.join(remote.app.getPath('home'), 'Boostnote'),
|
||||||
|
migrationRequested: true,
|
||||||
|
isLoading: true,
|
||||||
|
data: null,
|
||||||
|
legacyStorageExists: false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
handleCloseButtonClick (e) {
|
||||||
|
this.props.close()
|
||||||
|
}
|
||||||
|
|
||||||
|
handlePathChange (e) {
|
||||||
|
this.setState({
|
||||||
|
path: e.target.value
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidMount () {
|
||||||
|
let data = null
|
||||||
|
try {
|
||||||
|
data = CSON.readFileSync(path.join(remote.app.getPath('userData'), 'local.json'))
|
||||||
|
} catch (err) {
|
||||||
|
if (err.code === 'ENOENT') {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
console.error(err)
|
||||||
|
}
|
||||||
|
let newState = {
|
||||||
|
isLoading: false
|
||||||
|
}
|
||||||
|
if (data != null) {
|
||||||
|
newState.legacyStorageExists = true
|
||||||
|
newState.data = data
|
||||||
|
}
|
||||||
|
this.setState(newState)
|
||||||
|
}
|
||||||
|
|
||||||
|
handlePathBrowseButtonClick (e) {
|
||||||
|
browseFolder()
|
||||||
|
.then((targetPath) => {
|
||||||
|
if (targetPath.length > 0) {
|
||||||
|
this.setState({
|
||||||
|
path: targetPath
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
console.error('BrowseFAILED')
|
||||||
|
console.error(err)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
handleSubmitButtonClick (e) {
|
||||||
|
dataApi
|
||||||
|
.addStorage({
|
||||||
|
name: 'My Storage',
|
||||||
|
path: this.state.path
|
||||||
|
})
|
||||||
|
.then((data) => {
|
||||||
|
if (this.state.migrationRequested && _.isObject(this.state.data) && _.isArray(this.state.data.folders) && _.isArray(this.state.data.articles)) {
|
||||||
|
return dataApi.migrateFromV5(data.storage.key, this.state.data)
|
||||||
|
}
|
||||||
|
return data
|
||||||
|
})
|
||||||
|
.then((data) => {
|
||||||
|
store.dispatch({
|
||||||
|
type: 'ADD_STORAGE',
|
||||||
|
storage: data.storage,
|
||||||
|
notes: data.notes
|
||||||
|
})
|
||||||
|
|
||||||
|
let defaultMarkdownNote = dataApi
|
||||||
|
.createMarkdownNote(data.storage.key, data.storage.folders[0].key, {
|
||||||
|
title: 'Welcome to Boostnote :)',
|
||||||
|
content: '# Welcome to Boostnote :)\nThis is a markdown note.\n\nClick to edit this note.'
|
||||||
|
})
|
||||||
|
.then((note) => {
|
||||||
|
store.dispatch({
|
||||||
|
type: 'CREATE_NOTE',
|
||||||
|
note: note
|
||||||
|
})
|
||||||
|
})
|
||||||
|
let defaultSnippetNote = dataApi
|
||||||
|
.createSnippetNote(data.storage.key, data.storage.folders[0].key, {
|
||||||
|
title: 'Snippet note example',
|
||||||
|
description: 'Snippet note example\nYou can store a series of snippet as a single note like Gist.',
|
||||||
|
snippets: [
|
||||||
|
{
|
||||||
|
name: 'example.html',
|
||||||
|
mode: 'html',
|
||||||
|
content: '<html>\n<body>\n<h1 id=\'hello\'>Hello World</h1>\n</body>\n</html>'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'example.js',
|
||||||
|
mode: 'javascript',
|
||||||
|
content: 'var html = document.getElementById(\'hello\').innerHTML\n\nconsole.log(html)'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
})
|
||||||
|
.then((note) => {
|
||||||
|
store.dispatch({
|
||||||
|
type: 'CREATE_NOTE',
|
||||||
|
note: note
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
return Promise.resolve(defaultSnippetNote)
|
||||||
|
.then(defaultMarkdownNote)
|
||||||
|
.then(() => data.storage)
|
||||||
|
})
|
||||||
|
.then((storage) => {
|
||||||
|
hashHistory.push('/storages/' + storage.key)
|
||||||
|
this.props.close()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
handleMigrationRequestedChange (e) {
|
||||||
|
this.setState({
|
||||||
|
migrationRequested: e.target.checked
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
render () {
|
||||||
|
if (this.state.isLoading) {
|
||||||
|
return <div styleName='root--loading'>
|
||||||
|
<i styleName='spinner' className='fa fa-spin fa-spinner'/>
|
||||||
|
<div styleName='loadingMessage'>Preparing initialization...</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<div styleName='root'>
|
||||||
|
|
||||||
|
<div styleName='header'>
|
||||||
|
<div styleName='header-title'>Initialize Storage</div>
|
||||||
|
</div>
|
||||||
|
<button styleName='closeButton'
|
||||||
|
onClick={(e) => this.handleCloseButtonClick(e)}
|
||||||
|
>Close</button>
|
||||||
|
<div styleName='body'>
|
||||||
|
<div styleName='body-welcome'>
|
||||||
|
Welcome you!
|
||||||
|
</div>
|
||||||
|
<div styleName='body-description'>
|
||||||
|
Boostnote will use this directory as a default storage.
|
||||||
|
</div>
|
||||||
|
<div styleName='body-path'>
|
||||||
|
<input styleName='body-path-input'
|
||||||
|
placeholder='Select Folder'
|
||||||
|
value={this.state.path}
|
||||||
|
onChange={(e) => this.handlePathChange(e)}
|
||||||
|
/>
|
||||||
|
<button styleName='body-path-button'
|
||||||
|
onClick={(e) => this.handlePathBrowseButtonClick(e)}
|
||||||
|
>
|
||||||
|
...
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div styleName='body-migration'>
|
||||||
|
<label><input type='checkbox' checked={this.state.migrationRequested} onChange={(e) => this.handleMigrationRequestedChange(e)}/> Migrate old data from the legacy app v0.5</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div styleName='body-control'>
|
||||||
|
<button styleName='body-control-createButton'
|
||||||
|
onClick={(e) => this.handleSubmitButtonClick(e)}
|
||||||
|
>Let's Go!</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
InitModal.propTypes = {
|
||||||
|
}
|
||||||
|
|
||||||
|
export default CSSModules(InitModal, styles)
|
||||||
86
browser/main/modals/InitModal.styl
Normal file
86
browser/main/modals/InitModal.styl
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
.root
|
||||||
|
modal()
|
||||||
|
max-width 540px
|
||||||
|
overflow hidden
|
||||||
|
position relative
|
||||||
|
.root--loading
|
||||||
|
@extend .root
|
||||||
|
text-align center
|
||||||
|
.spinner
|
||||||
|
font-size 100px
|
||||||
|
margin 35px auto
|
||||||
|
color $ui-text-color
|
||||||
|
.loadingMessage
|
||||||
|
color $ui-text-color
|
||||||
|
margin 15px auto 35px
|
||||||
|
.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()
|
||||||
|
|
||||||
|
.body
|
||||||
|
padding 30px
|
||||||
|
|
||||||
|
.body-welcome
|
||||||
|
text-align center
|
||||||
|
margin-bottom 25px
|
||||||
|
font-size 32px
|
||||||
|
color $ui-text-color
|
||||||
|
|
||||||
|
.body-description
|
||||||
|
font-size 14px
|
||||||
|
color $ui-text-color
|
||||||
|
text-align center
|
||||||
|
margin-bottom 25px
|
||||||
|
|
||||||
|
.body-path
|
||||||
|
margin 0 auto 25px
|
||||||
|
width 280px
|
||||||
|
|
||||||
|
.body-path-input
|
||||||
|
height 30px
|
||||||
|
vertical-align middle
|
||||||
|
width 250px
|
||||||
|
font-size 12px
|
||||||
|
border-style solid
|
||||||
|
border-width 1px 0 1px 1px
|
||||||
|
border-color $border-color
|
||||||
|
border-top-left-radius 2px
|
||||||
|
border-bottom-left-radius 2px
|
||||||
|
padding 0 5px
|
||||||
|
|
||||||
|
.body-path-button
|
||||||
|
height 30px
|
||||||
|
border none
|
||||||
|
border-top-right-radius 2px
|
||||||
|
border-bottom-right-radius 2px
|
||||||
|
colorPrimaryButton()
|
||||||
|
vertical-align middle
|
||||||
|
.body-migration
|
||||||
|
margin 0 auto 25px
|
||||||
|
text-align center
|
||||||
|
|
||||||
|
.body-control
|
||||||
|
text-align center
|
||||||
|
|
||||||
|
.body-control-createButton
|
||||||
|
colorPrimaryButton()
|
||||||
|
border none
|
||||||
|
border-radius 2px
|
||||||
|
height 40px
|
||||||
|
padding 0 25px
|
||||||
@@ -1,193 +0,0 @@
|
|||||||
import React, { PropTypes } from 'react'
|
|
||||||
import CSSModules from 'browser/lib/CSSModules'
|
|
||||||
import styles from './NewRepositoryModal.styl'
|
|
||||||
import Repository from 'browser/lib/Repository'
|
|
||||||
import store from 'browser/main/store'
|
|
||||||
|
|
||||||
const electron = require('electron')
|
|
||||||
const remote = electron.remote
|
|
||||||
|
|
||||||
function browseFolder () {
|
|
||||||
let dialog = remote.dialog
|
|
||||||
|
|
||||||
let defaultPath = remote.app.getPath('home')
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
dialog.showOpenDialog({
|
|
||||||
title: 'Select Directory',
|
|
||||||
defaultPath,
|
|
||||||
properties: ['openDirectory', 'createDirectory']
|
|
||||||
}, function (targetPaths) {
|
|
||||||
if (targetPaths == null) return resolve('')
|
|
||||||
resolve(targetPaths[0])
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
class NewRepositoryModal extends React.Component {
|
|
||||||
constructor (props) {
|
|
||||||
super(props)
|
|
||||||
|
|
||||||
this.state = {
|
|
||||||
name: '',
|
|
||||||
path: '',
|
|
||||||
isPathSectionFocused: false,
|
|
||||||
error: null,
|
|
||||||
isBrowsingPath: false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
componentDidMount () {
|
|
||||||
this.refs.nameInput.focus()
|
|
||||||
}
|
|
||||||
|
|
||||||
handleCloseButtonClick (e) {
|
|
||||||
this.props.close()
|
|
||||||
}
|
|
||||||
|
|
||||||
handlePathFocus (e) {
|
|
||||||
this.setState({
|
|
||||||
isPathSectionFocused: true
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
handlePathBlur (e) {
|
|
||||||
if (e.relatedTarget !== this.refs.pathInput && e.relatedTarget !== this.refs.browseButton) {
|
|
||||||
this.setState({
|
|
||||||
isPathSectionFocused: false
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
handleBrowseButtonClick (e) {
|
|
||||||
this.setState({
|
|
||||||
isBrowsingPath: true
|
|
||||||
}, () => {
|
|
||||||
browseFolder()
|
|
||||||
.then((targetPath) => {
|
|
||||||
this.setState({
|
|
||||||
path: targetPath,
|
|
||||||
isBrowsingPath: false
|
|
||||||
})
|
|
||||||
})
|
|
||||||
.catch((err) => {
|
|
||||||
console.error('BrowseFAILED')
|
|
||||||
console.error(err)
|
|
||||||
this.setState({
|
|
||||||
isBrowsingPath: false
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
handleConfirmButtonClick (e) {
|
|
||||||
let targetPath = this.state.path
|
|
||||||
let name = this.state.name
|
|
||||||
|
|
||||||
let repository = new Repository({
|
|
||||||
name: name,
|
|
||||||
path: targetPath
|
|
||||||
})
|
|
||||||
|
|
||||||
repository
|
|
||||||
.mount()
|
|
||||||
.then(() => repository.load())
|
|
||||||
.then((data) => {
|
|
||||||
store.dispatch({
|
|
||||||
type: 'ADD_REPOSITORY',
|
|
||||||
repository: data
|
|
||||||
})
|
|
||||||
this.props.close()
|
|
||||||
})
|
|
||||||
.catch((err) => {
|
|
||||||
console.error(err)
|
|
||||||
this.setState({
|
|
||||||
error: err.message
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
handleChange (e) {
|
|
||||||
let name = this.refs.nameInput.value
|
|
||||||
let path = this.refs.pathInput.value
|
|
||||||
this.setState({
|
|
||||||
name,
|
|
||||||
path
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
render () {
|
|
||||||
return (
|
|
||||||
<div className='NewRepositoryModal'
|
|
||||||
styleName='root'
|
|
||||||
>
|
|
||||||
<div styleName='header'>
|
|
||||||
<div styleName='header-title'>New Repository</div>
|
|
||||||
<button styleName='header-closeButton'
|
|
||||||
onClick={(e) => this.handleCloseButtonClick(e)}
|
|
||||||
>
|
|
||||||
<i className='fa fa-times'/>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<div styleName='body'>
|
|
||||||
<div styleName='body-section'>
|
|
||||||
<div styleName='body-section-label'>Repository Name</div>
|
|
||||||
<input styleName='body-section-input'
|
|
||||||
ref='nameInput'
|
|
||||||
value={this.state.name}
|
|
||||||
onChange={(e) => this.handleChange(e)}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div styleName='body-section'>
|
|
||||||
<div styleName='body-section-label'>Repository Path</div>
|
|
||||||
<div styleName={!this.state.isPathSectionFocused ? 'body-section-path' : 'body-section-path--focus'}>
|
|
||||||
<input styleName='body-section-path-input'
|
|
||||||
ref='pathInput'
|
|
||||||
value={this.state.path}
|
|
||||||
style={styles.body_section_path_input}
|
|
||||||
onFocus={(e) => this.handlePathFocus(e)}
|
|
||||||
onBlur={(e) => this.handlePathBlur(e)}
|
|
||||||
disabled={this.state.isBrowsingPath}
|
|
||||||
onChange={(e) => this.handleChange(e)}
|
|
||||||
/>
|
|
||||||
<button styleName='body-section-path-button'
|
|
||||||
onClick={(e) => this.handleBrowseButtonClick(e)}
|
|
||||||
onFocus={(e) => this.handlePathFocus(e)}
|
|
||||||
onBlur={(e) => this.handlePathBlur(e)}
|
|
||||||
disabled={this.state.isBrowsingPath}
|
|
||||||
>
|
|
||||||
...
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{
|
|
||||||
this.state.error != null && (
|
|
||||||
<div styleName='body-error'>
|
|
||||||
{this.state.error}
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div styleName='footer'>
|
|
||||||
<button styleName='footer-cancelButton'
|
|
||||||
onClick={(e) => this.handleCloseButtonClick(e)}
|
|
||||||
>
|
|
||||||
<i className='fa fa-times'/> Cancel
|
|
||||||
</button>
|
|
||||||
<button styleName='footer-confirmButton'
|
|
||||||
onClick={(e) => this.handleConfirmButtonClick(e)}
|
|
||||||
>
|
|
||||||
<i className='fa fa-check'/> Confirm
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
NewRepositoryModal.propTypes = {
|
|
||||||
close: PropTypes.func
|
|
||||||
}
|
|
||||||
|
|
||||||
export default CSSModules(NewRepositoryModal, styles)
|
|
||||||
@@ -1,133 +0,0 @@
|
|||||||
$modal-width = 550px
|
|
||||||
$modal-header-color = #F2F2F2
|
|
||||||
$body-button-background-color = #2BAC8F
|
|
||||||
|
|
||||||
.root
|
|
||||||
modal()
|
|
||||||
width $modal-width
|
|
||||||
height 310px
|
|
||||||
|
|
||||||
.header
|
|
||||||
height 50px
|
|
||||||
background-color $modal-header-color
|
|
||||||
|
|
||||||
.header-title
|
|
||||||
font-size 24px
|
|
||||||
line-height 50px
|
|
||||||
padding-left 15px
|
|
||||||
|
|
||||||
.header-closeButton
|
|
||||||
position absolute
|
|
||||||
top 8.5px
|
|
||||||
right 8.5px
|
|
||||||
width 33px
|
|
||||||
height 33px
|
|
||||||
font-size 20px
|
|
||||||
background-color transparent
|
|
||||||
border none
|
|
||||||
color #AAA
|
|
||||||
&:hover
|
|
||||||
color #4D4D4D
|
|
||||||
|
|
||||||
.body
|
|
||||||
absolute left right
|
|
||||||
top 50px
|
|
||||||
bottom 50px
|
|
||||||
padding 35px 0
|
|
||||||
|
|
||||||
.body-section
|
|
||||||
height 33px
|
|
||||||
margin-bottom 15px
|
|
||||||
position relative
|
|
||||||
|
|
||||||
.body-section-label
|
|
||||||
absolute top bottom left
|
|
||||||
width 175px
|
|
||||||
text-align right
|
|
||||||
line-height 33px
|
|
||||||
padding-right 15px
|
|
||||||
|
|
||||||
.body-section-input
|
|
||||||
absolute top bottom
|
|
||||||
left 175px
|
|
||||||
width 315px
|
|
||||||
padding 0 10px
|
|
||||||
border $default-border
|
|
||||||
border-radius 5px
|
|
||||||
outline none
|
|
||||||
&:focus
|
|
||||||
border $active-border
|
|
||||||
|
|
||||||
.body-section-path
|
|
||||||
absolute top bottom
|
|
||||||
left 175px
|
|
||||||
width 315px
|
|
||||||
padding 0 10px
|
|
||||||
border $default-border
|
|
||||||
border-radius 5px
|
|
||||||
outline none
|
|
||||||
overflow hidden
|
|
||||||
|
|
||||||
.body-section-path--focus
|
|
||||||
@extend .body-section-path
|
|
||||||
border $active-border
|
|
||||||
|
|
||||||
.body-section-path-input
|
|
||||||
absolute top left bottom
|
|
||||||
width 265px
|
|
||||||
border none
|
|
||||||
outline none
|
|
||||||
padding 0 10px
|
|
||||||
|
|
||||||
.body-section-path-button
|
|
||||||
absolute top right bottom
|
|
||||||
width 50px
|
|
||||||
border none
|
|
||||||
border-left $default-border
|
|
||||||
outline none
|
|
||||||
color white
|
|
||||||
background-color $body-button-background-color
|
|
||||||
transition 0.15s
|
|
||||||
&:hover
|
|
||||||
background-color lighten($body-button-background-color, 7%)
|
|
||||||
&:disabled
|
|
||||||
background-color lighten(gray, 15%)
|
|
||||||
|
|
||||||
.body-error
|
|
||||||
height 33px
|
|
||||||
margin 35px auto 0
|
|
||||||
width 320px
|
|
||||||
border-radius 5px
|
|
||||||
text-align center
|
|
||||||
line-height 33px
|
|
||||||
color $danger-color
|
|
||||||
background-color $danger-lighten-color
|
|
||||||
|
|
||||||
.footer
|
|
||||||
absolute left right bottom
|
|
||||||
height 50px
|
|
||||||
|
|
||||||
.footer-cancelButton
|
|
||||||
position absolute
|
|
||||||
height 33px
|
|
||||||
right 85.5px
|
|
||||||
width 72px
|
|
||||||
top 8.5px
|
|
||||||
border-radius 5px
|
|
||||||
border $default-border
|
|
||||||
background-color darken(white, 0.03)
|
|
||||||
&:hover
|
|
||||||
background-color white
|
|
||||||
|
|
||||||
.footer-confirmButton
|
|
||||||
position absolute
|
|
||||||
height 33px
|
|
||||||
right 8.5px
|
|
||||||
width 72px
|
|
||||||
top 8.5px
|
|
||||||
color white
|
|
||||||
border-radius 5px
|
|
||||||
border none
|
|
||||||
background-color #2BAC8F
|
|
||||||
&:hover
|
|
||||||
background-color lighten($body-button-background-color, 7%)
|
|
||||||
@@ -208,7 +208,7 @@ class StorageItem extends React.Component {
|
|||||||
let { storage } = this.props
|
let { storage } = this.props
|
||||||
let input = {
|
let input = {
|
||||||
name: 'Untitled',
|
name: 'Untitled',
|
||||||
color: consts.FOLDER_COLORS[Math.floor(Math.random() * 7)]
|
color: consts.FOLDER_COLORS[Math.floor(Math.random() * 7) % 7]
|
||||||
}
|
}
|
||||||
|
|
||||||
dataApi.createFolder(storage.key, input)
|
dataApi.createFolder(storage.key, input)
|
||||||
|
|||||||
@@ -44,10 +44,7 @@ function notes (state = [], action) {
|
|||||||
return action.notes
|
return action.notes
|
||||||
case 'ADD_STORAGE':
|
case 'ADD_STORAGE':
|
||||||
{
|
{
|
||||||
let notes = state.slice()
|
let notes = state.concat(action.notes)
|
||||||
|
|
||||||
notes.concat(action.notes)
|
|
||||||
|
|
||||||
return notes
|
return notes
|
||||||
}
|
}
|
||||||
case 'REMOVE_STORAGE':
|
case 'REMOVE_STORAGE':
|
||||||
|
|||||||
Reference in New Issue
Block a user