mirror of
https://github.com/muerwre/vault-frontend.git
synced 2025-04-24 20:36:40 +07:00
fixed sortable audio grid
This commit is contained in:
parent
47a6e02c21
commit
37a2e54543
6 changed files with 131 additions and 90 deletions
|
@ -2,9 +2,7 @@ import React, { FC, useCallback, useMemo } from 'react';
|
|||
|
||||
import { UploadDropzone } from '~/components/upload/UploadDropzone';
|
||||
import { UploadType } from '~/constants/uploads';
|
||||
import { useNodeAudios } from '~/hooks/node/useNodeAudios';
|
||||
import { useNodeFormContext } from '~/hooks/node/useNodeFormFormik';
|
||||
import { useNodeImages } from '~/hooks/node/useNodeImages';
|
||||
import { IFile } from '~/types';
|
||||
import { NodeEditorProps } from '~/types/node';
|
||||
import { useUploaderContext } from '~/utils/context/UploaderContextProvider';
|
||||
import { values } from '~/utils/ramda';
|
||||
|
@ -17,11 +15,7 @@ import styles from './styles.module.scss';
|
|||
type IProps = NodeEditorProps;
|
||||
|
||||
const AudioEditor: FC<IProps> = () => {
|
||||
const formik = useNodeFormContext();
|
||||
const { pending, setFiles, uploadFiles } = useUploaderContext()!;
|
||||
|
||||
const images = useNodeImages(formik.values);
|
||||
const audios = useNodeAudios(formik.values);
|
||||
const { pending, filesAudios, filesImages, setFiles, uploadFiles } = useUploaderContext()!;
|
||||
|
||||
const pendingImages = useMemo(
|
||||
() => values(pending).filter(item => item.type === UploadType.Image),
|
||||
|
@ -33,15 +27,20 @@ const AudioEditor: FC<IProps> = () => {
|
|||
[pending]
|
||||
);
|
||||
|
||||
const setImages = useCallback(values => setFiles([...values, ...audios]), [setFiles, audios]);
|
||||
|
||||
const setAudios = useCallback(values => setFiles([...values, ...images]), [setFiles, images]);
|
||||
const setImages = useCallback((values: IFile[]) => setFiles([...values, ...filesAudios]), [
|
||||
setFiles,
|
||||
filesAudios,
|
||||
]);
|
||||
const setAudios = useCallback((values: IFile[]) => setFiles([...values, ...filesImages]), [
|
||||
setFiles,
|
||||
filesImages,
|
||||
]);
|
||||
|
||||
return (
|
||||
<UploadDropzone onUpload={uploadFiles} helperClassName={styles.dropzone}>
|
||||
<div className={styles.wrap}>
|
||||
<ImageGrid files={images} setFiles={setImages} locked={pendingImages} />
|
||||
<AudioGrid files={audios} setFiles={setAudios} locked={pendingAudios} />
|
||||
<ImageGrid files={filesImages} setFiles={setImages} locked={pendingImages} />
|
||||
<AudioGrid files={filesAudios} setFiles={setAudios} locked={pendingAudios} />
|
||||
</div>
|
||||
</UploadDropzone>
|
||||
);
|
||||
|
|
|
@ -20,15 +20,20 @@ type Props = {
|
|||
const AudioPlayer = memo(({ file, onDelete, isEditing, onTitleChange }: Props) => {
|
||||
const { toPercent, file: currentFile, setFile, play, status, progress, pause } = useAudioPlayer();
|
||||
|
||||
const onPlay = useCallback(async () => {
|
||||
if (file.id !== currentFile?.id) {
|
||||
setFile(file);
|
||||
setTimeout(() => void play(), 0);
|
||||
return;
|
||||
}
|
||||
const onPlay = useCallback(
|
||||
async event => {
|
||||
event.stopPropagation();
|
||||
|
||||
status === PlayerState.PLAYING ? pause() : await play();
|
||||
}, [play, pause, setFile, file, currentFile, status]);
|
||||
if (file.id !== currentFile?.id) {
|
||||
setFile(file);
|
||||
setTimeout(() => void play(), 0);
|
||||
return;
|
||||
}
|
||||
|
||||
status === PlayerState.PLAYING ? pause() : await play();
|
||||
},
|
||||
[play, pause, setFile, file, currentFile, status]
|
||||
);
|
||||
|
||||
const onSeek = useCallback(
|
||||
event => {
|
||||
|
@ -65,17 +70,32 @@ const AudioPlayer = memo(({ file, onDelete, isEditing, onTitleChange }: Props) =
|
|||
[onTitleChange, file.id]
|
||||
);
|
||||
|
||||
const stopPropagation = useCallback(
|
||||
event => {
|
||||
if (!isEditing) {
|
||||
return;
|
||||
}
|
||||
|
||||
event.stopPropagation();
|
||||
},
|
||||
[isEditing]
|
||||
);
|
||||
|
||||
const playing = currentFile?.id === file.id;
|
||||
|
||||
return (
|
||||
<div onClick={onPlay} className={classNames(styles.wrap, { [styles.playing]: playing })}>
|
||||
<div
|
||||
className={classNames(styles.wrap, {
|
||||
[styles.playing]: playing,
|
||||
})}
|
||||
>
|
||||
{onDelete && (
|
||||
<div className={styles.drop} onMouseDown={onDropClick}>
|
||||
<Icon icon="close" />
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className={styles.playpause}>
|
||||
<div className={styles.playpause} onClick={onPlay} onMouseDown={stopPropagation}>
|
||||
{playing && status === PlayerState.PLAYING ? <Icon icon="pause" /> : <Icon icon="play" />}
|
||||
</div>
|
||||
|
||||
|
@ -85,6 +105,7 @@ const AudioPlayer = memo(({ file, onDelete, isEditing, onTitleChange }: Props) =
|
|||
placeholder={title}
|
||||
handler={onRename}
|
||||
value={file.metadata && file.metadata.title}
|
||||
onMouseDown={stopPropagation}
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
|
@ -92,7 +113,12 @@ const AudioPlayer = memo(({ file, onDelete, isEditing, onTitleChange }: Props) =
|
|||
<div className={styles.title}>{title || ''}</div>
|
||||
|
||||
<div className={styles.progress} onClick={onSeek}>
|
||||
<div className={styles.bar} style={{ width: `${progress.progress}%` }} />
|
||||
<div
|
||||
className={styles.bar}
|
||||
style={{
|
||||
width: `${progress.progress}%`,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
|
|
@ -26,16 +26,28 @@ const SortableAudioGrid: FC<SortableAudioGridProps> = ({
|
|||
onSortEnd,
|
||||
onTitleChange,
|
||||
}) => {
|
||||
const renderItem = useCallback<FC<{ item: IFile }>>(
|
||||
({ item }) => (
|
||||
<AudioPlayer file={item} onDelete={onDelete} isEditing onTitleChange={onTitleChange} />
|
||||
const renderItem = useCallback<FC<{ item: IFile; key?: string | number }>>(
|
||||
({ item, key }) => (
|
||||
<AudioPlayer
|
||||
file={item}
|
||||
onDelete={onDelete}
|
||||
isEditing
|
||||
onTitleChange={onTitleChange}
|
||||
key={key}
|
||||
/>
|
||||
),
|
||||
[]
|
||||
[onTitleChange, onDelete]
|
||||
);
|
||||
|
||||
const renderLocked = useCallback<FC<{ locked: UploadStatus }>>(
|
||||
({ locked }) => (
|
||||
<AudioUpload id={locked.id} is_uploading title={locked.name} progress={locked.progress} />
|
||||
<AudioUpload
|
||||
id={locked.id}
|
||||
is_uploading
|
||||
title={locked.name}
|
||||
progress={locked.progress}
|
||||
key={locked.id}
|
||||
/>
|
||||
),
|
||||
[]
|
||||
);
|
||||
|
|
|
@ -1,16 +1,16 @@
|
|||
import React, { createElement, FC, useMemo } from "react";
|
||||
import React, { createElement, FC, useMemo } from 'react';
|
||||
|
||||
import { closestCenter, DndContext, DragOverlay } from "@dnd-kit/core";
|
||||
import { rectSortingStrategy, SortableContext } from "@dnd-kit/sortable";
|
||||
import classNames from "classnames";
|
||||
import { closestCenter, DndContext, DragOverlay } from '@dnd-kit/core';
|
||||
import { rectSortingStrategy, SortableContext } from '@dnd-kit/sortable';
|
||||
import classNames from 'classnames';
|
||||
|
||||
import { DragOverlayItem } from "~/components/sortable/DragOverlayItem";
|
||||
import { useSortableActions } from "~/hooks/sortable";
|
||||
import { DivProps } from "~/utils/types";
|
||||
import { DragOverlayItem } from '~/components/sortable/DragOverlayItem';
|
||||
import { useSortableActions } from '~/hooks/sortable';
|
||||
import { DivProps } from '~/utils/types';
|
||||
|
||||
import { SortableItem } from "../SortableItem";
|
||||
import { SortableItem } from '../SortableItem';
|
||||
|
||||
import styles from "./styles.module.scss";
|
||||
import styles from './styles.module.scss';
|
||||
|
||||
interface SortableGridProps<T extends {}, R extends {}> {
|
||||
items: T[];
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import React, { createElement, FC } from 'react';
|
||||
import React, { createElement, FC, memo } from 'react';
|
||||
|
||||
import { closestCenter, DndContext, DragOverlay } from '@dnd-kit/core';
|
||||
import { SortableContext, verticalListSortingStrategy } from '@dnd-kit/sortable';
|
||||
|
@ -21,56 +21,60 @@ interface SortableListProps<T extends {}, R extends {}> {
|
|||
className?: string;
|
||||
}
|
||||
|
||||
const SortableList = <T, R>({
|
||||
items,
|
||||
locked,
|
||||
getID,
|
||||
getLockedID,
|
||||
className,
|
||||
renderItem,
|
||||
renderLocked,
|
||||
onSortEnd,
|
||||
}: SortableListProps<T, R>) => {
|
||||
const { sensors, onDragEnd, onDragStart, draggingItem, ids } = useSortableActions(
|
||||
const SortableList = memo(
|
||||
<T, R>({
|
||||
items,
|
||||
locked,
|
||||
getID,
|
||||
onSortEnd
|
||||
);
|
||||
getLockedID,
|
||||
className,
|
||||
renderItem,
|
||||
renderLocked,
|
||||
onSortEnd,
|
||||
}: SortableListProps<T, R>) => {
|
||||
const { sensors, onDragEnd, onDragStart, draggingItem, ids } = useSortableActions(
|
||||
items,
|
||||
getID,
|
||||
onSortEnd
|
||||
);
|
||||
|
||||
return (
|
||||
<DndContext
|
||||
sensors={sensors}
|
||||
collisionDetection={closestCenter}
|
||||
onDragEnd={onDragEnd}
|
||||
onDragStart={onDragStart}
|
||||
>
|
||||
<SortableContext items={ids} strategy={verticalListSortingStrategy}>
|
||||
<div className={classNames(styles.grid, className)}>
|
||||
{items.map(item => (
|
||||
<SortableItem
|
||||
key={getID(item)}
|
||||
id={getID(item)}
|
||||
className={
|
||||
draggingItem && getID(item) === getID(draggingItem) ? styles.dragging : undefined
|
||||
}
|
||||
>
|
||||
{createElement(renderItem, { item })}
|
||||
</SortableItem>
|
||||
))}
|
||||
return (
|
||||
<DndContext
|
||||
sensors={sensors}
|
||||
collisionDetection={closestCenter}
|
||||
onDragEnd={onDragEnd}
|
||||
onDragStart={onDragStart}
|
||||
>
|
||||
<SortableContext items={ids} strategy={verticalListSortingStrategy}>
|
||||
<div className={classNames(styles.grid, className)}>
|
||||
{items.map(item => (
|
||||
<SortableItem
|
||||
key={getID(item)}
|
||||
id={getID(item)}
|
||||
className={
|
||||
draggingItem && getID(item) === getID(draggingItem) ? styles.dragging : undefined
|
||||
}
|
||||
>
|
||||
{createElement(renderItem, { item, key: getID(item) })}
|
||||
</SortableItem>
|
||||
))}
|
||||
|
||||
{locked.map(item =>
|
||||
createElement(renderLocked, { locked: item, key: getLockedID(item) })
|
||||
)}
|
||||
{locked.map(item =>
|
||||
createElement(renderLocked, { locked: item, key: getLockedID(item) })
|
||||
)}
|
||||
|
||||
<DragOverlay>
|
||||
{draggingItem ? (
|
||||
<DragOverlayItem>{createElement(renderItem, { item: draggingItem })}</DragOverlayItem>
|
||||
) : null}
|
||||
</DragOverlay>
|
||||
</div>
|
||||
</SortableContext>
|
||||
</DndContext>
|
||||
);
|
||||
};
|
||||
<DragOverlay>
|
||||
{draggingItem ? (
|
||||
<DragOverlayItem>
|
||||
{createElement(renderItem, { item: draggingItem })}
|
||||
</DragOverlayItem>
|
||||
) : null}
|
||||
</DragOverlay>
|
||||
</div>
|
||||
</SortableContext>
|
||||
</DndContext>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
export { SortableList };
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
import React, { FC, useCallback } from "react";
|
||||
import React, { FC, useCallback } from 'react';
|
||||
|
||||
import classNames from "classnames";
|
||||
import classNames from 'classnames';
|
||||
|
||||
import { ArcProgress } from "~/components/input/ArcProgress";
|
||||
import { Icon } from "~/components/input/Icon";
|
||||
import { ArcProgress } from '~/components/input/ArcProgress';
|
||||
import { Icon } from '~/components/input/Icon';
|
||||
|
||||
import styles from "./styles.module.scss";
|
||||
import styles from './styles.module.scss';
|
||||
|
||||
interface IProps {
|
||||
id?: string;
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue