1
0
Fork 0
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:
Fedor Katurov 2022-06-30 17:13:10 +07:00
parent 47a6e02c21
commit 37a2e54543
6 changed files with 131 additions and 90 deletions

View file

@ -2,9 +2,7 @@ import React, { FC, useCallback, useMemo } from 'react';
import { UploadDropzone } from '~/components/upload/UploadDropzone'; import { UploadDropzone } from '~/components/upload/UploadDropzone';
import { UploadType } from '~/constants/uploads'; import { UploadType } from '~/constants/uploads';
import { useNodeAudios } from '~/hooks/node/useNodeAudios'; import { IFile } from '~/types';
import { useNodeFormContext } from '~/hooks/node/useNodeFormFormik';
import { useNodeImages } from '~/hooks/node/useNodeImages';
import { NodeEditorProps } from '~/types/node'; import { NodeEditorProps } from '~/types/node';
import { useUploaderContext } from '~/utils/context/UploaderContextProvider'; import { useUploaderContext } from '~/utils/context/UploaderContextProvider';
import { values } from '~/utils/ramda'; import { values } from '~/utils/ramda';
@ -17,11 +15,7 @@ import styles from './styles.module.scss';
type IProps = NodeEditorProps; type IProps = NodeEditorProps;
const AudioEditor: FC<IProps> = () => { const AudioEditor: FC<IProps> = () => {
const formik = useNodeFormContext(); const { pending, filesAudios, filesImages, setFiles, uploadFiles } = useUploaderContext()!;
const { pending, setFiles, uploadFiles } = useUploaderContext()!;
const images = useNodeImages(formik.values);
const audios = useNodeAudios(formik.values);
const pendingImages = useMemo( const pendingImages = useMemo(
() => values(pending).filter(item => item.type === UploadType.Image), () => values(pending).filter(item => item.type === UploadType.Image),
@ -33,15 +27,20 @@ const AudioEditor: FC<IProps> = () => {
[pending] [pending]
); );
const setImages = useCallback(values => setFiles([...values, ...audios]), [setFiles, audios]); const setImages = useCallback((values: IFile[]) => setFiles([...values, ...filesAudios]), [
setFiles,
const setAudios = useCallback(values => setFiles([...values, ...images]), [setFiles, images]); filesAudios,
]);
const setAudios = useCallback((values: IFile[]) => setFiles([...values, ...filesImages]), [
setFiles,
filesImages,
]);
return ( return (
<UploadDropzone onUpload={uploadFiles} helperClassName={styles.dropzone}> <UploadDropzone onUpload={uploadFiles} helperClassName={styles.dropzone}>
<div className={styles.wrap}> <div className={styles.wrap}>
<ImageGrid files={images} setFiles={setImages} locked={pendingImages} /> <ImageGrid files={filesImages} setFiles={setImages} locked={pendingImages} />
<AudioGrid files={audios} setFiles={setAudios} locked={pendingAudios} /> <AudioGrid files={filesAudios} setFiles={setAudios} locked={pendingAudios} />
</div> </div>
</UploadDropzone> </UploadDropzone>
); );

View file

@ -20,15 +20,20 @@ type Props = {
const AudioPlayer = memo(({ file, onDelete, isEditing, onTitleChange }: Props) => { const AudioPlayer = memo(({ file, onDelete, isEditing, onTitleChange }: Props) => {
const { toPercent, file: currentFile, setFile, play, status, progress, pause } = useAudioPlayer(); const { toPercent, file: currentFile, setFile, play, status, progress, pause } = useAudioPlayer();
const onPlay = useCallback(async () => { const onPlay = useCallback(
if (file.id !== currentFile?.id) { async event => {
setFile(file); event.stopPropagation();
setTimeout(() => void play(), 0);
return;
}
status === PlayerState.PLAYING ? pause() : await play(); if (file.id !== currentFile?.id) {
}, [play, pause, setFile, file, currentFile, status]); setFile(file);
setTimeout(() => void play(), 0);
return;
}
status === PlayerState.PLAYING ? pause() : await play();
},
[play, pause, setFile, file, currentFile, status]
);
const onSeek = useCallback( const onSeek = useCallback(
event => { event => {
@ -65,17 +70,32 @@ const AudioPlayer = memo(({ file, onDelete, isEditing, onTitleChange }: Props) =
[onTitleChange, file.id] [onTitleChange, file.id]
); );
const stopPropagation = useCallback(
event => {
if (!isEditing) {
return;
}
event.stopPropagation();
},
[isEditing]
);
const playing = currentFile?.id === file.id; const playing = currentFile?.id === file.id;
return ( return (
<div onClick={onPlay} className={classNames(styles.wrap, { [styles.playing]: playing })}> <div
className={classNames(styles.wrap, {
[styles.playing]: playing,
})}
>
{onDelete && ( {onDelete && (
<div className={styles.drop} onMouseDown={onDropClick}> <div className={styles.drop} onMouseDown={onDropClick}>
<Icon icon="close" /> <Icon icon="close" />
</div> </div>
)} )}
<div className={styles.playpause}> <div className={styles.playpause} onClick={onPlay} onMouseDown={stopPropagation}>
{playing && status === PlayerState.PLAYING ? <Icon icon="pause" /> : <Icon icon="play" />} {playing && status === PlayerState.PLAYING ? <Icon icon="pause" /> : <Icon icon="play" />}
</div> </div>
@ -85,6 +105,7 @@ const AudioPlayer = memo(({ file, onDelete, isEditing, onTitleChange }: Props) =
placeholder={title} placeholder={title}
handler={onRename} handler={onRename}
value={file.metadata && file.metadata.title} value={file.metadata && file.metadata.title}
onMouseDown={stopPropagation}
/> />
</div> </div>
) : ( ) : (
@ -92,7 +113,12 @@ const AudioPlayer = memo(({ file, onDelete, isEditing, onTitleChange }: Props) =
<div className={styles.title}>{title || ''}</div> <div className={styles.title}>{title || ''}</div>
<div className={styles.progress} onClick={onSeek}> <div className={styles.progress} onClick={onSeek}>
<div className={styles.bar} style={{ width: `${progress.progress}%` }} /> <div
className={styles.bar}
style={{
width: `${progress.progress}%`,
}}
/>
</div> </div>
</div> </div>
)} )}

View file

@ -26,16 +26,28 @@ const SortableAudioGrid: FC<SortableAudioGridProps> = ({
onSortEnd, onSortEnd,
onTitleChange, onTitleChange,
}) => { }) => {
const renderItem = useCallback<FC<{ item: IFile }>>( const renderItem = useCallback<FC<{ item: IFile; key?: string | number }>>(
({ item }) => ( ({ item, key }) => (
<AudioPlayer file={item} onDelete={onDelete} isEditing onTitleChange={onTitleChange} /> <AudioPlayer
file={item}
onDelete={onDelete}
isEditing
onTitleChange={onTitleChange}
key={key}
/>
), ),
[] [onTitleChange, onDelete]
); );
const renderLocked = useCallback<FC<{ locked: UploadStatus }>>( const renderLocked = useCallback<FC<{ locked: UploadStatus }>>(
({ locked }) => ( ({ 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}
/>
), ),
[] []
); );

View file

@ -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 { closestCenter, DndContext, DragOverlay } from '@dnd-kit/core';
import { rectSortingStrategy, SortableContext } from "@dnd-kit/sortable"; import { rectSortingStrategy, SortableContext } from '@dnd-kit/sortable';
import classNames from "classnames"; import classNames from 'classnames';
import { DragOverlayItem } from "~/components/sortable/DragOverlayItem"; import { DragOverlayItem } from '~/components/sortable/DragOverlayItem';
import { useSortableActions } from "~/hooks/sortable"; import { useSortableActions } from '~/hooks/sortable';
import { DivProps } from "~/utils/types"; 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 {}> { interface SortableGridProps<T extends {}, R extends {}> {
items: T[]; items: T[];

View file

@ -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 { closestCenter, DndContext, DragOverlay } from '@dnd-kit/core';
import { SortableContext, verticalListSortingStrategy } from '@dnd-kit/sortable'; import { SortableContext, verticalListSortingStrategy } from '@dnd-kit/sortable';
@ -21,56 +21,60 @@ interface SortableListProps<T extends {}, R extends {}> {
className?: string; className?: string;
} }
const SortableList = <T, R>({ const SortableList = memo(
items, <T, R>({
locked,
getID,
getLockedID,
className,
renderItem,
renderLocked,
onSortEnd,
}: SortableListProps<T, R>) => {
const { sensors, onDragEnd, onDragStart, draggingItem, ids } = useSortableActions(
items, items,
locked,
getID, getID,
onSortEnd getLockedID,
); className,
renderItem,
renderLocked,
onSortEnd,
}: SortableListProps<T, R>) => {
const { sensors, onDragEnd, onDragStart, draggingItem, ids } = useSortableActions(
items,
getID,
onSortEnd
);
return ( return (
<DndContext <DndContext
sensors={sensors} sensors={sensors}
collisionDetection={closestCenter} collisionDetection={closestCenter}
onDragEnd={onDragEnd} onDragEnd={onDragEnd}
onDragStart={onDragStart} onDragStart={onDragStart}
> >
<SortableContext items={ids} strategy={verticalListSortingStrategy}> <SortableContext items={ids} strategy={verticalListSortingStrategy}>
<div className={classNames(styles.grid, className)}> <div className={classNames(styles.grid, className)}>
{items.map(item => ( {items.map(item => (
<SortableItem <SortableItem
key={getID(item)} key={getID(item)}
id={getID(item)} id={getID(item)}
className={ className={
draggingItem && getID(item) === getID(draggingItem) ? styles.dragging : undefined draggingItem && getID(item) === getID(draggingItem) ? styles.dragging : undefined
} }
> >
{createElement(renderItem, { item })} {createElement(renderItem, { item, key: getID(item) })}
</SortableItem> </SortableItem>
))} ))}
{locked.map(item => {locked.map(item =>
createElement(renderLocked, { locked: item, key: getLockedID(item) }) createElement(renderLocked, { locked: item, key: getLockedID(item) })
)} )}
<DragOverlay> <DragOverlay>
{draggingItem ? ( {draggingItem ? (
<DragOverlayItem>{createElement(renderItem, { item: draggingItem })}</DragOverlayItem> <DragOverlayItem>
) : null} {createElement(renderItem, { item: draggingItem })}
</DragOverlay> </DragOverlayItem>
</div> ) : null}
</SortableContext> </DragOverlay>
</DndContext> </div>
); </SortableContext>
}; </DndContext>
);
}
);
export { SortableList }; export { SortableList };

View file

@ -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 { ArcProgress } from '~/components/input/ArcProgress';
import { Icon } from "~/components/input/Icon"; import { Icon } from '~/components/input/Icon';
import styles from "./styles.module.scss"; import styles from './styles.module.scss';
interface IProps { interface IProps {
id?: string; id?: string;