From e15bc0e8b88f33823edaeabec308b8e28c1b7f66 Mon Sep 17 00:00:00 2001 From: Franco Montenegro Date: Wed, 6 Apr 2022 11:49:57 -0300 Subject: [PATCH 1/7] Add ability to search through publishes. --- ui/constants/action_types.js | 2 + ui/page/fileListPublished/index.js | 15 +++--- ui/page/fileListPublished/view.jsx | 80 +++++++++++++++++++++--------- ui/redux/actions/claims.js | 23 +++++++++ ui/redux/reducers/claims.js | 15 ++++++ ui/redux/selectors/claims.js | 4 ++ 6 files changed, 106 insertions(+), 33 deletions(-) diff --git a/ui/constants/action_types.js b/ui/constants/action_types.js index 8cc077d48..e12958db6 100644 --- a/ui/constants/action_types.js +++ b/ui/constants/action_types.js @@ -128,6 +128,8 @@ export const FETCH_CHANNEL_CLAIMS_COMPLETED = 'FETCH_CHANNEL_CLAIMS_COMPLETED'; export const FETCH_CHANNEL_CLAIM_COUNT_STARTED = 'FETCH_CHANNEL_CLAIM_COUNT_STARTED'; export const FETCH_CLAIM_LIST_MINE_STARTED = 'FETCH_CLAIM_LIST_MINE_STARTED'; export const FETCH_CLAIM_LIST_MINE_COMPLETED = 'FETCH_CLAIM_LIST_MINE_COMPLETED'; +export const FETCH_ALL_CLAIM_LIST_MINE_STARTED = 'FETCH_ALL_CLAIM_LIST_MINE_STARTED'; +export const FETCH_ALL_CLAIM_LIST_MINE_COMPLETED = 'FETCH_ALL_CLAIM_LIST_MINE_COMPLETED'; export const ABANDON_CLAIM_STARTED = 'ABANDON_CLAIM_STARTED'; export const ABANDON_CLAIM_SUCCEEDED = 'ABANDON_CLAIM_SUCCEEDED'; export const FETCH_CHANNEL_LIST_STARTED = 'FETCH_CHANNEL_LIST_STARTED'; diff --git a/ui/page/fileListPublished/index.js b/ui/page/fileListPublished/index.js index 75e6b4806..eebe6307d 100644 --- a/ui/page/fileListPublished/index.js +++ b/ui/page/fileListPublished/index.js @@ -1,11 +1,10 @@ import { connect } from 'react-redux'; import { - selectIsFetchingClaimListMine, - selectMyClaimsPage, - selectMyClaimsPageItemCount, + selectIsFetchingAllMyClaims, selectFetchingMyClaimsPageError, + selectAllMyClaims, } from 'redux/selectors/claims'; -import { doFetchClaimListMine, doCheckPendingClaims } from 'redux/actions/claims'; +import { doCheckPendingClaims, doFetchAllClaimListMine } from 'redux/actions/claims'; import { doClearPublish } from 'redux/actions/publish'; import FileListPublished from './view'; import { withRouter } from 'react-router'; @@ -20,18 +19,16 @@ const select = (state, props) => { return { page, pageSize, - fetching: selectIsFetchingClaimListMine(state), - urls: selectMyClaimsPage(state), - urlTotal: selectMyClaimsPageItemCount(state), + fetching: selectIsFetchingAllMyClaims(state), error: selectFetchingMyClaimsPageError(state), + myClaims: selectAllMyClaims(state), }; }; const perform = (dispatch) => ({ checkPendingPublishes: () => dispatch(doCheckPendingClaims()), - fetchClaimListMine: (page, pageSize, resolve, filterBy) => - dispatch(doFetchClaimListMine(page, pageSize, resolve, filterBy)), clearPublish: () => dispatch(doClearPublish()), + fetchAllMyClaims: () => dispatch(doFetchAllClaimListMine()), }); export default withRouter(connect(select, perform)(FileListPublished)); diff --git a/ui/page/fileListPublished/view.jsx b/ui/page/fileListPublished/view.jsx index 9e1a43ceb..2f2a57da5 100644 --- a/ui/page/fileListPublished/view.jsx +++ b/ui/page/fileListPublished/view.jsx @@ -1,7 +1,7 @@ // @flow import * as PAGES from 'constants/pages'; import * as ICONS from 'constants/icons'; -import React, { useEffect } from 'react'; +import React, { useEffect, useMemo } from 'react'; import Button from 'component/button'; import ClaimList from 'component/claimList'; import Page from 'component/page'; @@ -9,6 +9,8 @@ import Paginate from 'component/common/paginate'; import { PAGE_PARAM, PAGE_SIZE_PARAM } from 'constants/claim'; import Spinner from 'component/spinner'; import Yrbl from 'component/yrbl'; +import { FormField, Form } from 'component/common/form'; +import Icon from 'component/common/icon'; import classnames from 'classnames'; const FILTER_ALL = 'stream,repost'; @@ -18,19 +20,19 @@ const FILTER_REPOSTS = 'repost'; type Props = { checkPendingPublishes: () => void, clearPublish: () => void, - fetchClaimListMine: (number, number, boolean, Array) => void, fetching: boolean, - urls: Array, - urlTotal: number, history: { replace: (string) => void, push: (string) => void }, page: number, pageSize: number, + myClaims: any, + fetchAllMyClaims: () => void, }; function FileListPublished(props: Props) { - const { checkPendingPublishes, clearPublish, fetchClaimListMine, fetching, urls, urlTotal, page, pageSize } = props; + const { checkPendingPublishes, clearPublish, fetching, page, pageSize, myClaims, fetchAllMyClaims } = props; const [filterBy, setFilterBy] = React.useState(FILTER_ALL); + const [searchText, setSearchText] = React.useState(''); const params = {}; params[PAGE_PARAM] = Number(page); @@ -42,12 +44,44 @@ function FileListPublished(props: Props) { checkPendingPublishes(); }, [checkPendingPublishes]); - useEffect(() => { - if (paramsString && fetchClaimListMine) { - const params = JSON.parse(paramsString); - fetchClaimListMine(params.page, params.page_size, true, filterBy.split(',')); + const filteredClaims = useMemo(() => { + if (fetching) { + return []; } - }, [paramsString, filterBy, fetchClaimListMine]); + + return myClaims.filter((claim) => { + const value = claim.value || {}; + const src = value.source || {}; + const title = (value.title || '').toLowerCase(); + const description = (value.description || '').toLowerCase(); + const tags = (value.tags || []).join('').toLowerCase(); + const srcName = (src.name || '').toLowerCase(); + const lowerCaseSearchText = searchText.toLowerCase(); + const textMatches = + !searchText || + title.indexOf(lowerCaseSearchText) !== -1 || + description.indexOf(lowerCaseSearchText) !== -1 || + tags.indexOf(lowerCaseSearchText) !== -1 || + srcName.indexOf(lowerCaseSearchText) !== -1; + return textMatches && filterBy.includes(claim.value_type); + }); + }, [fetching, myClaims, filterBy, searchText]); + + const urlTotal = filteredClaims.length; + + const urls = useMemo(() => { + const params = JSON.parse(paramsString); + const zeroIndexPage = Math.max(0, params.page - 1); + const paginated = filteredClaims.slice( + zeroIndexPage * params.page_size, + zeroIndexPage * params.page_size + params.page_size + ); + return paginated.map((claim) => claim.permanent_url); + }, [filteredClaims, paramsString]); + + useEffect(() => { + fetchAllMyClaims(); + }, [fetchAllMyClaims]); return ( @@ -89,20 +123,18 @@ function FileListPublished(props: Props) {
{fetching && } {!fetching && ( -
} persistedStorageKey="claim-list-published" @@ -112,7 +144,7 @@ function FileListPublished(props: Props) { )} - {!(urls && urls.length) && ( + {!fetching && myClaims.length === 0 && ( {!fetching ? (
diff --git a/ui/redux/actions/claims.js b/ui/redux/actions/claims.js index 9bbe510c1..3e1daccd8 100644 --- a/ui/redux/actions/claims.js +++ b/ui/redux/actions/claims.js @@ -184,6 +184,29 @@ export function doFetchClaimListMine( }; } +export function doFetchAllClaimListMine() { + return (dispatch: Dispatch) => { + dispatch({ + type: ACTIONS.FETCH_ALL_CLAIM_LIST_MINE_STARTED, + }); + + // $FlowFixMe + Lbry.claim_list({ + page: 1, + page_size: 99999, + claim_type: ['stream', 'repost'], + resolve: true, + }).then((result: StreamListResponse) => { + dispatch({ + type: ACTIONS.FETCH_ALL_CLAIM_LIST_MINE_COMPLETED, + data: { + result, + }, + }); + }); + }; +} + export function doAbandonTxo(txo: Txo, cb: (string) => void) { return (dispatch: Dispatch) => { if (cb) cb(ABANDON_STATES.PENDING); diff --git a/ui/redux/reducers/claims.js b/ui/redux/reducers/claims.js index da63ef650..21b028dd1 100644 --- a/ui/redux/reducers/claims.js +++ b/ui/redux/reducers/claims.js @@ -61,6 +61,8 @@ type State = { isCheckingNameForPublish: boolean, checkingPending: boolean, checkingReflecting: boolean, + isFetchingAllClaimListMine: boolean, + allClaimListMine: ?ClaimListResponse, }; const reducers = {}; @@ -109,6 +111,8 @@ const defaultState = { isCheckingNameForPublish: false, checkingPending: false, checkingReflecting: false, + isFetchingAllClaimListMine: false, + allClaimListMine: undefined, }; function handleClaimAction(state: State, action: any): State { @@ -273,6 +277,17 @@ reducers[ACTIONS.FETCH_CLAIM_LIST_MINE_COMPLETED] = (state: State, action: any): }); }; +reducers[ACTIONS.FETCH_ALL_CLAIM_LIST_MINE_STARTED] = (state: State): State => + Object.assign({}, state, { + isFetchingAllClaimListMine: true, + }); + +reducers[ACTIONS.FETCH_ALL_CLAIM_LIST_MINE_COMPLETED] = (state: State, action: any): State => + Object.assign({}, state, { + isFetchingAllClaimListMine: false, + allClaimListMine: action.data.result.items, + }); + reducers[ACTIONS.FETCH_CHANNEL_LIST_STARTED] = (state: State): State => Object.assign({}, state, { fetchingMyChannels: true }); diff --git a/ui/redux/selectors/claims.js b/ui/redux/selectors/claims.js index 677476415..396bd3d29 100644 --- a/ui/redux/selectors/claims.js +++ b/ui/redux/selectors/claims.js @@ -410,6 +410,10 @@ export const selectIsFetchingClaimListMine = (state: State) => selectState(state export const selectMyClaimsPage = createSelector(selectState, (state) => state.myClaimsPageResults || []); +export const selectAllMyClaims = createSelector(selectState, (state) => state.allClaimListMine || []); + +export const selectIsFetchingAllMyClaims = createSelector(selectState, (state) => state.isFetchingAllClaimListMine); + export const selectMyClaimsPageNumber = createSelector( selectState, (state) => (state.claimListMinePage && state.claimListMinePage.items) || [], -- 2.45.2 From 88ba769340ae609fab25161b1a1c82a018b56910 Mon Sep 17 00:00:00 2001 From: Franco Montenegro Date: Wed, 6 Apr 2022 11:56:53 -0300 Subject: [PATCH 2/7] Small fix in allClaimListMine type. --- ui/redux/reducers/claims.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ui/redux/reducers/claims.js b/ui/redux/reducers/claims.js index 21b028dd1..2b1d3aa31 100644 --- a/ui/redux/reducers/claims.js +++ b/ui/redux/reducers/claims.js @@ -62,7 +62,7 @@ type State = { checkingPending: boolean, checkingReflecting: boolean, isFetchingAllClaimListMine: boolean, - allClaimListMine: ?ClaimListResponse, + allClaimListMine: Array, }; const reducers = {}; @@ -112,7 +112,7 @@ const defaultState = { checkingPending: false, checkingReflecting: false, isFetchingAllClaimListMine: false, - allClaimListMine: undefined, + allClaimListMine: [], }; function handleClaimAction(state: State, action: any): State { -- 2.45.2 From b472f8c47dd8e3360b24701a1df6ae002a4f829c Mon Sep 17 00:00:00 2001 From: Franco Montenegro Date: Wed, 6 Apr 2022 12:36:53 -0300 Subject: [PATCH 3/7] Small fix for search claims in uploads page. --- ui/page/fileListPublished/view.jsx | 30 +++++++++++++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/ui/page/fileListPublished/view.jsx b/ui/page/fileListPublished/view.jsx index 2f2a57da5..8504fda89 100644 --- a/ui/page/fileListPublished/view.jsx +++ b/ui/page/fileListPublished/view.jsx @@ -16,6 +16,7 @@ import classnames from 'classnames'; const FILTER_ALL = 'stream,repost'; const FILTER_UPLOADS = 'stream'; const FILTER_REPOSTS = 'repost'; +const PAGINATE_PARAM = 'page'; type Props = { checkPendingPublishes: () => void, @@ -26,10 +27,23 @@ type Props = { pageSize: number, myClaims: any, fetchAllMyClaims: () => void, + location: { search: string }, + disableHistory?: boolean, // Disables the use of '&page=' param and history stack. }; function FileListPublished(props: Props) { - const { checkPendingPublishes, clearPublish, fetching, page, pageSize, myClaims, fetchAllMyClaims } = props; + const { + checkPendingPublishes, + clearPublish, + fetching, + page, + pageSize, + myClaims, + fetchAllMyClaims, + location, + disableHistory, + history, + } = props; const [filterBy, setFilterBy] = React.useState(FILTER_ALL); const [searchText, setSearchText] = React.useState(''); @@ -79,6 +93,20 @@ function FileListPublished(props: Props) { return paginated.map((claim) => claim.permanent_url); }, [filteredClaims, paramsString]); + const { search } = location; + + // Go back to the first page when the filtered claims change. + // This way, we avoid hiding results just because the + // user may be on a different page (page that was calculated + // using a different state, ie, different filtered claims) + useEffect(() => { + if (!disableHistory) { + const params = new URLSearchParams(search); + params.set(PAGINATE_PARAM, '1'); + history.push('?' + params.toString()); + } + }, [filteredClaims]); + useEffect(() => { fetchAllMyClaims(); }, [fetchAllMyClaims]); -- 2.45.2 From d2c9dea9259bf68e00c93084dce0101cc9b62f87 Mon Sep 17 00:00:00 2001 From: Franco Montenegro Date: Thu, 14 Apr 2022 21:40:54 -0300 Subject: [PATCH 4/7] Add search term in uri when filtering uploads. --- ui/page/fileListPublished/index.js | 2 ++ ui/page/fileListPublished/view.jsx | 58 +++++++++++++++++++----------- ui/util/debounce.js | 6 ++-- 3 files changed, 42 insertions(+), 24 deletions(-) diff --git a/ui/page/fileListPublished/index.js b/ui/page/fileListPublished/index.js index eebe6307d..aa66f0274 100644 --- a/ui/page/fileListPublished/index.js +++ b/ui/page/fileListPublished/index.js @@ -15,6 +15,7 @@ const select = (state, props) => { const urlParams = new URLSearchParams(search); const page = Number(urlParams.get(PAGE_PARAM)) || '1'; const pageSize = urlParams.get(PAGE_SIZE_PARAM) || String(MY_CLAIMS_PAGE_SIZE); + const initialSearchTerm = urlParams.get('searchText') || ''; return { page, @@ -22,6 +23,7 @@ const select = (state, props) => { fetching: selectIsFetchingAllMyClaims(state), error: selectFetchingMyClaimsPageError(state), myClaims: selectAllMyClaims(state), + initialSearchTerm, }; }; diff --git a/ui/page/fileListPublished/view.jsx b/ui/page/fileListPublished/view.jsx index 8504fda89..05e9d4679 100644 --- a/ui/page/fileListPublished/view.jsx +++ b/ui/page/fileListPublished/view.jsx @@ -11,6 +11,7 @@ import Spinner from 'component/spinner'; import Yrbl from 'component/yrbl'; import { FormField, Form } from 'component/common/form'; import Icon from 'component/common/icon'; +import debounce from 'util/debounce'; import classnames from 'classnames'; const FILTER_ALL = 'stream,repost'; @@ -28,7 +29,7 @@ type Props = { myClaims: any, fetchAllMyClaims: () => void, location: { search: string }, - disableHistory?: boolean, // Disables the use of '&page=' param and history stack. + initialSearchTerm: string, }; function FileListPublished(props: Props) { @@ -41,12 +42,14 @@ function FileListPublished(props: Props) { myClaims, fetchAllMyClaims, location, - disableHistory, history, + initialSearchTerm, } = props; const [filterBy, setFilterBy] = React.useState(FILTER_ALL); - const [searchText, setSearchText] = React.useState(''); + const [searchText, setSearchText] = React.useState(initialSearchTerm); + const [filteredClaims, setFilteredClaims] = React.useState([]); + const { search } = location; const params = {}; params[PAGE_PARAM] = Number(page); @@ -54,16 +57,11 @@ function FileListPublished(props: Props) { const paramsString = JSON.stringify(params); - useEffect(() => { - checkPendingPublishes(); - }, [checkPendingPublishes]); - - const filteredClaims = useMemo(() => { + const doFilterClaims = () => { if (fetching) { - return []; + return; } - - return myClaims.filter((claim) => { + const filtered = myClaims.filter((claim) => { const value = claim.value || {}; const src = value.source || {}; const title = (value.title || '').toLowerCase(); @@ -79,7 +77,25 @@ function FileListPublished(props: Props) { srcName.indexOf(lowerCaseSearchText) !== -1; return textMatches && filterBy.includes(claim.value_type); }); - }, [fetching, myClaims, filterBy, searchText]); + setFilteredClaims(filtered); + }; + + const debounceFilter = debounce(doFilterClaims, 200); + + useEffect(() => { + checkPendingPublishes(); + }, [checkPendingPublishes]); + + useEffect(() => { + const params = new URLSearchParams(search); + params.set('searchText', searchText); + history.replace('?' + params.toString()); + debounceFilter(); + }, [myClaims, searchText]); + + useEffect(() => { + doFilterClaims(); + }, [myClaims, filterBy]); const urlTotal = filteredClaims.length; @@ -93,18 +109,14 @@ function FileListPublished(props: Props) { return paginated.map((claim) => claim.permanent_url); }, [filteredClaims, paramsString]); - const { search } = location; - // Go back to the first page when the filtered claims change. // This way, we avoid hiding results just because the // user may be on a different page (page that was calculated // using a different state, ie, different filtered claims) useEffect(() => { - if (!disableHistory) { - const params = new URLSearchParams(search); - params.set(PAGINATE_PARAM, '1'); - history.push('?' + params.toString()); - } + const params = new URLSearchParams(search); + params.set(PAGINATE_PARAM, '1'); + history.replace('?' + params.toString()); }, [filteredClaims]); useEffect(() => { @@ -114,7 +126,7 @@ function FileListPublished(props: Props) { return (
- {!!urls && ( + {!fetching && myClaims.length > 0 && ( <> setFilterBy(FILTER_REPOSTS)} + onClick={() => { + setFilterBy(FILTER_REPOSTS); + setSearchText(''); + }} className={classnames(`button-toggle`, { 'button-toggle--active': filterBy === FILTER_REPOSTS, })} @@ -161,6 +176,7 @@ function FileListPublished(props: Props) { onChange={(e) => setSearchText(e.target.value)} type="text" placeholder={__('Search')} + disabled={filterBy === FILTER_REPOSTS} />
diff --git a/ui/util/debounce.js b/ui/util/debounce.js index f702f7b0b..cd3995f42 100644 --- a/ui/util/debounce.js +++ b/ui/util/debounce.js @@ -2,10 +2,10 @@ // be triggered. The function will be called after it stops being called for // N milliseconds. If `immediate` is passed, trigger the function on the // leading edge, instead of the trailing. -export default function debouce(func, wait, immediate) { +export default function debouce(func, waitInMs, immediate) { let timeout; - return function() { + return function () { const context = this; const args = arguments; const later = () => { @@ -15,7 +15,7 @@ export default function debouce(func, wait, immediate) { const callNow = immediate && !timeout; clearTimeout(timeout); - timeout = setTimeout(later, wait); + timeout = setTimeout(later, waitInMs); if (callNow) func.apply(context, args); }; } -- 2.45.2 From db3868d65e6a3f645dda7778e4acf4a1ed2b2c2e Mon Sep 17 00:00:00 2001 From: zeppi Date: Thu, 14 Apr 2022 22:49:26 -0400 Subject: [PATCH 5/7] ui/ux touchup --- static/app-strings.json | 1 + ui/page/fileListPublished/view.jsx | 122 ++++++++++++++--------------- ui/scss/component/_wunderbar.scss | 1 + 3 files changed, 63 insertions(+), 61 deletions(-) diff --git a/static/app-strings.json b/static/app-strings.json index 7cea4e7d6..556fc65da 100644 --- a/static/app-strings.json +++ b/static/app-strings.json @@ -2306,5 +2306,6 @@ "Privacy": "Privacy", "LBRY takes privacy and choice seriously. Is it ok if we monitor performance and help creators track their views?": "LBRY takes privacy and choice seriously. Is it ok if we monitor performance and help creators track their views?", "Yes, share with LBRY": "Yes, share with LBRY", + "Search Uploads": "Search Uploads", "--end--": "--end--" } diff --git a/ui/page/fileListPublished/view.jsx b/ui/page/fileListPublished/view.jsx index 05e9d4679..972e2d03d 100644 --- a/ui/page/fileListPublished/view.jsx +++ b/ui/page/fileListPublished/view.jsx @@ -126,67 +126,67 @@ function FileListPublished(props: Props) { return (
- {!fetching && myClaims.length > 0 && ( - <> - -
- } - persistedStorageKey="claim-list-published" - uris={urls} - /> - 0 ? Math.ceil(urlTotal / Number(pageSize)) : 1} /> - - )} + +