-
+
+
{moduleCards.map((card) => {
const { title, disabled, description, icon, bottom } = card;
@@ -266,7 +245,7 @@ export const Module = () => {
);
})}
- {showModal && }
+ {showModal && }
);
};
@@ -281,9 +260,6 @@ const BookmarkedQuestionsBottom = () => {
//Params
const { moduleID } = useParams
();
- //Context
- const { setFilteredQuestions, moduleData } = useContext(ModuleContext);
-
//Reset anchor if component unmounts
useLayoutEffect(() => {
return () => {
@@ -367,6 +343,9 @@ const BookmarkedQuestionsBottom = () => {
let rejectedIDs: IBookmarkedQuestions["questions"] = [];
let newIDs: IBookmarkedQuestions["questions"] = [];
+ //Get module from localStorage
+ const module = parseJSON(localStorage.getItem(`repeatio-module-${moduleID}`));
+
//Get old saved questions from localStorage or provide empty array
const bookmarkedLocalStorageItem = getBookmarkedLocalStorageItem(moduleID);
@@ -376,7 +355,7 @@ const BookmarkedQuestionsBottom = () => {
//Add only ids that are in the module (as question ids) and only if not already in localStorage
importedBookmarkedFile?.questions?.forEach((importedID) => {
//Return if id of the imported saved questions is not in the module
- if (moduleData.questions?.findIndex((question: IQuestion) => question.id === importedID) === -1) {
+ if (module?.questions?.findIndex((question: IQuestion) => question.id === importedID) === -1) {
rejectedIDs?.push(importedID);
return false;
}
@@ -429,8 +408,6 @@ const BookmarkedQuestionsBottom = () => {
//Train with only the saved Questions
const onBookmarkedQuestionsClick = () => {
- //TODO for electron get from filesystem
-
//Get the bookmarked ids from the localStorage item
const bookmarkedQuestionsIDs = getBookmarkedQuestionsFromModule(moduleID);
@@ -442,25 +419,27 @@ const BookmarkedQuestionsBottom = () => {
return;
}
- //For each element in the bookmarked array return the question object
- //kinda expensive calculation (array in array) :/
- let bookmarkedQuestions: IQuestion[] = [];
- bookmarkedQuestionsIDs.forEach((item) => {
- const question = moduleData.questions.find((question: IQuestion) => question.id === item);
- //push question object to array if question is found
- if (question !== undefined) {
- bookmarkedQuestions.push(question);
- }
- });
+ // Get an array of all question IDs from the module in local storage
+ const allIds = parseJSON(localStorage.getItem("repeatio-module-cypress_1"))?.questions.reduce(
+ (acc: string[], question) => {
+ acc.push(question.id);
+ return acc;
+ },
+ []
+ );
- //Update the context
- setFilteredQuestions(bookmarkedQuestions);
+ // Find the first valid ID in bookmarkedQuestionsIDs that exists in allIds
+ const validId = bookmarkedQuestionsIDs.find((id) => allIds?.includes(id));
- //Navigate to question component
- history.push({
- pathname: `/module/${moduleID}/question/${bookmarkedQuestions[0].id}`,
- search: "?mode=chronological",
- });
+ if (validId) {
+ //Navigate to question component
+ history.push({
+ pathname: `/module/${moduleID}/question/${validId}`,
+ search: "?mode=bookmarked&order=chronological",
+ });
+ } else {
+ toast.error("Bookmarked Questions only include invalid ids! Please contact the developer on GitHub!");
+ }
};
return (
diff --git a/src/components/module/moduleContext.tsx b/src/components/module/moduleContext.tsx
deleted file mode 100644
index 450f02d..0000000
--- a/src/components/module/moduleContext.tsx
+++ /dev/null
@@ -1,126 +0,0 @@
-import React, { createContext, useMemo, useState, useEffect, useCallback } from "react";
-import { toast } from "react-toastify";
-
-//Functions
-import isElectron from "is-electron";
-import { fetchModuleFromPublicFolder } from "../../utils/fetchModuleFromPublicFolder";
-import { parseJSON } from "../../utils/parseJSON";
-import { IModule } from "./module";
-
-export interface IModuleContext {
- moduleData: IModule;
- setModuleData: React.Dispatch>;
- setContextModuleID: React.Dispatch>;
- filteredQuestions: IModule["questions"];
- setFilteredQuestions: React.Dispatch>;
-}
-
-//Create Question Context
-export const ModuleContext = createContext({} as IModuleContext);
-
-//Provide the data to all children
-export const ModuleProvider = ({ children }: { children: React.ReactNode }) => {
- const [initialData, setInitialData] = useState({} as IModule);
- const [filteredQuestions, setFilteredQuestions] = useState([]);
- const [moduleContextID, setContextModuleID] = useState("");
-
- //Change every time module name changes
- const initialDataProvider = useMemo(() => ({ initialData, setInitialData }), [initialData, setInitialData]);
-
- const filterProvider = useMemo(
- () => ({ filteredQuestions, setFilteredQuestions }),
- [filteredQuestions, setFilteredQuestions]
- );
-
- //TODO move setFilteredQuestions/setInitialData into a callback
-
- //Get the module data from the localStorage of the browser
- const getDataFromBrowser = useCallback(async () => {
- let module;
-
- if (moduleContextID !== "types_1") {
- //Fetch data from the locale Storage
- try {
- //module = JSON.parse(localStorage.getItem(`repeatio-module-${moduleContextID}`));
- module = parseJSON(localStorage.getItem(`repeatio-module-${moduleContextID}`));
- } catch (error) {
- if (error instanceof Error) {
- toast.warn(error.message);
- }
- }
- } else {
- //Fetch data from public folder
- module = await fetchModuleFromPublicFolder();
- }
-
- //Set the data
- setInitialData(module);
- setFilteredQuestions(module?.questions);
- }, [moduleContextID]);
-
- //Get all Questions from the file system / locale storage and provide them
- useEffect(() => {
- if (moduleContextID === "") return;
-
- //Get the data from the locale file system when using the electron application else (when using the website) get the data from the public folder/browser storage
- if (isElectron()) {
- // Send a message to the main process
- (window as any).api.request("toMain", ["getModule", moduleContextID]);
-
- // Called when message received from main process
- (window as any).api.response("fromMain", (data: IModule) => {
- setInitialData(data);
- setFilteredQuestions(data.questions);
- });
- } else {
- //Not using electron
- getDataFromBrowser();
- }
-
- //Cleanup
- return () => {
- setInitialData({} as IModule);
- setFilteredQuestions([]);
- };
- }, [moduleContextID, getDataFromBrowser]);
-
- //Update the localStorage/filesystem if initialData changes
- useEffect(() => {
- //Don't update the storage if the data is undefined or from the public folder (id: types_1)
- if (
- !initialData ||
- initialData === undefined ||
- Object.keys(initialData)?.length < 1 ||
- initialData?.id === "types_1"
- ) {
- return;
- }
-
- //Update filesystem (electron) or localStorage (website)
- if (isElectron()) {
- //TODO save to filesystem
- } else if (!isElectron()) {
- try {
- localStorage.setItem(`repeatio-module-${initialData.id}`, JSON.stringify(initialData, null, "\t"));
- } catch (error) {
- if (error instanceof Error) {
- toast.warn(error.message);
- }
- }
- }
- }, [initialData]);
-
- return (
-
- {children}
-
- );
-};
diff --git a/src/components/module/questionIdsContext.tsx b/src/components/module/questionIdsContext.tsx
new file mode 100644
index 0000000..4928c8f
--- /dev/null
+++ b/src/components/module/questionIdsContext.tsx
@@ -0,0 +1,31 @@
+import React, { createContext, useMemo, useState } from "react";
+
+// Interfaces
+import { IQuestion } from "../Question/useQuestion";
+
+export interface IQuestionIdsContext {
+ questionIds: IQuestion["id"][];
+ setQuestionIds: React.Dispatch>;
+}
+
+//Create Context
+export const QuestionIdsContext = createContext({} as IQuestionIdsContext);
+
+//Provide the data to all children
+export const QuestionIdsProvider = ({ children }: { children: React.ReactNode }) => {
+ const [data, setData] = useState([]);
+
+ // Memorize the data
+ const dataProvider = useMemo(() => ({ data, setData }), [data, setData]);
+
+ return (
+
+ {children}
+
+ );
+};
diff --git a/src/index.tsx b/src/index.tsx
index 7a74625..9989453 100644
--- a/src/index.tsx
+++ b/src/index.tsx
@@ -29,7 +29,7 @@ import { Footer } from "./components/Footer/Footer";
import { CustomToastContainer } from "./components/toast/toast";
//Context
-import { ModuleProvider } from "./components/module/moduleContext";
+import { QuestionIdsProvider } from "./components/module/questionIdsContext";
//Import functions
import { ScrollToTop } from "./utils/ScrollToTop";
@@ -52,11 +52,11 @@ ReactDOM.render(
-
-
+
+
+
-
-
+
diff --git a/src/utils/fetchModuleFromPublicFolder.ts b/src/utils/fetchModuleFromPublicFolder.ts
index b8c13c2..ce52f7d 100644
--- a/src/utils/fetchModuleFromPublicFolder.ts
+++ b/src/utils/fetchModuleFromPublicFolder.ts
@@ -1,22 +1,20 @@
-import { toast } from "react-toastify";
+import { IModule } from "../components/module/module";
//Fetch the module from the public folder
-export async function fetchModuleFromPublicFolder() {
+export async function fetchModuleFromPublicFolder(): Promise {
try {
- const data = await fetch("/data.json", { mode: "no-cors" });
+ const res = await fetch("/data.json", { mode: "no-cors" });
- const toJsObject = await data.json();
-
- if (data.ok) {
- return toJsObject;
+ if (res.ok) {
+ const data: IModule = await res.json();
+ return data;
} else {
- toast.warn(toJsObject.message || toJsObject.statusText);
- return;
+ const errorData = await res.json();
+ throw new Error(errorData.message || res.statusText);
}
} catch (error) {
if (error instanceof Error) {
- console.warn(error.message);
+ console.error(error.message);
}
- return;
}
}
diff --git a/src/utils/parseJSON.ts b/src/utils/parseJSON.ts
index 3be9a46..d3c6f18 100644
--- a/src/utils/parseJSON.ts
+++ b/src/utils/parseJSON.ts
@@ -5,7 +5,7 @@
export function parseJSON(value: string | null): T | undefined | null {
if (value === null) {
return null;
- } else if (value === "undefined") {
+ } else if (value === "undefined" || typeof value === "undefined") {
return undefined;
} else {
try {
diff --git a/src/utils/types.ts b/src/utils/types.ts
index be9481b..85e414e 100644
--- a/src/utils/types.ts
+++ b/src/utils/types.ts
@@ -2,3 +2,12 @@ export interface IParams {
moduleID?: string;
questionID?: string;
}
+
+export interface ISearchParams {
+ mode?: "practice" | "bookmarked"; // "selected" | "exam"
+ order?: "chronological" | "random";
+}
+
+export type TSettings = {
+ addedExampleModule?: boolean;
+};