perf: improve list rendering performance

fundamentally, we have done 3 things:
1. move out rendering of menu per item to a global component so it renders only once
2. ditch react-virtuoso and migrate to react-window
3. make all items of predictable size and give that size to react-window List

Combining all three, the performance while scrolling and initial rendering shoul improve by almost 50%.
This commit is contained in:
thecodrr
2020-09-17 13:36:47 +05:00
parent b516ebba2c
commit eac78e1a61
22 changed files with 384 additions and 316 deletions

View File

@@ -24,11 +24,8 @@
"react-app-polyfill": "^1.0.6",
"react-dom": "^16.13.1",
"react-modal": "^3.11.2",
"react-placeholder": "^4.0.3",
"react-scripts": "3.4.1",
"react-tiny-virtual-list": "^2.2.0",
"react-virtualized-auto-sizer": "^1.0.2",
"react-virtuoso": "^0.15.0",
"react-window": "^1.8.5",
"rebass": "^4.0.7",
"timeago-react": "^3.0.0",
@@ -74,4 +71,4 @@
"last 4 edge version"
]
}
}
}

View File

@@ -45,6 +45,19 @@
To begin the development, run `npm start` or `yarn start`.
To create a production bundle, use `npm run build` or `yarn build`.
-->
<!-- for calculating 1 em size -->
<div
id="div"
style="
height: 0;
width: 0;
outline: none;
border: none;
padding: none;
margin: none;
box-sizing: content-box;
"
></div>
<script>
(function (document, src, libName, config) {
var script = document.createElement("script");

View File

@@ -14,6 +14,7 @@ import { useRoutes } from "hookrouter";
import routes from "./navigation/routes";
import Editor from "./components/editor";
import useMobile from "./utils/use-mobile";
import GlobalMenuWrapper from "./components/globalmenuwrapper";
function App() {
const [show, setShow] = usePersistentState("isContainerVisible", true);
@@ -89,7 +90,7 @@ function App() {
</Flex>
</Flex>
<Box id="dialogContainer" />
<Box id="snackbarContainer" />
<GlobalMenuWrapper />
</Flex>
</ThemeProvider>
);

View File

@@ -0,0 +1,48 @@
const SINGLE_LINE_HEIGHT = 1.4;
const DEFAULT_FONT_SIZE = 16;
function getNoteHeight(item) {
const { title, headline } = item;
let height = SINGLE_LINE_HEIGHT * 3;
if (title.length > 40) height += SINGLE_LINE_HEIGHT;
if (headline.length > 0) height += SINGLE_LINE_HEIGHT;
if (headline.length > 60) height += SINGLE_LINE_HEIGHT;
return height * DEFAULT_FONT_SIZE;
}
function getNotebookHeight(item) {
const { topics, description, title } = item;
// at the minimum we will have a title and the info text
let height = SINGLE_LINE_HEIGHT * 3; // 2.8 = 2 lines
if (title.length > 40) {
height += SINGLE_LINE_HEIGHT; // title has become multiline
}
if (topics.length > 1) {
height += SINGLE_LINE_HEIGHT;
}
if (description.length > 0) {
height += SINGLE_LINE_HEIGHT;
}
if (description.length > 80) {
height += SINGLE_LINE_HEIGHT;
}
return height * DEFAULT_FONT_SIZE;
}
function getItemHeight(item) {
const { title } = item;
// at the minimum we will have a title and the info text
let height = SINGLE_LINE_HEIGHT * 3; // 2.8 = 2 lines
if (title.length > 40) {
height += SINGLE_LINE_HEIGHT; // title has become multiline
}
return height * DEFAULT_FONT_SIZE;
}
export { getNoteHeight, getNotebookHeight, getItemHeight };

View File

@@ -0,0 +1,23 @@
import React from "react";
import Menu from "../menu";
import useContextMenu from "../../utils/useContextMenu";
function GlobalMenuWrapper() {
const [items, data, title, closeMenu] = useContextMenu();
return (
<Menu
id="globalContextMenu"
menuItems={items}
data={data}
title={title}
style={{
position: "absolute",
display: "none",
zIndex: 999,
}}
closeMenu={closeMenu}
/>
);
}
export default GlobalMenuWrapper;

View File

@@ -0,0 +1,15 @@
import React from "react";
import { Box, Text } from "rebass";
function GroupHeader(props) {
const { title } = props;
if (title === "Pinned") return null;
return (
<Box height={22} mx={2} bg="background" py={0}>
<Text variant="heading" color="primary" fontSize="subtitle">
{title}
</Text>
</Box>
);
}
export default GroupHeader;

View File

@@ -1,17 +1,20 @@
import React, { useEffect } from "react";
import React, { useEffect, useRef } from "react";
import { Flex } from "rebass";
import Button from "../button";
import Search from "../search";
import * as Icon from "../icons";
import { Virtuoso as List } from "react-virtuoso";
import { VariableSizeList as List } from "react-window";
import AutoSizer from "react-virtualized-auto-sizer";
import { useStore as useSearchStore } from "../../stores/searchstore";
import { useStore as useSelectionStore } from "../../stores/selection-store";
import LoginBar from "../loginbar";
import GroupHeader from "../group-header";
function ListContainer(props) {
const setSearchContext = useSearchStore((store) => store.setSearchContext);
const shouldSelectAll = useSelectionStore((store) => store.shouldSelectAll);
const setSelectedItems = useSelectionStore((store) => store.setSelectedItems);
const listRef = useRef();
useEffect(() => {
if (shouldSelectAll) setSelectedItems(props.items);
@@ -26,6 +29,15 @@ function ListContainer(props) {
});
}, [setSearchContext, props.item, props.items, props.type, props.noSearch]);
useEffect(() => {
if (props.static) return;
// whenever there is a change in items array we have to reset the size cache
// so it can be recalculated.
if (listRef.current) {
listRef.current.resetAfterIndex(0, true);
}
}, [props.items, listRef, props.static]);
return (
<Flex variant="columnFill">
{!props.items.length && props.placeholder ? (
@@ -40,20 +52,44 @@ function ListContainer(props) {
{props.children
? props.children
: props.items.length > 0 && (
<List
style={{
width: "100%",
flex: "1 1 auto",
height: "auto",
overflowX: "hidden",
}}
totalCount={props.items.length}
item={(index) => {
const item = props.items[index];
if (!item) return null;
return props.item(index, item);
}}
/>
<AutoSizer>
{({ height, width }) => (
<List
ref={listRef}
height={height}
width={width}
itemKey={(index) => {
const item = props.items[index];
return item.id || item.title;
}}
overscanCount={2}
estimatedItemSize={props.estimatedItemHeight}
itemSize={(index) => {
const item = props.items[index];
if (item.type === "header") {
if (item.title === "Pinned") return 22;
else return 22;
} else {
return props.itemHeight(item);
}
}}
itemCount={props.items.length}
>
{({ index, style }) => {
const item = props.items[index];
return (
<div key={item.id} style={style}>
{item.type === "header" ? (
<GroupHeader title={item.title} />
) : (
props.item(index, item)
)}
</div>
);
}}
</List>
)}
</AutoSizer>
)}
</Flex>
</>

View File

@@ -1,13 +1,10 @@
import React, { useEffect, useState, useCallback } from "react";
import ReactDOM from "react-dom";
import React, { useEffect, useCallback } from "react";
import { Flex, Box, Text } from "rebass";
import * as Icon from "../icons";
import Menu from "../menu";
import {
store as selectionStore,
useStore as useSelectionStore,
} from "../../stores/selection-store";
import useContextMenu from "../../utils/useContextMenu";
function selectMenuItem(isSelected, toggleSelection) {
return {
@@ -42,14 +39,11 @@ const ItemSelector = ({ isSelected, toggleSelection }) => {
};
function ListItem(props) {
const menuId = `contextMenu-${props.item.id}`;
const [parentRef, closeMenu, openMenu] = useContextMenu(menuId);
const isSelectionMode = useSelectionStore((store) => store.isSelectionMode);
const selectedItems = useSelectionStore((store) => store.selectedItems);
const isSelected =
selectedItems.findIndex((item) => props.item.id === item.id) > -1;
const selectItem = useSelectionStore((store) => store.selectItem);
const [menuItems, setMenuItems] = useState(props.menuItems);
const toggleSelection = useCallback(
function toggleSelection() {
@@ -62,24 +56,44 @@ function ListItem(props) {
if (!isSelectionMode && isSelected) toggleSelection();
}, [isSelectionMode, toggleSelection, isSelected]);
useEffect(() => {
if (props.selectable) {
setMenuItems([
selectMenuItem(isSelected, toggleSelection),
...props.menuItems,
]);
}
}, [props.menuItems, isSelected, props.selectable, toggleSelection]);
const openContextMenu = useCallback(
(event, withClick) => {
let items = props.menuItems;
if (props.selectable)
items = [selectMenuItem(isSelected, toggleSelection), ...items];
window.dispatchEvent(
new CustomEvent("globalcontextmenu", {
detail: {
items,
data: props.menuData,
internalEvent: event,
withClick,
},
})
);
},
[
isSelected,
props.menuData,
props.menuItems,
props.selectable,
toggleSelection,
]
);
return (
<Flex
ref={parentRef}
bg={props.pinned ? "shade" : "background"}
alignItems="center"
onContextMenu={openContextMenu}
p={2}
justifyContent="space-between"
sx={{
height: "inherit",
borderBottom: "1px solid",
borderBottomColor: "border",
cursor: "pointer",
position: "relative",
":hover": {
borderBottomColor: "primary",
},
@@ -91,122 +105,99 @@ function ListItem(props) {
toggleSelection={toggleSelection}
/>
)}
<Flex
flex="1 1 auto"
alignItems="center"
justifyContent="space-between"
px={2}
sx={{
width: "full",
position: "relative",
marginTop: props.pinned ? 4 : 0,
paddingTop: props.pinned ? 0 : 2,
paddingBottom: 2,
//TODO add onpressed reaction
}}
>
{props.pinned && (
<Flex
variant="rowCenter"
bg="primary"
sx={{
position: "absolute",
top: -15,
left: 0,
borderRadius: 35,
width: 30,
height: 30,
boxShadow: "2px 1px 3px #00000066",
}}
mx={2}
>
<Box
bg="static"
sx={{
borderRadius: 5,
width: 5,
height: 5,
}}
/>
</Flex>
)}
<Box
{props.pinned && (
<Flex
variant="rowCenter"
bg="primary"
onClick={() => {
//e.stopPropagation();
if (isSelectionMode) {
toggleSelection();
} else if (props.onClick) {
props.onClick();
}
//TODO unpin
}}
sx={{
flex: "1 1 auto",
paddingTop: props.pinned ? 4 : 0,
width: "90%",
":hover": {
cursor: "pointer",
},
position: "absolute",
top: -15,
right: 0,
borderRadius: 35,
width: 30,
height: 30,
boxShadow: "2px 1px 3px #00000066",
}}
mx={2}
>
<Text
color={props.focused ? "primary" : "text"}
fontFamily={"heading"}
fontSize="title"
fontWeight={"bold"}
>
{props.title}
</Text>
<Text
display={props.body ? "flex" : "none"}
variant="body"
<Box
bg="static"
sx={{
marginBottom: 1,
":hover": {
cursor: "pointer",
},
flexWrap: "wrap",
}}
>
{props.body}
</Text>
{props.subBody && props.subBody}
<Text
display={props.info ? "flex" : "none"}
variant="body"
fontSize={11}
color="fontTertiary"
sx={{ marginTop: 2 }}
>
{props.info}
</Text>
</Box>
{props.menuItems && (
<Icon.MoreVertical
size={22}
color="icon"
sx={{ marginRight: -1 }}
onClick={(e) => {
openMenu(e.nativeEvent, menuId, true);
borderRadius: 5,
width: 5,
height: 5,
}}
/>
)}
</Flex>
)}
<Flex
flexDirection="column"
onClick={() => {
//e.stopPropagation();
if (isSelectionMode) {
toggleSelection();
} else if (props.onClick) {
props.onClick();
}
}}
sx={{
width: "90%",
":hover": {
cursor: "pointer",
},
}}
>
<Text
as="h3"
color={props.focused ? "primary" : "text"}
fontFamily={"heading"}
fontSize="title"
fontWeight={"bold"}
sx={{
lineHeight: "1.4em",
maxHeight: "2.8em", // 2 lines, i hope
overflow: "hidden",
textOverflow: "ellipsis",
}}
>
{props.title}
</Text>
<Text
as="p"
display={props.body ? "box" : "none"}
variant="body"
sx={{
cursor: "pointer",
lineHeight: "1.4em",
maxHeight: "2.8em", // 2 lines, i hope
overflow: "hidden",
textOverflow: "ellipsis",
}}
>
{props.body}
</Text>
{props.subBody && props.subBody}
<Text
display={props.info ? "flex" : "none"}
variant="body"
fontSize={11}
color="fontTertiary"
sx={{ marginTop: 2 }}
>
{props.info}
</Text>
</Flex>
{props.menuItems &&
ReactDOM.createPortal(
<Menu
id={menuId}
menuItems={menuItems}
data={props.menuData}
style={{
position: "absolute",
display: "none",
zIndex: 999,
}}
closeMenu={() => closeMenu()}
/>,
document.body
)}
{props.menuItems && (
<Icon.MoreVertical
size={22}
color="icon"
onClick={(event) => openContextMenu(event, true)}
/>
)}
</Flex>
);
}

View File

@@ -68,7 +68,6 @@ class Notebook extends React.Component {
onTopicClick(notebook, index + 1);
e.stopPropagation();
}}
key={topic.title}
bg="primary"
px={1}
sx={{
@@ -80,7 +79,7 @@ class Notebook extends React.Component {
}}
>
<Text variant="body" color="static" fontSize={11}>
{topic.title}
{"ghghgeh"}
</Text>
</Flex>
))}

View File

@@ -1,5 +1,10 @@
@import url("https://fonts.googleapis.com/css2?family=DM+Sans:ital,wght@0,400;0,500;0,700;1,400;1,500;1,700&family=DM+Serif+Text:ital@0;1&display=swap");
html,
body {
font-size: 16px;
}
:root,
body,
#root {

View File

@@ -12,6 +12,7 @@ class AppStore extends BaseStore {
isSideMenuOpen = false;
isFocusMode = false;
colors = [];
globalMenu = { items: [], data: {} };
refresh = async () => {
noteStore.refresh();
@@ -31,7 +32,15 @@ class AppStore extends BaseStore {
};
toggleSideMenu = (toggleState) => {
this.set((state) => (state.isSideMenuOpen = toggleState != null ? toggleState : !state.isSideMenuOpen));
this.set(
(state) =>
(state.isSideMenuOpen =
toggleState != null ? toggleState : !state.isSideMenuOpen)
);
};
setGlobalMenu = (items, data) => {
this.set((state) => (state.globalMenu = { items, data }));
};
}

View File

@@ -6,11 +6,7 @@ import BaseStore from ".";
import { navigate } from "hookrouter";
class NoteStore extends BaseStore {
notes = {
items: [],
groupCounts: [],
groups: [],
};
notes = [];
context = undefined;
selectedNote = 0;
@@ -41,7 +37,7 @@ class NoteStore extends BaseStore {
refresh = () => {
this.refreshContext();
this.set((state) => (state.notes = db.notes.group(undefined, true)));
this.set((state) => (state.notes = db.notes.group()));
};
refreshContext = () => {
@@ -126,12 +122,12 @@ class NoteStore extends BaseStore {
_setValue = (noteId, prop, value) => {
this.set((state) => {
const { context, notes } = state;
const arr = !context ? notes.items : context.notes;
const arr = !context ? notes : context.notes;
let index = arr.findIndex((note) => note.id === noteId);
if (index < 0) return;
arr[index][prop] = value;
// this._syncEditor(noteId, prop, value);
this._syncEditor(noteId, prop, value);
});
};

View File

@@ -1,66 +1,66 @@
import { useEffect, useRef } from "react";
var oldOpenedMenu;
import { useEffect, useState } from "react";
var isOpening = false;
function isMouseInside(e, element) {
if (!e || !element) return false;
return element.contains(e.target);
}
function contextMenuHandler(event, ref, menuId) {
if (
isMouseInside(event, ref.current) &&
!isMouseInside(event, oldOpenedMenu)
) {
dismissMenu(oldOpenedMenu);
event.preventDefault();
openMenu(event, menuId);
}
}
function onKeyDown(event) {
if (event.keyCode === 27) dismissMenu(oldOpenedMenu);
}
function onClick() {
function closeMenu() {
if (isOpening) {
isOpening = false;
return;
}
dismissMenu(oldOpenedMenu);
const menu = document.getElementById("globalContextMenu");
menu.style.display = "none";
window.removeEventListener("click", onWindowClick);
window.removeEventListener("keydown", onKeyDown);
}
function dismissMenu(menu) {
if (menu) {
menu.style.display = "none";
oldOpenedMenu = undefined;
}
function onKeyDown(event) {
if (event.keyCode === 27) closeMenu();
}
function openMenu(event, menuId, withOnClick = false) {
if (withOnClick) {
function onWindowClick() {
closeMenu();
}
// updated positionMenu function
var lastTarget;
function openMenu(e) {
e.preventDefault();
const menu = document.getElementById("globalContextMenu");
if (e.type === "click") {
isOpening = true;
// make it work like a toggle button
if (menu.style.display === "block" && e.target === lastTarget) {
closeMenu();
return;
}
lastTarget = e.target;
}
const menu = document.getElementById(menuId);
if (!menu) return;
menu.style.display = "block";
positionMenu(event, menu);
oldOpenedMenu = menu;
}
function useContextMenu(menuId) {
const ref = useRef();
useEffect(() => {
const parent = ref.current;
const handler = (e) => contextMenuHandler(e, ref, menuId);
parent.addEventListener("contextmenu", handler);
window.onkeydown = onKeyDown;
window.onclick = onClick;
return () => {
parent.removeEventListener("contextmenu", handler);
};
});
return [ref, onClick, openMenu];
const clickCoords = getPosition(e);
const clickCoordsX = clickCoords.x;
const clickCoordsY = clickCoords.y;
const menuWidth = menu.offsetWidth + 4;
const menuHeight = menu.offsetHeight + 4;
const windowWidth = window.innerWidth;
const windowHeight = window.innerHeight;
if (windowWidth - clickCoordsX < menuWidth) {
menu.style.left = windowWidth - menuWidth + "px";
} else {
menu.style.left = clickCoordsX + "px";
}
if (windowHeight - clickCoordsY < menuHeight) {
menu.style.top = windowHeight - menuHeight + "px";
} else {
menu.style.top = clickCoordsY + "px";
}
menu.style.display = "block";
window.addEventListener("keydown", onKeyDown);
window.addEventListener("click", onWindowClick);
}
function getPosition(e) {
@@ -87,29 +87,24 @@ function getPosition(e) {
};
}
// updated positionMenu function
function positionMenu(e, menu) {
const clickCoords = getPosition(e);
const clickCoordsX = clickCoords.x;
const clickCoordsY = clickCoords.y;
const menuWidth = menu.offsetWidth + 4;
const menuHeight = menu.offsetHeight + 4;
const windowWidth = window.innerWidth;
const windowHeight = window.innerHeight;
if (windowWidth - clickCoordsX < menuWidth) {
menu.style.left = windowWidth - menuWidth + "px";
} else {
menu.style.left = clickCoordsX + "px";
}
if (windowHeight - clickCoordsY < menuHeight) {
menu.style.top = windowHeight - menuHeight + "px";
} else {
menu.style.top = clickCoordsY + "px";
}
function useContextMenu() {
const [items, setItems] = useState([]);
const [data, setData] = useState({});
const [title, setTitle] = useState();
useEffect(() => {
const onGlobalContextMenu = (e) => {
const { items, data, title, internalEvent } = e.detail;
setItems(items);
setData(data);
setTitle(title);
openMenu(internalEvent);
};
window.addEventListener("globalcontextmenu", onGlobalContextMenu);
return () => {
window.removeEventListener("globalcontextmenu", onGlobalContextMenu);
};
}, []);
return [items, data, title, closeMenu];
}
export default useContextMenu;

View File

@@ -1,11 +1,10 @@
import React, { useEffect } from "react";
import { Text, Box } from "rebass";
import { GroupedVirtuoso as GroupList } from "react-virtuoso";
import Note from "../components/note";
import { useStore, store } from "../stores/note-store";
import { useStore as useEditorStore } from "../stores/editor-store";
import ListContainer from "../components/list-container";
import NotesPlaceholder from "../components/placeholders/notesplacholder";
import { getNoteHeight } from "../common/height-calculator";
function Home() {
useEffect(() => store.refresh(), []);
@@ -14,42 +13,14 @@ function Home() {
return (
<ListContainer
items={notes.items}
type="notes"
item={(index, item) => (
<Note index={index} pinnable={false} item={item} />
)}
items={notes}
estimatedItemHeight={100}
itemHeight={getNoteHeight}
item={(index, item) => <Note index={index} pinnable item={item} />}
placeholder={NotesPlaceholder}
button={{ content: "Make a new note", onClick: () => newSession() }}
>
<GroupList
style={{
width: "100%",
flex: "1 1 auto",
height: "auto",
overflowX: "hidden",
}}
groupCounts={notes.groupCounts}
group={(groupIndex) => {
if (!notes.groups[groupIndex]) return;
return notes.groups[groupIndex].title === "Pinned" ? (
<Box px={2} bg="background" py={1} />
) : (
<Box mx={2} bg="background" py={0}>
<Text variant="heading" color="primary" fontSize="subtitle">
{notes.groups[groupIndex].title}
</Text>
</Box>
);
}}
item={(index, groupIndex) => {
if (!notes.groupCounts[groupIndex] || !notes.items[index]) return;
return (
<Note index={index} pinnable={true} item={notes.items[index]} />
);
}}
/>
</ListContainer>
/>
);
}
export default Home;

View File

@@ -1,9 +0,0 @@
export const Home = require("./home").default;
export const Notebooks = require("./notebooks").default;
export const Notes = require("./notes.js").default;
export const Topics = require("./topics").default;
export const Settings = require("./settings.js").default;
export const Trash = require("./trash").default;
export const Account = require("./Account").default;
export const Tags = require("./tags").default;
export const Search = require("./search").default;

View File

@@ -9,6 +9,7 @@ import Notes from "./notes.js";
import { useRoutes, navigate } from "hookrouter";
import RouteContainer from "../components/route-container";
import { db } from "../common";
import { getNotebookHeight } from "../common/height-calculator";
const routes = {
"/": () => (
@@ -51,7 +52,7 @@ function NotebooksContainer() {
return routeResult;
}
function Notebooks(props) {
function Notebooks() {
const [open, setOpen] = useState(false);
useEffect(() => store.refresh(), []);
const notebooks = useStore((state) => state.notebooks);
@@ -62,6 +63,8 @@ function Notebooks(props) {
<ListContainer
type="notebooks"
items={notebooks}
estimatedItemHeight={120}
itemHeight={getNotebookHeight}
item={(index, item) => (
<Notebook
index={index}

View File

@@ -4,6 +4,7 @@ import ListContainer from "../components/list-container";
import { useStore } from "../stores/editor-store";
import { useStore as useNotesStore } from "../stores/note-store";
import NotesPlaceholder from "../components/placeholders/notesplacholder";
import { getNoteHeight } from "../common/height-calculator";
function Notes(props) {
const newSession = useStore((store) => store.newSession);
@@ -20,6 +21,8 @@ function Notes(props) {
return (
<ListContainer
type="notes"
estimatedItemHeight={100}
itemHeight={getNoteHeight}
items={context.notes}
placeholder={props.placeholder || NotesPlaceholder}
item={(index, item) => (

View File

@@ -9,6 +9,7 @@ window.addEventListener("load", () => {
if (path === "/search") window.location = "/";
});
// TODO this will break for now.
function Search() {
const results = useStore((store) => store.results);
const item = useStore((store) => store.item);

View File

@@ -7,6 +7,7 @@ import TagsPlaceholder from "../components/placeholders/tags-placeholder";
import Notes from "./notes";
import { useRoutes, navigate } from "hookrouter";
import RouteContainer from "../components/route-container";
import { getItemHeight } from "../common/height-calculator";
function TagNode({ title }) {
return (
@@ -46,6 +47,8 @@ function Tags() {
<ListContainer
type="tags"
items={tags}
itemHeight={getItemHeight}
estimatedItemHeight={80}
item={(index, item) => {
const { title, noteIds } = item;
return (

View File

@@ -5,6 +5,7 @@ import ListContainer from "../components/list-container";
import { useStore as useNbStore } from "../stores/notebook-store";
import { showTopicDialog } from "../components/dialogs/topicdialog";
import { navigate } from "hookrouter";
import { getItemHeight } from "../common/height-calculator";
function Topics(props) {
const { notebookId } = props;
@@ -30,22 +31,13 @@ function Topics(props) {
<ListContainer
type="topics"
items={topics}
itemHeight={getItemHeight}
estimatedItemHeight={80}
item={(index, item) => (
<Topic
index={index}
item={item}
onClick={() => {
//let topic = item;
navigate(`/notebooks/${notebookId}/${index}`);
/* props.navigator.navigate("notes", {
title: props.notebook.title,
subtitle: topic.title,
context: {
type: "topic",
value: { id: props.notebook.id, topic: topic.title },
},
}); */
}}
onClick={() => navigate(`/notebooks/${notebookId}/${item.id}`)}
/>
)}
placeholder={Flex}

View File

@@ -10,6 +10,7 @@ import { toTitleCase } from "../utils/string";
import TrashPlaceholder from "../components/placeholders/trash-placeholder";
import { showToast } from "../utils/toast";
import { showPermanentDeleteToast } from "../common/toasts";
import { getNotebookHeight, getNoteHeight } from "../common/height-calculator";
function menuItems(item, index) {
return [
@@ -49,13 +50,18 @@ function Trash() {
<ListContainer
type="trash"
placeholder={TrashPlaceholder}
estimatedItemHeight={120}
itemHeight={(item) => {
if (item.type === "note") return getNoteHeight(item);
else if (item.type === "notebook") return getNotebookHeight(item);
}}
items={items}
item={(index, item) => (
<ListItem
selectable
item={item}
title={item.title}
body={item.headline}
body={item.headline || item.description}
index={index}
info={
<Flex variant="rowCenter">

View File

@@ -4639,11 +4639,6 @@ etag@~1.8.1:
resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887"
integrity sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=
event-source-polyfill@1.0.18:
version "1.0.18"
resolved "https://registry.yarnpkg.com/event-source-polyfill/-/event-source-polyfill-1.0.18.tgz#186488ddb81b4efce3e22f59c2fa68bd0df27c56"
integrity sha512-4ooLMUkUYFNXjYl96twwoMkXrhwmue5aI5ayU4ORswP2b8F6bYGNsdkYqutWV77DKJLI+ZRRLvZZTSuZcUCVTA==
eventemitter3@^2.0.3:
version "2.0.3"
resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-2.0.3.tgz#b5e1079b59fb5e1ba2771c0a993be060a58c99ba"
@@ -9004,7 +8999,7 @@ prompts@^2.0.1:
kleur "^3.0.3"
sisteransi "^1.0.4"
prop-types@^15.5.10, prop-types@^15.5.7, prop-types@^15.6.2, prop-types@^15.7.2:
prop-types@^15.5.10, prop-types@^15.6.2, prop-types@^15.7.2:
version "15.7.2"
resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.7.2.tgz#52c41e75b8c87e72b9d9360e0206b99dcbffa6c5"
integrity sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==
@@ -9281,11 +9276,6 @@ react-modal@^3.11.2:
react-lifecycles-compat "^3.0.0"
warning "^4.0.3"
react-placeholder@^4.0.3:
version "4.0.3"
resolved "https://registry.yarnpkg.com/react-placeholder/-/react-placeholder-4.0.3.tgz#098f8ee00122bfd73f1ce47d7d99de00384ab141"
integrity sha512-yPyqFYbh/u72p0UHsKTu19KruK/aJFP0x3YENg8ZBBjf13PoTKBQckVxlpLCfn0Rm2MaMly7IxZuRsUIQV+mrw==
react-scripts@3.4.1:
version "3.4.1"
resolved "https://registry.yarnpkg.com/react-scripts/-/react-scripts-3.4.1.tgz#f551298b5c71985cc491b9acf3c8e8c0ae3ada0a"
@@ -9346,26 +9336,11 @@ react-scripts@3.4.1:
optionalDependencies:
fsevents "2.1.2"
react-tiny-virtual-list@^2.2.0:
version "2.2.0"
resolved "https://registry.yarnpkg.com/react-tiny-virtual-list/-/react-tiny-virtual-list-2.2.0.tgz#eafb6fcf764e4ed41150ff9752cdaad8b35edf4a"
integrity sha512-MDiy2xyqfvkWrRiQNdHFdm36lfxmcLLKuYnUqcf9xIubML85cmYCgzBJrDsLNZ3uJQ5LEHH9BnxGKKSm8+C0Bw==
dependencies:
prop-types "^15.5.7"
react-virtualized-auto-sizer@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/react-virtualized-auto-sizer/-/react-virtualized-auto-sizer-1.0.2.tgz#a61dd4f756458bbf63bd895a92379f9b70f803bd"
integrity sha512-MYXhTY1BZpdJFjUovvYHVBmkq79szK/k7V3MO+36gJkWGkrXKtyr4vCPtpphaTLRAdDNoYEYFZWE8LjN+PIHNg==
react-virtuoso@^0.15.0:
version "0.15.0"
resolved "https://registry.yarnpkg.com/react-virtuoso/-/react-virtuoso-0.15.0.tgz#e429abb0179c3d0ff12860e984b4d3b5ea9ee735"
integrity sha512-hyGLs/Puxju73zThFD8JXpEbdF9b0p8FpovkA6gk83EqGoveh5FJsNwFshRzrJ8UAfaStoGMrJpt53Rzj4mp2A==
dependencies:
resize-observer-polyfill "^1.5.1"
tslib "^1.10.0"
react-window@^1.8.5:
version "1.8.5"
resolved "https://registry.yarnpkg.com/react-window/-/react-window-1.8.5.tgz#a56b39307e79979721021f5d06a67742ecca52d1"
@@ -9664,11 +9639,6 @@ requires-port@^1.0.0:
resolved "https://registry.yarnpkg.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff"
integrity sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=
resize-observer-polyfill@^1.5.1:
version "1.5.1"
resolved "https://registry.yarnpkg.com/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz#0e9020dd3d21024458d4ebd27e23e40269810464"
integrity sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg==
resolve-cwd@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/resolve-cwd/-/resolve-cwd-2.0.0.tgz#00a9f7387556e27038eae232caa372a6a59b665a"