2021-08-27 10:20:09 +02:00
// @flow
import React from 'react' ;
2021-10-08 05:47:39 +02:00
import { isNameValid , parseURI } from 'util/lbryURI' ;
2021-08-27 10:20:09 +02:00
import Button from 'component/button' ;
import ClaimPreview from 'component/claimPreview' ;
import { FormField } from 'component/common/form-components/form-field' ;
import Icon from 'component/common/icon' ;
import TagsSearch from 'component/tagsSearch' ;
import * as ICONS from 'constants/icons' ;
import { getUriForSearchTerm } from 'util/search' ;
type Props = {
label : string ,
labelAddNew : string ,
labelFoundAction : string ,
values : Array < string > , // [ 'name#id', 'name#id' ]
onAdd ? : ( channelUri : string ) => void ,
onRemove ? : ( channelUri : string ) => void ,
// --- perform ---
doToast : ( { message : string } ) => void ,
} ;
export default function SearchChannelField ( props : Props ) {
const { label , labelAddNew , labelFoundAction , values , onAdd , onRemove , doToast } = props ;
const [ searchTerm , setSearchTerm ] = React . useState ( '' ) ;
const [ searchTermError , setSearchTermError ] = React . useState ( '' ) ;
const [ searchUri , setSearchUri ] = React . useState ( '' ) ;
const addTagRef = React . useRef < any > ( ) ;
function parseUri ( name : string ) {
try {
return parseURI ( name ) ;
} catch ( e ) { }
return undefined ;
}
function addTag ( newTags : Array < Tag > ) {
// Ignoring multiple entries for now, although <TagsSearch> supports it.
const uri = parseUri ( newTags [ 0 ] . name ) ;
if ( uri && uri . isChannel && uri . claimName && uri . claimId ) {
if ( ! values . includes ( newTags [ 0 ] . name ) ) {
if ( onAdd ) {
onAdd ( newTags [ 0 ] . name ) ;
}
}
} else {
doToast ( { message : _ _ ( 'Invalid channel URL "%url%"' , { url : newTags [ 0 ] . name } ) , isError : true } ) ;
}
}
function removeTag ( tagToRemove : Tag ) {
const uri = parseUri ( tagToRemove . name ) ;
if ( uri && uri . isChannel && uri . claimName && uri . claimId ) {
if ( values . includes ( tagToRemove . name ) ) {
if ( onRemove ) {
onRemove ( tagToRemove . name ) ;
}
}
}
}
function clearSearchTerm ( ) {
setSearchTerm ( '' ) ;
setSearchTermError ( '' ) ;
setSearchUri ( '' ) ;
}
function handleKeyPress ( e ) {
// We have to use 'key' instead of 'keyCode' in this event.
if ( e . key === 'Enter' && addTagRef && addTagRef . current && addTagRef . current . click ) {
e . preventDefault ( ) ;
addTagRef . current . click ( ) ;
}
}
function getFoundChannelRenderActionsFn ( ) {
function handleFoundChannelClick ( claim ) {
if ( claim && claim . name && claim . claim _id ) {
addTag ( [ { name : claim . name + '#' + claim . claim _id } ] ) ;
clearSearchTerm ( ) ;
}
}
return ( claim ) => {
return (
< Button
ref = { addTagRef }
button = "primary"
label = { labelFoundAction }
onClick = { ( ) => handleFoundChannelClick ( claim ) }
/ >
) ;
} ;
}
// 'searchTerm' sanitization
React . useEffect ( ( ) => {
if ( ! searchTerm ) {
clearSearchTerm ( ) ;
} else {
const isUrl = searchTerm . startsWith ( 'https://' ) || searchTerm . startsWith ( 'lbry://' ) ;
const autoAlias = ! isUrl && ! searchTerm . startsWith ( '@' ) ? '@' : '' ;
const [ uri , error ] = getUriForSearchTerm ( ` ${ autoAlias } ${ searchTerm } ` ) ;
setSearchTermError ( error ? _ _ ( 'Something not quite right..' ) : '' ) ;
try {
const { streamName , channelName , isChannel } = parseURI ( uri ) ;
if ( ! isChannel && streamName && isNameValid ( streamName ) ) {
setSearchTermError ( _ _ ( 'Not a channel (prefix with "@", or enter the channel URL)' ) ) ;
setSearchUri ( '' ) ;
} else if ( isChannel && channelName && isNameValid ( channelName ) ) {
setSearchUri ( uri ) ;
}
} catch ( e ) {
setSearchTermError ( e . message ) ;
setSearchUri ( '' ) ;
}
}
} , [ searchTerm , setSearchTermError ] ) ;
return (
< div className = "search__channel tag--blocked-words" >
< TagsSearch
label = { label }
labelAddNew = { labelAddNew }
tagsPassedIn = { values . map ( ( x ) => ( { name : x } ) ) }
onSelect = { addTag }
onRemove = { removeTag }
disableAutoFocus
hideInputField
hideSuggestions
disableControlTags
/ >
< div className = "search__channel--popup" >
< FormField
type = "text"
name = "moderator_search"
className = "form-field--address"
label = {
< >
{ labelAddNew }
< Icon
customTooltipText = { _ _ ( HELP . CHANNEL _SEARCH ) }
className = "icon--help"
icon = { ICONS . HELP }
tooltip
size = { 16 }
/ >
< / >
}
placeholder = { _ _ ( 'Enter full channel name or URL' ) }
value = { searchTerm }
error = { searchTermError }
onKeyPress = { ( e ) => handleKeyPress ( e ) }
onChange = { ( e ) => setSearchTerm ( e . target . value ) }
/ >
{ searchUri && (
< div className = "search__channel--popup-results" >
< ClaimPreview
uri = { searchUri }
hideMenu
hideRepostLabel
disableNavigation
showNullPlaceholder
properties = { '' }
renderActions = { getFoundChannelRenderActionsFn ( ) }
empty = {
< div className = "claim-preview claim-preview--inactive claim-preview--large claim-preview__empty" >
{ _ _ ( 'Channel not found' ) }
< Icon
customTooltipText = { _ _ ( HELP . CHANNEL _SEARCH ) }
className = "icon--help"
icon = { ICONS . HELP }
tooltip
size = { 22 }
/ >
< / div >
}
/ >
< / div >
) }
< / div >
< / div >
) ;
}
// prettier-ignore
const HELP = {
CHANNEL _SEARCH : 'Enter the full channel name or URL to search.\n\nExamples:\n - @channel\n - @channel#3\n - https://odysee.com/@Odysee:8\n - lbry://@Odysee#8' ,
} ;