refactor code

Co-authored-by: Niko Lockenvitz <nl@nikolockenvitz.de>
This commit is contained in:
Rene Fischer 2020-06-26 20:09:17 +02:00
parent f2f3c2bbfd
commit 066e9d3c38
No known key found for this signature in database
GPG key ID: 5D8E12AC54D3C1B5
13 changed files with 240 additions and 106 deletions

View file

@ -3,23 +3,26 @@
<v-window :touch="{ left: swipeLeft, right: swipeRight }"> <v-window :touch="{ left: swipeLeft, right: swipeRight }">
<v-app id="inspire"> <v-app id="inspire">
<v-app id="sandbox"> <v-app id="sandbox">
<v-content> <v-main>
<NavigationBar <NavigationBar
ref="navbar" ref="navbar"
title="Fancy Flashcard" title="Fancy Flashcard"
v-bind:decks="decks" :decks="decks"
v-bind:numberOfSelectedDecks="numberOfSelectedDecks" :numberOfSelectedDecks="numberOfSelectedDecks"
v-bind:navBarList="navBarList"
></NavigationBar> ></NavigationBar>
<router-view v-bind:decks="decks" v-bind:numberOfSelectedDecks="numberOfSelectedDecks" /> <router-view
:decks="decks"
:numberOfSelectedDecks="numberOfSelectedDecks"
:learningSession="learningSession"
/>
<v-snackbar app v-model="snackbar.snackbar" :timeout="snackbar.timeout"> <v-snackbar app v-model="snackbar.snackbar" :timeout="snackbar.timeout">
{{ snackbar.text }} {{ snackbar.text }}
<template> <template v-slot:action="{ attrs }">
<v-btn color="orange darken-1" text @click="snackbar.snackbar = false">Close</v-btn> <v-btn color="orange darken-1" text v-bind="attrs" @click="snackbar.snackbar = false">Close</v-btn>
</template> </template>
</v-snackbar> </v-snackbar>
<CustomDialog ref="customDialog" /> <CustomDialog ref="customDialog" />
</v-content> </v-main>
</v-app> </v-app>
</v-app> </v-app>
</v-window> </v-window>
@ -30,19 +33,16 @@
import Vue from "vue"; import Vue from "vue";
import Component from "vue-class-component"; 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 NavigationBar from "./components/layout/NavigationBar.vue";
import CustomDialog from "./components/customdialog/CustomDialog.vue"; import CustomDialog from "./components/customdialog/CustomDialog.vue";
import { registerEventListenerForMainApp } from "./helpers/eventListener";
import { import {
readFromLocalStorage, readFromLocalStorage,
saveToLocalStorage, saveToLocalStorage,
clearLocalStorage, SyncItem
SyncItem,
} from "./helpers/localStorageHelper"; } from "./helpers/localStorageHelper";
import { addDecksFromFile, addDecksFromJSON } from "./helpers/addDecksHelper";
const DEFAULT_SNACKBAR_TIMEOUT = 2000;
const AppProps = Vue.extend({ const AppProps = Vue.extend({
props: { props: {
@ -57,34 +57,18 @@ const AppProps = Vue.extend({
} }
}) })
export default class App extends AppProps { export default class App extends AppProps {
propertiesToSyncWithLocalStorage = [{ key: "decks", defaultValue: [] }] as SyncItem[]; propertiesToSyncWithLocalStorage = [
{ key: "decks", defaultValue: [] },
{ key: "lerningSession", defaultValue: {} }
] as SyncItem[];
decks = [] as Deck[]; decks = [] as Deck[];
navBarList = [ learningSession = {
{ elements: [{ deckId: 1, cardId: 1, showAnswer: false, rating: 100 }],
to: "/", currentElementIndex: -1
icon: "mdi-home", } as LearningSession;
title: "Home"
},
{
to: "/add",
icon: "mdi-plus",
title: "Add Deck"
},
{
to: "/settings",
icon: "mdi-cog",
title: "Settings"
},
{
to: "/about",
icon: "mdi-information",
title: "About"
}
];
snackbar = { snackbar = {
text: "", text: "",
snackbar: false, snackbar: false
timeout: DEFAULT_SNACKBAR_TIMEOUT
}; };
$refs!: { $refs!: {
@ -92,25 +76,14 @@ export default class App extends AppProps {
customDialog: CustomDialog; customDialog: CustomDialog;
}; };
setSelectedStatusForAllDecks(status: boolean) {
this.decks.forEach(deck => {
deck.selected = status;
});
}
created() { created() {
this.$eventHub.$on("deleteSelectedDecks", () => { registerEventListenerForMainApp(this);
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);
});
for (const item of this.propertiesToSyncWithLocalStorage) { for (const item of this.propertiesToSyncWithLocalStorage) {
this.$watch( this.$watch(
@ -125,9 +98,14 @@ export default class App extends AppProps {
mounted() { mounted() {
readFromLocalStorage(this); readFromLocalStorage(this);
this.decks.forEach(deck => { if (
deck.selected = false; !this.learningSession.elements ||
}); this.learningSession.elements.length === 0
) {
this.setSelectedStatusForAllDecks(false);
} else {
this.$router.replace("learn");
}
} }
get numberOfSelectedDecks() { get numberOfSelectedDecks() {
@ -146,14 +124,6 @@ export default class App extends AppProps {
} }
this.$refs.navbar.showDrawer(); 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) { showCustomDialog(options: CustomDialogOptions) {
this.$refs.customDialog.show(options); this.$refs.customDialog.show(options);
} }

View file

@ -67,16 +67,17 @@
import Vue from "vue"; import Vue from "vue";
import Component from "vue-class-component"; 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 selectedDeckDialogHelper from "../../helpers/selectedDeckDialogHelper";
import * as quitLearningDialogHelper from "../../helpers/quitLearningDialogHelper";
const NavigationBarProps = Vue.extend({ const NavigationBarProps = Vue.extend({
props: { props: {
title: String, title: String,
decks: {type: Array as () => Deck[]}, decks: {type: Array as () => Deck[]},
numberOfSelectedDecks: Number, numberOfSelectedDecks: Number,
navBarList: Array
} }
}); });
@ -89,6 +90,7 @@ export default class NavigationBar extends NavigationBarProps {
floating: false, floating: false,
mini: false mini: false
}; };
navBarList = navBarConfigJson as NavBarConfigItem[];
get isInDeckSelection() { get isInDeckSelection() {
return this.$route.name === "DeckSelection"; return this.$route.name === "DeckSelection";
@ -112,14 +114,10 @@ export default class NavigationBar extends NavigationBarProps {
} }
deselectAll() { deselectAll() {
this.decks.forEach(deck => { this.$eventHub.$emit(Event.DESELECT_ALL_DECKS);
deck.selected = false;
});
} }
selectAll() { selectAll() {
this.decks.forEach(deck => { this.$eventHub.$emit(Event.SELECT_ALL_DECKS);
deck.selected = true;
});
} }
deleteSelected() { deleteSelected() {
selectedDeckDialogHelper.deleteSelected(this); selectedDeckDialogHelper.deleteSelected(this);
@ -134,7 +132,7 @@ export default class NavigationBar extends NavigationBarProps {
this.primaryDrawer.model = false; this.primaryDrawer.model = false;
} }
quitLearning() { quitLearning() {
selectedDeckDialogHelper.quitLearning(this); quitLearningDialogHelper.quitLearning(this);
} }
togglePrimaryDrawer() { togglePrimaryDrawer() {
this.primaryDrawer.model = !this.primaryDrawer.model; this.primaryDrawer.model = !this.primaryDrawer.model;

View file

@ -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"
}
]

View file

@ -1,8 +1,8 @@
import { FFCFile, Deck, CustomDialogOptions } from '@/types'; import { FFCFile, Deck, CustomDialogOptions } from '@/types';
import router from '@/router'; import router from '@/router';
import { showSnackbar } from './snackbarHelper';
interface Context { interface Context {
showSnackbar: Function,
decks: Deck[], decks: Deck[],
showCustomDialog: Function, showCustomDialog: Function,
$router: typeof router, $router: typeof router,
@ -12,7 +12,7 @@ export function addDecksFromFile(context: Context, fileContent: string) {
try { try {
addDecksFromJSON(context, JSON.parse(fileContent)); addDecksFromJSON(context, JSON.parse(fileContent));
} catch (e) { } catch (e) {
context.showSnackbar(e); showSnackbar(context, e);
} }
} }
@ -55,7 +55,7 @@ export function addDecksFromJSON(context: Context, fileContent: FFCFile) {
showAddedDecksConfirmation(context, addedDecksAndCards); showAddedDecksConfirmation(context, addedDecksAndCards);
} catch (e) { } catch (e) {
context.showSnackbar(e); showSnackbar(context, e);
} }
} }

View file

@ -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}`;
}

View file

@ -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("/");
}
});
}

View file

@ -1,3 +1,5 @@
import { showSnackbar } from './snackbarHelper';
const LOCAL_STORAGE_APP_CONTEXT = "ffc_"; const LOCAL_STORAGE_APP_CONTEXT = "ffc_";
export interface SyncItem { export interface SyncItem {
@ -47,5 +49,5 @@ export function clearLocalStorage(context: Context) {
context.propertiesToSyncWithLocalStorage.forEach((item) => { context.propertiesToSyncWithLocalStorage.forEach((item) => {
context[item.key] = item.defaultValue; context[item.key] = item.defaultValue;
}); });
context.showSnackbar("Removed All App Data From Local Storage."); showSnackbar(context, "Removed All App Data From Local Storage.");
} }

View file

@ -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);
}
}
]
});
}

View file

@ -89,26 +89,3 @@ export function showInfoForSelectedDeck(context: any) {
}); });
context.$eventHub.$emit("showCustomDialog", options); 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("/");
}
}
]
});
}

View file

@ -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;
}

View file

@ -40,14 +40,26 @@ export interface Card {
id: number; id: number;
q: string; q: string;
a: string; a: string;
r?: Rating[]; r?: CardRating[];
} }
export interface Rating { export interface CardRating {
t: number; t: number;
r: number; r: number;
} }
export interface LearningSession {
elements: LearningSessionElement[];
currentElementIndex: number;
}
export interface LearningSessionElement {
deckId: number;
cardId: number;
showAnswer?: boolean;
rating?: number;
}
export interface CustomDialogOptions { export interface CustomDialogOptions {
title: string; title: string;
format?: string; format?: string;
@ -70,3 +82,26 @@ export interface CustomDialogOptionsButton {
color: string; color: string;
callback?: Function; 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;
}

View file

@ -3,6 +3,7 @@
<LearnComponent <LearnComponent
:decks="decks" :decks="decks"
:numberOfSelectedDecks="numberOfSelectedDecks" :numberOfSelectedDecks="numberOfSelectedDecks"
:learningSession="learningSession"
/> />
</div> </div>
</template> </template>
@ -14,6 +15,7 @@ export default {
name: "Learn", name: "Learn",
props: { props: {
decks: Array, decks: Array,
learningSession: Object,
numberOfSelectedDecks: Number, numberOfSelectedDecks: Number,
}, },
components: { components: {

View file

@ -10,6 +10,7 @@
"jsx": "preserve", "jsx": "preserve",
"importHelpers": true, "importHelpers": true,
"moduleResolution": "node", "moduleResolution": "node",
"resolveJsonModule": true,
"esModuleInterop": true, "esModuleInterop": true,
"allowSyntheticDefaultImports": true, "allowSyntheticDefaultImports": true,
"sourceMap": true, "sourceMap": true,