Calendar: Fix re-scheduling to the past; Composer: Add schedule icon (#1653)
This commit is contained in:
parent
417745a998
commit
3a282b8086
@ -3,7 +3,7 @@ import React, {
|
||||
} from '../../lib/teact/teact';
|
||||
|
||||
import buildClassName from '../../util/buildClassName';
|
||||
import { formatTime, formatDateToString } from '../../util/dateFormat';
|
||||
import { formatTime, formatDateToString, getDayStart } from '../../util/dateFormat';
|
||||
import useLang, { LangFn } from '../../hooks/useLang';
|
||||
import usePrevious from '../../hooks/usePrevious';
|
||||
import useFlag from '../../hooks/useFlag';
|
||||
@ -14,9 +14,11 @@ import Button from '../ui/Button';
|
||||
import './CalendarModal.scss';
|
||||
|
||||
const MAX_SAFE_DATE = 2147483647 * 1000; // API has int for dates
|
||||
const MIN_SAFE_DATE = 0;
|
||||
|
||||
export type OwnProps = {
|
||||
selectedAt?: number;
|
||||
minAt?: number;
|
||||
maxAt?: number;
|
||||
isFutureMode?: boolean;
|
||||
isPastMode?: boolean;
|
||||
@ -41,6 +43,7 @@ const WEEKDAY_LETTERS = [
|
||||
|
||||
const CalendarModal: FC<OwnProps> = ({
|
||||
selectedAt,
|
||||
minAt,
|
||||
maxAt,
|
||||
isFutureMode,
|
||||
isPastMode,
|
||||
@ -54,20 +57,29 @@ const CalendarModal: FC<OwnProps> = ({
|
||||
}) => {
|
||||
const lang = useLang();
|
||||
const now = new Date();
|
||||
const defaultSelectedDate = useMemo(() => (selectedAt ? new Date(selectedAt) : new Date()), [selectedAt]);
|
||||
const maxDate = new Date(Math.min(maxAt || MAX_SAFE_DATE, MAX_SAFE_DATE));
|
||||
|
||||
const minDate = useMemo(() => {
|
||||
if (isFutureMode && !minAt) return new Date();
|
||||
return new Date(Math.max(minAt || MIN_SAFE_DATE, MIN_SAFE_DATE));
|
||||
}, [isFutureMode, minAt]);
|
||||
const maxDate = useMemo(() => {
|
||||
if (isPastMode && !maxAt) return new Date();
|
||||
return new Date(Math.min(maxAt || MAX_SAFE_DATE, MAX_SAFE_DATE));
|
||||
}, [isPastMode, maxAt]);
|
||||
|
||||
const passedSelectedDate = useMemo(() => (selectedAt ? new Date(selectedAt) : new Date()), [selectedAt]);
|
||||
const prevIsOpen = usePrevious(isOpen);
|
||||
const [isTimeInputFocused, markTimeInputAsFocused, unmarkTimeInputAsFocused] = useFlag(false);
|
||||
|
||||
const [selectedDate, setSelectedDate] = useState<Date>(defaultSelectedDate);
|
||||
const [selectedDate, setSelectedDate] = useState<Date>(passedSelectedDate);
|
||||
const [currentMonthAndYear, setCurrentMonthAndYear] = useState<Date>(
|
||||
new Date(selectedDate.getFullYear(), selectedDate.getMonth(), 1),
|
||||
);
|
||||
const [selectedHours, setSelectedHours] = useState<string>(
|
||||
formatInputTime(defaultSelectedDate.getHours()),
|
||||
formatInputTime(passedSelectedDate.getHours()),
|
||||
);
|
||||
const [selectedMinutes, setSelectedMinutes] = useState<string>(
|
||||
formatInputTime(defaultSelectedDate.getMinutes()),
|
||||
formatInputTime(passedSelectedDate.getMinutes()),
|
||||
);
|
||||
|
||||
const selectedDay = formatDay(selectedDate.getFullYear(), selectedDate.getMonth(), selectedDate.getDate());
|
||||
@ -76,22 +88,39 @@ const CalendarModal: FC<OwnProps> = ({
|
||||
|
||||
useEffect(() => {
|
||||
if (!prevIsOpen && isOpen) {
|
||||
setSelectedDate(defaultSelectedDate);
|
||||
setCurrentMonthAndYear(new Date(defaultSelectedDate.getFullYear(), defaultSelectedDate.getMonth(), 1));
|
||||
setSelectedDate(passedSelectedDate);
|
||||
setCurrentMonthAndYear(new Date(passedSelectedDate.getFullYear(), passedSelectedDate.getMonth(), 1));
|
||||
if (withTimePicker) {
|
||||
setSelectedHours(defaultSelectedDate.getHours().toString());
|
||||
setSelectedMinutes(defaultSelectedDate.getMinutes().toString());
|
||||
setSelectedHours(formatInputTime(passedSelectedDate.getHours()));
|
||||
setSelectedMinutes(formatInputTime(passedSelectedDate.getMinutes()));
|
||||
}
|
||||
}
|
||||
}, [defaultSelectedDate, isOpen, prevIsOpen, withTimePicker]);
|
||||
}, [passedSelectedDate, isOpen, prevIsOpen, withTimePicker]);
|
||||
|
||||
useEffect(() => {
|
||||
if (isFutureMode && !isTimeInputFocused && selectedDate.getTime() < defaultSelectedDate.getTime()) {
|
||||
setSelectedDate(defaultSelectedDate);
|
||||
setSelectedHours(formatInputTime(defaultSelectedDate.getHours()));
|
||||
setSelectedMinutes(formatInputTime(defaultSelectedDate.getMinutes()));
|
||||
if (isFutureMode && !isTimeInputFocused && selectedDate.getTime() < minDate.getTime()) {
|
||||
setSelectedDate(minDate);
|
||||
setSelectedHours(formatInputTime(minDate.getHours()));
|
||||
setSelectedMinutes(formatInputTime(minDate.getMinutes()));
|
||||
}
|
||||
}, [defaultSelectedDate, isTimeInputFocused, isFutureMode, selectedDate]);
|
||||
}, [isFutureMode, isTimeInputFocused, minDate, selectedDate]);
|
||||
|
||||
useEffect(() => {
|
||||
if (isPastMode && !isTimeInputFocused && selectedDate.getTime() > maxDate.getTime()) {
|
||||
setSelectedDate(maxDate);
|
||||
setSelectedHours(formatInputTime(maxDate.getHours()));
|
||||
setSelectedMinutes(formatInputTime(maxDate.getMinutes()));
|
||||
}
|
||||
}, [isFutureMode, isPastMode, isTimeInputFocused, maxDate, minDate, selectedDate]);
|
||||
|
||||
useEffect(() => {
|
||||
if (selectedAt) {
|
||||
const newSelectedDate = new Date(selectedAt);
|
||||
setSelectedDate(newSelectedDate);
|
||||
setSelectedHours(formatInputTime(newSelectedDate.getHours()));
|
||||
setSelectedMinutes(formatInputTime(newSelectedDate.getMinutes()));
|
||||
}
|
||||
}, [selectedAt]);
|
||||
|
||||
const shouldDisableNextMonth = (isPastMode && currentYear >= now.getFullYear() && currentMonth >= now.getMonth())
|
||||
|| (maxDate && currentYear >= maxDate.getFullYear() && currentMonth >= maxDate.getMonth());
|
||||
@ -261,7 +290,7 @@ const CalendarModal: FC<OwnProps> = ({
|
||||
className={buildClassName(
|
||||
'day-button',
|
||||
isDisabledDay(
|
||||
currentYear, currentMonth, gridDate, isFutureMode ? now : undefined, isPastMode ? now : maxDate,
|
||||
currentYear, currentMonth, gridDate, minDate, maxDate,
|
||||
)
|
||||
? 'disabled'
|
||||
: `${gridDate ? 'clickable' : ''}`,
|
||||
@ -328,9 +357,9 @@ function buildCalendarGrid(year: number, month: number) {
|
||||
}
|
||||
|
||||
function isDisabledDay(year: number, month: number, day: number, minDate?: Date, maxDate?: Date) {
|
||||
const selectedDay = new Date(year, month, day, 0, 0, 0, 0);
|
||||
const fixedMinDate = minDate && new Date(minDate.getFullYear(), minDate.getMonth(), minDate.getDate(), 0, 0, 0, 0);
|
||||
const fixedMaxDate = maxDate && new Date(maxDate.getFullYear(), maxDate.getMonth(), maxDate.getDate(), 0, 0, 0, 0);
|
||||
const selectedDay = new Date(year, month, day);
|
||||
const fixedMinDate = minDate && getDayStart(minDate);
|
||||
const fixedMaxDate = maxDate && getDayStart(maxDate);
|
||||
|
||||
if (fixedMaxDate && selectedDay > fixedMaxDate) {
|
||||
return true;
|
||||
|
||||
@ -59,6 +59,7 @@
|
||||
}
|
||||
|
||||
.icon-send,
|
||||
.icon-schedule,
|
||||
.icon-microphone-alt,
|
||||
.icon-check {
|
||||
position: absolute;
|
||||
@ -66,6 +67,7 @@
|
||||
|
||||
&:not(:active):not(:focus):not(:hover) {
|
||||
.icon-send,
|
||||
.icon-schedule,
|
||||
.icon-check {
|
||||
color: var(--color-primary);
|
||||
}
|
||||
@ -92,7 +94,20 @@
|
||||
}
|
||||
|
||||
.icon-microphone-alt,
|
||||
.icon-check {
|
||||
.icon-check,
|
||||
.icon-schedule {
|
||||
animation: hide-icon .4s forwards ease-out;
|
||||
}
|
||||
}
|
||||
|
||||
&.schedule {
|
||||
.icon-schedule {
|
||||
animation: grow-icon .4s ease-out;
|
||||
}
|
||||
|
||||
.icon-microphone-alt,
|
||||
.icon-check,
|
||||
.icon-send {
|
||||
animation: hide-icon .4s forwards ease-out;
|
||||
}
|
||||
}
|
||||
@ -103,20 +118,22 @@
|
||||
}
|
||||
|
||||
.icon-send,
|
||||
.icon-check {
|
||||
.icon-check,
|
||||
.icon-schedule {
|
||||
animation: hide-icon .4s forwards ease-out;
|
||||
}
|
||||
}
|
||||
|
||||
&.edit {
|
||||
.icon-send,
|
||||
.icon-microphone-alt {
|
||||
animation: hide-icon .4s forwards ease-out;
|
||||
}
|
||||
|
||||
.icon-check {
|
||||
animation: grow-icon .4s ease-out;
|
||||
}
|
||||
|
||||
.icon-send,
|
||||
.icon-microphone-alt,
|
||||
.icon-schedule {
|
||||
animation: hide-icon .4s forwards ease-out;
|
||||
}
|
||||
}
|
||||
|
||||
&.not-ready > i {
|
||||
@ -124,7 +141,7 @@
|
||||
}
|
||||
|
||||
body.animation-level-0 &, body.animation-level-1 & {
|
||||
.icon-send, .icon-microphone-alt, .icon-check {
|
||||
.icon-send, .icon-microphone-alt, .icon-check, .icon-schedule {
|
||||
animation-duration: 0ms !important;
|
||||
}
|
||||
}
|
||||
|
||||
@ -154,6 +154,7 @@ enum MainButtonState {
|
||||
Send = 'send',
|
||||
Record = 'record',
|
||||
Edit = 'edit',
|
||||
Schedule = 'schedule',
|
||||
}
|
||||
|
||||
const VOICE_RECORDING_FILENAME = 'wonderful-voice-message.ogg';
|
||||
@ -312,10 +313,9 @@ const Composer: FC<OwnProps & StateProps> = ({
|
||||
}
|
||||
}, [activeVoiceRecording, sendMessageAction]);
|
||||
|
||||
const mainButtonState = editingMessage
|
||||
? MainButtonState.Edit
|
||||
: !IS_VOICE_RECORDING_SUPPORTED || activeVoiceRecording || (html && !attachments.length) || isForwarding
|
||||
? MainButtonState.Send
|
||||
const mainButtonState = editingMessage ? MainButtonState.Edit
|
||||
: (!IS_VOICE_RECORDING_SUPPORTED || activeVoiceRecording || (html && !attachments.length) || isForwarding)
|
||||
? (shouldSchedule ? MainButtonState.Schedule : MainButtonState.Send)
|
||||
: MainButtonState.Record;
|
||||
const canShowCustomSendMenu = !shouldSchedule;
|
||||
|
||||
@ -769,14 +769,7 @@ const Composer: FC<OwnProps & StateProps> = ({
|
||||
const mainButtonHandler = useCallback(() => {
|
||||
switch (mainButtonState) {
|
||||
case MainButtonState.Send:
|
||||
if (shouldSchedule) {
|
||||
if (activeVoiceRecording) {
|
||||
pauseRecordingVoice();
|
||||
}
|
||||
openCalendar();
|
||||
} else {
|
||||
void handleSend();
|
||||
}
|
||||
handleSend();
|
||||
break;
|
||||
case MainButtonState.Record:
|
||||
void startRecordingVoice();
|
||||
@ -784,12 +777,18 @@ const Composer: FC<OwnProps & StateProps> = ({
|
||||
case MainButtonState.Edit:
|
||||
handleEditComplete();
|
||||
break;
|
||||
case MainButtonState.Schedule:
|
||||
if (activeVoiceRecording) {
|
||||
pauseRecordingVoice();
|
||||
}
|
||||
openCalendar();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}, [
|
||||
mainButtonState, shouldSchedule, startRecordingVoice, handleEditComplete,
|
||||
activeVoiceRecording, openCalendar, pauseRecordingVoice, handleSend,
|
||||
mainButtonState, handleSend, startRecordingVoice, handleEditComplete,
|
||||
activeVoiceRecording, openCalendar, pauseRecordingVoice,
|
||||
]);
|
||||
|
||||
const areVoiceMessagesNotAllowed = mainButtonState === MainButtonState.Record
|
||||
@ -832,7 +831,8 @@ const Composer: FC<OwnProps & StateProps> = ({
|
||||
|
||||
const onSend = mainButtonState === MainButtonState.Edit
|
||||
? handleEditComplete
|
||||
: (shouldSchedule ? openCalendar : handleSend);
|
||||
: mainButtonState === MainButtonState.Schedule ? openCalendar
|
||||
: handleSend;
|
||||
|
||||
return (
|
||||
<div className={className}>
|
||||
@ -1103,6 +1103,7 @@ const Composer: FC<OwnProps & StateProps> = ({
|
||||
}
|
||||
>
|
||||
<i className="icon-send" />
|
||||
<i className="icon-schedule" />
|
||||
<i className="icon-microphone-alt" />
|
||||
<i className="icon-check" />
|
||||
</Button>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user