Skip to content

Commit

Permalink
adds documentation
Browse files Browse the repository at this point in the history
  • Loading branch information
joluj committed Jun 15, 2021
1 parent d7879ed commit 3e7524b
Show file tree
Hide file tree
Showing 2 changed files with 61 additions and 22 deletions.
79 changes: 59 additions & 20 deletions frontend/src/common/AnimatedList/AnimatedList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,21 +8,42 @@ import React, {
} from 'react';
import { List } from '@material-ui/core';

/**
* GUI element of the {@link AnimatedList}.
*/
export type AnimatedListElement = JSX.Element & {
ref?: RefObject<HTMLElement>;
};

type Props = {
/**
* List-items; must have a key and a ref.
*/
children: AnimatedListElement[];
className?: string;
};

type BoundingBoxes = {
/**
* Information about items in the AnimatedList-
*/
type ItemGuiInformation = {
/**
* Map with key = element key and value = bounding box.
*/
boxes: Map<Key, DOMRect>;
/**
* Scroll position at the creation of objects of this type.
*/
scrollY: number;
};

function getBoundingBoxes(children: AnimatedListElement[]): BoundingBoxes {
/**
* Returns an {@link ItemGuiInformation} object for the given list items
* @param children elements of the list
*/
function createItemGuiInformation(
children: AnimatedListElement[]
): ItemGuiInformation {
const boundingBoxes = new Map<Key, DOMRect>();

React.Children.forEach(children, (child) => {
Expand All @@ -35,59 +56,77 @@ function getBoundingBoxes(children: AnimatedListElement[]): BoundingBoxes {
return { boxes: boundingBoxes, scrollY: window.scrollY };
}

/**
* Outputs the previous state from a variable of useState.
* See https://reactjs.org/docs/hooks-faq.html#how-to-get-the-previous-props-or-state
*/
function usePrevious<T>(value: T) {
const prevChildrenRef = useRef<T>();
const prevValue = useRef<T>();

useEffect(() => {
prevChildrenRef.current = value;
prevValue.current = value;
}, [value]);

return prevChildrenRef.current;
return prevValue.current;
}

/**
* Returns a list that animated the reordering of its children.
* @param children list-items
* @param className react standard className attribute
*/
function AnimatedList({ children, className }: Props): JSX.Element {
const [boundingBox, setBoundingBox] = useState<BoundingBoxes>({
const [currBoundingBox, setCurrBoundingBox] = useState<ItemGuiInformation>({
boxes: new Map(),
scrollY: 0,
});
const prevBoundingBox = usePrevious(boundingBox);
const prevBoundingBox = usePrevious(currBoundingBox);

useLayoutEffect(() => {
setBoundingBox(getBoundingBoxes(children));
// When children change (e.g. reorder) => update item information
setCurrBoundingBox(createItemGuiInformation(children));
}, [children]);

useEffect(() => {
// if prevBoundingBox exists (=> no animation on first draw)
if (prevBoundingBox && prevBoundingBox.boxes.size > 0) {
// Check each child for changed position
React.Children.forEach(children, (child) => {
const domNode = child.ref?.current;
const prevBox = boundingBox.boxes.get(child.key ?? '');
const currBox = prevBoundingBox.boxes.get(child.key ?? '');
// current/new bounding box for that child
const currBox = currBoundingBox.boxes.get(child.key ?? '');
// previous bounding box
const prevBox = prevBoundingBox.boxes.get(child.key ?? '');

if (domNode == null || prevBox === undefined || currBox === undefined)
// If box/box-position does not exists (e.g. on new item) => stop
if (domNode == null || currBox === undefined || prevBox === undefined)
return;

const currScrollCleanedY = currBox.top - boundingBox.scrollY;
const prevScrollCleanedY = prevBox.top - prevBoundingBox.scrollY;
// y-positions with cleaned scrolling (y independent of scrolling)
// e.g. pos = 50, then 25px scrolling => normal y would be 75, cleaned is still 50
const currScrollCleanedY = prevBox.top - currBoundingBox.scrollY;
const prevScrollCleanedY = currBox.top - prevBoundingBox.scrollY;

const positionChanged = currScrollCleanedY - prevScrollCleanedY;
// difference of prev and curr position
const deltaY = currScrollCleanedY - prevScrollCleanedY;

if (positionChanged) {
// if difference is there => animate
if (deltaY !== 0) {
requestAnimationFrame(() => {
// Before the DOM paints, invert child to old position
domNode.style.transform = `translateY(${positionChanged}px)`;
// first, put item to its prev position
domNode.style.transform = `translateY(${deltaY}px)`;
domNode.style.transition = 'transform 0s';

requestAnimationFrame(() => {
// After the previous frame, remove
// the transition to play the animation
// after the previous frame, remove the transition and play the animation
domNode.style.transform = '';
domNode.style.transition = 'transform 500ms';
});
});
}
});
}
}, [boundingBox, children]);
}, [currBoundingBox, children]);

return <List className={className}>{children}</List>;
}
Expand Down
4 changes: 2 additions & 2 deletions frontend/src/exploration/Exploration.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,11 @@ import useResizeObserver from '@react-hook/resize-observer';
import Previews from './previews/Previews';
import Questions from './questions/Questions';

type ThemeProps = {
type StyleProps = {
previewHeaderHeight: number;
};

const useStyle = makeStyles<Theme, ThemeProps>((theme) =>
const useStyle = makeStyles<Theme, StyleProps>((theme) =>
createStyles({
container: {
margin: theme.spacing(10),
Expand Down

0 comments on commit 3e7524b

Please sign in to comment.