1
0
Fork 0
mirror of https://github.com/muerwre/vault-frontend.git synced 2025-04-24 20:36:40 +07:00

made sortable grid component

This commit is contained in:
Fedor Katurov 2022-06-24 16:47:38 +07:00
parent 24ab0cb050
commit 3345939670
13 changed files with 243 additions and 170 deletions

View file

@ -3,7 +3,7 @@ import React, { FC, useCallback } from 'react';
import { SortEnd } from 'react-sortable-hoc';
import { SortableAudioGrid } from '~/components/editors/SortableAudioGrid';
import { OnSortEnd, SortableImageGrid } from '~/components/editors/SortableImageGrid';
import { SortableImageGrid } from '~/components/sortable';
import { COMMENT_FILE_TYPES } from '~/constants/uploads';
import { useFileDropZone } from '~/hooks';
import { IFile } from '~/types';
@ -29,16 +29,9 @@ const CommentFormAttaches: FC = () => {
const hasAudioAttaches = filesAudios.length > 0 || pendingAudios.length > 0;
const hasAttaches = hasImageAttaches || hasAudioAttaches;
const onImageMove = useCallback<OnSortEnd>(
({ oldIndex, newIndex }) => {
setFiles([
...filesAudios,
...(moveArrItem(
oldIndex,
newIndex,
filesImages.filter(file => !!file)
) as IFile[]),
]);
const onImageMove = useCallback(
(newFiles: IFile[]) => {
setFiles([...filesAudios, ...newFiles.filter(it => it)]);
},
[setFiles, filesImages, filesAudios]
);
@ -85,6 +78,7 @@ const CommentFormAttaches: FC = () => {
onSortEnd={onImageMove}
items={filesImages}
locked={pendingImages}
size={160}
/>
)}

View file

@ -1,14 +1,9 @@
import React, { FC, useCallback } from 'react';
import { SortEnd } from 'react-sortable-hoc';
import { OnSortEnd, SortableImageGrid } from '~/components/editors/SortableImageGrid';
import { SortableImageGrid } from '~/components/sortable';
import { useWindowSize } from '~/hooks/dom/useWindowSize';
import { UploadStatus } from '~/store/uploader/UploaderStore';
import { IFile } from '~/types';
import { moveArrItem } from '~/utils/fn';
import styles from './styles.module.scss';
interface IProps {
files: IFile[];
@ -19,15 +14,9 @@ interface IProps {
const ImageGrid: FC<IProps> = ({ files, setFiles, locked }) => {
const { innerWidth } = useWindowSize();
const onMove = useCallback<OnSortEnd>(
({ oldIndex, newIndex }) => {
setFiles(
moveArrItem(
oldIndex,
newIndex,
files.filter(file => !!file)
) as IFile[]
);
const onMove = useCallback(
(newFiles: IFile[]) => {
setFiles(newFiles.filter(it => it));
},
[setFiles, files]
);
@ -39,7 +28,15 @@ const ImageGrid: FC<IProps> = ({ files, setFiles, locked }) => {
[setFiles, files]
);
return <SortableImageGrid onDelete={onDrop} onSortEnd={onMove} items={files} locked={locked} />;
return (
<SortableImageGrid
onDelete={onDrop}
onSortEnd={onMove}
items={files}
locked={locked}
size={innerWidth > 768 ? 220 : 160}
/>
);
};
export { ImageGrid };

View file

@ -1,139 +0,0 @@
import React, { FC, useCallback, useMemo, useState } from 'react';
import {
closestCenter,
DndContext,
DragOverlay,
DragStartEvent,
KeyboardSensor,
MouseSensor,
PointerSensor,
TouchSensor,
useSensor,
useSensors,
} from '@dnd-kit/core';
import { DragEndEvent } from '@dnd-kit/core/dist/types';
import {
rectSortingStrategy,
SortableContext,
sortableKeyboardCoordinates,
} from '@dnd-kit/sortable';
import classNames from 'classnames';
import { SortableImageGridItem } from '~/components/editors/SortableImageGridItem';
import { ImageUpload } from '~/components/upload/ImageUpload';
import { ImagePresets } from '~/constants/urls';
import { UploadStatus } from '~/store/uploader/UploaderStore';
import { IFile } from '~/types';
import { getURL } from '~/utils/dom';
import styles from './styles.module.scss';
export type OnSortEnd = (props: { oldIndex: number; newIndex: number }) => void;
interface SortableImageGridProps {
onSortEnd: OnSortEnd;
items: IFile[];
locked: UploadStatus[];
onDelete: (file_id: IFile['id']) => void;
className?: string;
}
const SortableImageGrid: FC<SortableImageGridProps> = ({
items,
locked,
onDelete,
className,
onSortEnd,
}) => {
const [draggingItem, setDraggingItem] = useState<IFile | null>(null);
const preventEvent = useCallback(event => event.preventDefault(), []);
const sensors = useSensors(
useSensor(TouchSensor, {
activationConstraint: {
delay: 200,
tolerance: 5,
},
}),
useSensor(MouseSensor)
);
const ids = useMemo(() => items.map(it => it.id ?? 0), [items]);
const onDragEnd = useCallback(
({ active, over }: DragEndEvent) => {
setDraggingItem(null);
if (active.id === over?.id || !over?.id) {
return;
}
const oldIndex = items.findIndex(it => it.id === active.id);
const newIndex = items.findIndex(it => it.id === over.id);
onSortEnd({ oldIndex, newIndex });
},
[items]
);
const onDragStart = useCallback(
({ active }: DragStartEvent) => {
if (!active.id) {
return;
}
const activeItem = items.find(it => it.id === active.id);
setDraggingItem(activeItem ?? null);
},
[items]
);
return (
<DndContext
sensors={sensors}
collisionDetection={closestCenter}
onDragEnd={onDragEnd}
onDragStart={onDragStart}
>
<SortableContext items={ids} strategy={rectSortingStrategy}>
<div className={classNames(styles.grid, className)} onDropCapture={preventEvent}>
{items
.filter(file => file && file.id)
.map((file, index) => (
<SortableImageGridItem
key={file.id}
id={file.id!}
className={file.id === draggingItem?.id ? styles.dragging : undefined}
>
<ImageUpload
id={file.id}
thumb={getURL(file, ImagePresets.cover)}
onDrop={onDelete}
/>
</SortableImageGridItem>
))}
{locked.map((item, index) => (
<ImageUpload
thumb={item.thumbnail}
progress={item.progress}
is_uploading
key={item.id}
/>
))}
<DragOverlay>
{draggingItem ? (
<div className={styles.overlay}>
<ImageUpload thumb={getURL(draggingItem, ImagePresets.cover)} />
</div>
) : null}
</DragOverlay>
</div>
</SortableContext>
</DndContext>
);
};
export { SortableImageGrid };

View file

@ -0,0 +1,136 @@
import React, { createElement, FC, useCallback, useMemo, useState } from 'react';
import {
closestCenter,
DndContext,
DragOverlay,
DragStartEvent,
MouseSensor,
TouchSensor,
useSensor,
useSensors,
} from '@dnd-kit/core';
import { DragEndEvent } from '@dnd-kit/core/dist/types';
import { rectSortingStrategy, SortableContext } from '@dnd-kit/sortable';
import classNames from 'classnames';
import { moveArrItem } from '~/utils/fn';
import { DivProps } from '~/utils/types';
import { SortableImageGridItem } from '../SortableGridItem';
import styles from './styles.module.scss';
interface SortableGridProps<T extends {}, R extends {}> {
items: T[];
locked: R[];
getID: (item: T) => number | string;
getLockedID: (locked: R) => number | string;
renderItem: FC<{ item: T }>;
renderLocked: FC<{ locked: R }>;
onSortEnd: (newVal: T[]) => void;
className?: string;
size?: number;
}
const SortableGrid = <T, R>({
items,
locked,
getID,
getLockedID,
className,
renderItem,
renderLocked,
onSortEnd,
size,
}: SortableGridProps<T, R>) => {
const [draggingItem, setDraggingItem] = useState<T | null>(null);
const ids = useMemo(() => items.map(getID), [items]);
const onDragEnd = useCallback(
({ active, over }: DragEndEvent) => {
setDraggingItem(null);
if (!over?.id || active.id === over.id) {
return;
}
const oldIndex = items.findIndex(it => getID(it) === active.id);
const newIndex = items.findIndex(it => getID(it) === over.id);
onSortEnd(moveArrItem(oldIndex, newIndex, items));
},
[items]
);
const onDragStart = useCallback(
({ active }: DragStartEvent) => {
if (!active.id) {
return;
}
const activeItem = items.find(it => getID(it) === active.id);
setDraggingItem(activeItem ?? null);
},
[items]
);
const sensors = useSensors(
useSensor(TouchSensor, {
activationConstraint: {
delay: 200,
tolerance: 5,
},
}),
useSensor(MouseSensor)
);
const gridStyle = useMemo<DivProps['style']>(
() =>
size
? { gridTemplateColumns: size && `repeat(auto-fill, minmax(${size}px, 1fr))` }
: undefined,
[size]
);
return (
<DndContext
sensors={sensors}
collisionDetection={closestCenter}
onDragEnd={onDragEnd}
onDragStart={onDragStart}
>
<SortableContext items={ids} strategy={rectSortingStrategy}>
<div className={classNames(styles.grid, className)} style={gridStyle}>
{items.map(item => (
<SortableImageGridItem
key={getID(item)}
id={getID(item)}
className={
draggingItem && getID(item) === getID(draggingItem) ? styles.dragging : undefined
}
>
{createElement(renderItem, { item })}
</SortableImageGridItem>
))}
{locked.map(item =>
createElement(renderLocked, { locked: item, key: getLockedID(item) })
)}
<DragOverlay>
{draggingItem ? (
<div className={styles.overlay}>
{createElement(renderItem, { item: draggingItem })}
</div>
) : null}
</DragOverlay>
</div>
</SortableContext>
</DndContext>
);
};
export { SortableGrid };

View file

@ -0,0 +1,19 @@
@import "src/styles/variables";
.grid {
box-sizing: border-box;
display: grid;
grid-column-gap: $gap;
grid-row-gap: $gap;
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
}
.overlay {
box-shadow: rgba(0, 0, 0, 0.3) 5px 5px 10px 2px;
border-radius: $radius;
}
.dragging {
opacity: 0.1;
}

View file

@ -7,7 +7,7 @@ import classNames from 'classnames';
import styles from './styles.module.scss';
interface SortableImageGridItemProps {
id: number;
id: number | string;
disabled?: boolean;
className?: string;
}

View file

@ -0,0 +1,64 @@
import React, { FC, useCallback } from 'react';
import { ImageUpload } from '~/components/upload/ImageUpload';
import { ImagePresets } from '~/constants/urls';
import { UploadStatus } from '~/store/uploader/UploaderStore';
import { IFile } from '~/types';
import { getURL } from '~/utils/dom';
import { SortableGrid } from '../SortableGrid';
type OnSortEnd = (newValue: IFile[]) => void;
interface SortableImageGridProps {
onSortEnd: OnSortEnd;
items: IFile[];
locked: UploadStatus[];
onDelete: (file_id: IFile['id']) => void;
className?: string;
size?: number;
}
const SortableImageGrid: FC<SortableImageGridProps> = ({
items,
locked,
onDelete,
className,
onSortEnd,
size,
}) => {
const renderItem = useCallback<FC<{ item: IFile }>>(
({ item }) => (
<ImageUpload id={item.id} thumb={getURL(item, ImagePresets.cover)} onDrop={onDelete} />
),
[]
);
const renderLocked = useCallback<FC<{ locked: UploadStatus }>>(
({ locked }) => (
<ImageUpload
thumb={locked.thumbnail}
onDrop={onDelete}
progress={locked.progress}
is_uploading
/>
),
[]
);
return (
<SortableGrid
items={items}
locked={locked}
getID={it => it.id}
getLockedID={it => it.id}
renderItem={renderItem}
renderLocked={renderLocked}
onSortEnd={onSortEnd}
className={className}
size={size}
/>
);
};
export { SortableImageGrid };

View file

@ -0,0 +1,2 @@
export * from './SortableImageGrid';
export * from './SortableGrid';

View file

@ -1,7 +1,7 @@
import { IFile } from '~/types';
export const EMPTY_FILE: IFile = {
id: undefined,
id: 0,
user_id: undefined,
node_id: undefined,

View file

@ -24,7 +24,7 @@ export type UUID = string;
export type IUploadType = 'image' | 'text' | 'audio' | 'video' | 'other';
export interface IFile {
id?: number;
id: number;
temp_id?: UUID;
user_id?: UUID;
node_id?: UUID;

File diff suppressed because one or more lines are too long