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-app id="inspire">
<v-app id="sandbox">
<v-content>
<v-main>
<NavigationBar
ref="navbar"
title="Fancy Flashcard"
v-bind:decks="decks"
v-bind:numberOfSelectedDecks="numberOfSelectedDecks"
v-bind:navBarList="navBarList"
:decks="decks"
:numberOfSelectedDecks="numberOfSelectedDecks"
></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">
{{ snackbar.text }}
<template>
<v-btn color="orange darken-1" text @click="snackbar.snackbar = false">Close</v-btn>
<template v-slot:action="{ attrs }">
<v-btn color="orange darken-1" text v-bind="attrs" @click="snackbar.snackbar = false">Close</v-btn>
</template>
</v-snackbar>
<CustomDialog ref="customDialog" />
</v-content>
</v-main>
</v-app>
</v-app>
</v-window>
@ -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);
}

View file

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

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

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_";
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.");
}

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

View file

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

View file

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