Sticky Dates: Support opening history calendar
This commit is contained in:
parent
b9dbcab72c
commit
36c4901e5f
@ -4,6 +4,7 @@ export { default as ForwardPicker } from '../components/main/ForwardPicker';
|
||||
export { default as Errors } from '../components/main/Errors';
|
||||
export { default as Notifications } from '../components/main/Notifications';
|
||||
export { default as SafeLinkModal } from '../components/main/SafeLinkModal';
|
||||
export { default as HistoryCalendar } from '../components/main/HistoryCalendar';
|
||||
|
||||
export { default as CalendarModal } from '../components/common/CalendarModal';
|
||||
export { default as DeleteMessageModal } from '../components/common/DeleteMessageModal';
|
||||
|
||||
16
src/components/main/HistoryCalendar.async.tsx
Normal file
16
src/components/main/HistoryCalendar.async.tsx
Normal file
@ -0,0 +1,16 @@
|
||||
import React, { FC, memo } from '../../lib/teact/teact';
|
||||
import { Bundles } from '../../util/moduleLoader';
|
||||
|
||||
import { OwnProps } from './HistoryCalendar';
|
||||
|
||||
import useModuleLoader from '../../hooks/useModuleLoader';
|
||||
|
||||
const HistoryCalendarAsync: FC<OwnProps> = (props) => {
|
||||
const { isOpen } = props;
|
||||
const HistoryCalendar = useModuleLoader(Bundles.Extra, 'HistoryCalendar', !isOpen);
|
||||
|
||||
// eslint-disable-next-line react/jsx-props-no-spreading
|
||||
return HistoryCalendar ? <HistoryCalendar {...props} /> : undefined;
|
||||
};
|
||||
|
||||
export default memo(HistoryCalendarAsync);
|
||||
52
src/components/main/HistoryCalendar.tsx
Normal file
52
src/components/main/HistoryCalendar.tsx
Normal file
@ -0,0 +1,52 @@
|
||||
import React, { FC, memo, useCallback } from '../../lib/teact/teact';
|
||||
import { withGlobal } from '../../lib/teact/teactn';
|
||||
|
||||
import { GlobalActions } from '../../global/types';
|
||||
|
||||
import { pick } from '../../util/iteratees';
|
||||
import useLang from '../../hooks/useLang';
|
||||
|
||||
import CalendarModal from '../common/CalendarModal';
|
||||
|
||||
export type OwnProps = {
|
||||
isOpen: boolean;
|
||||
};
|
||||
|
||||
type StateProps = {
|
||||
selectedAt?: number;
|
||||
};
|
||||
|
||||
type DispatchProps = Pick<GlobalActions, 'searchMessagesByDate' | 'closeHistoryCalendar'>;
|
||||
|
||||
const HistoryCalendar: FC<OwnProps & StateProps & DispatchProps> = ({
|
||||
isOpen, selectedAt, searchMessagesByDate, closeHistoryCalendar,
|
||||
}) => {
|
||||
const handleJumpToDate = useCallback((date: Date) => {
|
||||
searchMessagesByDate({ timestamp: date.valueOf() / 1000 });
|
||||
closeHistoryCalendar();
|
||||
}, [closeHistoryCalendar, searchMessagesByDate]);
|
||||
|
||||
const lang = useLang();
|
||||
|
||||
return (
|
||||
<CalendarModal
|
||||
isOpen={isOpen}
|
||||
selectedAt={selectedAt}
|
||||
isPastMode
|
||||
submitButtonLabel={lang('JumpToDate')}
|
||||
onClose={closeHistoryCalendar}
|
||||
onSubmit={handleJumpToDate}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(withGlobal<OwnProps>(
|
||||
(global): StateProps => {
|
||||
return {
|
||||
selectedAt: global.historyCalendarSelectedAt,
|
||||
};
|
||||
},
|
||||
(setGlobal, actions): DispatchProps => pick(actions, [
|
||||
'searchMessagesByDate', 'closeHistoryCalendar',
|
||||
]),
|
||||
)(HistoryCalendar));
|
||||
@ -30,6 +30,7 @@ import Notifications from './Notifications.async';
|
||||
import Errors from './Errors.async';
|
||||
import ForwardPicker from './ForwardPicker.async';
|
||||
import SafeLinkModal from './SafeLinkModal.async';
|
||||
import HistoryCalendar from './HistoryCalendar.async';
|
||||
|
||||
import './Main.scss';
|
||||
|
||||
@ -44,6 +45,7 @@ type StateProps = {
|
||||
hasErrors: boolean;
|
||||
audioMessage?: ApiMessage;
|
||||
safeLinkModalUrl?: string;
|
||||
isHistoryCalendarOpen: boolean;
|
||||
};
|
||||
|
||||
type DispatchProps = Pick<GlobalActions, 'loadAnimatedEmojis'>;
|
||||
@ -58,7 +60,6 @@ let DEBUG_isLogged = false;
|
||||
|
||||
const Main: FC<StateProps & DispatchProps> = ({
|
||||
lastSyncTime,
|
||||
loadAnimatedEmojis,
|
||||
isLeftColumnShown,
|
||||
isRightColumnShown,
|
||||
isMediaViewerOpen,
|
||||
@ -68,6 +69,8 @@ const Main: FC<StateProps & DispatchProps> = ({
|
||||
hasErrors,
|
||||
audioMessage,
|
||||
safeLinkModalUrl,
|
||||
isHistoryCalendarOpen,
|
||||
loadAnimatedEmojis,
|
||||
}) => {
|
||||
if (DEBUG && !DEBUG_isLogged) {
|
||||
DEBUG_isLogged = true;
|
||||
@ -171,6 +174,7 @@ const Main: FC<StateProps & DispatchProps> = ({
|
||||
<Errors isOpen={hasErrors} />
|
||||
{audioMessage && <AudioPlayer key={audioMessage.id} message={audioMessage} noUi />}
|
||||
<SafeLinkModal url={safeLinkModalUrl} />
|
||||
<HistoryCalendar isOpen={isHistoryCalendarOpen} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@ -206,6 +210,7 @@ export default memo(withGlobal(
|
||||
hasErrors: Boolean(global.errors.length),
|
||||
audioMessage,
|
||||
safeLinkModalUrl: global.safeLinkModalUrl,
|
||||
isHistoryCalendarOpen: Boolean(global.historyCalendarSelectedAt),
|
||||
};
|
||||
},
|
||||
(setGlobal, actions): DispatchProps => pick(actions, ['loadAnimatedEmojis']),
|
||||
|
||||
@ -186,11 +186,23 @@
|
||||
|
||||
body:not(.is-scrolling-messages) &.stuck {
|
||||
opacity: 0;
|
||||
|
||||
span {
|
||||
pointer-events: none;
|
||||
}
|
||||
}
|
||||
|
||||
body.animation-level-0 & {
|
||||
transition: none;
|
||||
}
|
||||
|
||||
&.interactive {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
span {
|
||||
pointer-events: auto;
|
||||
}
|
||||
}
|
||||
|
||||
&.scrolled .sticky-date {
|
||||
|
||||
@ -22,7 +22,8 @@ import {
|
||||
selectScrollOffset,
|
||||
selectThreadTopMessageId,
|
||||
selectFirstMessageId,
|
||||
selectScheduledMessages, selectCurrentMessageIds,
|
||||
selectScheduledMessages,
|
||||
selectCurrentMessageIds,
|
||||
} from '../../modules/selectors';
|
||||
import {
|
||||
getMessageOriginalId,
|
||||
@ -92,7 +93,7 @@ type StateProps = {
|
||||
};
|
||||
|
||||
type DispatchProps = Pick<GlobalActions, (
|
||||
'loadViewportMessages' | 'markMessageListRead' | 'markMessagesRead' | 'setScrollOffset'
|
||||
'loadViewportMessages' | 'markMessageListRead' | 'markMessagesRead' | 'setScrollOffset' | 'openHistoryCalendar'
|
||||
)>;
|
||||
|
||||
const BOTTOM_THRESHOLD = 100;
|
||||
@ -138,6 +139,7 @@ const MessageList: FC<OwnProps & StateProps & DispatchProps> = ({
|
||||
botDescription,
|
||||
threadTopMessageId,
|
||||
hasLinkedChat,
|
||||
openHistoryCalendar,
|
||||
}) => {
|
||||
// eslint-disable-next-line no-null/no-null
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
@ -557,9 +559,10 @@ const MessageList: FC<OwnProps & StateProps & DispatchProps> = ({
|
||||
type,
|
||||
threadTopMessageId,
|
||||
threadFirstMessageId,
|
||||
hasLinkedChat,
|
||||
Boolean(hasLinkedChat),
|
||||
messageGroups ? type === 'scheduled' : false,
|
||||
!messageGroups || !shouldAnimateAppearanceRef.current,
|
||||
openHistoryCalendar,
|
||||
)}
|
||||
</MessageScroll>
|
||||
) : (
|
||||
@ -580,11 +583,12 @@ function renderMessages(
|
||||
memoFirstUnreadIdRef: { current: number | undefined },
|
||||
threadId: number,
|
||||
type: MessageListType,
|
||||
threadTopMessageId?: number,
|
||||
threadFirstMessageId?: number,
|
||||
hasLinkedChat?: boolean,
|
||||
isSchedule = false,
|
||||
noAppearanceAnimation = false,
|
||||
threadTopMessageId: number | undefined,
|
||||
threadFirstMessageId: number | undefined,
|
||||
hasLinkedChat: boolean,
|
||||
isSchedule: boolean,
|
||||
noAppearanceAnimation: boolean,
|
||||
openHistoryCalendar: Function,
|
||||
) {
|
||||
const unreadDivider = (
|
||||
<div className={buildClassName(UNREAD_DIVIDER_CLASS, 'local-action-message')} key="unread-messages">
|
||||
@ -701,7 +705,11 @@ function renderMessages(
|
||||
key={dateGroup.datetime}
|
||||
teactFastList
|
||||
>
|
||||
<div className="sticky-date" key="date-header">
|
||||
<div
|
||||
className={buildClassName('sticky-date', !isSchedule && 'interactive')}
|
||||
key="date-header"
|
||||
onClick={!isSchedule ? () => openHistoryCalendar({ selectedAt: dateGroup.datetime }) : undefined}
|
||||
>
|
||||
<span dir="auto">
|
||||
{isSchedule && dateGroup.originalDate === SCHEDULED_WHEN_ONLINE && (
|
||||
lang('MessageScheduledUntilOnline')
|
||||
@ -785,5 +793,6 @@ export default memo(withGlobal<OwnProps>(
|
||||
'markMessageListRead',
|
||||
'markMessagesRead',
|
||||
'setScrollOffset',
|
||||
'openHistoryCalendar',
|
||||
]),
|
||||
)(MessageList));
|
||||
|
||||
@ -9,12 +9,10 @@ import { GlobalActions } from '../../global/types';
|
||||
import { debounce } from '../../util/schedulers';
|
||||
import { selectCurrentTextSearch, selectCurrentChat } from '../../modules/selectors';
|
||||
import { pick } from '../../util/iteratees';
|
||||
import useFlag from '../../hooks/useFlag';
|
||||
import useLang from '../../hooks/useLang';
|
||||
import { getDayStartAt } from '../../util/dateFormat';
|
||||
|
||||
import Button from '../ui/Button';
|
||||
import SearchInput from '../ui/SearchInput';
|
||||
import CalendarModal from '../common/CalendarModal';
|
||||
|
||||
import './MobileSearch.scss';
|
||||
|
||||
@ -28,10 +26,11 @@ type StateProps = {
|
||||
query?: string;
|
||||
totalCount?: number;
|
||||
foundIds?: number[];
|
||||
isHistoryCalendarOpen?: boolean;
|
||||
};
|
||||
|
||||
type DispatchProps = Pick<GlobalActions, (
|
||||
'setLocalTextSearchQuery' | 'searchTextMessagesLocal' | 'closeLocalTextSearch' | 'searchMessagesByDate' |
|
||||
'setLocalTextSearchQuery' | 'searchTextMessagesLocal' | 'closeLocalTextSearch' | 'openHistoryCalendar' |
|
||||
'focusMessage'
|
||||
)>;
|
||||
|
||||
@ -43,16 +42,16 @@ const MobileSearchFooter: FC<StateProps & DispatchProps> = ({
|
||||
query,
|
||||
totalCount,
|
||||
foundIds,
|
||||
isHistoryCalendarOpen,
|
||||
setLocalTextSearchQuery,
|
||||
searchTextMessagesLocal,
|
||||
focusMessage,
|
||||
closeLocalTextSearch,
|
||||
searchMessagesByDate,
|
||||
openHistoryCalendar,
|
||||
}) => {
|
||||
// eslint-disable-next-line no-null/no-null
|
||||
const inputRef = useRef<HTMLInputElement>(null);
|
||||
const [focusedIndex, setFocusedIndex] = useState(0);
|
||||
const [isCalendarOpen, openCalendar, closeCalendar] = useFlag();
|
||||
|
||||
// Fix for iOS keyboard
|
||||
useEffect(() => {
|
||||
@ -113,7 +112,7 @@ const MobileSearchFooter: FC<StateProps & DispatchProps> = ({
|
||||
useLayoutEffect(() => {
|
||||
const searchInput = document.querySelector<HTMLInputElement>('#MobileSearch input')!;
|
||||
searchInput.blur();
|
||||
}, [isCalendarOpen]);
|
||||
}, [isHistoryCalendarOpen]);
|
||||
|
||||
const handleMessageSearchQueryChange = useCallback((newQuery: string) => {
|
||||
setLocalTextSearchQuery({ query: newQuery });
|
||||
@ -123,11 +122,6 @@ const MobileSearchFooter: FC<StateProps & DispatchProps> = ({
|
||||
}
|
||||
}, [searchTextMessagesLocal, setLocalTextSearchQuery]);
|
||||
|
||||
const handleJumpToDate = useCallback((date: Date) => {
|
||||
searchMessagesByDate({ timestamp: date.valueOf() / 1000 });
|
||||
closeCalendar();
|
||||
}, [closeCalendar, searchMessagesByDate]);
|
||||
|
||||
const handleUp = useCallback(() => {
|
||||
if (chat && foundIds) {
|
||||
const newFocusIndex = focusedIndex + 1;
|
||||
@ -144,8 +138,6 @@ const MobileSearchFooter: FC<StateProps & DispatchProps> = ({
|
||||
}
|
||||
}, [chat, focusedIndex, focusMessage, foundIds]);
|
||||
|
||||
const lang = useLang();
|
||||
|
||||
return (
|
||||
<div id="MobileSearch" className={isActive ? 'active' : ''}>
|
||||
<div className="header">
|
||||
@ -178,7 +170,7 @@ const MobileSearchFooter: FC<StateProps & DispatchProps> = ({
|
||||
round
|
||||
size="smaller"
|
||||
color="translucent"
|
||||
onClick={openCalendar}
|
||||
onClick={() => openHistoryCalendar({ selectedAt: getDayStartAt(Date.now()) })}
|
||||
ariaLabel="Search messages by date"
|
||||
>
|
||||
<i className="icon-calendar" />
|
||||
@ -204,13 +196,6 @@ const MobileSearchFooter: FC<StateProps & DispatchProps> = ({
|
||||
<i className="icon-down" />
|
||||
</Button>
|
||||
</div>
|
||||
<CalendarModal
|
||||
isOpen={isCalendarOpen}
|
||||
isPastMode
|
||||
submitButtonLabel={lang('JumpToDate')}
|
||||
onClose={closeCalendar}
|
||||
onSubmit={handleJumpToDate}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@ -230,6 +215,7 @@ export default memo(withGlobal<OwnProps>(
|
||||
query,
|
||||
totalCount,
|
||||
foundIds,
|
||||
isHistoryCalendarOpen: Boolean(global.historyCalendarSelectedAt),
|
||||
};
|
||||
},
|
||||
(setGlobal, actions): DispatchProps => pick(actions, [
|
||||
@ -237,6 +223,6 @@ export default memo(withGlobal<OwnProps>(
|
||||
'searchTextMessagesLocal',
|
||||
'focusMessage',
|
||||
'closeLocalTextSearch',
|
||||
'searchMessagesByDate',
|
||||
'openHistoryCalendar',
|
||||
]),
|
||||
)(MobileSearchFooter));
|
||||
|
||||
@ -19,14 +19,13 @@ import {
|
||||
} from '../../modules/selectors';
|
||||
import { isChatAdmin, isChatChannel, isChatPrivate } from '../../modules/helpers';
|
||||
import useCurrentOrPrev from '../../hooks/useCurrentOrPrev';
|
||||
import useFlag from '../../hooks/useFlag';
|
||||
import useLang from '../../hooks/useLang';
|
||||
|
||||
import CalendarModal from '../common/CalendarModal.async';
|
||||
import SearchInput from '../ui/SearchInput';
|
||||
import Button from '../ui/Button';
|
||||
import Transition from '../ui/Transition';
|
||||
import './RightHeader.scss';
|
||||
import { getDayStartAt } from '../../util/dateFormat';
|
||||
|
||||
type OwnProps = {
|
||||
chatId?: number;
|
||||
@ -52,7 +51,7 @@ type StateProps = {
|
||||
|
||||
type DispatchProps = Pick<GlobalActions, (
|
||||
'setLocalTextSearchQuery' | 'setStickerSearchQuery' | 'setGifSearchQuery' |
|
||||
'searchTextMessagesLocal' | 'toggleManagement' | 'searchMessagesByDate'
|
||||
'searchTextMessagesLocal' | 'toggleManagement' | 'openHistoryCalendar'
|
||||
)>;
|
||||
|
||||
const COLUMN_CLOSE_DELAY_MS = 300;
|
||||
@ -102,13 +101,11 @@ const RightHeader: FC<OwnProps & StateProps & DispatchProps> = ({
|
||||
setGifSearchQuery,
|
||||
searchTextMessagesLocal,
|
||||
toggleManagement,
|
||||
searchMessagesByDate,
|
||||
openHistoryCalendar,
|
||||
}) => {
|
||||
// eslint-disable-next-line no-null/no-null
|
||||
const backButtonRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
const [isCalendarOpen, openCalendar, closeCalendar] = useFlag();
|
||||
|
||||
const handleMessageSearchQueryChange = useCallback((query: string) => {
|
||||
setLocalTextSearchQuery({ query });
|
||||
|
||||
@ -117,11 +114,6 @@ const RightHeader: FC<OwnProps & StateProps & DispatchProps> = ({
|
||||
}
|
||||
}, [searchTextMessagesLocal, setLocalTextSearchQuery]);
|
||||
|
||||
const handleJumpToDate = useCallback((date: Date) => {
|
||||
searchMessagesByDate({ timestamp: date.valueOf() / 1000 });
|
||||
closeCalendar();
|
||||
}, [closeCalendar, searchMessagesByDate]);
|
||||
|
||||
const handleStickerSearchQueryChange = useCallback((query: string) => {
|
||||
setStickerSearchQuery({ query });
|
||||
}, [setStickerSearchQuery]);
|
||||
@ -205,7 +197,7 @@ const RightHeader: FC<OwnProps & StateProps & DispatchProps> = ({
|
||||
round
|
||||
size="smaller"
|
||||
color="translucent"
|
||||
onClick={openCalendar}
|
||||
onClick={() => openHistoryCalendar({ selectedAt: getDayStartAt(Date.now()) })}
|
||||
ariaLabel="Search messages by date"
|
||||
>
|
||||
<i className="icon-calendar" />
|
||||
@ -312,15 +304,6 @@ const RightHeader: FC<OwnProps & StateProps & DispatchProps> = ({
|
||||
>
|
||||
{renderHeaderContent}
|
||||
</Transition>
|
||||
{!IS_MOBILE_SCREEN && (
|
||||
<CalendarModal
|
||||
isOpen={isCalendarOpen}
|
||||
isPastMode
|
||||
submitButtonLabel={lang('JumpToDate')}
|
||||
onClose={closeCalendar}
|
||||
onSubmit={handleJumpToDate}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@ -356,6 +339,6 @@ export default memo(withGlobal<OwnProps>(
|
||||
'setGifSearchQuery',
|
||||
'searchTextMessagesLocal',
|
||||
'toggleManagement',
|
||||
'searchMessagesByDate',
|
||||
'openHistoryCalendar',
|
||||
]),
|
||||
)(RightHeader));
|
||||
|
||||
@ -389,6 +389,7 @@ export type GlobalState = {
|
||||
};
|
||||
|
||||
safeLinkModalUrl?: string;
|
||||
historyCalendarSelectedAt?: number;
|
||||
};
|
||||
|
||||
export type ActionTypes = (
|
||||
@ -397,7 +398,7 @@ export type ActionTypes = (
|
||||
'showNotification' | 'dismissNotification' | 'showError' | 'dismissError' |
|
||||
// ui
|
||||
'toggleChatInfo' | 'setIsUiReady' | 'addRecentEmoji' | 'addRecentSticker' | 'toggleLeftColumn' |
|
||||
'toggleSafeLinkModal' |
|
||||
'toggleSafeLinkModal' | 'openHistoryCalendar' | 'closeHistoryCalendar' |
|
||||
// auth
|
||||
'setAuthPhoneNumber' | 'setAuthCode' | 'setAuthPassword' | 'signUp' | 'returnToAuthPhoneNumber' | 'signOut' |
|
||||
'setAuthRememberMe' | 'clearAuthError' | 'uploadProfilePhoto' | 'gotToAuthQrCode' | 'clearCache' |
|
||||
|
||||
@ -199,3 +199,19 @@ addReducer('toggleSafeLinkModal', (global, actions, payload) => {
|
||||
safeLinkModalUrl,
|
||||
};
|
||||
});
|
||||
|
||||
addReducer('openHistoryCalendar', (global, actions, payload) => {
|
||||
const { selectedAt } = payload;
|
||||
|
||||
return {
|
||||
...global,
|
||||
historyCalendarSelectedAt: selectedAt,
|
||||
};
|
||||
});
|
||||
|
||||
addReducer('closeHistoryCalendar', (global) => {
|
||||
return {
|
||||
...global,
|
||||
historyCalendarSelectedAt: undefined,
|
||||
};
|
||||
});
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user