Skip to content
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

perf: wait to update sidebar size on graph #2321

Merged
merged 8 commits into from
Sep 1, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 1 addition & 5 deletions src/cljs/athens/views.cljs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
(:require
["/components/App/ContextMenuContext" :refer [ContextMenuProvider]]
["/components/Layout/MainContent" :refer [MainContent]]
["/components/Layout/RightSidebarResizeControl" :refer [RightSidebarResizeControl]]
["/components/Layout/useLayoutState" :refer [LayoutProvider]]
["/theme/theme" :refer [theme]]
["@chakra-ui/react" :refer [ChakraProvider Flex VStack HStack Spinner Center]]
Expand Down Expand Up @@ -43,7 +42,7 @@
[:> ChakraProvider {:theme theme,
:bg "background.basement"}
[:> ContextMenuProvider
[:> LayoutProvider
[:> LayoutProvider {:rightSidebarWidth @right-sidebar-width}
[help-popup]
[alert]
[athena-component]
Expand Down Expand Up @@ -87,7 +86,4 @@
[:> MainContent {:rightSidebarWidth @right-sidebar-width
:isRightSidebarOpen @right-sidebar-open?}
[pages/view]]
[:> RightSidebarResizeControl {:rightSidebarWidth @right-sidebar-width
:isRightSidebarOpen @right-sidebar-open?
:onResizeSidebar #(rf/dispatch [:right-sidebar/set-width %])}]
[right-sidebar/right-sidebar]]]])]]]])))
6 changes: 4 additions & 2 deletions src/cljs/athens/views/right_sidebar/core.cljs
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,10 @@

(defn right-sidebar-el
"Resizable: use local atom for width, but dispatch value to re-frame on mouse up. Instantiate local value with re-frame width too."
[open? items rf-width]
[open? items on-resize rf-width]
[:> RightSidebar
{:isOpen open?
:onResize on-resize
:rightSidebarWidth rf-width}
(if (empty? items)
[empty-message]
Expand All @@ -49,5 +50,6 @@
[]
(let [open? (shared/get-open?)
items (shared/get-items)
on-resize #(rf/dispatch [:right-sidebar/set-width %])
width @(rf/subscribe [:right-sidebar/width])]
[right-sidebar-el open? items width]))
[right-sidebar-el open? items on-resize width]))
24 changes: 3 additions & 21 deletions src/js/components/AppToolbar/AppToolbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,6 @@ import { WindowButtons } from './components/WindowButtons';
import { LocationIndicator } from './components/LocationIndicator';
import { reusableToast } from '@/utils/reusableToast';

const PAGE_TITLE_SHOW_HEIGHT = 24;

interface ToolbarButtonGroupProps extends ButtonGroupProps {
key: string
}
Expand Down Expand Up @@ -166,6 +164,7 @@ export const AppToolbar = (props: AppToolbarProps): React.ReactElement => {
isWinMaximized,
isThemeDark,
isLeftSidebarOpen,
isRightSidebarOpen,
isShowComments,
onClickComments: handleClickComments,
onPressHelp: handlePressHelp,
Expand All @@ -190,30 +189,13 @@ export const AppToolbar = (props: AppToolbarProps): React.ReactElement => {
toolbarHeight,
mainSidebarWidth,
isScrolledPastTitle,
setIsScrolledPastTitle
} = React.useContext(LayoutContext);

const toast = useToast();
const commentsToggleToastRef = React.useRef(null);

// add event listener to detect when the user scrolls past the title
React.useLayoutEffect(() => {
const scrollContainer = document.getElementById("main-layout") as HTMLElement;
if (scrollContainer) {
const handleScroll = () => {
if (scrollContainer.scrollTop > PAGE_TITLE_SHOW_HEIGHT) {
setIsScrolledPastTitle(prev => ({ ...prev, "mainContent": true }));
} else {
setIsScrolledPastTitle(prev => ({ ...prev, "mainContent": false }));
}
}
handleScroll();
scrollContainer.addEventListener('scroll', handleScroll);
return () => scrollContainer.removeEventListener('scroll', handleScroll);
}
}, []);

const shouldShowUnderlay = Object.values(isScrolledPastTitle).some(x => x);
const shouldShowUnderlay = isScrolledPastTitle["mainContent"] || (isScrolledPastTitle["rightSidebar"] && isRightSidebarOpen);

// If the workspace color mode doesn't match
// the chakra color mode, update the chakra color mode
Expand Down Expand Up @@ -362,7 +344,7 @@ export const AppToolbar = (props: AppToolbarProps): React.ReactElement => {
>
{currentPageTitle && (
<LocationIndicator
isVisible={isScrolledPastTitle.mainContent}
isVisible={isScrolledPastTitle["mainContent"]}
type="node"
uid="123"
title={currentPageTitle}
Expand Down
37 changes: 33 additions & 4 deletions src/js/components/Layout/MainContent.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,34 @@
import { Flex } from "@chakra-ui/react";
import { Box, Flex } from "@chakra-ui/react";
import { AnimatePresence, motion } from "framer-motion";
import * as React from "react";
import { LayoutContext, layoutAnimationTransition } from "./useLayoutState";
import { useInView } from 'react-intersection-observer';

/** Main Content */
export const MainContent = ({ children, isRightSidebarOpen, rightSidebarWidth }) => {
export const MainContent = ({ children, isRightSidebarOpen }) => {
const {
toolbarHeight,
mainContentRef,
isResizingLayout,
isScrolledPastTitle,
setIsScrolledPastTitle,
unsavedRightSidebarWidth
} = React.useContext(LayoutContext);

const { ref: markerRef, inView } = useInView({ threshold: 0 });

React.useEffect(() => {
if (inView) {
if (isScrolledPastTitle["mainContent"]) {
setIsScrolledPastTitle(prev => ({ ...prev, "mainContent": false }));
}
} else {
if (!isScrolledPastTitle["mainContent"]) {
setIsScrolledPastTitle(prev => ({ ...prev, "mainContent": true }));
}
}
}, [inView, setIsScrolledPastTitle]);

return (
// AnimatePresence is required to prevent
// the content from jumping when the sidebar starts open
Expand All @@ -30,10 +49,20 @@ export const MainContent = ({ children, isRightSidebarOpen, rightSidebarWidth })
"--app-header-height": toolbarHeight,
}}
animate={{
paddingRight: isRightSidebarOpen ? rightSidebarWidth + "vw" : 0,
transition: layoutAnimationTransition
paddingRight: isRightSidebarOpen ? unsavedRightSidebarWidth + "vw" : 0,
transition: isResizingLayout ? {
...layoutAnimationTransition,
mass: 0,
} : layoutAnimationTransition
}}
>
<Box
aria-hidden
position="absolute"
ref={markerRef}
height="20px"
top={0}
/>
{children}
</Flex>
</AnimatePresence>
Expand Down
37 changes: 30 additions & 7 deletions src/js/components/Layout/RightSidebar.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import * as React from "react";
import { LayoutContext, layoutAnimationProps } from "./useLayoutState";
import { LayoutContext, layoutAnimationProps, layoutAnimationTransition } from "./useLayoutState";
import { AnimatePresence, motion } from 'framer-motion';
import { RightSidebarResizeControl } from "./RightSidebarResizeControl";
import { XmarkIcon, ChevronRightIcon, PageIcon, PageFillIcon, BlockIcon, BlockFillIcon, GraphIcon, ArrowLeftOnBoxIcon } from '@/Icons/Icons';
import { Button, IconButton, Box, Collapse, VStack, BoxProps } from '@chakra-ui/react';
import { useInView } from 'react-intersection-observer';
Expand All @@ -10,32 +11,52 @@ import { useInView } from 'react-intersection-observer';

interface RightSidebarProps extends BoxProps {
isOpen: boolean;
onResize: (size: number) => void;
rightSidebarWidth: number;
}

export const RightSidebar = (props: RightSidebarProps) => {
const { children, rightSidebarWidth, isOpen } = props;
const { children, onResize, isOpen } = props;
const {
toolbarHeight,
isScrolledPastTitle,
setIsScrolledPastTitle,
isResizingLayout,
unsavedRightSidebarWidth
} = React.useContext(LayoutContext);

const { ref: markerRef, inView } = useInView({ threshold: 0 });

React.useEffect(() => {
if (inView) {
setIsScrolledPastTitle(prev => ({ ...prev, "rightSidebar": false }));
if (isScrolledPastTitle["rightSidebar"]) {
setIsScrolledPastTitle(prev => ({ ...prev, "rightSidebar": false }));
}
} else {
setIsScrolledPastTitle(prev => ({ ...prev, "rightSidebar": true }));
if (!isScrolledPastTitle["rightSidebar"]) {
setIsScrolledPastTitle(prev => ({ ...prev, "rightSidebar": true }));
}
}
}, [inView, setIsScrolledPastTitle]);

const layoutAnimation = {
...layoutAnimationProps(unsavedRightSidebarWidth + "vw"),
animate: {
width: unsavedRightSidebarWidth + "vw",
opacity: 1,
transition: isResizingLayout ? {
...layoutAnimationTransition,
mass: 0,
} : layoutAnimationTransition
},
}

return (
<AnimatePresence initial={false}>
{isOpen && (
<Box
as={motion.div}
{...layoutAnimationProps(rightSidebarWidth + "vw")}
{...layoutAnimation}
zIndex={1}
bg="background.floor"
transitionProperty="background"
Expand All @@ -52,16 +73,18 @@ export const RightSidebar = (props: RightSidebarProps) => {
pt={`calc(${toolbarHeight} + 1rem)`}
left="auto"
>
<RightSidebarResizeControl
onResizeSidebar={onResize}
/>
<Box
bg="green"
aria-hidden
position="absolute"
ref={markerRef}
height="20px"
top={0}
/>
<Box
width={rightSidebarWidth + "vw"}>
width={unsavedRightSidebarWidth + "vw"}>
{children}
</Box>
</Box>
Expand Down
46 changes: 30 additions & 16 deletions src/js/components/Layout/RightSidebarResizeControl.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import React from 'react';
import { LayoutContext } from './useLayoutState';
import { Box, BoxProps } from '@chakra-ui/react';

const MIN_VW = 20;
Expand All @@ -15,23 +16,40 @@ const getVW = (e, window) => {

interface RightSidebarResizeControlProps extends BoxProps {
onResizeSidebar: (size: number) => void;
isRightSidebarOpen: boolean;
rightSidebarWidth: number;
}

export const RightSidebarResizeControl = (props: RightSidebarResizeControlProps) => {
const { onResizeSidebar, isRightSidebarOpen, rightSidebarWidth, ...rest } = props;
const { onResizeSidebar, ...rest } = props;
const [isDragging, setIsDragging] = React.useState(false);
const { unsavedRightSidebarWidth, setUnsavedRightSidebarWidth,
setIsResizingLayout
} = React.useContext(LayoutContext);

const moveHandler = (e) => {
const updateWidthTimer = React.useRef<number>();

const updateWidth = (e) => {
if (isDragging) {
setIsResizingLayout(true);
e.preventDefault();
const calcVW = getVW(e, window);
const clampVW = clamp(calcVW, MIN_VW, MAX_VW);
onResizeSidebar(clampVW);
const vw = getVW(e, window);
const clampedVW = clamp(vw, MIN_VW, MAX_VW);
setUnsavedRightSidebarWidth(clampedVW);

if (updateWidthTimer.current) {
clearTimeout(updateWidthTimer.current);
}

updateWidthTimer.current = window.setTimeout(() => {
onResizeSidebar(unsavedRightSidebarWidth);
setIsResizingLayout(false);
}, 1000)
}
}

const moveHandler = (e) => {
updateWidth(e)
}

const mouseUpHandler = () => {
setIsDragging(false);
}
Expand All @@ -45,27 +63,23 @@ export const RightSidebarResizeControl = (props: RightSidebarResizeControlProps)
}
});

if (!isRightSidebarOpen) {
return null;
}

return (
<Box
as="button"
width="3px"
transform="translateX(50%)"
position="fixed"
zIndex={100}
opacity={0}
right={rightSidebarWidth + "vw"}
height="100%"
position="absolute"
left={0}
top={0}
bottom={0}
cursor="col-resize"
onMouseDown={() => setIsDragging(true)}
onMouseMove={moveHandler}
onMouseUp={mouseUpHandler}
bg="link"
transition="opacity 0.2s ease-in-out"
_hover={{ opacity: 1 }}
_hover={{ opacity: 0.6 }}
{...isDragging && { opacity: 1 }}
{...rest}
>
Expand Down
18 changes: 14 additions & 4 deletions src/js/components/Layout/useLayoutState.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import * as React from "react";

export const LayoutContext = React.createContext(null);
export const ContextMenuContext = React.createContext(null);

export const VIEW_MODES = ["regular", "compact"];

Expand Down Expand Up @@ -30,16 +29,26 @@ export const layoutAnimationProps = (openWidth) => ({
/**
* Instantiate state for an app layout
*/
export const useLayoutState = () => {
export const useLayoutState = (props) => {
const { rightSidebarWidth } = props;

const mainContentRef = React.useRef();
const toolbarRef = React.useRef();
const [mainSidebarWidth, setMainSidebarWidth] = React.useState(300);
const [unsavedRightSidebarWidth, setUnsavedRightSidebarWidth] = React.useState(rightSidebarWidth);
const [isResizingLayout, setIsResizingLayout] = React.useState(false);
const [isScrolledPastTitle, setIsScrolledPastTitle] = React.useState({});
const toolbarHeight = "3rem";

console.log(isScrolledPastTitle);

return {
mainSidebarWidth,
setMainSidebarWidth,
unsavedRightSidebarWidth,
setUnsavedRightSidebarWidth,
isResizingLayout,
setIsResizingLayout,
isScrolledPastTitle,
setIsScrolledPastTitle,
toolbarHeight,
Expand All @@ -48,8 +57,9 @@ export const useLayoutState = () => {
};
};

export const LayoutProvider = ({ children }) => {
const layoutState = useLayoutState();
export const LayoutProvider = (props) => {
const { children, ...rest } = props;
const layoutState = useLayoutState(rest);

return <LayoutContext.Provider value={layoutState}>
{children}
Expand Down