Merge pull request #7 from fancy-flashcard/dialog-refactor
Dialog refactor
3
.browserslistrc
Normal file
|
@ -0,0 +1,3 @@
|
|||
> 1%
|
||||
last 2 versions
|
||||
not dead
|
25
.eslintrc.js
Normal file
|
@ -0,0 +1,25 @@
|
|||
module.exports = {
|
||||
root: true,
|
||||
env: {
|
||||
node: true,
|
||||
},
|
||||
extends: [
|
||||
"plugin:vue/essential",
|
||||
"eslint:recommended",
|
||||
"@vue/typescript/recommended",
|
||||
"@vue/prettier",
|
||||
"@vue/prettier/@typescript-eslint",
|
||||
],
|
||||
parserOptions: {
|
||||
ecmaVersion: 2020,
|
||||
},
|
||||
rules: {
|
||||
"no-console": process.env.NODE_ENV === "production" ? "warn" : "off",
|
||||
"no-debugger": process.env.NODE_ENV === "production" ? "warn" : "off",
|
||||
"@typescript-eslint/no-use-before-define": ["error", { functions: false }],
|
||||
"@typescript-eslint/camelcase": "off",
|
||||
"@typescript-eslint/class-name-casing": "off",
|
||||
"@typescript-eslint/no-explicit-any": "off",
|
||||
"prettier/prettier": 0,
|
||||
},
|
||||
};
|
1
.gitignore
vendored
|
@ -10,6 +10,7 @@ node_modules
|
|||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
|
||||
# Editor directories and files
|
||||
.idea
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
module.exports = {
|
||||
presets: [
|
||||
'@vue/cli-plugin-babel/preset'
|
||||
]
|
||||
}
|
||||
presets: ["@vue/cli-plugin-babel/preset"]
|
||||
};
|
||||
|
|
7103
package-lock.json
generated
49
package.json
|
@ -1,6 +1,8 @@
|
|||
{
|
||||
"name": "ffc",
|
||||
"version": "0.1.0",
|
||||
"author": "Niko Lockenvitz & Rene-Pascal Fischer",
|
||||
"license": "MIT",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"serve": "vue-cli-service serve",
|
||||
|
@ -9,44 +11,33 @@
|
|||
"start": "vue-cli-service serve"
|
||||
},
|
||||
"dependencies": {
|
||||
"core-js": "^3.6.4",
|
||||
"core-js": "^3.6.5",
|
||||
"register-service-worker": "^1.7.1",
|
||||
"vue": "^2.6.11",
|
||||
"vue-router": "^3.1.6",
|
||||
"vue-class-component": "^7.2.3",
|
||||
"vue-router": "^3.2.0",
|
||||
"vuetify": "^2.2.11"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@vue/cli-plugin-babel": "~4.3.0",
|
||||
"@vue/cli-plugin-eslint": "~4.3.0",
|
||||
"@vue/cli-plugin-pwa": "^4.4.1",
|
||||
"@vue/cli-plugin-router": "^4.3.1",
|
||||
"@vue/cli-service": "~4.3.0",
|
||||
"babel-eslint": "^10.1.0",
|
||||
"@typescript-eslint/eslint-plugin": "^2.33.0",
|
||||
"@typescript-eslint/parser": "^2.33.0",
|
||||
"@vue/cli-plugin-babel": "^4.4.0",
|
||||
"@vue/cli-plugin-eslint": "^4.4.0",
|
||||
"@vue/cli-plugin-pwa": "^4.4.0",
|
||||
"@vue/cli-plugin-router": "^4.4.0",
|
||||
"@vue/cli-plugin-typescript": "^4.4.0",
|
||||
"@vue/cli-service": "^4.4.0",
|
||||
"@vue/eslint-config-prettier": "^6.0.0",
|
||||
"@vue/eslint-config-typescript": "^5.0.2",
|
||||
"eslint": "^6.7.2",
|
||||
"eslint-plugin-prettier": "^3.1.3",
|
||||
"eslint-plugin-vue": "^6.2.2",
|
||||
"prettier": "^1.19.1",
|
||||
"sass": "^1.19.0",
|
||||
"sass-loader": "^8.0.0",
|
||||
"vue-cli-plugin-vuetify": "~2.0.5",
|
||||
"typescript": "~3.9.3",
|
||||
"vue-cli-plugin-vuetify": "^2.0.6",
|
||||
"vue-template-compiler": "^2.6.11",
|
||||
"vuetify-loader": "^1.3.0"
|
||||
},
|
||||
"eslintConfig": {
|
||||
"root": true,
|
||||
"env": {
|
||||
"node": true
|
||||
},
|
||||
"extends": [
|
||||
"plugin:vue/essential",
|
||||
"eslint:recommended"
|
||||
],
|
||||
"parserOptions": {
|
||||
"parser": "babel-eslint"
|
||||
},
|
||||
"rules": {}
|
||||
},
|
||||
"browserslist": [
|
||||
"> 1%",
|
||||
"last 2 versions",
|
||||
"not dead"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
Before Width: | Height: | Size: 4.2 KiB After Width: | Height: | Size: 4.2 KiB |
5
public/img/flash.svg
Normal file
|
@ -0,0 +1,5 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 100 100">
|
||||
<polygon points="26,44 60,15 52,43 74,56 40,85 48,57"
|
||||
style="fill:#fc0;stroke:#fc0;stroke-width:5;stroke-linejoin:round;" />
|
||||
</svg>
|
After Width: | Height: | Size: 204 B |
6
public/img/flashAppIcon.svg
Normal file
|
@ -0,0 +1,6 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 100 100">
|
||||
<rect x="5" y="5" width="90" height="90" rx="5" fill="#222" />
|
||||
<polygon points="26,44 60,15 52,43 74,56 40,85 48,57"
|
||||
style="fill:#fc0;stroke:#fc0;stroke-width:5;stroke-linejoin:round;" />
|
||||
</svg>
|
After Width: | Height: | Size: 269 B |
Before Width: | Height: | Size: 9.2 KiB After Width: | Height: | Size: 3.5 KiB |
Before Width: | Height: | Size: 29 KiB After Width: | Height: | Size: 9.8 KiB |
Before Width: | Height: | Size: 6.3 KiB After Width: | Height: | Size: 3.1 KiB |
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 8.4 KiB |
Before Width: | Height: | Size: 3.3 KiB After Width: | Height: | Size: 5.7 KiB |
Before Width: | Height: | Size: 4 KiB After Width: | Height: | Size: 5.9 KiB |
Before Width: | Height: | Size: 4.6 KiB After Width: | Height: | Size: 6.3 KiB |
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 5 KiB |
Before Width: | Height: | Size: 1.8 KiB After Width: | Height: | Size: 5 KiB |
Before Width: | Height: | Size: 4.6 KiB After Width: | Height: | Size: 6.3 KiB |
Before Width: | Height: | Size: 799 B After Width: | Height: | Size: 831 B |
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 1.1 KiB |
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 2.7 KiB |
Before Width: | Height: | Size: 4.2 KiB After Width: | Height: | Size: 3.4 KiB |
|
@ -1,3 +1,4 @@
|
|||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M8.00251 14.9297L0 1.07422H6.14651L8.00251 4.27503L9.84583 1.07422H16L8.00251 14.9297Z" fill="black"/>
|
||||
<polygon points="4,7 9.67,2.17 8.33,6.83 12,9 6.33,13.83 7.67,9.17"
|
||||
style="fill:black;stroke:black;stroke-width:1;stroke-linejoin:round;" />
|
||||
</svg>
|
||||
|
|
Before Width: | Height: | Size: 215 B After Width: | Height: | Size: 251 B |
|
@ -1,19 +1,23 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1.0">
|
||||
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
|
||||
<title><%= htmlWebpackPlugin.options.title %></title>
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:100,300,400,500,700,900">
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@mdi/font@latest/css/materialdesignicons.min.css">
|
||||
</head>
|
||||
<body>
|
||||
<noscript>
|
||||
<strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
|
||||
</noscript>
|
||||
<div id="app"></div>
|
||||
<!-- built files will be auto injected -->
|
||||
</body>
|
||||
</html>
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
|
||||
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
|
||||
<title><%= htmlWebpackPlugin.options.title %></title>
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:100,300,400,500,700,900">
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@mdi/font@latest/css/materialdesignicons.min.css">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<noscript>
|
||||
<strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled.
|
||||
Please enable it to continue.</strong>
|
||||
</noscript>
|
||||
<div id="app"></div>
|
||||
<!-- built files will be auto injected -->
|
||||
</body>
|
||||
|
||||
</html>
|
194
src/App.vue
|
@ -15,7 +15,7 @@
|
|||
<v-snackbar app v-model="snackbar.snackbar" :timeout="snackbar.timeout">
|
||||
{{ snackbar.text }}
|
||||
<template>
|
||||
<v-btn color="indigo" text @click="snackbar.snackbar = false">Close</v-btn>
|
||||
<v-btn color="orange darken-1" text @click="snackbar.snackbar = false">Close</v-btn>
|
||||
</template>
|
||||
</v-snackbar>
|
||||
<CustomDialog ref="customDialog" />
|
||||
|
@ -26,44 +26,91 @@
|
|||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
<script lang="ts">
|
||||
import Vue from "vue";
|
||||
import Component from "vue-class-component";
|
||||
|
||||
import { Deck, CustomDialogOptions, FFCFile } from "./types";
|
||||
|
||||
import NavigationBar from "./components/layout/NavigationBar.vue";
|
||||
import CustomDialog from "./components/customdialog/CustomDialog.vue";
|
||||
import {
|
||||
readFromLocalStorage,
|
||||
saveToLocalStorage,
|
||||
clearLocalStorage
|
||||
clearLocalStorage,
|
||||
SyncItem,
|
||||
} from "./helpers/localStorageHelper";
|
||||
import { addDecksFromFile, addDecksFromJSON } from "./helpers/addDecksHelper";
|
||||
|
||||
const DEFAULT_SNACKBAR_TIMEOUT = 2000;
|
||||
|
||||
export default {
|
||||
const AppProps = Vue.extend({
|
||||
props: {
|
||||
title: String
|
||||
},
|
||||
}
|
||||
});
|
||||
|
||||
@Component({
|
||||
components: {
|
||||
NavigationBar,
|
||||
CustomDialog
|
||||
},
|
||||
}
|
||||
})
|
||||
export default class App extends AppProps {
|
||||
propertiesToSyncWithLocalStorage = [{ key: "decks", 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"
|
||||
}
|
||||
];
|
||||
snackbar = {
|
||||
text: "",
|
||||
snackbar: false,
|
||||
timeout: DEFAULT_SNACKBAR_TIMEOUT
|
||||
};
|
||||
|
||||
$refs!: {
|
||||
navbar: NavigationBar;
|
||||
customDialog: CustomDialog;
|
||||
};
|
||||
|
||||
created() {
|
||||
this.$eventHub.$on("deleteDecks", decksToBeDeleted => {
|
||||
this.decks = this.decks.filter(
|
||||
deck => !decksToBeDeleted.includes(deck.id)
|
||||
);
|
||||
this.$eventHub.$on("deleteSelectedDecks", () => {
|
||||
this.decks = this.decks.filter(deck => !deck.selected);
|
||||
});
|
||||
this.$eventHub.$on("addDecksFromFile", fileContent => {
|
||||
this.$eventHub.$on("addDecksFromFile", (fileContent: string) => {
|
||||
addDecksFromFile(this, fileContent);
|
||||
});
|
||||
this.$eventHub.$on("addDecksFromJSON", fileContent => {
|
||||
this.$eventHub.$on("addDecksFromJSON", (fileContent: FFCFile) => {
|
||||
addDecksFromJSON(this, fileContent);
|
||||
});
|
||||
this.$eventHub.$on("snackbarEvent", output => {
|
||||
this.showSnackbar(output);
|
||||
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) {
|
||||
this.$watch(
|
||||
|
@ -74,74 +121,43 @@ export default {
|
|||
{ deep: true }
|
||||
);
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
propertiesToSyncWithLocalStorage: [{ key: "decks", defaultValue: [] }],
|
||||
decks: [],
|
||||
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"
|
||||
}
|
||||
],
|
||||
snackbar: {
|
||||
text: "",
|
||||
snackbar: false,
|
||||
timeout: DEFAULT_SNACKBAR_TIMEOUT
|
||||
}
|
||||
};
|
||||
},
|
||||
}
|
||||
|
||||
mounted() {
|
||||
readFromLocalStorage(this);
|
||||
},
|
||||
computed: {
|
||||
numberOfSelectedDecks() {
|
||||
return this.decks.filter(deck => deck.selected).length;
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
swipeLeft() {
|
||||
if (this.$route.name === "Learn") {
|
||||
return;
|
||||
}
|
||||
this.$refs.navbar.hideDrawer();
|
||||
},
|
||||
swipeRight() {
|
||||
if (this.$route.name === "Learn") {
|
||||
return;
|
||||
}
|
||||
this.$refs.navbar.showDrawer();
|
||||
},
|
||||
showSnackbar(text, timeout) {
|
||||
// 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) {
|
||||
this.$refs.customDialog.show(options);
|
||||
},
|
||||
this.decks.forEach(deck => {
|
||||
deck.selected = false;
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
get numberOfSelectedDecks() {
|
||||
return this.decks.filter(deck => deck.selected).length;
|
||||
}
|
||||
|
||||
swipeLeft() {
|
||||
if (this.$route.name === "Learn") {
|
||||
return;
|
||||
}
|
||||
this.$refs.navbar.hideDrawer();
|
||||
}
|
||||
swipeRight() {
|
||||
if (this.$route.name === "Learn") {
|
||||
return;
|
||||
}
|
||||
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);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
|
@ -151,13 +167,29 @@ body {
|
|||
background-color: #000;
|
||||
/* remove scrollbar on desktop when not needed */
|
||||
overflow-y: auto !important;
|
||||
overscroll-behavior: none;
|
||||
}
|
||||
|
||||
/* reduce margin of file input */
|
||||
.v-card__title {
|
||||
word-break: normal;
|
||||
}
|
||||
|
||||
/* reduce margin of file/url deck input */
|
||||
.deck-input .v-input__control .v-input__slot {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
.deck-input .v-input__control .v-text-field__details {
|
||||
display: none;
|
||||
}
|
||||
</style>
|
||||
|
||||
/* fix highlighting problems in vuetify */
|
||||
.theme--dark.v-list-item:hover::before,
|
||||
.theme--dark.v-btn:hover::before,
|
||||
.v-btn:not(.v-btn--text):not(.v-btn--outlined):hover:before {
|
||||
opacity: 0;
|
||||
}
|
||||
.theme--dark.v-list-item--active:hover::before,
|
||||
.theme--dark.v-list-item--active::before {
|
||||
opacity: 0.24;
|
||||
}
|
||||
</style>
|
1
src/assets/logo.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 87.5 100"><defs><style>.cls-1{fill:#1697f6;}.cls-2{fill:#7bc6ff;}.cls-3{fill:#1867c0;}.cls-4{fill:#aeddff;}</style></defs><title>Artboard 46</title><polyline class="cls-1" points="43.75 0 23.31 0 43.75 48.32"/><polygon class="cls-2" points="43.75 62.5 43.75 100 0 14.58 22.92 14.58 43.75 62.5"/><polyline class="cls-3" points="43.75 0 64.19 0 43.75 48.32"/><polygon class="cls-4" points="64.58 14.58 87.5 14.58 43.75 100 43.75 62.5 64.58 14.58"/></svg>
|
After Width: | Height: | Size: 539 B |
|
@ -27,7 +27,7 @@ export default {
|
|||
name: "ImportDeckFromURL",
|
||||
data() {
|
||||
return {
|
||||
chosenURL: null,
|
||||
chosenURL: "https://raw.githubusercontent.com/fancy-flashcard/ffc/master/cli/test.json",
|
||||
fileContent: "",
|
||||
urlRules: [
|
||||
value =>
|
||||
|
@ -43,7 +43,7 @@ export default {
|
|||
const fileContent = await response.json();
|
||||
this.$eventHub.$emit("addDecksFromJSON", fileContent);
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
// console.log(error);
|
||||
// TODO: cors?!
|
||||
this.$eventHub.$emit("snackbarEvent", "An Error Occurred While Loading The File");
|
||||
}
|
||||
|
|
|
@ -3,46 +3,115 @@
|
|||
<v-card color="#2e2e2e">
|
||||
<v-card-title class="headline">{{ options.title }}</v-card-title>
|
||||
|
||||
<v-card-text class="text-left">{{ options.message }}</v-card-text>
|
||||
<v-card-text v-if="options.message" class="text-left">{{ options.message }}</v-card-text>
|
||||
|
||||
<v-list v-if="options.table">
|
||||
<v-list-item v-if="options.tableHead">
|
||||
<v-list-item-content class="font-weight-bold">{{ options.tableHead.name }}</v-list-item-content>
|
||||
<v-list-item-content class="font-weight-bold">{{ options.tableHead.value }}</v-list-item-content>
|
||||
</v-list-item>
|
||||
<v-list-item v-for="item in options.table" :key="item.name">
|
||||
<v-list-item-content>{{ item.name }}</v-list-item-content>
|
||||
<v-list-item-content>{{ item.value }}</v-list-item-content>
|
||||
</v-list-item>
|
||||
</v-list>
|
||||
|
||||
<div v-if="options.url" class="share-ffc-url">
|
||||
<v-text-field type="text" id="ffc-url" :value="options.url"></v-text-field>
|
||||
</div>
|
||||
|
||||
<v-card-actions>
|
||||
<v-spacer></v-spacer>
|
||||
<v-btn color="grey" text @click="close()">Close</v-btn>
|
||||
<v-btn
|
||||
v-for="btn in options.buttons"
|
||||
:key="btn.name"
|
||||
:color="btn.color"
|
||||
text
|
||||
@click="close(btn)"
|
||||
>{{ btn.name }}</v-btn>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: "Dialog",
|
||||
props: {},
|
||||
data() {
|
||||
return {
|
||||
showDialog: false,
|
||||
options: {
|
||||
title: "",
|
||||
message: "",
|
||||
redirectRouteAfterClose: "/"
|
||||
}
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
hide() {
|
||||
this.showDialog = false;
|
||||
<script lang="ts">
|
||||
import Vue from "vue";
|
||||
import Component from "vue-class-component";
|
||||
import { CustomDialogOptions, CustomDialogOptionsButton } from "../../types";
|
||||
|
||||
@Component
|
||||
export default class Dialog extends Vue {
|
||||
showDialog = false;
|
||||
options = {
|
||||
title: "",
|
||||
format: "",
|
||||
message: "",
|
||||
tableHead: {
|
||||
name: "",
|
||||
value: ""
|
||||
},
|
||||
show(options) {
|
||||
this.showDialog = true;
|
||||
this.options = options;
|
||||
},
|
||||
close() {
|
||||
this.showDialog = false;
|
||||
if (this.options.redirectRouteAfterClose) {
|
||||
this.$router.push(this.options.redirectRouteAfterClose);
|
||||
table: [
|
||||
{
|
||||
name: "",
|
||||
value: ""
|
||||
}
|
||||
],
|
||||
buttons: [
|
||||
{
|
||||
name: "Close",
|
||||
color: "indigo",
|
||||
callback: undefined,
|
||||
}
|
||||
]
|
||||
} as CustomDialogOptions;
|
||||
|
||||
hide() {
|
||||
this.showDialog = false;
|
||||
}
|
||||
show(options: CustomDialogOptions) {
|
||||
this.showDialog = true;
|
||||
this.options = options;
|
||||
if (
|
||||
!this.options.buttons ||
|
||||
(this.options.buttons && this.options.buttons.length === 0)
|
||||
) {
|
||||
this.options.buttons = [
|
||||
{
|
||||
name: "Close",
|
||||
color: "indigo",
|
||||
callback: undefined,
|
||||
}
|
||||
];
|
||||
}
|
||||
}
|
||||
};
|
||||
close(btn?: CustomDialogOptionsButton) {
|
||||
this.showDialog = false;
|
||||
if (btn && btn.callback) {
|
||||
btn.callback();
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.v-list,
|
||||
.v-sheet {
|
||||
background-color: unset;
|
||||
}
|
||||
.v-list {
|
||||
padding: 0 24px;
|
||||
}
|
||||
.v-list-item {
|
||||
padding: 0;
|
||||
}
|
||||
.v-dialog > .v-card > .v-card__text {
|
||||
padding: 12px 24px;
|
||||
}
|
||||
.v-list-item__content {
|
||||
padding: 6px 0;
|
||||
}
|
||||
.share-ffc-url {
|
||||
text-align: center;
|
||||
margin: 0 24px;
|
||||
}
|
||||
</style>
|
|
@ -3,16 +3,13 @@
|
|||
<v-subheader>Decks</v-subheader>
|
||||
<v-list v-if="decks.length > 0">
|
||||
<v-list-item-group multiple color="indigo" v-model="deckModel">
|
||||
<v-list-item
|
||||
v-for="deck in decks"
|
||||
:key="deck.id"
|
||||
:value="deck.id"
|
||||
:id="deck.id"
|
||||
>
|
||||
<v-list-item v-for="deck in decks" :key="deck.id" :value="deck.id" :id="deck.id">
|
||||
<v-list-item-content>
|
||||
<v-list-item-title v-text="deck.name"></v-list-item-title>
|
||||
</v-list-item-content>
|
||||
<v-list-item-icon v-bind:class="{ hidden: numberOfSelectedDecks===0, visible: numberOfSelectedDecks>0 }">
|
||||
<v-list-item-icon
|
||||
v-bind:class="{ hidden: numberOfSelectedDecks===0, visible: numberOfSelectedDecks>0 }"
|
||||
>
|
||||
<v-icon v-if="deck.selected">mdi-check-box-outline</v-icon>
|
||||
<v-icon v-else>mdi-checkbox-blank-outline</v-icon>
|
||||
</v-list-item-icon>
|
||||
|
@ -21,43 +18,32 @@
|
|||
</v-list>
|
||||
<p v-else id="no-decks-yet-notice">
|
||||
You don't have any decks yet.
|
||||
You might want to add some by clicking on the button in the bottom right corner.</p>
|
||||
<v-btn class="mx-2 btn-fixed-bottom-right-corner" fab dark color="indigo" @click="onButtonClick">
|
||||
You might want to add some by clicking on the button in the bottom right corner.
|
||||
</p>
|
||||
<v-btn
|
||||
class="mx-2"
|
||||
id="btn-fixed-bottom-right-corner"
|
||||
fab
|
||||
dark
|
||||
color="indigo"
|
||||
@click="onButtonClick"
|
||||
>
|
||||
<v-icon
|
||||
v-text="numberOfSelectedDecks === 0 ? 'mdi-plus' : 'mdi-navigation'"
|
||||
:class="{ 'rotate-90': numberOfSelectedDecks > 0 }" />
|
||||
:class="{ 'rotate-90': numberOfSelectedDecks > 0 }"
|
||||
/>
|
||||
</v-btn>
|
||||
|
||||
<DialogDeleteDecks
|
||||
ref="confirmDelete"
|
||||
:numberOfSelectedDecks="numberOfSelectedDecks"
|
||||
@confirmed="deleteSelectedDecks"
|
||||
/>
|
||||
<DialogDeckInfo
|
||||
ref="info"
|
||||
:deck="selectedDeck"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import DialogDeleteDecks from "./DialogDeleteDecks.vue";
|
||||
import DialogDeckInfo from "./DialogDeckInfo.vue";
|
||||
|
||||
export default {
|
||||
name: "DeckSelection",
|
||||
components: {
|
||||
DialogDeleteDecks,
|
||||
DialogDeckInfo,
|
||||
},
|
||||
props: {
|
||||
decks: Array,
|
||||
numberOfSelectedDecks: Number,
|
||||
numberOfSelectedDecks: Number
|
||||
},
|
||||
created() {
|
||||
this.$eventHub.$on("askForConfirmationToDeleteSelectedDecks", () => {
|
||||
if (this.$refs.confirmDelete) this.$refs.confirmDelete.show();
|
||||
});
|
||||
this.$eventHub.$on("showInfoForSelectedDeck", () => {
|
||||
if (this.$refs.info) this.$refs.info.show();
|
||||
});
|
||||
|
@ -66,46 +52,48 @@ export default {
|
|||
return {
|
||||
showDeleteDialog: false,
|
||||
showInfo: false,
|
||||
showDialog: false
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
deckModel: {
|
||||
get () {
|
||||
return this.decks.map((deck) => deck.selected ? deck.id : undefined).filter((id) => id !== undefined);
|
||||
get() {
|
||||
return this.decks
|
||||
.map(deck => (deck.selected ? deck.id : undefined))
|
||||
.filter(id => id !== undefined);
|
||||
},
|
||||
set (newModel) {
|
||||
this.decks.forEach((deck) => {
|
||||
set(newModel) {
|
||||
this.decks.forEach(deck => {
|
||||
deck.selected = newModel.includes(deck.id);
|
||||
});
|
||||
}
|
||||
},
|
||||
selectedDeck() {
|
||||
return this.deckModel.length !== 1 ? null : this.decks.find((deck) => deck.id === this.deckModel[0]);
|
||||
},
|
||||
return this.deckModel.length !== 1
|
||||
? null
|
||||
: this.decks.find(deck => deck.id === this.deckModel[0]);
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
onButtonClick() {
|
||||
if (this.numberOfSelectedDecks === 0) {
|
||||
this.$router.push('add');
|
||||
this.$router.push("add");
|
||||
} else {
|
||||
// start learning with selected decks
|
||||
this.$router.push('learn');
|
||||
this.$router.push("learn");
|
||||
}
|
||||
},
|
||||
deleteSelectedDecks() {
|
||||
this.$refs.confirmDelete.hide();
|
||||
this.$eventHub.$emit("deleteDecks", this.deckModel);
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<!-- Add "scoped" attribute to limit CSS to this component only -->
|
||||
<style scoped>
|
||||
.btn-fixed-bottom-right-corner {
|
||||
#btn-fixed-bottom-right-corner {
|
||||
position: fixed;
|
||||
bottom: 20px;
|
||||
right: 20px;
|
||||
margin: 0 !important;
|
||||
}
|
||||
|
||||
.hidden {
|
||||
|
|
|
@ -1,58 +0,0 @@
|
|||
<template>
|
||||
<v-dialog v-model="showInfo" max-width="400">
|
||||
<v-card color="#2e2e2e">
|
||||
<v-card-title class="headline">{{deck ? deck.name : ""}}</v-card-title>
|
||||
|
||||
<v-list v-if="deck">
|
||||
<v-list-item v-for="item in fileInfos" :key="item.key">
|
||||
<v-list-item-content>{{ item.name }}</v-list-item-content>
|
||||
<v-list-item-content>{{ deck.meta.file[item.key] || "-" }}</v-list-item-content>
|
||||
</v-list-item>
|
||||
<v-list-item v-for="item in deckInfos" :key="item.key">
|
||||
<v-list-item-content>{{ item.name }}</v-list-item-content>
|
||||
<v-list-item-content>{{ deck.meta.deck[item.key] || "-" }}</v-list-item-content>
|
||||
</v-list-item>
|
||||
<v-list-item>
|
||||
<v-list-item-content>Number of Cards</v-list-item-content>
|
||||
<v-list-item-content>{{ deck.cards.length }}</v-list-item-content>
|
||||
</v-list-item>
|
||||
</v-list>
|
||||
|
||||
<v-card-actions>
|
||||
<v-spacer></v-spacer>
|
||||
<v-btn color="grey" text @click="showInfo = false">Close</v-btn>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: "DialogDeckInfo",
|
||||
props: {
|
||||
deck: Object
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
showInfo: false,
|
||||
fileInfos: [{ key: "author", name: "Author" }],
|
||||
deckInfos: [{ key: "description", name: "Description" }]
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
hide() {
|
||||
this.showInfo = false;
|
||||
},
|
||||
show() {
|
||||
this.showInfo = true;
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.v-list,
|
||||
.v-sheet {
|
||||
background-color: unset;
|
||||
}
|
||||
</style>
|
|
@ -1,59 +0,0 @@
|
|||
<template>
|
||||
<v-dialog
|
||||
v-model="showDeleteDialog"
|
||||
max-width="400"
|
||||
>
|
||||
<v-card color="#2e2e2e">
|
||||
<v-card-title class="headline">Delete Deck{{numberOfSelectedDecks > 1 ? "s" : ""}}?</v-card-title>
|
||||
|
||||
<v-card-text class="text-left">
|
||||
Do you really want to delete the {{numberOfSelectedDecks > 1 ? numberOfSelectedDecks + " " : ""}}selected
|
||||
deck{{numberOfSelectedDecks > 1 ? "s" : ""}}?</v-card-text>
|
||||
|
||||
<v-card-actions>
|
||||
<v-spacer></v-spacer>
|
||||
|
||||
<v-btn
|
||||
color="grey"
|
||||
text
|
||||
@click="showDeleteDialog = false"
|
||||
>
|
||||
Cancel
|
||||
</v-btn>
|
||||
|
||||
<v-btn
|
||||
color="red darken-1"
|
||||
text
|
||||
@click="$emit('confirmed')"
|
||||
>
|
||||
Delete
|
||||
</v-btn>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: "DialogDeleteDecks",
|
||||
props: {
|
||||
numberOfSelectedDecks: Number,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
showDeleteDialog: false,
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
hide() {
|
||||
this.showDeleteDialog = false;
|
||||
},
|
||||
show() {
|
||||
this.showDeleteDialog = true;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
</style>
|
|
@ -1,25 +0,0 @@
|
|||
<template>
|
||||
<div class="footer">
|
||||
<v-footer :inset="footer.inset" app>
|
||||
<span class="px-4">© {{ new Date().getFullYear() }} Niko Lockenvitz & Rene-Pascal Fischer</span>
|
||||
</v-footer>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: "Footer",
|
||||
props: {
|
||||
title: String
|
||||
},
|
||||
data: () => ({
|
||||
footer: {
|
||||
inset: false
|
||||
}
|
||||
})
|
||||
};
|
||||
</script>
|
||||
|
||||
<!-- Add "scoped" attribute to limit CSS to this component only -->
|
||||
<style scoped>
|
||||
</style>
|
|
@ -11,6 +11,10 @@
|
|||
overflow
|
||||
>
|
||||
<v-list>
|
||||
<v-list-item>
|
||||
<v-img src="../../../public/img/flash.svg" class="ffc-icon"></v-img>
|
||||
</v-list-item>
|
||||
<v-divider></v-divider>
|
||||
<v-list-item v-for="navItem in navBarList" :key="navItem.to" :to="navItem.to" link>
|
||||
<v-list-item-icon>
|
||||
<v-icon>{{navItem.icon}}</v-icon>
|
||||
|
@ -22,38 +26,31 @@
|
|||
</v-list>
|
||||
</v-navigation-drawer>
|
||||
|
||||
<v-app-bar
|
||||
:clipped-left="primaryDrawer.clipped"
|
||||
app
|
||||
:class="colorAppBar"
|
||||
>
|
||||
<v-btn icon
|
||||
v-if="isInDeckSelection && numberOfSelectedDecks>0"
|
||||
@click="deselectAll"
|
||||
>
|
||||
<v-app-bar :clipped-left="primaryDrawer.clipped" app :class="colorAppBar">
|
||||
<v-btn icon v-if="isInDeckSelection && numberOfSelectedDecks>0" @click="deselectAll">
|
||||
<v-icon>mdi-close</v-icon>
|
||||
</v-btn>
|
||||
<v-btn icon
|
||||
v-else-if="isInLearning"
|
||||
@click="quitLearning"
|
||||
>
|
||||
<v-btn icon v-else-if="isInLearning" @click="quitLearning">
|
||||
<v-icon>mdi-arrow-left</v-icon>
|
||||
</v-btn>
|
||||
<v-app-bar-nav-icon v-else
|
||||
@click.stop="primaryDrawer.model = !primaryDrawer.model"
|
||||
></v-app-bar-nav-icon>
|
||||
<v-app-bar-nav-icon v-else @click.stop="togglePrimaryDrawer"></v-app-bar-nav-icon>
|
||||
|
||||
<v-toolbar-title>
|
||||
{{ toolbarTitle }}
|
||||
</v-toolbar-title>
|
||||
<v-toolbar-title>{{ toolbarTitle }}</v-toolbar-title>
|
||||
|
||||
<v-spacer></v-spacer>
|
||||
|
||||
<v-btn icon v-if="isInDeckSelection && numberOfSelectedDecks===1" @click="showInfoForSelectedDeck">
|
||||
<v-btn
|
||||
icon
|
||||
v-if="isInDeckSelection && numberOfSelectedDecks===1"
|
||||
@click="showInfoForSelectedDeck"
|
||||
>
|
||||
<v-icon>mdi-information</v-icon>
|
||||
</v-btn>
|
||||
|
||||
<v-btn icon v-if="isInDeckSelection && numberOfSelectedDecks>0" @click="selectAll"
|
||||
<v-btn
|
||||
icon
|
||||
v-if="isInDeckSelection && numberOfSelectedDecks>0"
|
||||
@click="selectAll"
|
||||
:disabled="numberOfSelectedDecks === decks.length"
|
||||
>
|
||||
<v-icon>mdi-checkbox-multiple-marked</v-icon>
|
||||
|
@ -66,72 +63,83 @@
|
|||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: "NavigationBar",
|
||||
<script lang="ts">
|
||||
import Vue from "vue";
|
||||
import Component from "vue-class-component";
|
||||
|
||||
import { Deck } from '../../types';
|
||||
|
||||
import * as selectedDeckDialogHelper from "../../helpers/selectedDeckDialogHelper";
|
||||
|
||||
const NavigationBarProps = Vue.extend({
|
||||
props: {
|
||||
title: String,
|
||||
decks: Array,
|
||||
decks: {type: Array as () => Deck[]},
|
||||
numberOfSelectedDecks: Number,
|
||||
navBarList: Array
|
||||
},
|
||||
data: () => ({
|
||||
primaryDrawer: {
|
||||
model: false,
|
||||
type: "temporary",
|
||||
clipped: true,
|
||||
floating: false,
|
||||
mini: false
|
||||
}
|
||||
}),
|
||||
computed: {
|
||||
isInDeckSelection() {
|
||||
return this.$route.name === "DeckSelection";
|
||||
},
|
||||
isInLearning() {
|
||||
return this.$route.name === "Learn";
|
||||
},
|
||||
colorAppBar() {
|
||||
if (this.isInDeckSelection && this.numberOfSelectedDecks > 0) {
|
||||
return "indigo";
|
||||
}
|
||||
return "";
|
||||
},
|
||||
toolbarTitle() {
|
||||
if (this.isInDeckSelection && this.numberOfSelectedDecks > 0) {
|
||||
return `${this.numberOfSelectedDecks} deck${this.numberOfSelectedDecks === 1 ? "":"s"} selected`;
|
||||
}
|
||||
return this.title;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
deselectAll() {
|
||||
this.decks.forEach(deck => {
|
||||
deck.selected = false;
|
||||
});
|
||||
},
|
||||
selectAll() {
|
||||
this.decks.forEach(deck => {
|
||||
deck.selected = true;
|
||||
});
|
||||
},
|
||||
deleteSelected() {
|
||||
this.$eventHub.$emit("askForConfirmationToDeleteSelectedDecks");
|
||||
},
|
||||
showInfoForSelectedDeck() {
|
||||
this.$eventHub.$emit("showInfoForSelectedDeck");
|
||||
},
|
||||
showDrawer() {
|
||||
this.primaryDrawer.model = true;
|
||||
},
|
||||
hideDrawer() {
|
||||
this.primaryDrawer.model = false;
|
||||
},
|
||||
quitLearning() {
|
||||
this.$eventHub.$emit("askForConfirmationToQuitLearning");
|
||||
},
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
@Component
|
||||
export default class NavigationBar extends NavigationBarProps {
|
||||
primaryDrawer = {
|
||||
model: false,
|
||||
type: "temporary",
|
||||
clipped: true,
|
||||
floating: false,
|
||||
mini: false
|
||||
};
|
||||
|
||||
get isInDeckSelection() {
|
||||
return this.$route.name === "DeckSelection";
|
||||
}
|
||||
get isInLearning() {
|
||||
return this.$route.name === "Learn";
|
||||
}
|
||||
get colorAppBar() {
|
||||
if (this.isInDeckSelection && this.numberOfSelectedDecks > 0) {
|
||||
return "indigo";
|
||||
}
|
||||
return "";
|
||||
}
|
||||
get toolbarTitle() {
|
||||
if (this.isInDeckSelection && this.numberOfSelectedDecks > 0) {
|
||||
return `${this.numberOfSelectedDecks} deck${
|
||||
this.numberOfSelectedDecks === 1 ? "" : "s"
|
||||
} selected`;
|
||||
}
|
||||
return this.title;
|
||||
}
|
||||
|
||||
deselectAll() {
|
||||
this.decks.forEach(deck => {
|
||||
deck.selected = false;
|
||||
});
|
||||
}
|
||||
selectAll() {
|
||||
this.decks.forEach(deck => {
|
||||
deck.selected = true;
|
||||
});
|
||||
}
|
||||
deleteSelected() {
|
||||
selectedDeckDialogHelper.deleteSelected(this);
|
||||
}
|
||||
showInfoForSelectedDeck() {
|
||||
selectedDeckDialogHelper.showInfoForSelectedDeck(this);
|
||||
}
|
||||
showDrawer() {
|
||||
this.primaryDrawer.model = true;
|
||||
}
|
||||
hideDrawer() {
|
||||
this.primaryDrawer.model = false;
|
||||
}
|
||||
quitLearning() {
|
||||
selectedDeckDialogHelper.quitLearning(this);
|
||||
}
|
||||
togglePrimaryDrawer() {
|
||||
this.primaryDrawer.model = !this.primaryDrawer.model;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<!-- Add "scoped" attribute to limit CSS to this component only -->
|
||||
|
|
|
@ -1,56 +0,0 @@
|
|||
<template>
|
||||
<v-dialog
|
||||
v-model="showQuitDialog"
|
||||
max-width="400"
|
||||
>
|
||||
<v-card color="#2e2e2e">
|
||||
<v-card-title class="headline">Quit Learning?</v-card-title>
|
||||
|
||||
<v-card-text class="text-left">
|
||||
Do you really want to quit learning?
|
||||
Nevertheless, your progress is saved.</v-card-text>
|
||||
|
||||
<v-card-actions>
|
||||
<v-spacer></v-spacer>
|
||||
|
||||
<v-btn
|
||||
color="grey"
|
||||
text
|
||||
@click="showQuitDialog = false"
|
||||
>
|
||||
Cancel
|
||||
</v-btn>
|
||||
|
||||
<v-btn
|
||||
color="red darken-1"
|
||||
text
|
||||
@click="$emit('confirmed')"
|
||||
>
|
||||
Quit
|
||||
</v-btn>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: "DialogQuit",
|
||||
data() {
|
||||
return {
|
||||
showQuitDialog: false,
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
hide() {
|
||||
this.showQuitDialog = false;
|
||||
},
|
||||
show() {
|
||||
this.showQuitDialog = true;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
</style>
|
|
@ -17,23 +17,16 @@
|
|||
<v-spacer></v-spacer>
|
||||
<v-btn text color="grey lighten-1">Next</v-btn>
|
||||
</v-card-actions>
|
||||
|
||||
<DialogQuit
|
||||
ref="confirmQuit"
|
||||
@confirmed="confirmedQuit"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Rating from './Rating.vue';
|
||||
import DialogQuit from './DialogQuit.vue';
|
||||
|
||||
export default {
|
||||
name: "Learn",
|
||||
components: {
|
||||
Rating,
|
||||
DialogQuit,
|
||||
},
|
||||
props: {
|
||||
decks: Array,
|
||||
|
@ -57,11 +50,6 @@ Morbi tempor quis justo vitae imperdiet.`,
|
|||
},
|
||||
};
|
||||
},
|
||||
created() {
|
||||
this.$eventHub.$on("askForConfirmationToQuitLearning", () => {
|
||||
if (this.$refs.confirmQuit) this.$refs.confirmQuit.show();
|
||||
});
|
||||
},
|
||||
beforeMount() {
|
||||
if (this.numberOfSelectedDecks === 0) {
|
||||
this.$router.replace("/");
|
||||
|
@ -75,7 +63,7 @@ Morbi tempor quis justo vitae imperdiet.`,
|
|||
}
|
||||
},
|
||||
updateVerticalCentering() {
|
||||
for (let el of document.getElementsByClassName("max-height")) {
|
||||
for (const el of document.getElementsByClassName("max-height")) {
|
||||
if (el.scrollHeight > el.clientHeight) {
|
||||
el.classList.remove("flex-center");
|
||||
} else {
|
||||
|
@ -83,9 +71,6 @@ Morbi tempor quis justo vitae imperdiet.`,
|
|||
}
|
||||
}
|
||||
},
|
||||
confirmedQuit() {
|
||||
this.$router.replace("/");
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.updateVerticalCentering();
|
||||
|
@ -96,11 +81,13 @@ Morbi tempor quis justo vitae imperdiet.`,
|
|||
};
|
||||
</script>
|
||||
|
||||
<!-- Add "scoped" attribute to limit CSS to this component only -->
|
||||
<style scoped>
|
||||
.learn {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.max-height {
|
||||
|
@ -112,6 +99,7 @@ Morbi tempor quis justo vitae imperdiet.`,
|
|||
align-items: center;
|
||||
padding: 0 16px;
|
||||
}
|
||||
|
||||
.flex-center {
|
||||
justify-content: center;
|
||||
}
|
||||
|
|
|
@ -1,61 +0,0 @@
|
|||
export function addDecksFromFile(context, fileContent) {
|
||||
try {
|
||||
addDecksFromJSON(context, JSON.parse(fileContent));
|
||||
} catch (e) {
|
||||
context.showSnackbar(e);
|
||||
}
|
||||
}
|
||||
|
||||
export function addDecksFromJSON(context, fileContent) {
|
||||
// Following decks have been added: d0 (5 cards), d1 (76 cards)...
|
||||
const addedDecksAndCards = [];
|
||||
try {
|
||||
for (const deckShortName in fileContent.decks) {
|
||||
const cards = [];
|
||||
for (const cardId in fileContent.decks[deckShortName].cards) {
|
||||
cards.push({
|
||||
id: Number(cardId),
|
||||
q: fileContent.decks[deckShortName].cards[cardId].q,
|
||||
a: fileContent.decks[deckShortName].cards[cardId].a,
|
||||
r: [],
|
||||
});
|
||||
}
|
||||
|
||||
const name = fileContent.decks[deckShortName].meta.deck_name || deckShortName;
|
||||
context.decks.push({
|
||||
id: context.decks.reduce((acc, cur) => Math.max(acc, cur.id), 0) + 1,
|
||||
selected: false,
|
||||
name,
|
||||
meta: {
|
||||
file: fileContent.meta,
|
||||
deck: {
|
||||
...fileContent.decks[deckShortName].meta,
|
||||
short_name: deckShortName,
|
||||
},
|
||||
},
|
||||
cards,
|
||||
});
|
||||
addedDecksAndCards.push({name, numberOfCards: cards.length});
|
||||
}
|
||||
|
||||
showAddedDecksConfirmation(context, addedDecksAndCards);
|
||||
} catch (e) {
|
||||
context.showSnackbar(e);
|
||||
}
|
||||
}
|
||||
|
||||
function showAddedDecksConfirmation(context, addedDecksAndCards) {
|
||||
const numberOfAddedCards = addedDecksAndCards.reduce((total, deck) => total + deck.numberOfCards, 0);
|
||||
if (numberOfAddedCards === 0) {
|
||||
throw new Error("No decks have been added");
|
||||
}
|
||||
|
||||
const message = addedDecksAndCards.reduce((message, deck, ix, arr) => {
|
||||
return `${message} Deck "${deck.name}" (Cards: ${deck.numberOfCards})${ix === arr.length-1 ? "" : ","}`;
|
||||
}, "Following Decks Have Been Added:");
|
||||
context.showCustomDialog({
|
||||
title: "Successfully Imported Decks",
|
||||
message,
|
||||
redirectRouteAfterClose: '/',
|
||||
});
|
||||
}
|
92
src/helpers/addDecksHelper.ts
Normal file
|
@ -0,0 +1,92 @@
|
|||
import { FFCFile, Deck, CustomDialogOptions } from '@/types';
|
||||
import router from '@/router';
|
||||
|
||||
interface Context {
|
||||
showSnackbar: Function,
|
||||
decks: Deck[],
|
||||
showCustomDialog: Function,
|
||||
$router: typeof router,
|
||||
}
|
||||
|
||||
export function addDecksFromFile(context: Context, fileContent: string) {
|
||||
try {
|
||||
addDecksFromJSON(context, JSON.parse(fileContent));
|
||||
} catch (e) {
|
||||
context.showSnackbar(e);
|
||||
}
|
||||
}
|
||||
|
||||
interface addedDeckAndCards {
|
||||
name: string,
|
||||
numberOfCards: number,
|
||||
}
|
||||
|
||||
export function addDecksFromJSON(context: Context, fileContent: FFCFile) {
|
||||
const addedDecksAndCards = [] as addedDeckAndCards[];
|
||||
try {
|
||||
for (const deckShortName in fileContent.decks) {
|
||||
const cards = [];
|
||||
for (const cardId in fileContent.decks[deckShortName].cards) {
|
||||
cards.push({
|
||||
id: Number(cardId),
|
||||
q: fileContent.decks[deckShortName].cards[cardId].q,
|
||||
a: fileContent.decks[deckShortName].cards[cardId].a,
|
||||
r: [],
|
||||
});
|
||||
}
|
||||
|
||||
const name =
|
||||
fileContent.decks[deckShortName].meta.deck_name || deckShortName;
|
||||
context.decks.push({
|
||||
id: context.decks.reduce((acc, cur) => Math.max(acc, cur.id), 0) + 1,
|
||||
selected: false,
|
||||
name,
|
||||
meta: {
|
||||
file: fileContent.meta,
|
||||
deck: {
|
||||
...fileContent.decks[deckShortName].meta,
|
||||
short_name: deckShortName,
|
||||
},
|
||||
},
|
||||
cards,
|
||||
});
|
||||
addedDecksAndCards.push({ name, numberOfCards: cards.length });
|
||||
}
|
||||
|
||||
showAddedDecksConfirmation(context, addedDecksAndCards);
|
||||
} catch (e) {
|
||||
context.showSnackbar(e);
|
||||
}
|
||||
}
|
||||
|
||||
function showAddedDecksConfirmation(context: Context, addedDecksAndCards: addedDeckAndCards[]) {
|
||||
const numberOfAddedCards = addedDecksAndCards.reduce(
|
||||
(total, deck) => total + deck.numberOfCards,
|
||||
0
|
||||
);
|
||||
if (numberOfAddedCards === 0) {
|
||||
throw new Error("No decks have been added");
|
||||
}
|
||||
|
||||
const options = {
|
||||
title: "Successfully Imported Decks",
|
||||
message: "Following decks have been added:",
|
||||
tableHead: { name: "Deck", value: "Number of Cards" },
|
||||
table: addedDecksAndCards.map((deck) => {
|
||||
return {
|
||||
name: deck.name,
|
||||
value: String(deck.numberOfCards),
|
||||
};
|
||||
}),
|
||||
buttons: [
|
||||
{
|
||||
name: "Close",
|
||||
color: "indigo",
|
||||
callback: function() {
|
||||
context.$router.push("/");
|
||||
},
|
||||
},
|
||||
],
|
||||
} as CustomDialogOptions;
|
||||
context.showCustomDialog(options);
|
||||
}
|
8
src/helpers/copyToClipboardHelper.ts
Normal file
|
@ -0,0 +1,8 @@
|
|||
export function copyToClipboard() {
|
||||
const ffcURL = document.getElementById("ffc-url") as HTMLInputElement;
|
||||
if (!ffcURL) return;
|
||||
ffcURL.select();
|
||||
ffcURL.setSelectionRange(0, ffcURL.value.length);
|
||||
document.execCommand("copy");
|
||||
ffcURL.blur();
|
||||
}
|
|
@ -1,27 +1,33 @@
|
|||
const LOCAL_STORAGE_APP_CONTEXT = "ffc_";
|
||||
|
||||
function get(key) {
|
||||
return localStorage.getItem(LOCAL_STORAGE_APP_CONTEXT + key);
|
||||
export interface SyncItem {
|
||||
key: string;
|
||||
defaultValue: any;
|
||||
}
|
||||
|
||||
function set(key, value) {
|
||||
interface Context {
|
||||
propertiesToSyncWithLocalStorage: SyncItem[];
|
||||
[x: string]: any;
|
||||
}
|
||||
|
||||
function get(key: string): string {
|
||||
return localStorage.getItem(LOCAL_STORAGE_APP_CONTEXT + key) || "";
|
||||
}
|
||||
|
||||
function set(key: string, value: string): void {
|
||||
localStorage.setItem(LOCAL_STORAGE_APP_CONTEXT + key, value);
|
||||
}
|
||||
|
||||
// function remove(key) {
|
||||
// localStorage.removeItem(LOCAL_STORAGE_APP_CONTEXT + key);
|
||||
// }
|
||||
|
||||
function clearAppData() {
|
||||
function clearAppData(): void {
|
||||
for (let i = 0; i < localStorage.length; i++) {
|
||||
const key = localStorage.key(i);
|
||||
const key = localStorage.key(i) || "";
|
||||
if (key.startsWith(LOCAL_STORAGE_APP_CONTEXT)) {
|
||||
localStorage.removeItem(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function readFromLocalStorage(context) {
|
||||
export function readFromLocalStorage(context: Context) {
|
||||
context.propertiesToSyncWithLocalStorage.forEach((item) => {
|
||||
try {
|
||||
context[item.key] = JSON.parse(get(item.key));
|
||||
|
@ -33,10 +39,10 @@ export function readFromLocalStorage(context) {
|
|||
}
|
||||
});
|
||||
}
|
||||
export function saveToLocalStorage(context, item) {
|
||||
export function saveToLocalStorage(context: Context, item: SyncItem) {
|
||||
set(item.key, JSON.stringify(context[item.key]));
|
||||
}
|
||||
export function clearLocalStorage(context) {
|
||||
export function clearLocalStorage(context: Context) {
|
||||
clearAppData();
|
||||
context.propertiesToSyncWithLocalStorage.forEach((item) => {
|
||||
context[item.key] = item.defaultValue;
|
114
src/helpers/selectedDeckDialogHelper.ts
Normal file
|
@ -0,0 +1,114 @@
|
|||
import Vue from 'vue';
|
||||
import { Deck, CustomDialogOptions } from '@/types';
|
||||
import router from '@/router';
|
||||
|
||||
interface Context {
|
||||
numberOfSelectedDecks: number,
|
||||
$eventHub: typeof Vue,
|
||||
decks: Deck[],
|
||||
deselectAll: Function,
|
||||
$router: typeof router,
|
||||
}
|
||||
|
||||
export function deleteSelected(context: any) {
|
||||
const options = {
|
||||
title: `Delete Deck${context.numberOfSelectedDecks > 1 ? "s" : ""}?`,
|
||||
message: `Do you really want to delete the ${
|
||||
context.numberOfSelectedDecks > 1
|
||||
? context.numberOfSelectedDecks + " "
|
||||
: ""
|
||||
}selected
|
||||
deck${context.numberOfSelectedDecks > 1 ? "s" : ""}?`,
|
||||
buttons: [
|
||||
{
|
||||
name: "Cancel",
|
||||
color: "grey",
|
||||
},
|
||||
{
|
||||
name: "Delete",
|
||||
color: "red darken-1",
|
||||
callback: () => {
|
||||
context.$eventHub.$emit("deleteSelectedDecks");
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
context.$eventHub.$emit("showCustomDialog", options);
|
||||
}
|
||||
|
||||
export function showInfoForSelectedDeck(context: any) {
|
||||
const selectedDeck = context.decks.find((deck: any) => deck.selected);
|
||||
const options = {
|
||||
title: selectedDeck?.name,
|
||||
table: [],
|
||||
buttons: [
|
||||
{
|
||||
name: "Close",
|
||||
color: "indigo",
|
||||
},
|
||||
],
|
||||
} as CustomDialogOptions;
|
||||
const infos = [
|
||||
{
|
||||
meta: "file",
|
||||
content: [
|
||||
{
|
||||
key: "author",
|
||||
name: "Author",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
meta: "deck",
|
||||
content: [
|
||||
{
|
||||
key: "description",
|
||||
name: "Description",
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
for (const info of infos) {
|
||||
for (const content of info.content) {
|
||||
if (info.meta === "file") {
|
||||
options.table?.push({
|
||||
name: content.name,
|
||||
value: selectedDeck?.meta.file[content.key] || "-",
|
||||
});
|
||||
} else if (info.meta === "deck") {
|
||||
options.table?.push({
|
||||
name: content.name,
|
||||
value: selectedDeck?.meta.deck[content.key] || "-",
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
options.table?.push({
|
||||
name: "Number of Cards",
|
||||
value: String(selectedDeck?.cards.length || 0),
|
||||
});
|
||||
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("/");
|
||||
}
|
||||
}
|
||||
]
|
||||
});
|
||||
}
|
15
src/main.js
|
@ -1,15 +0,0 @@
|
|||
import Vue from 'vue'
|
||||
import App from './App.vue'
|
||||
import router from './router'
|
||||
import vuetify from './plugins/vuetify';
|
||||
import './registerServiceWorker'
|
||||
|
||||
Vue.config.productionTip = false
|
||||
|
||||
Vue.prototype.$eventHub = new Vue()
|
||||
|
||||
new Vue({
|
||||
router,
|
||||
vuetify,
|
||||
render: h => h(App)
|
||||
}).$mount('#app')
|
20
src/main.ts
Normal file
|
@ -0,0 +1,20 @@
|
|||
import Vue from "vue";
|
||||
import App from "./App.vue";
|
||||
import "./registerServiceWorker";
|
||||
import router from "./router";
|
||||
import vuetify from "./plugins/vuetify";
|
||||
|
||||
Vue.config.productionTip = false;
|
||||
|
||||
declare module "vue/types/vue" {
|
||||
interface Vue {
|
||||
$eventHub: Vue;
|
||||
}
|
||||
}
|
||||
Vue.prototype.$eventHub = new Vue();
|
||||
|
||||
new Vue({
|
||||
router,
|
||||
vuetify,
|
||||
render: (h) => h(App),
|
||||
}).$mount("#app");
|
|
@ -1,10 +0,0 @@
|
|||
import Vue from 'vue';
|
||||
import Vuetify from 'vuetify/lib';
|
||||
|
||||
Vue.use(Vuetify);
|
||||
|
||||
export default new Vuetify({
|
||||
theme: {
|
||||
dark: true,
|
||||
},
|
||||
});
|
10
src/plugins/vuetify.ts
Normal file
|
@ -0,0 +1,10 @@
|
|||
import Vue from "vue";
|
||||
import Vuetify from "vuetify/lib";
|
||||
|
||||
Vue.use(Vuetify);
|
||||
|
||||
export default new Vuetify({
|
||||
theme: {
|
||||
dark: true,
|
||||
},
|
||||
});
|
|
@ -1,32 +0,0 @@
|
|||
/* eslint-disable no-console */
|
||||
|
||||
import { register } from 'register-service-worker'
|
||||
|
||||
if (process.env.NODE_ENV === 'production') {
|
||||
register(`${process.env.BASE_URL}service-worker.js`, {
|
||||
ready () {
|
||||
console.log(
|
||||
'App is being served from cache by a service worker.\n' +
|
||||
'For more details, visit https://goo.gl/AFskqB'
|
||||
)
|
||||
},
|
||||
registered () {
|
||||
console.log('Service worker has been registered.')
|
||||
},
|
||||
cached () {
|
||||
console.log('Content has been cached for offline use.')
|
||||
},
|
||||
updatefound () {
|
||||
console.log('New content is downloading.')
|
||||
},
|
||||
updated () {
|
||||
console.log('New content is available; please refresh.')
|
||||
},
|
||||
offline () {
|
||||
console.log('No internet connection found. App is running in offline mode.')
|
||||
},
|
||||
error (error) {
|
||||
console.error('Error during service worker registration:', error)
|
||||
}
|
||||
})
|
||||
}
|
34
src/registerServiceWorker.ts
Normal file
|
@ -0,0 +1,34 @@
|
|||
/* eslint-disable no-console */
|
||||
|
||||
import { register } from "register-service-worker";
|
||||
|
||||
if (process.env.NODE_ENV === "production") {
|
||||
register(`${process.env.BASE_URL}service-worker.js`, {
|
||||
ready() {
|
||||
console.log(
|
||||
"App is being served from cache by a service worker.\n" +
|
||||
"For more details, visit https://goo.gl/AFskqB"
|
||||
);
|
||||
},
|
||||
registered() {
|
||||
console.log("Service worker has been registered.");
|
||||
},
|
||||
cached() {
|
||||
console.log("Content has been cached for offline use.");
|
||||
},
|
||||
updatefound() {
|
||||
console.log("New content is downloading.");
|
||||
},
|
||||
updated() {
|
||||
console.log("New content is available; please refresh.");
|
||||
},
|
||||
offline() {
|
||||
console.log(
|
||||
"No internet connection found. App is running in offline mode."
|
||||
);
|
||||
},
|
||||
error(error) {
|
||||
console.error("Error during service worker registration:", error);
|
||||
}
|
||||
});
|
||||
}
|
|
@ -1,9 +1,9 @@
|
|||
import Vue from 'vue'
|
||||
import VueRouter from 'vue-router'
|
||||
import Vue from "vue";
|
||||
import VueRouter, { RouteConfig } from "vue-router";
|
||||
|
||||
Vue.use(VueRouter)
|
||||
Vue.use(VueRouter);
|
||||
|
||||
const routes = [
|
||||
const routes: Array<RouteConfig> = [
|
||||
{
|
||||
path: '/',
|
||||
name: 'DeckSelection',
|
||||
|
@ -32,12 +32,11 @@ const routes = [
|
|||
name: 'About',
|
||||
component: () => import('../views/About.vue'),
|
||||
}
|
||||
]
|
||||
];
|
||||
|
||||
const router = new VueRouter({
|
||||
mode: 'history',
|
||||
base: process.env.BASE_URL,
|
||||
routes
|
||||
})
|
||||
});
|
||||
|
||||
export default router
|
||||
export default router;
|
13
src/shims-tsx.d.ts
vendored
Normal file
|
@ -0,0 +1,13 @@
|
|||
import Vue, { VNode } from "vue";
|
||||
|
||||
declare global {
|
||||
namespace JSX {
|
||||
// tslint:disable no-empty-interface
|
||||
interface Element extends VNode {}
|
||||
// tslint:disable no-empty-interface
|
||||
interface ElementClass extends Vue {}
|
||||
interface IntrinsicElements {
|
||||
[elem: string]: any;
|
||||
}
|
||||
}
|
||||
}
|
4
src/shims-vue.d.ts
vendored
Normal file
|
@ -0,0 +1,4 @@
|
|||
declare module "*.vue" {
|
||||
import Vue from "vue";
|
||||
export default Vue;
|
||||
}
|
|
@ -1,22 +1,72 @@
|
|||
export interface FFCFile {
|
||||
meta: {
|
||||
author: string;
|
||||
[x: string]: any;
|
||||
};
|
||||
decks: {
|
||||
[deck_short_name: string]: {
|
||||
meta: {
|
||||
deck_name: string;
|
||||
description: string;
|
||||
next_card_id: number;
|
||||
[x: string]: any;
|
||||
};
|
||||
cards: {
|
||||
[cardId: string]: {
|
||||
q: string;
|
||||
a: string;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
export interface Deck {
|
||||
id: number,
|
||||
selected: boolean,
|
||||
name: string,
|
||||
meta: {
|
||||
file: object,
|
||||
deck: object
|
||||
},
|
||||
cards: Card[],
|
||||
id: number;
|
||||
selected: boolean;
|
||||
name: string;
|
||||
meta: {
|
||||
file: {
|
||||
[x: string]: any;
|
||||
};
|
||||
deck: {
|
||||
[x: string]: any;
|
||||
};
|
||||
};
|
||||
cards: Card[];
|
||||
}
|
||||
|
||||
export interface Card {
|
||||
id: number,
|
||||
q: string,
|
||||
a: string,
|
||||
r?: Rating[],
|
||||
id: number;
|
||||
q: string;
|
||||
a: string;
|
||||
r?: Rating[];
|
||||
}
|
||||
|
||||
export interface Rating {
|
||||
t: number,
|
||||
r: number,
|
||||
}
|
||||
t: number;
|
||||
r: number;
|
||||
}
|
||||
|
||||
export interface CustomDialogOptions {
|
||||
title: string;
|
||||
format?: string;
|
||||
message?: string;
|
||||
tableHead?: {
|
||||
name: string;
|
||||
value: string;
|
||||
};
|
||||
table?: CustomDialogOptionsTableRow[];
|
||||
buttons?: CustomDialogOptionsButton[];
|
||||
}
|
||||
|
||||
export interface CustomDialogOptionsTableRow {
|
||||
name: string;
|
||||
value: string;
|
||||
}
|
||||
|
||||
export interface CustomDialogOptionsButton {
|
||||
name: string;
|
||||
color: string;
|
||||
callback?: Function;
|
||||
}
|
||||
|
|
|
@ -5,15 +5,64 @@
|
|||
<v-col cols="10">
|
||||
<span class="title">Fancy Flashcard</span>
|
||||
<br />
|
||||
<span>© {{ new Date().getFullYear() }} Niko Lockenvitz & Rene-Pascal Fischer</span>
|
||||
<br />
|
||||
<a href="https://github.com/fancy-flashcard/ffc">https://github.com/fancy-flashcard/ffc</a>
|
||||
<v-btn color="indigo" @click="shareApp" class="my-4">Share Fancy Flashcard</v-btn>
|
||||
|
||||
<v-footer app>
|
||||
<span class="px-4">
|
||||
© {{ new Date().getFullYear() }} Niko Lockenvitz & Rene-Pascal Fischer
|
||||
<br />
|
||||
<a
|
||||
href="https://github.com/fancy-flashcard/ffc"
|
||||
>https://github.com/fancy-flashcard/ffc</a>
|
||||
</span>
|
||||
</v-footer>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-container>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { copyToClipboard } from "../helpers/copyToClipboardHelper";
|
||||
|
||||
export default {
|
||||
name: "About",
|
||||
methods: {
|
||||
shareApp() {
|
||||
if (navigator.share) {
|
||||
navigator
|
||||
.share({
|
||||
title: "Fancy Flashcard",
|
||||
url: "https://fancy-flashcard.github.io/ffc/",
|
||||
text: "Try out this cool app I found."
|
||||
})
|
||||
.then(() => {
|
||||
console.log("Thanks for sharing!");
|
||||
})
|
||||
.catch(console.error);
|
||||
} else {
|
||||
const options = {
|
||||
title: "Share Fancy Flashcard",
|
||||
url: "https://fancy-flashcard.github.io/ffc/",
|
||||
buttons: [
|
||||
{
|
||||
name: "Close",
|
||||
color: "grey"
|
||||
},
|
||||
{
|
||||
name: "Copy URL",
|
||||
color: "indigo",
|
||||
callback: copyToClipboard
|
||||
}
|
||||
]
|
||||
};
|
||||
this.$eventHub.$emit("showCustomDialog", options);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.about {
|
||||
text-align: center;
|
||||
|
|
|
@ -3,11 +3,7 @@
|
|||
<v-container fluid>
|
||||
<v-row align="center" justify="center">
|
||||
<v-col cols="10">
|
||||
<span class="title">Fancy Flashcard</span>
|
||||
<br />
|
||||
<span>You will be able to change your settings here.</span>
|
||||
<br />
|
||||
<a href="https://github.com/fancy-flashcard/ffc">https://github.com/fancy-flashcard/ffc</a>
|
||||
<span class="title">Settings</span>
|
||||
<br />
|
||||
<v-btn color="red" @click="clearLocalStorage" class="my-4">Clear Local Storage</v-btn>
|
||||
</v-col>
|
||||
|
|
47
tsconfig.json
Normal file
|
@ -0,0 +1,47 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"target": "esnext",
|
||||
"module": "esnext",
|
||||
"noImplicitAny": true,
|
||||
"noImplicitThis": true,
|
||||
"noImplicitReturns": true,
|
||||
"strict": true,
|
||||
"experimentalDecorators": true,
|
||||
"jsx": "preserve",
|
||||
"importHelpers": true,
|
||||
"moduleResolution": "node",
|
||||
"esModuleInterop": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"sourceMap": true,
|
||||
"baseUrl": ".",
|
||||
"types": [
|
||||
"webpack-env",
|
||||
"vuetify"
|
||||
],
|
||||
"typeRoots": [
|
||||
"./node_modules/@types",
|
||||
"./node_modules/vuetify/types"
|
||||
],
|
||||
"paths": {
|
||||
"@/*": [
|
||||
"src/*"
|
||||
]
|
||||
},
|
||||
"lib": [
|
||||
"esnext",
|
||||
"dom",
|
||||
"dom.iterable",
|
||||
"scripthost"
|
||||
]
|
||||
},
|
||||
"include": [
|
||||
"src/**/*.ts",
|
||||
"src/**/*.tsx",
|
||||
"src/**/*.vue",
|
||||
"tests/**/*.ts",
|
||||
"tests/**/*.tsx"
|
||||
],
|
||||
"exclude": [
|
||||
"node_modules"
|
||||
]
|
||||
}
|
|
@ -1,6 +1,12 @@
|
|||
module.exports = {
|
||||
"publicPath": "/ffc/",
|
||||
"transpileDependencies": [
|
||||
"vuetify"
|
||||
]
|
||||
}
|
||||
publicPath: "/ffc/",
|
||||
transpileDependencies: ["vuetify"],
|
||||
pwa: {
|
||||
themeColor: "#363636",
|
||||
msTileColor: "#3F51B5",
|
||||
manifestOptions: {
|
||||
name: "Fancy Flashcard",
|
||||
short_name: "FFC",
|
||||
},
|
||||
},
|
||||
};
|
||||
|
|