From 8be00032b013a32d30a5319de538cc28840d972f Mon Sep 17 00:00:00 2001 From: "D. Ror." Date: Wed, 24 Jul 2024 16:44:48 -0400 Subject: [PATCH] [DataEntryTable] Use current analysis lang with sense glosses (#3239) --- .../DataEntryTable/NewEntry/SenseDialog.tsx | 2 +- .../DataEntry/DataEntryTable/RecentEntry.tsx | 8 +-- .../DataEntry/DataEntryTable/index.tsx | 52 +++++++++++++------ .../DataEntryTable/tests/index.test.tsx | 39 ++++++++++++-- src/utilities/tests/wordUtilities.test.ts | 11 ++++ src/utilities/wordUtilities.ts | 10 ++-- 6 files changed, 96 insertions(+), 26 deletions(-) diff --git a/src/components/DataEntry/DataEntryTable/NewEntry/SenseDialog.tsx b/src/components/DataEntry/DataEntryTable/NewEntry/SenseDialog.tsx index 81ba60cba6..302c01f0b8 100644 --- a/src/components/DataEntry/DataEntryTable/NewEntry/SenseDialog.tsx +++ b/src/components/DataEntry/DataEntryTable/NewEntry/SenseDialog.tsx @@ -60,7 +60,7 @@ export function SenseList(props: SenseListProps): ReactElement { const menuItem = (sense: Sense): ReactElement => { const word: Word = { ...props.selectedWord, senses: [sense] }; - const gloss = firstGlossText(sense); + const gloss = firstGlossText(sense, props.analysisLang); return ( { - setEditing(gloss !== firstGlossText(sense)); + setEditing(gloss !== firstGlossText(sense, props.analysisLang.bcp47)); setGloss(gloss); }; const updateVernField = (vern: string): void => { @@ -54,7 +56,7 @@ export function RecentEntry(props: RecentEntryProps): ReactElement { }; function conditionallyUpdateGloss(): void { - if (firstGlossText(sense) !== gloss) { + if (firstGlossText(sense, props.analysisLang.bcp47) !== gloss) { props.updateGloss(props.rowIndex, gloss); } } diff --git a/src/components/DataEntry/DataEntryTable/index.tsx b/src/components/DataEntry/DataEntryTable/index.tsx index b7500316f4..bf0a0b5da4 100644 --- a/src/components/DataEntry/DataEntryTable/index.tsx +++ b/src/components/DataEntry/DataEntryTable/index.tsx @@ -150,7 +150,8 @@ export function makeSemDomCurrent(semDom: SemanticDomain): SemanticDomain { export function updateEntryGloss( entry: WordAccess, def: string, - semDomId: string + semDomId: string, + analysisLang: string ): Word { const sense = entry.word.senses.find((s) => s.guid === entry.senseGuid); if (!sense) { @@ -158,7 +159,15 @@ export function updateEntryGloss( } const newSense: Sense = { ...sense }; - newSense.glosses = [{ ...sense.glosses[0], def }]; + let glossIndex = sense.glosses.findIndex((g) => g.language === analysisLang); + if (glossIndex === -1) { + // It there's no gloss in the current analysis language, then it's the first gloss + // that was shown in the RecentEntry that's now being updated. + glossIndex = 0; + } + newSense.glosses = sense.glosses.map((g, i) => + i === glossIndex ? { ...g, def } : { ...g } + ); const oldSense: Sense = { ...sense }; // Move only the current semantic domain to the new sense. @@ -476,7 +485,9 @@ export default function DataEntryTable( : undefined; return { ...prev, - newGloss: selectedSense ? firstGlossText(selectedSense) : "", + newGloss: selectedSense + ? firstGlossText(selectedSense, analysisLang.bcp47) + : "", selectedSenseGuid: guid, }; }); @@ -774,14 +785,15 @@ export default function DataEntryTable( }; /** Update the selected duplicate with the new entry. - * (Only considers the first gloss, `.glosses[0]`, of each sense.) */ + * (Considers the gloss in the current analysis language.) */ const updateWordWithNewEntry = async (): Promise => { const oldWord = state.selectedDup; - if (!oldWord || !oldWord.id) { + if (!oldWord?.id) { throw new Error("You are trying to update a nonexistent word"); } const gloss = state.newGloss.trim(); + const lang = analysisLang.bcp47; const semDom = makeSemDomCurrent(props.semanticDomain); // If a dup sense is selected, update it. @@ -796,9 +808,9 @@ export default function DataEntryTable( oldSense.glosses.push(newGloss()); } - // If selected sense already has this domain, add audio without updating first. + // If sense already has this gloss and domain, add audio without updating first. if ( - oldSense.glosses[0].def === gloss && + oldSense.glosses.some((g) => g.def === gloss && g.language === lang) && oldSense.semanticDomains.some((d) => d.id === semDom.id) ) { enqueueSnackbar( @@ -810,11 +822,15 @@ export default function DataEntryTable( return; } - // Only update the selected sense if the old gloss is blank or matches the new gloss. - if (!oldSense.glosses[0].def.trim()) { - oldSense.glosses[0] = newGloss(gloss, analysisLang.bcp47); + // Only update the sense if the old gloss is missing or matches the new gloss. + let glossIndex = oldSense.glosses.findIndex((g) => g.language === lang); + if (glossIndex === -1) { + oldSense.glosses.push(newGloss(gloss, lang)); + glossIndex = oldSense.glosses.length - 1; + } else if (!oldSense.glosses[glossIndex].def.trim()) { + oldSense.glosses[glossIndex].def = gloss; } - if (oldSense.glosses[0].def === gloss) { + if (oldSense.glosses[glossIndex].def === gloss) { await updateWordBackAndFront( addSemanticDomainToSense(semDom, oldWord, state.selectedSenseGuid), state.selectedSenseGuid, @@ -826,7 +842,7 @@ export default function DataEntryTable( // Otherwise, if new gloss matches a sense, update that sense. for (const sense of oldWord.senses) { - if (sense.glosses?.length && sense.glosses[0].def === gloss) { + if (sense.glosses?.some((g) => g.def === gloss && g.language === lang)) { if (sense.semanticDomains.some((d) => d.id === semDom.id)) { // User is trying to add a sense that already exists. enqueueSnackbar( @@ -849,7 +865,7 @@ export default function DataEntryTable( // The gloss is new for this word, so add a new sense. defunctWord(oldWord.id); - const sense = newSense(gloss, analysisLang.bcp47, semDom); + const sense = newSense(gloss, lang, semDom); const senses = [...oldWord.senses, sense]; const newWord: Word = { ...oldWord, senses }; @@ -922,7 +938,7 @@ export default function DataEntryTable( // Retract and replaced with a new entry. const word = simpleWord( vernacular, - firstGlossText(oldSense), + firstGlossText(oldSense, analysisLang.bcp47), analysisLang.bcp47 ); word.id = ""; @@ -945,7 +961,12 @@ export default function DataEntryTable( const oldEntry = state.recentWords[index]; defunctWord(oldEntry.word.id); def = def.trim(); - const newWord = updateEntryGloss(oldEntry, def, props.semanticDomain.id); + const newWord = updateEntryGloss( + oldEntry, + def, + props.semanticDomain.id, + analysisLang.bcp47 + ); await updateWordInBackend(newWord); // If a sense with a new guid was added, it needs to replace the old sense in the display. @@ -959,6 +980,7 @@ export default function DataEntryTable( } }, [ + analysisLang.bcp47, defunctWord, props.semanticDomain.id, state.recentWords, diff --git a/src/components/DataEntry/DataEntryTable/tests/index.test.tsx b/src/components/DataEntry/DataEntryTable/tests/index.test.tsx index a4b1176a91..d733021b16 100644 --- a/src/components/DataEntry/DataEntryTable/tests/index.test.tsx +++ b/src/components/DataEntry/DataEntryTable/tests/index.test.tsx @@ -26,7 +26,13 @@ import { newSemanticDomainTreeNode, semDomFromTreeNode, } from "types/semanticDomain"; -import { multiSenseWord, newSense, newWord, simpleWord } from "types/word"; +import { + multiSenseWord, + newGloss, + newSense, + newWord, + simpleWord, +} from "types/word"; import { Bcp47Code } from "types/writingSystem"; import { firstGlossText } from "utilities/wordUtilities"; @@ -240,7 +246,7 @@ describe("DataEntryTable", () => { describe("updateEntryGloss", () => { it("throws error when entry doesn't have sense with specified guid", () => { const entry: WordAccess = { word: newWord(), senseGuid: "gibberish" }; - expect(() => updateEntryGloss(entry, "def", "semDomId")).toThrow(); + expect(() => updateEntryGloss(entry, "def", "semDomId", "en")).toThrow(); }); it("directly updates a sense with no other semantic domains", () => { @@ -254,7 +260,30 @@ describe("DataEntryTable", () => { const expectedWord: Word = { ...entry.word }; expectedWord.senses[senseIndex] = { ...sense, glosses: [expectedGloss] }; - expect(updateEntryGloss(entry, def, mockSemDom.id)).toEqual(expectedWord); + expect( + updateEntryGloss(entry, def, mockSemDom.id, sense.glosses[0].language) + ).toEqual(expectedWord); + }); + + it("updates gloss of specified language", () => { + const senseIndex = 1; + const sense: Sense = { ...mockMultiWord.senses[senseIndex] }; + const targetGloss = newGloss("target language", "tl"); + sense.glosses = [...sense.glosses, targetGloss]; + sense.semanticDomains = [mockSemDom]; + const entry: WordAccess = { word: mockMultiWord, senseGuid: sense.guid }; + const def = "newGlossDef"; + + const expectedGloss: Gloss = { ...targetGloss, def }; + const expectedWord: Word = { ...entry.word }; + expectedWord.senses[senseIndex] = { + ...sense, + glosses: [sense.glosses[0], expectedGloss], + }; + + expect( + updateEntryGloss(entry, def, mockSemDom.id, targetGloss.language) + ).toEqual(expectedWord); }); it("splits a sense with multiple semantic domains", () => { @@ -272,7 +301,9 @@ describe("DataEntryTable", () => { newSense.semanticDomains = [mockSemDom]; const expectedWord: Word = { ...word, senses: [oldSense, newSense] }; - expect(updateEntryGloss(entry, def, mockSemDom.id)).toEqual(expectedWord); + expect( + updateEntryGloss(entry, def, mockSemDom.id, sense.glosses[0].language) + ).toEqual(expectedWord); }); }); diff --git a/src/utilities/tests/wordUtilities.test.ts b/src/utilities/tests/wordUtilities.test.ts index bb96304972..e787c2fa00 100644 --- a/src/utilities/tests/wordUtilities.test.ts +++ b/src/utilities/tests/wordUtilities.test.ts @@ -120,6 +120,17 @@ describe("wordUtilities", () => { sense.glosses[0] = newGloss(text); expect(firstGlossText(sense)).toEqual(text); }); + + it("matches language, if lang specified and present", () => { + const sense = newSense(); + const lang = "gg"; + const defFirst = "Not this one."; + const defInLang = "This one!"; + expect(firstGlossText(sense)).toEqual(""); + sense.glosses.push(newGloss(defFirst, "en"), newGloss(defInLang, lang)); + expect(firstGlossText(sense, lang)).toEqual(defInLang); + expect(firstGlossText(sense, "other")).toEqual(defFirst); + }); }); describe("getAnalysisLangsFromWords", () => { diff --git a/src/utilities/wordUtilities.ts b/src/utilities/wordUtilities.ts index 770bc5a98e..62466173fb 100644 --- a/src/utilities/wordUtilities.ts +++ b/src/utilities/wordUtilities.ts @@ -55,11 +55,15 @@ export function compareFlags(a: Flag, b: Flag): number { } /** - * Returns the text of the first gloss of a sense. + * Returns the text of the first gloss of a sense, matching the lang tag if given. * In the case that the array of glosses is empty, returns an empty string. */ -export function firstGlossText(sense: Sense): string { - return sense.glosses[0]?.def ?? ""; +export function firstGlossText(sense: Sense, lang?: string): string { + return ( + sense.glosses.find((g) => g.language === lang)?.def ?? + sense.glosses[0]?.def ?? + "" + ); } /**