Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Port remaining media references to TypeScript #3052

Merged
merged 4 commits into from
Sep 29, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,23 +1,36 @@
import clsx from 'clsx';
import React from 'react';
import PropTypes from 'prop-types';
import formatDuration from 'format-duration';
import { useTranslator } from '@u-wave/react-translate';
import MenuItem from '@mui/material/MenuItem';
import Select from '../../Form/Select';

type Chapter = {
start: number,
end: number,
title: string,
};
type ChaptersProps = {
className?: string,
available: Chapter[],
start: number,
end: number,
tabIndex: number,
onChange: (value: Chapter) => void,
};
function Chapters({
className,
available,
start,
end,
tabIndex,
onChange,
...props
}) {
}: ChaptersProps) {
const { t } = useTranslator();
const handleChange = (event) => {
const newIndex = event.target.value;
onChange(available[newIndex]);
const handleChange = (event: React.FormEvent<{ value: number }>) => {
const newIndex = event.currentTarget.value;
if (available[newIndex] != null) {
onChange(available[newIndex]);
}
};

const selectedIndex = available.findIndex(
Expand All @@ -32,7 +45,7 @@ function Chapters({
}}
onChange={handleChange}
value={selectedIndex}
{...props}
tabIndex={tabIndex}
>
<MenuItem disabled value={-1}>
<span className="Chapters-placeholder">
Expand All @@ -53,16 +66,4 @@ function Chapters({
);
}

Chapters.propTypes = {
className: PropTypes.string,
available: PropTypes.arrayOf(PropTypes.shape({
start: PropTypes.number.isRequired,
end: PropTypes.number.isRequired,
title: PropTypes.string.isRequired,
})).isRequired,
start: PropTypes.number.isRequired,
end: PropTypes.number.isRequired,
onChange: PropTypes.func.isRequired,
};

export default Chapters;
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import cx from 'clsx';
import React from 'react';
import PropTypes from 'prop-types';
import { useCallback, useId, useState } from 'react';
import { useTranslator } from '@u-wave/react-translate';
import formatDuration from 'format-duration';
import Dialog from '@mui/material/Dialog';
Expand All @@ -22,20 +21,49 @@ import FormGroup from '../../Form/Group';
import Button from '../../Form/Button';
import TextField from '../../Form/TextField';
import Chapters from './Chapters';

const {
useCallback,
useId,
useState,
} = React;
import type { Media } from '../../../reducers/booth';

// naive HH:mm:ss → seconds
const parseDuration = (str) => str.split(':')
.map((part) => parseInt(part.trim(), 10))
.reduce((duration, part) => (duration * 60) + part, 0);
function parseDuration(str: string) {
return str.split(':')
.map((part) => parseInt(part.trim(), 10))
.reduce((duration, part) => (duration * 60) + part, 0);
}

const BASE_TAB_INDEX = 1000;

type Chapter = {
start: number,
end: number,
title: string,
};

function hasChapters(
sourceData: Record<string, unknown> | null,
): sourceData is { chapters: Chapter[] } {
return sourceData != null
&& Array.isArray(sourceData.chapters)
&& sourceData.chapters.every((chapter) => (
typeof chapter === 'object'
&& chapter != null
&& 'start' in chapter
&& typeof chapter.start === 'number'
&& 'end' in chapter
&& typeof chapter.end === 'number'
&& 'title' in chapter
&& typeof chapter.title === 'string'
));
}

type EditMediaDialogProps = {
media: Media,
open: boolean,
bodyClassName?: string,
contentClassName?: string,
titleClassName?: string,
onEditedMedia: (update: { artist: string, title: string, start: number, end: number }) => void,
onCloseDialog: () => void,
};
function EditMediaDialog({
media,
open,
Expand All @@ -44,20 +72,20 @@ function EditMediaDialog({
titleClassName,
onEditedMedia,
onCloseDialog,
}) {
}: EditMediaDialogProps) {
const { t } = useTranslator();
const id = useId();
const ariaTitle = `${id}-title`;
const startFieldId = `${id}-start`;
const endFieldId = `${id}-end`;

const [errors, setErrors] = useState(null);
const [errors, setErrors] = useState<string[] | null>(null);
const [artist, setArtist] = useState(media.artist);
const [title, setTitle] = useState(media.title);
const [start, setStart] = useState(formatDuration(media.start * 1000));
const [end, setEnd] = useState(formatDuration(media.end * 1000));

const handleSubmit = (e) => {
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();

const startSeconds = parseDuration(start);
Expand Down Expand Up @@ -89,23 +117,23 @@ function EditMediaDialog({
onCloseDialog();
};

const handleChangeArtist = useCallback((event) => {
setArtist(event.target.value);
const handleChangeArtist = useCallback((event: React.FormEvent<HTMLInputElement>) => {
setArtist(event.currentTarget.value);
}, []);

const handleChangeTitle = useCallback((event) => {
setTitle(event.target.value);
const handleChangeTitle = useCallback((event: React.FormEvent<HTMLInputElement>) => {
setTitle(event.currentTarget.value);
}, []);

const handleChangeStart = useCallback((event) => {
setStart(event.target.value);
const handleChangeStart = useCallback((event: React.FormEvent<HTMLInputElement>) => {
setStart(event.currentTarget.value);
}, []);

const handleChangeEnd = useCallback((event) => {
setEnd(event.target.value);
const handleChangeEnd = useCallback((event: React.FormEvent<HTMLInputElement>) => {
setEnd(event.currentTarget.value);
}, []);

const handleChangeChapter = useCallback((chapter) => {
const handleChangeChapter = useCallback((chapter: Chapter) => {
setStart(formatDuration(chapter.start * 1000));
setEnd(formatDuration(chapter.end * 1000));
}, []);
Expand Down Expand Up @@ -181,8 +209,7 @@ function EditMediaDialog({
/>
);

const chapterCount = media.sourceData?.chapters?.length ?? 0;
const chapters = chapterCount > 0 ? (
const chapters = hasChapters(media.sourceData) && media.sourceData.chapters.length > 0 ? (
<FormGroup className="FormGroup--noSpacing EditMediaDialog-chapters">
<p className="EditMediaDialog-chaptersLabel">
{t('dialogs.editMedia.chapterLabel')}
Expand Down Expand Up @@ -270,14 +297,4 @@ function EditMediaDialog({
);
}

EditMediaDialog.propTypes = {
open: PropTypes.bool,
media: PropTypes.object,
bodyClassName: PropTypes.string,
contentClassName: PropTypes.string,
titleClassName: PropTypes.string,
onEditedMedia: PropTypes.func.isRequired,
onCloseDialog: PropTypes.func.isRequired,
};

export default EditMediaDialog;
63 changes: 0 additions & 63 deletions src/components/Dialogs/PreviewMediaDialog/index.jsx

This file was deleted.

51 changes: 51 additions & 0 deletions src/components/Dialogs/PreviewMediaDialog/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import Dialog from '@mui/material/Dialog';
import DialogContent from '@mui/material/DialogContent';
import DialogCloseAnimation from '../../DialogCloseAnimation';
import PreviewPlayer from '../../Video/Player';
import type { Media } from '../../../reducers/booth';

function getTitle(media: Media) {
return `${media.artist} – ${media.title}`;
}

type PreviewMediaDialogProps = {
open: boolean,
media?: Media | null,
volume: number,
onCloseDialog: () => void,
};
function PreviewMediaDialog({
open,
media,
volume,
onCloseDialog,
}: PreviewMediaDialogProps) {
const dialog = open && media != null ? (
<Dialog
onClose={onCloseDialog}
aria-label={getTitle(media)}
open
disableEnforceFocus
maxWidth={false}
classes={{
root: 'AppColumn AppColumn--left',
paper: 'Dialog PreviewMediaDialog',
}}
BackdropProps={{
className: 'AppColumn AppColumn--left',
}}
>
<DialogContent className="Dialog-body PreviewMediaDialog-content">
<PreviewPlayer media={media} volume={volume} />
</DialogContent>
</Dialog>
) : null;

return (
<DialogCloseAnimation delay={450}>
{dialog}
</DialogCloseAnimation>
);
}

export default PreviewMediaDialog;
2 changes: 1 addition & 1 deletion src/components/Player/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ type PlayerProps = {
mode?: 'preview' | undefined,
volume: number,
isMuted: boolean,
media: Media,
media: Media | null,
seek: number,
onPlay?: () => void,
};
Expand Down
4 changes: 1 addition & 3 deletions src/components/Video/Player.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,11 @@ type PreviewPlayerProps = {
media: Media,
seek?: number,
volume: number,
isMuted: boolean,
};
function PreviewPlayer({
media,
seek = 0,
volume,
isMuted,
}: PreviewPlayerProps) {
const { getMediaSource } = useMediaSources();
const source = getMediaSource(media.sourceType);
Expand All @@ -27,7 +25,7 @@ function PreviewPlayer({
active
seek={seek}
media={media}
volume={isMuted ? 0 : volume}
volume={volume}
mode="preview"
/>
);
Expand Down
26 changes: 0 additions & 26 deletions src/components/Video/VideoProgressBar.jsx

This file was deleted.

Loading