TelegramPWA/src/components/middle/MobileSearch.tsx
2021-06-15 02:21:29 +03:00

229 lines
6.4 KiB
TypeScript

import React, {
FC, memo, useCallback, useEffect, useRef, useState, useLayoutEffect,
} from '../../lib/teact/teact';
import { withGlobal } from '../../lib/teact/teactn';
import { ApiChat } from '../../api/types';
import { GlobalActions } from '../../global/types';
import { debounce } from '../../util/schedulers';
import { selectCurrentTextSearch, selectCurrentChat } from '../../modules/selectors';
import { pick } from '../../util/iteratees';
import { getDayStartAt } from '../../util/dateFormat';
import Button from '../ui/Button';
import SearchInput from '../ui/SearchInput';
import './MobileSearch.scss';
export type OwnProps = {
isActive: boolean;
};
type StateProps = {
isActive?: boolean;
chat?: ApiChat;
query?: string;
totalCount?: number;
foundIds?: number[];
isHistoryCalendarOpen?: boolean;
};
type DispatchProps = Pick<GlobalActions, (
'setLocalTextSearchQuery' | 'searchTextMessagesLocal' | 'closeLocalTextSearch' | 'openHistoryCalendar' |
'focusMessage'
)>;
const runDebouncedForSearch = debounce((cb) => cb(), 200, false);
const MobileSearchFooter: FC<StateProps & DispatchProps> = ({
isActive,
chat,
query,
totalCount,
foundIds,
isHistoryCalendarOpen,
setLocalTextSearchQuery,
searchTextMessagesLocal,
focusMessage,
closeLocalTextSearch,
openHistoryCalendar,
}) => {
// eslint-disable-next-line no-null/no-null
const inputRef = useRef<HTMLInputElement>(null);
const [focusedIndex, setFocusedIndex] = useState(0);
// Fix for iOS keyboard
useEffect(() => {
const { visualViewport } = window as any;
if (!visualViewport) {
return undefined;
}
const mainEl = document.getElementById('Main') as HTMLDivElement;
const handleResize = () => {
const { activeElement } = document;
if (activeElement && (activeElement === inputRef.current)) {
const { pageTop, height } = visualViewport;
mainEl.style.transform = `translateY(${pageTop}px)`;
mainEl.style.height = `${height}px`;
document.documentElement.scrollTop = pageTop;
} else {
mainEl.style.transform = '';
mainEl.style.height = '';
}
};
visualViewport.addEventListener('resize', handleResize);
return () => {
visualViewport.removeEventListener('resize', handleResize);
};
}, []);
// Focus message
useEffect(() => {
if (chat && foundIds && foundIds.length) {
focusMessage({ chatId: chat.id, messageId: foundIds[foundIds.length - 1] });
setFocusedIndex(0);
} else {
setFocusedIndex(-1);
}
}, [chat, focusMessage, foundIds]);
// Disable native up/down buttons on iOS
useEffect(() => {
Array.from(document.querySelectorAll<HTMLInputElement>('input')).forEach((input) => {
input.disabled = Boolean(isActive && input !== inputRef.current);
});
Array.from(document.querySelectorAll<HTMLDivElement>('div[contenteditable]')).forEach((div) => {
div.contentEditable = isActive ? 'false' : 'true';
});
}, [isActive]);
// Blur on exit
useEffect(() => {
if (!isActive) {
inputRef.current!.blur();
}
}, [isActive]);
useLayoutEffect(() => {
const searchInput = document.querySelector<HTMLInputElement>('#MobileSearch input')!;
searchInput.blur();
}, [isHistoryCalendarOpen]);
const handleMessageSearchQueryChange = useCallback((newQuery: string) => {
setLocalTextSearchQuery({ query: newQuery });
if (newQuery.length) {
runDebouncedForSearch(searchTextMessagesLocal);
}
}, [searchTextMessagesLocal, setLocalTextSearchQuery]);
const handleUp = useCallback(() => {
if (chat && foundIds) {
const newFocusIndex = focusedIndex + 1;
focusMessage({ chatId: chat.id, messageId: foundIds[foundIds.length - 1 - newFocusIndex] });
setFocusedIndex(newFocusIndex);
}
}, [chat, focusedIndex, focusMessage, foundIds]);
const handleDown = useCallback(() => {
if (chat && foundIds) {
const newFocusIndex = focusedIndex - 1;
focusMessage({ chatId: chat.id, messageId: foundIds[foundIds.length - 1 - newFocusIndex] });
setFocusedIndex(newFocusIndex);
}
}, [chat, focusedIndex, focusMessage, foundIds]);
return (
<div id="MobileSearch" className={isActive ? 'active' : ''}>
<div className="header">
<Button
size="smaller"
round
color="translucent"
onClick={closeLocalTextSearch}
>
<i className="icon-arrow-left" />
</Button>
<SearchInput
ref={inputRef}
value={query}
onChange={handleMessageSearchQueryChange}
/>
</div>
<div className="footer">
<div className="counter">
{query ? (
foundIds && foundIds.length ? (
`${focusedIndex + 1} of ${totalCount}`
) : foundIds && !foundIds.length ? (
'No results'
) : (
''
)
) : (
<Button
round
size="smaller"
color="translucent"
onClick={() => openHistoryCalendar({ selectedAt: getDayStartAt(Date.now()) })}
ariaLabel="Search messages by date"
>
<i className="icon-calendar" />
</Button>
)}
</div>
<Button
round
size="smaller"
color="translucent"
onClick={handleUp}
disabled={!foundIds || !foundIds.length || focusedIndex === foundIds.length - 1}
>
<i className="icon-up" />
</Button>
<Button
round
size="smaller"
color="translucent"
onClick={handleDown}
disabled={!foundIds || !foundIds.length || focusedIndex === 0}
>
<i className="icon-down" />
</Button>
</div>
</div>
);
};
export default memo(withGlobal<OwnProps>(
(global): StateProps => {
const chat = selectCurrentChat(global);
if (!chat) {
return {};
}
const { query, results } = selectCurrentTextSearch(global) || {};
const { totalCount, foundIds } = results || {};
return {
chat,
query,
totalCount,
foundIds,
isHistoryCalendarOpen: Boolean(global.historyCalendarSelectedAt),
};
},
(setGlobal, actions): DispatchProps => pick(actions, [
'setLocalTextSearchQuery',
'searchTextMessagesLocal',
'focusMessage',
'closeLocalTextSearch',
'openHistoryCalendar',
]),
)(MobileSearchFooter));