Skip to content

Commit

Permalink
Port EditMediaDialog to TypeScript
Browse files Browse the repository at this point in the history
  • Loading branch information
goto-bus-stop committed Sep 29, 2024
1 parent e0a6626 commit 6592812
Show file tree
Hide file tree
Showing 8 changed files with 81 additions and 63 deletions.
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;
4 changes: 2 additions & 2 deletions src/hooks/useRoomHistory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ interface ApiMedia {
_id: string
sourceID: string
sourceType: string
sourceData: object
sourceData: Record<string, unknown> | null
artist: string
title: string
duration: number
Expand All @@ -30,7 +30,7 @@ interface ApiHistoryEntry {
title: string,
start: number,
end: number,
sourceData: object,
sourceData: Record<string, unknown> | null,
},
playedAt: string,
upvotes: string[],
Expand Down
2 changes: 1 addition & 1 deletion src/reducers/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ interface Media {
_id: string,
sourceType: string,
sourceID: string,
sourceData: object,
sourceData: Record<string, unknown> | null,
artist: string,
title: string,
duration: number,
Expand Down
2 changes: 1 addition & 1 deletion src/reducers/booth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ export interface Media {
_id: string,
sourceType: string,
sourceID: string,
sourceData: object,
sourceData: Record<string, unknown> | null,
artist: string,
title: string,
thumbnail: string,
Expand Down
3 changes: 2 additions & 1 deletion src/reducers/playlists.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { type PayloadAction, createSelector, createSlice } from '@reduxjs/toolki
import escapeStringRegExp from 'escape-string-regexp';
import indexBy from 'just-index';
import naturalCmp from 'natural-compare';
import type { JsonObject } from 'type-fest';
import { createAsyncThunk } from '../redux/api';
import uwFetch, { type ListResponse } from '../utils/fetch';
import mergeIncludedModels from '../utils/mergeIncludedModels';
Expand All @@ -26,7 +27,7 @@ interface ApiMedia {
_id: string;
sourceID: string;
sourceType: string;
sourceData: object;
sourceData: JsonObject | null;
artist: string;
title: string;
thumbnail: string;
Expand Down
3 changes: 1 addition & 2 deletions src/redux/socket.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import type { AnyAction, Middleware } from 'redux';
import { createAction } from '@reduxjs/toolkit';
import type { JsonObject } from 'type-fest';
import type { AppDispatch } from './configureStore';
import {
SOCKET_CONNECT,
Expand Down Expand Up @@ -54,7 +53,7 @@ export type SocketMessageParams = {
title: string,
thumbnail: string,
duration: number,
sourceData: JsonObject,
sourceData: Record<string, unknown> | null,
},
},
playedAt: number,
Expand Down
2 changes: 1 addition & 1 deletion src/sources/soundcloud/Player.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ function SoundCloudPlayer({
const needsTap = error?.name === 'NotAllowedError';

const audioUrl = useMemo(() => {
if ('streamUrl' in media.sourceData && typeof media.sourceData.streamUrl === 'string'
if (media.sourceData != null && typeof media.sourceData.streamUrl === 'string'
&& enabled && active) {
const { streamUrl } = media.sourceData;
return `${streamUrl}?client_id=${CLIENT_ID}`;
Expand Down

0 comments on commit 6592812

Please sign in to comment.