Skip to content

Commit

Permalink
Add edit version of PronunciationsCell (#2615)
Browse files Browse the repository at this point in the history
Also: Split Pronunciations component into Backend, Frontend components
  • Loading branch information
imnasnainaec authored Sep 25, 2023
1 parent feb92ec commit 4ecb7bd
Show file tree
Hide file tree
Showing 24 changed files with 535 additions and 328 deletions.
10 changes: 4 additions & 6 deletions src/components/DataEntry/DataEntryTable/NewEntry/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import {
} from "components/DataEntry/DataEntryTable/EntryCellComponents";
import SenseDialog from "components/DataEntry/DataEntryTable/NewEntry/SenseDialog";
import VernDialog from "components/DataEntry/DataEntryTable/NewEntry/VernDialog";
import Pronunciations from "components/Pronunciations/PronunciationsComponent";
import PronunciationsFrontend from "components/Pronunciations/PronunciationsFrontend";
import { StoreState } from "types";
import theme from "types/theme";

Expand Down Expand Up @@ -290,12 +290,10 @@ export default function NewEntry(props: NewEntryProps): ReactElement {
)}
</Grid>
<Grid item xs={2} style={gridItemStyle(1)}>
<Pronunciations
wordId={""}
audioInFrontend
<PronunciationsFrontend
pronunciationFiles={newAudioUrls}
deleteAudio={(_, fileName: string) => delNewAudioUrl(fileName)}
uploadAudio={(_, audioFile: File) => addNewAudioUrl(audioFile)}
deleteAudio={delNewAudioUrl}
uploadAudio={addNewAudioUrl}
/>
</Grid>
<Grid item xs={1} style={gridItemStyle(1)}>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { newWritingSystem } from "types/writingSystem";

jest.mock("@mui/material/Autocomplete", () => "div");

jest.mock("components/Pronunciations/PronunciationsComponent", () => "div");
jest.mock("components/Pronunciations/PronunciationsFrontend", () => "div");
jest.mock("components/Pronunciations/Recorder");

const mockStore = configureMockStore()({ treeViewState: { open: false } });
Expand Down
14 changes: 7 additions & 7 deletions src/components/DataEntry/DataEntryTable/RecentEntry.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import {
GlossWithSuggestions,
VernWithSuggestions,
} from "components/DataEntry/DataEntryTable/EntryCellComponents";
import Pronunciations from "components/Pronunciations/PronunciationsComponent";
import PronunciationsBackend from "components/Pronunciations/PronunciationsBackend";
import theme from "types/theme";
import { newGloss } from "types/word";
import { firstGlossText } from "utilities/wordUtilities";
Expand Down Expand Up @@ -129,14 +129,14 @@ export default function RecentEntry(props: RecentEntryProps): ReactElement {
}}
>
{!props.disabled && (
<Pronunciations
wordId={props.entry.id}
<PronunciationsBackend
pronunciationFiles={props.entry.audio}
deleteAudio={(wordId: string, fileName: string) => {
props.deleteAudioFromWord(wordId, fileName);
wordId={props.entry.id}
deleteAudio={(fileName: string) => {
props.deleteAudioFromWord(props.entry.id, fileName);
}}
uploadAudio={(wordId: string, audioFile: File) => {
props.addAudioToWord(wordId, audioFile);
uploadAudio={(audioFile: File) => {
props.addAudioToWord(props.entry.id, audioFile);
}}
/>
)}
Expand Down
11 changes: 2 additions & 9 deletions src/components/DataEntry/DataEntryTable/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ import { getUserId } from "backend/localStorage";
import NewEntry from "components/DataEntry/DataEntryTable/NewEntry";
import RecentEntry from "components/DataEntry/DataEntryTable/RecentEntry";
import { filterWordsWithSenses } from "components/DataEntry/utilities";
import { getFileNameForWord } from "components/Pronunciations/AudioRecorder";
import { uploadFileFromUrl } from "components/Pronunciations/utilities";
import { StoreState } from "types";
import { Hash } from "types/hash";
import { useAppSelector } from "types/hooks";
Expand Down Expand Up @@ -568,14 +568,7 @@ export default function DataEntryTable(
defunctWord(oldId);
let newId = oldId;
for (const audioURL of audioURLs) {
const audioBlob = await fetch(audioURL).then((result) => result.blob());
const fileName = getFileNameForWord(newId);
const audioFile = new File([audioBlob], fileName, {
type: audioBlob.type,
lastModified: Date.now(),
});
newId = await backend.uploadAudio(newId, audioFile);
URL.revokeObjectURL(audioURL);
newId = await uploadFileFromUrl(newId, audioURL);
}
defunctWord(oldId, newId);
return newId;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ jest.mock(
"components/DataEntry/DataEntryTable/RecentEntry",
() => MockRecentEntry
);
jest.mock("components/Pronunciations/PronunciationsComponent", () => "div");
jest.mock("components/Pronunciations/PronunciationsFrontend", () => "div");
jest.mock("components/Pronunciations/Recorder");
jest.mock("utilities/utilities");

Expand Down
13 changes: 3 additions & 10 deletions src/components/Pronunciations/AudioPlayer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,10 @@ import { useAppDispatch, useAppSelector } from "types/hooks";
import { themeColors } from "types/theme";

interface PlayerProps {
pronunciationUrl: string;
wordId: string;
deleteAudio: (fileName: string) => void;
fileName: string;
deleteAudio?: (wordId: string, fileName: string) => void;
isPlaying?: boolean;
pronunciationUrl: string;
}

const useStyles = makeStyles((theme: Theme) =>
Expand Down Expand Up @@ -62,12 +61,6 @@ export default function AudioPlayer(props: PlayerProps): ReactElement {
}
}, [audio, dispatchReset, isPlaying]);

function deleteAudio(): void {
if (props.deleteAudio) {
props.deleteAudio(props.wordId, props.fileName);
}
}

function togglePlay(): void {
if (!isPlaying) {
dispatch(playing(props.fileName));
Expand Down Expand Up @@ -161,7 +154,7 @@ export default function AudioPlayer(props: PlayerProps): ReactElement {
textId="buttons.deletePermanently"
titleId="pronunciations.deleteRecording"
onClose={() => setDeleteConf(false)}
onConfirm={deleteAudio}
onConfirm={() => props.deleteAudio(props.fileName)}
buttonIdClose="audio-delete-cancel"
buttonIdConfirm="audio-delete-confirm"
/>
Expand Down
13 changes: 3 additions & 10 deletions src/components/Pronunciations/AudioRecorder.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,11 @@ import { toast } from "react-toastify";
import Recorder from "components/Pronunciations/Recorder";
import RecorderContext from "components/Pronunciations/RecorderContext";
import RecorderIcon from "components/Pronunciations/RecorderIcon";
import { getFileNameForWord } from "components/Pronunciations/utilities";

interface RecorderProps {
wordId: string;
uploadAudio: (wordId: string, audioFile: File) => void;
}

export function getFileNameForWord(wordId: string): string {
const fourCharParts = wordId.match(/.{1,6}/g);
const compressed = fourCharParts?.map((i) =>
Number("0x" + i).toString(36)
) ?? ["unknownWord"];
return compressed.join("") + "_" + new Date().getTime().toString(36);
uploadAudio: (audioFile: File) => void;
}

export default function AudioRecorder(props: RecorderProps): ReactElement {
Expand All @@ -38,7 +31,7 @@ export default function AudioRecorder(props: RecorderProps): ReactElement {
lastModified: Date.now(),
type: Recorder.blobType,
};
props.uploadAudio(props.wordId, new File([blob], fileName, options));
props.uploadAudio(new File([blob], fileName, options));
}

return (
Expand Down
64 changes: 64 additions & 0 deletions src/components/Pronunciations/PronunciationsBackend.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import { memo, ReactElement } from "react";

import { getAudioUrl } from "backend";
import AudioPlayer from "components/Pronunciations/AudioPlayer";
import AudioRecorder from "components/Pronunciations/AudioRecorder";

interface PronunciationsBackendProps {
playerOnly?: boolean;
overrideMemo?: boolean;
pronunciationFiles: string[];
wordId: string;
deleteAudio: (fileName: string) => void;
uploadAudio?: (audioFile: File) => void;
}

/** Audio recording/playing component for backend audio. */
export function PronunciationsBackend(
props: PronunciationsBackendProps
): ReactElement {
if (props.playerOnly && props.uploadAudio) {
console.warn("uploadAudio is defined but unused since playerOnly is true");
}
if (!props.playerOnly && !props.uploadAudio) {
console.warn("uploadAudio undefined; playerOnly should be set to true");
}

const audioButtons: ReactElement[] = props.pronunciationFiles.map(
(fileName) => (
<AudioPlayer
fileName={fileName}
key={fileName}
pronunciationUrl={getAudioUrl(props.wordId, fileName)}
deleteAudio={props.deleteAudio}
/>
)
);

return (
<>
{!props.playerOnly && !!props.uploadAudio && (
<AudioRecorder wordId={props.wordId} uploadAudio={props.uploadAudio} />
)}
{audioButtons}
</>
);
}

// Memoize to decrease unnecessary fetching of audio files.
// https://dmitripavlutin.com/use-react-memo-wisely/#11-custom-equality-check-of-props
function propsAreEqual(
prev: PronunciationsBackendProps,
next: PronunciationsBackendProps
): boolean {
if (next.overrideMemo) {
return false;
}
return (
prev.wordId === next.wordId &&
JSON.stringify(prev.pronunciationFiles) ===
JSON.stringify(next.pronunciationFiles)
);
}

export default memo(PronunciationsBackend, propsAreEqual);
52 changes: 0 additions & 52 deletions src/components/Pronunciations/PronunciationsComponent.tsx

This file was deleted.

35 changes: 35 additions & 0 deletions src/components/Pronunciations/PronunciationsFrontend.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { ReactElement } from "react";

import AudioPlayer from "components/Pronunciations/AudioPlayer";
import AudioRecorder from "components/Pronunciations/AudioRecorder";

interface PronunciationFrontendProps {
pronunciationFiles: string[];
elemBetweenRecordAndPlay?: ReactElement;
deleteAudio: (fileName: string) => void;
uploadAudio: (audioFile: File) => void;
}

/** Audio recording/playing component for audio being recorded and held in the frontend. */
export default function PronunciationsFrontend(
props: PronunciationFrontendProps
): ReactElement {
const audioButtons: ReactElement[] = props.pronunciationFiles.map(
(fileName) => (
<AudioPlayer
fileName={fileName}
key={fileName}
pronunciationUrl={fileName}
deleteAudio={props.deleteAudio}
/>
)
);

return (
<>
<AudioRecorder wordId={""} uploadAudio={props.uploadAudio} />
{props.elemBetweenRecordAndPlay}
{audioButtons}
</>
);
}
Loading

0 comments on commit 4ecb7bd

Please sign in to comment.