[WIP] move to typescript
This commit is contained in:
parent
7c59e4ec49
commit
9a58d66eff
30 changed files with 2436 additions and 5593 deletions
3
.browserslistrc
Normal file
3
.browserslistrc
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
> 1%
|
||||||
|
last 2 versions
|
||||||
|
not dead
|
25
.eslintrc.js
Normal file
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
1
.gitignore
vendored
|
@ -10,6 +10,7 @@ node_modules
|
||||||
npm-debug.log*
|
npm-debug.log*
|
||||||
yarn-debug.log*
|
yarn-debug.log*
|
||||||
yarn-error.log*
|
yarn-error.log*
|
||||||
|
pnpm-debug.log*
|
||||||
|
|
||||||
# Editor directories and files
|
# Editor directories and files
|
||||||
.idea
|
.idea
|
||||||
|
|
|
@ -1,5 +1,3 @@
|
||||||
module.exports = {
|
module.exports = {
|
||||||
presets: [
|
presets: ["@vue/cli-plugin-babel/preset"]
|
||||||
'@vue/cli-plugin-babel/preset'
|
};
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
7103
package-lock.json
generated
7103
package-lock.json
generated
File diff suppressed because it is too large
Load diff
49
package.json
49
package.json
|
@ -1,6 +1,8 @@
|
||||||
{
|
{
|
||||||
"name": "ffc",
|
"name": "ffc",
|
||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
|
"author": "Niko Lockenvitz & Rene-Pascal Fischer",
|
||||||
|
"license": "MIT",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"serve": "vue-cli-service serve",
|
"serve": "vue-cli-service serve",
|
||||||
|
@ -9,44 +11,33 @@
|
||||||
"start": "vue-cli-service serve"
|
"start": "vue-cli-service serve"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"core-js": "^3.6.4",
|
"core-js": "^3.6.5",
|
||||||
"register-service-worker": "^1.7.1",
|
"register-service-worker": "^1.7.1",
|
||||||
"vue": "^2.6.11",
|
"vue": "^2.6.11",
|
||||||
"vue-router": "^3.1.6",
|
"vue-class-component": "^7.2.3",
|
||||||
|
"vue-router": "^3.2.0",
|
||||||
"vuetify": "^2.2.11"
|
"vuetify": "^2.2.11"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@vue/cli-plugin-babel": "~4.3.0",
|
"@typescript-eslint/eslint-plugin": "^2.33.0",
|
||||||
"@vue/cli-plugin-eslint": "~4.3.0",
|
"@typescript-eslint/parser": "^2.33.0",
|
||||||
"@vue/cli-plugin-pwa": "^4.4.1",
|
"@vue/cli-plugin-babel": "^4.4.0",
|
||||||
"@vue/cli-plugin-router": "^4.3.1",
|
"@vue/cli-plugin-eslint": "^4.4.0",
|
||||||
"@vue/cli-service": "~4.3.0",
|
"@vue/cli-plugin-pwa": "^4.4.0",
|
||||||
"babel-eslint": "^10.1.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": "^6.7.2",
|
||||||
|
"eslint-plugin-prettier": "^3.1.3",
|
||||||
"eslint-plugin-vue": "^6.2.2",
|
"eslint-plugin-vue": "^6.2.2",
|
||||||
|
"prettier": "^1.19.1",
|
||||||
"sass": "^1.19.0",
|
"sass": "^1.19.0",
|
||||||
"sass-loader": "^8.0.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",
|
"vue-template-compiler": "^2.6.11",
|
||||||
"vuetify-loader": "^1.3.0"
|
"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"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
<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" />
|
<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">
|
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
|
||||||
<title><%= htmlWebpackPlugin.options.title %></title>
|
<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://fonts.googleapis.com/css?family=Roboto:100,300,400,500,700,900">
|
||||||
|
|
182
src/App.vue
182
src/App.vue
|
@ -26,48 +26,88 @@
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</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 NavigationBar from "./components/layout/NavigationBar.vue";
|
||||||
import CustomDialog from "./components/customdialog/CustomDialog.vue";
|
import CustomDialog from "./components/customdialog/CustomDialog.vue";
|
||||||
import {
|
import {
|
||||||
readFromLocalStorage,
|
readFromLocalStorage,
|
||||||
saveToLocalStorage,
|
saveToLocalStorage,
|
||||||
clearLocalStorage
|
clearLocalStorage
|
||||||
} from "./helpers/localStorageHelper.js";
|
} from "./helpers/localStorageHelper";
|
||||||
import {
|
import { addDecksFromFile, addDecksFromJSON } from "./helpers/addDecksHelper";
|
||||||
addDecksFromFile,
|
|
||||||
addDecksFromJSON
|
|
||||||
} from "./helpers/addDecksHelper.js";
|
|
||||||
|
|
||||||
const DEFAULT_SNACKBAR_TIMEOUT = 2000;
|
const DEFAULT_SNACKBAR_TIMEOUT = 2000;
|
||||||
|
|
||||||
export default {
|
const AppProps = Vue.extend({
|
||||||
props: {
|
props: {
|
||||||
title: String
|
title: String
|
||||||
},
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
@Component({
|
||||||
components: {
|
components: {
|
||||||
NavigationBar,
|
NavigationBar,
|
||||||
CustomDialog
|
CustomDialog
|
||||||
},
|
}
|
||||||
|
})
|
||||||
|
export default class App extends AppProps {
|
||||||
|
propertiesToSyncWithLocalStorage = [{ key: "decks", defaultValue: [] }];
|
||||||
|
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() {
|
created() {
|
||||||
this.$eventHub.$on("deleteSelectedDecks", () => {
|
this.$eventHub.$on("deleteSelectedDecks", () => {
|
||||||
this.decks = this.decks.filter(
|
this.decks = this.decks.filter(deck => !deck.selected);
|
||||||
deck => !deck.selected
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
this.$eventHub.$on("addDecksFromFile", fileContent => {
|
this.$eventHub.$on("addDecksFromFile", (fileContent: string) => {
|
||||||
addDecksFromFile(this, fileContent);
|
addDecksFromFile(this, fileContent);
|
||||||
});
|
});
|
||||||
this.$eventHub.$on("addDecksFromJSON", fileContent => {
|
this.$eventHub.$on("addDecksFromJSON", (fileContent: FFCFile) => {
|
||||||
addDecksFromJSON(this, fileContent);
|
addDecksFromJSON(this, fileContent);
|
||||||
});
|
});
|
||||||
this.$eventHub.$on("snackbarEvent", output => {
|
this.$eventHub.$on("snackbarEvent", (message: string) => {
|
||||||
this.showSnackbar(output);
|
this.showSnackbar(message);
|
||||||
});
|
});
|
||||||
this.$eventHub.$on("clearLocalStorage", () => {
|
this.$eventHub.$on("clearLocalStorage", () => {
|
||||||
clearLocalStorage(this);
|
clearLocalStorage(this);
|
||||||
});
|
});
|
||||||
this.$eventHub.$on("showCustomDialog", options => {
|
this.$eventHub.$on("showCustomDialog", (options: CustomDialogOptions) => {
|
||||||
this.showCustomDialog(options);
|
this.showCustomDialog(options);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -80,74 +120,43 @@ export default {
|
||||||
{ deep: true }
|
{ 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() {
|
mounted() {
|
||||||
readFromLocalStorage(this);
|
readFromLocalStorage(this);
|
||||||
},
|
this.decks.forEach(deck => {
|
||||||
computed: {
|
deck.selected = false;
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
|
||||||
|
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>
|
</script>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
@ -173,10 +182,13 @@ body {
|
||||||
}
|
}
|
||||||
|
|
||||||
/* fix highlighting problems in vuetify */
|
/* 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{
|
.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;
|
opacity: 0;
|
||||||
}
|
}
|
||||||
.theme--dark.v-list-item--active:hover::before, .theme--dark.v-list-item--active::before {
|
.theme--dark.v-list-item--active:hover::before,
|
||||||
|
.theme--dark.v-list-item--active::before {
|
||||||
opacity: 0.24;
|
opacity: 0.24;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
1
src/assets/logo.svg
Normal file
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 |
|
@ -34,65 +34,63 @@
|
||||||
</v-dialog>
|
</v-dialog>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script lang="ts">
|
||||||
export default {
|
import Vue from "vue";
|
||||||
name: "Dialog",
|
import Component from "vue-class-component";
|
||||||
props: {},
|
import { CustomDialogOptions, CustomDialogOptionsButton } from "../../types";
|
||||||
data() {
|
|
||||||
return {
|
@Component
|
||||||
showDialog: false,
|
export default class Dialog extends Vue {
|
||||||
options: {
|
showDialog = false;
|
||||||
title: "",
|
options = {
|
||||||
format: "",
|
title: "",
|
||||||
message: "",
|
format: "",
|
||||||
tableHead: {
|
message: "",
|
||||||
name: "",
|
tableHead: {
|
||||||
value: ""
|
name: "",
|
||||||
},
|
value: ""
|
||||||
table: [
|
|
||||||
{
|
|
||||||
name: "",
|
|
||||||
value: ""
|
|
||||||
}
|
|
||||||
],
|
|
||||||
buttons: [
|
|
||||||
{
|
|
||||||
name: "Close",
|
|
||||||
color: "indigo",
|
|
||||||
callback: null
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
};
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
hide() {
|
|
||||||
this.showDialog = false;
|
|
||||||
},
|
},
|
||||||
show(options) {
|
table: [
|
||||||
this.showDialog = true;
|
{
|
||||||
this.options = options;
|
name: "",
|
||||||
if (
|
value: ""
|
||||||
!this.options.buttons ||
|
|
||||||
(this.options.buttons && this.options.buttons.length === 0)
|
|
||||||
) {
|
|
||||||
this.options.buttons = [
|
|
||||||
{
|
|
||||||
name: "Close",
|
|
||||||
color: "indigo",
|
|
||||||
callback: null
|
|
||||||
}
|
|
||||||
];
|
|
||||||
}
|
}
|
||||||
},
|
],
|
||||||
close(btn) {
|
buttons: [
|
||||||
this.showDialog = false;
|
{
|
||||||
if (btn && btn.callback) {
|
name: "Close",
|
||||||
btn.callback();
|
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>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
|
|
@ -63,79 +63,83 @@
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script lang="ts">
|
||||||
import * as selectedDeckDialogHelper from "../../helpers/selectedDeckDialogHelper.js";
|
import Vue from "vue";
|
||||||
|
import Component from "vue-class-component";
|
||||||
|
|
||||||
export default {
|
import { Deck } from '../../types';
|
||||||
name: "NavigationBar",
|
|
||||||
|
import * as selectedDeckDialogHelper from "../../helpers/selectedDeckDialogHelper";
|
||||||
|
|
||||||
|
const NavigationBarProps = Vue.extend({
|
||||||
props: {
|
props: {
|
||||||
title: String,
|
title: String,
|
||||||
decks: Array,
|
decks: {type: Array as () => Deck[]},
|
||||||
numberOfSelectedDecks: Number,
|
numberOfSelectedDecks: Number,
|
||||||
navBarList: Array
|
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() {
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
});
|
||||||
|
|
||||||
|
@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>
|
</script>
|
||||||
|
|
||||||
<!-- Add "scoped" attribute to limit CSS to this component only -->
|
<!-- Add "scoped" attribute to limit CSS to this component only -->
|
||||||
|
|
|
@ -63,7 +63,7 @@ Morbi tempor quis justo vitae imperdiet.`,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
updateVerticalCentering() {
|
updateVerticalCentering() {
|
||||||
for (let el of document.getElementsByClassName("max-height")) {
|
for (const el of document.getElementsByClassName("max-height")) {
|
||||||
if (el.scrollHeight > el.clientHeight) {
|
if (el.scrollHeight > el.clientHeight) {
|
||||||
el.classList.remove("flex-center");
|
el.classList.remove("flex-center");
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -1,4 +1,14 @@
|
||||||
export function addDecksFromFile(context, fileContent) {
|
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 {
|
try {
|
||||||
addDecksFromJSON(context, JSON.parse(fileContent));
|
addDecksFromJSON(context, JSON.parse(fileContent));
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
@ -6,9 +16,13 @@ export function addDecksFromFile(context, fileContent) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function addDecksFromJSON(context, fileContent) {
|
interface addedDeckAndCards {
|
||||||
// Following decks have been added: d0 (5 cards), d1 (76 cards)...
|
name: string,
|
||||||
const addedDecksAndCards = [];
|
numberOfCards: number,
|
||||||
|
}
|
||||||
|
|
||||||
|
export function addDecksFromJSON(context: Context, fileContent: FFCFile) {
|
||||||
|
const addedDecksAndCards = [] as addedDeckAndCards[];
|
||||||
try {
|
try {
|
||||||
for (const deckShortName in fileContent.decks) {
|
for (const deckShortName in fileContent.decks) {
|
||||||
const cards = [];
|
const cards = [];
|
||||||
|
@ -45,7 +59,7 @@ export function addDecksFromJSON(context, fileContent) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function showAddedDecksConfirmation(context, addedDecksAndCards) {
|
function showAddedDecksConfirmation(context: Context, addedDecksAndCards: addedDeckAndCards[]) {
|
||||||
const numberOfAddedCards = addedDecksAndCards.reduce(
|
const numberOfAddedCards = addedDecksAndCards.reduce(
|
||||||
(total, deck) => total + deck.numberOfCards,
|
(total, deck) => total + deck.numberOfCards,
|
||||||
0
|
0
|
||||||
|
@ -61,7 +75,7 @@ function showAddedDecksConfirmation(context, addedDecksAndCards) {
|
||||||
table: addedDecksAndCards.map((deck) => {
|
table: addedDecksAndCards.map((deck) => {
|
||||||
return {
|
return {
|
||||||
name: deck.name,
|
name: deck.name,
|
||||||
value: deck.numberOfCards,
|
value: String(deck.numberOfCards),
|
||||||
};
|
};
|
||||||
}),
|
}),
|
||||||
buttons: [
|
buttons: [
|
||||||
|
@ -73,6 +87,6 @@ function showAddedDecksConfirmation(context, addedDecksAndCards) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
};
|
} as CustomDialogOptions;
|
||||||
context.showCustomDialog(options);
|
context.showCustomDialog(options);
|
||||||
}
|
}
|
|
@ -1,6 +0,0 @@
|
||||||
export function copyToClipboard() {
|
|
||||||
let ffcURL = document.getElementById("ffc-url");
|
|
||||||
ffcURL.select();
|
|
||||||
ffcURL.setSelectionRange(0, 99999);
|
|
||||||
document.execCommand("copy");
|
|
||||||
}
|
|
8
src/helpers/copyToClipboardHelper.ts
Normal file
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_";
|
const LOCAL_STORAGE_APP_CONTEXT = "ffc_";
|
||||||
|
|
||||||
function get(key) {
|
interface SyncItem {
|
||||||
return localStorage.getItem(LOCAL_STORAGE_APP_CONTEXT + key);
|
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);
|
localStorage.setItem(LOCAL_STORAGE_APP_CONTEXT + key, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
// function remove(key) {
|
function clearAppData(): void {
|
||||||
// localStorage.removeItem(LOCAL_STORAGE_APP_CONTEXT + key);
|
|
||||||
// }
|
|
||||||
|
|
||||||
function clearAppData() {
|
|
||||||
for (let i = 0; i < localStorage.length; i++) {
|
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)) {
|
if (key.startsWith(LOCAL_STORAGE_APP_CONTEXT)) {
|
||||||
localStorage.removeItem(key);
|
localStorage.removeItem(key);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function readFromLocalStorage(context) {
|
export function readFromLocalStorage(context: Context) {
|
||||||
context.propertiesToSyncWithLocalStorage.forEach((item) => {
|
context.propertiesToSyncWithLocalStorage.forEach((item) => {
|
||||||
try {
|
try {
|
||||||
context[item.key] = JSON.parse(get(item.key));
|
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]));
|
set(item.key, JSON.stringify(context[item.key]));
|
||||||
}
|
}
|
||||||
export function clearLocalStorage(context) {
|
export function clearLocalStorage(context: Context) {
|
||||||
clearAppData();
|
clearAppData();
|
||||||
context.propertiesToSyncWithLocalStorage.forEach((item) => {
|
context.propertiesToSyncWithLocalStorage.forEach((item) => {
|
||||||
context[item.key] = item.defaultValue;
|
context[item.key] = item.defaultValue;
|
|
@ -1,4 +1,16 @@
|
||||||
export function deleteSelected(context) {
|
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: Context) {
|
||||||
const options = {
|
const options = {
|
||||||
title: `Delete Deck${context.numberOfSelectedDecks > 1 ? "s" : ""}?`,
|
title: `Delete Deck${context.numberOfSelectedDecks > 1 ? "s" : ""}?`,
|
||||||
message: `Do you really want to delete the ${
|
message: `Do you really want to delete the ${
|
||||||
|
@ -24,10 +36,10 @@ export function deleteSelected(context) {
|
||||||
context.$eventHub.$emit("showCustomDialog", options);
|
context.$eventHub.$emit("showCustomDialog", options);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function showInfoForSelectedDeck(context) {
|
export function showInfoForSelectedDeck(context: Context) {
|
||||||
const selectedDeck = context.decks.find((deck) => deck.selected);
|
const selectedDeck = context.decks.find((deck) => deck.selected);
|
||||||
const options = {
|
const options = {
|
||||||
title: selectedDeck.name,
|
title: selectedDeck?.name,
|
||||||
table: [],
|
table: [],
|
||||||
buttons: [
|
buttons: [
|
||||||
{
|
{
|
||||||
|
@ -35,7 +47,7 @@ export function showInfoForSelectedDeck(context) {
|
||||||
color: "indigo",
|
color: "indigo",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
};
|
} as CustomDialogOptions;
|
||||||
const infos = [
|
const infos = [
|
||||||
{
|
{
|
||||||
meta: "file",
|
meta: "file",
|
||||||
|
@ -58,20 +70,28 @@ export function showInfoForSelectedDeck(context) {
|
||||||
];
|
];
|
||||||
for (const info of infos) {
|
for (const info of infos) {
|
||||||
for (const content of info.content) {
|
for (const content of info.content) {
|
||||||
options.table.push({
|
if (info.meta === "file") {
|
||||||
name: content.name,
|
options.table?.push({
|
||||||
value: selectedDeck.meta[info.meta][content.key] || "-",
|
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({
|
options.table?.push({
|
||||||
name: "Number of Cards",
|
name: "Number of Cards",
|
||||||
value: selectedDeck.cards.length,
|
value: String(selectedDeck?.cards.length || 0),
|
||||||
});
|
});
|
||||||
context.$eventHub.$emit("showCustomDialog", options);
|
context.$eventHub.$emit("showCustomDialog", options);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function quitLearning(context) {
|
|
||||||
|
export function quitLearning(context: Context) {
|
||||||
context.$eventHub.$emit("showCustomDialog", {
|
context.$eventHub.$emit("showCustomDialog", {
|
||||||
title: "Quit Learning?",
|
title: "Quit Learning?",
|
||||||
message:
|
message:
|
||||||
|
@ -91,4 +111,4 @@ export function quitLearning(context) {
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
});
|
});
|
||||||
}
|
}
|
15
src/main.js
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
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
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
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 Vue from "vue";
|
||||||
import VueRouter from 'vue-router'
|
import VueRouter, { RouteConfig } from "vue-router";
|
||||||
|
|
||||||
Vue.use(VueRouter)
|
Vue.use(VueRouter);
|
||||||
|
|
||||||
const routes = [
|
const routes: Array<RouteConfig> = [
|
||||||
{
|
{
|
||||||
path: '/',
|
path: '/',
|
||||||
name: 'DeckSelection',
|
name: 'DeckSelection',
|
||||||
|
@ -32,11 +32,11 @@ const routes = [
|
||||||
name: 'About',
|
name: 'About',
|
||||||
component: () => import('../views/About.vue'),
|
component: () => import('../views/About.vue'),
|
||||||
}
|
}
|
||||||
]
|
];
|
||||||
|
|
||||||
const router = new VueRouter({
|
const router = new VueRouter({
|
||||||
base: process.env.BASE_URL,
|
base: process.env.BASE_URL,
|
||||||
routes
|
routes
|
||||||
})
|
});
|
||||||
|
|
||||||
export default router
|
export default router;
|
13
src/shims-tsx.d.ts
vendored
Normal file
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
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 {
|
export interface Deck {
|
||||||
id: number,
|
id: number;
|
||||||
selected: boolean,
|
selected: boolean;
|
||||||
name: string,
|
name: string;
|
||||||
meta: {
|
meta: {
|
||||||
file: object,
|
file: {
|
||||||
deck: object
|
[x: string]: any;
|
||||||
},
|
};
|
||||||
cards: Card[],
|
deck: {
|
||||||
|
[x: string]: any;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
cards: Card[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Card {
|
export interface Card {
|
||||||
id: number,
|
id: number;
|
||||||
q: string,
|
q: string;
|
||||||
a: string,
|
a: string;
|
||||||
r?: Rating[],
|
r?: Rating[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Rating {
|
export interface Rating {
|
||||||
t: number,
|
t: number;
|
||||||
r: 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;
|
||||||
|
}
|
||||||
|
|
|
@ -23,7 +23,7 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { copyToClipboard } from "../helpers/copyToClipboardHelper.js";
|
import { copyToClipboard } from "../helpers/copyToClipboardHelper";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "About",
|
name: "About",
|
||||||
|
|
47
tsconfig.json
Normal file
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,14 +1,12 @@
|
||||||
module.exports = {
|
module.exports = {
|
||||||
"publicPath": "/ffc/",
|
publicPath: "/ffc/",
|
||||||
"transpileDependencies": [
|
transpileDependencies: ["vuetify"],
|
||||||
"vuetify"
|
|
||||||
],
|
|
||||||
pwa: {
|
pwa: {
|
||||||
themeColor: "#363636",
|
themeColor: "#363636",
|
||||||
msTileColor: "#3F51B5",
|
msTileColor: "#3F51B5",
|
||||||
manifestOptions: {
|
manifestOptions: {
|
||||||
name: "Fancy Flashcard",
|
name: "Fancy Flashcard",
|
||||||
short_name: "FFC",
|
short_name: "FFC",
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
};
|
||||||
|
|
Loading…
Reference in a new issue