Skip to content

Commit

Permalink
Statement list virtualized (#2435)
Browse files Browse the repository at this point in the history
* Create row is visible hook

* Integrate hook into statement list row, fix some stylings

* Refactor entity tag

* Isolate expand button on row to prevent column reload on row select

* Improve hidden table styles

* theme loader

* Fix row dragging

* shorter context menu timeout on mouse leave

---------

Co-authored-by: Petr Hanak <petr.nahak@gmail.com>
  • Loading branch information
adammertel and Ptrhnk authored Nov 13, 2024
1 parent a315fc6 commit ff5a327
Show file tree
Hide file tree
Showing 11 changed files with 390 additions and 291 deletions.
2 changes: 1 addition & 1 deletion packages/client/src/Theme/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ export const fourthPanelBoxesHeightThirds = {

// the minimum pixels for the results section height
export const MIN_SEARCH_RESULT_HEIGHT = 100;
export const COLLAPSED_TABLE_WIDTH = 90;
export const COLLAPSED_TABLE_WIDTH = 80;

// LIMITS
export const maxTabCount = 10;
Expand Down
21 changes: 7 additions & 14 deletions packages/client/src/components/advanced/EntityTag/EntityTag.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { IEntity } from "@shared/types";
import { ThemeColor } from "Theme/theme";
import { Button, Tag } from "components";
import { EntityTooltip } from "components/advanced";
import React, { ReactNode, useCallback, useState } from "react";
import React, { ReactNode, useCallback, useRef, useState } from "react";
import { FaUnlink } from "react-icons/fa";
import { useAppSelector } from "redux/hooks";
import { DraggedEntityReduxItem, EntityDragItem } from "types";
Expand Down Expand Up @@ -78,21 +78,14 @@ export const EntityTag: React.FC<EntityTag> = ({
}

const classId = entity.class;

const [buttonHovered, setButtonHovered] = useState(false);
const [elvlHovered, setElvlHovered] = useState(false);

const [referenceElement, setReferenceElement] =
useState<HTMLDivElement | null>(null);
const [tagHovered, setTagHovered] = useState(false);

const handleSetReferenceElement = useCallback(
(node: HTMLDivElement | null) => {
setReferenceElement(node);
},
[]
);
const referenceEl = useRef<HTMLDivElement | null>(null);

const renderUnlinkButton = useCallback((unlinkButton: UnlinkButton) => {
const renderUnlinkButton = (unlinkButton: UnlinkButton) => {
return (
<Button
key="d"
Expand All @@ -107,7 +100,7 @@ export const EntityTag: React.FC<EntityTag> = ({
onClick={unlinkButton.onClick}
/>
);
}, []);
};

if (!isValidEntityClass(entity.class)) {
// labels needs to have length and first label needs to be non-empty
Expand Down Expand Up @@ -141,7 +134,7 @@ export const EntityTag: React.FC<EntityTag> = ({
<>
<StyledEntityTagWrap
$flexListMargin={flexListMargin}
ref={handleSetReferenceElement}
ref={referenceEl}
onMouseEnter={handleTagHovered}
onMouseLeave={handleTagUnhovered}
>
Expand Down Expand Up @@ -171,7 +164,7 @@ export const EntityTag: React.FC<EntityTag> = ({
Object.keys(draggedEntity).length !== 0
}
tagHovered={tagHovered}
referenceElement={referenceElement}
referenceElement={referenceEl.current}
customTooltipAttributes={customTooltipAttributes}
/>
)}
Expand Down
249 changes: 95 additions & 154 deletions packages/client/src/components/basic/Tag/Tag.tsx
Original file line number Diff line number Diff line change
@@ -1,32 +1,13 @@
import { EntityEnums, InterfaceEnums } from "@shared/enums";
import { EntityEnums } from "@shared/enums";
import { IEntity } from "@shared/types";
import { useSearchParams } from "hooks";
import React, {
ReactNode,
useContext,
useEffect,
useMemo,
useRef,
useState,
} from "react";
import {
DragSourceMonitor,
DropTargetMonitor,
useDrag,
useDrop,
} from "react-dnd";
import React, { ReactNode, useEffect, useMemo, useRef, useState } from "react";
import { FaStar } from "react-icons/fa";
import { toast } from "react-toastify";
import { setDraggedEntity } from "redux/features/territoryTree/draggedEntitySlice";
import { useAppDispatch, useAppSelector } from "redux/hooks";
import { ThemeContext } from "styled-components";
import {
DraggedEntityReduxItem,
EntityColors,
EntityDragItem,
ItemTypes,
} from "types";
import { dndHoverFn, getShortLabelByLetterCount } from "utils/utils";
import theme from "Theme/theme";
import { DraggedEntityReduxItem, EntityColors, EntityDragItem } from "types";
import { getShortLabelByLetterCount } from "utils/utils";
import {
StyledButtonWrapper,
StyledElvlWrapper,
Expand All @@ -36,6 +17,7 @@ import {
StyledStarWrap,
StyledTagWrapper,
} from "./TagStyles";
import useDragDrop from "./useDragDrop";

interface TagProps {
propId: string;
Expand Down Expand Up @@ -106,62 +88,26 @@ export const Tag: React.FC<TagProps> = ({
const draggedEntity: DraggedEntityReduxItem = useAppSelector(
(state) => state.draggedEntity
);
const selectedThemeId: InterfaceEnums.Theme = useAppSelector(
(state) => state.theme
);

const [clickedOnce, setClickedOnce] = useState(false);
const ref = useRef<HTMLDivElement>(null);

const [, drop] = useDrop<EntityDragItem>({
accept: ItemTypes.TAG,
hover(item: EntityDragItem, monitor: DropTargetMonitor) {
// TODO: debounce?
if (moveFn && draggedEntity && draggedEntity.lvl === lvl) {
dndHoverFn(item, index, monitor, ref, moveFn);
}
},
});

const canDrag = useMemo(
() => entityClass !== EntityEnums.Extension.Empty && !disableDrag,
[entityClass, disableDrag]
);

const [{ isDragging }, drag] = useDrag<
EntityDragItem,
unknown,
{ isDragging: boolean }
>({
type: ItemTypes.TAG,
item: {
id: propId,
index,
entityClass: entityClass as EntityEnums.Class,
isTemplate,
isDiscouraged,
entity: entity || false,
},
collect: (monitor: DragSourceMonitor) => ({
isDragging: monitor.isDragging(),
}),
end: (item: EntityDragItem | undefined, monitor: DragSourceMonitor) => {
if (item && item.index !== index) updateOrderFn(item);
},
canDrag: canDrag,
const [isDragging, canDrag, drag, drop] = useDragDrop({
entity,
isTemplate,
isDiscouraged,
propId,
entityClass,
disableDrag,
index,
lvl,
updateOrderFn,
draggedEntity,
dispatch,
moveFn,
ref,
});

useEffect(() => {
if (isDragging) {
dispatch(setDraggedEntity({ id: propId, parentId, index, lvl }));
} else {
dispatch(setDraggedEntity({}));
}
}, [isDragging]);

drag(drop(ref));

const [clickedOnce, setClickedOnce] = useState(false);

useEffect(() => {
if (clickedOnce) {
const timeout = setTimeout(() => {
Expand All @@ -174,118 +120,113 @@ export const Tag: React.FC<TagProps> = ({
);
setClickedOnce(false);
}, 500);

return () => clearTimeout(timeout);
}
}, [clickedOnce]);

const renderEntityTag = () => (
<StyledEntityTag
$color={
entityClass !== EntityEnums.Extension.Invalid
? EntityColors[entityClass].color
: "white"
}
$isTemplate={isTemplate}
$darkTheme={selectedThemeId === InterfaceEnums.Theme.Dark}
>
{entityClass}
</StyledEntityTag>
);

const renderElvl = () => (
<StyledElvlWrapper>{elvlButtonGroup}</StyledElvlWrapper>
);

const renderButton = () => (
<StyledButtonWrapper
$status={status}
onMouseEnter={onButtonOver}
onMouseLeave={onButtonOut}
onClick={onBtnClick}
>
{button}
</StyledButtonWrapper>
);

const onDoubleClick = (e: React.MouseEvent) => {
e.stopPropagation();
setClickedOnce(false);

!disableDoubleClick && appendDetailId(propId);
};
const renderTag = useMemo(() => {
const entityTag = (
<StyledEntityTag
$color={
entityClass !== EntityEnums.Extension.Invalid
? EntityColors[entityClass].color
: "white"
}
$isTemplate={isTemplate}
$darkTheme={true}
>
{entityClass}
</StyledEntityTag>
);

const themeContext = useContext(ThemeContext);
const elvlWrapper = elvlButtonGroup && (
<StyledElvlWrapper>{elvlButtonGroup}</StyledElvlWrapper>
);

const renderLabel = (labelOnly: boolean = false) => {
return (
const labelWrap = (
<StyledLabelWrap $invertedLabel={invertedLabel}>
{isFavorited && (
<StyledStarWrap>
<FaStar
color={themeContext?.color.warning}
color={theme.color.warning}
style={{ marginBottom: "0.1rem" }}
/>
</StyledStarWrap>
)}
<StyledLabel
$invertedLabel={invertedLabel}
$status={status}
$labelOnly={showOnly === "label"}
$borderStyle={borderStyle}
$fullWidth={fullWidth}
$isFavorited={isFavorited}
$labelOnly={labelOnly}
$isItalic={labelItalic}
>
{label}
</StyledLabel>
</StyledLabelWrap>
);
};

const renderShortTag = () => {
return (
<>
{showOnly === "entity" ? (
<>{renderEntityTag()}</>
) : (
<>{renderLabel(true)}</>
)}
{button && renderButton()}
</>
const buttonWrap = button && (
<StyledButtonWrapper
$status={status}
onMouseEnter={onButtonOver}
onMouseLeave={onButtonOut}
onClick={onBtnClick}
>
{button}
</StyledButtonWrapper>
);
};

const renderFullTag = () => {
return (
return showOnly ? (
<>
{showOnly === "entity" ? entityTag : labelWrap}
{buttonWrap}
</>
) : (
<>
{renderEntityTag()}
{renderLabel()}
{elvlButtonGroup && renderElvl()}
{button && renderButton()}
{entityTag}
{labelWrap}
{elvlWrapper}
{buttonWrap}
</>
);
};
}, [
entityClass,
isFavorited,
invertedLabel,
label,
labelItalic,
elvlButtonGroup,
borderStyle,
fullWidth,
showOnly,
status,
button,
isTemplate,
onButtonOver,
onButtonOut,
onBtnClick,
]);

return (
<>
<StyledTagWrapper
className="tag"
ref={ref}
$dragDisabled={!canDrag}
$status={status}
$ltype={ltype}
$borderStyle={borderStyle}
onClick={(e: React.MouseEvent) => {
e.stopPropagation();
if (!disableCopyLabel) {
setClickedOnce(true);
}
}}
onDoubleClick={(e: React.MouseEvent) => onDoubleClick(e)}
>
{showOnly ? <>{renderShortTag()}</> : <>{renderFullTag()}</>}
</StyledTagWrapper>
</>
<StyledTagWrapper
className="tag"
ref={ref}
$dragDisabled={!canDrag}
$status={status}
$ltype={ltype}
$borderStyle={borderStyle}
onClick={(e) => {
e.stopPropagation();
if (!disableCopyLabel) setClickedOnce(true);
}}
onDoubleClick={(e) => {
e.stopPropagation();
!disableDoubleClick && appendDetailId(propId);
}}
>
{renderTag}
</StyledTagWrapper>
);
};
Loading

0 comments on commit ff5a327

Please sign in to comment.