refactor code
Co-authored-by: Niko Lockenvitz <nl@nikolockenvitz.de>
This commit is contained in:
parent
f2f3c2bbfd
commit
066e9d3c38
13 changed files with 240 additions and 106 deletions
106
src/App.vue
106
src/App.vue
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
22
src/components/layout/navbar.json
Normal file
22
src/components/layout/navbar.json
Normal 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"
|
||||
}
|
||||
]
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
49
src/helpers/cardSelectionHelper.ts
Normal file
49
src/helpers/cardSelectionHelper.ts
Normal 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}`;
|
||||
}
|
42
src/helpers/eventListener.ts
Normal file
42
src/helpers/eventListener.ts
Normal 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("/");
|
||||
}
|
||||
});
|
||||
}
|
|
@ -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.");
|
||||
}
|
||||
|
|
23
src/helpers/quitLearningDialogHelper.ts
Normal file
23
src/helpers/quitLearningDialogHelper.ts
Normal 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);
|
||||
}
|
||||
}
|
||||
]
|
||||
});
|
||||
}
|
||||
|
|
@ -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("/");
|
||||
}
|
||||
}
|
||||
]
|
||||
});
|
||||
}
|
||||
|
|
13
src/helpers/snackbarHelper.ts
Normal file
13
src/helpers/snackbarHelper.ts
Normal 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;
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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: {
|
||||
|
|
|
@ -10,6 +10,7 @@
|
|||
"jsx": "preserve",
|
||||
"importHelpers": true,
|
||||
"moduleResolution": "node",
|
||||
"resolveJsonModule": true,
|
||||
"esModuleInterop": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"sourceMap": true,
|
||||
|
|
Loading…
Reference in a new issue