Removing last item padding; Improving scroll restoration, fixing #618

This commit is contained in:
James Barnsley
2021-03-12 22:20:05 +13:00
parent 845ceda09c
commit 3adfb47f57
9 changed files with 235 additions and 179 deletions

View File

@ -134289,30 +134289,32 @@ var App = /*#__PURE__*/function (_React$Component) {
}, {
key: "componentDidUpdate",
value: function componentDidUpdate(_ref) {
var prevLocation = _ref.location;
var prevPathname = _ref.location.pathname;
var _this$props3 = this.props,
_this$props3$location = _this$props3.location,
location = _this$props3$location === void 0 ? {} : _this$props3$location,
_this$props3$location = _this$props3.location;
_this$props3$location = _this$props3$location === void 0 ? {} : _this$props3$location;
var pathname = _this$props3$location.pathname,
_this$props3$location2 = _this$props3$location.state;
_this$props3$location2 = _this$props3$location2 === void 0 ? {} : _this$props3$location2;
var scroll_position = _this$props3$location2.scroll_position,
allow_reporting = _this$props3.allow_reporting,
uiActions = _this$props3.uiActions,
context_menu = _this$props3.context_menu; // When we have navigated to a new route
if (location !== prevLocation) {
if (pathname !== prevPathname) {
// Log our pageview
if (allow_reporting) {
react_ga__WEBPACK_IMPORTED_MODULE_4__["default"].set({
page: location.pathname
page: pathname
});
react_ga__WEBPACK_IMPORTED_MODULE_4__["default"].pageview(location.pathname);
react_ga__WEBPACK_IMPORTED_MODULE_4__["default"].pageview(pathname);
} // If the location has a "scroll_position" state variable, scroll to it.
// This is invisibly injected to the history by the Link component when navigating, so
// hitting back in the browser allows us to restore the position
var location_state = location.state || {};
if (location_state.scroll_position) {
Object(_util_helpers__WEBPACK_IMPORTED_MODULE_50__["scrollTo"])(parseInt(location_state.scroll_position), false);
if (scroll_position) {
Object(_util_helpers__WEBPACK_IMPORTED_MODULE_50__["scrollTo"])(parseInt(scroll_position, 10), false);
}
uiActions.toggleSidebar(false);
@ -141780,11 +141782,13 @@ var LazyLoadListener = /*#__PURE__*/function (_React$Component) {
/*!***********************************!*\
!*** ./src/js/components/Link.js ***!
\***********************************/
/*! exports provided: default */
/*! exports provided: default, Link, updateScrollPosition */
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
__webpack_require__.r(__webpack_exports__);
/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "Link", function() { return Link; });
/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "updateScrollPosition", function() { return updateScrollPosition; });
/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! react */ "./node_modules/react/index.js");
/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(react__WEBPACK_IMPORTED_MODULE_0__);
/* harmony import */ var react_router_dom__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! react-router-dom */ "./node_modules/react-router-dom/esm/react-router-dom.js");
@ -141800,21 +141804,46 @@ function _defineProperty(obj, key, value) { if (key in obj) { Object.definePrope
var updateScrollPosition = function updateScrollPosition(_ref) {
var _ref$retainScroll = _ref.retainScroll,
retainScroll = _ref$retainScroll === void 0 ? false : _ref$retainScroll,
_ref$scrollTarget = _ref.scrollTarget,
scrollTarget = _ref$scrollTarget === void 0 ? 0 : _ref$scrollTarget,
history = _ref.history,
_ref$location = _ref.location,
state = _ref$location.state,
pathname = _ref$location.pathname;
// Fetch the current scroll position of our #main element and save to our history's state, so
// clicking 'back' etc will restore the previous scroll position.
// This doesn't trigger lazy-load elements (unless scrolling exposes the LazyLoader component).
var main = document.getElementById('main');
history.replace(pathname, _objectSpread(_objectSpread({}, state), {}, {
scroll_position: main.scrollTop,
previous: {
pathname: pathname
}
})); // And now scroll to the top of the page. This can be disabled to allow in-page navigation of
// tabs, etc
if (!retainScroll) Object(_util_helpers__WEBPACK_IMPORTED_MODULE_2__["scrollTo"])(scrollTarget, scrollTarget);
};
/**
* Extends react-router's Link but provides the ability to hook in to the navigation event
* which lets us scroll to the top of our <main> for a more traditional navigation experience
* */
/* harmony default export */ __webpack_exports__["default"] = (function (_ref) {
var retainScroll = _ref.retainScroll,
scrollToProp = _ref.scrollTo,
onContextMenu = _ref.onContextMenu,
_ref$className = _ref.className,
className = _ref$className === void 0 ? '' : _ref$className,
activeClassName = _ref.activeClassName,
to = _ref.to,
exact = _ref.exact,
children = _ref.children;
var Link = function Link(_ref2) {
var retainScroll = _ref2.retainScroll,
scrollTarget = _ref2.scrollTo,
onContextMenu = _ref2.onContextMenu,
_ref2$className = _ref2.className,
className = _ref2$className === void 0 ? '' : _ref2$className,
activeClassName = _ref2.activeClassName,
to = _ref2.to,
exact = _ref2.exact,
children = _ref2.children;
if (!to) return /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement("span", {
className: className,
"data-qa-node": "span",
@ -141824,19 +141853,12 @@ function _defineProperty(obj, key, value) { if (key in obj) { Object.definePrope
var location = Object(react_router_dom__WEBPACK_IMPORTED_MODULE_1__["useLocation"])();
var onClick = function onClick() {
// Fetch the current scroll position of our #main element and save to our history's state, so
// clicking 'back' etc will restore the previous scroll position.
// This doesn't trigger lazy-load elements (unless scrolling exposes the LazyLoader component).
var main = document.getElementById('main');
history.replace(location.pathname, _objectSpread(_objectSpread({}, location.state), {}, {
scroll_position: main.scrollTop,
previous: {
pathname: location.pathname
}
})); // Allow a link to disable auto-scrolling to the top of the page
// on navigation. Useful for tabs, etc.
if (!retainScroll) Object(_util_helpers__WEBPACK_IMPORTED_MODULE_2__["scrollTo"])(scrollToProp, scrollToProp);
return updateScrollPosition({
history: history,
location: location,
retainScroll: retainScroll,
scrollTarget: scrollTarget
});
}; // Decode both links. This handles issues where one link is encoded and the other isn't, but
// they're otherwise identical
@ -141852,10 +141874,13 @@ function _defineProperty(obj, key, value) { if (key in obj) { Object.definePrope
onContextMenu: onContextMenu,
className: "".concat(className, " ").concat(active),
to: to,
"data-qa-node": "Link",
"data-qa-node": "RouterLink",
"data-qa-file": "Link"
}, children);
});
};
/* harmony default export */ __webpack_exports__["default"] = (Link);
/***/ }),
@ -142002,15 +142027,13 @@ __webpack_require__.r(__webpack_exports__);
/* harmony import */ var _Icon__WEBPACK_IMPORTED_MODULE_8__ = __webpack_require__(/*! ./Icon */ "./src/js/components/Icon.js");
/* harmony import */ var _Thumbnail__WEBPACK_IMPORTED_MODULE_9__ = __webpack_require__(/*! ./Thumbnail */ "./src/js/components/Thumbnail.js");
/* harmony import */ var _Popularity__WEBPACK_IMPORTED_MODULE_10__ = __webpack_require__(/*! ./Popularity */ "./src/js/components/Popularity.js");
/* harmony import */ var _Link__WEBPACK_IMPORTED_MODULE_11__ = __webpack_require__(/*! ./Link */ "./src/js/components/Link.js");
/* harmony import */ var _locale__WEBPACK_IMPORTED_MODULE_12__ = __webpack_require__(/*! ../locale */ "./src/js/locale/index.js");
/* harmony import */ var _util_format__WEBPACK_IMPORTED_MODULE_13__ = __webpack_require__(/*! ../util/format */ "./src/js/util/format.js");
/* harmony import */ var _util_helpers__WEBPACK_IMPORTED_MODULE_14__ = __webpack_require__(/*! ../util/helpers */ "./src/js/util/helpers.js");
/* harmony import */ var _locale__WEBPACK_IMPORTED_MODULE_11__ = __webpack_require__(/*! ../locale */ "./src/js/locale/index.js");
/* harmony import */ var _util_format__WEBPACK_IMPORTED_MODULE_12__ = __webpack_require__(/*! ../util/format */ "./src/js/util/format.js");
/* harmony import */ var _util_helpers__WEBPACK_IMPORTED_MODULE_13__ = __webpack_require__(/*! ../util/helpers */ "./src/js/util/helpers.js");
/* harmony import */ var _Link__WEBPACK_IMPORTED_MODULE_14__ = __webpack_require__(/*! ./Link */ "./src/js/components/Link.js");
/* harmony import */ var _services_ui_actions__WEBPACK_IMPORTED_MODULE_15__ = __webpack_require__(/*! ../services/ui/actions */ "./src/js/services/ui/actions.js");
/* harmony import */ var _services_lastfm_actions__WEBPACK_IMPORTED_MODULE_16__ = __webpack_require__(/*! ../services/lastfm/actions */ "./src/js/services/lastfm/actions.js");
/* harmony import */ var _services_mopidy_actions__WEBPACK_IMPORTED_MODULE_17__ = __webpack_require__(/*! ../services/mopidy/actions */ "./src/js/services/mopidy/actions.js");
/* harmony import */ var _services_spotify_actions__WEBPACK_IMPORTED_MODULE_18__ = __webpack_require__(/*! ../services/spotify/actions */ "./src/js/services/spotify/actions.js");
/* harmony import */ var _services_mopidy_actions__WEBPACK_IMPORTED_MODULE_16__ = __webpack_require__(/*! ../services/mopidy/actions */ "./src/js/services/mopidy/actions.js");
/* harmony import */ var _services_spotify_actions__WEBPACK_IMPORTED_MODULE_17__ = __webpack_require__(/*! ../services/spotify/actions */ "./src/js/services/spotify/actions.js");
@ -142040,7 +142063,7 @@ var getValue = function getValue() {
{
var total = item.tracks_total;
if (!total && !value) return null;
return /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(_locale__WEBPACK_IMPORTED_MODULE_12__["I18n"], {
return /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(_locale__WEBPACK_IMPORTED_MODULE_11__["I18n"], {
path: "specs.tracks",
count: Object(_NiceNumber__WEBPACK_IMPORTED_MODULE_5__["nice_number"])(total || value.length),
"data-qa-node": "I18n",
@ -142059,7 +142082,7 @@ var getValue = function getValue() {
"data-qa-file": "ListItem"
});
if (!_total && !array) return null;
return /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(_locale__WEBPACK_IMPORTED_MODULE_12__["I18n"], {
return /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(_locale__WEBPACK_IMPORTED_MODULE_11__["I18n"], {
path: "specs.artists",
count: Object(_NiceNumber__WEBPACK_IMPORTED_MODULE_5__["nice_number"])(_total || array.length),
"data-qa-node": "I18n",
@ -142072,7 +142095,7 @@ var getValue = function getValue() {
var _total2 = item.albums_total,
_array = item.albums_uris;
if (!_total2 && !_array) return null;
return /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(_locale__WEBPACK_IMPORTED_MODULE_12__["I18n"], {
return /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(_locale__WEBPACK_IMPORTED_MODULE_11__["I18n"], {
path: "specs.albums",
count: _total2 || _array.length,
"data-qa-node": "I18n",
@ -142083,7 +142106,7 @@ var getValue = function getValue() {
case 'source':
return /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(_Icon__WEBPACK_IMPORTED_MODULE_8__["default"], {
type: "fontawesome",
name: Object(_util_helpers__WEBPACK_IMPORTED_MODULE_14__["sourceIcon"])(item.uri),
name: Object(_util_helpers__WEBPACK_IMPORTED_MODULE_13__["sourceIcon"])(item.uri),
fixedWidth: true,
"data-qa-node": "Icon",
"data-qa-file": "ListItem"
@ -142098,7 +142121,7 @@ var getValue = function getValue() {
switch (name) {
case 'followers':
return /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(_locale__WEBPACK_IMPORTED_MODULE_12__["I18n"], {
return /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(_locale__WEBPACK_IMPORTED_MODULE_11__["I18n"], {
path: "specs.followers",
count: Object(_NiceNumber__WEBPACK_IMPORTED_MODULE_5__["nice_number"])(value),
"data-qa-node": "I18n",
@ -142106,7 +142129,7 @@ var getValue = function getValue() {
});
case 'listeners':
return /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(_locale__WEBPACK_IMPORTED_MODULE_12__["I18n"], {
return /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(_locale__WEBPACK_IMPORTED_MODULE_11__["I18n"], {
path: "specs.listeners",
count: Object(_NiceNumber__WEBPACK_IMPORTED_MODULE_5__["nice_number"])(value),
"data-qa-node": "I18n",
@ -142114,7 +142137,7 @@ var getValue = function getValue() {
});
case 'added_at':
return /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(_locale__WEBPACK_IMPORTED_MODULE_12__["I18n"], {
return /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(_locale__WEBPACK_IMPORTED_MODULE_11__["I18n"], {
path: "specs.added_ago",
time: Object(_Dater__WEBPACK_IMPORTED_MODULE_4__["dater"])('ago', value),
"data-qa-node": "I18n",
@ -142122,7 +142145,7 @@ var getValue = function getValue() {
});
case 'last_modified':
return /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(_locale__WEBPACK_IMPORTED_MODULE_12__["I18n"], {
return /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(_locale__WEBPACK_IMPORTED_MODULE_11__["I18n"], {
path: "specs.updated_ago",
time: Object(_Dater__WEBPACK_IMPORTED_MODULE_4__["dater"])('ago', value),
"data-qa-node": "I18n",
@ -142174,20 +142197,21 @@ var ListItem = function ListItem(_ref) {
var spotify_available = Object(react_redux__WEBPACK_IMPORTED_MODULE_1__["useSelector"])(function (state) {
return state.spotify.access_token;
});
var history = Object(react_router_dom__WEBPACK_IMPORTED_MODULE_2__["useHistory"])(); // Load images
var history = Object(react_router_dom__WEBPACK_IMPORTED_MODULE_2__["useHistory"])();
var location = Object(react_router_dom__WEBPACK_IMPORTED_MODULE_2__["useLocation"])(); // Load images
Object(react__WEBPACK_IMPORTED_MODULE_0__["useEffect"])(function () {
if (!item.images) {
switch (item.type) {
case 'artist':
if (spotify_available) {
dispatch(_services_spotify_actions__WEBPACK_IMPORTED_MODULE_18__["getArtistImages"](item));
dispatch(_services_spotify_actions__WEBPACK_IMPORTED_MODULE_17__["getArtistImages"](item));
}
break;
case 'album':
dispatch(_services_mopidy_actions__WEBPACK_IMPORTED_MODULE_17__["getImages"]([item.uri]));
dispatch(_services_mopidy_actions__WEBPACK_IMPORTED_MODULE_16__["getImages"]([item.uri]));
break;
default:
@ -142224,13 +142248,16 @@ var ListItem = function ListItem(_ref) {
} else if (item.link) {
to = item.link;
} else {
to = "/".concat(item.type, "/").concat(Object(_util_format__WEBPACK_IMPORTED_MODULE_13__["encodeUri"])(item.uri));
to = "/".concat(item.type, "/").concat(Object(_util_format__WEBPACK_IMPORTED_MODULE_12__["encodeUri"])(item.uri));
}
if (e.target.tagName.toLowerCase() !== 'a') {
Object(_Link__WEBPACK_IMPORTED_MODULE_14__["updateScrollPosition"])({
location: location,
history: history
});
e.preventDefault();
history.push(to);
Object(_util_helpers__WEBPACK_IMPORTED_MODULE_14__["scrollTo"])();
}
};
@ -146284,7 +146311,6 @@ function _extends() { _extends = Object.assign || function (target) { for (var i
var SmartListBatch = react_in_viewport__WEBPACK_IMPORTED_MODULE_1___default()(function (_ref) {
var items = _ref.items,
isFirst = _ref.isFirst,
inViewport = _ref.inViewport,
forwardedRef = _ref.forwardedRef,
setItemHeight = _ref.setItemHeight,
@ -146294,7 +146320,9 @@ var SmartListBatch = react_in_viewport__WEBPACK_IMPORTED_MODULE_1___default()(fu
batchIndex = _ref.batchIndex,
chunkSize = _ref.chunkSize,
_ref$className = _ref.className,
className = _ref$className === void 0 ? '' : _ref$className;
className = _ref$className === void 0 ? '' : _ref$className,
isFirst = _ref.isFirst,
isLast = _ref.isLast;
// Listen for changes to our height, and pass it up to our Grid. This is then used to build the
// placeholder elements when out of viewport. We only care about the first item because this
// represents the same heights for everything else (in almost all circumstances).
@ -146319,9 +146347,9 @@ var SmartListBatch = react_in_viewport__WEBPACK_IMPORTED_MODULE_1___default()(fu
ref: forwardedRef,
"data-qa-node": "div",
"data-qa-file": "SmartList"
}, inViewport || isFirst ? /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement("div", {
}, inViewport || isFirst || isLast ? /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement("div", {
className: "smart-list__batch__inner",
style: isFirst ? {} : {
style: isFirst || isLast ? {} : {
minHeight: itemHeight
},
"data-qa-node": "div",
@ -146362,10 +146390,11 @@ var SmartList = /*#__PURE__*/Object(react__WEBPACK_IMPORTED_MODULE_0__["memo"])(
itemHeight = _useState2[0],
setItemHeight = _useState2[1];
var chunks = Object(lodash__WEBPACK_IMPORTED_MODULE_2__["chunk"])(items, chunkSize);
return /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(_ErrorBoundary__WEBPACK_IMPORTED_MODULE_3__["default"], {
"data-qa-node": "ErrorBoundary",
"data-qa-file": "SmartList"
}, Object(lodash__WEBPACK_IMPORTED_MODULE_2__["chunk"])(items, chunkSize).map(function (chunked, index) {
}, chunks.map(function (chunked, index) {
return /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(SmartListBatch, {
key: "smart-list__batch-".concat(index) // Yeah yeah, I know; TODO
,
@ -146376,8 +146405,9 @@ var SmartList = /*#__PURE__*/Object(react__WEBPACK_IMPORTED_MODULE_0__["memo"])(
itemProps: itemProps,
batchIndex: index,
chunkSize: chunkSize,
isFirst: index === 0,
setItemHeight: setItemHeight,
isFirst: index === 0,
isLast: index === chunks.length - 1,
"data-qa-node": "SmartListBatch",
"data-qa-file": "SmartList"
});
@ -163636,48 +163666,47 @@ var iconFromKeyword = function iconFromKeyword(name) {
var scrollTo = function scrollTo() {
var target = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : null;
var smooth_scroll = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;
console.debug('scrollTo', {
target: target,
smooth_scroll: smooth_scroll
});
var main = document.getElementById('main'); // Remove our smooth-scroll class
var main = document.getElementById('main');
if (!smooth_scroll) {
main.classList.remove('smooth-scroll');
} // Target is a number, so treat as pixel position
var performScroll = function performScroll() {
if (!smooth_scroll) main.classList.remove('smooth-scroll'); // Target is a number, so treat as pixel position
if (target && Number.isInteger(target)) {
if (typeof main.scrollTo === 'function') {
console.debug('Scrolling to', {
main: main,
target: target
});
main.scrollTo(0, target);
} // Target is a string representing a DOM element by class/id
if (target && Number.isInteger(target)) {
if (typeof main.scrollTo === 'function') {
main.scrollTop = target;
} // Target is a string representing a DOM element by class/id
} else if (target) {
var element = null;
} else if (target) {
var element = null;
if (target.charAt(0) === '#') {
element = document.getElementById(target.substring(1));
} else if (target.charAt(0) === '.') {
element = document.getElementsByClassName(target.substring(1));
if (target.charAt(0) == '#') {
element = document.getElementById(target.substring(1));
} else if (target.charAt(0) == '.') {
element = document.getElementsByClassName(target.substring(1));
if (element.length > 0) {
element = element[0];
}
} else {
console.error("Invalid target type '".concat(target, "'. Must start with '#' or '.'."));
}
if (element.length > 0) {
element = element[0];
if (element && typeof element.scrollIntoView === 'function') {
element.scrollIntoView();
}
} else {
console.error("Invalid target type '".concat(target, "'. Must start with '#' or '.'."));
main.scrollTop = 0;
}
if (element && typeof element.scrollIntoView === 'function') {
element.scrollIntoView();
}
} else {
main.scrollTop = 0;
} // Now reinstate smooth scroll
if (!smooth_scroll) main.classList.add('smooth-scroll');
}; // Give .main a moment to render it's contents
if (!smooth_scroll) {
main.classList.add('smooth-scroll');
}
setTimeout(performScroll, 1);
};
/**
* Upgrade one or many Spotify Playlist URIs

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -106,7 +106,7 @@
// Release details
// These are automatically injected to built HTML
var build = "1615274990";
var build = "1615540275";
var version = "3.57.0";
// Construct the script tag

View File

@ -161,29 +161,35 @@ export class App extends React.Component {
}
componentDidUpdate({
location: prevLocation,
location: {
pathname: prevPathname,
},
}) {
const {
location = {},
location: {
pathname,
state: {
scroll_position,
} = {},
} = {},
allow_reporting,
uiActions,
context_menu,
} = this.props;
// When we have navigated to a new route
if (location !== prevLocation) {
if (pathname !== prevPathname) {
// Log our pageview
if (allow_reporting) {
ReactGA.set({ page: location.pathname });
ReactGA.pageview(location.pathname);
ReactGA.set({ page: pathname });
ReactGA.pageview(pathname);
}
// If the location has a "scroll_position" state variable, scroll to it.
// This is invisibly injected to the history by the Link component when navigating, so
// hitting back in the browser allows us to restore the position
const location_state = location.state || {};
if (location_state.scroll_position) {
scrollTo(parseInt(location_state.scroll_position), false);
if (scroll_position) {
scrollTo(parseInt(scroll_position, 10), false);
}
uiActions.toggleSidebar(false);

View File

@ -1,15 +1,46 @@
import React from 'react';
import { Link, useHistory, useLocation } from 'react-router-dom';
import { Link as RouterLink, useHistory, useLocation } from 'react-router-dom';
import { scrollTo } from '../util/helpers';
import { decodeUri } from '../util/format';
const updateScrollPosition = ({
retainScroll = false,
scrollTarget = 0,
history,
location: {
state,
pathname,
},
}) => {
// Fetch the current scroll position of our #main element and save to our history's state, so
// clicking 'back' etc will restore the previous scroll position.
// This doesn't trigger lazy-load elements (unless scrolling exposes the LazyLoader component).
const main = document.getElementById('main');
history.replace(
pathname,
{
...state,
scroll_position: main.scrollTop,
previous: {
pathname,
},
},
);
// And now scroll to the top of the page. This can be disabled to allow in-page navigation of
// tabs, etc
if (!retainScroll) scrollTo(scrollTarget, (scrollTarget));
}
/**
* Extends react-router's Link but provides the ability to hook in to the navigation event
* which lets us scroll to the top of our <main> for a more traditional navigation experience
* */
export default ({
const Link = ({
retainScroll,
scrollTo: scrollToProp,
scrollTo: scrollTarget,
onContextMenu,
className = '',
activeClassName,
@ -20,28 +51,7 @@ export default ({
if (!to) return <span className={className}>{children}</span>;
const history = useHistory();
const location = useLocation();
const onClick = () => {
// Fetch the current scroll position of our #main element and save to our history's state, so
// clicking 'back' etc will restore the previous scroll position.
// This doesn't trigger lazy-load elements (unless scrolling exposes the LazyLoader component).
const main = document.getElementById('main');
history.replace(
location.pathname,
{
...location.state,
scroll_position: main.scrollTop,
previous: {
pathname: location.pathname,
},
},
);
// Allow a link to disable auto-scrolling to the top of the page
// on navigation. Useful for tabs, etc.
if (!retainScroll) scrollTo(scrollToProp, (scrollToProp));
};
const onClick = () => updateScrollPosition({ history, location, retainScroll, scrollTarget });
// Decode both links. This handles issues where one link is encoded and the other isn't, but
// they're otherwise identical
@ -53,13 +63,20 @@ export default ({
// This is used almost solely by the Sidebar navigation
const active = history && isLinkActive ? (activeClassName || 'active') : '';
return (
<Link
<RouterLink
onClick={onClick}
onContextMenu={onContextMenu}
className={`${className} ${active}`}
to={to}
>
{children}
</Link>
</RouterLink>
);
};
export default Link;
export {
Link,
updateScrollPosition,
};

View File

@ -1,6 +1,6 @@
import React, { useEffect } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { useHistory } from 'react-router-dom';
import { useHistory, useLocation } from 'react-router-dom';
import LinksSentence from './LinksSentence';
import { dater } from './Dater';
import { nice_number } from './NiceNumber';
@ -9,13 +9,12 @@ import ContextMenuTrigger from './ContextMenuTrigger';
import Icon from './Icon';
import Thumbnail from './Thumbnail';
import Popularity from './Popularity';
import Link from './Link';
import { I18n } from '../locale';
import { encodeUri } from '../util/format';
import { scrollTo, sourceIcon } from '../util/helpers';
import { sourceIcon } from '../util/helpers';
import { updateScrollPosition } from './Link';
import * as uiActions from '../services/ui/actions';
import * as lastfmActions from '../services/lastfm/actions';
import * as mopidyActions from '../services/mopidy/actions';
import * as spotifyActions from '../services/spotify/actions';
@ -92,6 +91,7 @@ const ListItem = ({
const dispatch = useDispatch();
const spotify_available = useSelector((state) => state.spotify.access_token);
const history = useHistory();
const location = useLocation();
// Load images
useEffect(() => {
@ -142,11 +142,11 @@ const ListItem = ({
}
if (e.target.tagName.toLowerCase() !== 'a') {
updateScrollPosition({ location, history });
e.preventDefault();
history.push(to);
scrollTo();
}
}
};
let className = 'list__item';
if (item.type) className += ` list__item--${item.type}`;

View File

@ -6,7 +6,6 @@ import ErrorBoundary from './ErrorBoundary';
const SmartListBatch = handleViewport(
({
items,
isFirst,
inViewport,
forwardedRef,
setItemHeight,
@ -16,6 +15,8 @@ const SmartListBatch = handleViewport(
batchIndex,
chunkSize,
className = '',
isFirst,
isLast,
}) => {
// Listen for changes to our height, and pass it up to our Grid. This is then used to build the
// placeholder elements when out of viewport. We only care about the first item because this
@ -34,8 +35,11 @@ const SmartListBatch = handleViewport(
return (
<div className={`smart-list__batch ${className}`} ref={forwardedRef}>
{inViewport || isFirst ? (
<div className="smart-list__batch__inner" style={isFirst ? {} : { minHeight: itemHeight }}>
{inViewport || isFirst || isLast ? (
<div
className="smart-list__batch__inner"
style={isFirst || isLast ? {} : { minHeight: itemHeight }}
>
{
items.map((item, index) => (
<ItemComponent
@ -67,11 +71,12 @@ const SmartList = memo(({
if (!itemComponent) return null;
const [itemHeight, setItemHeight] = useState(initialHeight);
const chunks = chunk(items, chunkSize);
return (
<ErrorBoundary>
{
chunk(items, chunkSize).map((chunked, index) => (
chunks.map((chunked, index) => (
<SmartListBatch
key={`smart-list__batch-${index}`} // Yeah yeah, I know; TODO
className={className}
@ -81,8 +86,9 @@ const SmartList = memo(({
itemProps={itemProps}
batchIndex={index}
chunkSize={chunkSize}
isFirst={index === 0}
setItemHeight={setItemHeight}
isFirst={index === 0}
isLast={index === chunks.length - 1}
/>
))
}

View File

@ -540,47 +540,45 @@ const iconFromKeyword = (name) => {
* @param smooth_scroll Boolean (optional)
* */
const scrollTo = function (target = null, smooth_scroll = false) {
console.debug('scrollTo', { target, smooth_scroll })
const main = document.getElementById('main');
// Remove our smooth-scroll class
if (!smooth_scroll) {
main.classList.remove('smooth-scroll');
}
// Target is a number, so treat as pixel position
if (target && Number.isInteger(target)) {
if (typeof main.scrollTo === 'function') {
main.scrollTop = target;
}
const performScroll = () => {
if (!smooth_scroll) main.classList.remove('smooth-scroll');
// Target is a number, so treat as pixel position
if (target && Number.isInteger(target)) {
if (typeof main.scrollTo === 'function') {
console.debug('Scrolling to', { main, target })
main.scrollTo(0, target);
}
// Target is a string representing a DOM element by class/id
} else if (target) {
let element = null;
} else if (target) {
let element = null;
if (target.charAt(0) == '#') {
element = document.getElementById(target.substring(1));
} else if (target.charAt(0) == '.') {
element = document.getElementsByClassName(target.substring(1));
if (element.length > 0) {
element = element[0];
if (target.charAt(0) === '#') {
element = document.getElementById(target.substring(1));
} else if (target.charAt(0) === '.') {
element = document.getElementsByClassName(target.substring(1));
if (element.length > 0) {
element = element[0];
}
} else {
console.error(`Invalid target type '${target}'. Must start with '#' or '.'.`);
}
if (element && typeof element.scrollIntoView === 'function') {
element.scrollIntoView();
}
} else {
console.error(`Invalid target type '${target}'. Must start with '#' or '.'.`);
main.scrollTop = 0;
}
if (element && typeof element.scrollIntoView === 'function') {
element.scrollIntoView();
}
} else {
main.scrollTop = 0;
}
if (!smooth_scroll) main.classList.add('smooth-scroll');
};
// Now reinstate smooth scroll
if (!smooth_scroll) {
main.classList.add('smooth-scroll');
}
// Give .main a moment to render it's contents
setTimeout(performScroll, 1);
};