mirror of
https://github.com/makeplane/plane
synced 2024-06-14 14:31:34 +00:00
Virtualization like core changes with intersection observer
This commit is contained in:
parent
7d08a57be6
commit
08487fb7d4
9
packages/types/src/issues.d.ts
vendored
9
packages/types/src/issues.d.ts
vendored
@ -221,3 +221,12 @@ export interface IGroupByColumn {
|
|||||||
export interface IIssueMap {
|
export interface IIssueMap {
|
||||||
[key: string]: TIssue;
|
[key: string]: TIssue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface IIssueListRow {
|
||||||
|
id: string;
|
||||||
|
groupId: string;
|
||||||
|
type: "HEADER" | "NO_ISSUES" | "QUICK_ADD" | "ISSUE";
|
||||||
|
name?: string;
|
||||||
|
icon?: ReactElement | undefined;
|
||||||
|
payload?: Partial<TIssue>;
|
||||||
|
}
|
||||||
|
74
web/components/core/render-if-visible-HOC.tsx
Normal file
74
web/components/core/render-if-visible-HOC.tsx
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
import { cn } from "helpers/common.helper";
|
||||||
|
import React, { useState, useRef, useEffect, ReactNode, MutableRefObject } from "react";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
defaultHeight?: number;
|
||||||
|
verticalOffset?: number;
|
||||||
|
horizonatlOffset?: number;
|
||||||
|
root?: MutableRefObject<HTMLElement | null>;
|
||||||
|
children: ReactNode;
|
||||||
|
as?: keyof JSX.IntrinsicElements;
|
||||||
|
classNames?: string;
|
||||||
|
alwaysRender?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
const RenderIfVisible: React.FC<Props> = (props) => {
|
||||||
|
const {
|
||||||
|
defaultHeight = 300,
|
||||||
|
root,
|
||||||
|
verticalOffset = 50,
|
||||||
|
horizonatlOffset = 0,
|
||||||
|
as = "div",
|
||||||
|
children,
|
||||||
|
classNames = "",
|
||||||
|
alwaysRender = false,
|
||||||
|
...others
|
||||||
|
} = props;
|
||||||
|
const [shouldVisible, setShouldVisible] = useState<boolean>(alwaysRender);
|
||||||
|
const placeholderHeight = useRef<number>(defaultHeight);
|
||||||
|
const intersectionRef = useRef<HTMLElement | null>(null);
|
||||||
|
|
||||||
|
const isVisible = alwaysRender || shouldVisible;
|
||||||
|
|
||||||
|
// Set visibility with intersection observer
|
||||||
|
useEffect(() => {
|
||||||
|
if (intersectionRef.current) {
|
||||||
|
const observer = new IntersectionObserver(
|
||||||
|
(entries) => {
|
||||||
|
if (typeof window !== undefined && window.requestIdleCallback) {
|
||||||
|
window.requestIdleCallback(() => setShouldVisible(entries[0].isIntersecting), {
|
||||||
|
timeout: 600,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
setShouldVisible(entries[0].isIntersecting);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
root: root?.current,
|
||||||
|
rootMargin: `${verticalOffset}% ${horizonatlOffset}% ${verticalOffset}% ${horizonatlOffset}%`,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
observer.observe(intersectionRef.current);
|
||||||
|
return () => {
|
||||||
|
if (intersectionRef.current) {
|
||||||
|
observer.unobserve(intersectionRef.current);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}, [root?.current, intersectionRef, children]);
|
||||||
|
|
||||||
|
// Set height after render
|
||||||
|
useEffect(() => {
|
||||||
|
if (intersectionRef.current && isVisible) {
|
||||||
|
placeholderHeight.current = intersectionRef.current.offsetHeight;
|
||||||
|
}
|
||||||
|
}, [isVisible, intersectionRef, alwaysRender]);
|
||||||
|
|
||||||
|
const child = isVisible ? <>{children}</> : null;
|
||||||
|
const style = isVisible ? {} : { height: placeholderHeight.current, width: "100%" };
|
||||||
|
const className = isVisible ? classNames : cn(classNames, "animate-pulse");
|
||||||
|
|
||||||
|
return React.createElement(as, { ref: intersectionRef, style, className }, child);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default RenderIfVisible;
|
@ -1,10 +1,10 @@
|
|||||||
import { Avatar, PriorityIcon, StateGroupIcon } from "@plane/ui";
|
import { Avatar, PriorityIcon, StateGroupIcon } from "@plane/ui";
|
||||||
import { ISSUE_PRIORITIES } from "constants/issue";
|
import { EIssueListRow, ISSUE_PRIORITIES } from "constants/issue";
|
||||||
import { renderEmoji } from "helpers/emoji.helper";
|
import { renderEmoji } from "helpers/emoji.helper";
|
||||||
import { IMemberRootStore } from "store/member";
|
import { IMemberRootStore } from "store/member";
|
||||||
import { IProjectStore } from "store/project/project.store";
|
import { IProjectStore } from "store/project/project.store";
|
||||||
import { IStateStore } from "store/state.store";
|
import { IStateStore } from "store/state.store";
|
||||||
import { GroupByColumnTypes, IGroupByColumn } from "@plane/types";
|
import { GroupByColumnTypes, IGroupByColumn, IIssueListRow, TGroupedIssues, TUnGroupedIssues } from "@plane/types";
|
||||||
import { STATE_GROUPS } from "constants/state";
|
import { STATE_GROUPS } from "constants/state";
|
||||||
import { ILabelStore } from "store/label.store";
|
import { ILabelStore } from "store/label.store";
|
||||||
|
|
||||||
@ -155,3 +155,47 @@ const getCreatedByColumns = (member: IMemberRootStore) => {
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export function getIssueFlatList(
|
||||||
|
groups: IGroupByColumn[],
|
||||||
|
issueIds: TGroupedIssues | TUnGroupedIssues,
|
||||||
|
showEmptyGroup: boolean
|
||||||
|
): IIssueListRow[] | undefined {
|
||||||
|
let list: IIssueListRow[] = [];
|
||||||
|
|
||||||
|
if (Array.isArray(issueIds)) {
|
||||||
|
return wrapIssuesWithHeaderAndFooter(groups[0], issueIds, showEmptyGroup);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const group of groups) {
|
||||||
|
const groupList = wrapIssuesWithHeaderAndFooter(group, issueIds[group.id], showEmptyGroup);
|
||||||
|
|
||||||
|
if (!groupList) continue;
|
||||||
|
|
||||||
|
list = list.concat(groupList);
|
||||||
|
}
|
||||||
|
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
|
function wrapIssuesWithHeaderAndFooter(
|
||||||
|
group: IGroupByColumn,
|
||||||
|
issueIds: string[],
|
||||||
|
showEmptyGroup: boolean
|
||||||
|
): IIssueListRow[] | undefined {
|
||||||
|
const header: IIssueListRow = { ...group, groupId: group.id, type: EIssueListRow.HEADER };
|
||||||
|
const quickAdd: IIssueListRow = { ...group, groupId: group.id, type: EIssueListRow.QUICK_ADD };
|
||||||
|
if (issueIds && issueIds.length > 0) {
|
||||||
|
const list: IIssueListRow[] = [header];
|
||||||
|
|
||||||
|
for (const issueId of issueIds) {
|
||||||
|
list.push({ id: issueId, groupId: group.id, type: EIssueListRow.ISSUE });
|
||||||
|
}
|
||||||
|
|
||||||
|
list.push(quickAdd);
|
||||||
|
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (showEmptyGroup) return [header, { id: group.id, groupId: group.id, type: EIssueListRow.NO_ISSUES }, quickAdd];
|
||||||
|
}
|
@ -412,6 +412,13 @@ export const ISSUE_DISPLAY_FILTERS_BY_LAYOUT: {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export enum EIssueListRow {
|
||||||
|
HEADER = "HEADER",
|
||||||
|
ISSUE = "ISSUE",
|
||||||
|
NO_ISSUES = "NO_ISSUES",
|
||||||
|
QUICK_ADD = "QUICK_ADD",
|
||||||
|
}
|
||||||
|
|
||||||
export const getValueFromObject = (object: Object, key: string): string | number | boolean | null => {
|
export const getValueFromObject = (object: Object, key: string): string | number | boolean | null => {
|
||||||
const keys = key ? key.split(".") : [];
|
const keys = key ? key.split(".") : [];
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user