Skip to content
Merged
Show file tree
Hide file tree
Changes from 11 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
2 changes: 1 addition & 1 deletion 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 Pronunciations from "components/Pronunciations";
import { StoreState } from "types";
import theme from "types/theme";

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", () => "div");
jest.mock("components/Pronunciations/Recorder");

const mockStore = configureMockStore()({ treeViewState: { open: false } });
Expand Down
2 changes: 1 addition & 1 deletion 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 Pronunciations from "components/Pronunciations";
import theme from "types/theme";
import { newGloss } from "types/word";
import { firstGlossText } from "utilities/wordUtilities";
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", () => "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
9 changes: 1 addition & 8 deletions src/components/Pronunciations/AudioRecorder.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,20 +5,13 @@ 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);
}

export default function AudioRecorder(props: RecorderProps): ReactElement {
const recorder = useContext(RecorderContext);
const { t } = useTranslation();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,32 +5,38 @@ import AudioPlayer from "components/Pronunciations/AudioPlayer";
import AudioRecorder from "components/Pronunciations/AudioRecorder";

interface PronunciationProps {
wordId: string;
audioInFrontend?: boolean;
pronunciationFiles: string[];
overrideMemo?: boolean;
spacer?: ReactElement;
wordId: string;
deleteAudio: (wordId: string, fileName: string) => void;
uploadAudio: (wordId: string, audioFile: File) => void;
uploadAudio?: (wordId: string, audioFile: File) => void;
}

/** Audio recording/playing component */
export function Pronunciations(props: PronunciationProps): ReactElement {
const audioButtons: ReactElement[] = props.pronunciationFiles.map(
(fileName) => (
<AudioPlayer
key={fileName}
wordId={props.wordId}
fileName={fileName}
key={fileName}
pronunciationUrl={
props.audioInFrontend ? fileName : getAudioUrl(props.wordId, fileName)
}
deleteAudio={props.deleteAudio}
deleteAudio={(fileName: string) =>
props.deleteAudio(props.wordId, fileName)
}
/>
)
);

return (
<>
<AudioRecorder wordId={props.wordId} uploadAudio={props.uploadAudio} />
{!!props.uploadAudio && (
<AudioRecorder wordId={props.wordId} uploadAudio={props.uploadAudio} />
)}
{props.spacer}
{audioButtons}
</>
);
Expand All @@ -42,6 +48,9 @@ function pronunciationPropsAreEqual(
prev: PronunciationProps,
next: PronunciationProps
): boolean {
if (next.overrideMemo) {
return false;
}
return (
prev.wordId === next.wordId &&
JSON.stringify(prev.pronunciationFiles) ===
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@ import configureMockStore from "redux-mock-store";

import "tests/reactI18nextMock";

import Pronunciations from "components/Pronunciations";
import AudioPlayer from "components/Pronunciations/AudioPlayer";
import AudioRecorder from "components/Pronunciations/AudioRecorder";
import Pronunciations from "components/Pronunciations/PronunciationsComponent";
import RecorderIcon, {
recordButtonId,
recordIconId,
Expand Down
28 changes: 28 additions & 0 deletions src/components/Pronunciations/utilities.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { uploadAudio } from "backend";

/** Generate a timestamp-based file name for the given `wordId`. */
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);
}

/** Given an audio file `url` that was generated with `URL.createObjectURL()`,
* add that audio file to the word with the given `wordId`.
* Return the id of the updated word. */
export async function uploadFileFromUrl(
wordId: string,
url: string
): Promise<string> {
const audioBlob = await fetch(url).then((result) => result.blob());
const fileName = getFileNameForWord(wordId);
const audioFile = new File([audioBlob], fileName, {
type: audioBlob.type,
lastModified: Date.now(),
});
const newId = await uploadAudio(wordId, audioFile);
URL.revokeObjectURL(url);
return newId;
}
43 changes: 38 additions & 5 deletions src/goals/ReviewEntries/ReviewEntriesComponent/CellColumns.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ function cleanRegExp(input: string): RegExp {
export interface FieldParameterStandard {
rowData: ReviewEntriesWord;
value: any;
onRowDataChange?: (...args: any) => any;
onRowDataChange?: (word: ReviewEntriesWord) => any;
}

let currentSort: SortStyle = SortStyle.None;
Expand Down Expand Up @@ -394,26 +394,59 @@ const columns: Column<any>[] = [
// Audio column
{
title: ColumnTitle.Pronunciations,
// field determines what is passed as props.value to editComponent
field: ReviewEntriesWordField.Pronunciations,
editable: "never",
filterPlaceholder: "#",
render: (rowData: ReviewEntriesWord) => (
<PronunciationsCell
pronunciationFiles={rowData.audio}
wordId={rowData.id}
pronunciationFiles={rowData.pronunciationFiles}
/>
),
editComponent: (props: FieldParameterStandard) => (
<PronunciationsCell
audioFunctions={{
addNewAudio: (file: File): void => {
props.onRowDataChange &&
props.onRowDataChange({
...props.rowData,
audioNew: [
...(props.rowData.audioNew ?? []),
URL.createObjectURL(file),
],
});
},
delNewAudio: (url: string): void => {
props.onRowDataChange &&
props.onRowDataChange({
...props.rowData,
audioNew: props.rowData.audioNew?.filter((u) => u !== url),
});
},
delOldAudio: (fileName: string): void => {
props.onRowDataChange &&
props.onRowDataChange({
...props.rowData,
audio: props.rowData.audio.filter((f) => f !== fileName),
});
},
}}
pronunciationFiles={props.rowData.audio}
pronunciationsNew={props.rowData.audioNew}
wordId={props.rowData.id}
/>
),
customFilterAndSearch: (
filter: string,
rowData: ReviewEntriesWord
): boolean => {
return parseInt(filter) === rowData.pronunciationFiles.length;
return parseInt(filter) === rowData.audio.length;
},
customSort: (a: ReviewEntriesWord, b: ReviewEntriesWord): number => {
if (currentSort !== SortStyle.Pronunciation) {
currentSort = SortStyle.Pronunciation;
}
return b.pronunciationFiles.length - a.pronunciationFiles.length;
return b.audio.length - a.audio.length;
},
},

Expand Down
Original file line number Diff line number Diff line change
@@ -1,27 +1,51 @@
import Pronunciations from "components/Pronunciations/PronunciationsComponent";
import Pronunciations from "components/Pronunciations";
import {
deleteAudio,
uploadAudio,
} from "goals/ReviewEntries/ReviewEntriesComponent/Redux/ReviewEntriesActions";
import { useAppDispatch } from "types/hooks";

interface PronunciationsCellProps {
wordId: string;
audioFunctions?: {
addNewAudio: (file: File) => void;
delNewAudio: (url: string) => void;
delOldAudio: (fileName: string) => void;
};
pronunciationFiles: string[];
pronunciationsNew?: string[];
wordId: string;
}

/** Used to connect the pronunciation component to the deleteAudio and uploadAudio actions */
export default function PronunciationsCell(props: PronunciationsCellProps) {
const dispatch = useAppDispatch();
const dispatchDelete = (wordId: string, fileName: string) =>
dispatch(deleteAudio(wordId, fileName));
const dispatchUpload = (oldWordId: string, audioFile: File) =>
dispatch(uploadAudio(oldWordId, audioFile));

return (
const { addNewAudio, delNewAudio, delOldAudio } = props.audioFunctions ?? {};

return props.audioFunctions ? (
<Pronunciations
audioInFrontend
overrideMemo
pronunciationFiles={props.pronunciationsNew ?? []}
spacer={
<Pronunciations
overrideMemo
pronunciationFiles={props.pronunciationFiles}
wordId={props.wordId}
deleteAudio={(_, fileName: string) => delOldAudio!(fileName)}
/>
}
wordId={""}
deleteAudio={(_, fileName: string) => delNewAudio!(fileName)}
uploadAudio={(_, audioFile: File) => addNewAudio!(audioFile)}
/>
) : (
<Pronunciations
wordId={props.wordId}
pronunciationFiles={props.pronunciationFiles}
wordId={props.wordId}
deleteAudio={dispatchDelete}
uploadAudio={dispatchUpload}
/>
Expand Down
Loading