From aefb84df3b030bb12e187b140c9bcea88dabefaa Mon Sep 17 00:00:00 2001 From: Dick Choi Date: Tue, 30 Aug 2016 02:33:28 +0900 Subject: [PATCH] Mutable INIT_ALL, NOTE_MOVE, NOTE_UPDATE(create/update) done --- browser/finder/NoteItem.js | 2 +- browser/finder/NoteList.js | 2 +- browser/lib/Mutable.js | 87 ++++++ browser/main/Detail/FolderSelect.js | 4 +- browser/main/Detail/MarkdownNoteDetail.js | 41 ++- browser/main/Detail/SnippetNoteDetail.js | 41 ++- browser/main/Detail/index.js | 14 +- browser/main/Main.js | 13 +- browser/main/NoteList/index.js | 36 +-- browser/main/SideNav/index.js | 5 +- browser/main/TopBar/index.js | 13 +- browser/main/modals/NewNoteModal.js | 18 +- browser/main/store.js | 325 +++++++++++++++++----- 13 files changed, 449 insertions(+), 152 deletions(-) create mode 100644 browser/lib/Mutable.js diff --git a/browser/finder/NoteItem.js b/browser/finder/NoteItem.js index 9dd183ee..bc3ae253 100644 --- a/browser/finder/NoteItem.js +++ b/browser/finder/NoteItem.js @@ -34,7 +34,7 @@ class NoteItem extends React.Component { ? 'root--active' : 'root' } - key={note.uniqueKey} + key={note.storage + '-' + note.key} onClick={(e) => this.handleClick(e)} >
diff --git a/browser/finder/NoteList.js b/browser/finder/NoteList.js index 986efcf7..7087eb53 100644 --- a/browser/finder/NoteList.js +++ b/browser/finder/NoteList.js @@ -64,7 +64,7 @@ class NoteList extends React.Component { return ( { + data.storageMap.forEach((storage, index) => { storage.folders.forEach((folder) => { options.push({ storage: storage, diff --git a/browser/main/Detail/MarkdownNoteDetail.js b/browser/main/Detail/MarkdownNoteDetail.js index 5c37074d..b156f0fa 100644 --- a/browser/main/Detail/MarkdownNoteDetail.js +++ b/browser/main/Detail/MarkdownNoteDetail.js @@ -35,6 +35,7 @@ class MarkdownNoteDetail extends React.Component { componentWillReceiveProps (nextProps) { if (nextProps.note.key !== this.props.note.key && !this.isMovingNote) { + if (this.saveQueue != null) this.saveNow() this.setState({ note: Object.assign({}, nextProps.note), isDeleting: false @@ -45,6 +46,10 @@ class MarkdownNoteDetail extends React.Component { } } + componentWillUnmount () { + if (this.saveQueue != null) this.saveNow() + } + findTitle (value) { let splitted = value.split('\n') let title = null @@ -91,17 +96,25 @@ class MarkdownNoteDetail extends React.Component { save () { clearTimeout(this.saveQueue) this.saveQueue = setTimeout(() => { - let { note, dispatch } = this.props - dispatch({ - type: 'UPDATE_NOTE', - note: this.state.note - }) - - dataApi - .updateNote(note.storage, note.folder, note.key, this.state.note) + this.saveNow() }, 1000) } + saveNow () { + let { note, dispatch } = this.props + clearTimeout(this.saveQueue) + this.saveQueue = null + + dataApi + .updateNote(note.storage, note.key, this.state.note) + .then((note) => { + dispatch({ + type: 'UPDATE_NOTE', + note: note + }) + }) + } + handleFolderChange (e) { let { note } = this.state let value = this.refs.folder.value @@ -110,7 +123,7 @@ class MarkdownNoteDetail extends React.Component { let newFolderKey = splitted.shift() dataApi - .moveNote(note.storage, note.folder, note.key, newStorageKey, newFolderKey) + .moveNote(note.storage, note.key, newStorageKey, newFolderKey) .then((newNote) => { this.setState({ isMovingNote: true, @@ -119,13 +132,13 @@ class MarkdownNoteDetail extends React.Component { let { dispatch, location } = this.props dispatch({ type: 'MOVE_NOTE', - note: note, - newNote: newNote + originNote: note, + note: newNote }) hashHistory.replace({ pathname: location.pathname, query: { - key: newNote.uniqueKey + key: newNote.storage + '-' + newNote.key } }) this.setState({ @@ -210,7 +223,7 @@ class MarkdownNoteDetail extends React.Component { } render () { - let { storages, config } = this.props + let { data, config } = this.props let { note } = this.state return ( @@ -243,7 +256,7 @@ class MarkdownNoteDetail extends React.Component { this.handleFolderChange(e)} />
diff --git a/browser/main/Detail/SnippetNoteDetail.js b/browser/main/Detail/SnippetNoteDetail.js index 2bfc5714..6cb80bb7 100644 --- a/browser/main/Detail/SnippetNoteDetail.js +++ b/browser/main/Detail/SnippetNoteDetail.js @@ -48,6 +48,7 @@ class SnippetNoteDetail extends React.Component { componentWillReceiveProps (nextProps) { if (nextProps.note.key !== this.props.note.key) { + if (this.saveQueue != null) this.saveNow() let nextNote = Object.assign({ description: '' }, nextProps.note, { @@ -67,6 +68,10 @@ class SnippetNoteDetail extends React.Component { } } + componentWillUnmount () { + if (this.saveQueue != null) this.saveNow() + } + findTitle (value) { let splitted = value.split('\n') let title = null @@ -113,17 +118,25 @@ class SnippetNoteDetail extends React.Component { save () { clearTimeout(this.saveQueue) this.saveQueue = setTimeout(() => { - let { note, dispatch } = this.props - dispatch({ - type: 'UPDATE_NOTE', - note: this.state.note - }) - - dataApi - .updateNote(note.storage, note.folder, note.key, this.state.note) + this.saveNow() }, 1000) } + saveNow () { + let { note, dispatch } = this.props + clearTimeout(this.saveQueue) + this.saveQueue = null + + dataApi + .updateNote(note.storage, note.key, this.state.note) + .then((note) => { + dispatch({ + type: 'UPDATE_NOTE', + note: note + }) + }) + } + handleFolderChange (e) { let { note } = this.state let value = this.refs.folder.value @@ -132,7 +145,7 @@ class SnippetNoteDetail extends React.Component { let newFolderKey = splitted.shift() dataApi - .moveNote(note.storage, note.folder, note.key, newStorageKey, newFolderKey) + .moveNote(note.storage, note.key, newStorageKey, newFolderKey) .then((newNote) => { this.setState({ isMovingNote: true, @@ -141,13 +154,13 @@ class SnippetNoteDetail extends React.Component { let { dispatch, location } = this.props dispatch({ type: 'MOVE_NOTE', - note: note, - newNote: newNote + originNote: note, + note: newNote }) hashHistory.replace({ pathname: location.pathname, query: { - key: newNote.uniqueKey + key: newNote.storage + '-' + newNote.key } }) this.setState({ @@ -321,7 +334,7 @@ class SnippetNoteDetail extends React.Component { } render () { - let { storages, config } = this.props + let { data, config } = this.props let { note } = this.state let editorFontSize = parseInt(config.editor.fontSize, 10) @@ -434,7 +447,7 @@ class SnippetNoteDetail extends React.Component { this.handleFolderChange(e)} /> diff --git a/browser/main/Detail/index.js b/browser/main/Detail/index.js index d2c96726..89d1dd56 100644 --- a/browser/main/Detail/index.js +++ b/browser/main/Detail/index.js @@ -31,19 +31,14 @@ class Detail extends React.Component { } render () { - let { location, notes, config } = this.props + let { location, data, config } = this.props let note = null if (location.query.key != null) { let splitted = location.query.key.split('-') let storageKey = splitted.shift() - let folderKey = splitted.shift() let noteKey = splitted.shift() - note = _.find(notes, { - storage: storageKey, - folder: folderKey, - key: noteKey - }) + note = data.noteMap.get(storageKey + '-' + noteKey) } if (note == null) { @@ -67,7 +62,7 @@ class Detail extends React.Component { ref='root' {..._.pick(this.props, [ 'dispatch', - 'storages', + 'data', 'style', 'ignorePreviewPointerEvents', 'location' @@ -83,7 +78,7 @@ class Detail extends React.Component { ref='root' {..._.pick(this.props, [ 'dispatch', - 'storages', + 'data', 'style', 'ignorePreviewPointerEvents', 'location' @@ -95,7 +90,6 @@ class Detail extends React.Component { Detail.propTypes = { dispatch: PropTypes.func, - storages: PropTypes.array, style: PropTypes.shape({ left: PropTypes.number }), diff --git a/browser/main/Main.js b/browser/main/Main.js index 45295a4d..9c3138e1 100644 --- a/browser/main/Main.js +++ b/browser/main/Main.js @@ -101,7 +101,7 @@ class Main extends React.Component { x)(CSSModules(Main, styles)) diff --git a/browser/main/NoteList/index.js b/browser/main/NoteList/index.js index c44a5bdd..f23eafb0 100644 --- a/browser/main/NoteList/index.js +++ b/browser/main/NoteList/index.js @@ -43,7 +43,7 @@ class NoteList extends React.Component { router.replace({ pathname: location.pathname, query: { - key: this.notes[0].uniqueKey + key: this.notes[0].storage + '-' + this.notes[0].key } }) return @@ -52,7 +52,7 @@ class NoteList extends React.Component { // Auto scroll if (_.isString(location.query.key)) { let targetIndex = _.findIndex(this.notes, (note) => { - return note.uniqueKey === location.query.key + return note != null && note.storage + '-' + note.key === location.query.key }) if (targetIndex > -1) { let list = this.refs.root @@ -153,30 +153,33 @@ class NoteList extends React.Component { } getNotes () { - let { storages, notes, params, location } = this.props + let { data, params, location } = this.props if (location.pathname.match(/\/home/)) { - return notes + return data.noteMap.map((note) => note) } if (location.pathname.match(/\/starred/)) { - return notes - .filter((note) => note.isStarred) + return data.starredSet.toJS() + .map((uniqueKey) => data.noteMap.get(uniqueKey)) } let storageKey = params.storageKey let folderKey = params.folderKey - let storage = _.find(storages, {key: storageKey}) + let storage = data.storageMap.get(storageKey) if (storage == null) return [] let folder = _.find(storage.folders, {key: folderKey}) if (folder == null) { - return notes - .filter((note) => note.storage === storageKey) + return data.storeageNoteMap + .get(storage.key) + .map((uniqueKey) => data.noteMap.get(uniqueKey)) } - return notes - .filter((note) => note.folder === folderKey) + let folderNoteKeyList = data.folderNoteMap + .get(storage.key + '-' + folder.key) + return folderNoteKeyList + .map((uniqueKey) => data.noteMap.get(uniqueKey)) } handleNoteClick (uniqueKey) { @@ -194,13 +197,14 @@ class NoteList extends React.Component { } render () { - let { location, storages, notes } = this.props + let { location, data, notes } = this.props this.notes = notes = this.getNotes() .sort((a, b) => new Date(b.updatedAt) - new Date(a.updatedAt)) let noteList = notes .map((note) => { - let storage = _.find(storages, {key: note.storage}) + if (note == null) return null + let storage = data.storageMap.get(note.storage) let folder = _.find(storage.folders, {key: note.folder}) let tagElements = _.isArray(note.tags) ? note.tags.map((tag) => { @@ -212,14 +216,14 @@ class NoteList extends React.Component { ) }) : [] - let isActive = location.query.key === note.uniqueKey + let isActive = location.query.key === note.storage + '-' + note.key return (
this.handleNoteClick(note.uniqueKey)(e)} + key={note.storage + '-' + note.key} + onClick={(e) => this.handleNoteClick(note.storage + '-' + note.key)(e)} >
diff --git a/browser/main/SideNav/index.js b/browser/main/SideNav/index.js index 37bf994a..3e541413 100644 --- a/browser/main/SideNav/index.js +++ b/browser/main/SideNav/index.js @@ -36,12 +36,13 @@ class SideNav extends React.Component { } render () { - let { storages, location, config } = this.props + let { data, location, config } = this.props let isFolded = config.isSideNavFolded let isHomeActive = location.pathname.match(/^\/home$/) let isStarredActive = location.pathname.match(/^\/starred$/) - let storageList = storages.map((storage) => { + + let storageList = data.storageMap.map((storage, key) => { return { dispatch({ - type: 'CREATE_NOTE', + type: 'UPDATE_NOTE', note: note }) hashHistory.push({ pathname: location.pathname, - query: {key: note.uniqueKey} + query: {key: note.storage + '-' + note.key} }) ee.emit('detail:focus') this.props.close() }) } + handleMarkdownNoteButtonKeyDown (e) { if (e.keyCode === 9) { e.preventDefault() @@ -50,8 +53,11 @@ class NewNoteModal extends React.Component { handleSnippetNoteButtonClick (e) { let { storage, folder, dispatch, location } = this.props + dataApi - .createSnippetNote(storage, folder, { + .createNote(storage, { + type: 'SNIPPET_NOTE', + folder: folder, title: '', description: '', snippets: [{ @@ -62,12 +68,12 @@ class NewNoteModal extends React.Component { }) .then((note) => { dispatch({ - type: 'CREATE_NOTE', + type: 'UPDATE_NOTE', note: note }) hashHistory.push({ pathname: location.pathname, - query: {key: note.uniqueKey} + query: {key: note.storage + '-' + note.key} }) ee.emit('detail:focus') this.props.close() diff --git a/browser/main/store.js b/browser/main/store.js index b1c2e68b..b93a64d8 100644 --- a/browser/main/store.js +++ b/browser/main/store.js @@ -1,94 +1,270 @@ import { combineReducers, createStore } from 'redux' import { routerReducer } from 'react-router-redux' import ConfigManager from 'browser/main/lib/ConfigManager' +import { Map, Set } from 'browser/lib/Mutable' +import _ from 'lodash' -function storages (state = [], action) { - console.info('REDUX >> ', action) - switch (action.type) { - case 'INIT_ALL': - return action.storages - case 'ADD_STORAGE': - { - let storages = state.slice() - - storages.push(action.storage) - - return storages - } - case 'ADD_FOLDER': - case 'REMOVE_FOLDER': - case 'UPDATE_STORAGE': - case 'RENAME_STORAGE': - { - let storages = state.slice() - storages = storages - .filter((storage) => storage.key !== action.storage.key) - storages.push(action.storage) - - return storages - } - case 'REMOVE_STORAGE': - { - let storages = state.slice() - storages = storages - .filter((storage) => storage.key !== action.key) - - return storages - } +function defaultDataMap () { + return { + storageMap: new Map(), + noteMap: new Map(), + starredSet: new Set(), + storeageNoteMap: new Map(), + folderNoteMap: new Map(), + tagNoteMap: new Map() } - return state } -function notes (state = [], action) { +function data (state = defaultDataMap(), action) { switch (action.type) { case 'INIT_ALL': - return action.notes - case 'ADD_STORAGE': - { - let notes = state.concat(action.notes) - return notes - } - case 'REMOVE_STORAGE': - { - let notes = state.slice() - notes = notes - .filter((note) => note.storage !== action.key) + state = defaultDataMap() - return notes - } - case 'REMOVE_FOLDER': - { - let notes = state.slice() - notes = notes - .filter((note) => note.storage !== action.storage.key || note.folder !== action.key) + action.storages.forEach((storage) => { + state.storageMap.set(storage.key, storage) + }) - return notes - } - case 'CREATE_NOTE': - { - let notes = state.slice() - notes.push(action.note) - return notes - } + action.notes.forEach((note) => { + let uniqueKey = note.storage + '-' + note.key + let folderKey = note.storage + '-' + note.folder + state.noteMap.set(uniqueKey, note) + + if (note.isStarred) { + state.starredSet.add(uniqueKey) + } + + let storageNoteList = state.storeageNoteMap.get(note.storage) + if (storageNoteList == null) { + storageNoteList = new Set(storageNoteList) + state.storeageNoteMap.set(note.storage, storageNoteList) + } + storageNoteList.add(uniqueKey) + + let folderNoteList = state.folderNoteMap.get(folderKey) + if (folderNoteList == null) { + folderNoteList = new Set(folderNoteList) + state.folderNoteMap.set(folderKey, folderNoteList) + } + folderNoteList.add(uniqueKey) + + note.tags.forEach((tag) => { + let tagNoteList = state.tagNoteMap.get(tag) + if (tagNoteList == null) { + tagNoteList = new Set(tagNoteList) + state.tagNoteMap.set(tag, tagNoteList) + } + tagNoteList.add(uniqueKey) + }) + }) + return state case 'UPDATE_NOTE': { - let notes = state.slice() - notes = notes.filter((note) => note.key !== action.note.key || note.folder !== action.note.folder || note.storage !== action.note.storage) - notes.push(action.note) - return notes + let note = action.note + let uniqueKey = note.storage + '-' + note.key + let folderKey = note.storage + '-' + note.folder + let oldNote = state.noteMap.get(uniqueKey) + + state = Object.assign({}, state) + state.noteMap = new Map(state.noteMap) + state.noteMap.set(uniqueKey, note) + + if (oldNote == null || oldNote.isStarred !== note.isStarred) { + state.starredSet = new Set(state.starredSet) + if (note.isStarred) { + state.starredSet.add(uniqueKey) + } else { + state.starredSet.delete(uniqueKey) + } + } + + // Update storageNoteMap if oldNote doesn't exist + if (oldNote == null) { + state.storeageNoteMap = new Map(state.storeageNoteMap) + let noteSet = state.storeageNoteMap.get(note.storage) + noteSet = new Set(noteSet) + noteSet.add(uniqueKey) + state.folderNoteMap.set(folderKey, noteSet) + } + + // Update foldermap if folder changed or post created + if (oldNote == null || oldNote.folder !== note.folder) { + state.folderNoteMap = new Map(state.folderNoteMap) + let folderNoteList = state.folderNoteMap.get(folderKey) + folderNoteList = new Set(folderNoteList) + folderNoteList.add(uniqueKey) + state.folderNoteMap.set(folderKey, folderNoteList) + + if (oldNote != null) { + let oldFolderKey = oldNote.storage + '-' + oldNote.folder + let oldFolderNoteList = state.folderNoteMap.get(oldFolderKey) + oldFolderNoteList = new Set(oldFolderNoteList) + oldFolderNoteList.delete(uniqueKey) + state.folderNoteMap.set(oldFolderKey, oldFolderNoteList) + } + } + + if (oldNote != null) { + let discardedTags = _.difference(oldNote.tags, note.tags) + let addedTags = _.difference(note.tags, oldNote.tags) + if (discardedTags.length + addedTags.length > 0) { + state.tagNoteMap = new Map(state.tagNoteMap) + + discardedTags.forEach((tag) => { + let tagNoteList = state.tagNoteMap.get(tag) + if (tagNoteList != null) { + tagNoteList = new Set(tagNoteList) + tagNoteList.delete(uniqueKey) + state.tagNoteMap.set(tag, tagNoteList) + } + }) + addedTags.forEach((tag) => { + let tagNoteList = state.tagNoteMap.get(tag) + tagNoteList = new Set(tagNoteList) + tagNoteList.add(uniqueKey) + + state.tagNoteMap.set(tag, tagNoteList) + }) + } + } else { + state.tagNoteMap = new Map(state.tagNoteMap) + note.tags.forEach((tag) => { + let tagNoteList = state.tagNoteMap.get(tag) + if (tagNoteList == null) { + tagNoteList = new Set(tagNoteList) + state.tagNoteMap.set(tag, tagNoteList) + } + tagNoteList.add(uniqueKey) + }) + } + + return state } case 'MOVE_NOTE': { - let notes = state.slice() - notes = notes.filter((note) => note.key !== action.note.key || note.folder !== action.note.folder || note.storage !== action.note.storage) - notes.push(action.newNote) - return notes + let originNote = action.originNote + let originKey = originNote.storage + '-' + originNote.key + let note = action.note + let uniqueKey = note.storage + '-' + note.key + let folderKey = note.storage + '-' + note.folder + let oldNote = state.noteMap.get(uniqueKey) + + state = Object.assign({}, state) + state.noteMap = new Map(state.noteMap) + state.noteMap.delete(originKey) + state.noteMap.set(uniqueKey, note) + + // If storage chanced, origin key must be discarded + if (originKey !== uniqueKey) { + console.log('diffrent storage') + // From isStarred + if (originNote.isStarred) { + state.starredSet = new Set(state.starredSet) + state.starredSet.delete(originKey) + } + + // From storageNoteMap + state.storeageNoteMap = new Map(state.storeageNoteMap) + let noteSet = state.storeageNoteMap.get(originNote.storage) + noteSet = new Set(noteSet) + noteSet.delete(originKey) + state.storeageNoteMap.set(originNote.storage, noteSet) + + // From folderNoteMap + state.folderNoteMap = new Map(state.folderNoteMap) + let originFolderKey = originNote.storage + '-' + originNote.folder + let originFolderList = state.folderNoteMap.get(originFolderKey) + originFolderList = new Set(originFolderList) + originFolderList.delete(originKey) + state.folderNoteMap.set(originFolderKey, originFolderList) + + // From tagMap + if (originNote.tags.length > 0) { + state.tagNoteMap = new Map(state.tagNoteMap) + originNote.tags.forEach((tag) => { + let noteSet = state.tagNoteMap.get(tag) + noteSet = new Set(noteSet) + noteSet.delete(originKey) + state.tagNoteMap.set(tag, noteSet) + }) + } + } + + if (oldNote == null || oldNote.isStarred !== note.isStarred) { + state.starredSet = new Set(state.starredSet) + if (note.isStarred) { + state.starredSet.add(uniqueKey) + } else { + state.starredSet.delete(uniqueKey) + } + } + + // Update storageNoteMap if oldNote doesn't exist + if (oldNote == null) { + state.storeageNoteMap = new Map(state.storeageNoteMap) + let noteSet = state.storeageNoteMap.get(note.storage) + noteSet = new Set(noteSet) + noteSet.add(uniqueKey) + state.folderNoteMap.set(folderKey, noteSet) + } + + // Update foldermap if folder changed or post created + if (oldNote == null || oldNote.folder !== note.folder) { + state.folderNoteMap = new Map(state.folderNoteMap) + let folderNoteList = state.folderNoteMap.get(folderKey) + folderNoteList = new Set(folderNoteList) + folderNoteList.add(uniqueKey) + state.folderNoteMap.set(folderKey, folderNoteList) + + if (oldNote != null) { + let oldFolderKey = oldNote.storage + '-' + oldNote.folder + let oldFolderNoteList = state.folderNoteMap.get(oldFolderKey) + oldFolderNoteList = new Set(oldFolderNoteList) + oldFolderNoteList.delete(uniqueKey) + state.folderNoteMap.set(oldFolderKey, oldFolderNoteList) + } + } + + // Remove from old folder map + if (oldNote != null) { + let discardedTags = _.difference(oldNote.tags, note.tags) + let addedTags = _.difference(note.tags, oldNote.tags) + if (discardedTags.length + addedTags.length > 0) { + state.tagNoteMap = new Map(state.tagNoteMap) + + discardedTags.forEach((tag) => { + let tagNoteList = state.tagNoteMap.get(tag) + if (tagNoteList != null) { + tagNoteList = new Set(tagNoteList) + tagNoteList.delete(uniqueKey) + state.tagNoteMap.set(tag, tagNoteList) + } + }) + addedTags.forEach((tag) => { + let tagNoteList = state.tagNoteMap.get(tag) + tagNoteList = new Set(tagNoteList) + tagNoteList.add(uniqueKey) + + state.tagNoteMap.set(tag, tagNoteList) + }) + } + } else { + state.tagNoteMap = new Map(state.tagNoteMap) + note.tags.forEach((tag) => { + let tagNoteList = state.tagNoteMap.get(tag) + if (tagNoteList == null) { + tagNoteList = new Set(tagNoteList) + state.tagNoteMap.set(tag, tagNoteList) + } + tagNoteList.add(uniqueKey) + }) + } + + return state } - case 'REMOVE_NOTE': + case 'DELETE_NOTE': { - let notes = state.slice() - notes = notes.filter((note) => note.key !== action.note.key || note.folder !== action.note.folder || note.storage !== action.note.storage) - return notes + + return state } } return state @@ -116,8 +292,7 @@ function config (state = defaultConfig, action) { } let reducer = combineReducers({ - storages, - notes, + data, config, routing: routerReducer })