Right Click to Navigate History #3547

Merged
dalhill merged 11 commits from 3474-history-buttons into master 2020-02-03 16:19:16 +01:00
10 changed files with 208 additions and 48 deletions

View file

@ -915,12 +915,7 @@
"Support %amount% LBC": "Support %amount% LBC",
"You deposited %amount% LBC as a support!": "You deposited %amount% LBC as a support!",
"LBRY Link": "LBRY Link",
"Publish to %uri%": "Publish to %uri%",
"Your wallet": "Your wallet",
"Publish a file, or create a channel": "Publish a file, or create a channel",
"Your account": "Your account",
"Channel profile picture": "Channel profile picture",
"refreshing the app": "refreshing the app",
"Follower": "Follower",
"%repost_channel_link% reposted": "%repost_channel_link% reposted"
"Your account": "Your account"
}

View file

@ -11,6 +11,7 @@ import WunderBar from 'component/wunderbar';
import Icon from 'component/common/icon';
import { Menu, MenuList, MenuButton, MenuItem } from '@reach/menu-button';
import Tooltip from 'component/common/tooltip';
import NavigationButton from 'component/navigationButton';
// @if TARGET='app'
import { IS_MAC } from 'component/app/view';
// @endif
@ -18,7 +19,15 @@ import { IS_MAC } from 'component/app/view';
type Props = {
balance: string,
roundedBalance: number,
history: { push: string => void, goBack: () => void, goForward: () => void, location: { pathname: string } },
history: {
entities: {}[],
goBack: () => void,
goForward: () => void,
index: number,
length: number,
location: { pathname: string },
push: string => void,
},
currentTheme: string,
automaticDarkModeEnabled: boolean,
setClientSetting: (string, boolean | string) => void,
@ -105,21 +114,8 @@ const Header = (props: Props) => {
{/* @if TARGET='app' */}
{!authHeader && (
<div className="header__navigation-arrows">
<Button
className="header__navigation-item header__navigation-item--back"
description={__('Navigate back')}
onClick={() => history.goBack()}
icon={ICONS.ARROW_LEFT}
iconSize={18}
/>
<Button
className="header__navigation-item header__navigation-item--forward"
description={__('Navigate forward')}
onClick={() => history.goForward()}
icon={ICONS.ARROW_RIGHT}
iconSize={18}
/>
<NavigationButton isBackward history={history} />
<NavigationButton isBackward={false} history={history} />
</div>
)}
{/* @endif */}

View file

@ -0,0 +1,3 @@
import NavigationButton from './view';
export default NavigationButton;

View file

@ -0,0 +1,91 @@
// @flow
import React, { useState, useCallback } from 'react';
import * as ICONS from 'constants/icons';
import Button from 'component/button';
// the maximum length of history to show per button
const MAX_HISTORY_SIZE = 12;
type Props = {
isBackward: boolean,
history: {
entries: Array<{ key: string, title: string, pathname: string }>,
go: number => void,
goBack: () => void,
goForward: () => void,
index: number,
length: number,
location: { pathname: string },
push: string => void,
},
};
// determines which slice of entries should make up the back or forward button drop-downs (isBackward vs !isBackward respectively)
const sliceEntries = (currentIndex, entries, historyLength, isBackward, maxSize) => {
let l = isBackward ? 0 : currentIndex + 1;
let r = isBackward ? currentIndex : historyLength;
const exceedsMax = maxSize < r - l;
if (!exceedsMax) {
return entries.slice(l, r);
} else if (isBackward) {
l = r - maxSize;
} else {
r = l + maxSize;
}
return entries.slice(l, r);
};
const NavigationButton = (props: Props) => {
const { isBackward, history } = props;
const { entries, go } = history;
const currentIndex = history.index;
const historyLength = history.length;
const [showHistory, setShowHistory] = useState(false);
// creates an <li> intended for the button's <ul>
const makeItem = useCallback(
(entry: { pathname: string, title: string, key: string }, index: number) => {
// difference between the current index and the index of the entry
const backwardDif = index - (currentIndex < MAX_HISTORY_SIZE ? currentIndex : MAX_HISTORY_SIZE);
const forwardDif = index + 1;
return (
<li
className="header__navigation-button"
role="link"
key={entry.key}
onMouseDown={() => {
setShowHistory(false);
go(isBackward ? backwardDif : forwardDif);
}}
>
<span>{entry.title}</span>
<span className="header__navigation-button-help">{entry.pathname === '/' ? __('Home') : entry.pathname}</span>
</li>
);
},
[currentIndex, isBackward, go]
);
const slicedEntries = sliceEntries(currentIndex, entries, historyLength, isBackward, MAX_HISTORY_SIZE);
return (
<div>
<Button
neb-b commented 2020-01-27 16:57:36 +01:00 (Migrated from github.com)
Review

Are you able to determine if we can go back here or not? If so, it would be great to disable the back button and give it some disabled style so it's easier to tell that you can't

Are you able to determine if we can go back here or not? If so, it would be great to disable the back button and give it some disabled style so it's easier to tell that you can't
dalhill commented 2020-01-28 02:07:31 +01:00 (Migrated from github.com)
Review

PR has been updated to disable button if no historical entries.

PR has been updated to disable button if no historical entries.
neb-b commented 2020-01-28 16:24:37 +01:00 (Migrated from github.com)
Review

Awesome!

Awesome!
className={`header__navigation-item header__navigation-item--${isBackward ? 'back' : 'forward'}`}
description={isBackward ? __('Navigate back') : __('Navigate forward')}
onBlur={() => setShowHistory(false)}
onClick={() => (isBackward ? history.goBack() : history.goForward())}
onContextMenu={e => {
setShowHistory(!showHistory);
// the following three lines prevent the regular context menu (right click menu) from appearing
e.preventDefault();
e.stopPropagation();
return false;
}}
icon={isBackward ? ICONS.ARROW_LEFT : ICONS.ARROW_RIGHT}
iconSize={18}
disabled={slicedEntries.length === 0}
/>
{showHistory && <ul className={'header__navigation-dropdown'}>{slicedEntries.map(makeItem)}</ul>}
</div>
);
};
export default NavigationButton;

View file

@ -2,10 +2,31 @@ import { connect } from 'react-redux';
import { selectUserVerifiedEmail } from 'lbryinc';
import { selectScrollStartingPosition } from 'redux/selectors/app';
import Router from './view';
import { normalizeURI, makeSelectTitleForUri } from 'lbry-redux';
const select = state => ({
currentScroll: selectScrollStartingPosition(state),
isAuthenticated: selectUserVerifiedEmail(state),
});
const select = state => {
const { pathname, hash } = state.router.location;
const urlPath = pathname + hash;
// Remove the leading "/" added by the browser
const path = urlPath.slice(1).replace(/:/g, '#');
let uri;
try {
uri = normalizeURI(path);
} catch (e) {
const match = path.match(/[#/:]/);
if (!path.startsWith('$/') && match && match.index) {
uri = `lbry://${path.slice(0, match.index)}`;
}
}
return {
uri,
title: makeSelectTitleForUri(uri)(state),
currentScroll: selectScrollStartingPosition(state),
isAuthenticated: selectUserVerifiedEmail(state),
};
};
export default connect(select)(Router);

View file

@ -29,6 +29,8 @@ import SignInPage from 'page/signIn';
import SignInVerifyPage from 'page/signInVerify';
import ChannelsPage from 'page/channels';
import EmbedWrapperPage from 'page/embedWrapper';
import { parseURI } from 'lbry-redux';
import { SITE_TITLE } from 'config';
// Tell the browser we are handling scroll restoration
if ('scrollRestoration' in history) {
@ -61,6 +63,19 @@ type Props = {
currentScroll: number,
location: { pathname: string, search: string },
isAuthenticated: boolean,
history: {
entries: { title: string }[],
goBack: () => void,
goForward: () => void,
index: number,
length: number,
location: { pathname: string },
push: string => void,
state: {},
replaceState: ({}, string, string) => void,
},
uri: string,
title: string,
};
function AppRouter(props: Props) {
@ -68,7 +83,37 @@ function AppRouter(props: Props) {
currentScroll,
location: { pathname },
isAuthenticated,
history,
uri,
title,
} = props;
const { entries } = history;
const entryIndex = history.index;
useEffect(() => {
if (uri) {
const { channelName, streamName } = parseURI(uri);
if (typeof title !== 'undefined' && title !== '') {
document.title = title;
} else if (streamName) {
document.title = streamName;
} else if (channelName) {
neb-b commented 2020-01-27 16:53:52 +01:00 (Migrated from github.com)
Review

Why did you move this effect out of the show component?

Why did you move this effect out of the show component?
dalhill commented 2020-01-28 00:22:52 +01:00 (Migrated from github.com)
Review

I want this to fire on every page and not just the pages that render using the ShowPage component.

I want this to fire on every page and not just the pages that render using the ShowPage component.
document.title = channelName;
} else {
document.title = IS_WEB ? SITE_TITLE : 'LBRY';
}
} else {
document.title = IS_WEB ? SITE_TITLE : 'LBRY';
}
// @if TARGET='app'
entries[entryIndex].title = document.title;
// @endif
return () => {
document.title = IS_WEB ? SITE_TITLE : 'LBRY';
};
}, [entries, entryIndex, title, uri]);
useEffect(() => {
window.scrollTo(0, currentScroll);

View file

@ -1,12 +1,10 @@
// @flow
import React, { useEffect } from 'react';
import { parseURI } from 'lbry-redux';
import BusyIndicator from 'component/common/busy-indicator';
import ChannelPage from 'page/channel';
import FilePage from 'page/file';
import Page from 'component/page';
import Button from 'component/button';
import { SITE_TITLE } from 'config';
import Card from 'component/common/card';
type Props = {
@ -24,8 +22,7 @@ type Props = {
};
function ShowPage(props: Props) {
const { isResolvingUri, resolveUri, uri, claim, blackListedOutpoints, location, title, claimIsMine } = props;
const { channelName, streamName } = parseURI(uri);
const { isResolvingUri, resolveUri, uri, claim, blackListedOutpoints, location, claimIsMine } = props;
const signingChannel = claim && claim.signing_channel;
const canonicalUrl = claim && claim.canonical_url;
const claimExists = claim !== null && claim !== undefined;
@ -46,22 +43,6 @@ function ShowPage(props: Props) {
}
}, [resolveUri, isResolvingUri, canonicalUrl, uri, claimExists, haventFetchedYet]);
useEffect(() => {
if (title) {
document.title = title;
} else if (streamName) {
document.title = streamName;
} else if (channelName) {
document.title = channelName;
} else {
document.title = IS_WEB ? SITE_TITLE : 'LBRY';
}
return () => {
document.title = IS_WEB ? SITE_TITLE : 'LBRY';
};
}, [title, channelName, streamName]);
let innerContent = '';
if (!claim || (claim && !claim.name)) {

View file

@ -147,3 +147,29 @@
.header__navigation-item--balance {
margin: 0 var(--spacing-medium);
}
.header__navigation-dropdown {
@extend .menu__list--header;
padding: 0;
position: absolute;
list-style-type: none;
background-color: var(--color-header-background);
}
.header__navigation-button {
margin: 0;
padding: var(--spacing-miniscule) var(--spacing-medium);
display: flex;
align-items: center;
&:hover {
cursor: pointer;
background-color: var(--color-menu-background--active);
}
}
.header__navigation-button-help {
@extend .help;
margin-top: 0;
margin-left: var(--spacing-small);
}

View file

@ -55,6 +55,8 @@
margin-left: calc(var(--spacing-medium) * -1);
box-shadow: var(--card-box-shadow);
animation: menu-animate-in var(--animation-duration) var(--animation-style);
border-bottom-left-radius: var(--border-radius);
border-bottom-right-radius: var(--border-radius);
}
.menu__link {

View file

@ -7,7 +7,7 @@ import { createFilter, createBlacklistFilter } from 'redux-persist-transform-fil
import localForage from 'localforage';
import { createStore, applyMiddleware, compose } from 'redux';
import thunk from 'redux-thunk';
import { createHashHistory, createBrowserHistory } from 'history';
import { createMemoryHistory, createBrowserHistory } from 'history';
import { routerMiddleware } from 'connected-react-router';
import createRootReducer from './reducers';
import { buildSharedStateMiddleware, ACTIONS as LBRY_REDUX_ACTIONS } from 'lbry-redux';
@ -96,7 +96,7 @@ const persistOptions = {
let history;
// @if TARGET='app'
history = createHashHistory();
history = createMemoryHistory();
// @endif
// @if TARGET='web'
history = createBrowserHistory();