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

refactored media components

This commit is contained in:
Fedor Katurov 2023-11-21 19:34:22 +06:00
parent b551fc44ea
commit d757b74fd0
9 changed files with 6 additions and 210 deletions

View file

@ -0,0 +1,149 @@
import { memo, useCallback, useMemo } from 'react';
import classNames from 'classnames';
import { Icon } from '~/components/common/Icon';
import { InputText } from '~/components/input/InputText';
import { PlayerState } from '~/constants/player';
import { IFile } from '~/types';
import { useAudioPlayer } from '~/utils/providers/AudioPlayerProvider';
import styles from './styles.module.scss';
type Props = {
file: IFile;
isEditing?: boolean;
onDelete?: (id: IFile['id']) => void;
onTitleChange?: (file_id: IFile['id'], title: string) => void;
};
const AudioPlayer = memo(
({ file, onDelete, isEditing, onTitleChange }: Props) => {
const {
toPercent,
file: currentFile,
setFile,
play,
status,
progress,
pause,
} = useAudioPlayer();
const onPlay = useCallback(
async (event) => {
event.stopPropagation();
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) => {
event.stopPropagation();
const { clientX, target } = event;
const { left, width } = target.getBoundingClientRect();
toPercent(((clientX - left) / width) * 100);
},
[toPercent],
);
const onDropClick = useCallback(() => {
if (!onDelete) return;
onDelete(file.id);
}, [file, onDelete]);
const title = useMemo(
() =>
(file.metadata &&
(file.metadata.title ||
[file.metadata.id3artist, file.metadata.id3title]
.filter((el) => el)
.join(' - '))) ||
file.orig_name ||
'',
[file.metadata, file.orig_name],
);
const onRename = useCallback(
(val: string) => {
if (!onTitleChange) return;
onTitleChange(file.id, val);
},
[onTitleChange, file.id],
);
const stopPropagation = useCallback(
(event) => {
if (!isEditing) {
return;
}
event.stopPropagation();
},
[isEditing],
);
const playing = currentFile?.id === file.id;
return (
<div
className={classNames(styles.wrap, {
[styles.playing]: playing,
})}
>
{onDelete && (
<div className={styles.drop} onMouseDown={onDropClick}>
<Icon icon="close" />
</div>
)}
<div
className={styles.playpause}
onClick={onPlay}
onMouseDown={stopPropagation}
>
{playing && status === PlayerState.PLAYING ? (
<Icon icon="pause" />
) : (
<Icon icon="play" />
)}
</div>
{isEditing ? (
<div className={styles.input}>
<InputText
placeholder={title}
handler={onRename}
value={file.metadata && file.metadata.title}
onMouseDown={stopPropagation}
/>
</div>
) : (
<div className={styles.content}>
<div className={styles.title}>{title || ''}</div>
<div className={styles.progress} onClick={onSeek}>
<div
className={styles.bar}
style={{
width: `${progress.progress}%`,
}}
/>
</div>
</div>
)}
</div>
);
},
);
export { AudioPlayer };

View file

@ -0,0 +1,138 @@
@import 'src/styles/variables';
.wrap {
display: flex;
flex-direction: row;
height: $comment_height;
position: relative;
align-items: center;
justify-content: stretch;
flex: 1;
user-select: none;
}
.playpause {
flex: 0 0 $comment_height;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
svg {
width: 32px;
height: 32px;
fill: $gray_50;
stroke: $gray_50;
transition: fill 250ms, stroke 250ms;
}
&:hover {
svg {
fill: white;
stroke: white;
}
}
}
.content {
flex: 1;
display: flex;
flex-direction: column;
min-width: 0;
padding: 0 $gap * 2 0 $gap;
position: relative;
}
.title {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
position: absolute;
bottom: 0;
left: 0;
width: 100%;
opacity: 0.7;
pointer-events: none;
touch-action: none;
padding: 0 10px;
box-sizing: border-box;
height: 100%;
top: 0;
text-align: left;
transition: all 0.5s;
font: $font_18_semibold;
.playing & {
top: 20px;
opacity: 1;
font-size: 12px;
padding-right: 140px;
color: $gray_75;
}
}
.progress {
height: 20px;
position: relative;
opacity: 0;
pointer-events: none;
touch-action: none;
transition: opacity 0.5s;
left: 0;
cursor: pointer;
.playing & {
opacity: 1;
pointer-events: all;
touch-action: auto;
}
&::after {
content: ' ';
position: absolute;
height: 10px;
border-radius: 5px;
background: $gray_90;
width: 100%;
top: 5px;
left: 0;
}
}
.bar {
background: $primary_gradient;
position: absolute;
height: 10px;
left: 0;
top: 5px;
border-radius: 5px;
min-width: 10px;
transition: width 0.5s;
}
.drop {
width: 24px;
height: 24px;
background: #222222;
position: absolute;
right: 10px;
top: 10px;
border-radius: 12px;
z-index: 2;
transition: background-color 250ms, opacity 0.25s;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
svg {
width: 20px;
height: 20px;
}
}
.input {
flex: 1;
box-sizing: border-box;
padding: 0 48px 0 0;
}

View file

@ -0,0 +1,109 @@
import {
FC,
ReactElement,
useCallback,
useEffect,
useRef,
useState,
} from 'react';
interface Props {
children: (props: {
setRef: (ref: HTMLElement | null) => void;
}) => ReactElement;
}
const getDistance = (event: TouchEvent) => {
return Math.hypot(
event.touches[0].pageX - event.touches[1].pageX,
event.touches[0].pageY - event.touches[1].pageY,
);
};
interface Start {
x: number;
y: number;
distance: number;
}
const PinchZoom: FC<Props> = ({ children }) => {
const start = useRef<Start>({ x: 0, y: 0, distance: 0 });
const [ref, setRef] = useState<HTMLElement | null>(null);
const imageElementScale = useRef(1);
const onTouchStart = useCallback((event: TouchEvent) => {
if (event.touches.length !== 2) {
return;
}
event.preventDefault(); // Prevent page scroll
// Calculate where the fingers have started on the X and Y axis
start.current.x = (event.touches[0].pageX + event.touches[1].pageX) / 2;
start.current.y = (event.touches[0].pageY + event.touches[1].pageY) / 2;
start.current.distance = getDistance(event);
}, []);
const onTouchMove = useCallback(
(event) => {
if (event.touches.length !== 2 || !ref) {
return;
}
event.preventDefault(); // Prevent page scroll
// Safari provides event.scale as two fingers move on the screen
// For other browsers just calculate the scale manually
const scale = event.scale ?? getDistance(event) / start.current.distance;
imageElementScale.current = Math.min(Math.max(1, scale), 4);
// Calculate how much the fingers have moved on the X and Y axis
const deltaX =
((event.touches[0].pageX + event.touches[1].pageX) / 2 -
start.current.x) *
2; // x2 for accelarated movement
const deltaY =
((event.touches[0].pageY + event.touches[1].pageY) / 2 -
start.current.y) *
2; // x2 for accelarated movement
// Transform the image to make it grow and move with fingers
const transform = `translate3d(${deltaX}px, ${deltaY}px, 0) scale(${imageElementScale})`;
ref.style.transform = transform;
ref.style.zIndex = '9999';
},
[ref],
);
const onTouchEnd = useCallback(
(event) => {
if (!ref) {
return;
}
// Reset image to it's original format
ref.style.transform = '';
ref.style.zIndex = '';
},
[ref],
);
useEffect(() => {
if (!ref) {
return;
}
ref.addEventListener('touchstart', onTouchStart);
ref.addEventListener('touchmove', onTouchMove);
ref.addEventListener('touchend', onTouchEnd);
return () => {
ref.removeEventListener('touchstart', onTouchStart);
ref.removeEventListener('touchmove', onTouchMove);
ref.removeEventListener('touchend', onTouchEnd);
};
}, [onTouchEnd, onTouchMove, onTouchStart, ref]);
return children({ setRef });
};
export { PinchZoom };