diff --git a/ui/js/actions/app.js b/ui/js/actions/app.js
index ec2af78f0..3d81d6c69 100644
--- a/ui/js/actions/app.js
+++ b/ui/js/actions/app.js
@@ -5,18 +5,10 @@ import {
selectUpgradeDownloadPath,
selectUpgradeDownloadItem,
selectUpgradeFilename,
- selectPageTitle,
- selectCurrentPage,
- selectCurrentParams,
- selectHistoryBack,
- selectHistoryForward,
} from "selectors/app";
-import { doSearch } from "actions/search";
import { doFetchDaemonSettings } from "actions/settings";
import { doAuthenticate } from "actions/user";
import { doFileList } from "actions/file_info";
-import { toQueryString } from "util/query_params";
-import { parseQueryParams } from "util/query_params";
const { remote, ipcRenderer, shell } = require("electron");
const path = require("path");
@@ -24,130 +16,6 @@ const { download } = remote.require("electron-dl");
const fs = remote.require("fs");
const { lbrySettings: config } = require("../../../app/package.json");
-export function doNavigate(path, params = {}, options = {}) {
- return function(dispatch, getState) {
- if (!path) {
- return;
- }
-
- let url = path;
- if (params) url = `${url}?${toQueryString(params)}`;
-
- dispatch(doChangePath(url));
-
- const state = getState();
- const pageTitle = selectPageTitle(state);
- const historyState = history.state;
-
- dispatch(
- doHistoryPush({ params, page: historyState.page + 1 }, pageTitle, url)
- );
- };
-}
-
-export function doAuthNavigate(pathAfterAuth = null, params = {}) {
- return function(dispatch, getState) {
- if (pathAfterAuth) {
- dispatch({
- type: types.CHANGE_AFTER_AUTH_PATH,
- data: {
- path: `${pathAfterAuth}?${toQueryString(params)}`,
- },
- });
- }
- dispatch(doNavigate("/auth"));
- };
-}
-
-export function doChangePath(path, options = {}) {
- return function(dispatch, getState) {
- dispatch({
- type: types.CHANGE_PATH,
- data: {
- path,
- },
- });
-
- const state = getState();
- const pageTitle = selectPageTitle(state);
- const scrollY = options.scrollY;
-
- window.document.title = pageTitle;
-
- if (scrollY) window.scrollTo(0, scrollY);
- else window.scrollTo(0, 0);
-
- const currentPage = selectCurrentPage(state);
- if (currentPage === "search") {
- const params = selectCurrentParams(state);
- dispatch(doSearch(params.query));
- }
- };
-}
-
-export function doHistoryBack() {
- return function(dispatch, getState) {
- // Get back history from stack
- const back = selectHistoryBack(getState());
-
- if (back) {
- // Set location
- dispatch(doChangePath(back.location));
-
- dispatch({
- type: types.HISTORY_NAVIGATE,
- data: { page: back },
- });
- }
- };
-}
-
-export function doHistoryForward() {
- return function(dispatch, getState) {
- // Get forward history from stack
- const forward = selectHistoryForward(getState());
-
- if (forward) {
- // Set location
- dispatch(doChangePath(forward.location));
-
- dispatch({
- type: types.HISTORY_NAVIGATE,
- data: { page: forward },
- });
- }
- };
-}
-
-export function doHistoryPush(currentState, title, relativeUrl) {
- return function(dispatch, getState) {
- title += " - LBRY";
- history.pushState(currentState, title, `#${relativeUrl}`);
- dispatch({
- type: types.HISTORY_NAVIGATE,
- data: {
- location: relativeUrl,
- },
- });
- };
-}
-
-export function doRecordScroll(scroll) {
- return function(dispatch, getState) {
- const state = getState();
- const historyState = history.state;
-
- if (!historyState) return;
-
- historyState.scrollY = scroll;
- history.replaceState(
- historyState,
- document.title,
- `#${state.app.currentPath}`
- );
- };
-}
-
export function doOpenModal(modal) {
return {
type: types.OPEN_MODAL,
@@ -305,25 +173,8 @@ export function doAlertError(errorList) {
export function doDaemonReady() {
return function(dispatch, getState) {
- const path = window.location.hash || "#/discover";
- const params = parseQueryParams(path.split("?")[1] || "");
-
- // Get first page
- const page = {
- index: 0,
- location: path.replace(/^#/, ""),
- };
-
- history.replaceState(
- { params, is_first_page: true, page: 1 },
- document.title,
- `${path}`
- );
dispatch(doAuthenticate());
- dispatch({
- type: types.DAEMON_READY,
- data: { page },
- });
+ dispatch({ type: types.DAEMON_READY });
dispatch(doFetchDaemonSettings());
dispatch(doFileList());
};
diff --git a/ui/js/actions/file_info.js b/ui/js/actions/file_info.js
index c69db230c..7d9d102c6 100644
--- a/ui/js/actions/file_info.js
+++ b/ui/js/actions/file_info.js
@@ -12,7 +12,8 @@ import {
selectUrisLoading,
selectTotalDownloadProgress,
} from "selectors/file_info";
-import { doCloseModal, doHistoryBack } from "actions/app";
+import { doCloseModal } from "actions/app";
+import { doHistoryBack } from "actions/navigation";
import setProgressBar from "util/setProgressBar";
import batchActions from "util/batchActions";
diff --git a/ui/js/actions/navigation.js b/ui/js/actions/navigation.js
new file mode 100644
index 000000000..b34a2e876
--- /dev/null
+++ b/ui/js/actions/navigation.js
@@ -0,0 +1,104 @@
+import * as types from "constants/action_types";
+import {
+ selectPageTitle,
+ selectCurrentPage,
+ selectCurrentParams,
+ selectHistoryStack,
+ selectHistoryIndex,
+} from "selectors/navigation";
+import { doSearch } from "actions/search";
+import { toQueryString } from "util/query_params";
+
+export function doNavigate(path, params = {}, options = {}) {
+ return function(dispatch, getState) {
+ if (!path) {
+ return;
+ }
+
+ let url = path;
+ if (params && Object.values(params).length) {
+ url += "?" + toQueryString(params);
+ }
+
+ dispatch(doChangePath(url, options));
+
+ const pageTitle = selectPageTitle(getState()) + " - LBRY";
+
+ dispatch({
+ type: types.HISTORY_NAVIGATE,
+ data: { url, index: options.index },
+ });
+ };
+}
+
+export function doAuthNavigate(pathAfterAuth = null, params = {}) {
+ return function(dispatch, getState) {
+ if (pathAfterAuth) {
+ dispatch({
+ type: types.CHANGE_AFTER_AUTH_PATH,
+ data: {
+ path: `${pathAfterAuth}?${toQueryString(params)}`,
+ },
+ });
+ }
+ dispatch(doNavigate("/auth"));
+ };
+}
+
+export function doChangePath(path, options = {}) {
+ return function(dispatch, getState) {
+ dispatch({
+ type: types.CHANGE_PATH,
+ data: {
+ path,
+ },
+ });
+
+ const state = getState();
+ const scrollY = options.scrollY;
+
+ //I wasn't seeing it scroll to the proper position without this -- possibly because the page isn't fully rendered? Not sure - Jeremy
+ setTimeout(() => {
+ window.scrollTo(0, scrollY ? scrollY : 0);
+ }, 100);
+
+ const currentPage = selectCurrentPage(state);
+ if (currentPage === "search") {
+ const params = selectCurrentParams(state);
+ dispatch(doSearch(params.query));
+ }
+ };
+}
+
+export function doHistoryTraverse(dispatch, state, modifier) {
+ const stack = selectHistoryStack(state),
+ index = selectHistoryIndex(state) + modifier;
+
+ if (index >= 0 && index < stack.length) {
+ const historyItem = stack[index];
+ return dispatch(
+ doNavigate(historyItem.path, {}, { scrollY: historyItem.scrollY, index })
+ );
+ }
+}
+
+export function doHistoryBack() {
+ return function(dispatch, getState) {
+ return doHistoryTraverse(dispatch, getState(), -1);
+ };
+}
+
+export function doHistoryForward() {
+ return function(dispatch, getState) {
+ return doHistoryTraverse(dispatch, getState(), 1);
+ };
+}
+
+export function doRecordScroll(scroll) {
+ return function(dispatch, getState) {
+ dispatch({
+ type: types.WINDOW_SCROLLED,
+ data: { scrollY: scroll },
+ });
+ };
+}
diff --git a/ui/js/actions/search.js b/ui/js/actions/search.js
index a693dc26a..2d6be08ee 100644
--- a/ui/js/actions/search.js
+++ b/ui/js/actions/search.js
@@ -2,8 +2,8 @@ import * as types from "constants/action_types";
import lbryuri from "lbryuri";
import lighthouse from "lighthouse";
import { doResolveUri } from "actions/content";
-import { doNavigate, doHistoryPush } from "actions/app";
-import { selectCurrentPage } from "selectors/app";
+import { doNavigate } from "actions/navigation";
+import { selectCurrentPage } from "selectors/navigation";
import batchActions from "util/batchActions";
export function doSearch(query) {
diff --git a/ui/js/component/app/index.js b/ui/js/component/app/index.js
index fdacf035d..38527d94b 100644
--- a/ui/js/component/app/index.js
+++ b/ui/js/component/app/index.js
@@ -1,16 +1,18 @@
import React from "react";
import { connect } from "react-redux";
+import { selectPageTitle } from "selectors/navigation";
+import { selectUser } from "selectors/user";
import {
doCheckUpgradeAvailable,
doAlertError,
- doRecordScroll,
} from "actions/app";
+import { doRecordScroll } from "actions/navigation";
import { doFetchRewardedContent } from "actions/content";
import { doUpdateBalance } from "actions/wallet";
-import { selectUser } from "selectors/user";
import App from "./view";
const select = (state, props) => ({
+ pageTitle: selectPageTitle(state),
user: selectUser(state),
});
diff --git a/ui/js/component/app/view.jsx b/ui/js/component/app/view.jsx
index 32cb74247..e3c2465de 100644
--- a/ui/js/component/app/view.jsx
+++ b/ui/js/component/app/view.jsx
@@ -30,12 +30,22 @@ class App extends React.PureComponent {
this.scrollListener = () => this.props.recordScroll(window.scrollY);
window.addEventListener("scroll", this.scrollListener);
+
+ this.setTitleFromProps(this.props);
}
componentWillUnmount() {
window.removeEventListener("scroll", this.scrollListener);
}
+ componentWillReceiveProps(props) {
+ this.setTitleFromProps(props);
+ }
+
+ setTitleFromProps(props) {
+ window.document.title = props.pageTitle;
+ }
+
render() {
return (
diff --git a/ui/js/component/fileActions/index.js b/ui/js/component/fileActions/index.js
index 4311823d1..759abee47 100644
--- a/ui/js/component/fileActions/index.js
+++ b/ui/js/component/fileActions/index.js
@@ -1,13 +1,12 @@
import React from "react";
import { connect } from "react-redux";
-import { selectPlatform } from "selectors/app";
+import { selectPlatform, selectCurrentModal } from "selectors/app";
import {
makeSelectFileInfoForUri,
makeSelectDownloadingForUri,
makeSelectLoadingForUri,
} from "selectors/file_info";
import { makeSelectIsAvailableForUri } from "selectors/availability";
-import { selectCurrentModal } from "selectors/app";
import { makeSelectCostInfoForUri } from "selectors/cost_info";
import { doCloseModal, doOpenModal } from "actions/app";
import { doFetchAvailability } from "actions/availability";
diff --git a/ui/js/component/fileCard/index.js b/ui/js/component/fileCard/index.js
index 68ede91e1..d933736e8 100644
--- a/ui/js/component/fileCard/index.js
+++ b/ui/js/component/fileCard/index.js
@@ -1,6 +1,6 @@
import React from "react";
import { connect } from "react-redux";
-import { doNavigate } from "actions/app";
+import { doNavigate } from "actions/navigation";
import { doResolveUri } from "actions/content";
import { selectShowNsfw } from "selectors/settings";
import {
diff --git a/ui/js/component/fileListSearch/index.js b/ui/js/component/fileListSearch/index.js
index 8008aaaef..bdb88faab 100644
--- a/ui/js/component/fileListSearch/index.js
+++ b/ui/js/component/fileListSearch/index.js
@@ -6,7 +6,7 @@ import {
selectCurrentSearchResults,
selectSearchQuery,
} from "selectors/search";
-import { doNavigate } from "actions/app";
+import { doNavigate } from "actions/navigation";
import FileListSearch from "./view";
const select = state => ({
diff --git a/ui/js/component/fileTile/index.js b/ui/js/component/fileTile/index.js
index 0631ae849..2df504226 100644
--- a/ui/js/component/fileTile/index.js
+++ b/ui/js/component/fileTile/index.js
@@ -1,6 +1,6 @@
import React from "react";
import { connect } from "react-redux";
-import { doNavigate } from "actions/app";
+import { doNavigate } from "actions/navigation";
import { doResolveUri } from "actions/content";
import {
makeSelectClaimForUri,
diff --git a/ui/js/component/header/index.js b/ui/js/component/header/index.js
index 464a471c8..857a04beb 100644
--- a/ui/js/component/header/index.js
+++ b/ui/js/component/header/index.js
@@ -1,9 +1,16 @@
import React from "react";
import { formatCredits } from "util/formatCredits";
import { connect } from "react-redux";
-import { selectIsBackDisabled, selectIsForwardDisabled } from "selectors/app";
+import {
+ selectIsBackDisabled,
+ selectIsForwardDisabled,
+} from "selectors/navigation";
import { selectBalance } from "selectors/wallet";
-import { doNavigate, doHistoryBack, doHistoryForward } from "actions/app";
+import {
+ doNavigate,
+ doHistoryBack,
+ doHistoryForward,
+} from "actions/navigation";
import Header from "./view";
const select = state => ({
diff --git a/ui/js/component/nsfwOverlay/index.js b/ui/js/component/nsfwOverlay/index.js
index eb3242410..8395df0ac 100644
--- a/ui/js/component/nsfwOverlay/index.js
+++ b/ui/js/component/nsfwOverlay/index.js
@@ -1,6 +1,6 @@
import React from "react";
import { connect } from "react-redux";
-import { doNavigate } from "actions/app";
+import { doNavigate } from "actions/navigation";
import NsfwOverlay from "./view";
const perform = dispatch => ({
diff --git a/ui/js/component/rewardLink/index.js b/ui/js/component/rewardLink/index.js
index 3375fbfdf..aa2034835 100644
--- a/ui/js/component/rewardLink/index.js
+++ b/ui/js/component/rewardLink/index.js
@@ -5,7 +5,7 @@ import {
makeSelectRewardByType,
makeSelectIsRewardClaimPending,
} from "selectors/rewards";
-import { doNavigate } from "actions/app";
+import { doNavigate } from "actions/navigation";
import { doClaimRewardType, doClaimRewardClearError } from "actions/rewards";
import RewardLink from "./view";
diff --git a/ui/js/component/router/index.js b/ui/js/component/router/index.js
index bc40986f5..a5c0e9e63 100644
--- a/ui/js/component/router/index.js
+++ b/ui/js/component/router/index.js
@@ -1,7 +1,10 @@
import React from "react";
import { connect } from "react-redux";
import Router from "./view.jsx";
-import { selectCurrentPage, selectCurrentParams } from "selectors/app.js";
+import {
+ selectCurrentPage,
+ selectCurrentParams,
+} from "selectors/navigation.js";
const select = state => ({
params: selectCurrentParams(state),
diff --git a/ui/js/component/snackBar/index.js b/ui/js/component/snackBar/index.js
index 7996009c4..ff083c3d3 100644
--- a/ui/js/component/snackBar/index.js
+++ b/ui/js/component/snackBar/index.js
@@ -1,11 +1,10 @@
import React from "react";
import { connect } from "react-redux";
-import { doNavigate, doRemoveSnackBarSnack } from "actions/app";
+import { doRemoveSnackBarSnack } from "actions/app";
import { selectSnackBarSnacks } from "selectors/app";
import SnackBar from "./view";
const perform = dispatch => ({
- navigate: path => dispatch(doNavigate(path)),
removeSnack: () => dispatch(doRemoveSnackBarSnack()),
});
diff --git a/ui/js/component/snackBar/view.jsx b/ui/js/component/snackBar/view.jsx
index 363c412d6..f2ee2c49d 100644
--- a/ui/js/component/snackBar/view.jsx
+++ b/ui/js/component/snackBar/view.jsx
@@ -10,7 +10,7 @@ class SnackBar extends React.PureComponent {
}
render() {
- const { navigate, snacks, removeSnack } = this.props;
+ const { snacks, removeSnack } = this.props;
if (!snacks.length) {
this._hideTimeout = null; //should be unmounting anyway, but be safe?
@@ -33,7 +33,7 @@ class SnackBar extends React.PureComponent {
{linkText &&
linkTarget &&
navigate(linkTarget)}
+ navigate={linkTarget}
className="snack-bar__action"
label={linkText}
/>}
diff --git a/ui/js/component/subHeader/index.js b/ui/js/component/subHeader/index.js
index 97a991436..2806828ae 100644
--- a/ui/js/component/subHeader/index.js
+++ b/ui/js/component/subHeader/index.js
@@ -1,7 +1,7 @@
import React from "react";
import { connect } from "react-redux";
-import { selectCurrentPage, selectHeaderLinks } from "selectors/app";
-import { doNavigate } from "actions/app";
+import { selectCurrentPage, selectHeaderLinks } from "selectors/navigation";
+import { doNavigate } from "actions/navigation";
import SubHeader from "./view";
const select = (state, props) => ({
diff --git a/ui/js/component/userVerify/index.js b/ui/js/component/userVerify/index.js
index 043b52c75..b90719084 100644
--- a/ui/js/component/userVerify/index.js
+++ b/ui/js/component/userVerify/index.js
@@ -1,6 +1,6 @@
import React from "react";
import { connect } from "react-redux";
-import { doNavigate } from "actions/app";
+import { doNavigate } from "actions/navigation";
import { doUserIdentityVerify } from "actions/user";
import rewards from "rewards";
import { makeSelectRewardByType } from "selectors/rewards";
diff --git a/ui/js/component/video/index.js b/ui/js/component/video/index.js
index dc4b465cd..f28fb94b8 100644
--- a/ui/js/component/video/index.js
+++ b/ui/js/component/video/index.js
@@ -1,7 +1,7 @@
import React from "react";
import { connect } from "react-redux";
import { doCloseModal } from "actions/app";
-import { doNavigate, doChangeVolume } from "actions/app";
+import { doChangeVolume } from "actions/app";
import { selectCurrentModal, selectVolume } from "selectors/app";
import { doPurchaseUri, doLoadVideo } from "actions/content";
import {
diff --git a/ui/js/component/wunderbar/index.js b/ui/js/component/wunderbar/index.js
index 9d23f4d26..bd327fd92 100644
--- a/ui/js/component/wunderbar/index.js
+++ b/ui/js/component/wunderbar/index.js
@@ -2,7 +2,7 @@ import React from "react";
import { connect } from "react-redux";
import lbryuri from "lbryuri.js";
import { selectWunderBarAddress, selectWunderBarIcon } from "selectors/search";
-import { doNavigate } from "actions/app";
+import { doNavigate } from "actions/navigation";
import Wunderbar from "./view";
const select = state => ({
diff --git a/ui/js/constants/action_types.js b/ui/js/constants/action_types.js
index edb60621f..84c8e0e06 100644
--- a/ui/js/constants/action_types.js
+++ b/ui/js/constants/action_types.js
@@ -1,16 +1,19 @@
-export const CHANGE_PATH = "CHANGE_PATH";
export const OPEN_MODAL = "OPEN_MODAL";
export const CLOSE_MODAL = "CLOSE_MODAL";
-export const HISTORY_NAVIGATE = "HISTORY_NAVIGATE";
export const SHOW_SNACKBAR = "SHOW_SNACKBAR";
export const REMOVE_SNACKBAR_SNACK = "REMOVE_SNACKBAR_SNACK";
export const WINDOW_FOCUSED = "WINDOW_FOCUSED";
-export const CHANGE_AFTER_AUTH_PATH = "CHANGE_AFTER_AUTH_PATH";
export const DAEMON_READY = "DAEMON_READY";
export const DAEMON_VERSION_MATCH = "DAEMON_VERSION_MATCH";
export const DAEMON_VERSION_MISMATCH = "DAEMON_VERSION_MISMATCH";
export const VOLUME_CHANGED = "VOLUME_CHANGED";
+// Navigation
+export const CHANGE_PATH = "CHANGE_PATH";
+export const CHANGE_AFTER_AUTH_PATH = "CHANGE_AFTER_AUTH_PATH";
+export const WINDOW_SCROLLED = "WINDOW_SCROLLED";
+export const HISTORY_NAVIGATE = "HISTORY_NAVIGATE";
+
// Upgrades
export const UPGRADE_CANCELLED = "UPGRADE_CANCELLED";
export const DOWNLOAD_UPGRADE = "DOWNLOAD_UPGRADE";
diff --git a/ui/js/main.js b/ui/js/main.js
index 42c40a3a2..7dadc22b8 100644
--- a/ui/js/main.js
+++ b/ui/js/main.js
@@ -6,9 +6,9 @@ import SnackBar from "component/snackBar";
import { Provider } from "react-redux";
import store from "store.js";
import SplashScreen from "component/splash";
-import { doChangePath, doNavigate, doDaemonReady } from "actions/app";
+import { doDaemonReady } from "actions/app";
+import { doNavigate } from "actions/navigation";
import { doDownloadLanguages } from "actions/settings";
-import { toQueryString } from "util/query_params";
import * as types from "constants/action_types";
const env = ENV;
@@ -28,23 +28,6 @@ window.addEventListener("contextmenu", event => {
event.preventDefault();
});
-window.addEventListener("popstate", (event, param) => {
- event.preventDefault();
-
- const hash = document.location.hash;
- let action;
-
- if (hash !== "") {
- const url = hash.replace(/^#/, "");
- const { params, scrollY } = event.state || {};
- const queryString = toQueryString(params);
-
- app.store.dispatch(doChangePath(`${url}?${queryString}`, { scrollY }));
- } else {
- app.store.dispatch(doChangePath("/discover"));
- }
-});
-
ipcRenderer.on("open-uri-requested", (event, uri) => {
if (uri && uri.startsWith("lbry://")) {
app.store.dispatch(doNavigate("/show", { uri }));
diff --git a/ui/js/modal/modalCreditIntro/index.js b/ui/js/modal/modalCreditIntro/index.js
index 37e72dc9a..c793ec22a 100644
--- a/ui/js/modal/modalCreditIntro/index.js
+++ b/ui/js/modal/modalCreditIntro/index.js
@@ -1,6 +1,7 @@
import React from "react";
import { connect } from "react-redux";
-import { doCloseModal, doAuthNavigate } from "actions/app";
+import { doCloseModal } from "actions/app";
+import { doAuthNavigate } from "actions/navigation";
import { doSetClientSetting } from "actions/settings";
import { selectUserIsRewardApproved } from "selectors/user";
import { selectBalance } from "selectors/wallet";
diff --git a/ui/js/modal/modalInsufficientCredits/index.js b/ui/js/modal/modalInsufficientCredits/index.js
index eefaa7c68..c50d34d44 100644
--- a/ui/js/modal/modalInsufficientCredits/index.js
+++ b/ui/js/modal/modalInsufficientCredits/index.js
@@ -1,6 +1,7 @@
import React from "react";
import { connect } from "react-redux";
-import { doCloseModal, doNavigate } from "actions/app";
+import { doCloseModal } from "actions/app";
+import { doNavigate } from "actions/navigation";
import ModalInsufficientCredits from "./view";
const select = state => ({});
diff --git a/ui/js/modal/modalRemoveFile/index.js b/ui/js/modal/modalRemoveFile/index.js
index 211bdff26..f64c1987e 100644
--- a/ui/js/modal/modalRemoveFile/index.js
+++ b/ui/js/modal/modalRemoveFile/index.js
@@ -1,9 +1,8 @@
import React from "react";
import { connect } from "react-redux";
-import { doCloseModal, doHistoryBack } from "actions/app";
+import { doCloseModal } from "actions/app";
import { doDeleteFileAndGoBack } from "actions/file_info";
import { makeSelectClaimForUriIsMine } from "selectors/claims";
-import batchActions from "util/batchActions";
import ModalRemoveFile from "./view";
diff --git a/ui/js/modal/modalRouter/index.js b/ui/js/modal/modalRouter/index.js
index 9bf9601c5..79f02385f 100644
--- a/ui/js/modal/modalRouter/index.js
+++ b/ui/js/modal/modalRouter/index.js
@@ -1,11 +1,12 @@
import React from "react";
import { connect } from "react-redux";
-import { selectCurrentModal, selectCurrentPage } from "selectors/app";
import { doOpenModal } from "actions/app";
+import * as settings from "constants/settings";
+import { selectCurrentModal } from "selectors/app";
+import { selectCurrentPage } from "selectors/navigation";
+import { selectCostForCurrentPageUri } from "selectors/cost_info";
import { makeSelectClientSetting } from "selectors/settings";
import { selectUser } from "selectors/user";
-import { selectCostForCurrentPageUri } from "selectors/cost_info";
-import * as settings from "constants/settings";
import { selectBalance } from "selectors/wallet";
import ModalRouter from "./view";
diff --git a/ui/js/page/auth/index.js b/ui/js/page/auth/index.js
index 541504dbb..205400088 100644
--- a/ui/js/page/auth/index.js
+++ b/ui/js/page/auth/index.js
@@ -1,7 +1,7 @@
import React from "react";
-import { doNavigate } from "actions/app";
+import { doNavigate } from "actions/navigation";
import { connect } from "react-redux";
-import { selectPathAfterAuth } from "selectors/app";
+import { selectPathAfterAuth } from "selectors/navigation";
import {
selectAuthenticationIsPending,
selectEmailToVerify,
diff --git a/ui/js/page/channel/index.js b/ui/js/page/channel/index.js
index dfd75d5fe..95134bdc3 100644
--- a/ui/js/page/channel/index.js
+++ b/ui/js/page/channel/index.js
@@ -9,8 +9,8 @@ import {
makeSelectClaimsInChannelForCurrentPage,
makeSelectFetchingChannelClaims,
} from "selectors/claims";
-import { selectCurrentParams } from "selectors/app";
-import { doNavigate } from "actions/app";
+import { selectCurrentParams } from "selectors/navigation";
+import { doNavigate } from "actions/navigation";
import { makeSelectTotalPagesForChannel } from "selectors/content";
import ChannelPage from "./view";
diff --git a/ui/js/page/file/index.js b/ui/js/page/file/index.js
index 26b98c93a..19cdde31f 100644
--- a/ui/js/page/file/index.js
+++ b/ui/js/page/file/index.js
@@ -1,6 +1,6 @@
import React from "react";
import { connect } from "react-redux";
-import { doNavigate } from "actions/app";
+import { doNavigate } from "actions/navigation";
import { doFetchFileInfo } from "actions/file_info";
import { makeSelectFileInfoForUri } from "selectors/file_info";
import { selectRewardContentClaimIds } from "selectors/content";
diff --git a/ui/js/page/fileListDownloaded/index.js b/ui/js/page/fileListDownloaded/index.js
index c2473e02a..449d3f196 100644
--- a/ui/js/page/fileListDownloaded/index.js
+++ b/ui/js/page/fileListDownloaded/index.js
@@ -10,7 +10,7 @@ import {
selectIsFetchingClaimListMine,
} from "selectors/claims";
import { doFetchClaimListMine } from "actions/content";
-import { doNavigate } from "actions/app";
+import { doNavigate } from "actions/navigation";
import { doCancelAllResolvingUris } from "actions/content";
import FileListDownloaded from "./view";
diff --git a/ui/js/page/fileListPublished/index.js b/ui/js/page/fileListPublished/index.js
index 8100c2977..c6bf2a43a 100644
--- a/ui/js/page/fileListPublished/index.js
+++ b/ui/js/page/fileListPublished/index.js
@@ -7,7 +7,7 @@ import {
selectIsFetchingClaimListMine,
} from "selectors/claims";
import { doClaimRewardType } from "actions/rewards";
-import { doNavigate } from "actions/app";
+import { doNavigate } from "actions/navigation";
import { doCancelAllResolvingUris } from "actions/content";
import FileListPublished from "./view";
diff --git a/ui/js/page/help/index.js b/ui/js/page/help/index.js
index 93492d915..601cdb8a7 100644
--- a/ui/js/page/help/index.js
+++ b/ui/js/page/help/index.js
@@ -1,5 +1,5 @@
import React from "react";
-import { doAuthNavigate } from "actions/app";
+import { doAuthNavigate } from "actions/navigation";
import { connect } from "react-redux";
import { doFetchAccessToken } from "actions/user";
import { selectAccessToken, selectUser } from "selectors/user";
diff --git a/ui/js/page/publish/index.js b/ui/js/page/publish/index.js
index 1816bf2d5..f0547941d 100644
--- a/ui/js/page/publish/index.js
+++ b/ui/js/page/publish/index.js
@@ -1,6 +1,6 @@
import React from "react";
import { connect } from "react-redux";
-import { doNavigate, doHistoryBack } from "actions/app";
+import { doNavigate, doHistoryBack } from "actions/navigation";
import { doClaimRewardType } from "actions/rewards";
import {
selectMyClaims,
diff --git a/ui/js/page/rewards/index.js b/ui/js/page/rewards/index.js
index db479ff15..e9dca47f9 100644
--- a/ui/js/page/rewards/index.js
+++ b/ui/js/page/rewards/index.js
@@ -5,7 +5,7 @@ import {
selectUnclaimedRewards,
} from "selectors/rewards";
import { selectUser } from "selectors/user";
-import { doAuthNavigate, doNavigate } from "actions/app";
+import { doAuthNavigate, doNavigate } from "actions/navigation";
import { doRewardList } from "actions/rewards";
import RewardsPage from "./view";
diff --git a/ui/js/page/search/index.js b/ui/js/page/search/index.js
index c8d93fae1..a6c571882 100644
--- a/ui/js/page/search/index.js
+++ b/ui/js/page/search/index.js
@@ -5,7 +5,7 @@ import {
selectSearchQuery,
selectCurrentSearchResults,
} from "selectors/search";
-import { doNavigate } from "actions/app";
+import { doNavigate } from "actions/navigation";
import SearchPage from "./view";
const select = state => ({
diff --git a/ui/js/reducers/app.js b/ui/js/reducers/app.js
index dd0237f28..857a5a708 100644
--- a/ui/js/reducers/app.js
+++ b/ui/js/reducers/app.js
@@ -1,12 +1,6 @@
import * as types from "constants/action_types";
import * as modalTypes from "constants/modal_types";
-const currentPath = () => {
- const hash = document.location.hash;
- if (hash !== "") return hash.replace(/^#/, "");
- else return "/discover";
-};
-
const { remote } = require("electron");
const application = remote.app;
const win = remote.BrowserWindow.getFocusedWindow();
@@ -14,28 +8,18 @@ const win = remote.BrowserWindow.getFocusedWindow();
const reducers = {};
const defaultState = {
isLoaded: false,
- isBackDisabled: true,
- isForwardDisabled: true,
- currentPath: currentPath(),
- pathAfterAuth: "/discover",
platform: process.platform,
upgradeSkipped: sessionStorage.getItem("upgradeSkipped"),
daemonVersionMatched: null,
daemonReady: false,
hasSignature: false,
badgeNumber: 0,
- history: { index: 0, stack: [] },
volume: sessionStorage.getItem("volume") || 1,
};
reducers[types.DAEMON_READY] = function(state, action) {
- const { history } = state;
- const { page } = action.data;
- history.stack.push(page);
-
return Object.assign({}, state, {
daemonReady: true,
- history,
});
};
@@ -52,18 +36,6 @@ reducers[types.DAEMON_VERSION_MISMATCH] = function(state, action) {
});
};
-reducers[types.CHANGE_PATH] = function(state, action) {
- return Object.assign({}, state, {
- currentPath: action.data.path,
- });
-};
-
-reducers[types.CHANGE_AFTER_AUTH_PATH] = function(state, action) {
- return Object.assign({}, state, {
- pathAfterAuth: action.data.path,
- });
-};
-
reducers[types.UPGRADE_CANCELLED] = function(state, action) {
return Object.assign({}, state, {
downloadProgress: null,
@@ -171,55 +143,6 @@ reducers[types.WINDOW_FOCUSED] = function(state, action) {
});
};
-reducers[types.HISTORY_NAVIGATE] = (state, action) => {
- let page = false;
- let location = false;
-
- // Get history from state
- const { history } = state;
-
- if (action.data.page) {
- // Get page
- page = action.data.page;
- } else if (action.data.location) {
- // Get new location
- location = action.data.location;
- }
-
- // Add new location to stack
- if (location) {
- const lastItem = history.stack.length - 1;
-
- // Check for duplicated
- let is_duplicate = lastItem > -1
- ? history.stack[lastItem].location === location
- : false;
-
- if (!is_duplicate) {
- // Create new page
- page = {
- index: history.stack.length,
- location,
- };
-
- // Update index
- history.index = history.stack.length;
-
- // Add to stack
- history.stack.push(page);
- }
- } else if (page) {
- // Update index
- history.index = page.index;
- }
-
- return Object.assign({}, state, {
- history,
- isBackDisabled: history.index === 0, // First page
- isForwardDisabled: history.index === history.stack.length - 1, // Last page
- });
-};
-
reducers[types.VOLUME_CHANGED] = function(state, action) {
return Object.assign({}, state, {
volume: action.data.volume,
diff --git a/ui/js/reducers/navigation.js b/ui/js/reducers/navigation.js
new file mode 100644
index 000000000..eca9bd008
--- /dev/null
+++ b/ui/js/reducers/navigation.js
@@ -0,0 +1,85 @@
+import * as types from "constants/action_types";
+import { parseQueryParams } from "util/query_params";
+
+const currentPath = () => {
+ const hash = document.location.hash;
+ if (hash !== "") return hash.replace(/^#/, "");
+ else return "/discover";
+};
+
+const reducers = {};
+const defaultState = {
+ currentPath: currentPath(),
+ pathAfterAuth: "/discover",
+ index: 0,
+ stack: [],
+};
+
+reducers[types.DAEMON_READY] = function(state, action) {
+ const { currentPath } = state;
+ const params = parseQueryParams(currentPath.split("?")[1] || "");
+
+ return Object.assign({}, state, {
+ stack: [{ path: currentPath, scrollY: 0 }],
+ });
+};
+
+reducers[types.CHANGE_PATH] = function(state, action) {
+ return Object.assign({}, state, {
+ currentPath: action.data.path,
+ });
+};
+
+reducers[types.CHANGE_AFTER_AUTH_PATH] = function(state, action) {
+ return Object.assign({}, state, {
+ pathAfterAuth: action.data.path,
+ });
+};
+
+reducers[types.HISTORY_NAVIGATE] = (state, action) => {
+ const { stack, index } = state;
+
+ let newState = {};
+
+ const path = action.data.url,
+ previousIndex = index - 1;
+
+ // Check for duplicated
+ if (action.data.index >= 0) {
+ newState.index = action.data.index;
+ } else if (
+ previousIndex === -1 ||
+ !stack[previousIndex] ||
+ stack[previousIndex].path !== path
+ ) {
+ newState.stack = [...stack.slice(0, index + 1), { path, scrollY: 0 }];
+ newState.index = newState.stack.length - 1;
+ }
+
+ history.replaceState(null, null, "#" + path); //this allows currentPath() to retain the URL on reload
+
+ return Object.assign({}, state, newState);
+};
+
+reducers[types.WINDOW_SCROLLED] = (state, action) => {
+ const { stack, index } = state;
+ const { scrollY } = action.data;
+
+ return Object.assign({}, state, {
+ stack: state.stack.map((stackItem, itemIndex) => {
+ if (itemIndex !== index) {
+ return stackItem;
+ }
+ return {
+ ...stackItem,
+ scrollY: scrollY,
+ };
+ }),
+ });
+};
+
+export default function reducer(state = defaultState, action) {
+ const handler = reducers[action.type];
+ if (handler) return handler(state, action);
+ return state;
+}
diff --git a/ui/js/selectors/app.js b/ui/js/selectors/app.js
index eb950f3b1..3e8e68223 100644
--- a/ui/js/selectors/app.js
+++ b/ui/js/selectors/app.js
@@ -1,88 +1,7 @@
import { createSelector } from "reselect";
-import { parseQueryParams, toQueryString } from "util/query_params";
-import * as settings from "constants/settings.js";
-import lbryuri from "lbryuri";
export const _selectState = state => state.app || {};
-export const selectIsLoaded = createSelector(
- _selectState,
- state => state.isLoaded
-);
-
-export const selectCurrentPath = createSelector(
- _selectState,
- state => state.currentPath
-);
-
-export const selectCurrentPage = createSelector(selectCurrentPath, path => {
- return path.replace(/^\//, "").split("?")[0];
-});
-
-export const selectCurrentParams = createSelector(selectCurrentPath, path => {
- if (path === undefined) return {};
- if (!path.match(/\?/)) return {};
-
- return parseQueryParams(path.split("?")[1]);
-});
-
-export const selectPageTitle = createSelector(
- selectCurrentPage,
- selectCurrentParams,
- (page, params) => {
- switch (page) {
- case "settings":
- return __("Settings");
- case "report":
- return __("Report");
- case "wallet":
- return __("Wallet");
- case "send":
- return __("Send Credits");
- case "receive":
- return __("Wallet Address");
- case "backup":
- return __("Backup Your Wallet");
- case "rewards":
- return __("Rewards");
- case "invite":
- return __("Invites");
- case "start":
- return __("Start");
- case "publish":
- return __("Publish");
- case "help":
- return __("Help");
- case "developer":
- return __("Developer");
- case "search":
- return params.query
- ? __("Search results for %s", params.query)
- : __("Search");
- case "show": {
- const parts = [lbryuri.normalize(params.uri)];
- // If the params has any keys other than "uri"
- if (Object.keys(params).length > 1) {
- parts.push(toQueryString(Object.assign({}, params, { uri: null })));
- }
- return parts.join("?");
- }
- case "downloaded":
- return __("Downloads & Purchases");
- case "published":
- return __("Publishes");
- case "discover":
- return __("Home");
- case false:
- case null:
- case "":
- return "";
- default:
- return page[0].toUpperCase() + (page.length > 0 ? page.substr(1) : "");
- }
- }
-);
-
export const selectPlatform = createSelector(
_selectState,
state => state.platform
@@ -137,41 +56,6 @@ export const selectDownloadComplete = createSelector(
state => state.upgradeDownloadCompleted
);
-export const selectHeaderLinks = createSelector(selectCurrentPage, page => {
- // This contains intentional fall throughs
- switch (page) {
- case "wallet":
- case "history":
- case "send":
- case "receive":
- case "invite":
- case "rewards":
- case "backup":
- return {
- wallet: __("Overview"),
- history: __("History"),
- send: __("Send"),
- receive: __("Receive"),
- rewards: __("Rewards"),
- invite: __("Invites"),
- };
- case "downloaded":
- case "published":
- return {
- downloaded: __("Downloaded"),
- published: __("Published"),
- };
- case "settings":
- case "help":
- return {
- settings: __("Settings"),
- help: __("Help"),
- };
- default:
- return null;
- }
-});
-
export const selectUpgradeSkipped = createSelector(
_selectState,
state => state.upgradeSkipped
@@ -222,41 +106,4 @@ export const selectCurrentLanguage = createSelector(
() => app.i18n.getLocale() || "en"
);
-export const selectPathAfterAuth = createSelector(
- _selectState,
- state => state.pathAfterAuth
-);
-
-export const selectIsBackDisabled = createSelector(
- _selectState,
- state => state.isBackDisabled
-);
-
-export const selectIsForwardDisabled = createSelector(
- _selectState,
- state => state.isForwardDisabled
-);
-
-export const selectHistoryBack = createSelector(_selectState, state => {
- const { history } = state;
- const index = history.index - 1;
-
- // Check if page exists
- if (index > -1) {
- // Get back history
- return history.stack[index];
- }
-});
-
-export const selectHistoryForward = createSelector(_selectState, state => {
- const { history } = state;
- const index = history.index + 1;
-
- // Check if page exists
- if (index <= history.stack.length) {
- // Get forward history
- return history.stack[index];
- }
-});
-
export const selectVolume = createSelector(_selectState, state => state.volume);
diff --git a/ui/js/selectors/claims.js b/ui/js/selectors/claims.js
index 2c4b4b511..630e72c3e 100644
--- a/ui/js/selectors/claims.js
+++ b/ui/js/selectors/claims.js
@@ -1,5 +1,5 @@
import { createSelector } from "reselect";
-import { selectCurrentParams } from "selectors/app";
+import { selectCurrentParams } from "selectors/navigation";
import lbryuri from "lbryuri";
const _selectState = state => state.claims || {};
diff --git a/ui/js/selectors/cost_info.js b/ui/js/selectors/cost_info.js
index 034c4572f..b40d62d5e 100644
--- a/ui/js/selectors/cost_info.js
+++ b/ui/js/selectors/cost_info.js
@@ -1,5 +1,5 @@
import { createSelector } from "reselect";
-import { selectCurrentParams } from "./app";
+import { selectCurrentParams } from "selectors/navigation";
export const _selectState = state => state.costInfo || {};
diff --git a/ui/js/selectors/navigation.js b/ui/js/selectors/navigation.js
new file mode 100644
index 000000000..16e1c53f9
--- /dev/null
+++ b/ui/js/selectors/navigation.js
@@ -0,0 +1,129 @@
+import { createSelector } from "reselect";
+import { parseQueryParams, toQueryString } from "util/query_params";
+import * as settings from "constants/settings.js";
+import lbryuri from "lbryuri";
+
+export const _selectState = state => state.navigation || {};
+
+export const selectCurrentPath = createSelector(
+ _selectState,
+ state => state.currentPath
+);
+
+export const selectCurrentPage = createSelector(selectCurrentPath, path => {
+ return path.replace(/^\//, "").split("?")[0];
+});
+
+export const selectCurrentParams = createSelector(selectCurrentPath, path => {
+ if (path === undefined) return {};
+ if (!path.match(/\?/)) return {};
+
+ return parseQueryParams(path.split("?")[1]);
+});
+
+export const selectHeaderLinks = createSelector(selectCurrentPage, page => {
+ // This contains intentional fall throughs
+ switch (page) {
+ case "wallet":
+ case "send":
+ case "receive":
+ case "rewards":
+ case "backup":
+ return {
+ wallet: __("Overview"),
+ send: __("Send"),
+ receive: __("Receive"),
+ rewards: __("Rewards"),
+ };
+ case "downloaded":
+ case "published":
+ return {
+ downloaded: __("Downloaded"),
+ published: __("Published"),
+ };
+ case "settings":
+ case "help":
+ return {
+ settings: __("Settings"),
+ help: __("Help"),
+ };
+ default:
+ return null;
+ }
+});
+
+export const selectPageTitle = createSelector(
+ selectCurrentPage,
+ selectCurrentParams,
+ (page, params) => {
+ switch (page) {
+ case "settings":
+ return __("Settings");
+ case "report":
+ return __("Report");
+ case "wallet":
+ return __("Wallet");
+ case "send":
+ return __("Send");
+ case "receive":
+ return __("Receive");
+ case "backup":
+ return __("Backup");
+ case "rewards":
+ return __("Rewards");
+ case "start":
+ return __("Start");
+ case "publish":
+ return __("Publish");
+ case "help":
+ return __("Help");
+ case "developer":
+ return __("Developer");
+ case "search":
+ return params.query
+ ? __("Search results for %s", params.query)
+ : __("Search");
+ case "show": {
+ const parts = [lbryuri.normalize(params.uri)];
+ // If the params has any keys other than "uri"
+ if (Object.keys(params).length > 1) {
+ parts.push(toQueryString(Object.assign({}, params, { uri: null })));
+ }
+ return parts.join("?");
+ }
+ case "downloaded":
+ return __("Downloads & Purchases");
+ case "published":
+ return __("Publishes");
+ case "discover":
+ return __("Home");
+ default:
+ return "";
+ }
+ }
+);
+
+export const selectPathAfterAuth = createSelector(
+ _selectState,
+ state => state.pathAfterAuth
+);
+
+export const selectIsBackDisabled = createSelector(
+ _selectState,
+ state => state.index === 0
+);
+
+export const selectIsForwardDisabled = createSelector(
+ _selectState,
+ state => state.index === state.stack.length - 1
+);
+
+export const selectHistoryIndex = createSelector(
+ _selectState,
+ state => state.index
+);
+
+export const selectHistoryStack = createSelector(
+ _selectState,
+ state => state.stack
+);
diff --git a/ui/js/selectors/search.js b/ui/js/selectors/search.js
index 2ef8a1410..457126597 100644
--- a/ui/js/selectors/search.js
+++ b/ui/js/selectors/search.js
@@ -1,5 +1,5 @@
import { createSelector } from "reselect";
-import { selectPageTitle, selectCurrentPage } from "selectors/app";
+import { selectPageTitle, selectCurrentPage } from "selectors/navigation";
export const _selectState = state => state.search || {};
diff --git a/ui/js/selectors/wallet.js b/ui/js/selectors/wallet.js
index 1334a17db..84ca37f8e 100644
--- a/ui/js/selectors/wallet.js
+++ b/ui/js/selectors/wallet.js
@@ -1,5 +1,4 @@
import { createSelector } from "reselect";
-import { selectCurrentPage, selectDaemonReady } from "selectors/app";
export const _selectState = state => state.wallet || {};
diff --git a/ui/js/store.js b/ui/js/store.js
index 5eb84d4cf..af1e68c3a 100644
--- a/ui/js/store.js
+++ b/ui/js/store.js
@@ -5,6 +5,7 @@ import claimsReducer from "reducers/claims";
import contentReducer from "reducers/content";
import costInfoReducer from "reducers/cost_info";
import fileInfoReducer from "reducers/file_info";
+import navigationReducer from "reducers/navigation";
import rewardsReducer from "reducers/rewards";
import searchReducer from "reducers/search";
import settingsReducer from "reducers/settings";
@@ -13,8 +14,8 @@ import walletReducer from "reducers/wallet";
import { persistStore, autoRehydrate } from "redux-persist";
import createCompressor from "redux-persist-transform-compress";
import createFilter from "redux-persist-transform-filter";
-import { REHYDRATE } from "redux-persist/constants";
-import createActionBuffer from "redux-action-buffer";
+//import { REHYDRATE } from "redux-persist/constants";
+//import createActionBuffer from "redux-action-buffer";
const localForage = require("localforage");
const redux = require("redux");
@@ -55,6 +56,7 @@ function enableBatching(reducer) {
const reducers = redux.combineReducers({
app: appReducer,
+ navigation: navigationReducer,
availability: availabilityReducer,
claims: claimsReducer,
fileInfo: fileInfoReducer,