From 7eb5eb99961e18a1ad4540dd1b40b911fe7bc166 Mon Sep 17 00:00:00 2001 From: Rafael Date: Wed, 4 May 2022 09:34:31 -0300 Subject: [PATCH] Add channel selector on HeaderMenuButton - Moved from reach/ui to material/ui menu components, because reach ui wouldn't work with 2 menus - This channel selector stores the default on settings - setActiveChannelIfNotSet was deprecated, if the account has channels, it will always return a channel even if there is no active channel or stored channel --- ui/component/app/index.js | 3 +- ui/component/app/view.jsx | 8 +- ui/component/channelSelector/view.jsx | 94 +++++++------- ui/component/channelThumbnail/view.jsx | 7 +- ui/component/common/header-menu-link.jsx | 14 ++- ui/component/headerProfileMenuButton/view.jsx | 118 +++++++++++------- ui/component/publishName/index.js | 2 - ui/page/creatorDashboard/index.js | 3 +- ui/redux/selectors/app.js | 2 +- ui/scss/component/_channel.scss | 6 + ui/scss/component/menu-button.scss | 41 +++++- 11 files changed, 190 insertions(+), 108 deletions(-) diff --git a/ui/component/app/index.js b/ui/component/app/index.js index 47eac8d2b..703bcb67d 100644 --- a/ui/component/app/index.js +++ b/ui/component/app/index.js @@ -23,7 +23,7 @@ import { import { selectUploadCount } from 'redux/selectors/publish'; import { doSetLanguage } from 'redux/actions/settings'; import { doSyncLoop } from 'redux/actions/sync'; -import { doDownloadUpgradeRequested, doSignIn, doSetActiveChannel, doSetIncognito } from 'redux/actions/app'; +import { doDownloadUpgradeRequested, doSignIn, doSetIncognito } from 'redux/actions/app'; import { doFetchModBlockedList, doFetchCommentModAmIList } from 'redux/actions/comments'; import App from './view'; @@ -57,7 +57,6 @@ const perform = (dispatch) => ({ requestDownloadUpgrade: () => dispatch(doDownloadUpgradeRequested()), syncLoop: (noInterval) => dispatch(doSyncLoop(noInterval)), setReferrer: (referrer, doClaim) => dispatch(doUserSetReferrer(referrer, doClaim)), - setActiveChannelIfNotSet: () => dispatch(doSetActiveChannel()), setIncognito: () => dispatch(doSetIncognito()), fetchModBlockedList: () => dispatch(doFetchModBlockedList()), fetchModAmIList: () => dispatch(doFetchCommentModAmIList()), diff --git a/ui/component/app/view.jsx b/ui/component/app/view.jsx index 2c2dc6d28..80abc4ab3 100644 --- a/ui/component/app/view.jsx +++ b/ui/component/app/view.jsx @@ -86,7 +86,6 @@ type Props = { activeChannelClaim: ?ChannelClaim, myChannelClaimIds: ?Array, hasPremiumPlus: ?boolean, - setActiveChannelIfNotSet: () => void, setIncognito: (boolean) => void, fetchModBlockedList: () => void, fetchModAmIList: () => void, @@ -120,7 +119,6 @@ function App(props: Props) { syncFatalError, myChannelClaimIds, activeChannelClaim, - setActiveChannelIfNotSet, setIncognito, fetchModBlockedList, hasPremiumPlus, @@ -312,9 +310,7 @@ function App(props: Props) { }, [currentModal]); useEffect(() => { - if (hasMyChannels && !hasActiveChannelClaim) { - setActiveChannelIfNotSet(); - } else if (hasNoChannels) { + if (hasNoChannels) { setIncognito(true); } @@ -323,7 +319,7 @@ function App(props: Props) { fetchModAmIList(); } // eslint-disable-next-line react-hooks/exhaustive-deps - }, [hasMyChannels, hasNoChannels, hasActiveChannelClaim, setActiveChannelIfNotSet, setIncognito]); + }, [hasMyChannels, hasNoChannels, hasActiveChannelClaim, setIncognito]); useEffect(() => { // $FlowFixMe diff --git a/ui/component/channelSelector/view.jsx b/ui/component/channelSelector/view.jsx index 9e5a278ae..ea3a156a9 100644 --- a/ui/component/channelSelector/view.jsx +++ b/ui/component/channelSelector/view.jsx @@ -26,49 +26,10 @@ type Props = { odyseeMembershipByUri: (uri: string) => string, storeSelection?: boolean, doSetClientSetting: (key: string, value: string, pushPrefs: boolean) => void, + isHeaderMenu: boolean, }; -type ListItemProps = { - uri: string, - isSelected?: boolean, - claimsByUri: { [string]: any }, - doFetchUserMemberships: (claimIdCsv: string) => void, - odyseeMembershipByUri: (uri: string) => string, -}; - -function ChannelListItem(props: ListItemProps) { - const { uri, isSelected = false, claimsByUri, doFetchUserMemberships, odyseeMembershipByUri } = props; - - const membership = odyseeMembershipByUri(uri); - - const shouldFetchUserMemberships = true; - useGetUserMemberships(shouldFetchUserMemberships, [uri], claimsByUri, doFetchUserMemberships, [uri]); - - return ( -
- - - - {isSelected && } -
- ); -} - -type IncognitoSelectorProps = { - isSelected?: boolean, -}; - -function IncognitoSelector(props: IncognitoSelectorProps) { - return ( -
- -

{__('Anonymous')}

- {props.isSelected && } -
- ); -} - -function ChannelSelector(props: Props) { +export default function ChannelSelector(props: Props) { const { channels, activeChannelClaim, @@ -81,6 +42,7 @@ function ChannelSelector(props: Props) { doFetchUserMemberships, storeSelection, doSetClientSetting, + isHeaderMenu, } = props; const { @@ -102,8 +64,14 @@ function ChannelSelector(props: Props) { return (
- - {(incognito && !hideAnon) || !activeChannelUrl ? ( + + {isHeaderMenu ? ( + <> + + {__('Change Default Channel')} + + + ) : (incognito && !hideAnon) || !activeChannelUrl ? ( ) : ( void, + odyseeMembershipByUri: (uri: string) => string, +}; + +function ChannelListItem(props: ListItemProps) { + const { uri, isSelected = false, claimsByUri, doFetchUserMemberships, odyseeMembershipByUri } = props; + + const membership = odyseeMembershipByUri(uri); + + const shouldFetchUserMemberships = true; + useGetUserMemberships(shouldFetchUserMemberships, [uri], claimsByUri, doFetchUserMemberships, [uri]); + + return ( +
+ + + + {isSelected && } +
+ ); +} + +type IncognitoSelectorProps = { + isSelected?: boolean, +}; + +function IncognitoSelector(props: IncognitoSelectorProps) { + return ( +
+ +

{__('Anonymous')}

+ {props.isSelected && } +
+ ); +} diff --git a/ui/component/channelThumbnail/view.jsx b/ui/component/channelThumbnail/view.jsx index 04d28e67b..638119f69 100644 --- a/ui/component/channelThumbnail/view.jsx +++ b/ui/component/channelThumbnail/view.jsx @@ -18,6 +18,7 @@ type Props = { obscure?: boolean, small?: boolean, xsmall?: boolean, + xxsmall?: boolean, allowGifs?: boolean, claim: ?ChannelClaim, doResolveUri: (string) => void, @@ -44,6 +45,7 @@ function ChannelThumbnail(props: Props) { obscure, small = false, xsmall = false, + xxsmall, allowGifs = false, claim, doResolveUri, @@ -110,6 +112,7 @@ function ChannelThumbnail(props: Props) { [colorClassName]: !showThumb, 'channel-thumbnail--small': small, 'channel-thumbnail--xsmall': xsmall, + 'channel-thumbnail--xxsmall': xxsmall, 'channel-thumbnail--resolving': isResolving, })} > @@ -117,8 +120,8 @@ function ChannelThumbnail(props: Props) { alt={__('Channel profile picture')} className={!channelThumbnail ? 'channel-thumbnail__default' : 'channel-thumbnail__custom'} src={(!thumbLoadError && channelThumbnail) || defaultAvatar} - width={small || xsmall ? 64 : 160} - quality={small || xsmall ? 85 : 95} + width={xxsmall ? 16 : small || xsmall ? 64 : 160} + quality={xxsmall ? 16 : small || xsmall ? 85 : 95} loading={noLazyLoad ? undefined : 'lazy'} onError={() => { if (setThumbUploadError) { diff --git a/ui/component/common/header-menu-link.jsx b/ui/component/common/header-menu-link.jsx index 1d0362b5b..64278f6be 100644 --- a/ui/component/common/header-menu-link.jsx +++ b/ui/component/common/header-menu-link.jsx @@ -3,6 +3,8 @@ import * as PAGES from 'constants/pages'; import React from 'react'; import { Link, useHistory } from 'react-router-dom'; import { MenuLink, MenuItem } from '@reach/menu-button'; +import MuiMenuItem from '@mui/material/MenuItem'; +import MuiLink from '@mui/material/Link'; import Icon from 'component/common/icon'; type Props = { @@ -10,13 +12,23 @@ type Props = { name: string, page: string, requiresAuth?: boolean, + useMui?: boolean, }; export default function HeaderMenuLink(props: Props) { - const { icon, name, page, requiresAuth } = props; + const { icon, name, page, requiresAuth, useMui } = props; const { push } = useHistory(); + if (useMui) { + return ( + + + {name} + + ); + } + if (requiresAuth) { return ( push(`/$/${PAGES.AUTH}`)}> diff --git a/ui/component/headerProfileMenuButton/view.jsx b/ui/component/headerProfileMenuButton/view.jsx index b5a90d5af..ccda3f112 100644 --- a/ui/component/headerProfileMenuButton/view.jsx +++ b/ui/component/headerProfileMenuButton/view.jsx @@ -1,7 +1,7 @@ // @flow import 'scss/component/_header.scss'; -import { Menu, MenuList, MenuButton, MenuItem } from '@reach/menu-button'; +import { Menu as MuiMenu, MenuItem as MuiMenuItem } from '@mui/material'; import * as ICONS from 'constants/icons'; import * as PAGES from 'constants/pages'; import ChannelThumbnail from 'component/channelThumbnail'; @@ -10,6 +10,8 @@ import HeaderMenuLink from 'component/common/header-menu-link'; import Icon from 'component/common/icon'; import React from 'react'; import Skeleton from '@mui/material/Skeleton'; +import ChannelSelector from 'component/channelSelector'; +import Button from 'component/button'; type HeaderMenuButtonProps = { myChannelClaimIds: ?Array, @@ -22,6 +24,15 @@ type HeaderMenuButtonProps = { export default function HeaderProfileMenuButton(props: HeaderMenuButtonProps) { const { myChannelClaimIds, activeChannelClaim, authenticated, email, signOut } = props; + const [anchorEl, setAnchorEl] = React.useState(null); + const open = Boolean(anchorEl); + const handleClick = (event) => { + setAnchorEl(!anchorEl ? event.currentTarget : null); + }; + const handleClose = () => { + setAnchorEl(null); + }; + const activeChannelUrl = activeChannelClaim && activeChannelClaim.permanent_url; // activeChannel will be: undefined = fetching, null = nothing, or { channel claim } const noActiveChannel = activeChannelUrl === null; @@ -29,53 +40,74 @@ export default function HeaderProfileMenuButton(props: HeaderMenuButtonProps) { return (
- - {pendingChannelFetch ? ( - - ) : ( - - {activeChannelUrl ? ( - - ) : ( - - )} - - )} + {pendingChannelFetch ? ( + + ) : ( + + )} + + {authenticated ? ( + <> + + + + + + + - - {authenticated ? ( - <> - - - - - - - - -
+ +
+
{__('Sign Out')}
{email} - - - ) : ( - <> - - - - - - )} - -
+
+ + + ) : ( + <> + + + + + + )} +
); } diff --git a/ui/component/publishName/index.js b/ui/component/publishName/index.js index 0f0df1a99..311cde6f2 100644 --- a/ui/component/publishName/index.js +++ b/ui/component/publishName/index.js @@ -8,7 +8,6 @@ import { selectCurrentUploads, } from 'redux/selectors/publish'; import { selectActiveChannelClaim, selectIncognito } from 'redux/selectors/app'; -import { doSetActiveChannel } from 'redux/actions/app'; import PublishPage from './view'; const select = (state) => ({ @@ -26,7 +25,6 @@ const select = (state) => ({ const perform = (dispatch) => ({ updatePublishForm: (value) => dispatch(doUpdatePublishForm(value)), prepareEdit: (claim, uri) => dispatch(doPrepareEdit(claim, uri)), - setActiveChannel: (claimId) => dispatch(doSetActiveChannel(claimId)), }); export default connect(select, perform)(PublishPage); diff --git a/ui/page/creatorDashboard/index.js b/ui/page/creatorDashboard/index.js index ac32402e5..0332b7ffd 100644 --- a/ui/page/creatorDashboard/index.js +++ b/ui/page/creatorDashboard/index.js @@ -1,7 +1,6 @@ import { connect } from 'react-redux'; import { selectHasChannels, selectFetchingMyChannels } from 'redux/selectors/claims'; import { selectActiveChannelClaim } from 'redux/selectors/app'; -import { doSetActiveChannel } from 'redux/actions/app'; import CreatorDashboardPage from './view'; const select = (state) => ({ @@ -10,4 +9,4 @@ const select = (state) => ({ activeChannelClaim: selectActiveChannelClaim(state), }); -export default connect(select, { doSetActiveChannel })(CreatorDashboardPage); +export default connect(select)(CreatorDashboardPage); diff --git a/ui/redux/selectors/app.js b/ui/redux/selectors/app.js index 234754cf7..3761811a5 100644 --- a/ui/redux/selectors/app.js +++ b/ui/redux/selectors/app.js @@ -76,7 +76,7 @@ export const selectActiveChannelClaim = createSelector( // Null: has none. Undefined: not resolved, default state, could have or not if (!userEmail || myChannelClaims === null) { return null; - } else if (!activeChannelClaim || !myChannelClaims || !myChannelClaims.length) { + } else if (!myChannelClaims || !myChannelClaims.length) { return undefined; } diff --git a/ui/scss/component/_channel.scss b/ui/scss/component/_channel.scss index abecffb97..b1696b699 100644 --- a/ui/scss/component/_channel.scss +++ b/ui/scss/component/_channel.scss @@ -267,6 +267,12 @@ $actions-z-index: 2; margin-right: var(--spacing-xs); } +.channel-thumbnail--xxsmall { + height: 16px; + width: 16px; + margin-right: var(--spacing-s) !important; +} + .channel-thumbnail--waiting { background-color: var(--color-gray-5); border-radius: var(--border-radius); diff --git a/ui/scss/component/menu-button.scss b/ui/scss/component/menu-button.scss index ca5a034c0..ff799e695 100644 --- a/ui/scss/component/menu-button.scss +++ b/ui/scss/component/menu-button.scss @@ -31,16 +31,17 @@ reach-portal { max-width: calc(100% - var(--height-button) - var(--spacing-xs)); } -[data-reach-menu-list] { +[data-reach-menu-list], +.MuiMenu-list--paper { display: block; white-space: nowrap; outline: none; // background-color: var(--color-header-background) !important; - background-color: rgba(var(--color-header-button-base), 0.9) !important; + background-color: rgba(var(--color-header-button-base), 0.5) !important; border: 2px solid var(--color-header-background) !important; border-top: none; - -webkit-backdrop-filter: blur(4px); - backdrop-filter: blur(4px); + -webkit-backdrop-filter: blur(4px) !important; + backdrop-filter: blur(4px) !important; &:focus-visible { background-color: var(--color-header-background) !important; @@ -85,6 +86,13 @@ reach-portal { } } +.menu__link:hover { + color: var(--color-odysee-contrast) !important; + background-color: var(--color-odysee) !important; + border-radius: var(--border-radius); + box-shadow: none; +} + .menu__button { display: flex; justify-content: center; @@ -125,6 +133,22 @@ reach-portal { .menu__list--header { @extend .menu__list; + .channel__selector { + margin: 0px !important; + display: flex; + + .menu__link { + margin: 0px !important; + padding: var(--spacing-xs) var(--spacing-s) !important; + width: 100%; + color: inherit; + } + + .icon--ChevronDown { + margin-left: var(--spacing-s); + } + } + [data-reach-menu-item][data-selected] { .menu__link { color: var(--color-odysee-contrast) !important; @@ -155,6 +179,12 @@ reach-portal { @extend .menu__list; } +.MuiMenuItem-root { + margin-left: 0px !important; + font-size: var(--font-small) !important; + display: block !important; +} + .menu__link { display: flex; align-items: center; @@ -162,6 +192,7 @@ reach-portal { // padding-right: var(--spacing-l); padding: var(--spacing-xs) var(--spacing-s) var(--spacing-xs) var(--spacing-s); height: var(--button-height); + color: var(--color-primary-contrast) !important; .icon { stroke: var(--color-menu-icon); @@ -186,7 +217,7 @@ reach-portal { display: block; color: var(--color-text); font-size: var(--font-small); - padding-top: 0; + padding: 0px; white-space: normal; text-overflow: ellipsis; overflow-x: hidden;