diff --git a/src/components/DataEntry/DataEntryTable/RecentEntryCold.tsx b/src/components/DataEntry/DataEntryTable/RecentEntryCold.tsx new file mode 100644 index 0000000000..a3657dd667 --- /dev/null +++ b/src/components/DataEntry/DataEntryTable/RecentEntryCold.tsx @@ -0,0 +1,74 @@ +import { Edit } from "@mui/icons-material"; +import { Grid2 } from "@mui/material"; +import { ReactElement, memo } from "react"; + +import { Word, WritingSystem } from "api/models"; +import { NoteButton } from "components/Buttons"; +import { AudioSummary } from "components/WordCard"; +import theme from "types/theme"; +import { TypographyWithFont } from "utilities/fontComponents"; +import { firstGlossText } from "utilities/wordUtilities"; + +const idAffix = "recent-entry"; + +export interface RecentEntryColdProps { + analysisLang: WritingSystem; + entry: Word; + rowIndex: number; + senseGuid: string; +} + +/** Displays a recently entered word that a user cannot edit. */ +export function RecentEntryCold(props: RecentEntryColdProps): ReactElement { + const sense = props.entry.senses.find((s) => s.guid === props.senseGuid); + const gloss = sense ? firstGlossText(sense, props.analysisLang.bcp47) : ""; + + return ( + + + + {props.entry.vernacular} + + + + + + {gloss} + + + + + {props.entry.note.text ? ( + + ) : null} + + + + + + + + + + + + + ); +} + +export default memo(RecentEntryCold); diff --git a/src/components/DataEntry/DataEntryTable/RecentEntryHot.tsx b/src/components/DataEntry/DataEntryTable/RecentEntryHot.tsx new file mode 100644 index 0000000000..2fcff083db --- /dev/null +++ b/src/components/DataEntry/DataEntryTable/RecentEntryHot.tsx @@ -0,0 +1,182 @@ +import { Check } from "@mui/icons-material"; +import { Grid2, IconButton } from "@mui/material"; +import { ReactElement, memo, useState } from "react"; + +import { Pronunciation, Word, WritingSystem } from "api/models"; +import { NoteButton } from "components/Buttons"; +import { + DeleteEntry, + GlossWithSuggestions, + VernWithSuggestions, +} from "components/DataEntry/DataEntryTable/EntryCellComponents"; +import PronunciationsBackend from "components/Pronunciations/PronunciationsBackend"; +import theme from "types/theme"; +import { FileWithSpeakerId, newGloss } from "types/word"; +import { firstGlossText } from "utilities/wordUtilities"; + +const idAffix = "recent-entry"; + +export interface RecentEntryHotProps { + rowIndex: number; + entry: Word; + senseGuid: string; + updateGloss: (index: number, gloss: string) => void; + updateNote: (index: number, newText: string) => Promise; + updateVern: (index: number, newVern: string, targetWordId?: string) => void; + removeEntry: (index: number) => void; + addAudioToWord: (wordId: string, file: FileWithSpeakerId) => void; + delAudioFromWord: (wordId: string, fileName: string) => void; + repAudioInWord: (wordId: string, audio: Pronunciation) => void; + focusNewEntry: () => void; + analysisLang: WritingSystem; + vernacularLang: WritingSystem; + disabled?: boolean; + close: () => void; +} + +/** + * Displays a recently entered word that a user can still edit + */ +export function RecentEntryHot(props: RecentEntryHotProps): ReactElement { + const sense = props.entry.senses.find((s) => s.guid === props.senseGuid)!; + if (sense.glosses.length < 1) { + sense.glosses.push(newGloss("", props.analysisLang.bcp47)); + } + const [editing, setEditing] = useState(false); + const [gloss, setGloss] = useState( + firstGlossText(sense, props.analysisLang.bcp47) + ); + const [vernacular, setVernacular] = useState(props.entry.vernacular); + + const updateGlossField = (gloss: string): void => { + setEditing(gloss !== firstGlossText(sense, props.analysisLang.bcp47)); + setGloss(gloss); + }; + const updateVernField = (vern: string): void => { + setEditing(vern !== props.entry.vernacular); + setVernacular(vern); + }; + + function conditionallyUpdateGloss(): void { + if (firstGlossText(sense, props.analysisLang.bcp47) !== gloss) { + props.updateGloss(props.rowIndex, gloss); + props.close(); + } + } + + function conditionallyUpdateVern(): void { + if (vernacular.trim()) { + if (props.entry.vernacular !== vernacular) { + props.updateVern(props.rowIndex, vernacular); + props.close(); + } + } else { + setVernacular(props.entry.vernacular); + } + } + + const handleRemoveEntry = (): void => props.removeEntry(props.rowIndex); + const handleUpdateNote = async (noteText: string): Promise => { + await props.updateNote(props.rowIndex, noteText); + props.close(); + }; + + return ( + + + 1} + updateVernField={updateVernField} + onBlur={() => conditionallyUpdateVern()} + handleEnter={() => { + vernacular && props.focusNewEntry(); + }} + vernacularLang={props.vernacularLang} + textFieldId={`${idAffix}-${props.rowIndex}-vernacular`} + /> + + + + + t.palette.success.main }} /> + + + + + conditionallyUpdateGloss()} + handleEnter={() => { + gloss && props.focusNewEntry(); + }} + analysisLang={props.analysisLang} + textFieldId={`${idAffix}-${props.rowIndex}-gloss`} + /> + + + + + + + + + + { + props.delAudioFromWord(props.entry.id, fileName); + }} + replaceAudio={(audio) => props.repAudioInWord(props.entry.id, audio)} + uploadAudio={(file) => { + props.addAudioToWord(props.entry.id, file); + }} + /> + + + + + + + + + ); +} + +export default memo(RecentEntryHot); diff --git a/src/components/DataEntry/DataEntryTable/index.tsx b/src/components/DataEntry/DataEntryTable/index.tsx index 0a119b4c50..3ad99501b5 100644 --- a/src/components/DataEntry/DataEntryTable/index.tsx +++ b/src/components/DataEntry/DataEntryTable/index.tsx @@ -1,9 +1,8 @@ import { ExitToApp, List as ListIcon } from "@mui/icons-material"; -import { Button, Grid, Typography } from "@mui/material"; +import { Button, Grid2, Typography } from "@mui/material"; import { enqueueSnackbar } from "notistack"; import { FormEvent, - Fragment, ReactElement, useCallback, useContext, @@ -27,7 +26,8 @@ import { import * as backend from "backend"; import { getCurrentUser, getUserId } from "backend/localStorage"; import NewEntry from "components/DataEntry/DataEntryTable/NewEntry"; -import RecentEntry from "components/DataEntry/DataEntryTable/RecentEntry"; +import RecentEntryCold from "components/DataEntry/DataEntryTable/RecentEntryCold"; +import RecentEntryHot from "components/DataEntry/DataEntryTable/RecentEntryHot"; import { filterWordsWithSenses, focusInput, @@ -36,7 +36,6 @@ import { uploadFileFromPronunciation } from "components/Pronunciations/utilities import { useAppSelector } from "rootRedux/hooks"; import { type StoreState } from "rootRedux/types"; import { type Hash } from "types/hash"; -import theme from "types/theme"; import { FileWithSpeakerId, newGloss, @@ -215,6 +214,7 @@ const defaultNewEntryState = (): NewEntryState => ({ interface EntryTableState extends NewEntryState { defunctUpdates: Hash; defunctWordIds: Hash; + recentWordEditingIndex: number | undefined; recentWords: WordAccess[]; senseSwitches: SenseSwitch[]; } @@ -223,6 +223,7 @@ const defaultEntryTableState = (): EntryTableState => ({ ...defaultNewEntryState(), defunctUpdates: {}, defunctWordIds: {}, + recentWordEditingIndex: undefined, recentWords: [], senseSwitches: [], }); @@ -1017,61 +1018,71 @@ export default function DataEntryTable( return ( ) => e?.preventDefault()}> - - - - + + + {t("addWords.vernacular")} - - - + + + + {t("addWords.glosses")} - + {state.recentWords.map((wordAccess, index) => ( - - - + {index === state.recentWordEditingIndex ? ( + + setState((prev) => ({ + ...prev, + recentWordEditingIndex: undefined, + })) + } + /> + ) : ( + + setState((prev) => ({ + ...prev, + recentWordEditingIndex: index, + })) + } + > + + + )} + ))} - + - - + - - + {props.hasDrawerButton ? ( - - + + ) : ( - + )} - - + } tabIndex={-1} onClick={handleExit} > {t("buttons.exit")} - - + + ); }