Skip to content

Commit

Permalink
Implement find-and-replace in goal history with undo
Browse files Browse the repository at this point in the history
  • Loading branch information
imnasnainaec committed Feb 20, 2024
1 parent c5a28ed commit 9956777
Show file tree
Hide file tree
Showing 11 changed files with 333 additions and 79 deletions.
26 changes: 26 additions & 0 deletions Backend/Controllers/WordController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,32 @@ public async Task<IActionResult> IsInFrontier(string projectId, string wordId)
return Ok(await _wordRepo.IsInFrontier(projectId, wordId));
}

/// <summary> Checks if Frontier has words in specified <see cref="Project"/>. </summary>
[HttpPost("areinfrontier", Name = "AreInFrontier")]
[ProducesResponseType(StatusCodes.Status200OK, Type = typeof(List<string>))]
public async Task<IActionResult> AreInFrontier(string projectId, [FromBody, BindRequired] List<string> wordIds)
{
if (!await _permissionService.HasProjectPermission(HttpContext, Permission.WordEntry, projectId))
{
return Forbid();
}
var project = await _projRepo.GetProject(projectId);
if (project is null)
{
return NotFound(projectId);
}

var idsInFrontier = new List<string>();
foreach (var id in wordIds)
{
if (await _wordRepo.IsInFrontier(projectId, id))
{
idsInFrontier.Add(id);
}
}
return Ok(idsInFrontier);
}

/// <summary>
/// Checks if a <see cref="Word"/> is a duplicate--i.e., are its primary text fields
/// (Vernacular, Gloss text, Definition text) contained in a frontier entry?
Expand Down
10 changes: 8 additions & 2 deletions public/locales/en/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -335,7 +335,7 @@
"undo": {
"undo": "Undo Edit",
"undoDialog": "Undo this edit?",
"undoDisabled": "Undo Unavailable"
"undoDisabled": "Undo unavailable"
}
},
"charInventory": {
Expand Down Expand Up @@ -371,6 +371,12 @@
"more": "more",
"noWordChanges": "No words changed by find-and-replace.",
"wordChanges": "Words changed by find-and-replace:"
},
"undo": {
"undo": "Undo find-and-replace",
"undoDialog": "Undo words changes by find-and-replace?",
"undoDisabled": "Undo unavailable",
"undoWords": "Undo available for {{ val1 }} of the {{ val2 }} changed words."
}
},
"mergeDups": {
Expand Down Expand Up @@ -427,7 +433,7 @@
"undo": {
"undo": "Undo Merge",
"undoDialog": "Undo this merge?",
"undoDisabled": "Undo Unavailable"
"undoDisabled": "Undo unavailable"
}
},
"flags": {
Expand Down
140 changes: 140 additions & 0 deletions src/api/api/word-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,63 @@ export const WordApiAxiosParamCreator = function (
configuration?: Configuration
) {
return {
/**
*
* @param {string} projectId
* @param {Array<string>} requestBody
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
areInFrontier: async (
projectId: string,
requestBody: Array<string>,
options: any = {}
): Promise<RequestArgs> => {
// verify required parameter 'projectId' is not null or undefined
assertParamExists("areInFrontier", "projectId", projectId);
// verify required parameter 'requestBody' is not null or undefined
assertParamExists("areInFrontier", "requestBody", requestBody);
const localVarPath =
`/v1/projects/{projectId}/words/areinfrontier`.replace(
`{${"projectId"}}`,
encodeURIComponent(String(projectId))
);
// use dummy base URL string because the URL constructor only accepts absolute URLs.
const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);
let baseOptions;
if (configuration) {
baseOptions = configuration.baseOptions;
}

const localVarRequestOptions = {
method: "POST",
...baseOptions,
...options,
};
const localVarHeaderParameter = {} as any;
const localVarQueryParameter = {} as any;

localVarHeaderParameter["Content-Type"] = "application/json";

setSearchParams(localVarUrlObj, localVarQueryParameter, options.query);
let headersFromBaseOptions =
baseOptions && baseOptions.headers ? baseOptions.headers : {};
localVarRequestOptions.headers = {
...localVarHeaderParameter,
...headersFromBaseOptions,
...options.headers,
};
localVarRequestOptions.data = serializeDataIfNeeded(
requestBody,
localVarRequestOptions,
configuration
);

return {
url: toPathString(localVarUrlObj),
options: localVarRequestOptions,
};
},
/**
*
* @param {string} projectId
Expand Down Expand Up @@ -568,6 +625,32 @@ export const WordApiAxiosParamCreator = function (
export const WordApiFp = function (configuration?: Configuration) {
const localVarAxiosParamCreator = WordApiAxiosParamCreator(configuration);
return {
/**
*
* @param {string} projectId
* @param {Array<string>} requestBody
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
async areInFrontier(
projectId: string,
requestBody: Array<string>,
options?: any
): Promise<
(axios?: AxiosInstance, basePath?: string) => AxiosPromise<Array<string>>
> {
const localVarAxiosArgs = await localVarAxiosParamCreator.areInFrontier(
projectId,
requestBody,
options
);
return createRequestFunction(
localVarAxiosArgs,
globalAxios,
BASE_PATH,
configuration
);
},
/**
*
* @param {string} projectId
Expand Down Expand Up @@ -839,6 +922,22 @@ export const WordApiFactory = function (
) {
const localVarFp = WordApiFp(configuration);
return {
/**
*
* @param {string} projectId
* @param {Array<string>} requestBody
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
areInFrontier(
projectId: string,
requestBody: Array<string>,
options?: any
): AxiosPromise<Array<string>> {
return localVarFp
.areInFrontier(projectId, requestBody, options)
.then((request) => request(axios, basePath));
},
/**
*
* @param {string} projectId
Expand Down Expand Up @@ -1000,6 +1099,27 @@ export const WordApiFactory = function (
};
};

/**
* Request parameters for areInFrontier operation in WordApi.
* @export
* @interface WordApiAreInFrontierRequest
*/
export interface WordApiAreInFrontierRequest {
/**
*
* @type {string}
* @memberof WordApiAreInFrontier
*/
readonly projectId: string;

/**
*
* @type {Array<string>}
* @memberof WordApiAreInFrontier
*/
readonly requestBody: Array<string>;
}

/**
* Request parameters for createWord operation in WordApi.
* @export
Expand Down Expand Up @@ -1210,6 +1330,26 @@ export interface WordApiUpdateWordRequest {
* @extends {BaseAPI}
*/
export class WordApi extends BaseAPI {
/**
*
* @param {WordApiAreInFrontierRequest} requestParameters Request parameters.
* @param {*} [options] Override http request option.
* @throws {RequiredError}
* @memberof WordApi
*/
public areInFrontier(
requestParameters: WordApiAreInFrontierRequest,
options?: any
) {
return WordApiFp(this.configuration)
.areInFrontier(
requestParameters.projectId,
requestParameters.requestBody,
options
)
.then((request) => request(this.axios, this.basePath));
}

/**
*
* @param {WordApiCreateWordRequest} requestParameters Request parameters.
Expand Down
11 changes: 10 additions & 1 deletion src/backend/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -812,11 +812,20 @@ export async function isInFrontier(
wordId: string,
projectId?: string
): Promise<boolean> {
projectId = projectId || LocalStorage.getProjectId();
projectId ||= LocalStorage.getProjectId();
const params = { projectId, wordId };
return (await wordApi.isInFrontier(params, defaultOptions())).data;
}

export async function areInFrontier(
wordIds: string[],
projectId?: string
): Promise<string[]> {
projectId ||= LocalStorage.getProjectId();
const params = { projectId, requestBody: wordIds };
return (await wordApi.areInFrontier(params, defaultOptions())).data;
}

export async function updateDuplicate(
dupId: string,
word: Word
Expand Down
2 changes: 1 addition & 1 deletion src/components/GoalTimeline/tests/GoalRedux.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ const mockCompletedMerge: MergeUndoIds = {
};
const mockCharInvChanges: CharInvChanges = {
charChanges: [["'", CharacterStatus.Undecided, CharacterStatus.Accepted]],
wordChanges: { ["id-a"]: "id-b" },
wordChanges: [{ ["id-a"]: "id-b" }],
};

const mockEdit = (): Edit => ({
Expand Down
65 changes: 36 additions & 29 deletions src/components/WordCard/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import {
Badge,
Card,
CardContent,
CardHeader,
IconButton,
Typography,
} from "@mui/material";
Expand Down Expand Up @@ -42,39 +43,45 @@ export default function WordCard(props: WordCardProps): ReactElement {
}
}, [editedBy, provenance]);

/* Vernacular */
const title = (
<TypographyWithFont variant="h5" vernacular>
{word.vernacular}
</TypographyWithFont>
);

/* Icons/buttons beside vernacular */
const action = (
<>
{/* Condensed audio, note, flag */}
{!full && (
<>
<AudioSummary count={audio.length} />
{!!note.text && <EntryNote noteText={note.text} />}
{flag.active && <FlagButton flag={flag} />}
</>
)}
{/* Button for expand/condense */}
<IconButtonWithTooltip
buttonId={buttonIdFull(word.id)}
icon={
full ? (
<CloseFullscreen sx={{ color: (t) => t.palette.grey[900] }} />
) : (
<OpenInFull sx={{ color: (t) => t.palette.grey[600] }} />
)
}
onClick={() => setFull(!full)}
/>
</>
);

return (
<Card
sx={{ backgroundColor: (t) => t.palette.grey[300], minWidth: "200px" }}
>
<CardContent sx={{ position: "relative" }}>
{/* Vernacular */}
<TypographyWithFont variant="h5" vernacular>
{word.vernacular}
</TypographyWithFont>

<div style={{ position: "absolute", right: 0, top: 0 }}>
{/* Condensed audio, note, flag */}
{!full && (
<>
<AudioSummary count={audio.length} />
{!!note.text && <EntryNote noteText={note.text} />}
{flag.active && <FlagButton flag={flag} />}
</>
)}
{/* Button for expand/condense */}
<IconButtonWithTooltip
buttonId={buttonIdFull(word.id)}
icon={
full ? (
<CloseFullscreen sx={{ color: (t) => t.palette.grey[900] }} />
) : (
<OpenInFull sx={{ color: (t) => t.palette.grey[600] }} />
)
}
onClick={() => setFull(!full)}
/>
</div>

<CardHeader action={action} title={title} />
<CardContent>
{/* Expanded audio, note, flag */}
{full && (
<>
Expand Down
Loading

0 comments on commit 9956777

Please sign in to comment.