web: fix attachments flicker in editor note properties

This commit is contained in:
Abdullah Atta
2024-07-19 12:23:58 +05:00
committed by Abdullah Atta
parent 920b1d91f3
commit 8fdef2ac7d
6 changed files with 123 additions and 104 deletions

View File

@@ -20,7 +20,6 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
import React, { useEffect } from "react";
import { useStore } from "./stores/app-store";
import { useStore as useUserStore } from "./stores/user-store";
import { useStore as useAttachmentStore } from "./stores/attachment-store";
import { useEditorStore } from "./stores/editor-store";
import { useStore as useAnnouncementStore } from "./stores/announcement-store";
import { resetNotices, scheduleBackups } from "./common/notices";
@@ -51,7 +50,6 @@ export default function AppEffects({ setShow }: AppEffectsProps) {
const isFocusMode = useStore((store) => store.isFocusMode);
const initUser = useUserStore((store) => store.init);
const initStore = useStore((store) => store.init);
const initAttachments = useAttachmentStore((store) => store.init);
const setIsVaultCreated = useStore((store) => store.setIsVaultCreated);
const initEditorStore = useEditorStore((store) => store.init);
const dialogAnnouncements = useAnnouncementStore(
@@ -77,7 +75,6 @@ export default function AppEffects({ setShow }: AppEffectsProps) {
);
initStore();
initAttachments();
initEditorStore();
(async function () {
@@ -102,7 +99,6 @@ export default function AppEffects({ setShow }: AppEffectsProps) {
[
initEditorStore,
initStore,
initAttachments,
updateLastSynced,
refreshNavItems,
initUser,

View File

@@ -209,7 +209,12 @@ export function Attachment({
<Text
as="td"
variant="body"
sx={{ color: status ? "accent" : "paragraph" }}
sx={{
color: status ? "accent" : "paragraph",
whiteSpace: "nowrap",
overflow: "hidden",
textOverflow: "ellipsis"
}}
>
{status ? (
<>

View File

@@ -666,7 +666,6 @@ function Attachments({ noteId }: { noteId: string }) {
<th style={{ width: "20%" }} />
</tr>
}
headerSize={0}
renderRow={({ index }) => (
<ResolvedItem index={index} type="attachment" items={result.value}>
{({ item }) => <ListItemWrapper item={item} compact />}

View File

@@ -17,9 +17,10 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { Virtualizer, useVirtualizer } from "@tanstack/react-virtual";
import { Box } from "@theme-ui/components";
import { Virtualizer } from "@tanstack/react-virtual";
import { Flex } from "@theme-ui/components";
import React, { useRef } from "react";
import { TableVirtuoso } from "react-virtuoso";
export type VirtualizedTableRowProps<T, C> = {
item: T;
@@ -36,12 +37,10 @@ type VirtualizedTableProps<T, C> = {
mode?: "fixed" | "dynamic";
items: T[];
estimatedSize: number;
headerSize: number;
getItemKey: (index: number) => string;
scrollElement?: Element | null;
scrollElement?: HTMLElement | null;
context?: C;
renderRow: (props: VirtualizedTableRowProps<T, C>) => JSX.Element | null;
scrollMargin?: number;
header: React.ReactNode;
style?: React.CSSProperties;
};
@@ -50,59 +49,95 @@ export function VirtualizedTable<T, C>(props: VirtualizedTableProps<T, C>) {
items,
getItemKey,
scrollElement,
scrollMargin,
headerSize,
renderRow: Row,
estimatedSize,
mode,
virtualizerRef,
header,
style,
context
context,
style
} = props;
const containerRef = useRef<HTMLDivElement>(null);
const virtualizer = useVirtualizer({
count: items.length,
estimateSize: () => estimatedSize,
getItemKey,
getScrollElement: () =>
scrollElement || containerRef.current?.closest(".ms-container") || null,
scrollMargin: scrollMargin || containerRef.current?.offsetTop || 0
});
if (virtualizerRef) virtualizerRef.current = virtualizer;
const virtualItems = virtualizer.getVirtualItems();
return (
<Box
<Flex
ref={containerRef}
sx={{
height: virtualizer.getTotalSize() + headerSize
}}
variant="columnFill"
sx={{ height: estimatedSize * items.length }}
>
<table style={style}>
<thead>{header}</thead>
<tbody>
{virtualItems.map((row, index) => (
<Row
key={row.key}
item={items[row.index]}
index={row.index}
rowRef={mode === "dynamic" ? virtualizer.measureElement : null}
context={context}
style={{
height: mode === "dynamic" ? "unset" : `${row.size}px`,
transform: `translateY(${
row.start -
index * row.size -
virtualizer.options.scrollMargin
}px)`
}}
/>
))}
</tbody>
</table>
</Box>
<TableVirtuoso
data={items}
context={context}
customScrollParent={
scrollElement ||
containerRef.current?.closest(".ms-container") ||
undefined
}
computeItemKey={(index) => getItemKey(index)}
defaultItemHeight={estimatedSize}
fixedHeaderContent={() => <>{header}</>}
fixedItemHeight={mode === "fixed" ? estimatedSize : undefined}
components={{
Table: (props) => (
<table {...props} style={{ ...style, ...props.style }} />
),
TableRow: (props) => {
return (
<Row
index={props["data-item-index"]}
item={props.item}
style={props.style || {}}
context={props.context}
/>
);
}
}}
/>
</Flex>
);
//
// const virtualizer = useVirtualizer({
// count: items.length,
// estimateSize: () => estimatedSize,
// getItemKey,
// getScrollElement: () =>
// scrollElement || containerRef.current?.closest(".ms-container") || null,
// scrollMargin: scrollMargin || containerRef.current?.offsetTop || 0
// });
// if (virtualizerRef) virtualizerRef.current = virtualizer;
// const virtualItems = virtualizer.getVirtualItems();
// return (
// <Box
// ref={containerRef}
// sx={{
// height: virtualizer.getTotalSize() + headerSize
// }}
// >
// <table style={style}>
// <thead>{header}</thead>
// <tbody>
// {virtualItems.map((row, index) => (
// <Row
// key={row.key}
// item={items[row.index]}
// index={row.index}
// rowRef={mode === "dynamic" ? virtualizer.measureElement : null}
// context={context}
// style={{
// height: mode === "dynamic" ? "unset" : `${row.size}px`,
// transform: `translateY(${
// row.start -
// index * row.size -
// virtualizer.options.scrollMargin
// }px)`
// }}
// />
// ))}
// </tbody>
// </table>
// </Box>
// );
}

View File

@@ -17,7 +17,7 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { useEffect, useState, memo, useRef } from "react";
import { useEffect, useState, memo, useRef, startTransition } from "react";
import {
Box,
Button,
@@ -29,7 +29,7 @@ import {
Text
} from "@theme-ui/components";
import { store, useStore } from "../stores/attachment-store";
import { ResolvedItem, formatBytes, usePromise } from "@notesnook/common";
import { formatBytes, usePromise, useResolvedItem } from "@notesnook/common";
import Dialog from "../components/dialog";
import {
ChevronDown,
@@ -66,6 +66,7 @@ import { FlexScrollContainer } from "../components/scroll-container";
import { BaseDialogProps, DialogManager } from "../common/dialog-manager";
import { ConfirmDialog } from "./confirm";
import { showToast } from "../utils/toast";
import { Loader } from "../components/loader";
type ToolbarAction = {
title: string;
@@ -107,7 +108,7 @@ type SortOptions = {
type AttachmentsDialogProps = BaseDialogProps<false>;
export const AttachmentsDialog = DialogManager.register(
function AttachmentsDialog({ onClose }: AttachmentsDialogProps) {
const allAttachments = useStore((store) => store.attachments);
const nonce = useStore((store) => store.nonce);
const [attachments, setAttachments] =
useState<VirtualizedGrouping<AttachmentType>>();
const [counts, setCounts] = useState<Record<Route, number>>({
@@ -125,29 +126,20 @@ export const AttachmentsDialog = DialogManager.register(
direction: "asc"
});
const currentRoute = useRef<Route>("all");
const refresh = useStore((store) => store.refresh);
const download = useStore((store) => store.download);
useEffect(() => {
refresh();
}, [refresh]);
filterAttachments(currentRoute.current)
.sorted({
sortBy: sortBy.id,
sortDirection: sortBy.direction
})
.then((value) => startTransition(() => setAttachments(value)));
}, [sortBy, nonce]);
useEffect(() => {
(async function () {
setAttachments(
await filterAttachments(currentRoute.current).sorted({
sortBy: sortBy.id,
sortDirection: sortBy.direction
})
);
})();
}, [sortBy, allAttachments]);
useEffect(() => {
(async function () {
setCounts(await getCounts());
})();
}, [allAttachments]);
getCounts().then((counts) => startTransition(() => setCounts(counts)));
}, [nonce]);
return (
<Dialog
@@ -234,7 +226,7 @@ export const AttachmentsDialog = DialogManager.register(
</Text>
</Button> */}
</Flex>
{attachments && (
{attachments ? (
<VirtualizedTable
style={{
tableLayout: "fixed",
@@ -327,7 +319,6 @@ export const AttachmentsDialog = DialogManager.register(
}
mode="fixed"
estimatedSize={30}
headerSize={40}
getItemKey={(index) => attachments.key(index)}
items={attachments.placeholders}
context={{
@@ -345,6 +336,8 @@ export const AttachmentsDialog = DialogManager.register(
}}
renderRow={AttachmentRow}
/>
) : (
<Loader title="Loading attachments..." />
)}
</FlexScrollContainer>
</Flex>
@@ -364,23 +357,21 @@ function AttachmentRow(
}
>
) {
if (!props.context) return null;
const item = useResolvedItem({
index: props.index,
items: props.context!.attachments,
type: "attachment"
});
if (!item) return null;
return (
<ResolvedItem
index={props.index}
items={props.context.attachments}
type="attachment"
>
{({ item }) => (
<Attachment
rowRef={props.rowRef}
style={props.style}
item={item}
isSelected={props.context?.isSelected(item.id)}
onSelected={() => props.context?.select(item.id)}
/>
)}
</ResolvedItem>
<Attachment
key={item.item.id}
rowRef={props.rowRef}
style={props.style}
item={item?.item}
isSelected={props.context?.isSelected(item.item.id)}
onSelected={() => props.context?.select(item.item.id)}
/>
);
}

View File

@@ -27,11 +27,11 @@ import { showToast } from "../utils/toast";
import { AttachmentStream } from "../utils/streams/attachment-stream";
import { createZipStream } from "../utils/streams/zip-stream";
import { createWriteStream } from "../utils/stream-saver";
import { Attachment, VirtualizedGrouping } from "@notesnook/core";
import { Attachment } from "@notesnook/core";
let abortController: AbortController | undefined = undefined;
class AttachmentStore extends BaseStore<AttachmentStore> {
attachments?: VirtualizedGrouping<Attachment>;
nonce = 0;
status?: { current: number; total: number };
processing: Record<
string,
@@ -40,17 +40,10 @@ class AttachmentStore extends BaseStore<AttachmentStore> {
refresh = async () => {
this.set({
attachments: await db.attachments.all.sorted({
sortBy: "dateCreated",
sortDirection: "desc"
})
nonce: this.get().nonce + 1
});
};
init = () => {
this.refresh();
};
download = async (ids: string[]) => {
if (this.get().status)
throw new Error(