diff --git a/src/App.vue b/src/App.vue
index 8a8dc23..b7acef0 100644
--- a/src/App.vue
+++ b/src/App.vue
@@ -3,23 +3,26 @@
-
+
-
+
{{ snackbar.text }}
-
- Close
+
+ Close
-
+
@@ -30,19 +33,16 @@
import Vue from "vue";
import Component from "vue-class-component";
-import { Deck, CustomDialogOptions, FFCFile } from "./types";
+import { Deck, LearningSession, CustomDialogOptions, Event } from "./types";
import NavigationBar from "./components/layout/NavigationBar.vue";
import CustomDialog from "./components/customdialog/CustomDialog.vue";
+import { registerEventListenerForMainApp } from "./helpers/eventListener";
import {
readFromLocalStorage,
saveToLocalStorage,
- clearLocalStorage,
- SyncItem,
+ SyncItem
} from "./helpers/localStorageHelper";
-import { addDecksFromFile, addDecksFromJSON } from "./helpers/addDecksHelper";
-
-const DEFAULT_SNACKBAR_TIMEOUT = 2000;
const AppProps = Vue.extend({
props: {
@@ -57,34 +57,18 @@ const AppProps = Vue.extend({
}
})
export default class App extends AppProps {
- propertiesToSyncWithLocalStorage = [{ key: "decks", defaultValue: [] }] as SyncItem[];
+ propertiesToSyncWithLocalStorage = [
+ { key: "decks", defaultValue: [] },
+ { key: "lerningSession", defaultValue: {} }
+ ] as SyncItem[];
decks = [] as Deck[];
- navBarList = [
- {
- to: "/",
- icon: "mdi-home",
- title: "Home"
- },
- {
- to: "/add",
- icon: "mdi-plus",
- title: "Add Deck"
- },
- {
- to: "/settings",
- icon: "mdi-cog",
- title: "Settings"
- },
- {
- to: "/about",
- icon: "mdi-information",
- title: "About"
- }
- ];
+ learningSession = {
+ elements: [{ deckId: 1, cardId: 1, showAnswer: false, rating: 100 }],
+ currentElementIndex: -1
+ } as LearningSession;
snackbar = {
text: "",
- snackbar: false,
- timeout: DEFAULT_SNACKBAR_TIMEOUT
+ snackbar: false
};
$refs!: {
@@ -92,25 +76,14 @@ export default class App extends AppProps {
customDialog: CustomDialog;
};
+ setSelectedStatusForAllDecks(status: boolean) {
+ this.decks.forEach(deck => {
+ deck.selected = status;
+ });
+ }
+
created() {
- this.$eventHub.$on("deleteSelectedDecks", () => {
- this.decks = this.decks.filter(deck => !deck.selected);
- });
- this.$eventHub.$on("addDecksFromFile", (fileContent: string) => {
- addDecksFromFile(this, fileContent);
- });
- this.$eventHub.$on("addDecksFromJSON", (fileContent: FFCFile) => {
- addDecksFromJSON(this, fileContent);
- });
- this.$eventHub.$on("snackbarEvent", (message: string) => {
- this.showSnackbar(message);
- });
- this.$eventHub.$on("clearLocalStorage", () => {
- clearLocalStorage(this);
- });
- this.$eventHub.$on("showCustomDialog", (options: CustomDialogOptions) => {
- this.showCustomDialog(options);
- });
+ registerEventListenerForMainApp(this);
for (const item of this.propertiesToSyncWithLocalStorage) {
this.$watch(
@@ -125,9 +98,14 @@ export default class App extends AppProps {
mounted() {
readFromLocalStorage(this);
- this.decks.forEach(deck => {
- deck.selected = false;
- });
+ if (
+ !this.learningSession.elements ||
+ this.learningSession.elements.length === 0
+ ) {
+ this.setSelectedStatusForAllDecks(false);
+ } else {
+ this.$router.replace("learn");
+ }
}
get numberOfSelectedDecks() {
@@ -146,14 +124,6 @@ export default class App extends AppProps {
}
this.$refs.navbar.showDrawer();
}
- showSnackbar(text: string, timeout?: { value?: number; factor?: number }) {
- // timeout: {value?: number, factor?: number}
- this.snackbar.text = text;
- this.snackbar.timeout = timeout
- ? timeout.value || (timeout.factor || 1) * DEFAULT_SNACKBAR_TIMEOUT
- : DEFAULT_SNACKBAR_TIMEOUT;
- this.snackbar.snackbar = true;
- }
showCustomDialog(options: CustomDialogOptions) {
this.$refs.customDialog.show(options);
}
diff --git a/src/components/layout/NavigationBar.vue b/src/components/layout/NavigationBar.vue
index 455ed85..5e41348 100644
--- a/src/components/layout/NavigationBar.vue
+++ b/src/components/layout/NavigationBar.vue
@@ -67,16 +67,17 @@
import Vue from "vue";
import Component from "vue-class-component";
-import { Deck } from '../../types';
+import { Deck, Event, NavBarConfigItem } from '../../types';
+import navBarConfigJson from './navbar.json';
import * as selectedDeckDialogHelper from "../../helpers/selectedDeckDialogHelper";
+import * as quitLearningDialogHelper from "../../helpers/quitLearningDialogHelper";
const NavigationBarProps = Vue.extend({
props: {
title: String,
decks: {type: Array as () => Deck[]},
numberOfSelectedDecks: Number,
- navBarList: Array
}
});
@@ -89,6 +90,7 @@ export default class NavigationBar extends NavigationBarProps {
floating: false,
mini: false
};
+ navBarList = navBarConfigJson as NavBarConfigItem[];
get isInDeckSelection() {
return this.$route.name === "DeckSelection";
@@ -112,14 +114,10 @@ export default class NavigationBar extends NavigationBarProps {
}
deselectAll() {
- this.decks.forEach(deck => {
- deck.selected = false;
- });
+ this.$eventHub.$emit(Event.DESELECT_ALL_DECKS);
}
selectAll() {
- this.decks.forEach(deck => {
- deck.selected = true;
- });
+ this.$eventHub.$emit(Event.SELECT_ALL_DECKS);
}
deleteSelected() {
selectedDeckDialogHelper.deleteSelected(this);
@@ -134,7 +132,7 @@ export default class NavigationBar extends NavigationBarProps {
this.primaryDrawer.model = false;
}
quitLearning() {
- selectedDeckDialogHelper.quitLearning(this);
+ quitLearningDialogHelper.quitLearning(this);
}
togglePrimaryDrawer() {
this.primaryDrawer.model = !this.primaryDrawer.model;
diff --git a/src/components/layout/navbar.json b/src/components/layout/navbar.json
new file mode 100644
index 0000000..274c3d0
--- /dev/null
+++ b/src/components/layout/navbar.json
@@ -0,0 +1,22 @@
+[
+ {
+ "to": "/",
+ "icon": "mdi-home",
+ "title": "Home"
+ },
+ {
+ "to": "/add",
+ "icon": "mdi-plus",
+ "title": "Add Deck"
+ },
+ {
+ "to": "/settings",
+ "icon": "mdi-cog",
+ "title": "Settings"
+ },
+ {
+ "to": "/about",
+ "icon": "mdi-information",
+ "title": "About"
+ }
+]
\ No newline at end of file
diff --git a/src/helpers/addDecksHelper.ts b/src/helpers/addDecksHelper.ts
index 71d5cc5..5bd08f0 100644
--- a/src/helpers/addDecksHelper.ts
+++ b/src/helpers/addDecksHelper.ts
@@ -1,8 +1,8 @@
import { FFCFile, Deck, CustomDialogOptions } from '@/types';
import router from '@/router';
+import { showSnackbar } from './snackbarHelper';
interface Context {
- showSnackbar: Function,
decks: Deck[],
showCustomDialog: Function,
$router: typeof router,
@@ -12,7 +12,7 @@ export function addDecksFromFile(context: Context, fileContent: string) {
try {
addDecksFromJSON(context, JSON.parse(fileContent));
} catch (e) {
- context.showSnackbar(e);
+ showSnackbar(context, e);
}
}
@@ -55,7 +55,7 @@ export function addDecksFromJSON(context: Context, fileContent: FFCFile) {
showAddedDecksConfirmation(context, addedDecksAndCards);
} catch (e) {
- context.showSnackbar(e);
+ showSnackbar(context, e);
}
}
diff --git a/src/helpers/cardSelectionHelper.ts b/src/helpers/cardSelectionHelper.ts
new file mode 100644
index 0000000..fc6765e
--- /dev/null
+++ b/src/helpers/cardSelectionHelper.ts
@@ -0,0 +1,49 @@
+import { Deck, LearningSession, LearningSessionElement } from "@/types";
+
+interface CardIgnoreMap {
+ [x: string]: boolean;
+}
+
+export function injectNewLearningElement(
+ decks: Deck[],
+ learningSession: LearningSession
+): boolean {
+ const cardIgnoreMap = getCardIgnoreMap(decks, learningSession);
+
+ const cardsToSelectFrom = [] as LearningSessionElement[];
+ decks.forEach((deck) => {
+ if (deck.selected) {
+ deck.cards.forEach((card) => {
+ if (!(getKeyForCardIgnoreMap(deck.id, card.id) in cardIgnoreMap)) {
+ cardsToSelectFrom.push({ deckId: deck.id, cardId: card.id });
+ }
+ });
+ }
+ });
+
+ if (cardsToSelectFrom.length === 0) {
+ return false;
+ }
+
+ const newLearningElement =
+ cardsToSelectFrom[Math.floor(Math.random() * cardsToSelectFrom.length)];
+ learningSession.elements.push(newLearningElement);
+ return true;
+}
+
+function getCardIgnoreMap(
+ decks: Deck[],
+ learningSession: LearningSession
+): CardIgnoreMap {
+ const cardIgnoreMap = {} as CardIgnoreMap;
+ learningSession.elements.forEach((element) => {
+ cardIgnoreMap[
+ getKeyForCardIgnoreMap(element.deckId, element.cardId)
+ ] = true;
+ });
+ return cardIgnoreMap;
+}
+
+function getKeyForCardIgnoreMap(deckId: number, cardId: number) {
+ return `${deckId}:${cardId}`;
+}
diff --git a/src/helpers/eventListener.ts b/src/helpers/eventListener.ts
new file mode 100644
index 0000000..ef4f72f
--- /dev/null
+++ b/src/helpers/eventListener.ts
@@ -0,0 +1,42 @@
+import {
+ QuitLearningReason,
+ CustomDialogOptions,
+ FFCFile,
+ Event,
+ Deck,
+} from "@/types";
+import { clearLocalStorage } from "./localStorageHelper";
+import { addDecksFromJSON, addDecksFromFile } from "./addDecksHelper";
+import { showSnackbar } from "./snackbarHelper";
+
+export function registerEventListenerForMainApp(context: any) {
+ context.$eventHub.$on("deleteSelectedDecks", () => {
+ context.decks = context.decks.filter((deck: Deck) => !deck.selected);
+ });
+ context.$eventHub.$on(Event.SELECT_ALL_DECKS, () => {
+ context.setSelectedStatusForAllDecks(true);
+ });
+ context.$eventHub.$on(Event.DESELECT_ALL_DECKS, () => {
+ context.setSelectedStatusForAllDecks(false);
+ });
+ context.$eventHub.$on("addDecksFromFile", (fileContent: string) => {
+ addDecksFromFile(context, fileContent);
+ });
+ context.$eventHub.$on("addDecksFromJSON", (fileContent: FFCFile) => {
+ addDecksFromJSON(context, fileContent);
+ });
+ context.$eventHub.$on("snackbarEvent", (message: string) => {
+ showSnackbar(context, message);
+ });
+ context.$eventHub.$on("clearLocalStorage", () => {
+ clearLocalStorage(context);
+ });
+ context.$eventHub.$on("showCustomDialog", (options: CustomDialogOptions) => {
+ context.showCustomDialog(options);
+ });
+ context.$eventHub.$on("quitLearning", (reason: QuitLearningReason) => {
+ if (reason === QuitLearningReason.USER_ACTION) {
+ context.$router.replace("/");
+ }
+ });
+}
diff --git a/src/helpers/localStorageHelper.ts b/src/helpers/localStorageHelper.ts
index f4eafbd..4113516 100644
--- a/src/helpers/localStorageHelper.ts
+++ b/src/helpers/localStorageHelper.ts
@@ -1,3 +1,5 @@
+import { showSnackbar } from './snackbarHelper';
+
const LOCAL_STORAGE_APP_CONTEXT = "ffc_";
export interface SyncItem {
@@ -47,5 +49,5 @@ export function clearLocalStorage(context: Context) {
context.propertiesToSyncWithLocalStorage.forEach((item) => {
context[item.key] = item.defaultValue;
});
- context.showSnackbar("Removed All App Data From Local Storage.");
+ showSnackbar(context, "Removed All App Data From Local Storage.");
}
diff --git a/src/helpers/quitLearningDialogHelper.ts b/src/helpers/quitLearningDialogHelper.ts
new file mode 100644
index 0000000..62f0d8d
--- /dev/null
+++ b/src/helpers/quitLearningDialogHelper.ts
@@ -0,0 +1,23 @@
+import { Event, QuitLearningReason } from "../types";
+
+export function quitLearning(context: any) {
+ context.$eventHub.$emit("showCustomDialog", {
+ title: "Quit Learning?",
+ message:
+ "Do you really want to quit learning? Nevertheless, your progress is saved.",
+ buttons: [
+ {
+ name: "Cancel",
+ color: "grey"
+ },
+ {
+ name: "Quit",
+ color: "orange darken-1",
+ callback: () => {
+ context.$eventHub.$emit(Event.QUIT_LEARNING, QuitLearningReason.USER_ACTION);
+ }
+ }
+ ]
+ });
+ }
+
\ No newline at end of file
diff --git a/src/helpers/selectedDeckDialogHelper.ts b/src/helpers/selectedDeckDialogHelper.ts
index a5dccab..d151471 100644
--- a/src/helpers/selectedDeckDialogHelper.ts
+++ b/src/helpers/selectedDeckDialogHelper.ts
@@ -89,26 +89,3 @@ export function showInfoForSelectedDeck(context: any) {
});
context.$eventHub.$emit("showCustomDialog", options);
}
-
-
-export function quitLearning(context: any) {
- context.$eventHub.$emit("showCustomDialog", {
- title: "Quit Learning?",
- message:
- "Do you really want to quit learning? Nevertheless, your progress is saved.",
- buttons: [
- {
- name: "Cancel",
- color: "grey"
- },
- {
- name: "Quit",
- color: "orange darken-1",
- callback: () => {
- context.deselectAll();
- context.$router.replace("/");
- }
- }
- ]
- });
-}
diff --git a/src/helpers/snackbarHelper.ts b/src/helpers/snackbarHelper.ts
new file mode 100644
index 0000000..5460c2f
--- /dev/null
+++ b/src/helpers/snackbarHelper.ts
@@ -0,0 +1,13 @@
+const DEFAULT_SNACKBAR_TIMEOUT = 2000;
+
+export function showSnackbar(
+ context: any,
+ text: string,
+ timeout?: { value?: number; factor?: number }
+) {
+ context.snackbar.text = text;
+ context.snackbar.timeout = timeout
+ ? timeout.value || (timeout.factor || 1) * DEFAULT_SNACKBAR_TIMEOUT
+ : DEFAULT_SNACKBAR_TIMEOUT;
+ context.snackbar.snackbar = true;
+}
diff --git a/src/types/index.ts b/src/types/index.ts
index ef2365f..2ca9e05 100644
--- a/src/types/index.ts
+++ b/src/types/index.ts
@@ -40,14 +40,26 @@ export interface Card {
id: number;
q: string;
a: string;
- r?: Rating[];
+ r?: CardRating[];
}
-export interface Rating {
+export interface CardRating {
t: number;
r: number;
}
+export interface LearningSession {
+ elements: LearningSessionElement[];
+ currentElementIndex: number;
+}
+
+export interface LearningSessionElement {
+ deckId: number;
+ cardId: number;
+ showAnswer?: boolean;
+ rating?: number;
+}
+
export interface CustomDialogOptions {
title: string;
format?: string;
@@ -70,3 +82,26 @@ export interface CustomDialogOptionsButton {
color: string;
callback?: Function;
}
+
+export enum QuitLearningReason {
+ USER_ACTION = 1,
+ NO_MORE_CARDS = 2,
+}
+
+export enum Event {
+ DELETE_SELECTED_DECKS = "deleteSelectedDecks",
+ ADD_DECKS_FROM_FILE = "addDecksFromFile",
+ ADD_DECKS_FROM_JSON = "addDecksFromJSON",
+ SNACKBAR_EVENT = "snackbarEvent",
+ CLEAR_LOCAL_STORAGE = "clearLocalStorage",
+ SHOW_CUSTOM_DIALOG = "showCustomDialog",
+ QUIT_LEARNING = "quitLearning",
+ SELECT_ALL_DECKS = "selectAllDecks",
+ DESELECT_ALL_DECKS = "deselectAllDecks",
+}
+
+export interface NavBarConfigItem {
+ to: string;
+ icon: string;
+ title: string;
+}
diff --git a/src/views/Learn.vue b/src/views/Learn.vue
index 79cfb51..79d1653 100644
--- a/src/views/Learn.vue
+++ b/src/views/Learn.vue
@@ -3,6 +3,7 @@
@@ -14,6 +15,7 @@ export default {
name: "Learn",
props: {
decks: Array,
+ learningSession: Object,
numberOfSelectedDecks: Number,
},
components: {
diff --git a/tsconfig.json b/tsconfig.json
index b8b190a..9f04721 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -10,6 +10,7 @@
"jsx": "preserve",
"importHelpers": true,
"moduleResolution": "node",
+ "resolveJsonModule": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"sourceMap": true,