Improve comment selectors

This commit is contained in:
Rafael 2022-02-07 16:30:42 -03:00 committed by Thomas Zarebczan
parent 2b56ca8599
commit c758c59066
8 changed files with 121 additions and 61 deletions

View file

@ -9,20 +9,26 @@ import React from 'react';
import { Tabs, TabList, Tab, TabPanels, TabPanel } from 'component/common/tabs'; import { Tabs, TabList, Tab, TabPanels, TabPanel } from 'component/common/tabs';
import { FREE_GLOBAL_STICKERS, PAID_GLOBAL_STICKERS } from 'constants/stickers'; import { FREE_GLOBAL_STICKERS, PAID_GLOBAL_STICKERS } from 'constants/stickers';
export const SELECTOR_TABS = {
EMOJI: 0,
STICKER: 1,
};
type Props = { type Props = {
claimIsMine?: boolean, claimIsMine?: boolean,
openTab?: number,
addEmoteToComment: (string) => void, addEmoteToComment: (string) => void,
handleSelectSticker: (any) => void, handleSelectSticker: (any) => void,
closeSelector?: () => void, closeSelector?: () => void,
}; };
export default function CommentSelectors(props: Props) { export default function CommentSelectors(props: Props) {
const { claimIsMine, addEmoteToComment, handleSelectSticker, closeSelector } = props; const { claimIsMine, openTab, addEmoteToComment, handleSelectSticker, closeSelector } = props;
const tabProps = { closeSelector }; const tabProps = { closeSelector };
return ( return (
<Tabs> <Tabs index={openTab}>
<TabList className="tabs__list--comment-selector"> <TabList className="tabs__list--comment-selector">
<Tab>{__('Emojis')}</Tab> <Tab>{__('Emojis')}</Tab>
<Tab>{__('Stickers')}</Tab> <Tab>{__('Stickers')}</Tab>

View file

@ -50,10 +50,6 @@ export const StickerActionButton = (stickerButtonProps: StickerButtonProps) => {
const { isReviewingStickerComment, ...buttonProps } = stickerButtonProps; const { isReviewingStickerComment, ...buttonProps } = stickerButtonProps;
return ( return (
<Button <Button {...buttonProps} title={__('Stickers')} label={isReviewingStickerComment ? __('Change') : undefined} />
{...buttonProps}
title={__('Stickers')}
label={isReviewingStickerComment ? __('Different Sticker') : undefined}
/>
); );
}; };

View file

@ -15,10 +15,12 @@ type Props = {
tipAmount: number, tipAmount: number,
activeTab: string, activeTab: string,
message: string, message: string,
isReviewingStickerComment?: boolean,
stickerPreviewComponent?: any,
}; };
export const TipReviewBox = (props: Props) => { export const TipReviewBox = (props: Props) => {
const { activeChannelUrl, tipAmount, activeTab, message } = props; const { activeChannelUrl, tipAmount, activeTab, message, isReviewingStickerComment, stickerPreviewComponent } = props;
return ( return (
<div className="commentCreate__supportCommentPreview"> <div className="commentCreate__supportCommentPreview">
@ -29,11 +31,18 @@ export const TipReviewBox = (props: Props) => {
size={activeTab === TAB_LBC ? 18 : 2} size={activeTab === TAB_LBC ? 18 : 2}
/> />
<ChannelThumbnail xsmall uri={activeChannelUrl} /> {isReviewingStickerComment ? (
<div className="commentCreate__supportCommentBody"> stickerPreviewComponent
<UriIndicator uri={activeChannelUrl} link /> ) : (
<div>{message}</div> <>
</div> <ChannelThumbnail xsmall uri={activeChannelUrl} />
<div className="commentCreate__supportCommentBody">
<UriIndicator uri={activeChannelUrl} link />
<div>{message}</div>
</div>
</>
)}
</div> </div>
); );
}; };

View file

@ -14,7 +14,7 @@ import * as PAGES from 'constants/pages';
import * as MODALS from 'constants/modal_types'; import * as MODALS from 'constants/modal_types';
import Button from 'component/button'; import Button from 'component/button';
import classnames from 'classnames'; import classnames from 'classnames';
import CommentSelectors from './comment-selectors'; import CommentSelectors, { SELECTOR_TABS } from './comment-selectors';
import React from 'react'; import React from 'react';
import type { ElementRef } from 'react'; import type { ElementRef } from 'react';
import usePersistedState from 'effects/use-persisted-state'; import usePersistedState from 'effects/use-persisted-state';
@ -34,7 +34,6 @@ type TipParams = { tipAmount: number, tipChannelName: string, channelClaimId: st
type UserParams = { activeChannelName: ?string, activeChannelId: ?string }; type UserParams = { activeChannelName: ?string, activeChannelId: ?string };
type Props = { type Props = {
activeChannel: string,
activeChannelClaimId?: string, activeChannelClaimId?: string,
activeChannelName?: string, activeChannelName?: string,
activeChannelUrl?: string, activeChannelUrl?: string,
@ -106,6 +105,7 @@ export function CommentCreate(props: Props) {
const formFieldRef: ElementRef<any> = React.useRef(); const formFieldRef: ElementRef<any> = React.useRef();
const buttonRef: ElementRef<any> = React.useRef(); const buttonRef: ElementRef<any> = React.useRef();
const slimInputButtonRef: ElementRef<any> = React.useRef();
const { const {
push, push,
@ -126,7 +126,7 @@ export function CommentCreate(props: Props) {
const [activeTab, setActiveTab] = React.useState(); const [activeTab, setActiveTab] = React.useState();
const [tipError, setTipError] = React.useState(); const [tipError, setTipError] = React.useState();
const [deletedComment, setDeletedComment] = React.useState(false); const [deletedComment, setDeletedComment] = React.useState(false);
const [showSelectors, setShowSelectors] = React.useState(false); const [showSelectors, setShowSelectors] = React.useState({ tab: undefined, open: false });
const [disableReviewButton, setDisableReviewButton] = React.useState(); const [disableReviewButton, setDisableReviewButton] = React.useState();
const [exchangeRate, setExchangeRate] = React.useState(); const [exchangeRate, setExchangeRate] = React.useState();
const [canReceiveFiatTip, setCanReceiveFiatTip] = React.useState(undefined); const [canReceiveFiatTip, setCanReceiveFiatTip] = React.useState(undefined);
@ -180,9 +180,15 @@ export function CommentCreate(props: Props) {
} }
function handleStickerComment() { function handleStickerComment() {
if (selectedSticker) setReviewingStickerComment(false); if (selectedSticker) {
setReviewingStickerComment(false);
setSelectedSticker(undefined);
setShowSelectors({ tab: SELECTOR_TABS.STICKER, open: true });
} else {
setShowSelectors({ tab: showSelectors.tab || undefined, open: !showSelectors.open });
}
setTipSelector(false); setTipSelector(false);
setShowSelectors(!showSelectors);
} }
function handleSelectSticker(sticker: any) { function handleSelectSticker(sticker: any) {
@ -190,7 +196,7 @@ export function CommentCreate(props: Props) {
setSelectedSticker(sticker); setSelectedSticker(sticker);
setReviewingStickerComment(true); setReviewingStickerComment(true);
setTipAmount(sticker.price || 0); setTipAmount(sticker.price || 0);
setShowSelectors(false); setShowSelectors({ tab: showSelectors.tab || undefined, open: false });
if (sticker.price && sticker.price > 0) { if (sticker.price && sticker.price > 0) {
setActiveTab(canReceiveFiatTip ? TAB_FIAT : TAB_LBC); setActiveTab(canReceiveFiatTip ? TAB_FIAT : TAB_LBC);
@ -209,7 +215,7 @@ export function CommentCreate(props: Props) {
if (stickerPrice) { if (stickerPrice) {
setReviewingStickerComment(false); setReviewingStickerComment(false);
setShowSelectors(false); setShowSelectors({ tab: showSelectors.tab || undefined, open: false });
setSelectedSticker(null); setSelectedSticker(null);
} }
} }
@ -362,7 +368,7 @@ export function CommentCreate(props: Props) {
setSelectedSticker(null); setSelectedSticker(null);
setReviewingStickerComment(false); setReviewingStickerComment(false);
setShowSelectors(false); setShowSelectors({ tab: showSelectors.tab || undefined, open: false });
setTipSelector(false); setTipSelector(false);
} }
@ -377,6 +383,13 @@ export function CommentCreate(props: Props) {
} }
}, []); // eslint-disable-line react-hooks/exhaustive-deps }, []); // eslint-disable-line react-hooks/exhaustive-deps
// change sticker selection
React.useEffect(() => {
if (isMobile && showSelectors.tab && slimInputButtonRef && slimInputButtonRef.current) {
slimInputButtonRef.current.click();
}
}, [isMobile, showSelectors.tab]);
// Notifications: Fetch top-level comments to identify if it has been deleted and can reply to it // Notifications: Fetch top-level comments to identify if it has been deleted and can reply to it
React.useEffect(() => { React.useEffect(() => {
if (shouldFetchComment && doCommentById) { if (shouldFetchComment && doCommentById) {
@ -394,7 +407,7 @@ export function CommentCreate(props: Props) {
// Stickers: Check if creator has a tip account saved (on selector so that if a paid sticker is selected, // Stickers: Check if creator has a tip account saved (on selector so that if a paid sticker is selected,
// it defaults to LBC tip instead of USD) // it defaults to LBC tip instead of USD)
React.useEffect(() => { React.useEffect(() => {
if (!stripeEnvironment || !showSelectors || canReceiveFiatTip !== undefined || !tipChannelName) return; if (!stripeEnvironment || canReceiveFiatTip !== undefined || !tipChannelName) return;
Lbryio.call( Lbryio.call(
'account', 'account',
@ -414,7 +427,7 @@ export function CommentCreate(props: Props) {
} }
}) })
.catch(() => {}); .catch(() => {});
}, [canReceiveFiatTip, channelClaimId, showSelectors, tipChannelName]); }, [canReceiveFiatTip, channelClaimId, tipChannelName]);
// Handle keyboard shortcut comment creation // Handle keyboard shortcut comment creation
React.useEffect(() => { React.useEffect(() => {
@ -486,7 +499,12 @@ export function CommentCreate(props: Props) {
); );
} }
const commentSelectorsProps = { claimIsMine, addEmoteToComment, handleSelectSticker }; const commentSelectorsProps = {
claimIsMine,
addEmoteToComment,
handleSelectSticker,
openTab: showSelectors.tab || undefined,
};
const submitButtonProps = { button: 'primary', type: 'submit', requiresAuth: true }; const submitButtonProps = { button: 'primary', type: 'submit', requiresAuth: true };
const actionButtonProps = { button: 'alt' }; const actionButtonProps = { button: 'alt' };
const tipButtonProps = { const tipButtonProps = {
@ -497,6 +515,12 @@ export function CommentCreate(props: Props) {
onClick: handleSelectTipComment, onClick: handleSelectTipComment,
}; };
const cancelButtonProps = { button: 'link', label: __('Cancel') }; const cancelButtonProps = { button: 'link', label: __('Cancel') };
const stickerReviewProps = {
activeChannelUrl,
src: selectedSticker ? selectedSticker.url : '',
price: selectedSticker ? selectedSticker.price : 0,
exchangeRate,
};
return ( return (
<Form <Form
@ -507,16 +531,7 @@ export function CommentCreate(props: Props) {
'commentCreate--bottom': bottom, 'commentCreate--bottom': bottom,
})} })}
> >
{selectedSticker ? ( {isReviewingSupportComment ? (
activeChannelUrl && (
<StickerReviewBox
activeChannelUrl={activeChannelUrl}
src={selectedSticker.url}
price={selectedSticker.price || 0}
exchangeRate={exchangeRate}
/>
)
) : isReviewingSupportComment ? (
activeChannelUrl && activeChannelUrl &&
activeTab && ( activeTab && (
<TipReviewBox <TipReviewBox
@ -524,12 +539,19 @@ export function CommentCreate(props: Props) {
tipAmount={tipAmount} tipAmount={tipAmount}
activeTab={activeTab} activeTab={activeTab}
message={commentValue} message={commentValue}
isReviewingStickerComment={isReviewingStickerComment}
stickerPreviewComponent={selectedSticker && <StickerReviewBox {...stickerReviewProps} />}
/> />
) )
) : selectedSticker ? (
activeChannelUrl && <StickerReviewBox {...stickerReviewProps} />
) : ( ) : (
<> <>
{!isMobile && showSelectors && ( {!isMobile && showSelectors.open && (
<CommentSelectors {...commentSelectorsProps} closeSelector={() => setShowSelectors(false)} /> <CommentSelectors
{...commentSelectorsProps}
closeSelector={() => setShowSelectors({ tab: showSelectors.tab || undefined, open: false })}
/>
)} )}
<FormField <FormField
@ -548,7 +570,8 @@ export function CommentCreate(props: Props) {
onChange={(e) => setCommentValue(SIMPLE_SITE || !advancedEditor || isReply ? e.target.value : e)} onChange={(e) => setCommentValue(SIMPLE_SITE || !advancedEditor || isReply ? e.target.value : e)}
handleTip={(isLBC) => handleSelectTipComment(isLBC ? TAB_LBC : TAB_FIAT)} handleTip={(isLBC) => handleSelectTipComment(isLBC ? TAB_LBC : TAB_FIAT)}
handleSubmit={handleCreateComment} handleSubmit={handleCreateComment}
slimInput={isMobile} slimInput={isMobile && uri} // "uri": make sure it's on a file page
slimInputButtonRef={slimInputButtonRef}
commentSelectorsProps={commentSelectorsProps} commentSelectorsProps={commentSelectorsProps}
submitButtonRef={buttonRef} submitButtonRef={buttonRef}
setShowSelectors={setShowSelectors} setShowSelectors={setShowSelectors}
@ -631,21 +654,23 @@ export function CommentCreate(props: Props) {
) )
)} )}
{!isMobile && ( {(!isMobile || isReviewingStickerComment) && (
<StickerActionButton
{...actionButtonProps}
isReviewingStickerComment={isReviewingStickerComment}
icon={ICONS.STICKER}
onClick={handleStickerComment}
/>
)}
{(!isMobile || isReviewingStickerComment) && !supportDisabled && (
<> <>
<TipActionButton {...tipButtonProps} name={__('Credits')} icon={ICONS.LBC} tab={TAB_LBC} /> <StickerActionButton
{...actionButtonProps}
isReviewingStickerComment={isReviewingStickerComment}
icon={ICONS.STICKER}
onClick={handleStickerComment}
/>
{stripeEnvironment && ( {!supportDisabled && !claimIsMine && (
<TipActionButton {...tipButtonProps} name={__('Cash')} icon={ICONS.FINANCE} tab={TAB_FIAT} /> <>
<TipActionButton {...tipButtonProps} name={__('Credits')} icon={ICONS.LBC} tab={TAB_LBC} />
{stripeEnvironment && (
<TipActionButton {...tipButtonProps} name={__('Cash')} icon={ICONS.FINANCE} tab={TAB_FIAT} />
)}
</>
)} )}
</> </>
)} )}

View file

@ -45,13 +45,14 @@ type Props = {
type?: string, type?: string,
value?: string | number, value?: string | number,
slimInput?: boolean, slimInput?: boolean,
slimInputButtonRef?: any,
commentSelectorsProps?: any, commentSelectorsProps?: any,
showSelectors?: boolean, showSelectors?: any,
submitButtonRef?: any, submitButtonRef?: any,
tipModalOpen?: boolean, tipModalOpen?: boolean,
noticeLabel?: any, noticeLabel?: any,
onChange?: (any) => any, onChange?: (any) => any,
setShowSelectors?: (boolean) => void, setShowSelectors?: ({ tab?: string, open: boolean }) => void,
quickActionHandler?: (any) => any, quickActionHandler?: (any) => any,
render?: () => React$Node, render?: () => React$Node,
handleTip?: (isLBC: boolean) => any, handleTip?: (isLBC: boolean) => any,
@ -88,7 +89,7 @@ export class FormField extends React.PureComponent<Props, State> {
const input = this.input.current; const input = this.input.current;
// Opened selectors (emoji/sticker) -> blur input and hide keyboard // Opened selectors (emoji/sticker) -> blur input and hide keyboard
if (slimInput && showSelectors && input) input.blur(); if (slimInput && showSelectors && showSelectors.open && input) input.blur();
} }
render() { render() {
@ -114,6 +115,7 @@ export class FormField extends React.PureComponent<Props, State> {
textAreaMaxLength, textAreaMaxLength,
type, type,
slimInput, slimInput,
slimInputButtonRef,
commentSelectorsProps, commentSelectorsProps,
showSelectors, showSelectors,
submitButtonRef, submitButtonRef,
@ -270,10 +272,15 @@ export class FormField extends React.PureComponent<Props, State> {
<TextareaWrapper <TextareaWrapper
isDrawerOpen={Boolean(this.state.drawerOpen)} isDrawerOpen={Boolean(this.state.drawerOpen)}
toggleDrawer={() => this.setState({ drawerOpen: !this.state.drawerOpen })} toggleDrawer={() => this.setState({ drawerOpen: !this.state.drawerOpen })}
closeSelector={setShowSelectors ? () => setShowSelectors(false) : () => {}} closeSelector={
setShowSelectors && showSelectors
? () => setShowSelectors({ tab: showSelectors.tab || undefined, open: false })
: () => {}
}
commentSelectorsProps={commentSelectorsProps} commentSelectorsProps={commentSelectorsProps}
showSelectors={Boolean(showSelectors)} showSelectors={Boolean(showSelectors && showSelectors.open)}
slimInput={slimInput} slimInput={slimInput}
slimInputButtonRef={slimInputButtonRef}
tipModalOpen={tipModalOpen} tipModalOpen={tipModalOpen}
> >
{(!slimInput || this.state.drawerOpen) && (label || quickAction) && ( {(!slimInput || this.state.drawerOpen) && (label || quickAction) && (
@ -303,7 +310,11 @@ export class FormField extends React.PureComponent<Props, State> {
maxLength={textAreaMaxLength || FF_MAX_CHARS_DEFAULT} maxLength={textAreaMaxLength || FF_MAX_CHARS_DEFAULT}
inputRef={this.input} inputRef={this.input}
isLivestream={isLivestream} isLivestream={isLivestream}
toggleSelectors={setShowSelectors ? () => setShowSelectors(!showSelectors) : undefined} toggleSelectors={
setShowSelectors && showSelectors
? () => setShowSelectors({ tab: showSelectors.tab || undefined, open: !showSelectors.open })
: undefined
}
handleTip={handleTip} handleTip={handleTip}
handleSubmit={() => { handleSubmit={() => {
if (handleSubmit) handleSubmit(); if (handleSubmit) handleSubmit();
@ -311,6 +322,7 @@ export class FormField extends React.PureComponent<Props, State> {
}} }}
claimIsMine={commentSelectorsProps && commentSelectorsProps.claimIsMine} claimIsMine={commentSelectorsProps && commentSelectorsProps.claimIsMine}
{...inputProps} {...inputProps}
slimInput={slimInput}
handlePreventClick={ handlePreventClick={
!this.state.drawerOpen ? () => this.setState({ drawerOpen: true }) : undefined !this.state.drawerOpen ? () => this.setState({ drawerOpen: true }) : undefined
} }
@ -360,6 +372,7 @@ export default FormField;
type TextareaWrapperProps = { type TextareaWrapperProps = {
slimInput?: boolean, slimInput?: boolean,
slimInputButtonRef?: any,
children: Node, children: Node,
isDrawerOpen: boolean, isDrawerOpen: boolean,
showSelectors?: boolean, showSelectors?: boolean,
@ -373,6 +386,7 @@ function TextareaWrapper(wrapperProps: TextareaWrapperProps) {
const { const {
children, children,
slimInput, slimInput,
slimInputButtonRef,
isDrawerOpen, isDrawerOpen,
commentSelectorsProps, commentSelectorsProps,
showSelectors, showSelectors,
@ -388,7 +402,7 @@ function TextareaWrapper(wrapperProps: TextareaWrapperProps) {
return slimInput ? ( return slimInput ? (
!isDrawerOpen ? ( !isDrawerOpen ? (
<div role="button" onClick={toggleDrawer}> <div ref={slimInputButtonRef} role="button" onClick={toggleDrawer}>
{children} {children}
</div> </div>
) : ( ) : (

View file

@ -1,5 +1,4 @@
// @flow // @flow
import { useIsMobile } from 'effects/use-screensize';
import * as ICONS from 'constants/icons'; import * as ICONS from 'constants/icons';
import React from 'react'; import React from 'react';
import TextField from '@mui/material/TextField'; import TextField from '@mui/material/TextField';
@ -13,6 +12,7 @@ type Props = {
inputRef: any, inputRef: any,
submitButtonRef?: any, submitButtonRef?: any,
claimIsMine?: boolean, claimIsMine?: boolean,
slimInput?: boolean,
toggleSelectors: () => any, toggleSelectors: () => any,
handleTip: (isLBC: boolean) => void, handleTip: (isLBC: boolean) => void,
handleSubmit: () => any, handleSubmit: () => any,
@ -27,19 +27,18 @@ const TextareaSuggestionsInput = (props: Props) => {
inputDefaultProps, inputDefaultProps,
submitButtonRef, submitButtonRef,
claimIsMine, claimIsMine,
slimInput,
toggleSelectors, toggleSelectors,
handleTip, handleTip,
handleSubmit, handleSubmit,
handlePreventClick, handlePreventClick,
} = props; } = props;
const isMobile = useIsMobile();
const { InputProps, disabled, fullWidth, id, inputProps: autocompleteInputProps } = params; const { InputProps, disabled, fullWidth, id, inputProps: autocompleteInputProps } = params;
const inputProps = { ...autocompleteInputProps, ...inputDefaultProps }; const inputProps = { ...autocompleteInputProps, ...inputDefaultProps };
const autocompleteProps = { InputProps, disabled, fullWidth, id, inputProps }; const autocompleteProps = { InputProps, disabled, fullWidth, id, inputProps };
if (isMobile) { if (slimInput) {
InputProps.startAdornment = ( InputProps.startAdornment = (
<Button <Button
icon={ICONS.STICKER} icon={ICONS.STICKER}

View file

@ -59,6 +59,7 @@ type Props = {
autoFocus?: boolean, autoFocus?: boolean,
submitButtonRef?: any, submitButtonRef?: any,
claimIsMine?: boolean, claimIsMine?: boolean,
slimInput?: boolean,
doResolveUris: (uris: Array<string>, cache: boolean) => void, doResolveUris: (uris: Array<string>, cache: boolean) => void,
doSetMentionSearchResults: (query: string, uris: Array<string>) => void, doSetMentionSearchResults: (query: string, uris: Array<string>) => void,
onBlur: (any) => any, onBlur: (any) => any,
@ -92,6 +93,7 @@ export default function TextareaWithSuggestions(props: Props) {
autoFocus, autoFocus,
submitButtonRef, submitButtonRef,
claimIsMine, claimIsMine,
slimInput,
doResolveUris, doResolveUris,
doSetMentionSearchResults, doSetMentionSearchResults,
onBlur, onBlur,
@ -421,6 +423,7 @@ export default function TextareaWithSuggestions(props: Props) {
handlePreventClick={handlePreventClick} handlePreventClick={handlePreventClick}
submitButtonRef={submitButtonRef} submitButtonRef={submitButtonRef}
claimIsMine={claimIsMine} claimIsMine={claimIsMine}
slimInput={slimInput}
/> />
)} )}
renderOption={(optionProps, option) => ( renderOption={(optionProps, option) => (

View file

@ -171,6 +171,10 @@
@media (max-width: $breakpoint-small) { @media (max-width: $breakpoint-small) {
margin-top: var(--spacing-xxs); margin-top: var(--spacing-xxs);
> *:not(:last-child) {
margin-right: var(--spacing-xxs);
}
button { button {
height: 2rem; height: 2rem;
padding: 0px var(--spacing-s); padding: 0px var(--spacing-s);
@ -183,6 +187,10 @@
} }
} }
} }
.button--link {
padding: 0px !important;
}
} }
} }