diff --git a/static/app-strings.json b/static/app-strings.json
index 67e0ef012..87ed5d91e 100644
--- a/static/app-strings.json
+++ b/static/app-strings.json
@@ -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"
}
\ No newline at end of file
diff --git a/ui/component/header/view.jsx b/ui/component/header/view.jsx
index 343167754..ecba00685 100644
--- a/ui/component/header/view.jsx
+++ b/ui/component/header/view.jsx
@@ -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 && (
-
)}
{/* @endif */}
diff --git a/ui/component/navigationButton/index.js b/ui/component/navigationButton/index.js
new file mode 100644
index 000000000..81cb43a8e
--- /dev/null
+++ b/ui/component/navigationButton/index.js
@@ -0,0 +1,3 @@
+import NavigationButton from './view';
+
+export default NavigationButton;
diff --git a/ui/component/navigationButton/view.jsx b/ui/component/navigationButton/view.jsx
new file mode 100644
index 000000000..72c9cb017
--- /dev/null
+++ b/ui/component/navigationButton/view.jsx
@@ -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 intended for the button's
+ 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 (
+ - {
+ setShowHistory(false);
+ go(isBackward ? backwardDif : forwardDif);
+ }}
+ >
+ {entry.title}
+ {entry.pathname === '/' ? __('Home') : entry.pathname}
+
+ );
+ },
+ [currentIndex, isBackward, go]
+ );
+ const slicedEntries = sliceEntries(currentIndex, entries, historyLength, isBackward, MAX_HISTORY_SIZE);
+ return (
+
+
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 && {slicedEntries.map(makeItem)}
}
+
+ );
+};
+export default NavigationButton;
diff --git a/ui/component/router/index.js b/ui/component/router/index.js
index 1d021ff84..d9107de3d 100644
--- a/ui/component/router/index.js
+++ b/ui/component/router/index.js
@@ -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);
diff --git a/ui/component/router/view.jsx b/ui/component/router/view.jsx
index 117722c90..fef7d1c17 100644
--- a/ui/component/router/view.jsx
+++ b/ui/component/router/view.jsx
@@ -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) {
+ 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);
diff --git a/ui/page/show/view.jsx b/ui/page/show/view.jsx
index 0bbdd1384..f01ff5592 100644
--- a/ui/page/show/view.jsx
+++ b/ui/page/show/view.jsx
@@ -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)) {
diff --git a/ui/scss/component/_header.scss b/ui/scss/component/_header.scss
index 93822000a..ecc7e36b4 100644
--- a/ui/scss/component/_header.scss
+++ b/ui/scss/component/_header.scss
@@ -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);
+}
diff --git a/ui/scss/component/menu-button.scss b/ui/scss/component/menu-button.scss
index c1f3896bb..ed03adcc1 100644
--- a/ui/scss/component/menu-button.scss
+++ b/ui/scss/component/menu-button.scss
@@ -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 {
diff --git a/ui/store.js b/ui/store.js
index 25967f822..af642c6cc 100644
--- a/ui/store.js
+++ b/ui/store.js
@@ -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();