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