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

Masonry: add experimental support for two-column items #2955

Merged
merged 34 commits into from
Jun 23, 2023
Merged
Show file tree
Hide file tree
Changes from 23 commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
ad44bd5
add graph classes
dangerismycat May 19, 2023
147afce
add support for twoColItems query param
dangerismycat May 19, 2023
8376d62
remove unneeded comment
dangerismycat May 19, 2023
dcf0865
add support for generating two column items
dangerismycat May 19, 2023
d2ad8e2
fix MasonryContainer.renderItem type
dangerismycat May 19, 2023
6cc22dd
add support in MasonryContainer for two-column items
dangerismycat May 19, 2023
ab4e5c4
add renderItem arg for eventual item height adjustments
dangerismycat May 19, 2023
aea4bcf
the bizness, WIP
dangerismycat May 19, 2023
1ef3af9
add support for edge scores
dangerismycat May 19, 2023
9d35ad6
get offscreen positions earlier
dangerismycat May 19, 2023
50d1f69
insert 2-col items later in the batches
dangerismycat May 19, 2023
b9c4369
WIP
dangerismycat May 20, 2023
28f525e
s/isNotNil/isNil
dangerismycat May 22, 2023
8036604
fix measurementStore type
dangerismycat May 22, 2023
be130d7
sort class fields
dangerismycat May 22, 2023
29c47f4
add Masonry.numColumns
dangerismycat May 22, 2023
325b399
s/itemsToRender/itemsWithMeasurements for clarity
dangerismycat May 22, 2023
ce46866
refactor defaultLayout to return the number of columns
dangerismycat May 22, 2023
90e4189
determine batch of items to measure using number of columns
dangerismycat May 22, 2023
2f20988
WIP
dangerismycat Jun 5, 2023
9b9cd7b
WIP
dangerismycat Jun 20, 2023
edeadf7
WIP
dangerismycat Jun 20, 2023
a101d4e
holy crap it works
dangerismycat Jun 22, 2023
cb05c60
Fork defaultLayout for 2-col support, use experimental prop
dangerismycat Jun 22, 2023
cc3929f
Merge branch 'master' into masonry-two-column-items
dangerismycat Jun 22, 2023
72718d0
fix Flow issues
dangerismycat Jun 22, 2023
7f5ce86
remove Position.column (not needed)
dangerismycat Jun 22, 2023
98a8d81
remove unneeded suppression
dangerismycat Jun 22, 2023
2e1ea79
remove prettyPrintGraph method (it never worked well anyway)
dangerismycat Jun 22, 2023
456853d
cleanup
dangerismycat Jun 22, 2023
8d08231
readonlyarray in graphnode
dangerismycat Jun 22, 2023
03e802d
update comment
dangerismycat Jun 23, 2023
512087b
improve hasTwoColumnItems check
dangerismycat Jun 23, 2023
cf3ce21
guh
dangerismycat Jun 23, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
6 changes: 5 additions & 1 deletion docs/integration-test-helpers/masonry/ExampleGridItem.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ export default function ExampleGridItem({ data = {}, itemIdx, expanded }: Props)
setCounter((prevCounter) => prevCounter + 1);
}

const isTwoColItem = data.columnSpan === 2;

return (
<div
style={{
Expand All @@ -30,11 +32,13 @@ export default function ExampleGridItem({ data = {}, itemIdx, expanded }: Props)
style={{
height: expanded ? data.height + 100 : data.height,
border: '1px solid #ff0000',
background: data.color,
background: isTwoColItem ? 'black' : data.color,
color: isTwoColItem ? 'white' : undefined,
}}
>
<div>
{data.name} • {data.height}px
{isTwoColItem ? ` • columnSpan: ${data.columnSpan}` : ''}
</div>
<div>Slot Index: {itemIdx}</div>
<div>
Expand Down
17 changes: 12 additions & 5 deletions docs/integration-test-helpers/masonry/MasonryContainer.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// @flow strict
import { Component, createRef, type Element, type Node } from 'react';
import { Component, createRef, type Element } from 'react';
import { Masonry } from 'gestalt';
import ExampleGridItem from './ExampleGridItem.js';
import getClassicGridServerStyles from './getClassicGridServerStyles.js';
Expand All @@ -18,6 +18,8 @@ import getRandomNumberGenerator from './items-utils/getRandomNumberGenerator.js'
// Masonry works, MasonryContainer also supports certain SSR functionality such
// as generating styles that affect pre-hydration Masonry content.

const TWO_COL_MINDEX = 50;

type Props = {|
// The actual Masonry component to be used (if using an experimental version of Masonry).
MasonryComponent: typeof Masonry,
Expand Down Expand Up @@ -49,6 +51,8 @@ type Props = {|
pinHeightsSample?: $ReadOnlyArray<number>,
// If we should position the grid within a scrollContainer besides the window.
scrollContainer?: boolean,
// Insert two-column items into the feed
twoColItems?: boolean,
// If we should virtualize the grid
virtualize?: boolean,
// The relative amount in pixel to extend the virtualized viewport top value.
Expand Down Expand Up @@ -206,7 +210,7 @@ export default class MasonryContainer extends Component<Props, State> {
from = 0,
force = false,
}) => {
const { collage, manualFetch, pinHeightsSample } = this.props;
const { collage, manualFetch, pinHeightsSample, twoColItems } = this.props;

if (manualFetch && !force) {
return;
Expand All @@ -231,13 +235,15 @@ export default class MasonryContainer extends Component<Props, State> {
previousItemCount: from,
randomNumberSeed: Math.random(),
pinHeightsSample,
twoColItems: twoColItems && from > TWO_COL_MINDEX,
})
: generateExampleItems({
name,
numberOfItems: until - from,
previousItemCount: from,
baseHeight,
randomNumberSeed: this.randomNumberSeed,
twoColItems: twoColItems && from > TWO_COL_MINDEX,
});

this.setState(({ items }) => ({
Expand All @@ -246,7 +252,7 @@ export default class MasonryContainer extends Component<Props, State> {
};

loadItems: $ElementType<React$ElementConfig<typeof Masonry>, 'loadItems'> = (props) => {
const { collage, manualFetch, pinHeightsSample } = this.props;
const { collage, manualFetch, pinHeightsSample, twoColItems } = this.props;

if (manualFetch) {
return;
Expand All @@ -273,22 +279,23 @@ export default class MasonryContainer extends Component<Props, State> {
previousItemCount: defaultFrom,
randomNumberSeed: Math.random(),
pinHeightsSample,
twoColItems: twoColItems && defaultFrom > TWO_COL_MINDEX,
})
: generateExampleItems({
name: undefined,
numberOfItems: until - defaultFrom,
previousItemCount: defaultFrom,
baseHeight,
randomNumberSeed: this.randomNumberSeed,
twoColItems: twoColItems && defaultFrom > TWO_COL_MINDEX,
});

this.setState(({ items }) => ({
items: [...items, ...newItems],
}));
};

// $FlowFixMe[unclear-type]
renderItem: ({| +data: any, +itemIdx: number, +isMeasuring: boolean |}) => Node = ({
renderItem: $ElementType<React$ElementConfig<typeof Masonry>, 'renderItem'> = ({
data,
itemIdx,
}) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,30 +4,42 @@ import getRandomNumberGenerator from './getRandomNumberGenerator.js';

type Args = {|
baseHeight?: number,
previousItemCount?: number,
name?: string,
randomNumberSeed?: number,
numberOfItems?: number,
previousItemCount?: number,
randomNumberSeed?: number,
twoColItems?: boolean,
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is new, the rest is alphabetizing

|};

type ExampleItem = {|
name: string,
height: number,
color: string,
columnSpan?: number,
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is new, the rest is alphabetizing

height: number,
name: string,
|};

export default function generateExampleItems({
baseHeight = 200,
previousItemCount = 0,
name = 'Pin',
randomNumberSeed = 0,
numberOfItems = 20,
previousItemCount = 0,
randomNumberSeed = 0,
twoColItems,
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is new, the rest is alphabetizing

}: Args): $ReadOnlyArray<ExampleItem> {
const getRandomNumber = getRandomNumberGenerator(randomNumberSeed);
const twoColItemIndex = twoColItems ? Math.floor(Math.random() * numberOfItems) : undefined;

return Array.from({ length: numberOfItems }).map((_, i) => ({
name: `${name} ${i + previousItemCount}`,
height: baseHeight + previousItemCount + i,
color: getRandomColor(getRandomNumber),
}));
return Array.from({ length: numberOfItems }).map((_, i) => {
const pin = {
name: `${name} ${i + previousItemCount}`,
height: baseHeight + previousItemCount + i,
color: getRandomColor(getRandomNumber),
};
return Boolean(twoColItemIndex) && i === twoColItemIndex
? {
...pin,
columnSpan: 2,
}
: pin;
});
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,17 @@ import getRandomColor from './getRandomColor.js';
import getRandomNumberGenerator from './getRandomNumberGenerator.js';

type Args = {|
previousItemCount?: number,
name?: string,
randomNumberSeed?: number,
numberOfItems?: number,
pinHeightsSample: $ReadOnlyArray<number>,
previousItemCount?: number,
randomNumberSeed?: number,
twoColItems?: boolean,
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is new, the rest is alphabetizing

|};

type ExampleItem = {|
color: string,
columnSpan?: number,
height: number,
name: string,
|};
Expand All @@ -21,20 +23,28 @@ type ExampleItem = {|
* This approach generates a finite set of pins with realistic heights — a fundamental shift from generating random pins on the fly. We have to take care to ensure that the same pins are generated on the server and the client. This is done by generating an array of random number seeds in `getServerSideProps` in docs/pages/integration-test/masonry.js, which are used to generate the pin heights. We then need to make sure we're pulling from these pin heights in a consistent way on the server and the client. This is where `randomNumberSeed` comes in: we pull the first `numberOfItems` heights from the array for SSR. For subsequent calls on the client, we use a random `randomNumberSeed` to pull random heights from the array. So SSR will always use the first `numberOfItems` heights, and we don't attempt to keep track of "where we are" in the heights array after that.
*/
export default function generateRealisticExampleItems({
previousItemCount = 0,
name = 'Pin',
randomNumberSeed = 0,
numberOfItems = 20,
pinHeightsSample,
previousItemCount = 0,
randomNumberSeed = 0,
twoColItems,
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is new, the rest is alphabetizing

}: Args): $ReadOnlyArray<ExampleItem> {
const getRandomNumber = getRandomNumberGenerator(randomNumberSeed);
const baseIndex = Math.ceil(randomNumberSeed * 10);
const twoColItemIndex = twoColItems ? Math.floor(Math.random() * numberOfItems) : undefined;

const pins = Array.from({ length: numberOfItems }).map((_, i) => ({
name: `${name} ${i + previousItemCount}`,
height: pinHeightsSample[baseIndex + i],
color: getRandomColor(getRandomNumber),
}));

return pins;
return Array.from({ length: numberOfItems }).map((_, i) => {
const pin = {
name: `${name} ${i + previousItemCount}`,
height: pinHeightsSample[baseIndex + i],
color: getRandomColor(getRandomNumber),
};
return Boolean(twoColItemIndex) && i === twoColItemIndex
? {
...pin,
columnSpan: 2,
}
: pin;
});
}
2 changes: 2 additions & 0 deletions docs/pages/integration-test/masonry.js
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ export default function TestPage({
offsetTop,
realisticPinHeights,
scrollContainer,
twoColItems,
virtualize,
virtualBoundsTop,
virtualBoundsBottom,
Expand Down Expand Up @@ -115,6 +116,7 @@ export default function TestPage({
offsetTop={offsetTop}
pinHeightsSample={realisticPinHeights ? pinHeightsSample : undefined}
scrollContainer={booleanize(scrollContainer)}
twoColItems={booleanize(twoColItems)}
virtualize={booleanize(virtualize)}
virtualBoundsTop={virtualBoundsTop}
virtualBoundsBottom={virtualBoundsBottom}
Expand Down