Skip to content

Commit

Permalink
ExportProject actions/reducer/state (#812)
Browse files Browse the repository at this point in the history
* Sort reducers and states; 
* Remove unnecessary layer from MergeDup reducer; 
* Add action, reducer, state for ExportProject; 
* Split export function into 2 on frontend (works as is, but needs to be modified when backend process is split);
  • Loading branch information
imnasnainaec authored Nov 16, 2020
1 parent 528e7a7 commit d1cd125
Show file tree
Hide file tree
Showing 19 changed files with 270 additions and 119 deletions.
13 changes: 13 additions & 0 deletions src/backend/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -326,8 +326,21 @@ export async function uploadLift(
return parseInt(resp.toString());
}

// Tell the backend to create a LIFT file for the project
export async function exportLift(projectId?: string) {
let projectIdToExport = projectId ? projectId : LocalStorage.getProjectId();
// ToDo: Once the create and download functions are split in the backend,
// call the create function here (fetching the project is an async placeholder)
let resp = await backendServer.get(`projects/${projectIdToExport}`, {
headers: authHeader(),
});
return resp.data;
}
// After the backend confirms that a LIFT file is ready, download it
export async function downloadLift(projectId?: string) {
let projectIdToExport = projectId ? projectId : LocalStorage.getProjectId();
// ToDo: Once the create and download functions are split in the backend,
// call the download function here (for now, both are done here)
let resp = await backendServer.get(
`projects/${projectIdToExport}/words/download`,
{
Expand Down
74 changes: 41 additions & 33 deletions src/components/App/DefaultState.tsx
Original file line number Diff line number Diff line change
@@ -1,54 +1,62 @@
import { simpleWord, Word } from "../../types/word";
import { defaultState as characterInventoryState } from "../../goals/CharInventoryCreation/CharacterInventoryReducer";
import { defaultState as mergeDuplicateGoal } from "../../goals/MergeDupGoal/MergeDupStep/MergeDupStepReducer";
import { defaultState as reviewEntriesState } from "../../goals/ReviewEntries/ReviewEntriesComponent/ReviewEntriesReducer";
import { defaultProject } from "../../types/project";
import { defaultState as loginState } from "../Login/LoginReducer";
import { defaultState as goalTimelineState } from "../GoalTimeline/DefaultState";
import { defaultState as createProjectState } from "../ProjectScreen/CreateProject/CreateProjectReducer";
import { defaultState as goalSelectorState } from "../GoalTimeline/GoalSwitcher/GoalSelectorScroll/GoalSelectorReducer";
import { defaultState as passwordResetState } from "../PasswordReset/reducer";
import { defaultState as reviewEntriesState } from "../../goals/ReviewEntries/ReviewEntriesComponent/ReviewEntriesReducer";
import { defaultState as createProjectState } from "../ProjectScreen/CreateProject/CreateProjectReducer";
import { defaultState as exportProjectState } from "../ProjectSettings/ProjectExport/ExportProjectReducer";
import { defaultState as treeViewState } from "../TreeView/TreeViewReducer";
import { defaultProject } from "../../types/project";

export const defaultState = {
draggedWordState: {
draggedWord: simpleWord("Ye", "You"),
},
mergeDupStepProps: {
parentWords: [
{
id: 1,
senses: [
{
id: 2,
dups: [simpleWord("Thee", "You"), simpleWord("Yes", "No")],
},
],
},
],
addParent: (word: Word) => word,
dropWord: () => null,
clearMerges: () => null,
draggedWord: simpleWord("Thou", "You"),
},
treeViewState: {
...treeViewState,
//login
loginState: {
...loginState,
},
goalsState: {
...goalTimelineState,
passwordResetState: {
...passwordResetState,
},

//project
createProjectState: {
...createProjectState,
name: "Test",
success: true,
},
currentProject: {
...defaultProject,
name: "Project",
},
exportProjectState: {
...exportProjectState,
},

//data entry
treeViewState: {
...treeViewState,
},

//general cleanup tools
goalSelectorState: {
...goalSelectorState,
},
passwordResetState: {
...passwordResetState,
goalsState: {
...goalTimelineState,
},
currentProject: {
...defaultProject,
name: "Project",

//merge duplicates goal
mergeDuplicateGoal: {
...mergeDuplicateGoal,
},

//character inventory goal
characterInventoryState: {
...characterInventoryState,
},

//review entries goal
reviewEntriesState: {
...reviewEntriesState,
},
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import { ThunkDispatch } from "redux-thunk";

import { downloadLift, exportLift } from "../../../backend";
import { StoreState } from "../../../types";

export enum ExportStatus {
Default = "DEFAULT",
InProgress = "IN_PROGRESS",
Success = "SUCCESS",
Failure = "FAILURE",
}

export interface ExportProjectAction {
type: ExportStatus;
projectId?: string;
}

export function asyncExportProject(projectId?: string) {
return async (
dispatch: ThunkDispatch<StoreState, any, ExportProjectAction>
) => {
dispatch(inProgress(projectId));
exportLift(projectId).catch(() => {
dispatch(failure(projectId));
});
};
}

export function asyncDownloadExport(projectId?: string) {
return async (
dispatch: ThunkDispatch<StoreState, any, ExportProjectAction>
) => {
return downloadLift(projectId)
.then((fileString) => {
return fetch(fileString)
.then((file) => {
dispatch(success(projectId));
return file.blob();
})
.catch(() => {
dispatch(failure(projectId));
});
})
.catch(() => {
dispatch(failure(projectId));
});
};
}

export function inProgress(projectId?: string): ExportProjectAction {
return {
type: ExportStatus.InProgress,
projectId,
};
}

export function success(projectId?: string): ExportProjectAction {
return {
type: ExportStatus.Success,
projectId,
};
}

export function failure(projectId?: string): ExportProjectAction {
return {
type: ExportStatus.Failure,
projectId,
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,16 @@ import { ButtonProps } from "@material-ui/core/Button";
import React, { useEffect } from "react";
import { Translate } from "react-localize-redux";

import { exportLift, getProjectName } from "../../../backend";
import { getProjectName } from "../../../backend";
import { getNowDateTimeString } from "../../../utilities";
import LoadingButton from "../../Buttons/LoadingButton";
import { ExportStatus } from "./ExportProjectActions";
import { ExportProjectState } from "./ExportProjectReducer";

interface ExportProjectButtonProps {
exportProject: (projectId?: string) => void;
downloadLift: (projectId?: string) => Promise<Blob | void>;
exportResult: ExportProjectState;
projectId?: string;
}

Expand All @@ -18,17 +23,14 @@ export default function ExportProjectButton(
) {
const [fileName, setFileName] = React.useState<null | string>(null);
const [fileUrl, setFileUrl] = React.useState<null | string>(null);
const [loading, setLoading] = React.useState<boolean>(false);
let downloadLink = React.createRef<HTMLAnchorElement>();

async function getFile() {
setLoading(true);
props.exportProject(props.projectId);
const projectName = await getProjectName(props.projectId);
setFileName(`${projectName}_${getNowDateTimeString()}`);
const fileString = await exportLift(props.projectId);
const file = await fetch(fileString).then(async (res) => res.blob());
setFileName(`${projectName}_${getNowDateTimeString()}.zip`);
const file = await props.downloadLift(props.projectId);
setFileUrl(URL.createObjectURL(file));
setLoading(false);
}

useEffect(() => {
Expand All @@ -44,7 +46,7 @@ export default function ExportProjectButton(
<LoadingButton
onClick={getFile}
color="primary"
loading={loading}
loading={props.exportResult.status === ExportStatus.InProgress}
{...props}
>
<Translate id="buttons.export" />
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { StoreAction, StoreActions } from "../../../rootActions";
import { ExportProjectAction, ExportStatus } from "./ExportProjectActions";

export interface ExportProjectState {
projectId: string;
status: ExportStatus;
}

export const defaultState: ExportProjectState = {
projectId: "",
status: ExportStatus.Default,
};

export const exportProjectReducer = (
state: ExportProjectState = defaultState,
action: StoreAction | ExportProjectAction
): ExportProjectState => {
switch (action.type) {
case ExportStatus.InProgress:
return {
...defaultState,
projectId: action.projectId ?? "",
status: action.type,
};
case ExportStatus.Success:
return {
...defaultState,
projectId: action.projectId ?? "",
status: action.type,
};
case ExportStatus.Failure:
return {
...defaultState,
projectId: action.projectId ?? "",
status: action.type,
};
case StoreActions.RESET:
return defaultState;
default:
return state;
}
};
34 changes: 34 additions & 0 deletions src/components/ProjectSettings/ProjectExport/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { connect } from "react-redux";
import { ThunkDispatch } from "redux-thunk";

import { StoreState } from "../../../types";
import {
asyncDownloadExport,
asyncExportProject,
ExportProjectAction,
} from "./ExportProjectActions";
import ExportProjectButton from "./ExportProjectButton";

function mapStateToProps(state: StoreState) {
return {
exportResult: state.exportProjectState,
};
}

function mapDispatchToProps(
dispatch: ThunkDispatch<StoreState, any, ExportProjectAction>
) {
return {
exportProject: (projectId?: string) => {
dispatch(asyncExportProject(projectId));
},
downloadLift: (projectId?: string) => {
return dispatch(asyncDownloadExport(projectId));
},
};
}

export default connect(
mapStateToProps,
mapDispatchToProps
)(ExportProjectButton);
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ import { AutoComplete } from "../../types/AutoComplete";
import { Project } from "../../types/project";
import { UserRole } from "../../types/userRole";
import BaseSettingsComponent from "../BaseSettings/BaseSettingsComponent";
import ExportProjectButton from "./ProjectExport/ExportProjectButton";
import ExportProjectButton from "./ProjectExport";
import ProjectImport from "./ProjectImport";
import ProjectLanguages from "./ProjectLanguages/ProjectLanguages";
import ProjectName from "./ProjectName";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { LocalizeContextProps, withLocalize } from "react-localize-redux";
import { getAllProjects } from "../../../backend";
import { Project } from "../../../types/project";
import theme from "../../../types/theme";
import ExportProjectButton from "../../ProjectSettings/ProjectExport/ExportProjectButton";
import ExportProjectButton from "../../ProjectSettings/ProjectExport";
import ProjectButtonWithConfirmation from "./ProjectButtonWithConfirmation";

interface ProjectManagementState {
Expand Down
17 changes: 6 additions & 11 deletions src/goals/DefaultGoal/BaseGoalScreen/tests/BaseGoalScreen.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,18 +13,13 @@ import BaseGoalScreen from "../BaseGoalScreen";
const createMockStore = configureMockStore([thunk]);
const mockStoreState = {
mergeDuplicateGoal: {
mergeTreeState: {
data: {
words: {},
senses: {},
},
tree: {
senses: {},
words: {},
},
data: {
words: {},
senses: {},
},
wordDragState: {
draggedWord: undefined,
tree: {
senses: {},
words: {},
},
},
goalsState: {
Expand Down
10 changes: 4 additions & 6 deletions src/goals/MergeDupGoal/MergeDupStep/MergeDupStepActions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -232,9 +232,9 @@ export async function mergeWord(
mapping: Hash<{ srcWord: string; order: number }>
): Promise<Hash<{ srcWord: string; order: number }>> {
// find and build MergeWord[]
const word = getState().mergeDuplicateGoal.mergeTreeState.tree.words[wordID];
const word = getState().mergeDuplicateGoal.tree.words[wordID];
if (word) {
const data = getState().mergeDuplicateGoal.mergeTreeState.data;
const data = getState().mergeDuplicateGoal.data;

// create a list of all senses and add merge type tags slit by src word
let senses: Hash<SenseWithState[]> = {};
Expand Down Expand Up @@ -394,7 +394,7 @@ export function mergeAll() {
) => {
// generate blacklist
const wordIDs: string[] = Object.keys(
getState().mergeDuplicateGoal.mergeTreeState.data.words
getState().mergeDuplicateGoal.data.words
);
const hash: string = wordIDs
.sort()
Expand All @@ -404,9 +404,7 @@ export function mergeAll() {
LocalStorage.setMergeDupsBlacklist(blacklist);
// merge words
let mapping: Hash<{ srcWord: string; order: number }> = {};
const words = Object.keys(
getState().mergeDuplicateGoal.mergeTreeState.tree.words
);
const words = Object.keys(getState().mergeDuplicateGoal.tree.words);
for (const wordID of words) {
mapping = await mergeWord(wordID, getState, mapping);
}
Expand Down
Loading

0 comments on commit d1cd125

Please sign in to comment.