-
Notifications
You must be signed in to change notification settings - Fork 909
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Some UX improvements #2425
Some UX improvements #2425
Changes from 8 commits
42aacdc
9667ae2
b91bda9
1288b31
39c565f
5ab358e
9f1a3ae
6628137
14362ed
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,7 +1,8 @@ | ||
import React from 'react'; | ||
import { localized, UndoRedoStore, SyncbackMetadataTask } from 'mailspring-exports'; | ||
import { localized, UndoRedoStore, SyncbackMetadataTask, DatabaseStore, Message, Actions } from 'mailspring-exports'; | ||
import { RetinaImg } from 'mailspring-component-kit'; | ||
import { CSSTransitionGroup } from 'react-transition-group'; | ||
import { PLUGIN_ID } from '../../../internal_packages/send-later/lib/send-later-constants'; | ||
|
||
function isUndoSend(block) { | ||
return ( | ||
|
@@ -11,6 +12,29 @@ function isUndoSend(block) { | |
); | ||
} | ||
|
||
async function sendMessageNow(block) { | ||
if (isUndoSend(block)) { | ||
const message = await DatabaseStore.find<Message>(Message, (block.tasks[0] as SyncbackMetadataTask).modelId), | ||
newExpiry = Math.floor(Date.now() / 1000); | ||
|
||
Actions.queueTask( | ||
SyncbackMetadataTask.forSaving({ | ||
model: message, | ||
pluginId: PLUGIN_ID, | ||
value: { | ||
expiration: newExpiry, | ||
}, | ||
}) | ||
); | ||
|
||
block.tasks[0].value.expiration = newExpiry; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This looks good, cool that updating the metadata actually causes it to send immediately. I'll try this a couple times once we merge just to stress test it a bit, but it seems safe to me. |
||
|
||
return true; | ||
} | ||
|
||
return false; | ||
} | ||
|
||
function getUndoSendExpiration(block) { | ||
return block.tasks[0].value.expiration * 1000; | ||
} | ||
|
@@ -75,6 +99,10 @@ const UndoSendContent = ({ block, onMouseEnter, onMouseLeave }) => { | |
<div className="content" onMouseEnter={onMouseEnter} onMouseLeave={onMouseLeave}> | ||
<Countdown expiration={getUndoSendExpiration(block)} /> | ||
<div className="message">{localized('Sending soon...')}</div> | ||
<div className="action" onClick={async () => { await sendMessageNow(block) && onMouseLeave() }}> | ||
<RetinaImg name="icon-composer-send.png" mode={RetinaImg.Mode.ContentIsMask} /> | ||
<span className="send-action-text">{localized('Send now instead')}</span> | ||
</div> | ||
<div className="action" onClick={() => AppEnv.commands.dispatch('core:undo')}> | ||
<RetinaImg name="undo-icon@2x.png" mode={RetinaImg.Mode.ContentIsMask} /> | ||
<span className="undo-action-text">{localized('Undo')}</span> | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -389,27 +389,40 @@ const DateUtils = { | |
* | ||
* The returned date/time format depends on how long ago the timestamp is. | ||
*/ | ||
shortTimeString(datetime) { | ||
shortTimeString(datetime: Date) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This all looks great, thank you for updating it to use the new browser standard APIs! |
||
const now = moment(); | ||
const diff = now.diff(datetime, 'days', true); | ||
const isSameDay = now.isSame(datetime, 'days'); | ||
let format = null; | ||
const opts: Intl.DateTimeFormatOptions = { | ||
hour12: !AppEnv.config.get('core.workspace.use24HourClock'), | ||
}; | ||
|
||
if (diff <= 1 && isSameDay) { | ||
// Time if less than 1 day old | ||
format = DateUtils.getTimeFormat(null); | ||
} else if (diff < 2 && !isSameDay) { | ||
// Month and day with time if up to 2 days ago | ||
format = `MMM D, ${DateUtils.getTimeFormat(null)}`; | ||
} else if (diff >= 2 && diff < 365) { | ||
// Month and day up to 1 year old | ||
format = 'MMM D'; | ||
opts.hour = 'numeric'; | ||
opts.minute = '2-digit'; | ||
} else if (diff < 5 && !isSameDay) { | ||
// Weekday with time if up to 2 days ago | ||
//opts.month = 'short'; | ||
//opts.day = 'numeric'; | ||
opts.weekday = 'short'; | ||
opts.hour = 'numeric'; | ||
opts.minute = '2-digit'; | ||
} else { | ||
// Month, day and year if over a year old | ||
format = 'MMM D YYYY'; | ||
if (diff < 365) { | ||
// Month and day up to 1 year old | ||
opts.month = 'short'; | ||
opts.day = 'numeric'; | ||
} else { | ||
// Month, day and year if over a year old | ||
opts.year = 'numeric'; | ||
opts.month = 'short'; | ||
opts.day = 'numeric'; | ||
} | ||
return datetime.toLocaleDateString(navigator.language, opts); | ||
} | ||
|
||
return moment(datetime).format(format); | ||
return datetime.toLocaleTimeString(navigator.language, opts); | ||
}, | ||
|
||
/** | ||
|
@@ -418,11 +431,14 @@ const DateUtils = { | |
* @param {Date} datetime - Timestamp | ||
* @return {String} Formated date/time | ||
*/ | ||
mediumTimeString(datetime) { | ||
let format = 'MMMM D, YYYY, '; | ||
format += DateUtils.getTimeFormat({ seconds: false, upperCase: true, timeZone: false }); | ||
|
||
return moment(datetime).format(format); | ||
mediumTimeString(datetime: Date) { | ||
return datetime.toLocaleTimeString(navigator.language, { | ||
hour12: !AppEnv.config.get('core.workspace.use24HourClock'), | ||
year: 'numeric', | ||
month: 'long', | ||
day: 'numeric', | ||
second: undefined, | ||
}); | ||
}, | ||
|
||
/** | ||
|
@@ -431,13 +447,20 @@ const DateUtils = { | |
* @param {Date} datetime - Timestamp | ||
* @return {String} Formated date/time | ||
*/ | ||
fullTimeString(datetime) { | ||
let format = 'dddd, MMMM Do YYYY, '; | ||
format += DateUtils.getTimeFormat({ seconds: true, upperCase: true, timeZone: true }); | ||
|
||
return moment(datetime) | ||
.tz(tz) | ||
.format(format); | ||
fullTimeString(datetime: Date) { | ||
// ISSUE: this does drop ordinal. There is this: | ||
// -> new Intl.PluralRules(LOCALE, { type: "ordinal" }).select(dateTime.getDay()) | ||
// which may work with the below regex, though localisation is required | ||
// `(?<!\d)${dateTime.getDay()}(?!\d)` replace `$1${localise(ordinal)}` | ||
|
||
return datetime.toLocaleTimeString(navigator.language, { | ||
hour12: !AppEnv.config.get('core.workspace.use24HourClock'), | ||
year: 'numeric', | ||
month: 'long', | ||
day: 'numeric', | ||
weekday: 'long', | ||
second: undefined, | ||
}); | ||
}, | ||
}; | ||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a super minor nit, but I think this line may need a
key="contact-more"
prop to silence a React warning because it's rendered out of an array.