From 6d88d87ff27c8fcf56991bd3f00dd49110175143 Mon Sep 17 00:00:00 2001 From: Dalton Hill Date: Mon, 3 Feb 2020 09:19:15 -0600 Subject: [PATCH] Right Click to Navigate History (#3547) * merge && backwards functionality working (kind of) WIP * wip - need to fix forward and backwards buttons * history works well but only for channel related pages - need to add title update hook for other pages * moved useEffect to router * renamed buttonNavigation -> navigationButton * removed unused history * fixed issue with lbry.tv * disable button if no entries * added max size for history * set correct margin-top for nav button dropdown * cleanup Co-authored-by: Sean Yesmunt --- static/app-strings.json | 7 +- ui/component/header/view.jsx | 28 ++++---- ui/component/navigationButton/index.js | 3 + ui/component/navigationButton/view.jsx | 91 ++++++++++++++++++++++++++ ui/component/router/index.js | 29 ++++++-- ui/component/router/view.jsx | 45 +++++++++++++ ui/page/show/view.jsx | 21 +----- ui/scss/component/_header.scss | 26 ++++++++ ui/scss/component/menu-button.scss | 2 + ui/store.js | 4 +- 10 files changed, 208 insertions(+), 48 deletions(-) create mode 100644 ui/component/navigationButton/index.js create mode 100644 ui/component/navigationButton/view.jsx 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 ( +
      +
      + ); +}; +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();