From 3311486d27222d82aad72fa8a4c61139ba34173a Mon Sep 17 00:00:00 2001 From: siddharthkp Date: Wed, 1 Apr 2020 12:17:42 +0200 Subject: [PATCH 1/3] massive imperative refactor --- .../screens/Comments/Dialog/index.tsx | 161 +++++++++++++++--- 1 file changed, 136 insertions(+), 25 deletions(-) diff --git a/packages/app/src/app/pages/Sandbox/Editor/Workspace/screens/Comments/Dialog/index.tsx b/packages/app/src/app/pages/Sandbox/Editor/Workspace/screens/Comments/Dialog/index.tsx index daf15ba522c..1bde6033c5c 100644 --- a/packages/app/src/app/pages/Sandbox/Editor/Workspace/screens/Comments/Dialog/index.tsx +++ b/packages/app/src/app/pages/Sandbox/Editor/Workspace/screens/Comments/Dialog/index.tsx @@ -28,7 +28,7 @@ export const CommentDialog = props => const DIALOG_WIDTH = 420; const DIALOG_TRANSITION_DURATION = 0.25; -const REPLY_TRANSITION_DELAY = 0.25; +const REPLY_TRANSITION_DELAY = 0.5; export const Dialog: React.FC = () => { const { state } = useOvermind(); @@ -365,43 +365,154 @@ const CommentBody = ({ comment, editing, setEditing, hasReplies }) => { }; const Replies = ({ replies, repliesRenderedCallback }) => { - const [isAnimating, setAnimating] = React.useState(true); - const repliesLoaded = !!replies[0]; + /** + * Loading animations: + * 0. Wait for the dialog to have animated in view and scaled up. + * 1. If replies have not loaded yet - show skeleton with 146px height, + * when the comments load, replace skeleton with replies and + * transition to height auto + * 2. If replies are already there - show replies with 0px height, + * transition to height: auto + * + */ + + const skeletonController = useAnimation(); + const repliesController = useAnimation(); /** Wait another ms after the dialog has transitioned into view */ const delay = DIALOG_TRANSITION_DURATION + REPLY_TRANSITION_DELAY; - const REPLY_TRANSITION_DURATION = 0.25; + const REPLY_TRANSITION_DURATION = Math.max(replies.length * 0.15, 0.5); + const SKELETON_FADE_DURATION = 0.25; const SKELETON_HEIGHT = 146; + // initial status of replies - + // this is false when it's the first time this specific comment is opened + // after that it will be true because we cache replies in state + const repliesAlreadyLoadedOnFirstRender = React.useRef(!!replies[0]); + + // current status of replies- + const repliesLoaded = !!replies[0]; + + /** Welcome to the imperative world of timeline animations + * + * ------------------------------------------------------- + * | | | | | + * t=0 t=R1 t=1 t=R2 t=2 + * + * Legend: + * t=0 DOM has rendered, animations can be started + * t=1 Dialog's enter animation has completed, replies animations can start + * t=2 Replies animation have started + * t=R1 Replies have loaded before t=1 + * t=R2 Replies have loaded after t=1 + * + */ + const [T, setStepInTimeline] = React.useState(-1); + + /* + * T = 0 (DOM has rendered, animations can be started) + * If replies aren't loaded, show skeleton + * If replies are loaded, do nothing and wait for next animation + * */ React.useEffect(() => { - if (repliesLoaded && !isAnimating) repliesRenderedCallback(); - }, [repliesLoaded, isAnimating, repliesRenderedCallback]); + if (!repliesAlreadyLoadedOnFirstRender.current) { + skeletonController.set({ height: SKELETON_HEIGHT, opacity: 1 }); + } else { + // do nothing, wait for the delayed animation to kick in + } + setStepInTimeline(0); + }, [skeletonController]); + + /** + * T = 1 (Dialog's enter animation has completed, hence the delay) + * If replies have loaded, remove skeleton, transition the replies + * If replies have not loaded, do nothing and wait for next animation + */ + React.useEffect(() => { + const timeout = window.setTimeout(() => { + if (repliesLoaded) { + skeletonController.set({ + position: 'absolute', + }); + skeletonController.start({ + height: 0, + opacity: 0, + transition: { duration: SKELETON_FADE_DURATION }, + }); + repliesController.set({ opacity: 1 }); + repliesController.start({ + height: 'auto', + transition: { duration: REPLY_TRANSITION_DURATION }, + }); + setStepInTimeline(2); + } else { + setStepInTimeline(1); + } + }, delay * 1000); + return () => window.clearTimeout(timeout); + }, [ + skeletonController, + repliesController, + repliesLoaded, + delay, + REPLY_TRANSITION_DURATION, + ]); + + /** + * T = R1 or R2 (Replies have now loaded) + * this is a parralel async process and can happen before or after t=1 + * If it's before T=1, do nothing, wait for T=1 + * If it's after T=1, start replies transition now! + */ + React.useEffect(() => { + if (!repliesLoaded) return; + if (T === 1) { + skeletonController.start({ + height: 0, + opacity: 0, + transition: { duration: REPLY_TRANSITION_DURATION }, + }); + repliesController.set({ opacity: 1 }); + repliesController.start({ + height: 'auto', + transition: { duration: REPLY_TRANSITION_DURATION }, + }); + setStepInTimeline(2); + } + }, [ + T, + repliesLoaded, + REPLY_TRANSITION_DURATION, + skeletonController, + repliesController, + ]); return ( - setAnimating(false)} - > - {repliesLoaded ? ( + <> + + + + + <> {replies.map( reply => reply && )} - ) : ( - - )} - + + ); }; From cba3c512f31f1e57044b661371543bf7487340e8 Mon Sep 17 00:00:00 2001 From: siddharthkp Date: Wed, 1 Apr 2020 17:07:21 +0200 Subject: [PATCH 2/3] its done, dont ask me --- .../screens/Comments/Dialog/index.tsx | 41 +++++++++++-------- 1 file changed, 24 insertions(+), 17 deletions(-) diff --git a/packages/app/src/app/pages/Sandbox/Editor/Workspace/screens/Comments/Dialog/index.tsx b/packages/app/src/app/pages/Sandbox/Editor/Workspace/screens/Comments/Dialog/index.tsx index 1bde6033c5c..45969d59e31 100644 --- a/packages/app/src/app/pages/Sandbox/Editor/Workspace/screens/Comments/Dialog/index.tsx +++ b/packages/app/src/app/pages/Sandbox/Editor/Workspace/screens/Comments/Dialog/index.tsx @@ -135,12 +135,11 @@ export const Dialog: React.FC = () => { setEditing={setEditing} hasReplies={replies.length} /> - {replies.length ? ( - setRepliesRendered(true)} - /> - ) : null} + + setRepliesRendered(true)} + /> { * t=R2 Replies have loaded after t=1 * */ + const [T, setStepInTimeline] = React.useState(-1); /* * T = 0 (DOM has rendered, animations can be started) + * If there are no replies, skip all of the animations * If replies aren't loaded, show skeleton * If replies are loaded, do nothing and wait for next animation * */ React.useEffect(() => { - if (!repliesAlreadyLoadedOnFirstRender.current) { + if (!replies.length || T > 0) { + // If the dialog is already open without any replies, + // just skip all of the animations for opening transitions + repliesController.set({ opacity: 1, height: 'auto' }); + setStepInTimeline(2); + } else if (!repliesAlreadyLoadedOnFirstRender.current) { skeletonController.set({ height: SKELETON_HEIGHT, opacity: 1 }); - } else { - // do nothing, wait for the delayed animation to kick in + setStepInTimeline(0); } - setStepInTimeline(0); - }, [skeletonController]); + }, [skeletonController, repliesController, replies.length, T]); /** * T = 1 (Dialog's enter animation has completed, hence the delay) @@ -430,10 +434,10 @@ const Replies = ({ replies, repliesRenderedCallback }) => { */ React.useEffect(() => { const timeout = window.setTimeout(() => { + if (T >= 1) return; // can't go back in time + if (repliesLoaded) { - skeletonController.set({ - position: 'absolute', - }); + skeletonController.set({ position: 'absolute' }); skeletonController.start({ height: 0, opacity: 0, @@ -444,6 +448,7 @@ const Replies = ({ replies, repliesRenderedCallback }) => { height: 'auto', transition: { duration: REPLY_TRANSITION_DURATION }, }); + setStepInTimeline(2); } else { setStepInTimeline(1); @@ -456,6 +461,7 @@ const Replies = ({ replies, repliesRenderedCallback }) => { repliesLoaded, delay, REPLY_TRANSITION_DURATION, + T, ]); /** @@ -465,8 +471,9 @@ const Replies = ({ replies, repliesRenderedCallback }) => { * If it's after T=1, start replies transition now! */ React.useEffect(() => { - if (!repliesLoaded) return; - if (T === 1) { + if (!repliesLoaded) { + // do nothing, wait for T=1 + } else if (T === 1) { skeletonController.start({ height: 0, opacity: 0, @@ -490,7 +497,7 @@ const Replies = ({ replies, repliesRenderedCallback }) => { return ( <> From a1b00934c84c802c1b44a440d72731f611217775 Mon Sep 17 00:00:00 2001 From: siddharthkp Date: Wed, 1 Apr 2020 17:10:01 +0200 Subject: [PATCH 3/3] ugh -1 not 0 --- .../Editor/Workspace/screens/Comments/Dialog/index.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/app/src/app/pages/Sandbox/Editor/Workspace/screens/Comments/Dialog/index.tsx b/packages/app/src/app/pages/Sandbox/Editor/Workspace/screens/Comments/Dialog/index.tsx index 45969d59e31..a8d4e0ba2c9 100644 --- a/packages/app/src/app/pages/Sandbox/Editor/Workspace/screens/Comments/Dialog/index.tsx +++ b/packages/app/src/app/pages/Sandbox/Editor/Workspace/screens/Comments/Dialog/index.tsx @@ -416,12 +416,12 @@ const Replies = ({ replies, repliesRenderedCallback }) => { * If replies are loaded, do nothing and wait for next animation * */ React.useEffect(() => { - if (!replies.length || T > 0) { + if (!replies.length) { // If the dialog is already open without any replies, // just skip all of the animations for opening transitions repliesController.set({ opacity: 1, height: 'auto' }); setStepInTimeline(2); - } else if (!repliesAlreadyLoadedOnFirstRender.current) { + } else if (!repliesAlreadyLoadedOnFirstRender.current && T === -1) { skeletonController.set({ height: SKELETON_HEIGHT, opacity: 1 }); setStepInTimeline(0); }