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
✨ [bento][amp-date-countdown] Initial Preact component for amp-date-countdown #30092
Conversation
src/preact/context.js
Outdated
@@ -59,3 +59,11 @@ export function WithAmpContext(props) { | |||
|
|||
return <AmpContext.Provider children={props['children']} value={current} />; | |||
} | |||
|
|||
/** | |||
* |
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.
Might need some help here with typing.
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 for now a JsonObject
. Not for much longer I think, but still the case. I'll ping you when this changes if you haven't yet merged this pull request.
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.
Here it is: #30102
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.
Thanks Dima sounds good!
if (input < -9 || input > 9) { | ||
return String(input); | ||
} else if (input >= -9 && input < 0) { | ||
return '-0' + Math.abs(input); |
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.
Nit:
return '-0' + Math.abs(input); | |
return '-0' + (-input); |
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.
Sounds good updated!
/** | ||
* @param {?string} message | ||
*/ | ||
function throwWarning(message) { |
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.
Nit: Let's rename this, since it doesn't actually throw.
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.
Sounds good updated to displayWarning
|
||
useEffect(() => { | ||
const interval = setInterval(() => { | ||
const newTimeLeft = new Date(epoch) - new Date(); |
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.
new Date(epoch)
is invariant to the interval loop, so let's move it out of setInterval
callback. Also if you switch to using Date.now()
you can avoid extra allocations.
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.
Thanks, moved new Date(epoch)
out of the interval loop and changed epoch
variable to be it's Date formatted version.
Also switched to using Date.now()
useEffect(() => { | ||
const interval = setInterval(() => { | ||
const newTimeLeft = new Date(epoch) - new Date(); | ||
setTimeLeft(() => newTimeLeft); |
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.
Why callback setter?
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.
Did not realize the appropriate syntax for this. Have updated.
const [timeLeft, setTimeLeft] = useState(new Date(epoch) - new Date()); | ||
|
||
useEffect(() => { | ||
const interval = setInterval(() => { |
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.
We have to call setInterval
and clearInterval
from an actual window here. Unlike a one-off setTimeout
or open
, the risk of leaking from setInterval
is very real. So, please add a ref
to the root element and use it to resolve window (win = element.ownerDocument.defautlView
) and use that window to call set/clearInterval
.
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.
Sounds good, updated!
return () => clearInterval(interval); | ||
}, [playable]); | ||
|
||
const data = Object.assign( |
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.
Let's do {...getYDMDMDJMD(), ...getLocaleWord()}
.
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.
Sounds good updated!
* @return {Object} | ||
*/ | ||
function getYDHMSFromMs(ms, biggestUnit) { | ||
/** @enum {number} */ |
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 not a enum since this map is accessed by a variable key. This is Object<string, number>
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.
Thanks, updated in the hoisted const.
* days, hours, minutes, etc. and returns formatted strings in an object. | ||
* @param {number} ms | ||
* @param {string} biggestUnit | ||
* @return {Object} |
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 seems like a JsonObject
as a result.
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.
Updated!
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.
Not seeing this change. Did you push?
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.
I think I missed it, it should be included in the latest revision. Thanks!
|
||
/** | ||
* @typedef {{ | ||
* endDate (string|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.
colons missing
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.
Thanks, updated!
src/preact/context.js
Outdated
@@ -59,3 +59,11 @@ export function WithAmpContext(props) { | |||
|
|||
return <AmpContext.Provider children={props['children']} value={current} />; | |||
} | |||
|
|||
/** | |||
* |
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 for now a JsonObject
. Not for much longer I think, but still the case. I'll ping you when this changes if you haven't yet merged this pull request.
Thanks for the review @dvoytenko @jridgewell . New revision is up with changes addressed. Key highlights:
May have a few more updates / merge conflict resolutions after #30102 is merged. |
@@ -0,0 +1,52 @@ | |||
/** |
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.
Let's rename this file to just messages.js
.
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.
Sounds good updated!
* Strings representing years, minutes, etc. in various locales | ||
* @typedef {Array<string>} | ||
*/ | ||
let DateCountdownLocaleDef; |
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.
No need for this type def. We just should use !Array<string>
inline.
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.
Updated!
src/preact/context.js
Outdated
/** | ||
* @return {JsonObject} | ||
*/ | ||
export function useAmpContext() { |
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.
Rebase. This should no longer be needed.
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.
Thanks updated!
...rest | ||
}) { | ||
useResourcesNotify(); | ||
const {'playable': playable} = useAmpContext(); |
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.
Should no longer need quotes. Rebase and go back to const {playable} = useAmpContext()
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.
Thanks, updated!
* days, hours, minutes, etc. and returns formatted strings in an object. | ||
* @param {number} ms | ||
* @param {string} biggestUnit | ||
* @return {Object} |
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.
Not seeing this change. Did you push?
} | ||
const win = rootRef.current.ownerDocument.defaultView; | ||
const interval = win.setInterval(() => { | ||
const newTimeLeft = epoch - Date.now() + DELAY; |
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.
what is the + DELAY
for?
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.
Since the setInterval
's callback is not being called until 1000ms (1 delay) from now, setting the correct time to be displayed at that time. So it will start with the initial value being displayed, and calculate the new time to be 1000ms less than the initial value, and will display that in 1000ms.
const rootRef = useRef(null); | ||
|
||
useEffect(() => { | ||
if (!playable) { |
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.
Should we also early-return if the rootRef
isn't set yet? e.g.:
if (!playable || ! rootRef.current) { return }
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.
Thanks, updated!
epoch = timestampSeconds * 1000; | ||
} | ||
|
||
if (epoch === 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.
What is the expected behavior when multiple are provided? e.g. both endDate
and timeleftMs
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.
In the current 0.1 implementation, it checks for the first one that is available in the order endDate
, timeleftMs
, timestampMs
, and timestampeSeconds
similar to how it's done here.
3f378e0
to
d9eefff
Compare
Thanks for reviews @dvoytenko @samouri ! Latest revision should have all requested changes! |
() => | ||
new Date( | ||
getEpoch(endDate, timeleftMs, timestampMs, timestampSeconds) + | ||
offsetSeconds * DELAY |
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.
Probably should use MILLISECONDS_IN_SECOND
here instead of DELAY
right?
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.
Yup.
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.
Thanks, updated!
* Strings representing years, minutes, etc. in various locales | ||
* @type {Object<string, !Array<string>>} | ||
*/ | ||
const LOCALE_WORD = { |
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.
Why don't we just store this in the expected { years: … }
format? We could then avoid the useMemo
and everything else necessary.
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.
So the idea would be to do something like LOCALE_WORD[props['locale']]
at each iteration?
One thing I am concerned about is there is a helper function that checks that the props['locale']
value is valid and displays a warning if not. The warning really should only be displayed one time. So even if we were to change the format, we would still have to useMemo
to store some info about what our locale
is or that the warning message has already been displayed.
Happy to update the format, if there's something I'm missing. If I am misunderstanding please let me know!
() => | ||
new Date( | ||
getEpoch(endDate, timeleftMs, timestampMs, timestampSeconds) + | ||
offsetSeconds * DELAY |
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.
Yup.
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.
LG on my side. 1 question. Please complete the review with others.
Thanks for reviews @dvoytenko @jridgewell @samouri ! Latest revision is up with responses to comments! |
LG from me. But please wait for another LG. |
7a1ae87
to
b480a76
Compare
b480a76
to
955c8db
Compare
…ountdown (ampproject#30092) * First commit Preact component * Add clear comments for helper functions * Various review comment updates * More review updates * More review updates * Add useMemo * Updates for code review * Review comments and rebase * Remove rebase conflict * remove quotes on playable * Remove date format from epoch * add quotes for object keys that are output to template
Tracking Issue: #30052
Design Doc: Doc Link
Initial amp-date-countdown preact component. Preact component takes in user defined props to calculate a date to countdown to. This is labeled
epoch
. Then we usesetInterval
to continuously calculate the difference between the current time and theepoch
time (this is labeledtimeLeft
).Please see Design Doc for more details on the component.
Design is similar to DateDisplay Bento component.