|
| 1 | +import { Check } from "@mui/icons-material"; |
| 2 | +import { Grid2, IconButton } from "@mui/material"; |
| 3 | +import { ReactElement, memo, useState } from "react"; |
| 4 | + |
| 5 | +import { Pronunciation, Word, WritingSystem } from "api/models"; |
| 6 | +import { NoteButton } from "components/Buttons"; |
| 7 | +import { |
| 8 | + DeleteEntry, |
| 9 | + GlossWithSuggestions, |
| 10 | + VernWithSuggestions, |
| 11 | +} from "components/DataEntry/DataEntryTable/EntryCellComponents"; |
| 12 | +import PronunciationsBackend from "components/Pronunciations/PronunciationsBackend"; |
| 13 | +import theme from "types/theme"; |
| 14 | +import { FileWithSpeakerId, newGloss } from "types/word"; |
| 15 | +import { firstGlossText } from "utilities/wordUtilities"; |
| 16 | + |
| 17 | +const idAffix = "recent-entry"; |
| 18 | + |
| 19 | +export interface RecentEntryHotProps { |
| 20 | + rowIndex: number; |
| 21 | + entry: Word; |
| 22 | + senseGuid: string; |
| 23 | + updateGloss: (index: number, gloss: string) => void; |
| 24 | + updateNote: (index: number, newText: string) => Promise<void>; |
| 25 | + updateVern: (index: number, newVern: string, targetWordId?: string) => void; |
| 26 | + removeEntry: (index: number) => void; |
| 27 | + addAudioToWord: (wordId: string, file: FileWithSpeakerId) => void; |
| 28 | + delAudioFromWord: (wordId: string, fileName: string) => void; |
| 29 | + repAudioInWord: (wordId: string, audio: Pronunciation) => void; |
| 30 | + focusNewEntry: () => void; |
| 31 | + analysisLang: WritingSystem; |
| 32 | + vernacularLang: WritingSystem; |
| 33 | + disabled?: boolean; |
| 34 | + close: () => void; |
| 35 | +} |
| 36 | + |
| 37 | +/** |
| 38 | + * Displays a recently entered word that a user can still edit |
| 39 | + */ |
| 40 | +export function RecentEntryHot(props: RecentEntryHotProps): ReactElement { |
| 41 | + const sense = props.entry.senses.find((s) => s.guid === props.senseGuid)!; |
| 42 | + if (sense.glosses.length < 1) { |
| 43 | + sense.glosses.push(newGloss("", props.analysisLang.bcp47)); |
| 44 | + } |
| 45 | + const [editing, setEditing] = useState(false); |
| 46 | + const [gloss, setGloss] = useState( |
| 47 | + firstGlossText(sense, props.analysisLang.bcp47) |
| 48 | + ); |
| 49 | + const [vernacular, setVernacular] = useState(props.entry.vernacular); |
| 50 | + |
| 51 | + const updateGlossField = (gloss: string): void => { |
| 52 | + setEditing(gloss !== firstGlossText(sense, props.analysisLang.bcp47)); |
| 53 | + setGloss(gloss); |
| 54 | + }; |
| 55 | + const updateVernField = (vern: string): void => { |
| 56 | + setEditing(vern !== props.entry.vernacular); |
| 57 | + setVernacular(vern); |
| 58 | + }; |
| 59 | + |
| 60 | + function conditionallyUpdateGloss(): void { |
| 61 | + if (firstGlossText(sense, props.analysisLang.bcp47) !== gloss) { |
| 62 | + props.updateGloss(props.rowIndex, gloss); |
| 63 | + props.close(); |
| 64 | + } |
| 65 | + } |
| 66 | + |
| 67 | + function conditionallyUpdateVern(): void { |
| 68 | + if (vernacular.trim()) { |
| 69 | + if (props.entry.vernacular !== vernacular) { |
| 70 | + props.updateVern(props.rowIndex, vernacular); |
| 71 | + props.close(); |
| 72 | + } |
| 73 | + } else { |
| 74 | + setVernacular(props.entry.vernacular); |
| 75 | + } |
| 76 | + } |
| 77 | + |
| 78 | + const handleRemoveEntry = (): void => props.removeEntry(props.rowIndex); |
| 79 | + const handleUpdateNote = async (noteText: string): Promise<void> => { |
| 80 | + await props.updateNote(props.rowIndex, noteText); |
| 81 | + props.close(); |
| 82 | + }; |
| 83 | + |
| 84 | + return ( |
| 85 | + <Grid2 |
| 86 | + alignItems="center" |
| 87 | + container |
| 88 | + id={`${idAffix}-${props.rowIndex}`} |
| 89 | + rowSpacing={1} |
| 90 | + > |
| 91 | + <Grid2 |
| 92 | + size={11} |
| 93 | + style={{ paddingInline: theme.spacing(2), position: "relative" }} |
| 94 | + > |
| 95 | + <VernWithSuggestions |
| 96 | + vernacular={vernacular} |
| 97 | + isNew |
| 98 | + isDisabled={props.disabled || props.entry.senses.length > 1} |
| 99 | + updateVernField={updateVernField} |
| 100 | + onBlur={() => conditionallyUpdateVern()} |
| 101 | + handleEnter={() => { |
| 102 | + vernacular && props.focusNewEntry(); |
| 103 | + }} |
| 104 | + vernacularLang={props.vernacularLang} |
| 105 | + textFieldId={`${idAffix}-${props.rowIndex}-vernacular`} |
| 106 | + /> |
| 107 | + </Grid2> |
| 108 | + |
| 109 | + <Grid2 size={1}> |
| 110 | + <IconButton onClick={props.close}> |
| 111 | + <Check sx={{ color: (t) => t.palette.success.main }} /> |
| 112 | + </IconButton> |
| 113 | + </Grid2> |
| 114 | + |
| 115 | + <Grid2 |
| 116 | + size={11} |
| 117 | + style={{ paddingInline: theme.spacing(2), position: "relative" }} |
| 118 | + > |
| 119 | + <GlossWithSuggestions |
| 120 | + gloss={gloss} |
| 121 | + isDisabled={props.disabled} |
| 122 | + isNew |
| 123 | + updateGlossField={updateGlossField} |
| 124 | + onBlur={() => conditionallyUpdateGloss()} |
| 125 | + handleEnter={() => { |
| 126 | + gloss && props.focusNewEntry(); |
| 127 | + }} |
| 128 | + analysisLang={props.analysisLang} |
| 129 | + textFieldId={`${idAffix}-${props.rowIndex}-gloss`} |
| 130 | + /> |
| 131 | + </Grid2> |
| 132 | + |
| 133 | + <Grid2 size={1} /> |
| 134 | + |
| 135 | + <Grid2 |
| 136 | + size={2} |
| 137 | + style={{ paddingInline: theme.spacing(1), position: "relative" }} |
| 138 | + > |
| 139 | + <NoteButton |
| 140 | + disabled={editing || props.disabled} |
| 141 | + noteText={props.entry.note.text} |
| 142 | + updateNote={handleUpdateNote} |
| 143 | + buttonId={`${idAffix}-${props.rowIndex}-note`} |
| 144 | + /> |
| 145 | + </Grid2> |
| 146 | + |
| 147 | + <Grid2 |
| 148 | + size={8} |
| 149 | + style={{ paddingInline: theme.spacing(1), position: "relative" }} |
| 150 | + > |
| 151 | + <PronunciationsBackend |
| 152 | + audio={props.entry.audio} |
| 153 | + disabled={editing || props.disabled} |
| 154 | + wordId={props.entry.id} |
| 155 | + deleteAudio={(fileName) => { |
| 156 | + props.delAudioFromWord(props.entry.id, fileName); |
| 157 | + }} |
| 158 | + replaceAudio={(audio) => props.repAudioInWord(props.entry.id, audio)} |
| 159 | + uploadAudio={(file) => { |
| 160 | + props.addAudioToWord(props.entry.id, file); |
| 161 | + }} |
| 162 | + /> |
| 163 | + </Grid2> |
| 164 | + |
| 165 | + <Grid2 size={1} /> |
| 166 | + |
| 167 | + <Grid2 |
| 168 | + size={1} |
| 169 | + style={{ paddingInline: theme.spacing(1), position: "relative" }} |
| 170 | + > |
| 171 | + <DeleteEntry |
| 172 | + removeEntry={handleRemoveEntry} |
| 173 | + buttonId={`${idAffix}-${props.rowIndex}-delete`} |
| 174 | + confirmId={"addWords.deleteRowWarning"} |
| 175 | + disabled={editing || props.disabled} |
| 176 | + /> |
| 177 | + </Grid2> |
| 178 | + </Grid2> |
| 179 | + ); |
| 180 | +} |
| 181 | + |
| 182 | +export default memo(RecentEntryHot); |
0 commit comments