Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[DataEntryTable] Use current analysis lang with sense glosses #3239

Merged
merged 4 commits into from
Jul 24, 2024
Merged
Show file tree
Hide file tree
Changes from all 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
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
<StyledMenuItem
id={sense.guid}
Expand Down
8 changes: 5 additions & 3 deletions src/components/DataEntry/DataEntryTable/RecentEntry.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -41,11 +41,13 @@ export function RecentEntry(props: RecentEntryProps): ReactElement {
sense.glosses.push(newGloss("", props.analysisLang.bcp47));
}
const [editing, setEditing] = useState(false);
const [gloss, setGloss] = useState(firstGlossText(sense));
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));
setEditing(gloss !== firstGlossText(sense, props.analysisLang.bcp47));
setGloss(gloss);
};
const updateVernField = (vern: string): void => {
Expand All @@ -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);
}
}
Expand Down
52 changes: 37 additions & 15 deletions src/components/DataEntry/DataEntryTable/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -150,15 +150,24 @@ 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) {
throw new Error("Word has no sense with specified guid");
}

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.
Expand Down Expand Up @@ -476,7 +485,9 @@ export default function DataEntryTable(
: undefined;
return {
...prev,
newGloss: selectedSense ? firstGlossText(selectedSense) : "",
newGloss: selectedSense
? firstGlossText(selectedSense, analysisLang.bcp47)
: "",
selectedSenseGuid: guid,
};
});
Expand Down Expand Up @@ -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<void> => {
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.
Expand All @@ -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(
Expand All @@ -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,
Expand All @@ -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(
Expand All @@ -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 };

Expand Down Expand Up @@ -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 = "";
Expand All @@ -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.
Expand All @@ -959,6 +980,7 @@ export default function DataEntryTable(
}
},
[
analysisLang.bcp47,
defunctWord,
props.semanticDomain.id,
state.recentWords,
Expand Down
39 changes: 35 additions & 4 deletions src/components/DataEntry/DataEntryTable/tests/index.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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";

Expand Down Expand Up @@ -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", () => {
Expand All @@ -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", () => {
Expand All @@ -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);
});
});

Expand Down
11 changes: 11 additions & 0 deletions src/utilities/tests/wordUtilities.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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", () => {
Expand Down
10 changes: 7 additions & 3 deletions src/utilities/wordUtilities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 ??
""
);
}

/**
Expand Down
Loading