[Gekidou - MM-44929] Search Screen - Always fetch filter results from server (#6366)

Co-authored-by: Daniel Espino García <larkox@gmail.com>
This commit is contained in:
Jason Frerich 2022-06-28 09:56:51 -05:00 committed by GitHub
parent 45f76cbc50
commit 2230fe8a70
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 86 additions and 40 deletions

View file

@ -35,7 +35,7 @@ const Files: Record<string, string[]> = {
PATCH_TYPES: ['patch'],
PDF_TYPES: ['pdf'],
PRESENTATION_TYPES: ['ppt', 'pptx', 'odp'],
SPREADSHEET_TYPES: ['xls, xlsx', 'csv', 'ods'],
SPREADSHEET_TYPES: ['xls', 'xlsx', 'csv', 'ods'],
TEXT_TYPES: ['txt', 'rtf'],
VIDEO_TYPES: ['mp4', 'avi', 'webm', 'mkv', 'wmv', 'mpg', 'mov', 'flv', 'ogm', 'mpeg'],
WORD_TYPES: ['doc', 'docx', 'odt'],

View file

@ -14,7 +14,7 @@ import {useIsTablet} from '@hooks/device';
import {t} from '@i18n';
import BottomSheetContent from '@screens/bottom_sheet/content';
import {dismissBottomSheet} from '@screens/navigation';
import {FileFilter} from '@utils/file';
import {FileFilter, FileFilters} from '@utils/file';
import {makeStyleSheetFromTheme} from '@utils/theme';
import {typography} from '@utils/typography';
@ -43,35 +43,35 @@ const data: FilterItem[] = [
{
id: t('screen.search.results.filter.all_file_types'),
defaultMessage: 'All file types',
filterType: 'all',
filterType: FileFilters.ALL,
}, {
id: t('screen.search.results.filter.documents'),
defaultMessage: 'Documents',
filterType: 'documents',
filterType: FileFilters.DOCUMENTS,
}, {
id: t('screen.search.results.filter.spreadsheets'),
defaultMessage: 'Spreadsheets',
filterType: 'spreadsheets',
filterType: FileFilters.SPREADSHEETS,
}, {
id: t('screen.search.results.filter.presentations'),
defaultMessage: 'Presentations',
filterType: 'presentations',
filterType: FileFilters.PRESENTATIONS,
}, {
id: t('screen.search.results.filter.code'),
defaultMessage: 'Code',
filterType: 'code',
filterType: FileFilters.CODE,
}, {
id: t('screen.search.results.filter.images'),
defaultMessage: 'Images',
filterType: 'images',
filterType: FileFilters.IMAGES,
}, {
id: t('screen.search.results.filter.audio'),
defaultMessage: 'Audio',
filterType: 'audio',
filterType: FileFilters.AUDIO,
}, {
id: t('screen.search.results.filter.videos'),
defaultMessage: 'Videos',
filterType: 'videos',
filterType: FileFilters.VIDEOS,
separator: false,
},
];

View file

@ -8,7 +8,7 @@ import Badge from '@components/badge';
import CompassIcon from '@components/compass_icon';
import {useTheme} from '@context/theme';
import {bottomSheet} from '@screens/navigation';
import {FileFilter} from '@utils/file';
import {FileFilter, FileFilters} from '@utils/file';
import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme';
import Filter from './filter';
@ -68,7 +68,7 @@ const Header = ({
const filesText = intl.formatMessage({id: 'screen.search.header.files', defaultMessage: 'Files'});
const showFilterIcon = selectedTab === 'files';
const hasFilters = selectedFilter !== 'all';
const hasFilters = selectedFilter !== FileFilters.ALL;
const handleMessagesPress = useCallback(() => {
onTabSelect('messages');

View file

@ -15,7 +15,7 @@ import NavigationHeader from '@components/navigation_header';
import RoundedHeaderContext from '@components/rounded_header_context';
import {useServerUrl} from '@context/server';
import {useCollapsibleHeader} from '@hooks/header';
import {FileFilter, filterFiles} from '@utils/file';
import {FileFilter, FileFilters, filterFileExtensions} from '@utils/file';
import Results from './results';
import Header, {SelectTab} from './results/header';
@ -46,14 +46,23 @@ const SearchScreen = ({teamId}: Props) => {
const [searchValue, setSearchValue] = useState<string>(searchTerm);
const [selectedTab, setSelectedTab] = useState<SelectTab>('messages');
const [filter, setFilter] = useState<FileFilter>('all');
const [filter, setFilter] = useState<FileFilter>(FileFilters.ALL);
const [loading, setLoading] = useState(false);
const [lastSearchedValue, setLastSearchedValue] = useState('');
const [postIds, setPostIds] = useState<string[]>(emptyPostResults);
const [fileInfos, setFileInfos] = useState<FileInfo[]>(emptyFileResults);
const [filteredFileInfos, setFilteredFileInfos] = useState<FileInfo[]>(emptyFileResults);
const getSearchParams = useCallback((filterValue?: FileFilter) => {
const terms = filterValue ? lastSearchedValue : searchValue;
const fileExtensions = filterFileExtensions(filterValue || filter);
const extensionTerms = fileExtensions ? ' ' + fileExtensions : '';
return {
terms: terms + extensionTerms,
is_or_search: true,
};
}, [filter, lastSearchedValue, searchValue]);
const handleSearch = useCallback((debounce(async () => {
// execute the search for the text in the navigation text box
@ -62,13 +71,9 @@ const SearchScreen = ({teamId}: Props) => {
// - updated recent createdAt if exists??
setLoading(true);
setFilter(FileFilters.ALL);
setLastSearchedValue(searchValue);
const searchParams: PostSearchParams | FileSearchParams = {
terms: searchValue,
is_or_search: true,
};
const searchParams = getSearchParams();
const [postResults, fileResults] = await Promise.all([
searchPosts(serverUrl, searchParams),
searchFiles(serverUrl, teamId, searchParams),
@ -85,14 +90,21 @@ const SearchScreen = ({teamId}: Props) => {
scrollRef.current?.scrollToOffset({offset, animated: true});
};
const handleFilterChange = useCallback(async (filterValue: FileFilter) => {
setLoading(true);
setFilter(filterValue);
const searchParams = getSearchParams(filterValue);
const fileResults = await searchFiles(serverUrl, teamId, searchParams);
const fileInfosResult = fileResults?.file_infos && Object.values(fileResults?.file_infos);
setFileInfos(fileInfosResult?.length ? fileInfosResult : emptyFileResults);
setLoading(false);
}, [lastSearchedValue]);
useEffect(() => {
setSearchValue(searchTerm);
}, [searchTerm]);
useEffect(() => {
setFilteredFileInfos(filterFiles(fileInfos, filter));
}, [filter, fileInfos]);
const {scrollPaddingTop, scrollRef, scrollValue, onScroll, headerHeight, hideHeader} = useCollapsibleHeader<FlatList>(true, onSnap);
const animated = useAnimatedStyle(() => {
@ -118,15 +130,21 @@ const SearchScreen = ({teamId}: Props) => {
};
}, [headerHeight, lastSearchedValue]);
const onClear = useCallback(() => {
setSearchValue('');
setLastSearchedValue('');
setFilter(FileFilters.ALL);
}, [filter]);
let header = null;
if (lastSearchedValue) {
header = (
<Header
onTabSelect={setSelectedTab}
onFilterChanged={setFilter}
onFilterChanged={handleFilterChange}
numberMessages={postIds.length}
selectedTab={selectedTab}
numberFiles={Object.keys(filteredFileInfos).length}
numberFiles={fileInfos.length}
selectedFilter={filter}
/>
);
@ -146,6 +164,7 @@ const SearchScreen = ({teamId}: Props) => {
blurOnSubmit={true}
placeholder={intl.formatMessage({id: 'screen.search.placeholder', defaultMessage: 'Search messages & files'})}
defaultValue={searchValue}
onClear={onClear}
/>
<SafeAreaView
style={styles.flex}
@ -160,7 +179,7 @@ const SearchScreen = ({teamId}: Props) => {
selectedTab={selectedTab}
searchValue={lastSearchedValue}
postIds={postIds}
fileInfos={filteredFileInfos}
fileInfos={fileInfos}
scrollRef={scrollRef}
onScroll={onScroll}
scrollPaddingTop={scrollPaddingTop}

View file

@ -15,6 +15,7 @@ import Permissions, {PERMISSIONS} from 'react-native-permissions';
import {Files} from '@constants';
import {generateId} from '@utils/general';
import keyMirror from '@utils/key_mirror';
import {deleteEntititesFile, getIOSAppGroupDetails} from '@utils/mattermost_managed';
import {hashCode} from '@utils/security';
@ -24,7 +25,17 @@ const EXTRACT_TYPE_REGEXP = /^\s*([^;\s]*)(?:;|\s|$)/;
const CONTENT_DISPOSITION_REGEXP = /inline;filename=".*\.([a-z]+)";/i;
const DEFAULT_SERVER_MAX_FILE_SIZE = 50 * 1024 * 1024;// 50 Mb
export type FileFilter = 'all' | 'documents' | 'spreadsheets'| 'presentations' | 'code' | 'images' | 'audio' | 'videos'
export const FileFilters = keyMirror({
ALL: null,
DOCUMENTS: null,
SPREADSHEETS: null,
PRESENTATIONS: null,
CODE: null,
IMAGES: null,
AUDIO: null,
VIDEOS: null,
});
export type FileFilter = keyof typeof FileFilters
export const GENERAL_SUPPORTED_DOCS_FORMAT = [
'application/json',
@ -60,20 +71,36 @@ const SUPPORTED_VIDEO_FORMAT = Platform.select({
const types: Record<string, string> = {};
const extensions: Record<string, readonly string[]> = {};
export function filterFiles<T extends FileModel | FileInfo>(files: T[], filter: FileFilter) {
export function filterFileExtensions(filter: FileFilter): string {
let searchTerms: string[] = [];
switch (filter) {
case 'all':
return files;
case 'videos':
return files.filter((f) => isVideo(f));
case 'documents':
return files.filter((f) => isDocument(f));
case 'images':
return files.filter((f) => isImage(f));
case FileFilters.ALL:
return '';
case FileFilters.DOCUMENTS:
searchTerms = Files.DOCUMENT_TYPES;
break;
case FileFilters.SPREADSHEETS:
searchTerms = Files.SPREADSHEET_TYPES;
break;
case FileFilters.PRESENTATIONS:
searchTerms = Files.PRESENTATION_TYPES;
break;
case FileFilters.CODE:
searchTerms = Files.CODE_TYPES;
break;
case FileFilters.IMAGES:
searchTerms = Files.IMAGE_TYPES;
break;
case FileFilters.AUDIO:
searchTerms = Files.AUDIO_TYPES;
break;
case FileFilters.VIDEOS:
searchTerms = Files.VIDEO_TYPES;
break;
default:
// TODO create the rest of the filters
return files.filter((f) => !isVideo(f) && !isDocument(f) && !isImage(f));
return '';
}
return 'ext:' + searchTerms.join(' ext:');
}
/**