From 1a959c1a6fb9ebd28dd366b43a27d21e705452a3 Mon Sep 17 00:00:00 2001 From: Quang Lam Date: Sat, 6 Jun 2020 23:36:00 +0700 Subject: [PATCH] Add history page --- public/electron.js | 2 +- public/libs/locales/en.json | 9 +- public/libs/preferences.js | 1 + src/components/app.js | 40 +-- src/components/pages/history/index.js | 236 ++++++++++++++++++ src/components/pages/history/search-box.js | 176 +++++++++++++ src/components/pages/home/history.js | 9 +- src/components/pages/home/index.js | 20 +- .../pages/language-list/search-box.js | 6 +- src/components/pages/phrasebook/index.js | 17 +- src/components/pages/phrasebook/search-box.js | 9 +- src/components/pages/preferences/index.js | 12 +- src/constants/actions.js | 4 + src/constants/routes.js | 1 + src/state/pages/history/actions.js | 156 ++++++++++++ src/state/pages/history/reducers.js | 29 +++ src/state/pages/home/history/actions.js | 12 +- src/state/pages/reducers.js | 2 + 18 files changed, 688 insertions(+), 53 deletions(-) create mode 100644 src/components/pages/history/index.js create mode 100644 src/components/pages/history/search-box.js create mode 100644 src/state/pages/history/actions.js create mode 100644 src/state/pages/history/reducers.js diff --git a/public/electron.js b/public/electron.js index 246f664c..2a7bd2d1 100644 --- a/public/electron.js +++ b/public/electron.js @@ -101,7 +101,7 @@ if (!gotTheLock) { alwaysOnTop: getPreference('alwaysOnTop'), width: 400, height: 500, - minWidth: 400, + minWidth: 460, minHeight: 500, webPreferences: { nodeIntegration: true, diff --git a/public/libs/locales/en.json b/public/libs/locales/en.json index c721c88a..bb0b6e7b 100755 --- a/public/libs/locales/en.json +++ b/public/libs/locales/en.json @@ -35,6 +35,7 @@ "quit": "Quit", "home": "Home", "phrasebook": "Phrasebook", + "phrasebookDesc": "Tap the star icon to save a translation.", "searchSavedTranslations": "Search saved translations...", "search": "Search", "noMatchingResults": "No Matching Results", @@ -140,11 +141,9 @@ "trialExpireIn": "Your trial period will expire in $TIME.", "visitStore": "Visit Store...", "register": "Register", - "definitions": "Definitions", - "synonyms": "Synonyms", - "examples": "Examples", - "seeAlso": "See also", - "translations": "Translations", + "history": "History", + "historyDesc": "View previously translated words and phrases here.", + "clearHistory": "Clear history", "auto": "Auto detect", "af": "Afrikaans", "sq": "Albanian", diff --git a/public/libs/preferences.js b/public/libs/preferences.js index 31e4e513..637e2232 100644 --- a/public/libs/preferences.js +++ b/public/libs/preferences.js @@ -35,6 +35,7 @@ const defaultPreferences = { themeSource: 'system', translateClipboardOnShortcut: false, translateWhenPressingEnter: true, + useHardwareAcceleration: false, }; let cachedPreferences = null; diff --git a/src/components/app.js b/src/components/app.js index 0500d7d6..488d41da 100644 --- a/src/components/app.js +++ b/src/components/app.js @@ -3,14 +3,16 @@ import PropTypes from 'prop-types'; import classNames from 'classnames'; import CircularProgress from '@material-ui/core/CircularProgress'; -import ActionHome from '@material-ui/icons/Home'; -import ActionSettings from '@material-ui/icons/Settings'; import BottomNavigation from '@material-ui/core/BottomNavigation'; import BottomNavigationAction from '@material-ui/core/BottomNavigationAction'; import Button from '@material-ui/core/Button'; import Paper from '@material-ui/core/Paper'; import Snackbar from '@material-ui/core/Snackbar'; -import ToggleStar from '@material-ui/icons/Star'; + +import HomeIcon from '@material-ui/icons/Home'; +import SettingsIcon from '@material-ui/icons/Settings'; +import StarIcon from '@material-ui/icons/Star'; +import HistoryIcon from '@material-ui/icons/History'; import connectComponent from '../helpers/connect-component'; import getLocale from '../helpers/get-locale'; @@ -25,6 +27,7 @@ import DialogAbout from './root/dialog-about'; import DialogLicenseRegistration from './root/dialog-license-registration'; import Home from './pages/home'; +import History from './pages/history'; import Phrasebook from './pages/phrasebook'; import Preferences from './pages/preferences'; import LanguageList from './pages/language-list'; @@ -32,6 +35,7 @@ import Ocr from './pages/ocr'; import { ROUTE_HOME, + ROUTE_HISTORY, ROUTE_PHRASEBOOK, ROUTE_PREFERENCES, ROUTE_LANGUAGE_LIST, @@ -80,15 +84,8 @@ const styles = (theme) => ({ flexDirection: 'column', overflow: 'hidden', }, - bottomNavigation: { - height: 40, - }, - bottomNavigationActionWrapper: { - flexDirection: 'row', - }, bottomNavigationActionLabel: { fontSize: '0.8rem !important', - paddingLeft: 4, }, hidden: { display: 'none !important', @@ -131,8 +128,10 @@ class App extends React.Component { switch (route) { case ROUTE_PREFERENCES: return ; + case ROUTE_HISTORY: + return ; case ROUTE_PHRASEBOOK: - return ; + return ; case ROUTE_LANGUAGE_LIST: return null; // already preloaded case ROUTE_OCR: @@ -145,8 +144,10 @@ class App extends React.Component { const bottomNavigationSelectedIndex = (() => { switch (route) { case ROUTE_PREFERENCES: - return 2; + return 3; case ROUTE_PHRASEBOOK: + return 2; + case ROUTE_HISTORY: return 1; case ROUTE_HOME: return 0; @@ -218,16 +219,25 @@ class App extends React.Component { > } + icon={} onClick={() => onChangeRoute(ROUTE_HOME)} classes={{ wrapper: classes.bottomNavigationActionWrapper, label: classes.bottomNavigationActionLabel, }} /> + } + onClick={() => onChangeRoute(ROUTE_HISTORY)} + classes={{ + wrapper: classes.bottomNavigationActionWrapper, + label: classes.bottomNavigationActionLabel, + }} + /> } + icon={} onClick={() => onChangeRoute(ROUTE_PHRASEBOOK)} classes={{ wrapper: classes.bottomNavigationActionWrapper, @@ -236,7 +246,7 @@ class App extends React.Component { /> } + icon={} onClick={() => onChangeRoute(ROUTE_PREFERENCES)} classes={{ wrapper: classes.bottomNavigationActionWrapper, diff --git a/src/components/pages/history/index.js b/src/components/pages/history/index.js new file mode 100644 index 00000000..46405d3e --- /dev/null +++ b/src/components/pages/history/index.js @@ -0,0 +1,236 @@ +import React from 'react'; +import PropTypes from 'prop-types'; + +import AppBar from '@material-ui/core/AppBar'; +import Divider from '@material-ui/core/Divider'; +import IconButton from '@material-ui/core/IconButton'; +import List from '@material-ui/core/List'; +import ListItem from '@material-ui/core/ListItem'; +import ListItemSecondaryAction from '@material-ui/core/ListItemSecondaryAction'; +import ListItemText from '@material-ui/core/ListItemText'; +import Toolbar from '@material-ui/core/Toolbar'; +import Tooltip from '@material-ui/core/Tooltip'; + +import Typography from '@material-ui/core/Typography'; +import DeleteIcon from '@material-ui/icons/Delete'; +import HistoryIcon from '@material-ui/icons/History'; +import SearchIcon from '@material-ui/icons/Search'; +import ClearAllIcon from '@material-ui/icons/ClearAll'; + +import connectComponent from '../../../helpers/connect-component'; +import getLocale from '../../../helpers/get-locale'; + +import { deleteHistoryItem, loadHistory, clearAllHistory } from '../../../state/pages/history/actions'; +import { loadOutput } from '../../../state/pages/home/actions'; +import { changeRoute } from '../../../state/root/router/actions'; + +import { ROUTE_HOME } from '../../../constants/routes'; + +import SearchBox from './search-box'; + +const styles = (theme) => ({ + emptyContainer: { + flex: 1, + display: 'flex', + alignItems: 'center', + justifyContent: 'center', + }, + emptyInnerContainer: { + textAlign: 'center', + }, + bigIcon: { + height: 96, + width: 96, + color: theme.palette.text.primary, + }, + container: { + flex: 1, + display: 'flex', + flexDirection: 'column', + position: 'relative', + overflow: 'hidden', + }, + listContainer: { + flex: 1, + overflowY: 'auto', + WebkitOverflowScrolling: 'touch', + padding: 0, + boxSizing: 'border-box', + }, + progress: { + marginTop: 12, + }, + appBarColorDefault: { + background: theme.palette.type === 'dark' ? theme.palette.grey[900] : theme.palette.primary.main, + color: theme.palette.type === 'dark' ? theme.palette.getContrastText(theme.palette.grey[900]) : theme.palette.primary.contrastText, + }, + title: { + flex: 1, + textAlign: 'center', + }, + toolbar: { + minHeight: 40, + paddingRight: theme.spacing(1.5), + paddingLeft: theme.spacing(1.5), + }, + appBarMenu: { + position: 'absolute', + right: theme.spacing(1.5), + }, + toolbarIconButton: { + padding: theme.spacing(1), + }, +}); + +class History extends React.Component { + componentDidMount() { + const { onLoadHistory } = this.props; + + onLoadHistory(true); + + if (this.listView) { + this.listView.onscroll = () => { + const { scrollTop, clientHeight, scrollHeight } = this.listView; + if (scrollTop + clientHeight > scrollHeight - 200) { + const { canLoadMore, historyLoading } = this.props; + if (canLoadMore === true && historyLoading === false) { + onLoadHistory(); + } + } + }; + } + } + + componentWillUnmount() { + if (this.listView) this.listView.onscroll = null; + } + + render() { + const { + classes, + historyItems, + historyLoading, + onChangeRoute, + onClearAllHistory, + onDeleteHistoryItem, + onLoadOutput, + query, + } = this.props; + + return ( +
+ + + {getLocale('history')} +
+ + + + + +
+
+
+ + {(() => { + if (historyItems.length < 1 && historyLoading === false) { + return ( +
+
+ {query ? ( + + ) : ( + + )} + + {query ? getLocale('noMatchingResults') : getLocale('history')} + + {!query && ( + + {getLocale('historyDesc')} + + )} +
+
+ ); + } + + return ( +
{ this.listView = c; }}> + + {historyItems.map((item) => [( + { + onLoadOutput(item); + onChangeRoute(ROUTE_HOME); + }} + > + + + + onDeleteHistoryItem( + item.historyId, + item.rev, + )} + > + + + + + + ), ])} + +
+ ); + })()} +
+ ); + } +} + +History.propTypes = { + canLoadMore: PropTypes.bool.isRequired, + classes: PropTypes.object.isRequired, + historyItems: PropTypes.arrayOf(PropTypes.object).isRequired, + historyLoading: PropTypes.bool.isRequired, + onChangeRoute: PropTypes.func.isRequired, + onClearAllHistory: PropTypes.func.isRequired, + onDeleteHistoryItem: PropTypes.func.isRequired, + onLoadHistory: PropTypes.func.isRequired, + onLoadOutput: PropTypes.func.isRequired, + query: PropTypes.string.isRequired, +}; + +const mapStateToProps = (state) => ({ + canLoadMore: state.pages.history.canLoadMore, + historyItems: state.pages.history.items, + historyLoading: state.pages.history.loading, + query: state.pages.history.query, +}); + +const actionCreators = { + changeRoute, + clearAllHistory, + deleteHistoryItem, + loadHistory, + loadOutput, +}; + +export default connectComponent( + History, + mapStateToProps, + actionCreators, + styles, +); diff --git a/src/components/pages/history/search-box.js b/src/components/pages/history/search-box.js new file mode 100644 index 00000000..0d1895ef --- /dev/null +++ b/src/components/pages/history/search-box.js @@ -0,0 +1,176 @@ +import React, { useCallback, useEffect, useRef } from 'react'; +import PropTypes from 'prop-types'; + +import CloseIcon from '@material-ui/icons/Close'; +import IconButton from '@material-ui/core/IconButton'; +import KeyboardReturnIcon from '@material-ui/icons/KeyboardReturn'; +import Paper from '@material-ui/core/Paper'; +import Typography from '@material-ui/core/Typography'; +import Tooltip from '@material-ui/core/Tooltip'; + +import connectComponent from '../../../helpers/connect-component'; +import getLocale from '../../../helpers/get-locale'; + + +import { loadHistory, updateQuery } from '../../../state/pages/history/actions'; + +const styles = (theme) => ({ + toolbarSearchContainer: { + flex: 0, + zIndex: 10, + position: 'relative', + borderRadius: 0, + paddingRight: theme.spacing(1.5), + paddingLeft: theme.spacing(1.5), + }, + toolbarSectionSearch: { + alignItems: 'center', + display: 'flex', + flexDirection: 'row', + height: 40, + margin: '0 auto', + }, + searchBarText: { + lineHeight: 1.5, + padding: '0 4px', + flex: 1, + userSelect: 'none', + overflow: 'hidden', + whiteSpace: 'nowrap', + textOverflow: 'ellipsis', + transform: 'translateY(-1px)', + fontWeight: 'normal', + }, + input: { + font: 'inherit', + border: 0, + display: 'block', + verticalAlign: 'middle', + whiteSpace: 'normal', + background: 'none', + margin: 0, + color: theme.palette.text.primary, + width: '100%', + padding: 0, + '&:focus': { + outline: 0, + }, + '&::placeholder': { + color: theme.palette.text.secondary, + }, + }, + searchButton: { + [theme.breakpoints.up('md')]: { + display: 'none', + }, + }, + toolbarIconButton: { + padding: theme.spacing(1), + }, +}); + +const SearchBox = ({ + classes, + onUpdateQuery, + query, + onLoadHistory, +}) => { + const inputRef = useRef(null); + // https://stackoverflow.com/a/57556594 + // Event handler utilizing useCallback ... + // ... so that reference never changes. + const handleOpenFind = useCallback(() => { + inputRef.current.focus(); + inputRef.current.select(); + }, [inputRef]); + useEffect(() => { + const { ipcRenderer } = window.require('electron'); + ipcRenderer.on('open-find', handleOpenFind); + // Remove event listener on cleanup + return () => { + ipcRenderer.removeListener('open-find', handleOpenFind); + }; + }, [handleOpenFind]); + + const clearSearchAction = query.length > 0 && ( + <> + + onUpdateQuery('')} + className={classes.toolbarIconButton} + > + + + + + onLoadHistory(true)} + className={classes.toolbarIconButton} + > + + + + + ); + + return ( + +
+ + onUpdateQuery(e.target.value)} + onInput={(e) => onUpdateQuery(e.target.value)} + onKeyDown={(e) => { + if (e.key === 'Enter' && query.length > 0) { + onLoadHistory(true); + } else if (e.key === 'Escape') { + e.target.blur(); + onUpdateQuery(''); + } + }} + placeholder={getLocale('searchSavedTranslations')} + value={query} + /> + + {clearSearchAction} +
+
+ ); +}; + +SearchBox.defaultProps = { + query: '', +}; + +SearchBox.propTypes = { + classes: PropTypes.object.isRequired, + onUpdateQuery: PropTypes.func.isRequired, + onLoadHistory: PropTypes.func.isRequired, + query: PropTypes.string, +}; + +const mapStateToProps = (state) => ({ + query: state.pages.history.query, +}); + +const actionCreators = { + updateQuery, + loadHistory, +}; + +export default connectComponent( + SearchBox, + mapStateToProps, + actionCreators, + styles, +); diff --git a/src/components/pages/home/history.js b/src/components/pages/home/history.js index ce06a49b..1ec6bbbf 100644 --- a/src/components/pages/home/history.js +++ b/src/components/pages/home/history.js @@ -6,10 +6,11 @@ import List from '@material-ui/core/List'; import ListItem from '@material-ui/core/ListItem'; import ListItemText from '@material-ui/core/ListItemText'; import ListItemSecondaryAction from '@material-ui/core/ListItemSecondaryAction'; -import ActionDelete from '@material-ui/icons/Delete'; import Divider from '@material-ui/core/Divider'; import Tooltip from '@material-ui/core/Tooltip'; +import DeleteIcon from '@material-ui/icons/Delete'; + import connectComponent from '../../../helpers/connect-component'; import getLocale from '../../../helpers/get-locale'; @@ -111,7 +112,7 @@ class History extends React.Component { ); }} > - + @@ -131,9 +132,9 @@ History.propTypes = { classes: PropTypes.object.isRequired, historyItems: PropTypes.arrayOf(PropTypes.object).isRequired, historyLoading: PropTypes.bool.isRequired, - onLoadOutput: PropTypes.func.isRequired, onDeleteHistoryItem: PropTypes.func.isRequired, onLoadHistory: PropTypes.func.isRequired, + onLoadOutput: PropTypes.func.isRequired, }; const mapStateToProps = (state) => ({ @@ -143,9 +144,9 @@ const mapStateToProps = (state) => ({ }); const actionCreators = { - loadOutput, deleteHistoryItem, loadHistory, + loadOutput, }; export default connectComponent( diff --git a/src/components/pages/home/index.js b/src/components/pages/home/index.js index 59f389a6..54cc7baa 100644 --- a/src/components/pages/home/index.js +++ b/src/components/pages/home/index.js @@ -598,6 +598,7 @@ class Home extends React.Component { Icon, tooltip, onClick, disabled, }) => (disabled ? (
- + {(inputText.length < 1 || (output && output.status === 'loading')) ? ( - + ) : ( + + + + )}
diff --git a/src/components/pages/language-list/search-box.js b/src/components/pages/language-list/search-box.js index 13934a1e..f9d8bee6 100644 --- a/src/components/pages/language-list/search-box.js +++ b/src/components/pages/language-list/search-box.js @@ -65,6 +65,9 @@ const styles = (theme) => ({ display: 'none', }, }, + toolbarIconButton: { + padding: theme.spacing(1), + }, }); const SearchBox = ({ @@ -97,8 +100,9 @@ const SearchBox = ({ color="default" aria-label={getLocale('clear')} onClick={() => onUpdateQuery('')} + className={classes.toolbarIconButton} > - +
); diff --git a/src/components/pages/phrasebook/index.js b/src/components/pages/phrasebook/index.js index e0e4e6d9..edeb2a14 100644 --- a/src/components/pages/phrasebook/index.js +++ b/src/components/pages/phrasebook/index.js @@ -109,13 +109,11 @@ class Phrasebook extends React.Component { return (
- {window.process.platform === 'darwin' && window.mode !== 'menubar' && ( - - - {getLocale('phrasebook')} - - - )} + + + {getLocale('phrasebook')} + + {(() => { if (phrasebookItems.length < 1 && phrasebookLoading === false) { @@ -130,6 +128,11 @@ class Phrasebook extends React.Component { {query ? getLocale('noMatchingResults') : getLocale('phrasebookIsEmpty')} + {!query && ( + + {getLocale('phrasebookDesc')} + + )}
); diff --git a/src/components/pages/phrasebook/search-box.js b/src/components/pages/phrasebook/search-box.js index 2173d758..ba662aa6 100644 --- a/src/components/pages/phrasebook/search-box.js +++ b/src/components/pages/phrasebook/search-box.js @@ -64,6 +64,9 @@ const styles = (theme) => ({ display: 'none', }, }, + toolbarIconButton: { + padding: theme.spacing(1), + }, }); const SearchBox = ({ @@ -96,8 +99,9 @@ const SearchBox = ({ color="default" aria-label={getLocale('clear')} onClick={() => onUpdateQuery('')} + className={classes.toolbarIconButton} > - + @@ -105,8 +109,9 @@ const SearchBox = ({ color="default" aria-label={getLocale('search')} onClick={() => onLoadPhrasebook(true)} + className={classes.toolbarIconButton} > - + diff --git a/src/components/pages/preferences/index.js b/src/components/pages/preferences/index.js index 4c259ce0..f9478695 100644 --- a/src/components/pages/preferences/index.js +++ b/src/components/pages/preferences/index.js @@ -127,13 +127,11 @@ const Preferences = (props) => { return (
- {window.process.platform === 'darwin' && window.mode !== 'menubar' && ( - - - {getLocale('preferences')} - - - )} + + + {getLocale('preferences')} + +
{getLocale('general')} diff --git a/src/constants/actions.js b/src/constants/actions.js index 8f717a83..3da03676 100644 --- a/src/constants/actions.js +++ b/src/constants/actions.js @@ -49,3 +49,7 @@ export const DIALOG_ABOUT_OPEN = 'DIALOG_ABOUT_OPEN'; // System Preferences export const SET_SYSTEM_PREFERENCE = 'SET_SYSTEM_PREFERENCE'; + +// History Page +export const UPDATE_HISTORY_PAGE = 'UPDATE_HISTORY_PAGE'; +export const UPDATE_HISTORY_PAGE_QUERY = 'UPDATE_HISTORY_PAGE_QUERY'; diff --git a/src/constants/routes.js b/src/constants/routes.js index bd34d9b0..b2c12d97 100644 --- a/src/constants/routes.js +++ b/src/constants/routes.js @@ -1,4 +1,5 @@ export const ROUTE_HOME = 'ROUTE_HOME'; +export const ROUTE_HISTORY = 'ROUTE_HISTORY'; export const ROUTE_PHRASEBOOK = 'ROUTE_PHRASEBOOK'; export const ROUTE_PREFERENCES = 'ROUTE_PREFERENCES'; export const ROUTE_LANGUAGE_LIST = 'ROUTE_LANGUAGE_LIST'; diff --git a/src/state/pages/history/actions.js b/src/state/pages/history/actions.js new file mode 100644 index 00000000..320538d7 --- /dev/null +++ b/src/state/pages/history/actions.js @@ -0,0 +1,156 @@ +import { + UPDATE_HISTORY, + UPDATE_HISTORY_PAGE, + UPDATE_HISTORY_PAGE_QUERY, +} from '../../../constants/actions'; +import historyDb from '../../../helpers/history-db'; + +import { loadHistory as loadHomeHistory } from '../home/history/actions'; + +const defaultOptions = { + include_docs: true, + descending: true, + limit: 10, +}; + +export const loadHistory = (init, limit) => ((dispatch, getState) => { + const { history } = getState().pages; + const { query } = history; + + const items = (init === true) ? [] : [...history.items]; + const canLoadMore = init === true ? false : history.canLoadMore; + + dispatch({ + type: UPDATE_HISTORY_PAGE, + items, + canLoadMore, + loading: true, + }); + + const options = { ...defaultOptions }; + const l = items.length; + if (l > 0) { + options.startkey = items[l - 1].historyId; + options.skip = 1; + } + if (limit) options.limit = limit; + + Promise.resolve() + .then(() => { + if (query) { + options.query = query; + options.fields = ['data.inputText', 'data.outputText']; + options.highlighting = true; + options.highlighting_pre = ''; + options.highlighting_post = ''; + return historyDb.search(options); + } + return historyDb.allDocs(options); + }) + .then((response) => { + response.rows.forEach((row) => { + /* eslint-disable no-underscore-dangle */ + const newItem = row.doc.data; + newItem.historyId = row.doc._id; + newItem.rev = row.doc._rev; + + items.push(newItem); + /* eslint-enable no-underscore-dangle */ + }); + + // verify the result query matched the current query + if (query !== getState().pages.history.query) return; + + dispatch({ + type: UPDATE_HISTORY_PAGE, + items, + canLoadMore: (response.total_rows > 0 && items.length < response.total_rows), + loading: false, + }); + }) + .catch((e) => { + // eslint-disable-next-line + console.log(e); + }); +}); + +export const deleteHistoryItem = (id, rev) => ((dispatch, getState) => { + const { history } = getState().pages; + const { items, loading, canLoadMore } = history; + + historyDb.remove(id, rev) + .then(() => { + items.every((doc, i) => { + if (doc.historyId === id) { + dispatch({ + type: UPDATE_HISTORY_PAGE, + items: items.filter((_, _i) => _i !== i), + loading, + canLoadMore, + }); + + return false; + } + return true; + }); + + if (canLoadMore) { + dispatch(loadHistory(false, 1)); + } + + // reload history for the home page + dispatch(loadHomeHistory(true)); + }) + .catch((e) => { + // eslint-disable-next-line + console.log(e); + }); +}); + +let timeout = null; +export const updateQuery = (query) => (dispatch) => { + dispatch({ + type: UPDATE_HISTORY_PAGE_QUERY, + query, + }); + clearTimeout(timeout); + if (query === '') { + dispatch(loadHistory(true)); + } else { + timeout = setTimeout(() => { + dispatch(loadHistory(true)); + }, 300); + } +}; + +export const clearAllHistory = () => (dispatch) => { + dispatch(updateQuery('')); + dispatch({ + type: UPDATE_HISTORY_PAGE, + items: [], + canLoadMore: false, + loading: false, + }); + + // reload history for the home page + dispatch({ + type: UPDATE_HISTORY, + items: [], + canLoadMore: false, + loading: true, + }); + + historyDb.allDocs() + .then((result) => Promise.all( + result.rows.map((row) => historyDb.remove(row.id, row.value.rev)), + )) + .catch((err) => { + // eslint-disable-next-line + console.log(err); + }) + .then(() => { + // reload after done deleting to be certain + dispatch(loadHistory(true)); + dispatch(loadHomeHistory(true)); + }); +}; diff --git a/src/state/pages/history/reducers.js b/src/state/pages/history/reducers.js new file mode 100644 index 00000000..9cfcd2c1 --- /dev/null +++ b/src/state/pages/history/reducers.js @@ -0,0 +1,29 @@ +import { UPDATE_HISTORY_PAGE, UPDATE_HISTORY_PAGE_QUERY } from '../../../constants/actions'; + +const initialState = { + items: [], + canLoadMore: false, + loading: true, + query: '', +}; + +const history = (state = initialState, action) => { + switch (action.type) { + case UPDATE_HISTORY_PAGE_QUERY: + return { + ...state, + query: action.query, + }; + case UPDATE_HISTORY_PAGE: + return { + ...state, + items: action.items, + canLoadMore: action.canLoadMore, + loading: action.loading, + }; + default: + return state; + } +}; + +export default history; diff --git a/src/state/pages/home/history/actions.js b/src/state/pages/home/history/actions.js index e38d4153..612bf096 100644 --- a/src/state/pages/home/history/actions.js +++ b/src/state/pages/home/history/actions.js @@ -8,10 +8,9 @@ const defaultOptions = { }; export const loadHistory = (init, limit) => ((dispatch, getState) => { - const { items, canLoadMore } = getState().pages.home.history; - - // only init once. - if (init === true && items.length > 0) return; + const { history } = getState().pages.home; + const items = init ? [] : history.items; + const canLoadMore = init ? false : history.canLoadMore; dispatch({ type: UPDATE_HISTORY, @@ -20,7 +19,6 @@ export const loadHistory = (init, limit) => ((dispatch, getState) => { loading: true, }); - const options = { ...defaultOptions }; const l = items.length; if (l > 0) { @@ -31,7 +29,7 @@ export const loadHistory = (init, limit) => ((dispatch, getState) => { historyDb.allDocs(options) .then((response) => { - let newItems = items; + const newItems = [...items]; response.rows.forEach((row) => { /* eslint-disable no-underscore-dangle */ @@ -39,7 +37,7 @@ export const loadHistory = (init, limit) => ((dispatch, getState) => { newItem.historyId = row.doc._id; newItem.rev = row.doc._rev; - newItems = [...newItems, newItem]; + newItems.push(newItem); /* eslint-enable no-underscore-dangle */ }); diff --git a/src/state/pages/reducers.js b/src/state/pages/reducers.js index 9d2a332a..dde84a09 100644 --- a/src/state/pages/reducers.js +++ b/src/state/pages/reducers.js @@ -1,5 +1,6 @@ import { combineReducers } from 'redux'; +import history from './history/reducers'; import home from './home/reducers'; import languageList from './language-list/reducers'; import ocr from './ocr/reducers'; @@ -7,6 +8,7 @@ import phrasebook from './phrasebook/reducers'; import preferences from './preferences/reducers'; export default combineReducers({ + history, home, languageList, ocr,