mirror of
https://github.com/muerwre/vault-frontend.git
synced 2025-04-24 20:36:40 +07:00
added dnd-kit for images
This commit is contained in:
parent
a811951a63
commit
dc1d66cec6
10 changed files with 227 additions and 77 deletions
|
@ -1,6 +1,6 @@
|
|||
#NEXT_PUBLIC_API_HOST=https://pig.staging.vault48.org/
|
||||
#NEXT_PUBLIC_REMOTE_CURRENT=https://pig.staging.vault48.org/static/
|
||||
NEXT_PUBLIC_API_HOST=https://pig.staging.vault48.org/
|
||||
NEXT_PUBLIC_REMOTE_CURRENT=https://pig.staging.vault48.org/static/
|
||||
#NEXT_PUBLIC_API_HOST=http://localhost:8888/
|
||||
#NEXT_PUBLIC_REMOTE_CURRENT=http://localhost:8888/static/
|
||||
NEXT_PUBLIC_API_HOST=https://pig.vault48.org/
|
||||
NEXT_PUBLIC_REMOTE_CURRENT=https://pig.vault48.org/static/
|
||||
#NEXT_PUBLIC_API_HOST=https://pig.vault48.org/
|
||||
#NEXT_PUBLIC_REMOTE_CURRENT=https://pig.vault48.org/static/
|
||||
|
|
|
@ -3,12 +3,13 @@
|
|||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@dnd-kit/core": "^6.0.5",
|
||||
"@dnd-kit/sortable": "^7.0.1",
|
||||
"@popperjs/core": "^2.11.2",
|
||||
"@testing-library/jest-dom": "^5.11.4",
|
||||
"@testing-library/react": "^11.1.0",
|
||||
"@testing-library/user-event": "^12.1.10",
|
||||
"@tippyjs/react": "^4.2.6",
|
||||
"@types/react-router-dom": "^5.1.7",
|
||||
"autosize": "^4.0.2",
|
||||
"axios": "^0.21.2",
|
||||
"body-scroll-lock": "^2.6.4",
|
||||
|
@ -78,9 +79,11 @@
|
|||
"devDependencies": {
|
||||
"@next/bundle-analyzer": "^12.0.8",
|
||||
"@next/eslint-plugin-next": "^12.0.8",
|
||||
"@types/marked": "^4.0.1",
|
||||
"@types/react-router-dom": "^5.1.7",
|
||||
"@types/react": "^17.0.2",
|
||||
"@types/node": "^11.13.22",
|
||||
"@types/ramda": "^0.26.33",
|
||||
"@types/marked": "^4.0.1",
|
||||
"@types/throttle-debounce": "^2.1.0",
|
||||
"@types/yup": "^0.29.11",
|
||||
"@typescript-eslint/eslint-plugin": "^5.10.1",
|
||||
|
|
|
@ -3,7 +3,7 @@ import React, { FC, useCallback } from 'react';
|
|||
import { SortEnd } from 'react-sortable-hoc';
|
||||
|
||||
import { SortableAudioGrid } from '~/components/editors/SortableAudioGrid';
|
||||
import { SortableImageGrid } from '~/components/editors/SortableImageGrid';
|
||||
import { OnSortEnd, SortableImageGrid } from '~/components/editors/SortableImageGrid';
|
||||
import { COMMENT_FILE_TYPES } from '~/constants/uploads';
|
||||
import { useFileDropZone } from '~/hooks';
|
||||
import { IFile } from '~/types';
|
||||
|
@ -29,8 +29,8 @@ const CommentFormAttaches: FC = () => {
|
|||
const hasAudioAttaches = filesAudios.length > 0 || pendingAudios.length > 0;
|
||||
const hasAttaches = hasImageAttaches || hasAudioAttaches;
|
||||
|
||||
const onImageMove = useCallback(
|
||||
({ oldIndex, newIndex }: SortEnd) => {
|
||||
const onImageMove = useCallback<OnSortEnd>(
|
||||
({ oldIndex, newIndex }) => {
|
||||
setFiles([
|
||||
...filesAudios,
|
||||
...(moveArrItem(
|
||||
|
@ -83,12 +83,8 @@ const CommentFormAttaches: FC = () => {
|
|||
<SortableImageGrid
|
||||
onDelete={onFileDelete}
|
||||
onSortEnd={onImageMove}
|
||||
axis="xy"
|
||||
items={filesImages}
|
||||
locked={pendingImages}
|
||||
pressDelay={50}
|
||||
helperClass={styles.helper}
|
||||
size={120}
|
||||
/>
|
||||
)}
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@ import React, { FC, useCallback } from 'react';
|
|||
|
||||
import { SortEnd } from 'react-sortable-hoc';
|
||||
|
||||
import { SortableImageGrid } from '~/components/editors/SortableImageGrid';
|
||||
import { OnSortEnd, SortableImageGrid } from '~/components/editors/SortableImageGrid';
|
||||
import { useWindowSize } from '~/hooks/dom/useWindowSize';
|
||||
import { UploadStatus } from '~/store/uploader/UploaderStore';
|
||||
import { IFile } from '~/types';
|
||||
|
@ -19,8 +19,8 @@ interface IProps {
|
|||
const ImageGrid: FC<IProps> = ({ files, setFiles, locked }) => {
|
||||
const { innerWidth } = useWindowSize();
|
||||
|
||||
const onMove = useCallback(
|
||||
({ oldIndex, newIndex }: SortEnd) => {
|
||||
const onMove = useCallback<OnSortEnd>(
|
||||
({ oldIndex, newIndex }) => {
|
||||
setFiles(
|
||||
moveArrItem(
|
||||
oldIndex,
|
||||
|
@ -39,17 +39,7 @@ const ImageGrid: FC<IProps> = ({ files, setFiles, locked }) => {
|
|||
[setFiles, files]
|
||||
);
|
||||
|
||||
return (
|
||||
<SortableImageGrid
|
||||
onDelete={onDrop}
|
||||
onSortEnd={onMove}
|
||||
axis="xy"
|
||||
items={files}
|
||||
locked={locked}
|
||||
pressDelay={innerWidth < 768 ? 200 : 0}
|
||||
helperClass={styles.helper}
|
||||
/>
|
||||
);
|
||||
return <SortableImageGrid onDelete={onDrop} onSortEnd={onMove} items={files} locked={locked} />;
|
||||
};
|
||||
|
||||
export { ImageGrid };
|
||||
|
|
|
@ -2,5 +2,4 @@
|
|||
|
||||
.helper {
|
||||
opacity: 0.5;
|
||||
z-index: 10000 !important;
|
||||
}
|
||||
|
|
|
@ -1,7 +1,24 @@
|
|||
import React, { useCallback } from 'react';
|
||||
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 { SortableContainer } from 'react-sortable-hoc';
|
||||
|
||||
import { SortableImageGridItem } from '~/components/editors/SortableImageGridItem';
|
||||
import { ImageUpload } from '~/components/upload/ImageUpload';
|
||||
|
@ -12,27 +29,83 @@ import { getURL } from '~/utils/dom';
|
|||
|
||||
import styles from './styles.module.scss';
|
||||
|
||||
const SortableImageGrid = SortableContainer(
|
||||
({
|
||||
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,
|
||||
}: {
|
||||
items: IFile[];
|
||||
locked: UploadStatus[];
|
||||
onDelete: (file_id: IFile['id']) => void;
|
||||
size?: number;
|
||||
className?: string;
|
||||
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} index={index} collection={0}>
|
||||
<SortableImageGridItem
|
||||
key={file.id}
|
||||
id={file.id!}
|
||||
className={file.id === draggingItem?.id ? styles.dragging : undefined}
|
||||
>
|
||||
<ImageUpload
|
||||
id={file.id}
|
||||
thumb={getURL(file, ImagePresets.cover)}
|
||||
|
@ -42,13 +115,25 @@ const SortableImageGrid = SortableContainer(
|
|||
))}
|
||||
|
||||
{locked.map((item, index) => (
|
||||
<SortableImageGridItem key={item.id} index={index} collection={1} disabled>
|
||||
<ImageUpload thumb={item.thumbnail} progress={item.progress} is_uploading />
|
||||
</SortableImageGridItem>
|
||||
<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 };
|
||||
|
|
|
@ -12,3 +12,12 @@
|
|||
grid-template-columns: repeat(auto-fill, minmax(30vw, 1fr));
|
||||
}
|
||||
}
|
||||
|
||||
.overlay {
|
||||
box-shadow: rgba(0, 0, 0, 0.3) 5px 5px 10px 2px;
|
||||
border-radius: $radius;
|
||||
}
|
||||
|
||||
.dragging {
|
||||
opacity: 0.1;
|
||||
}
|
||||
|
|
|
@ -1,11 +1,44 @@
|
|||
import React from 'react';
|
||||
import React, { FC } from 'react';
|
||||
|
||||
import { SortableElement } from 'react-sortable-hoc';
|
||||
import { useSortable } from '@dnd-kit/sortable';
|
||||
import { CSS } from '@dnd-kit/utilities';
|
||||
import classNames from 'classnames';
|
||||
|
||||
import styles from './styles.module.scss';
|
||||
|
||||
const SortableImageGridItem = SortableElement(({ children }) => (
|
||||
<div className={styles.item}>{children}</div>
|
||||
));
|
||||
interface SortableImageGridItemProps {
|
||||
id: number;
|
||||
disabled?: boolean;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
const SortableImageGridItem: FC<SortableImageGridItemProps> = ({
|
||||
children,
|
||||
id,
|
||||
disabled = false,
|
||||
className,
|
||||
}) => {
|
||||
const { attributes, listeners, setNodeRef, transform, transition } = useSortable({
|
||||
id,
|
||||
disabled,
|
||||
});
|
||||
|
||||
const style = {
|
||||
transform: CSS.Transform.toString(transform),
|
||||
transition,
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={setNodeRef}
|
||||
style={style}
|
||||
{...attributes}
|
||||
{...listeners}
|
||||
className={classNames(styles.item, className)}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export { SortableImageGridItem };
|
||||
|
|
File diff suppressed because one or more lines are too long
47
yarn.lock
47
yarn.lock
|
@ -38,13 +38,44 @@
|
|||
core-js-pure "^3.20.2"
|
||||
regenerator-runtime "^0.13.4"
|
||||
|
||||
"@babel/runtime@^7.1.2", "@babel/runtime@^7.1.5", "@babel/runtime@^7.10.2", "@babel/runtime@^7.12.1", "@babel/runtime@^7.12.13", "@babel/runtime@^7.12.5", "@babel/runtime@^7.15.4", "@babel/runtime@^7.2.0", "@babel/runtime@^7.9.2":
|
||||
"@babel/runtime@^7.1.2", "@babel/runtime@^7.10.2", "@babel/runtime@^7.12.1", "@babel/runtime@^7.12.13", "@babel/runtime@^7.12.5", "@babel/runtime@^7.15.4", "@babel/runtime@^7.2.0", "@babel/runtime@^7.9.2":
|
||||
version "7.16.7"
|
||||
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.16.7.tgz#03ff99f64106588c9c403c6ecb8c3bafbbdff1fa"
|
||||
integrity sha512-9E9FJowqAsytyOY6LG+1KuueckRL+aQW+mKvXRXnuFGyRAyepJPmEo9vgMfXUA6O9u3IeEdv9MAkppFcaQwogQ==
|
||||
dependencies:
|
||||
regenerator-runtime "^0.13.4"
|
||||
|
||||
"@dnd-kit/accessibility@^3.0.0":
|
||||
version "3.0.1"
|
||||
resolved "https://registry.yarnpkg.com/@dnd-kit/accessibility/-/accessibility-3.0.1.tgz#3ccbefdfca595b0a23a5dc57d3de96bc6935641c"
|
||||
integrity sha512-HXRrwS9YUYQO9lFRc/49uO/VICbM+O+ZRpFDe9Pd1rwVv2PCNkRiTZRdxrDgng/UkvdC3Re9r2vwPpXXrWeFzg==
|
||||
dependencies:
|
||||
tslib "^2.0.0"
|
||||
|
||||
"@dnd-kit/core@^6.0.5":
|
||||
version "6.0.5"
|
||||
resolved "https://registry.yarnpkg.com/@dnd-kit/core/-/core-6.0.5.tgz#5670ad0dcc83cd51dbf2fa8c6a5c8af4ac0c1989"
|
||||
integrity sha512-3nL+Zy5cT+1XwsWdlXIvGIFvbuocMyB4NBxTN74DeBaBqeWdH9JsnKwQv7buZQgAHmAH+eIENfS1ginkvW6bCw==
|
||||
dependencies:
|
||||
"@dnd-kit/accessibility" "^3.0.0"
|
||||
"@dnd-kit/utilities" "^3.2.0"
|
||||
tslib "^2.0.0"
|
||||
|
||||
"@dnd-kit/sortable@^7.0.1":
|
||||
version "7.0.1"
|
||||
resolved "https://registry.yarnpkg.com/@dnd-kit/sortable/-/sortable-7.0.1.tgz#99c6012bbab4d8bb726c0eef7b921a338c404fdb"
|
||||
integrity sha512-n77qAzJQtMMywu25sJzhz3gsHnDOUlEjTtnRl8A87rWIhnu32zuP+7zmFjwGgvqfXmRufqiHOSlH7JPC/tnJ8Q==
|
||||
dependencies:
|
||||
"@dnd-kit/utilities" "^3.2.0"
|
||||
tslib "^2.0.0"
|
||||
|
||||
"@dnd-kit/utilities@^3.2.0":
|
||||
version "3.2.0"
|
||||
resolved "https://registry.yarnpkg.com/@dnd-kit/utilities/-/utilities-3.2.0.tgz#b3e956ea63a1347c9d0e1316b037ddcc6140acda"
|
||||
integrity sha512-h65/pn2IPCCIWwdlR2BMLqRkDxpTEONA+HQW3n765HBijLYGyrnTCLa2YQt8VVjjSQD6EfFlTE6aS2Q/b6nb2g==
|
||||
dependencies:
|
||||
tslib "^2.0.0"
|
||||
|
||||
"@eslint/eslintrc@^0.4.3":
|
||||
version "0.4.3"
|
||||
resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-0.4.3.tgz#9e42981ef035beb3dd49add17acb96e8ff6f394c"
|
||||
|
@ -2391,12 +2422,11 @@ react-sortable-hoc@^2.0.0:
|
|||
invariant "^2.2.4"
|
||||
prop-types "^15.5.7"
|
||||
|
||||
react-sticky-box@^0.9.3:
|
||||
version "0.9.3"
|
||||
resolved "https://registry.yarnpkg.com/react-sticky-box/-/react-sticky-box-0.9.3.tgz#8450d4cef8e4fdd7b0351520365bc98c97da11af"
|
||||
integrity sha512-Y/qO7vTqAvXuRR6G6ZCW4fX2Bz0GZRwiiLTVeZN5CVz9wzs37ev0Xj3KSKF/PzF0jifwATivI4t24qXG8rSz4Q==
|
||||
react-sticky-box@^1.0.2:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/react-sticky-box/-/react-sticky-box-1.0.2.tgz#7e72a0f237bdf8270cec9254337f49519a411174"
|
||||
integrity sha512-Kyvtppdtv1KqJyNU4DtrSMI0unyQRgtraZvVQ0GAazVbYiTsIVpyhpr+5R0Aavzu4uJNSe1awj2rk/qI7i6Zfw==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.1.5"
|
||||
resize-observer-polyfill "^1.5.1"
|
||||
|
||||
react@^17.0.2:
|
||||
|
@ -2864,6 +2894,11 @@ tslib@^1.10.0, tslib@^1.8.1:
|
|||
resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00"
|
||||
integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==
|
||||
|
||||
tslib@^2.0.0:
|
||||
version "2.4.0"
|
||||
resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.4.0.tgz#7cecaa7f073ce680a05847aa77be941098f36dc3"
|
||||
integrity sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==
|
||||
|
||||
tslib@^2.0.3, tslib@^2.1.0:
|
||||
version "2.3.1"
|
||||
resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.3.1.tgz#e8a335add5ceae51aa261d32a490158ef042ef01"
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue