Merge pull request #750 from seanyesmunt/scroll-issues

refactor scroll navigation/restore
This commit is contained in:
Sean Yesmunt 2017-11-17 16:35:03 -05:00 committed by GitHub
commit dd978109f6
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 102 additions and 24 deletions

View file

@ -12,12 +12,12 @@ Web UI version numbers should always match the corresponding version of LBRY App
* *
### Changed ### Changed
* * Moved all redux code into /redux folder
* *
### Fixed ### Fixed
* Long channel names causing inconsistent thumbnail sizes (#721) * Long channel names causing inconsistent thumbnail sizes (#721)
* * Fixed scriolling restore/reset/set (#729)
### Deprecated ### Deprecated
* *

View file

@ -1,6 +1,10 @@
import React from "react"; import React from "react";
import { connect } from "react-redux"; import { connect } from "react-redux";
import { selectPageTitle } from "redux/selectors/navigation"; import {
selectPageTitle,
selectHistoryIndex,
selectActiveHistoryEntry,
} from "redux/selectors/navigation";
import { selectUser } from "redux/selectors/user"; import { selectUser } from "redux/selectors/user";
import { doCheckUpgradeAvailable, doAlertError } from "redux/actions/app"; import { doCheckUpgradeAvailable, doAlertError } from "redux/actions/app";
import { doRecordScroll } from "redux/actions/navigation"; import { doRecordScroll } from "redux/actions/navigation";
@ -10,6 +14,8 @@ import App from "./view";
const select = (state, props) => ({ const select = (state, props) => ({
pageTitle: selectPageTitle(state), pageTitle: selectPageTitle(state),
user: selectUser(state), user: selectUser(state),
currentStackIndex: selectHistoryIndex(state),
currentPageAttributes: selectActiveHistoryEntry(state),
}); });
const perform = dispatch => ({ const perform = dispatch => ({

View file

@ -4,8 +4,14 @@ import Header from "component/header";
import Theme from "component/theme"; import Theme from "component/theme";
import ModalRouter from "modal/modalRouter"; import ModalRouter from "modal/modalRouter";
import lbry from "lbry"; import lbry from "lbry";
import throttle from "util/throttle";
class App extends React.PureComponent { class App extends React.PureComponent {
constructor() {
super();
this.mainContent = undefined;
}
componentWillMount() { componentWillMount() {
const { const {
alertError, alertError,
@ -23,21 +29,36 @@ class App extends React.PureComponent {
fetchRewardedContent(); fetchRewardedContent();
this.scrollListener = () => this.props.recordScroll(window.scrollY);
window.addEventListener("scroll", this.scrollListener);
this.setTitleFromProps(this.props); this.setTitleFromProps(this.props);
} }
componentDidMount() {
const { recordScroll } = this.props;
const mainContent = document.getElementById("main-content");
this.mainContent = mainContent;
const scrollListener = () => recordScroll(this.mainContent.scrollTop);
this.mainContent.addEventListener("scroll", throttle(scrollListener, 750));
}
componentWillUnmount() { componentWillUnmount() {
window.removeEventListener("scroll", this.scrollListener); this.mainContent.removeEventListener("scroll", this.scrollListener);
} }
componentWillReceiveProps(props) { componentWillReceiveProps(props) {
this.setTitleFromProps(props); this.setTitleFromProps(props);
} }
componentDidUpdate(prevProps) {
const { currentStackIndex: prevStackIndex } = prevProps;
const { currentStackIndex, currentPageAttributes } = this.props;
if (currentStackIndex !== prevStackIndex) {
this.mainContent.scrollTop = currentPageAttributes.scrollY || 0;
}
}
setTitleFromProps(props) { setTitleFromProps(props) {
window.document.title = props.pageTitle || "LBRY"; window.document.title = props.pageTitle || "LBRY";
} }

View file

@ -21,21 +21,11 @@ export function doNavigate(path, params = {}, options = {}) {
url += "?" + toQueryString(params); url += "?" + toQueryString(params);
} }
const state = getState(), const scrollY = options.scrollY;
currentPage = selectCurrentPage(state),
nextPage = computePageFromPath(path),
scrollY = options.scrollY;
if (currentPage != nextPage) {
//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);
}
dispatch({ dispatch({
type: types.HISTORY_NAVIGATE, type: types.HISTORY_NAVIGATE,
data: { url, index: options.index }, data: { url, index: options.index, scrollY },
}); });
}; };
} }

View file

@ -32,14 +32,14 @@ reducers[types.CHANGE_AFTER_AUTH_PATH] = function(state, action) {
reducers[types.HISTORY_NAVIGATE] = (state, action) => { reducers[types.HISTORY_NAVIGATE] = (state, action) => {
const { stack, index } = state; const { stack, index } = state;
const path = action.data.url; const { url: path, index: newIndex, scrollY } = action.data;
let newState = { let newState = {
currentPath: path, currentPath: path,
}; };
if (action.data.index >= 0) { if (newIndex >= 0) {
newState.index = action.data.index; newState.index = newIndex;
} else if (!stack[index] || stack[index].path !== path) { } else if (!stack[index] || stack[index].path !== path) {
// ^ Check for duplicated // ^ Check for duplicated
newState.stack = [...stack.slice(0, index + 1), { path, scrollY: 0 }]; newState.stack = [...stack.slice(0, index + 1), { path, scrollY: 0 }];
@ -47,7 +47,6 @@ reducers[types.HISTORY_NAVIGATE] = (state, action) => {
} }
history.replaceState(null, null, "#" + path); //this allows currentPath() to retain the URL on reload history.replaceState(null, null, "#" + path); //this allows currentPath() to retain the URL on reload
return Object.assign({}, state, newState); return Object.assign({}, state, newState);
}; };

View file

@ -146,3 +146,9 @@ export const selectHistoryStack = createSelector(
_selectState, _selectState,
state => state.stack state => state.stack
); );
// returns current page attributes (scrollY, path)
export const selectActiveHistoryEntry = createSelector(
_selectState,
state => state.stack[state.index]
);

56
ui/js/util/throttle.js Normal file
View file

@ -0,0 +1,56 @@
// Taken from underscore.js (slightly modified to add the getNow function and use const/let over var)
// https://github.com/jashkenas/underscore/blob/master/underscore.js#L830-L874
// Returns a function, that, when invoked, will only be triggered at most once
// during a given window of time. Normally, the throttled function will run
// as much as it can, without ever going more than once per `wait` duration;
// but if you'd like to disable the execution on the leading edge, pass
// `{leading: false}`. To disable execution on the trailing edge, ditto.
export default function throttle(func, wait, options) {
let timeout, context, args, result;
let previous = 0;
const getNow = () => new Date().getTime();
if (!options) options = {};
const later = function() {
previous = options.leading === false ? 0 : getNow();
timeout = null;
result = func.apply(context, args);
if (!timeout) context = args = null;
};
const throttled = function() {
const now = getNow();
if (!previous && options.leading === false) previous = now;
const remaining = wait - (now - previous);
context = this;
args = arguments;
if (remaining <= 0 || remaining > wait) {
if (timeout) {
clearTimeout(timeout);
timeout = null;
}
previous = now;
result = func.apply(context, args);
if (!timeout) context = args = null;
} else if (!timeout && options.trailing !== false) {
timeout = setTimeout(later, remaining);
}
return result;
};
throttled.cancel = function() {
clearTimeout(timeout);
previous = 0;
timeout = context = args = null;
};
return throttled;
}