Skip to content

Commit

Permalink
refactor: prevent rendering
Browse files Browse the repository at this point in the history
  • Loading branch information
pyphilia committed Jul 29, 2024
1 parent 897af29 commit 6e60425
Show file tree
Hide file tree
Showing 5 changed files with 176 additions and 125 deletions.
8 changes: 7 additions & 1 deletion src/components/item/FolderContent.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Alert, Box, Stack, Typography } from '@mui/material';
import { Alert, Box, Stack, Typography, useTheme } from '@mui/material';

import {
PackedItem,
Expand Down Expand Up @@ -27,6 +27,7 @@ import {
SelectionContextProvider,
useSelectionContext,
} from '../main/list/SelectionContext';
import { useDragSelection } from '../main/list/useDragSelection';
import { DesktopMap } from '../map/DesktopMap';
import NoItemFilters from '../pages/NoItemFilters';
import SortingSelect from '../table/SortingSelect';
Expand All @@ -49,6 +50,10 @@ const Content = ({ item, searchText, items, sortBy }: Props) => {
const { itemTypes } = useFilterItemsContext();
const { selectedIds, clearSelection, toggleSelection } =
useSelectionContext();
const theme = useTheme();
const DragSelection = useDragSelection({
adjustments: { marginTop: 70, marginLeft: theme.spacing(3) },
});

const enableEditing = item.permission
? PermissionLevelCompare.lte(PermissionLevel.Write, item.permission)
Expand Down Expand Up @@ -86,6 +91,7 @@ const Content = ({ item, searchText, items, sortBy }: Props) => {
/>
</Stack>
)}
<DragSelection />
</>
);
}
Expand Down
81 changes: 17 additions & 64 deletions src/components/main/list/SelectionContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,34 +7,24 @@ import {
useState,
} from 'react';

import { PRIMARY_COLOR } from '@graasp/ui';

import {
Box,
boxesIntersect,
useSelectionContainer,
} from '@air/react-drag-to-select';

import { ITEM_CARD_CLASS } from '@/config/selectors';

type SelectionContextValue = {
selectedIds: string[];
toggleSelection: (id: string) => void;
addToSelection: (id: string) => void;
clearSelection: () => void;
};

export const SelectionContext = createContext<SelectionContextValue>({
selectedIds: [],
toggleSelection: () => {},
addToSelection: () => {},
clearSelection: () => {},
});

export const SelectionContextProvider = ({
children,
elementClass = ITEM_CARD_CLASS,
}: {
children: JSX.Element;
elementClass?: string;
}): JSX.Element => {
const [selection, setSelection] = useState(new Set<string>());
const elementsContainerRef = useRef<HTMLDivElement | null>(null);
Expand All @@ -55,72 +45,35 @@ export const SelectionContextProvider = ({
[selection],
);

const { DragSelection } = useSelectionContainer({
eventsElement: document.getElementById('root'),
onSelectionChange: (box) => {
/**
* Here we make sure to adjust the box's left and top with the scroll position of the window
* @see https://github.com/AirLabsTeam/react-drag-to-select/#scrolling
*/
const scrollAwareBox: Box = {
...box,
top: box.top + window.scrollY,
left: box.left + window.scrollX,
};

Array.from(document.getElementsByClassName(elementClass)).forEach(
(item) => {
const bb = item.getBoundingClientRect();
if (
boxesIntersect(scrollAwareBox, bb) &&
item.parentNode instanceof HTMLElement
) {
const itemId = item.parentNode.dataset.id;
if (itemId) {
selection.add(itemId);
}
}
},
);

setSelection(new Set(selection));
},
shouldStartSelecting: (e) => {
// does not trigger drag selection if mousedown on card
if (e instanceof HTMLElement) {
return !e?.closest(`.${ITEM_CARD_CLASS}`);
const addToSelection = useCallback(
(id: string) => {
if (!selection.has(id)) {
selection.add(id);
setSelection(new Set(selection));
}
return true;
},
onSelectionStart: () => {
// clear selection on new dragging action
clearSelection();
},
onSelectionEnd: () => {},
selectionProps: {
style: {
border: `2px dashed ${PRIMARY_COLOR}`,
borderRadius: 4,
backgroundColor: 'lightblue',
opacity: 0.5,
},
},
isEnabled: true,
});
[selection],
);

const value: SelectionContextValue = useMemo(
() => ({
selectedIds: [...selection.values()],
toggleSelection,
clearSelection,
elementsContainerRef,
addToSelection,
}),
[selection, toggleSelection, clearSelection, elementsContainerRef],
[
selection,
toggleSelection,
clearSelection,
addToSelection,
elementsContainerRef,
],
);

return (
<SelectionContext.Provider value={value}>
<DragSelection />
<div ref={elementsContainerRef}>{children}</div>
</SelectionContext.Provider>
);
Expand Down
76 changes: 76 additions & 0 deletions src/components/main/list/useDragSelection.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import { ReactElement } from 'react';

import { PRIMARY_COLOR } from '@graasp/ui';

import {
Box,
boxesIntersect,
useSelectionContainer,
} from '@air/react-drag-to-select';

import { ITEM_CARD_CLASS } from '@/config/selectors';

import { useSelectionContext } from './SelectionContext';

export const useDragSelection = ({
elementClass = ITEM_CARD_CLASS,
adjustments = {},
} = {}): (() => ReactElement) => {
const { addToSelection, clearSelection } = useSelectionContext();

const { DragSelection } = useSelectionContainer({
eventsElement: document.getElementById('root'),
onSelectionChange: (box) => {
/**
* Here we make sure to adjust the box's left and top with the scroll position of the window
* @see https://github.com/AirLabsTeam/react-drag-to-select/#scrolling
*/
const scrollAwareBox: Box = {
...box,
top: box.top + window.scrollY,
left: box.left + window.scrollX,
};

Array.from(document.getElementsByClassName(elementClass)).forEach(
(item) => {
const bb = item.getBoundingClientRect();
if (
boxesIntersect(scrollAwareBox, bb) &&
item.parentNode instanceof HTMLElement
) {
const itemId = item.parentNode.dataset.id;
if (itemId) {
addToSelection(itemId);
}
}
},
);
},
shouldStartSelecting: (e) => {
// does not trigger drag selection if mousedown on card
if (e instanceof HTMLElement) {
return !e?.closest(`.${elementClass}`);
}
return true;
},
onSelectionStart: () => {
// clear selection on new dragging action
clearSelection();
},
onSelectionEnd: () => {},
selectionProps: {
style: {
// adjustement
// https://github.com/AirLabsTeam/react-drag-to-select/issues/30
...adjustments,
border: `2px dashed ${PRIMARY_COLOR}`,
borderRadius: 4,
backgroundColor: 'lightblue',
opacity: 0.5,
},
},
isEnabled: true,
});

return DragSelection;
};
127 changes: 68 additions & 59 deletions src/components/pages/RecycledItemsScreen.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Alert, Stack } from '@mui/material';
import { Alert, Stack, useTheme } from '@mui/material';

import { Ordering } from '@/enums';

Expand All @@ -21,6 +21,7 @@ import {
SelectionContextProvider,
useSelectionContext,
} from '../main/list/SelectionContext';
import { useDragSelection } from '../main/list/useDragSelection';
import ItemCard from '../table/ItemCard';
import SortingSelect from '../table/SortingSelect';
import { SortingOptions } from '../table/types';
Expand All @@ -37,6 +38,7 @@ const RecycledItemsScreenContent = ({
const { data: recycledItems, isLoading, isError } = hooks.useRecycledItems();
const options = useTranslatedSortingOptions();
const { shouldDisplayItem } = useFilterItemsContext();
const theme = useTheme();
const { sortBy, setSortBy, ordering, setOrdering, sortFn } =
useSorting<SortingOptions>({
sortBy: SortingOptions.ItemUpdatedAt,
Expand All @@ -51,70 +53,77 @@ const RecycledItemsScreenContent = ({
?.sort(sortFn);
const { selectedIds, toggleSelection } = useSelectionContext();

const DragSelection = useDragSelection({
adjustments: { marginTop: 70, marginLeft: theme.spacing(3) },
});

// render this when there is data from the query
if (recycledItems?.length) {
const hasSelection = selectedIds.length && filteredData?.length;
return (
<Stack gap={1}>
<Stack
alignItems="space-between"
direction="column"
gap={1}
width="100%"
>
{hasSelection ? (
<RecycleBinToolbar items={filteredData} />
) : (
<Stack
spacing={1}
direction="row"
justifyContent="space-between"
alignItems="center"
>
<SelectTypes />
<Stack direction="row" gap={1}>
{sortBy && setSortBy && (
<SortingSelect
sortBy={sortBy}
options={options}
setSortBy={setSortBy}
ordering={ordering}
setOrdering={setOrdering}
/>
)}
<ModeButton />
<>
<Stack gap={1}>
<Stack
alignItems="space-between"
direction="column"
gap={1}
width="100%"
>
{hasSelection ? (
<RecycleBinToolbar items={filteredData} />
) : (
<Stack
spacing={1}
direction="row"
justifyContent="space-between"
alignItems="center"
>
<SelectTypes />
<Stack direction="row" gap={1}>
{sortBy && setSortBy && (
<SortingSelect
sortBy={sortBy}
options={options}
setSortBy={setSortBy}
ordering={ordering}
setOrdering={setOrdering}
/>
)}
<ModeButton />
</Stack>
</Stack>
</Stack>
)}
)}
</Stack>
{
// render the filtered data and when it is empty display that nothing matches the search
filteredData?.length ? (
filteredData.map((item) => (
<ItemCard
// todo: should not be able to click on the card
item={item}
onThumbnailClick={() => toggleSelection(item.id)}
isSelected={selectedIds.includes(item.id)}
showThumbnail={false}
allowNavigation={false}
footer={
<Stack justifyContent="right" direction="row">
<RestoreButton itemIds={[item.id]} />
<DeleteButton items={[item]} />
</Stack>
}
/>
))
) : (
<Alert severity="info">
{translateBuilder(BUILDER.TRASH_NO_ITEM_SEARCH, {
search: searchText,
})}
</Alert>
)
}
</Stack>
{
// render the filtered data and when it is empty display that nothing matches the search
filteredData?.length ? (
filteredData.map((item) => (
<ItemCard
// todo: should not be able to click on the card
item={item}
onThumbnailClick={() => toggleSelection(item.id)}
isSelected={selectedIds.includes(item.id)}
showThumbnail={false}
allowNavigation={false}
footer={
<Stack justifyContent="right" direction="row">
<RestoreButton itemIds={[item.id]} />
<DeleteButton items={[item]} />
</Stack>
}
/>
))
) : (
<Alert severity="info">
{translateBuilder(BUILDER.TRASH_NO_ITEM_SEARCH, {
search: searchText,
})}
</Alert>
)
}
</Stack>
<DragSelection />
</>
);
}

Expand Down
Loading

0 comments on commit 6e60425

Please sign in to comment.