claim-search by language

setting to search only in language as default

add channel update language selection and about

bump

searchable languages
This commit is contained in:
jessop 2020-10-15 13:56:55 -04:00 committed by Sean Yesmunt
parent 54466edafc
commit c4d05a5a1a
15 changed files with 221 additions and 9 deletions

View file

@ -136,7 +136,7 @@
"imagesloaded": "^4.1.4", "imagesloaded": "^4.1.4",
"json-loader": "^0.5.4", "json-loader": "^0.5.4",
"lbry-format": "https://github.com/lbryio/lbry-format.git", "lbry-format": "https://github.com/lbryio/lbry-format.git",
"lbry-redux": "lbryio/lbry-redux#1fc5afa0c45cfb4126539513088b580db9c4aca1", "lbry-redux": "lbryio/lbry-redux#6828dc2b86818a00d762c1b883572ea17f0bf6b9",
"lbryinc": "lbryio/lbryinc#517c28a183d6ab69a357227809bc7c3c12d8411e", "lbryinc": "lbryio/lbryinc#517c28a183d6ab69a357227809bc7c3c12d8411e",
"lint-staged": "^7.0.2", "lint-staged": "^7.0.2",
"localforage": "^1.7.1", "localforage": "^1.7.1",

View file

@ -1457,6 +1457,12 @@
"Pin": "Pin", "Pin": "Pin",
"Unpin": "Unpin", "Unpin": "Unpin",
"LBRY leveled up": "LBRY leveled up", "LBRY leveled up": "LBRY leveled up",
"Post": "Post",
"Primary Language": "Primary Language",
"Your main content language": "Your main content language",
"None selected": "None selected",
"Secondary Language": "Secondary Language",
"Your other content language": "Your other content language",
"This link leads to an external website.": "This link leads to an external website.", "This link leads to an external website.": "This link leads to an external website.",
"Hold on, we are setting up your account": "Hold on, we are setting up your account", "Hold on, we are setting up your account": "Hold on, we are setting up your account",
"No Content Found": "No Content Found", "No Content Found": "No Content Found",

View file

@ -7,6 +7,7 @@ const select = (state, props) => ({
description: makeSelectMetadataItemForUri(props.uri, 'description')(state), description: makeSelectMetadataItemForUri(props.uri, 'description')(state),
website: makeSelectMetadataItemForUri(props.uri, 'website_url')(state), website: makeSelectMetadataItemForUri(props.uri, 'website_url')(state),
email: makeSelectMetadataItemForUri(props.uri, 'email')(state), email: makeSelectMetadataItemForUri(props.uri, 'email')(state),
languages: makeSelectMetadataItemForUri(props.uri, 'languages')(state),
}); });
export default connect(select, null)(ChannelAbout); export default connect(select, null)(ChannelAbout);

View file

@ -8,6 +8,7 @@ import Button from 'component/button';
import * as PAGES from 'constants/pages'; import * as PAGES from 'constants/pages';
import DateTime from 'component/dateTime'; import DateTime from 'component/dateTime';
import YoutubeBadge from 'component/youtubeBadge'; import YoutubeBadge from 'component/youtubeBadge';
import SUPPORTED_LANGUAGES from 'constants/supported_languages';
type Props = { type Props = {
claim: ChannelClaim, claim: ChannelClaim,
@ -15,6 +16,7 @@ type Props = {
description: ?string, description: ?string,
email: ?string, email: ?string,
website: ?string, website: ?string,
languages: Array<string>,
}; };
const formatEmail = (email: string) => { const formatEmail = (email: string) => {
@ -27,7 +29,7 @@ const formatEmail = (email: string) => {
}; };
function ChannelAbout(props: Props) { function ChannelAbout(props: Props) {
const { claim, uri, description, email, website } = props; const { claim, uri, description, email, website, languages } = props;
const claimId = claim && claim.claim_id; const claimId = claim && claim.claim_id;
return ( return (
@ -64,6 +66,12 @@ function ChannelAbout(props: Props) {
<ClaimTags uri={uri} type="large" /> <ClaimTags uri={uri} type="large" />
</div> </div>
<label>{__('Languages')}</label>
<div className="media__info-text">
{/* this could use some nice 'tags' styling */}
{`${SUPPORTED_LANGUAGES[languages[0]]}${languages[1] ? ', ' + SUPPORTED_LANGUAGES[languages[1]] : ''}`}
</div>
<label>{__('Total Uploads')}</label> <label>{__('Total Uploads')}</label>
<div className="media__info-text">{claim.meta.claims_in_channel}</div> <div className="media__info-text">{claim.meta.claims_in_channel}</div>

View file

@ -18,6 +18,9 @@ import Card from 'component/common/card';
import * as PAGES from 'constants/pages'; import * as PAGES from 'constants/pages';
import analytics from 'analytics'; import analytics from 'analytics';
import LbcSymbol from 'component/common/lbc-symbol'; import LbcSymbol from 'component/common/lbc-symbol';
import SUPPORTED_LANGUAGES from 'constants/supported_languages';
const LANG_NONE = 'none';
const MAX_TAG_SELECT = 5; const MAX_TAG_SELECT = 5;
type Props = { type Props = {
@ -63,7 +66,7 @@ function ChannelForm(props: Props) {
coverUrl, coverUrl,
tags, tags,
locations, locations,
languages, languages = [],
onDone, onDone,
updateChannel, updateChannel,
updateError, updateError,
@ -83,6 +86,9 @@ function ChannelForm(props: Props) {
const name = params.name; const name = params.name;
const isNewChannel = !uri; const isNewChannel = !uri;
const { replace } = useHistory(); const { replace } = useHistory();
const languageParam = params.languages;
const primaryLanguage = Array.isArray(languageParam) && languageParam.length && languageParam[0];
const secondaryLanguage = Array.isArray(languageParam) && languageParam.length >= 2 && languageParam[1];
function getChannelParams() { function getChannelParams() {
// fill this in with sdk data // fill this in with sdk data
@ -141,6 +147,25 @@ function ChannelForm(props: Props) {
} }
} }
function handleLanguageChange(index, code) {
let langs = [...languageParam];
if (index === 0) {
if (code === LANG_NONE) {
// clear all
langs = [];
} else {
langs[0] = code;
}
} else {
if (code === LANG_NONE || code === langs[0]) {
langs.splice(1, 1);
} else {
langs[index] = code;
}
}
setParams({ ...params, languages: langs });
}
function handleThumbnailChange(thumbnailUrl: string) { function handleThumbnailChange(thumbnailUrl: string) {
setParams({ ...params, thumbnailUrl }); setParams({ ...params, thumbnailUrl });
} }
@ -356,6 +381,43 @@ function ChannelForm(props: Props) {
value={params.email} value={params.email}
onChange={e => setParams({ ...params, email: e.target.value })} onChange={e => setParams({ ...params, email: e.target.value })}
/> />
<FormField
name="language_select"
type="select"
label={__('Primary Language')}
onChange={event => handleLanguageChange(0, event.target.value)}
value={primaryLanguage}
helper={__('Your main content language')}
>
<option key={'pri-langNone'} value={LANG_NONE}>
{__('None selected')}
</option>
{Object.keys(SUPPORTED_LANGUAGES).map(language => (
<option key={language} value={language}>
{SUPPORTED_LANGUAGES[language]}
</option>
))}
</FormField>
<FormField
name="language_select2"
type="select"
label={__('Secondary Language')}
onChange={event => handleLanguageChange(1, event.target.value)}
value={secondaryLanguage}
disabled={!languageParam[0]}
helper={__('Your other content language')}
>
<option key={'sec-langNone'} value={LANG_NONE}>
{__('None selected')}
</option>
{Object.keys(SUPPORTED_LANGUAGES)
.filter(lang => lang !== languageParam[0])
.map(language => (
<option key={language} value={language}>
{SUPPORTED_LANGUAGES[language]}
</option>
))}
</FormField>
</> </>
} }
/> />

View file

@ -19,7 +19,9 @@ const select = state => ({
loading: selectFetchingClaimSearch(state), loading: selectFetchingClaimSearch(state),
showNsfw: makeSelectClientSetting(SETTINGS.SHOW_MATURE)(state), showNsfw: makeSelectClientSetting(SETTINGS.SHOW_MATURE)(state),
hideReposts: makeSelectClientSetting(SETTINGS.HIDE_REPOSTS)(state), hideReposts: makeSelectClientSetting(SETTINGS.HIDE_REPOSTS)(state),
languageSetting: makeSelectClientSetting(SETTINGS.LANGUAGE)(state),
hiddenUris: selectBlockedChannels(state), hiddenUris: selectBlockedChannels(state),
searchInLanguage: makeSelectClientSetting(SETTINGS.SEARCH_IN_LANGUAGE)(state),
}); });
const perform = { const perform = {

View file

@ -61,6 +61,8 @@ type Props = {
hideFilters?: boolean, hideFilters?: boolean,
maxPages?: number, maxPages?: number,
forceShowReposts?: boolean, forceShowReposts?: boolean,
languageSetting: string,
searchInLanguage: boolean,
}; };
function ClaimListDiscover(props: Props) { function ClaimListDiscover(props: Props) {
@ -106,6 +108,8 @@ function ClaimListDiscover(props: Props) {
claimIds, claimIds,
maxPages, maxPages,
forceShowReposts = false, forceShowReposts = false,
languageSetting,
searchInLanguage,
} = props; } = props;
const didNavigateForward = history.action === 'PUSH'; const didNavigateForward = history.action === 'PUSH';
const { search } = location; const { search } = location;
@ -121,6 +125,20 @@ function ClaimListDiscover(props: Props) {
(urlParams.get(CS.TAGS_KEY) !== null && urlParams.get(CS.TAGS_KEY)) || (urlParams.get(CS.TAGS_KEY) !== null && urlParams.get(CS.TAGS_KEY)) ||
(defaultTags && getParamFromTags(defaultTags)); (defaultTags && getParamFromTags(defaultTags));
const freshnessParam = freshness || urlParams.get(CS.FRESH_KEY) || defaultFreshness; const freshnessParam = freshness || urlParams.get(CS.FRESH_KEY) || defaultFreshness;
const langParam = urlParams.get(CS.LANGUAGE_KEY) || null;
const languageParam = searchInLanguage
? langParam === null
? languageSetting
: langParam === 'any'
? null
: langParam
: langParam === null
? null
: langParam === 'any'
? null
: langParam;
const contentTypeParam = urlParams.get(CS.CONTENT_KEY); const contentTypeParam = urlParams.get(CS.CONTENT_KEY);
const claimTypeParam = const claimTypeParam =
claimType || (CS.CLAIM_TYPES.includes(contentTypeParam) && contentTypeParam) || defaultClaimType || null; claimType || (CS.CLAIM_TYPES.includes(contentTypeParam) && contentTypeParam) || defaultClaimType || null;
@ -159,6 +177,7 @@ function ClaimListDiscover(props: Props) {
page: number, page: number,
no_totals: boolean, no_totals: boolean,
any_tags?: Array<string>, any_tags?: Array<string>,
any_languages?: Array<string>,
not_tags: Array<string>, not_tags: Array<string>,
channel_ids?: Array<string>, channel_ids?: Array<string>,
claim_ids?: Array<string>, claim_ids?: Array<string>,
@ -280,6 +299,24 @@ function ClaimListDiscover(props: Props) {
} }
} }
if (languageParam) {
if (languageParam !== CS.LANGUAGES_ALL) {
options.any_languages = [languageParam];
}
}
if (tagsParam) {
if (tagsParam !== CS.TAGS_ALL && tagsParam !== '') {
if (tagsParam === CS.TAGS_FOLLOWED) {
options.any_tags = followed;
} else if (Array.isArray(tagsParam)) {
options.any_tags = tagsParam;
} else {
options.any_tags = tagsParam.split(',');
}
}
}
if (hideReposts && !options.reposted_claim_id && !forceShowReposts) { if (hideReposts && !options.reposted_claim_id && !forceShowReposts) {
if (Array.isArray(options.claim_type)) { if (Array.isArray(options.claim_type)) {
if (options.claim_type.length > 1) { if (options.claim_type.length > 1) {

View file

@ -10,6 +10,8 @@ const select = state => ({
followedTags: selectFollowedTags(state), followedTags: selectFollowedTags(state),
loading: selectFetchingClaimSearch(state), loading: selectFetchingClaimSearch(state),
showNsfw: makeSelectClientSetting(SETTINGS.SHOW_MATURE)(state), showNsfw: makeSelectClientSetting(SETTINGS.SHOW_MATURE)(state),
searchInLanguage: makeSelectClientSetting(SETTINGS.SEARCH_IN_LANGUAGE)(state),
languageSetting: makeSelectClientSetting(SETTINGS.LANGUAGE)(state),
}); });
const perform = { const perform = {

View file

@ -11,6 +11,7 @@ import { SETTINGS } from 'lbry-redux';
import { FormField } from 'component/common/form'; import { FormField } from 'component/common/form';
import Button from 'component/button'; import Button from 'component/button';
import { toCapitalCase } from 'util/string'; import { toCapitalCase } from 'util/string';
import SEARCHABLE_LANGUAGES from 'constants/searchable_languages';
type Props = { type Props = {
defaultTags: string, defaultTags: string,
@ -32,6 +33,8 @@ type Props = {
doSetClientSetting: (string, boolean, ?boolean) => void, doSetClientSetting: (string, boolean, ?boolean) => void,
setPage: number => void, setPage: number => void,
hideFilters: boolean, hideFilters: boolean,
searchInLanguage: boolean,
languageSetting: string,
}; };
function ClaimListHeader(props: Props) { function ClaimListHeader(props: Props) {
@ -55,6 +58,8 @@ function ClaimListHeader(props: Props) {
doSetClientSetting, doSetClientSetting,
setPage, setPage,
hideFilters, hideFilters,
searchInLanguage,
languageSetting,
} = props; } = props;
const { action, push, location } = useHistory(); const { action, push, location } = useHistory();
const { search } = location; const { search } = location;
@ -72,6 +77,7 @@ function ClaimListHeader(props: Props) {
const streamTypeParam = const streamTypeParam =
streamType || (CS.FILE_TYPES.includes(contentTypeParam) && contentTypeParam) || defaultStreamType || null; streamType || (CS.FILE_TYPES.includes(contentTypeParam) && contentTypeParam) || defaultStreamType || null;
const durationParam = urlParams.get(CS.DURATION_KEY) || null; const durationParam = urlParams.get(CS.DURATION_KEY) || null;
const languageParam = urlParams.get(CS.LANGUAGE_KEY) || null;
const channelIdsInUrl = urlParams.get(CS.CHANNEL_IDS_KEY); const channelIdsInUrl = urlParams.get(CS.CHANNEL_IDS_KEY);
const channelIdsParam = channelIdsInUrl ? channelIdsInUrl.split(',') : channelIds; const channelIdsParam = channelIdsInUrl ? channelIdsInUrl.split(',') : channelIds;
const feeAmountParam = urlParams.get('fee_amount') || feeAmount || CS.FEE_AMOUNT_ANY; const feeAmountParam = urlParams.get('fee_amount') || feeAmount || CS.FEE_AMOUNT_ANY;
@ -82,9 +88,22 @@ function ClaimListHeader(props: Props) {
urlParams.get(CS.CONTENT_KEY) || urlParams.get(CS.CONTENT_KEY) ||
urlParams.get(CS.DURATION_KEY) || urlParams.get(CS.DURATION_KEY) ||
urlParams.get(CS.TAGS_KEY) || urlParams.get(CS.TAGS_KEY) ||
urlParams.get(CS.FEE_AMOUNT_KEY) urlParams.get(CS.FEE_AMOUNT_KEY) ||
urlParams.get(CS.LANGUAGE_KEY)
); );
const languageValue = searchInLanguage
? languageParam === null
? languageSetting
: languageParam
: languageParam === null
? CS.LANGUAGES_ALL
: languageParam;
const shouldHighlight = searchInLanguage
? languageParam !== languageSetting && languageParam !== null
: languageParam !== CS.LANGUAGES_ALL && languageParam !== null;
React.useEffect(() => { React.useEffect(() => {
if (action !== 'POP' && isFiltered()) { if (action !== 'POP' && isFiltered()) {
setExpanded(true); setExpanded(true);
@ -172,6 +191,9 @@ function ClaimListHeader(props: Props) {
newUrlParams.set(CS.DURATION_KEY, delta.value); newUrlParams.set(CS.DURATION_KEY, delta.value);
} }
break; break;
case CS.LANGUAGE_KEY:
newUrlParams.set(CS.LANGUAGE_KEY, delta.value);
break;
case CS.TAGS_KEY: case CS.TAGS_KEY:
if (delta.value === CS.TAGS_ALL) { if (delta.value === CS.TAGS_ALL) {
if (defaultTags === CS.TAGS_ALL) { if (defaultTags === CS.TAGS_ALL) {
@ -334,6 +356,43 @@ function ClaimListHeader(props: Props) {
</div> </div>
)} )}
{/* LANGUAGE FIELD */}
{!claimType && (
<div
className={classnames('claim-search__input-container', {
'claim-search__input-container--selected': shouldHighlight,
})}
>
<FormField
className={classnames('claim-search__dropdown', {
'claim-search__dropdown--selected': shouldHighlight,
})}
type="select"
name="claimType"
label={__('Language')}
value={languageValue || CS.LANGUAGES_ALL}
onChange={e =>
handleChange({
key: CS.LANGUAGE_KEY,
value: e.target.value,
})
}
>
<option key={CS.LANGUAGES_ALL} value={CS.LANGUAGES_ALL}>
{__('Any')}
{/* i18fixme */}
</option>
{Object.entries(SEARCHABLE_LANGUAGES).map(([code, label]) => {
return (
<option key={code} value={code}>
{__(String(label))}
</option>
);
})}
</FormField>
</div>
)}
{/* DURATIONS FIELD */} {/* DURATIONS FIELD */}
{showDuration && ( {showDuration && (
<div className={'claim-search__input-container'}> <div className={'claim-search__input-container'}>

View file

@ -1,15 +1,17 @@
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { SETTINGS } from 'lbry-redux'; import { SETTINGS } from 'lbry-redux';
import { doSetLanguage } from 'redux/actions/settings'; import { doSetLanguage, doSetClientSetting } from 'redux/actions/settings';
import { makeSelectClientSetting } from 'redux/selectors/settings'; import { makeSelectClientSetting } from 'redux/selectors/settings';
import SettingLanguage from './view'; import SettingLanguage from './view';
const select = state => ({ const select = state => ({
language: makeSelectClientSetting(SETTINGS.LANGUAGE)(state), language: makeSelectClientSetting(SETTINGS.LANGUAGE)(state),
searchInLanguage: makeSelectClientSetting(SETTINGS.SEARCH_IN_LANGUAGE)(state),
}); });
const perform = dispatch => ({ const perform = dispatch => ({
setLanguage: value => dispatch(doSetLanguage(value)), setLanguage: value => dispatch(doSetLanguage(value)),
setSearchInLanguage: value => dispatch(doSetClientSetting(SETTINGS.SEARCH_IN_LANGUAGE, value)),
}); });
export default connect(select, perform)(SettingLanguage); export default connect(select, perform)(SettingLanguage);

View file

@ -3,15 +3,17 @@
import React, { useState } from 'react'; import React, { useState } from 'react';
import { FormField } from 'component/common/form'; import { FormField } from 'component/common/form';
import Spinner from 'component/spinner'; import Spinner from 'component/spinner';
import SUPPORTED_LANGUAGES from '../../constants/supported_languages'; import SUPPORTED_LANGUAGES from 'constants/supported_languages';
type Props = { type Props = {
language: string, language: string,
setLanguage: string => void, setLanguage: string => void,
searchInLanguage: boolean,
setSearchInLanguage: boolean => void,
}; };
function SettingLanguage(props: Props) { function SettingLanguage(props: Props) {
const { language, setLanguage } = props; const { language, setLanguage, searchInLanguage, setSearchInLanguage } = props;
const [previousLanguage, setPreviousLanguage] = useState(null); const [previousLanguage, setPreviousLanguage] = useState(null);
const languages = SUPPORTED_LANGUAGES; const languages = SUPPORTED_LANGUAGES;
@ -45,6 +47,13 @@ function SettingLanguage(props: Props) {
))} ))}
</FormField> </FormField>
{previousLanguage && <Spinner type="small" />} {previousLanguage && <Spinner type="small" />}
<FormField
name="search-in-language"
type="checkbox"
label={__('Search only in this language by default')}
checked={searchInLanguage}
onChange={() => setSearchInLanguage(!searchInLanguage)}
/>
</React.Fragment> </React.Fragment>
); );
} }

View file

@ -3,6 +3,7 @@ export const PAGE_SIZE = 20;
export const FRESH_KEY = 'fresh'; export const FRESH_KEY = 'fresh';
export const ORDER_BY_KEY = 'order'; export const ORDER_BY_KEY = 'order';
export const DURATION_KEY = 'duration'; export const DURATION_KEY = 'duration';
export const LANGUAGE_KEY = 'language';
export const TAGS_KEY = 't'; export const TAGS_KEY = 't';
export const CONTENT_KEY = 'content'; export const CONTENT_KEY = 'content';
export const REPOSTED_URI_KEY = 'reposted_uri'; export const REPOSTED_URI_KEY = 'reposted_uri';
@ -15,6 +16,8 @@ export const FEE_AMOUNT_ANY = '>=0';
export const FEE_AMOUNT_ONLY_PAID = '>0'; export const FEE_AMOUNT_ONLY_PAID = '>0';
export const FEE_AMOUNT_ONLY_FREE = '<=0'; export const FEE_AMOUNT_ONLY_FREE = '<=0';
export const LANGUAGES_ALL = 'all';
export const FRESH_DAY = 'day'; export const FRESH_DAY = 'day';
export const FRESH_WEEK = 'week'; export const FRESH_WEEK = 'week';
export const FRESH_MONTH = 'month'; export const FRESH_MONTH = 'month';

View file

@ -0,0 +1,20 @@
import LANGUAGES from './languages';
const SEARCHABLE_LANGUAGES = {
en: LANGUAGES.en[1],
hr: LANGUAGES.hr[1],
nl: LANGUAGES.nl[1],
fr: LANGUAGES.fr[1],
de: LANGUAGES.de[1],
it: LANGUAGES.it[1],
pl: LANGUAGES.pl[1],
pt: LANGUAGES.pt[1],
ru: LANGUAGES.ru[1],
es: LANGUAGES.es[1],
tr: LANGUAGES.tr[1],
cs: LANGUAGES.cs[1],
};
// Properties: language code (e.g. 'ja')
// Values: name of the language in native form (e.g. '日本語')
export default SEARCHABLE_LANGUAGES;

View file

@ -37,6 +37,7 @@ const defaultState = {
// UI // UI
[SETTINGS.LANGUAGE]: settingLanguage.find(language => SUPPORTED_LANGUAGES[language]), [SETTINGS.LANGUAGE]: settingLanguage.find(language => SUPPORTED_LANGUAGES[language]),
[SETTINGS.SEARCH_IN_LANGUAGE]: false,
[SETTINGS.THEME]: __('light'), [SETTINGS.THEME]: __('light'),
[SETTINGS.THEMES]: [__('light'), __('dark')], [SETTINGS.THEMES]: [__('light'), __('dark')],
[SETTINGS.HIDE_SPLASH_ANIMATION]: false, [SETTINGS.HIDE_SPLASH_ANIMATION]: false,

View file

@ -7391,9 +7391,9 @@ lazy-val@^1.0.4:
yargs "^13.2.2" yargs "^13.2.2"
zstd-codec "^0.1.1" zstd-codec "^0.1.1"
lbry-redux@lbryio/lbry-redux#1fc5afa0c45cfb4126539513088b580db9c4aca1: lbry-redux@lbryio/lbry-redux#6828dc2b86818a00d762c1b883572ea17f0bf6b9:
version "0.0.1" version "0.0.1"
resolved "https://codeload.github.com/lbryio/lbry-redux/tar.gz/1fc5afa0c45cfb4126539513088b580db9c4aca1" resolved "https://codeload.github.com/lbryio/lbry-redux/tar.gz/6828dc2b86818a00d762c1b883572ea17f0bf6b9"
dependencies: dependencies:
proxy-polyfill "0.1.6" proxy-polyfill "0.1.6"
reselect "^3.0.0" reselect "^3.0.0"