diff --git a/ui/component/searchChannelField/index.js b/ui/component/searchChannelField/index.js new file mode 100644 index 000000000..ed70eff7a --- /dev/null +++ b/ui/component/searchChannelField/index.js @@ -0,0 +1,9 @@ +import { connect } from 'react-redux'; +import { doToast } from 'redux/actions/notifications'; +import SearchChannelField from './view'; + +const perform = (dispatch) => ({ + doToast: (options) => dispatch(doToast(options)), +}); + +export default connect(null, perform)(SearchChannelField); diff --git a/ui/component/searchChannelField/view.jsx b/ui/component/searchChannelField/view.jsx new file mode 100644 index 000000000..89fa419bd --- /dev/null +++ b/ui/component/searchChannelField/view.jsx @@ -0,0 +1,199 @@ +// @flow +import React from 'react'; +import { isNameValid, parseURI } from 'lbry-redux'; +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, // [ '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(); + + function parseUri(name: string) { + try { + return parseURI(name); + } catch (e) {} + + return undefined; + } + + function addTag(newTags: Array) { + // Ignoring multiple entries for now, although 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 ( +