Skip to content

Commit

Permalink
[RecentEntry] Disable row buttons when editing vern/gloss (#2944)
Browse files Browse the repository at this point in the history
  • Loading branch information
imnasnainaec authored Feb 23, 2024
1 parent d312c1a commit 179bb36
Show file tree
Hide file tree
Showing 10 changed files with 151 additions and 51 deletions.
3 changes: 2 additions & 1 deletion src/components/Buttons/IconButtonWithTooltip.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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}
</IconButton>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

Expand All @@ -34,6 +35,7 @@ export default function DeleteEntry(props: DeleteEntryProps): ReactElement {
<>
<Tooltip title={t("addWords.deleteRow")} placement="top">
<IconButton
disabled={props.disabled}
tabIndex={-1}
size="small"
onClick={handleClick}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,9 @@ import { IconButtonWithTooltip } from "components/Buttons";
import { EditTextDialog } from "components/Dialogs";

interface EntryNoteProps {
noteText: string;
buttonId?: string;
disabled?: boolean;
noteText: string;
updateNote?: (newText: string) => void | Promise<void>;
}

Expand All @@ -18,11 +19,16 @@ export default function EntryNote(props: EntryNoteProps): ReactElement {
<>
<IconButtonWithTooltip
buttonId={props.buttonId ?? "entry-note-button"}
disabled={props.disabled}
icon={
props.noteText ? (
<Comment sx={{ color: (t) => t.palette.grey[700] }} />
<Comment
sx={{ color: (t) => t.palette.grey[props.disabled ? 400 : 700] }}
/>
) : (
<AddComment sx={{ color: (t) => t.palette.grey[700] }} />
<AddComment
sx={{ color: (t) => t.palette.grey[props.disabled ? 400 : 700] }}
/>
)
}
onClick={props.updateNote ? () => setNoteOpen(true) : undefined}
Expand Down
69 changes: 37 additions & 32 deletions src/components/DataEntry/DataEntryTable/RecentEntry.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -77,7 +87,7 @@ export function RecentEntry(props: RecentEntryProps): ReactElement {
<VernWithSuggestions
vernacular={vernacular}
isDisabled={props.disabled || props.entry.senses.length > 1}
updateVernField={setVernacular}
updateVernField={updateVernField}
onBlur={() => conditionallyUpdateVern()}
handleEnter={() => {
vernacular && props.focusNewEntry();
Expand All @@ -98,7 +108,7 @@ export function RecentEntry(props: RecentEntryProps): ReactElement {
<GlossWithSuggestions
gloss={gloss}
isDisabled={props.disabled}
updateGlossField={setGloss}
updateGlossField={updateGlossField}
onBlur={() => conditionallyUpdateGloss()}
handleEnter={() => {
gloss && props.focusNewEntry();
Expand All @@ -116,13 +126,12 @@ export function RecentEntry(props: RecentEntryProps): ReactElement {
position: "relative",
}}
>
{!props.disabled && (
<EntryNote
noteText={props.entry.note.text}
updateNote={handleUpdateNote}
buttonId={`${idAffix}-${props.rowIndex}-note`}
/>
)}
<EntryNote
disabled={editing || props.disabled}
noteText={props.entry.note.text}
updateNote={handleUpdateNote}
buttonId={`${idAffix}-${props.rowIndex}-note`}
/>
</Grid>
<Grid
item
Expand All @@ -133,21 +142,18 @@ export function RecentEntry(props: RecentEntryProps): ReactElement {
position: "relative",
}}
>
{!props.disabled && (
<PronunciationsBackend
audio={props.entry.audio}
wordId={props.entry.id}
deleteAudio={(fileName) => {
props.delAudioFromWord(props.entry.id, fileName);
}}
replaceAudio={(audio) =>
props.repAudioInWord(props.entry.id, audio)
}
uploadAudio={(file) => {
props.addAudioToWord(props.entry.id, file);
}}
/>
)}
<PronunciationsBackend
audio={props.entry.audio}
disabled={editing || props.disabled}
wordId={props.entry.id}
deleteAudio={(fileName) => {
props.delAudioFromWord(props.entry.id, fileName);
}}
replaceAudio={(audio) => props.repAudioInWord(props.entry.id, audio)}
uploadAudio={(file) => {
props.addAudioToWord(props.entry.id, file);
}}
/>
</Grid>
<Grid
item
Expand All @@ -158,14 +164,13 @@ export function RecentEntry(props: RecentEntryProps): ReactElement {
position: "relative",
}}
>
{!props.disabled && (
<DeleteEntry
removeEntry={handleRemoveEntry}
buttonId={`${idAffix}-${props.rowIndex}-delete`}
confirmId={"addWords.deleteRowWarning"}
wordId={props.entry.id}
/>
)}
<DeleteEntry
removeEntry={handleRemoveEntry}
buttonId={`${idAffix}-${props.rowIndex}-delete`}
confirmId={"addWords.deleteRowWarning"}
disabled={editing || props.disabled}
wordId={props.entry.id}
/>
</Grid>
</Grid>
);
Expand Down
64 changes: 61 additions & 3 deletions src/components/DataEntry/DataEntryTable/tests/RecentEntry.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import "tests/reactI18nextMock";
import { Word } from "api/models";
import { defaultState } from "components/App/DefaultState";
import {
DeleteEntry,
EntryNote,
GlossWithSuggestions,
VernWithSuggestions,
Expand All @@ -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";
Expand Down Expand Up @@ -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<void> {
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);
Expand All @@ -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<void> {
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);
Expand All @@ -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);
});
});

Expand All @@ -131,7 +189,7 @@ describe("ExistingEntry", () => {
await act(async () => {
testHandle.props.updateText(mockText);
});
expect(mockUpdateNote).toBeCalledWith(0, mockText);
expect(mockUpdateNote).toHaveBeenCalledWith(0, mockText);
});
});
});
26 changes: 20 additions & 6 deletions src/components/Pronunciations/AudioPlayer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import {
Tooltip,
} from "@mui/material";
import {
CSSProperties,
MouseEvent,
ReactElement,
TouchEvent,
Expand All @@ -32,20 +31,18 @@ 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";
updateAudioSpeaker?: (speakerId?: string) => Promise<void> | void;
warningTextId?: string;
}

const iconStyle: CSSProperties = { color: themeColors.success };

export default function AudioPlayer(props: PlayerProps): ReactElement {
const isPlaying = useAppSelector(
(state: StoreState) =>
Expand Down Expand Up @@ -178,6 +175,22 @@ export default function AudioPlayer(props: PlayerProps): ReactElement {
);
}

const icon = isPlaying ? (
<Stop
sx={{
color: (t) =>
props.disabled ? t.palette.grey[400] : t.palette.success.main,
}}
/>
) : (
<PlayArrow
sx={{
color: (t) =>
props.disabled ? t.palette.grey[400] : t.palette.success.main,
}}
/>
);

return (
<>
<Tooltip
Expand All @@ -192,10 +205,11 @@ export default function AudioPlayer(props: PlayerProps): ReactElement {
onTouchStart={handleTouch}
onTouchEnd={enableContextMenu}
aria-label="play"
disabled={props.disabled}
id={`audio-${props.audio.fileName}`}
size={props.size || "large"}
>
{isPlaying ? <Stop sx={iconStyle} /> : <PlayArrow sx={iconStyle} />}
{icon}
</IconButton>
</Tooltip>
<Menu
Expand All @@ -214,7 +228,7 @@ export default function AudioPlayer(props: PlayerProps): ReactElement {
handleMenuOnClose();
}}
>
{isPlaying ? <Stop sx={iconStyle} /> : <PlayArrow sx={iconStyle} />}
{icon}
</MenuItem>
{canChangeSpeaker && (
<MenuItem
Expand Down
2 changes: 2 additions & 0 deletions src/components/Pronunciations/AudioRecorder.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { useAppSelector } from "types/hooks";
import { FileWithSpeakerId } from "types/word";

interface RecorderProps {
disabled?: boolean;
id: string;
noSpeaker?: boolean;
onClick?: () => void;
Expand Down Expand Up @@ -50,6 +51,7 @@ export default function AudioRecorder(props: RecorderProps): ReactElement {

return (
<RecorderIcon
disabled={props.disabled}
id={props.id}
startRecording={startRecording}
stopRecording={stopRecording}
Expand Down
Loading

0 comments on commit 179bb36

Please sign in to comment.