Skip to content

Commit

Permalink
Add sort and search on uploads page - Improvement attempt (#3105)
Browse files Browse the repository at this point in the history
* Change "Uploads" to mass fetch all claims

* Reset page number when filter changes

* Add sorting options and search on uploads page
(Copied from playlists page)

* Bring back old logic
Add toggle to enable filters

* Mass fetch all my uploads only once

* Show info text + spinner, if loading all claims takes long

* Appstring

* Fix Refresh button on claim_search tabs

* Creation Time -> Release Time

* Use reposted claim when searching and sorting by name

* Sort by name: handle numeric

---------

Co-authored-by: miko <[email protected]>
  • Loading branch information
keikari and miko authored May 31, 2024
1 parent 64f9569 commit 40922d5
Show file tree
Hide file tree
Showing 13 changed files with 650 additions and 112 deletions.
2 changes: 1 addition & 1 deletion flow-typed/Claim.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ declare type GenericClaim = {
type: 'claim' | 'update' | 'support',
value_type: 'stream' | 'channel' | 'collection',
signing_channel?: ChannelClaim,
reposted_claim?: GenericClaim,
reposted_claim?: Claim,
repost_channel_url?: string,
repost_url?: string,
repost_bid_amount?: string,
Expand Down
1 change: 1 addition & 0 deletions flow-typed/Claims.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ declare type ClaimsState = {
failedToResolveUris: Array<string>,
failedToResolveIds: Array<ClaimId>,
reflectingById: { [ClaimId]: ReflectingUpdate },
isAllMyClaimsFetched: ?boolean,
myClaims: ?Array<ClaimId>,
myChannelClaimsById: ?{ [channelClaimId: string]: ChannelClaim },
resolvedCollectionsById: { [collectionClaimId: string]: Collection },
Expand Down
2 changes: 2 additions & 0 deletions static/app-strings.json
Original file line number Diff line number Diff line change
Expand Up @@ -2933,6 +2933,8 @@
"Disable Tipping and Boosting": "Disable Tipping and Boosting",
"You will not see content under 1min long. Also hides non-video/audio content.": "You will not see content under 1min long. Also hides non-video/audio content.",
"Hide short content": "Hide short content",
"Larger upload list may take time to load with filters enabled": "Larger upload list may take time to load with filters enabled",
"Enable filters": "Enable filters",

"--end--": "--end--"
}
43 changes: 43 additions & 0 deletions ui/constants/file_list.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
export const FILTER_TYPE_KEY = 'filterType';

export const SEARCH_TERM_KEY = 'search';

export const PAGE_SIZE_ALL_ITEMS = 99999;

export const FILE_TYPE = Object.freeze({
ALL: { key: 'All', cmd: 'stream,repost', label: 'All', ariaLabel: 'All uploads' },
UPLOADS: { key: 'Uploads', cmd: 'stream', label: 'Uploads' },
REPOSTS: { key: 'Reposts', cmd: 'repost', label: 'Reposts' },
UNLISTED: { key: 'Unlisted', cmd: '', label: 'Unlisted' },
SCHEDULED: { key: 'Scheduled', cmd: '', label: 'Scheduled' },
});

export const SORT_ORDER = Object.freeze({
ASC: 'asc', // ascending
DESC: 'desc', // descending
});

export const SORT_KEYS = Object.freeze({
NAME: 'name',
RELEASED_AT: 'releasedAt',
UPDATED_AT: 'updatedAt',
});

export const SORT_VALUES = Object.freeze({
[SORT_KEYS.NAME]: { str: 'Name', orders: { [SORT_ORDER.ASC]: 'A-Z', [SORT_ORDER.DESC]: 'Z-A' } },
[SORT_KEYS.RELEASED_AT]: {
str: 'Release Time',
orders: { [SORT_ORDER.ASC]: 'Newest First', [SORT_ORDER.DESC]: 'Oldest First' },
},
[SORT_KEYS.UPDATED_AT]: {
str: 'Updated Time',
orders: { [SORT_ORDER.ASC]: 'Newest First', [SORT_ORDER.DESC]: 'Oldest First' },
},
});

export const METHOD = Object.freeze({
CLAIM_LIST: 'CLAIM_LIST',
CLAIM_SEARCH: 'CLAIM_SEARCH',
});

export const DEFAULT_SORT = { key: SORT_KEYS.UPDATED_AT, value: SORT_ORDER.ASC };
12 changes: 12 additions & 0 deletions ui/page/fileListPublished/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,12 @@ import {
selectMyClaimsPageItemCount,
selectFetchingMyClaimsPageError,
selectMyChannelClaimIds,
selectMyPublicationClaims,
selectMyStreamClaims,
selectMyRepostClaims,
selectMyUnlistedClaims,
selectMyScheduledClaims,
selectIsAllMyClaimsFetched,
} from 'redux/selectors/claims';
import { selectUploadCount } from 'redux/selectors/publish';
import { doFetchClaimListMine, doCheckPendingClaims, doClearClaimSearch } from 'redux/actions/claims';
Expand All @@ -25,6 +31,12 @@ const select = (state, props) => {
fetching: selectIsFetchingClaimListMine(state),
urls: selectMyClaimsPage(state),
urlTotal: selectMyClaimsPageItemCount(state),
isAllMyClaimsFetched: selectIsAllMyClaimsFetched(state),
myClaims: selectMyPublicationClaims(state),
myStreamClaims: selectMyStreamClaims(state),
myRepostClaims: selectMyRepostClaims(state),
myUnlistedClaims: selectMyUnlistedClaims(state),
myScheduledClaims: selectMyScheduledClaims(state),
error: selectFetchingMyClaimsPageError(state),
uploadCount: selectUploadCount(state),
myChannelIds: selectMyChannelClaimIds(state),
Expand Down
107 changes: 107 additions & 0 deletions ui/page/fileListPublished/internal/fileListHeader/index.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
// @flow
import React from 'react';
import Button from 'component/button';
import * as FILE_LIST from 'constants/file_list';
import { FileListContext } from 'page/fileListPublished/view';
import classnames from 'classnames';
import { FormField } from 'component/common/form';
import { useHistory } from 'react-router';
import RightSideActions from './internal/rightSideActions/index';

type Props = {
filterType: string,
sortOption: { key: string, value: string },
setFilterType: (type: string) => void,
setSortOption: (params: { key: string, value: string }) => void,
};

export default function ClaimListHeader(props: Props) {
const { filterType, sortOption, setFilterType, setSortOption } = props;

const { isFilteringEnabled } = React.useContext(FileListContext);

const history = useHistory();
const {
location: { search },
} = history;

const urlParams = new URLSearchParams(search);

function handleChange(sortObj) {
// can only have one sorting option at a time
Object.keys(FILE_LIST.SORT_VALUES).forEach((k) => urlParams.get(k) && urlParams.delete(k));

urlParams.set(sortObj.key, sortObj.value);
setSortOption(sortObj);

const url = `?${urlParams.toString()}`;
history.push(url);
}

function handleFilterTypeChange(value) {
urlParams.set(FILE_LIST.FILTER_TYPE_KEY, value);
setFilterType(value);

const url = `?${urlParams.toString()}`;
history.push(url);
}

return (
<div className="section__header-action-stack">
<div className="section__header--actions">
<div className="claim-search__wrapper--wrap">
{/* Filter Options */}
<div className="claim-search__menu-group">
<div className="claim-search__menu-subgroup">
{/* $FlowFixMe */}
{Object.values(FILE_LIST.FILE_TYPE).map((info: FilterInfo) => (
<Button
button="alt"
key={info.label}
label={__(info.label)}
aria-label={info.ariaLabel}
onClick={() => handleFilterTypeChange(info.key)}
className={classnames(`button-toggle`, { 'button-toggle--active': filterType === info.key })}
/>
))}
</div>
{isFilteringEnabled && (
<div className="claim-search__menu-subgroup">
<FormField
className="claim-search__dropdown"
type="select"
name="sort_by"
value={sortOption.key}
onChange={(e) => handleChange({ key: e.target.value, value: FILE_LIST.SORT_ORDER.ASC })}
>
{Object.entries(FILE_LIST.SORT_VALUES).map(([key, value]) => (
<option key={key} value={key}>
{/* $FlowFixMe */}
{__(value.str)}
</option>
))}
</FormField>
<FormField
className="claim-search__dropdown"
type="select"
name="order_by"
value={sortOption.value}
onChange={(e) => handleChange({ key: sortOption.key, value: e.target.value })}
>
{Object.entries(FILE_LIST.SORT_ORDER).map(([key, value]) => (
// $FlowFixMe
<option key={value} value={value}>
{__(FILE_LIST.SORT_VALUES[sortOption.key].orders[value])}
</option>
))}
</FormField>
</div>
)}
</div>
</div>

<RightSideActions />
</div>
</div>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { connect } from 'react-redux';
import { doClearClaimSearch, doFetchClaimListMine } from 'redux/actions/claims';
import RightSideActions from './view';

const perform = {
doClearClaimSearch,
fetchClaimListMine: doFetchClaimListMine,
};

export default connect(null, perform)(RightSideActions);
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
// @flow
import type { DoFetchClaimListMine } from 'redux/actions/claims';

import React from 'react';
import { FormField, Form } from 'component/common/form';
import { useHistory } from 'react-router';
import { FileListContext } from 'page/fileListPublished/view';
import * as FILE_LIST from 'constants/file_list';
import * as KEYCODES from 'constants/keycodes';
import * as ICONS from 'constants/icons';
import Icon from 'component/common/icon';
import Button from 'component/button';

type Props = {
// -- redux --
fetchClaimListMine: DoFetchClaimListMine,
doClearClaimSearch: () => void,
};

const RightSideActions = (props: Props) => {
const { fetchClaimListMine, doClearClaimSearch } = props;
const { searchText, setSearchText, isFilteringEnabled, setIsFilteringEnabled, fetching, method } =
React.useContext(FileListContext);

const history = useHistory();
const {
location: { search },
} = history;
const urlParams = new URLSearchParams(search);

function handleSearchTextChange(value) {
setSearchText(value);

if (value === '') {
urlParams.get(FILE_LIST.SEARCH_TERM_KEY) && urlParams.delete(FILE_LIST.SEARCH_TERM_KEY);
} else {
urlParams.set(FILE_LIST.SEARCH_TERM_KEY, value);
}

const url = `?${urlParams.toString()}`;
history.push(url);
}

function escapeListener(e: SyntheticKeyboardEvent<*>) {
if (e.keyCode === KEYCODES.ESCAPE) {
e.preventDefault();
setSearchText('');
}
}

function onTextareaFocus() {
window.addEventListener('keydown', escapeListener);
}

function onTextareaBlur() {
window.removeEventListener('keydown', escapeListener);
}

return (
<div className="claim-search__wrapper--wrap">
{/* Search Field */}
<div className="claim-search__menu-group">
{isFilteringEnabled && (
<Form onSubmit={() => {}} className="wunderbar--inline">
<Icon icon={ICONS.SEARCH} />
<FormField
name="collection_search"
onFocus={onTextareaFocus}
onBlur={onTextareaBlur}
className="wunderbar__input--inline"
value={searchText}
onChange={(e) => handleSearchTextChange(e.target.value)}
type="text"
placeholder={__('Search')}
/>
</Form>
)}
<div className="claim-search__menu-group enable-filters-checkbox">
<FormField
label={__('Enable filters')}
name="enable_filters"
type="checkbox"
checked={isFilteringEnabled}
onChange={() => setIsFilteringEnabled(!isFilteringEnabled)}
/>
</div>
</div>
{/* Playlist Create Button */}
<Button
button="alt"
label={__('Refresh')}
disabled={fetching}
icon={ICONS.REFRESH}
onClick={() => {
if (method === FILE_LIST.METHOD.CLAIM_LIST) {
fetchClaimListMine(1, FILE_LIST.PAGE_SIZE_ALL_ITEMS, true, [], true);
} else {
doClearClaimSearch();
}
}}
/>{' '}
</div>
);
};

export default RightSideActions;
Loading

0 comments on commit 40922d5

Please sign in to comment.