From 7e27113f617d35ee0dd8ef36a93679c285bc15c2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Espino=20Garc=C3=ADa?= Date: Thu, 19 May 2022 09:12:05 +0200 Subject: [PATCH] Use local channels on channel autocomplete (#6272) * Use local channels on channel autocomplete * Only load local channels on demand --- .../channel_mention/channel_mention.tsx | 55 ++++++++++++++++--- 1 file changed, 46 insertions(+), 9 deletions(-) diff --git a/app/components/autocomplete/channel_mention/channel_mention.tsx b/app/components/autocomplete/channel_mention/channel_mention.tsx index bab1e03dc..da22ba76d 100644 --- a/app/components/autocomplete/channel_mention/channel_mention.tsx +++ b/app/components/autocomplete/channel_mention/channel_mention.tsx @@ -12,10 +12,14 @@ import {General} from '@constants'; import {CHANNEL_MENTION_REGEX, CHANNEL_MENTION_SEARCH_REGEX} from '@constants/autocomplete'; import {useServerUrl} from '@context/server'; import {useTheme} from '@context/theme'; +import DatabaseManager from '@database/manager'; import useDidUpdate from '@hooks/did_update'; import {t} from '@i18n'; +import {queryAllChannelsForTeam} from '@queries/servers/channel'; +import {getCurrentTeamId} from '@queries/servers/system'; import {makeStyleSheetFromTheme} from '@utils/theme'; +import type ChannelModel from '@typings/database/models/servers/channel'; import type MyChannelModel from '@typings/database/models/servers/my_channel'; const keyExtractor = (item: Channel) => { @@ -48,9 +52,9 @@ const getMatchTermForChannelMention = (() => { }; })(); -const reduceChannelsForSearch = (channels: Channel[], members: MyChannelModel[]) => { +const reduceChannelsForSearch = (channels: Array, members: MyChannelModel[]) => { const memberIds = new Set(members.map((m) => m.id)); - return channels.reduce(([pubC, priC, dms], c) => { + return channels.reduce>>(([pubC, priC, dms], c) => { switch (c.type) { case General.OPEN_CHANNEL: if (memberIds.has(c.id)) { @@ -68,9 +72,9 @@ const reduceChannelsForSearch = (channels: Channel[], members: MyChannelModel[]) }, [[], [], []]); }; -const reduceChannelsForAutocomplete = (channels: Channel[], members: MyChannelModel[]) => { +const reduceChannelsForAutocomplete = (channels: Array, members: MyChannelModel[]) => { const memberIds = new Set(members.map((m) => m.id)); - return channels.reduce(([myC, otherC], c) => { + return channels.reduce>>(([myC, otherC], c) => { if (memberIds.has(c.id)) { myC.push(c); } else { @@ -80,7 +84,7 @@ const reduceChannelsForAutocomplete = (channels: Channel[], members: MyChannelMo }, [[], []]); }; -const makeSections = (channels: Channel[], myMembers: MyChannelModel[], isSearch = false) => { +const makeSections = (channels: Array, myMembers: MyChannelModel[], isSearch = false) => { const newSections = []; if (isSearch) { const [publicChannels, privateChannels, directAndGroupMessages] = reduceChannelsForSearch(channels, myMembers); @@ -143,6 +147,14 @@ const makeSections = (channels: Channel[], myMembers: MyChannelModel[], isSearch return newSections; }; + +const filterLocalResults = (channels: ChannelModel[], term: string) => { + return channels.filter((c) => + c.name.toLowerCase().startsWith(term) || + c.displayName.toLowerCase().startsWith(term), + ); +}; + type Props = { cursorPosition: number; isSearch: boolean; @@ -163,8 +175,19 @@ const getStyleFromTheme = makeStyleSheetFromTheme((theme) => { }; }); +const getAllChannels = async (serverUrl: string) => { + const database = DatabaseManager.serverDatabases[serverUrl]?.database; + if (!database) { + return []; + } + + const teamId = await getCurrentTeamId(database); + return queryAllChannelsForTeam(database, teamId).fetch(); +}; + const emptySections: Array> = []; const emptyChannels: Channel[] = []; +const emptyModelList: ChannelModel[] = []; const ChannelMention = ({ cursorPosition, @@ -180,11 +203,14 @@ const ChannelMention = ({ const theme = useTheme(); const style = getStyleFromTheme(theme); - const [sections, setSections] = useState>>(emptySections); + const [sections, setSections] = useState>>(emptySections); const [channels, setChannels] = useState(emptyChannels); const [loading, setLoading] = useState(false); const [noResultsTerm, setNoResultsTerm] = useState(null); const [localCursorPosition, setLocalCursorPosition] = useState(cursorPosition); // To avoid errors due to delay between value changes and cursor position changes. + const [useLocal, setUseLocal] = useState(true); + const [localChannels, setlocalChannels] = useState(); + const [filteredLocalChannels, setFilteredLocalChannels] = useState(emptyModelList); const listStyle = useMemo(() => [style.listView, {maxHeight: maxListHeight}] @@ -192,8 +218,18 @@ const ChannelMention = ({ const runSearch = useMemo(() => debounce(async (sUrl: string, term: string) => { setLoading(true); - const {channels: receivedChannels} = await searchChannels(sUrl, term); - if (receivedChannels) { + const {channels: receivedChannels, error} = await searchChannels(sUrl, term); + setUseLocal(Boolean(error)); + + if (error) { + let fallbackChannels = localChannels; + if (!fallbackChannels) { + fallbackChannels = await getAllChannels(sUrl); + setlocalChannels(fallbackChannels); + } + const filteredChannels = filterLocalResults(fallbackChannels, term); + setFilteredLocalChannels(filteredChannels.length ? filteredChannels : emptyModelList); + } else if (receivedChannels) { setChannels(receivedChannels.length ? receivedChannels : emptyChannels); } setLoading(false); @@ -201,6 +237,7 @@ const ChannelMention = ({ const matchTerm = getMatchTermForChannelMention(value.substring(0, localCursorPosition), isSearch); const resetState = () => { + setFilteredLocalChannels(emptyModelList); setChannels(emptyChannels); setSections(emptySections); runSearch.cancel(); @@ -285,7 +322,7 @@ const ChannelMention = ({ }, [matchTerm]); useDidUpdate(() => { - const newSections = makeSections(channels, myMembers, isSearch); + const newSections = makeSections(useLocal ? filteredLocalChannels : channels, myMembers, isSearch); const nSections = newSections.length; if (!loading && !nSections && noResultsTerm == null) {