mattermost-mobile/app/screens/browse_channels/search_handler.tsx
Elias Nahum 7aa5bd0611
Update Dependencies and bug fixes (#7000)
* update dependencies

* update dependencies

* feedback review

* update @mattermost/react-native-turbo-mailer
2023-01-24 09:14:23 +02:00

331 lines
10 KiB
TypeScript

// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import React, {useCallback, useEffect, useReducer, useRef, useState} from 'react';
import {fetchArchivedChannels, fetchChannels, fetchSharedChannels, searchChannels} from '@actions/remote/channel';
import {General} from '@constants';
import {useServerUrl} from '@context/server';
import useDidUpdate from '@hooks/did_update';
import BrowseChannels, {ARCHIVED, PUBLIC, SHARED} from './browse_channels';
import type MyChannelModel from '@typings/database/models/servers/my_channel';
import type {AvailableScreens} from '@typings/screens/navigation';
import type {ImageResource} from 'react-native-navigation';
type Props = {
// Screen Props (do not change during the lifetime of the screen)
componentId: AvailableScreens;
categoryId?: string;
closeButton: ImageResource;
// Properties not changing during the lifetime of the screen)
currentUserId: string;
currentTeamId: string;
// Calculated Props
canCreateChannels: boolean;
joinedChannels?: MyChannelModel[];
sharedChannelsEnabled: boolean;
canShowArchivedChannels: boolean;
}
const MIN_CHANNELS_LOADED = 10;
const LOAD = 'load';
const STOP = 'stop';
const filterChannelsByType = (channels: Channel[], joinedChannels: MyChannelModel[], channelType: string) => {
const ids = new Set(joinedChannels.map((c) => c.id));
let filter: (c: Channel) => boolean;
switch (channelType) {
case ARCHIVED:
filter = (c) => c.delete_at !== 0;
break;
case SHARED:
filter = (c) => c.delete_at === 0 && c.shared && !ids.has(c.id);
break;
case PUBLIC:
default:
filter = (c) => c.delete_at === 0 && !c.shared && !ids.has(c.id);
break;
}
return channels.filter(filter);
};
const filterJoinedChannels = (joinedChannels: MyChannelModel[], allChannels: Channel[] | undefined) => {
const ids = new Set(joinedChannels.map((c) => c.id));
return allChannels?.filter((c) => !ids.has(c.id));
};
type State = {
channels: Channel[];
archivedChannels: Channel[];
sharedChannels: Channel[];
loading: boolean;
}
type Action = {
type: string;
data: Channel[];
}
const LoadAction: Action = {type: LOAD, data: []};
const StopAction: Action = {type: STOP, data: []};
const addAction = (t: string, data: Channel[]) => {
return {type: t, data};
};
const reducer = (state: State, action: Action) => {
switch (action.type) {
case PUBLIC:
return {
...state,
channels: [...state.channels, ...action.data],
loading: false,
};
case ARCHIVED:
return {
...state,
archivedChannels: [...state.archivedChannels, ...action.data],
loading: false,
};
case SHARED:
return {
...state,
sharedChannels: [...state.sharedChannels, ...action.data],
loading: false,
};
case LOAD:
if (state.loading) {
return state;
}
return {
...state,
loading: true,
};
case STOP:
if (state.loading) {
return {
...state,
loading: false,
};
}
return state;
default:
return state;
}
};
const initialState = {channels: [], archivedChannels: [], sharedChannels: [], loading: false};
const defaultJoinedChannels: MyChannelModel[] = [];
const defaultSearchResults: Channel[] = [];
export default function SearchHandler(props: Props) {
const {
joinedChannels = defaultJoinedChannels,
currentTeamId,
...passProps
} = props;
const serverUrl = useServerUrl();
const [{channels, archivedChannels, sharedChannels, loading}, dispatch] = useReducer(reducer, initialState);
const [visibleChannels, setVisibleChannels] = useState<Channel[]>([]);
const [term, setTerm] = useState('');
const [typeOfChannels, setTypeOfChannels] = useState(PUBLIC);
const publicPage = useRef(-1);
const sharedPage = useRef(-1);
const archivedPage = useRef(-1);
const nextPublic = useRef(true);
const nextShared = useRef(true);
const nextArchived = useRef(true);
const loadedChannels = useRef<(data: Channel[] | undefined, typeOfChannels: string) => Promise<void>>(async () => {/* Do nothing */});
const searchTimeout = useRef<NodeJS.Timeout>();
const [searchResults, setSearchResults] = useState<Channel[]>(defaultSearchResults);
const isSearch = Boolean(term);
const doGetChannels = (t: string) => {
let next: (typeof nextPublic | typeof nextShared | typeof nextArchived);
let fetch: (typeof fetchChannels | typeof fetchSharedChannels | typeof fetchArchivedChannels);
let page: (typeof publicPage | typeof sharedPage | typeof archivedPage);
switch (t) {
case SHARED:
next = nextShared;
fetch = fetchSharedChannels;
page = sharedPage;
break;
case ARCHIVED:
next = nextArchived;
fetch = fetchArchivedChannels;
page = archivedPage;
break;
case PUBLIC:
default:
next = nextPublic;
fetch = fetchChannels;
page = publicPage;
}
if (next.current) {
dispatch(LoadAction);
fetch(
serverUrl,
currentTeamId,
page.current + 1,
General.CHANNELS_CHUNK_SIZE,
).then(
({channels: receivedChannels}) => loadedChannels.current(receivedChannels, t),
).catch(
() => dispatch(StopAction),
);
}
};
const onEndReached = useCallback(() => {
if (!loading && !term) {
doGetChannels(typeOfChannels);
}
}, [typeOfChannels, loading, term]);
let activeChannels: Channel[];
switch (typeOfChannels) {
case ARCHIVED:
activeChannels = archivedChannels;
break;
case SHARED:
activeChannels = sharedChannels;
break;
default:
activeChannels = channels;
}
const stopSearch = useCallback(() => {
setSearchResults(defaultSearchResults);
setTerm('');
}, [activeChannels]);
const doSearchChannels = useCallback((text: string) => {
if (text) {
setSearchResults(defaultSearchResults);
if (searchTimeout.current) {
clearTimeout(searchTimeout.current);
}
searchTimeout.current = setTimeout(async () => {
const results = await searchChannels(serverUrl, text, currentTeamId);
if (results.channels) {
setSearchResults(results.channels);
}
dispatch(StopAction);
}, 500);
setTerm(text);
setVisibleChannels(searchResults);
dispatch(LoadAction);
} else {
stopSearch();
}
}, [activeChannels, visibleChannels, joinedChannels, stopSearch]);
const changeChannelType = useCallback((channelType: string) => {
setTypeOfChannels(channelType);
}, []);
useEffect(() => {
loadedChannels.current = async (data: Channel[] | undefined, t: string) => {
let next: (typeof nextPublic | typeof nextShared | typeof nextArchived);
let page: (typeof publicPage | typeof sharedPage | typeof archivedPage);
let shouldFilterJoined: boolean;
switch (t) {
case SHARED:
page = sharedPage;
next = nextShared;
shouldFilterJoined = true;
break;
case ARCHIVED:
page = archivedPage;
next = nextArchived;
shouldFilterJoined = false;
break;
case PUBLIC:
default:
page = publicPage;
next = nextPublic;
shouldFilterJoined = true;
}
page.current += 1;
next.current = Boolean(data?.length);
let filtered = data;
if (shouldFilterJoined) {
filtered = filterJoinedChannels(joinedChannels, data);
}
if (filtered?.length) {
dispatch(addAction(t, filtered));
} else if (data?.length) {
doGetChannels(t);
} else {
dispatch(StopAction);
}
};
return () => {
loadedChannels.current = async () => {/* Do nothing */};
};
}, [joinedChannels]);
useEffect(() => {
if (!isSearch) {
doGetChannels(typeOfChannels);
}
}, [typeOfChannels, isSearch]);
useDidUpdate(() => {
if (isSearch) {
setVisibleChannels(filterChannelsByType(searchResults, joinedChannels, typeOfChannels));
} else {
setVisibleChannels(activeChannels);
}
}, [activeChannels, isSearch && searchResults, isSearch && typeOfChannels, joinedChannels]);
// Make sure enough channels are loaded to allow the FlatList to scroll,
// and let it call the onReachEnd function.
useDidUpdate(() => {
if (loading || isSearch || visibleChannels.length >= MIN_CHANNELS_LOADED) {
return;
}
let next;
switch (typeOfChannels) {
case PUBLIC:
next = nextPublic.current;
break;
case SHARED:
next = nextShared.current;
break;
default:
next = nextArchived.current;
}
if (next) {
doGetChannels(typeOfChannels);
}
}, [visibleChannels.length >= MIN_CHANNELS_LOADED, loading, isSearch]);
return (
<BrowseChannels
{...passProps}
currentTeamId={currentTeamId}
changeChannelType={changeChannelType}
channels={visibleChannels}
loading={loading}
onEndReached={onEndReached}
searchChannels={doSearchChannels}
stopSearch={stopSearch}
term={term}
typeOfChannels={typeOfChannels}
/>
);
}