mirror of
https://github.com/jeffvli/feishin.git
synced 2026-05-08 04:50:12 +02:00
add new grid carousels
This commit is contained in:
@@ -0,0 +1,223 @@
|
||||
import type { Variants } from 'motion/react';
|
||||
import type { ReactNode } from 'react';
|
||||
|
||||
import { AnimatePresence, motion, useMotionValue } from 'motion/react';
|
||||
import { memo, useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
|
||||
import styles from './grid-carousel.module.css';
|
||||
|
||||
import { useContainerQuery } from '/@/renderer/hooks';
|
||||
import { ActionIcon } from '/@/shared/components/action-icon/action-icon';
|
||||
import { Group } from '/@/shared/components/group/group';
|
||||
import { TextTitle } from '/@/shared/components/text-title/text-title';
|
||||
|
||||
interface Card {
|
||||
content: ReactNode;
|
||||
id: string;
|
||||
}
|
||||
|
||||
interface GridCarouselProps {
|
||||
cards: Card[];
|
||||
hasNextPage?: boolean;
|
||||
loadNextPage?: () => void;
|
||||
onNextPage: (page: number) => void;
|
||||
onPrevPage: (page: number) => void;
|
||||
rowCount?: number;
|
||||
title?: ReactNode | string;
|
||||
}
|
||||
|
||||
const MemoizedCard = memo(({ content }: { content: ReactNode }) => (
|
||||
<div className={styles.card}>{content}</div>
|
||||
));
|
||||
|
||||
MemoizedCard.displayName = 'MemoizedCard';
|
||||
|
||||
const pageVariants: Variants = {
|
||||
animate: { opacity: 1, transition: { duration: 0.3, ease: 'easeOut' }, x: 0 },
|
||||
exit: (custom: { isNext: boolean }) => ({
|
||||
opacity: 0,
|
||||
transition: { duration: 0.3, ease: 'easeIn' },
|
||||
x: custom.isNext ? -100 : 100,
|
||||
}),
|
||||
initial: (custom: { isNext: boolean }) => ({ opacity: 0, x: custom.isNext ? 100 : -100 }),
|
||||
};
|
||||
|
||||
export function GridCarousel(props: GridCarouselProps) {
|
||||
const { cards, hasNextPage, loadNextPage, onNextPage, onPrevPage, rowCount = 1, title } = props;
|
||||
const cq = useContainerQuery({
|
||||
lg: 900,
|
||||
md: 600,
|
||||
sm: 360,
|
||||
});
|
||||
|
||||
const [currentPage, setCurrentPage] = useState({
|
||||
isNext: false,
|
||||
page: 0,
|
||||
});
|
||||
|
||||
const handlePrevPage = useCallback(() => {
|
||||
setCurrentPage((prev) => ({
|
||||
isNext: false,
|
||||
page: prev.page > 0 ? prev.page - 1 : 0,
|
||||
}));
|
||||
onPrevPage(currentPage.page);
|
||||
}, [currentPage, onPrevPage]);
|
||||
|
||||
const handleNextPage = useCallback(() => {
|
||||
setCurrentPage((prev) => ({
|
||||
isNext: true,
|
||||
page: prev.page + 1,
|
||||
}));
|
||||
onNextPage(currentPage.page);
|
||||
}, [currentPage, onNextPage]);
|
||||
|
||||
const cardsToShow = getCardsToShow({
|
||||
isLargerThanLg: cq.isLg,
|
||||
isLargerThanMd: cq.isMd,
|
||||
isLargerThanSm: cq.isSm,
|
||||
isLargerThanXl: cq.isXl,
|
||||
isLargerThanXxl: cq.is2xl,
|
||||
isLargerThanXxxl: cq.is3xl,
|
||||
});
|
||||
|
||||
const visibleCards = useMemo(() => {
|
||||
return cards.slice(
|
||||
currentPage.page * cardsToShow * rowCount,
|
||||
(currentPage.page + 1) * cardsToShow * rowCount,
|
||||
);
|
||||
}, [cards, currentPage, cardsToShow, rowCount]);
|
||||
|
||||
const shouldLoadNextPage = visibleCards.length < cardsToShow * rowCount;
|
||||
|
||||
useEffect(() => {
|
||||
if (shouldLoadNextPage) {
|
||||
loadNextPage?.();
|
||||
}
|
||||
}, [loadNextPage, shouldLoadNextPage]);
|
||||
|
||||
const isPrevDisabled = currentPage.page === 0;
|
||||
const hasMoreCards = (currentPage.page + 1) * cardsToShow * rowCount < cards.length;
|
||||
const isNextDisabled = !hasMoreCards && (hasNextPage === false || hasNextPage === undefined);
|
||||
|
||||
const indicatorRef = useRef<HTMLDivElement>(null);
|
||||
const x = useMotionValue(0);
|
||||
const dragThreshold = 1;
|
||||
|
||||
const handleDragEnd = useCallback(() => {
|
||||
const dragDistance = x.get();
|
||||
|
||||
if (Math.abs(dragDistance) > dragThreshold) {
|
||||
if (dragDistance > 0 && !isPrevDisabled) {
|
||||
// Dragged right, go to previous page
|
||||
handlePrevPage();
|
||||
} else if (dragDistance < 0 && !isNextDisabled) {
|
||||
// Dragged left, go to next page
|
||||
handleNextPage();
|
||||
}
|
||||
}
|
||||
|
||||
x.set(0);
|
||||
}, [handleNextPage, handlePrevPage, isNextDisabled, isPrevDisabled, x]);
|
||||
|
||||
return (
|
||||
<div className={styles.gridCarousel} ref={cq.ref}>
|
||||
{cq.isCalculated && (
|
||||
<>
|
||||
<div className={styles.navigation}>
|
||||
{typeof title === 'string' ? (
|
||||
<TextTitle order={4}>{title}</TextTitle>
|
||||
) : (
|
||||
title
|
||||
)}
|
||||
<Group gap="xs" justify="end">
|
||||
<ActionIcon
|
||||
disabled={isPrevDisabled}
|
||||
icon="arrowLeftS"
|
||||
iconProps={{ size: 'lg' }}
|
||||
onClick={handlePrevPage}
|
||||
size="xs"
|
||||
variant="subtle"
|
||||
/>
|
||||
<ActionIcon
|
||||
disabled={isNextDisabled}
|
||||
icon="arrowRightS"
|
||||
iconProps={{ size: 'lg' }}
|
||||
onClick={handleNextPage}
|
||||
size="xs"
|
||||
variant="subtle"
|
||||
/>
|
||||
</Group>
|
||||
</div>
|
||||
<AnimatePresence custom={currentPage} initial={false} mode="wait">
|
||||
<motion.div
|
||||
animate="animate"
|
||||
className={styles.grid}
|
||||
custom={currentPage}
|
||||
exit="exit"
|
||||
initial="initial"
|
||||
key={currentPage.page}
|
||||
style={
|
||||
{
|
||||
'--cards-to-show': cardsToShow,
|
||||
'--row-count': rowCount,
|
||||
} as React.CSSProperties
|
||||
}
|
||||
variants={pageVariants}
|
||||
>
|
||||
{visibleCards.map((card) => (
|
||||
<MemoizedCard content={card.content} key={card.id} />
|
||||
))}
|
||||
</motion.div>
|
||||
</AnimatePresence>
|
||||
<motion.div
|
||||
className={styles.pageIndicator}
|
||||
drag="x"
|
||||
dragConstraints={{ left: -20, right: 20 }}
|
||||
dragElastic={0.3}
|
||||
dragSnapToOrigin={true}
|
||||
onDragEnd={handleDragEnd}
|
||||
ref={indicatorRef}
|
||||
style={{ x }}
|
||||
>
|
||||
<motion.div className={styles.indicatorTrack} />
|
||||
</motion.div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function getCardsToShow(breakpoints: {
|
||||
isLargerThanLg: boolean;
|
||||
isLargerThanMd: boolean;
|
||||
isLargerThanSm: boolean;
|
||||
isLargerThanXl: boolean;
|
||||
isLargerThanXxl: boolean;
|
||||
isLargerThanXxxl: boolean;
|
||||
}) {
|
||||
if (breakpoints.isLargerThanXxxl) {
|
||||
return 14;
|
||||
}
|
||||
|
||||
if (breakpoints.isLargerThanXxl) {
|
||||
return 10;
|
||||
}
|
||||
|
||||
if (breakpoints.isLargerThanXl) {
|
||||
return 8;
|
||||
}
|
||||
|
||||
if (breakpoints.isLargerThanLg) {
|
||||
return 6;
|
||||
}
|
||||
|
||||
if (breakpoints.isLargerThanMd) {
|
||||
return 4;
|
||||
}
|
||||
|
||||
if (breakpoints.isLargerThanSm) {
|
||||
return 3;
|
||||
}
|
||||
|
||||
return 2;
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
.grid-carousel {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--theme-spacing-md);
|
||||
width: 100%;
|
||||
margin: 0 auto;
|
||||
container-name: grid-carousel;
|
||||
container-type: inline-size;
|
||||
}
|
||||
|
||||
.navigation {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(var(--cards-to-show, 2), minmax(0, 1fr));
|
||||
gap: var(--theme-spacing-md);
|
||||
height: calc(var(--row-count) * (100cqw / var(--cards-to-show, 2) + 3rem));
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.page-indicator {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: var(--theme-spacing-sm) 0;
|
||||
cursor: grab;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.page-indicator:active {
|
||||
cursor: grabbing;
|
||||
}
|
||||
|
||||
.indicator-track {
|
||||
width: 20px;
|
||||
height: 4px;
|
||||
touch-action: none;
|
||||
cursor: grab;
|
||||
border-radius: 2px;
|
||||
|
||||
@mixin light {
|
||||
background-color: darken(var(--theme-colors-background), 10%);
|
||||
}
|
||||
|
||||
@mixin dark {
|
||||
background-color: lighten(var(--theme-colors-background), 15%);
|
||||
}
|
||||
}
|
||||
|
||||
.indicator-track:active {
|
||||
cursor: grabbing;
|
||||
}
|
||||
@@ -1,330 +0,0 @@
|
||||
import throttle from 'lodash/throttle';
|
||||
import {
|
||||
isValidElement,
|
||||
memo,
|
||||
ReactNode,
|
||||
useCallback,
|
||||
useEffect,
|
||||
useLayoutEffect,
|
||||
useMemo,
|
||||
useRef,
|
||||
useState,
|
||||
} from 'react';
|
||||
import { SwiperOptions, Virtual } from 'swiper';
|
||||
import 'swiper/css';
|
||||
import { Swiper, SwiperSlide } from 'swiper/react';
|
||||
import { Swiper as SwiperCore } from 'swiper/types';
|
||||
|
||||
import { PosterCard } from '/@/renderer/components/card/poster-card';
|
||||
import { usePlayQueueAdd } from '/@/renderer/features/player/hooks/use-playqueue-add';
|
||||
import { useCreateFavorite } from '/@/renderer/features/shared/mutations/create-favorite-mutation';
|
||||
import { useDeleteFavorite } from '/@/renderer/features/shared/mutations/delete-favorite-mutation';
|
||||
import { usePlayButtonBehavior } from '/@/renderer/store';
|
||||
import { Button } from '/@/shared/components/button/button';
|
||||
import { Group } from '/@/shared/components/group/group';
|
||||
import { Icon } from '/@/shared/components/icon/icon';
|
||||
import { Stack } from '/@/shared/components/stack/stack';
|
||||
import { TextTitle } from '/@/shared/components/text-title/text-title';
|
||||
import {
|
||||
Album,
|
||||
AlbumArtist,
|
||||
Artist,
|
||||
LibraryItem,
|
||||
RelatedArtist,
|
||||
} from '/@/shared/types/domain-types';
|
||||
import { CardRoute, CardRow } from '/@/shared/types/types';
|
||||
|
||||
const getSlidesPerView = (windowWidth: number) => {
|
||||
if (windowWidth < 400) return 2;
|
||||
if (windowWidth < 700) return 3;
|
||||
if (windowWidth < 900) return 4;
|
||||
if (windowWidth < 1100) return 5;
|
||||
if (windowWidth < 1300) return 6;
|
||||
if (windowWidth < 1500) return 7;
|
||||
if (windowWidth < 1920) return 8;
|
||||
return 10;
|
||||
};
|
||||
|
||||
interface TitleProps {
|
||||
handleNext?: () => void;
|
||||
handlePrev?: () => void;
|
||||
label?: ReactNode | string;
|
||||
pagination: {
|
||||
hasNextPage: boolean;
|
||||
hasPreviousPage: boolean;
|
||||
};
|
||||
}
|
||||
|
||||
const Title = ({ handleNext, handlePrev, label, pagination }: TitleProps) => {
|
||||
return (
|
||||
<Group justify="space-between">
|
||||
{isValidElement(label) ? (
|
||||
label
|
||||
) : (
|
||||
<TextTitle order={3} weight={700}>
|
||||
{label}
|
||||
</TextTitle>
|
||||
)}
|
||||
|
||||
<Group gap="sm">
|
||||
<Button
|
||||
disabled={!pagination.hasPreviousPage}
|
||||
onClick={handlePrev}
|
||||
size="compact-md"
|
||||
variant="subtle"
|
||||
>
|
||||
<Icon icon="arrowLeftS" />
|
||||
</Button>
|
||||
<Button
|
||||
disabled={!pagination.hasNextPage}
|
||||
onClick={handleNext}
|
||||
size="compact-md"
|
||||
variant="subtle"
|
||||
>
|
||||
<Icon icon="arrowRightS" />
|
||||
</Button>
|
||||
</Group>
|
||||
</Group>
|
||||
);
|
||||
};
|
||||
|
||||
export interface SwiperGridCarouselProps {
|
||||
cardRows: CardRow<Album>[] | CardRow<AlbumArtist>[] | CardRow<Artist>[];
|
||||
data: Album[] | AlbumArtist[] | Artist[] | RelatedArtist[] | undefined;
|
||||
isLoading?: boolean;
|
||||
itemType: LibraryItem;
|
||||
route: CardRoute;
|
||||
swiperProps?: SwiperOptions;
|
||||
title?: {
|
||||
children?: ReactNode;
|
||||
hasPagination?: boolean;
|
||||
icon?: ReactNode;
|
||||
label: ReactNode | string;
|
||||
};
|
||||
uniqueId: string;
|
||||
}
|
||||
|
||||
export const SwiperGridCarousel = ({
|
||||
cardRows,
|
||||
data,
|
||||
isLoading,
|
||||
itemType,
|
||||
route,
|
||||
swiperProps,
|
||||
title,
|
||||
uniqueId,
|
||||
}: SwiperGridCarouselProps) => {
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
const swiperRef = useRef<any | SwiperCore>(null);
|
||||
const playButtonBehavior = usePlayButtonBehavior();
|
||||
const handlePlayQueueAdd = usePlayQueueAdd();
|
||||
const [slideCount, setSlideCount] = useState(4);
|
||||
|
||||
useEffect(() => {
|
||||
swiperRef.current?.slideTo(0, 0);
|
||||
}, [data]);
|
||||
|
||||
const [pagination, setPagination] = useState({
|
||||
hasNextPage: (data?.length || 0) > Math.round(3),
|
||||
hasPreviousPage: false,
|
||||
});
|
||||
|
||||
const createFavoriteMutation = useCreateFavorite({});
|
||||
const deleteFavoriteMutation = useDeleteFavorite({});
|
||||
|
||||
const handleFavorite = useCallback(
|
||||
(options: {
|
||||
id: string[];
|
||||
isFavorite: boolean;
|
||||
itemType: LibraryItem;
|
||||
serverId: string;
|
||||
}) => {
|
||||
const { id, isFavorite, itemType, serverId } = options;
|
||||
if (isFavorite) {
|
||||
deleteFavoriteMutation.mutate({
|
||||
apiClientProps: { serverId },
|
||||
query: {
|
||||
id,
|
||||
type: itemType,
|
||||
},
|
||||
});
|
||||
} else {
|
||||
createFavoriteMutation.mutate({
|
||||
apiClientProps: { serverId },
|
||||
query: {
|
||||
id,
|
||||
type: itemType,
|
||||
},
|
||||
});
|
||||
}
|
||||
},
|
||||
[createFavoriteMutation, deleteFavoriteMutation],
|
||||
);
|
||||
|
||||
const slides = useMemo(() => {
|
||||
if (!data) return [];
|
||||
|
||||
return data.map((el) => (
|
||||
<PosterCard
|
||||
controls={{
|
||||
cardRows,
|
||||
handleFavorite,
|
||||
handlePlayQueueAdd,
|
||||
itemType,
|
||||
playButtonBehavior,
|
||||
route,
|
||||
}}
|
||||
data={el}
|
||||
isLoading={isLoading}
|
||||
key={`${uniqueId}-${el.id}`}
|
||||
uniqueId={uniqueId}
|
||||
/>
|
||||
));
|
||||
}, [
|
||||
cardRows,
|
||||
data,
|
||||
handleFavorite,
|
||||
handlePlayQueueAdd,
|
||||
isLoading,
|
||||
itemType,
|
||||
playButtonBehavior,
|
||||
route,
|
||||
uniqueId,
|
||||
]);
|
||||
|
||||
const handleNext = useCallback(() => {
|
||||
const activeIndex = swiperRef?.current?.activeIndex || 0;
|
||||
const slidesPerView = Math.round(Number(swiperProps?.slidesPerView || slideCount));
|
||||
swiperRef?.current?.slideTo(activeIndex + slidesPerView);
|
||||
}, [slideCount, swiperProps?.slidesPerView]);
|
||||
|
||||
const handlePrev = useCallback(() => {
|
||||
const activeIndex = swiperRef?.current?.activeIndex || 0;
|
||||
const slidesPerView = Math.round(Number(swiperProps?.slidesPerView || slideCount));
|
||||
swiperRef?.current?.slideTo(activeIndex - slidesPerView);
|
||||
}, [slideCount, swiperProps?.slidesPerView]);
|
||||
|
||||
const handleOnSlideChange = useCallback((e: SwiperCore) => {
|
||||
const { isBeginning, isEnd, params, slides } = e;
|
||||
if (isEnd || isBeginning) return;
|
||||
|
||||
const slideCount = (params.slidesPerView as number | undefined) || 4;
|
||||
setPagination({
|
||||
hasNextPage: slideCount < slides.length,
|
||||
hasPreviousPage: slideCount < slides.length,
|
||||
});
|
||||
}, []);
|
||||
|
||||
const handleOnZoomChange = useCallback((e: SwiperCore) => {
|
||||
const { isBeginning, isEnd, params, slides } = e;
|
||||
if (isEnd || isBeginning) return;
|
||||
|
||||
const slideCount = (params.slidesPerView as number | undefined) || 4;
|
||||
setPagination({
|
||||
hasNextPage: slideCount < slides.length,
|
||||
hasPreviousPage: slideCount < slides.length,
|
||||
});
|
||||
}, []);
|
||||
|
||||
const handleOnReachEnd = useCallback((e: SwiperCore) => {
|
||||
const { params, slides } = e;
|
||||
|
||||
const slideCount = (params.slidesPerView as number | undefined) || 4;
|
||||
setPagination({
|
||||
hasNextPage: false,
|
||||
hasPreviousPage: slideCount < slides.length,
|
||||
});
|
||||
}, []);
|
||||
|
||||
const handleOnReachBeginning = useCallback((e: SwiperCore) => {
|
||||
const { params, slides } = e;
|
||||
|
||||
const slideCount = (params.slidesPerView as number | undefined) || 4;
|
||||
setPagination({
|
||||
hasNextPage: slideCount < slides.length,
|
||||
hasPreviousPage: false,
|
||||
});
|
||||
}, []);
|
||||
|
||||
useLayoutEffect(() => {
|
||||
const handleResize = () => {
|
||||
// Use the container div ref and not swiper width, as this value is more accurate
|
||||
const width = containerRef.current?.clientWidth;
|
||||
const { activeIndex, params, slides } =
|
||||
(swiperRef.current as SwiperCore | undefined) ?? {};
|
||||
|
||||
if (width) {
|
||||
const slidesPerView = getSlidesPerView(width);
|
||||
setSlideCount(slidesPerView);
|
||||
}
|
||||
|
||||
if (activeIndex !== undefined && slides && params?.slidesPerView) {
|
||||
const slideCount = (params.slidesPerView as number | undefined) || 4;
|
||||
setPagination({
|
||||
hasNextPage: activeIndex + slideCount < slides.length,
|
||||
hasPreviousPage: activeIndex > 0,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
handleResize();
|
||||
|
||||
const throttledResize = throttle(handleResize, 200);
|
||||
window.addEventListener('resize', throttledResize);
|
||||
|
||||
return () => {
|
||||
window.removeEventListener('resize', throttledResize);
|
||||
};
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<Stack className="grid-carousel" gap="md" ref={containerRef as any}>
|
||||
{title ? (
|
||||
<Title
|
||||
{...title}
|
||||
handleNext={handleNext}
|
||||
handlePrev={handlePrev}
|
||||
pagination={pagination}
|
||||
/>
|
||||
) : null}
|
||||
<Swiper
|
||||
modules={[Virtual]}
|
||||
onBeforeInit={(swiper) => {
|
||||
swiperRef.current = swiper;
|
||||
}}
|
||||
onReachBeginning={handleOnReachBeginning}
|
||||
onReachEnd={handleOnReachEnd}
|
||||
onSlideChange={handleOnSlideChange}
|
||||
onZoomChange={handleOnZoomChange}
|
||||
ref={swiperRef}
|
||||
resizeObserver
|
||||
slidesPerView={slideCount}
|
||||
spaceBetween={20}
|
||||
style={{ height: '100%', width: '100%' }}
|
||||
{...swiperProps}
|
||||
>
|
||||
{slides.map((slideContent, index) => {
|
||||
return (
|
||||
<SwiperSlide
|
||||
key={`${uniqueId}-${slideContent?.props?.data?.id}-${index}`}
|
||||
virtualIndex={index}
|
||||
>
|
||||
{slideContent}
|
||||
</SwiperSlide>
|
||||
);
|
||||
})}
|
||||
</Swiper>
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
export const MemoizedSwiperGridCarousel = memo(
|
||||
function Carousel(props: SwiperGridCarouselProps) {
|
||||
return <SwiperGridCarousel {...props} />;
|
||||
},
|
||||
(oldProps, newProps) => {
|
||||
const uniqueIdIsEqual = oldProps.uniqueId === newProps.uniqueId;
|
||||
const dataIsEqual = oldProps.data === newProps.data;
|
||||
return uniqueIdIsEqual && dataIsEqual;
|
||||
},
|
||||
);
|
||||
@@ -460,12 +460,14 @@ const PosterItemCard = ({
|
||||
},
|
||||
itemType,
|
||||
onDragStart: () => {
|
||||
if (!data || !internalState) {
|
||||
if (!data) {
|
||||
return;
|
||||
}
|
||||
|
||||
const draggedItems = getDraggedItems(data, internalState);
|
||||
internalState.setDragging(draggedItems);
|
||||
if (internalState) {
|
||||
internalState.setDragging(draggedItems);
|
||||
}
|
||||
},
|
||||
onDrop: () => {
|
||||
if (internalState) {
|
||||
|
||||
@@ -26,9 +26,9 @@ const hasRequiredDragProperties = (
|
||||
* Gets the items that should be dragged based on the current data and selection state.
|
||||
* If the current item is already selected, drag all selected items.
|
||||
* Otherwise, select and drag only the current item.
|
||||
* If internalState is not provided, returns the single item wrapped in an array.
|
||||
*
|
||||
* @param data - The item data to drag (Album, AlbumArtist, Artist, Playlist, or Song)
|
||||
* @param itemType - The type of library item
|
||||
* @param internalState - The item list state actions (optional)
|
||||
* @param updateSelection - Whether to update the selection state (default: true)
|
||||
* @returns Array of items that should be dragged (with original values, asserting id, itemType, and _serverId)
|
||||
@@ -38,7 +38,7 @@ export const getDraggedItems = (
|
||||
internalState?: ItemListStateActions,
|
||||
updateSelection: boolean = true,
|
||||
): ItemListStateItemWithRequiredProperties[] => {
|
||||
if (!data || !internalState) {
|
||||
if (!data) {
|
||||
return [];
|
||||
}
|
||||
|
||||
@@ -46,14 +46,18 @@ export const getDraggedItems = (
|
||||
return [];
|
||||
}
|
||||
|
||||
const draggedItem = data as ItemListStateItemWithRequiredProperties;
|
||||
|
||||
if (!internalState) {
|
||||
return [draggedItem];
|
||||
}
|
||||
|
||||
const rowId = internalState.extractRowId(data);
|
||||
|
||||
if (!rowId) {
|
||||
return [];
|
||||
return [draggedItem];
|
||||
}
|
||||
|
||||
const draggedItem = data as ItemListStateItemWithRequiredProperties;
|
||||
|
||||
const previouslySelected = internalState.getSelected();
|
||||
const isDraggingSelectedItem = previouslySelected.some((selected) => {
|
||||
if (hasRequiredDragProperties(selected)) {
|
||||
|
||||
@@ -102,13 +102,14 @@ export const ItemTableListColumn = (props: ItemTableListColumn) => {
|
||||
},
|
||||
itemType: props.itemType,
|
||||
onDragStart: () => {
|
||||
if (!item || !isDataRow || !props.internalState) {
|
||||
if (!item || !isDataRow) {
|
||||
return;
|
||||
}
|
||||
|
||||
const draggedItems = getDraggedItems(item as any, props.internalState);
|
||||
|
||||
props.internalState.setDragging(draggedItems);
|
||||
if (props.internalState) {
|
||||
props.internalState.setDragging(draggedItems);
|
||||
}
|
||||
},
|
||||
onDrop: () => {
|
||||
if (props.internalState) {
|
||||
|
||||
Reference in New Issue
Block a user