diff --git a/src/components/Buttons/IconButtonWithTooltip.tsx b/src/components/Buttons/IconButtonWithTooltip.tsx
index 51527916db..fd854f1e77 100644
--- a/src/components/Buttons/IconButtonWithTooltip.tsx
+++ b/src/components/Buttons/IconButtonWithTooltip.tsx
@@ -3,6 +3,7 @@ import { MouseEventHandler, ReactElement, ReactNode } from "react";
import { useTranslation } from "react-i18next";
interface IconButtonWithTooltipProps {
+ disabled?: boolean;
icon: ReactElement;
text?: ReactNode;
textId?: string;
@@ -27,7 +28,7 @@ export default function IconButtonWithTooltip(
onClick={props.onClick}
size={props.size || "medium"}
id={props.buttonId}
- disabled={!props.onClick}
+ disabled={props.disabled || !props.onClick}
>
{props.icon}
diff --git a/src/components/DataEntry/DataEntryTable/EntryCellComponents/DeleteEntry.tsx b/src/components/DataEntry/DataEntryTable/EntryCellComponents/DeleteEntry.tsx
index 5e6377311c..7ab950514c 100644
--- a/src/components/DataEntry/DataEntryTable/EntryCellComponents/DeleteEntry.tsx
+++ b/src/components/DataEntry/DataEntryTable/EntryCellComponents/DeleteEntry.tsx
@@ -12,6 +12,7 @@ interface DeleteEntryProps {
// if no confirmId is specified, then there is no popup
// and deletion will happen when the button is pressed
confirmId?: string;
+ disabled?: boolean;
wordId?: string;
}
@@ -34,6 +35,7 @@ export default function DeleteEntry(props: DeleteEntryProps): ReactElement {
<>
void | Promise;
}
@@ -18,11 +19,16 @@ export default function EntryNote(props: EntryNoteProps): ReactElement {
<>
t.palette.grey[700] }} />
+ t.palette.grey[props.disabled ? 400 : 700] }}
+ />
) : (
- t.palette.grey[700] }} />
+ t.palette.grey[props.disabled ? 400 : 700] }}
+ />
)
}
onClick={props.updateNote ? () => setNoteOpen(true) : undefined}
diff --git a/src/components/DataEntry/DataEntryTable/RecentEntry.tsx b/src/components/DataEntry/DataEntryTable/RecentEntry.tsx
index 95264f1f85..84419bc9d1 100644
--- a/src/components/DataEntry/DataEntryTable/RecentEntry.tsx
+++ b/src/components/DataEntry/DataEntryTable/RecentEntry.tsx
@@ -40,9 +40,19 @@ export function RecentEntry(props: RecentEntryProps): ReactElement {
if (sense.glosses.length < 1) {
sense.glosses.push(newGloss("", props.analysisLang.bcp47));
}
+ const [editing, setEditing] = useState(false);
const [gloss, setGloss] = useState(firstGlossText(sense));
const [vernacular, setVernacular] = useState(props.entry.vernacular);
+ const updateGlossField = (gloss: string): void => {
+ setEditing(gloss !== firstGlossText(sense));
+ setGloss(gloss);
+ };
+ const updateVernField = (vern: string): void => {
+ setEditing(vern !== props.entry.vernacular);
+ setVernacular(vern);
+ };
+
function conditionallyUpdateGloss(): void {
if (firstGlossText(sense) !== gloss) {
props.updateGloss(props.rowIndex, gloss);
@@ -77,7 +87,7 @@ export function RecentEntry(props: RecentEntryProps): ReactElement {
1}
- updateVernField={setVernacular}
+ updateVernField={updateVernField}
onBlur={() => conditionallyUpdateVern()}
handleEnter={() => {
vernacular && props.focusNewEntry();
@@ -98,7 +108,7 @@ export function RecentEntry(props: RecentEntryProps): ReactElement {
conditionallyUpdateGloss()}
handleEnter={() => {
gloss && props.focusNewEntry();
@@ -116,13 +126,12 @@ export function RecentEntry(props: RecentEntryProps): ReactElement {
position: "relative",
}}
>
- {!props.disabled && (
-
- )}
+
- {!props.disabled && (
- {
- props.delAudioFromWord(props.entry.id, fileName);
- }}
- replaceAudio={(audio) =>
- props.repAudioInWord(props.entry.id, audio)
- }
- uploadAudio={(file) => {
- props.addAudioToWord(props.entry.id, file);
- }}
- />
- )}
+ {
+ props.delAudioFromWord(props.entry.id, fileName);
+ }}
+ replaceAudio={(audio) => props.repAudioInWord(props.entry.id, audio)}
+ uploadAudio={(file) => {
+ props.addAudioToWord(props.entry.id, file);
+ }}
+ />
- {!props.disabled && (
-
- )}
+
);
diff --git a/src/components/DataEntry/DataEntryTable/tests/RecentEntry.test.tsx b/src/components/DataEntry/DataEntryTable/tests/RecentEntry.test.tsx
index 1101939a44..3735826258 100644
--- a/src/components/DataEntry/DataEntryTable/tests/RecentEntry.test.tsx
+++ b/src/components/DataEntry/DataEntryTable/tests/RecentEntry.test.tsx
@@ -13,6 +13,7 @@ import "tests/reactI18nextMock";
import { Word } from "api/models";
import { defaultState } from "components/App/DefaultState";
import {
+ DeleteEntry,
EntryNote,
GlossWithSuggestions,
VernWithSuggestions,
@@ -21,6 +22,7 @@ import RecentEntry from "components/DataEntry/DataEntryTable/RecentEntry";
import { EditTextDialog } from "components/Dialogs";
import AudioPlayer from "components/Pronunciations/AudioPlayer";
import AudioRecorder from "components/Pronunciations/AudioRecorder";
+import PronunciationsBackend from "components/Pronunciations/PronunciationsBackend";
import theme from "types/theme";
import { newPronunciation, simpleWord } from "types/word";
import { newWritingSystem } from "types/writingSystem";
@@ -89,6 +91,34 @@ describe("ExistingEntry", () => {
});
describe("vernacular", () => {
+ it("disables buttons if changing", async () => {
+ await renderWithWord(mockWord);
+ const vern = testHandle.findByType(VernWithSuggestions);
+ const note = testHandle.findByType(EntryNote);
+ const audio = testHandle.findByType(PronunciationsBackend);
+ const del = testHandle.findByType(DeleteEntry);
+
+ expect(note.props.disabled).toBeFalsy();
+ expect(audio.props.disabled).toBeFalsy();
+ expect(del.props.disabled).toBeFalsy();
+
+ async function updateVern(text: string): Promise {
+ await act(async () => {
+ await vern.props.updateVernField(text);
+ });
+ }
+
+ await updateVern(mockText);
+ expect(note.props.disabled).toBeTruthy();
+ expect(audio.props.disabled).toBeTruthy();
+ expect(del.props.disabled).toBeTruthy();
+
+ await updateVern(mockVern);
+ expect(note.props.disabled).toBeFalsy();
+ expect(audio.props.disabled).toBeFalsy();
+ expect(del.props.disabled).toBeFalsy();
+ });
+
it("updates if changed", async () => {
await renderWithWord(mockWord);
testHandle = testHandle.findByType(VernWithSuggestions);
@@ -102,11 +132,39 @@ describe("ExistingEntry", () => {
await updateVernAndBlur(mockVern);
expect(mockUpdateVern).toHaveBeenCalledTimes(0);
await updateVernAndBlur(mockText);
- expect(mockUpdateVern).toBeCalledWith(0, mockText);
+ expect(mockUpdateVern).toHaveBeenCalledWith(0, mockText);
});
});
describe("gloss", () => {
+ it("disables buttons if changing", async () => {
+ await renderWithWord(mockWord);
+ const gloss = testHandle.findByType(GlossWithSuggestions);
+ const note = testHandle.findByType(EntryNote);
+ const audio = testHandle.findByType(PronunciationsBackend);
+ const del = testHandle.findByType(DeleteEntry);
+
+ expect(note.props.disabled).toBeFalsy();
+ expect(audio.props.disabled).toBeFalsy();
+ expect(del.props.disabled).toBeFalsy();
+
+ async function updateGloss(text: string): Promise {
+ await act(async () => {
+ await gloss.props.updateGlossField(text);
+ });
+ }
+
+ await updateGloss(mockText);
+ expect(note.props.disabled).toBeTruthy();
+ expect(audio.props.disabled).toBeTruthy();
+ expect(del.props.disabled).toBeTruthy();
+
+ await updateGloss(mockGloss);
+ expect(note.props.disabled).toBeFalsy();
+ expect(audio.props.disabled).toBeFalsy();
+ expect(del.props.disabled).toBeFalsy();
+ });
+
it("updates if changed", async () => {
await renderWithWord(mockWord);
testHandle = testHandle.findByType(GlossWithSuggestions);
@@ -120,7 +178,7 @@ describe("ExistingEntry", () => {
await updateGlossAndBlur(mockGloss);
expect(mockUpdateGloss).toHaveBeenCalledTimes(0);
await updateGlossAndBlur(mockText);
- expect(mockUpdateGloss).toBeCalledWith(0, mockText);
+ expect(mockUpdateGloss).toHaveBeenCalledWith(0, mockText);
});
});
@@ -131,7 +189,7 @@ describe("ExistingEntry", () => {
await act(async () => {
testHandle.props.updateText(mockText);
});
- expect(mockUpdateNote).toBeCalledWith(0, mockText);
+ expect(mockUpdateNote).toHaveBeenCalledWith(0, mockText);
});
});
});
diff --git a/src/components/Pronunciations/AudioPlayer.tsx b/src/components/Pronunciations/AudioPlayer.tsx
index 8222b8410f..74a2ff3a9f 100644
--- a/src/components/Pronunciations/AudioPlayer.tsx
+++ b/src/components/Pronunciations/AudioPlayer.tsx
@@ -10,7 +10,6 @@ import {
Tooltip,
} from "@mui/material";
import {
- CSSProperties,
MouseEvent,
ReactElement,
TouchEvent,
@@ -32,11 +31,11 @@ import {
import { PronunciationsStatus } from "components/Pronunciations/Redux/PronunciationsReduxTypes";
import { StoreState } from "types";
import { useAppDispatch, useAppSelector } from "types/hooks";
-import { themeColors } from "types/theme";
interface PlayerProps {
audio: Pronunciation;
deleteAudio?: (fileName: string) => void;
+ disabled?: boolean;
onClick?: () => void;
pronunciationUrl?: string;
size?: "large" | "medium" | "small";
@@ -44,8 +43,6 @@ interface PlayerProps {
warningTextId?: string;
}
-const iconStyle: CSSProperties = { color: themeColors.success };
-
export default function AudioPlayer(props: PlayerProps): ReactElement {
const isPlaying = useAppSelector(
(state: StoreState) =>
@@ -178,6 +175,22 @@ export default function AudioPlayer(props: PlayerProps): ReactElement {
);
}
+ const icon = isPlaying ? (
+
+ props.disabled ? t.palette.grey[400] : t.palette.success.main,
+ }}
+ />
+ ) : (
+
+ props.disabled ? t.palette.grey[400] : t.palette.success.main,
+ }}
+ />
+ );
+
return (
<>
- {isPlaying ? : }
+ {icon}