add third party deck support
Co-authored-by: Niko Lockenvitz <nl@nikolockenvitz.de>
This commit is contained in:
parent
0fee66d4e2
commit
e90f795f5e
16 changed files with 228 additions and 5 deletions
|
@ -40,7 +40,7 @@
|
|||
|
||||
## Screenshots
|
||||
|
||||
<img src="docs/img/deck-selection.png" alt="Deck Selection" width="30%" /> <img src="docs/img/deck-selection-selected.png" alt="Deck Selection - one deck selected" width="30%" /> <img src="docs/img/q-and-a.png" alt="Card" width="30%" /> <img src="docs/img/finish.png" alt="Evaluation after finishing learning" width="30%" /> <img src="docs/img/menu.png" alt="Menu" width="30%" /> <img src="docs/img/import.png" alt="Import" width="30%" />
|
||||
<img src="docs/img/deck-selection.png" alt="Deck Selection" width="24%" /> <img src="docs/img/deck-selection-selected.png" alt="Deck Selection - one deck selected" width="24%" /> <img src="docs/img/q-and-a.png" alt="Card" width="24%" /> <img src="docs/img/finish.png" alt="Evaluation after finishing learning" width="24%" /> <img src="docs/img/menu.png" alt="Menu" width="24%" /> <img src="docs/img/import.png" alt="Import" width="24%" /> <img src="docs/img/third-party-decks.png" alt="Third Party Decks" width="24%" /> <img src="docs/img/add-decks.png" alt="Successfully add a Deck" width="24%" />
|
||||
|
||||
## Deployment
|
||||
The app is build and deployed to https://fancy-flashcard.github.io/ffc on every push to master branch (via GitHub Actions and GitHub Pages).
|
||||
|
|
BIN
docs/img/add-decks.png
Normal file
BIN
docs/img/add-decks.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 46 KiB |
Binary file not shown.
Before Width: | Height: | Size: 38 KiB After Width: | Height: | Size: 44 KiB |
Binary file not shown.
Before Width: | Height: | Size: 23 KiB After Width: | Height: | Size: 26 KiB |
BIN
docs/img/third-party-decks.png
Normal file
BIN
docs/img/third-party-decks.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 21 KiB |
|
@ -2,6 +2,7 @@
|
|||
<div class="settings">
|
||||
<v-container fluid>
|
||||
<v-row>
|
||||
<ThirdPartyDeckCard />
|
||||
<ImportDeckFromURL />
|
||||
<ImportDeckFromFile />
|
||||
<DeckCreator />
|
||||
|
@ -16,12 +17,14 @@ import Component from "vue-class-component";
|
|||
|
||||
import ImportDeckFromURL from "./ImportDeckFromURL.vue";
|
||||
import ImportDeckFromFile from "./ImportDeckFromFile.vue";
|
||||
import ThirdPartyDeckCard from "./ThirdPartyDeckCard.vue"
|
||||
import DeckCreator from "./DeckCreator.vue";
|
||||
|
||||
@Component({
|
||||
components: {
|
||||
ImportDeckFromURL,
|
||||
ImportDeckFromFile,
|
||||
ThirdPartyDeckCard,
|
||||
DeckCreator
|
||||
}
|
||||
})
|
||||
|
|
|
@ -28,8 +28,7 @@ import Component from "vue-class-component";
|
|||
|
||||
@Component
|
||||
export default class ImportDeckFromURL extends Vue {
|
||||
chosenURL =
|
||||
"https://raw.githubusercontent.com/fancy-flashcard/ffc/master/cli/test.json";
|
||||
chosenURL = "";
|
||||
fileContent = "";
|
||||
urlRules = [
|
||||
(value: string) =>
|
||||
|
@ -46,7 +45,7 @@ export default class ImportDeckFromURL extends Vue {
|
|||
// TODO: cors?!
|
||||
this.$eventHub.$emit(
|
||||
"snackbarEvent",
|
||||
"An Error Occurred While Loading The File"
|
||||
"An error occurred while loading the file"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
40
src/components/addnewdeck/ThirdPartyDeckCard.vue
Normal file
40
src/components/addnewdeck/ThirdPartyDeckCard.vue
Normal file
|
@ -0,0 +1,40 @@
|
|||
<template>
|
||||
<v-col cols="12" sm="12" md="6" lg="4" xl="4">
|
||||
<v-card height="100%" raised>
|
||||
<v-card-title>Third Party Decks</v-card-title>
|
||||
<v-card-text>
|
||||
Import decks created by others here.
|
||||
You can add decks here as well, just checkout our GitHub repository.
|
||||
</v-card-text>
|
||||
<div class="horizontal-spacer"></div>
|
||||
<v-card-actions class="button-padding">
|
||||
<v-spacer></v-spacer>
|
||||
<v-btn color="indigo" right @click="$router.push('thirdparty')">View Third Party Decks</v-btn>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</v-col>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import Vue from "vue";
|
||||
import Component from "vue-class-component";
|
||||
|
||||
@Component
|
||||
export default class ThirdPartyDecks extends Vue {}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
#file-input-wrapper {
|
||||
padding-bottom: 0;
|
||||
}
|
||||
.button-padding {
|
||||
padding: 16px;
|
||||
}
|
||||
.v-card {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
.horizontal-spacer {
|
||||
flex-grow: 1;
|
||||
}
|
||||
</style>
|
|
@ -1,9 +1,27 @@
|
|||
<template>
|
||||
<v-dialog v-model="showDialog" max-width="400" persistent>
|
||||
<v-card color="#2e2e2e">
|
||||
<v-card-title class="headline">{{ options.title }}</v-card-title>
|
||||
<v-card-title class="headline">
|
||||
{{ options.title }}
|
||||
<v-icon v-if="options.type" size="0.9em" color="indigo" class="mx-2">
|
||||
{{options.type === 'sponsored' ?
|
||||
'mdi-cash-usd-outline' : options.type === 'curated' ?
|
||||
'mdi-check-decagram' : options.type === 'official' ?
|
||||
'mdi-flash-circle' : null}}
|
||||
</v-icon>
|
||||
</v-card-title>
|
||||
|
||||
<v-card-text v-if="options.message" class="text-left">{{ options.message }}</v-card-text>
|
||||
<v-card-text v-else-if="options.multipleMessages" class="text-left">
|
||||
<p
|
||||
class="multiple-messages"
|
||||
v-for="message in options.multipleMessages"
|
||||
:key="message.name"
|
||||
>
|
||||
<b>{{message.name}}:</b>
|
||||
{{message.value}}
|
||||
</p>
|
||||
</v-card-text>
|
||||
|
||||
<v-list v-if="options.table">
|
||||
<v-list-item v-if="options.tableHead">
|
||||
|
@ -121,4 +139,7 @@ export default class Dialog extends Vue {
|
|||
text-align: center;
|
||||
margin: 0 24px;
|
||||
}
|
||||
.multiple-messages {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
</style>
|
|
@ -9,6 +9,11 @@
|
|||
"icon": "mdi-plus",
|
||||
"title": "Add Deck"
|
||||
},
|
||||
{
|
||||
"to": "/thirdparty",
|
||||
"icon": "mdi-package-variant",
|
||||
"title": "Third Party Decks"
|
||||
},
|
||||
{
|
||||
"to": "/settings",
|
||||
"icon": "mdi-cog",
|
||||
|
|
106
src/components/thirdparty/ThirdPartyDeckSelection.vue
vendored
Normal file
106
src/components/thirdparty/ThirdPartyDeckSelection.vue
vendored
Normal file
|
@ -0,0 +1,106 @@
|
|||
<template>
|
||||
<div class="ThirdPartyDeckSelection">
|
||||
<v-subheader>Third Party Decks</v-subheader>
|
||||
<v-list>
|
||||
<v-list-item-group @change="onChange" v-model="deckModel">
|
||||
<v-list-item
|
||||
v-for="(deck, index) in thirdPartyList"
|
||||
:key="index"
|
||||
:value="deck.url"
|
||||
:id="deck.url"
|
||||
>
|
||||
<v-list-item-content>
|
||||
<v-list-item-title>
|
||||
{{ deck.name }}
|
||||
<v-icon size="1em" color="indigo">
|
||||
{{deck.type === 'sponsored' ?
|
||||
'mdi-cash-usd-outline' : deck.type === 'curated' ?
|
||||
'mdi-check-decagram' : deck.type === 'official' ?
|
||||
'mdi-flash-circle' : null}}
|
||||
</v-icon>
|
||||
</v-list-item-title>
|
||||
</v-list-item-content>
|
||||
<v-list-item-icon>
|
||||
<v-btn icon dark @click.stop="onDownload(deck.url)">
|
||||
<v-icon>mdi-download</v-icon>
|
||||
</v-btn>
|
||||
</v-list-item-icon>
|
||||
</v-list-item>
|
||||
</v-list-item-group>
|
||||
</v-list>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import Vue from "vue";
|
||||
import Component from "vue-class-component";
|
||||
import thirdPartyList from "../../../third-party-decks.json";
|
||||
|
||||
import { ThirdPartyDeck } from "../../types";
|
||||
|
||||
@Component
|
||||
export default class ThirdPartyDeckSelection extends Vue {
|
||||
thirdPartyList: ThirdPartyDeck[] = thirdPartyList;
|
||||
deckModel = null;
|
||||
|
||||
onChange() {
|
||||
const currentURL = this.deckModel;
|
||||
const currentDeck = thirdPartyList.find(deck => deck.url === currentURL);
|
||||
|
||||
const options = {
|
||||
title: currentDeck?.name,
|
||||
type: currentDeck?.type,
|
||||
multipleMessages: [
|
||||
{
|
||||
name: "Author",
|
||||
value: currentDeck?.author
|
||||
},
|
||||
{
|
||||
name: "Description",
|
||||
value: currentDeck?.desc
|
||||
},
|
||||
{
|
||||
name: "Type",
|
||||
value: currentDeck?.type
|
||||
}
|
||||
],
|
||||
buttons: [
|
||||
{
|
||||
name: "Close",
|
||||
color: "indigo",
|
||||
callback: () => {
|
||||
this.deckModel = null;
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
this.$eventHub.$emit("showCustomDialog", options);
|
||||
}
|
||||
|
||||
async onDownload(url: string) {
|
||||
try {
|
||||
const response = await fetch(url);
|
||||
const fileContent = await response.json();
|
||||
this.$eventHub.$emit("addDecksFromJSON", fileContent);
|
||||
} catch (error) {
|
||||
this.$eventHub.$emit(
|
||||
"snackbarEvent",
|
||||
"An error occurred while loading the third party deck"
|
||||
);
|
||||
}
|
||||
|
||||
this.deckModel = null;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.v-list-item__icon {
|
||||
margin: 8px;
|
||||
}
|
||||
.theme--dark.v-list-item--active:hover::before,
|
||||
.theme--dark.v-list-item--active::before {
|
||||
opacity: 0;
|
||||
}
|
||||
</style>
|
|
@ -69,6 +69,7 @@ function showAddedDecksConfirmation(context: Context, addedDeckAndCards: addedDe
|
|||
}
|
||||
|
||||
const options = {
|
||||
persistent: false,
|
||||
title: "Successfully Imported Decks",
|
||||
message: "Following decks have been added:",
|
||||
tableHead: { name: "Deck", value: "Number of Cards" },
|
||||
|
@ -81,6 +82,10 @@ function showAddedDecksConfirmation(context: Context, addedDeckAndCards: addedDe
|
|||
buttons: [
|
||||
{
|
||||
name: "Close",
|
||||
color: "grey",
|
||||
},
|
||||
{
|
||||
name: "Go Home",
|
||||
color: "indigo",
|
||||
callback: function() {
|
||||
context.$router.push("/");
|
||||
|
|
|
@ -22,6 +22,11 @@ const routes: Array<RouteConfig> = [
|
|||
component: () => import('../views/AddNewDeck.vue'),
|
||||
props: true,
|
||||
},
|
||||
{
|
||||
path: '/thirdparty',
|
||||
name: 'Third Party Decks',
|
||||
component: () => import('../views/ThirdPartyDecks.vue'),
|
||||
},
|
||||
{
|
||||
path: '/settings',
|
||||
name: 'Settings',
|
||||
|
|
|
@ -119,3 +119,11 @@ export interface NavBarConfigItem {
|
|||
icon: string;
|
||||
title: string;
|
||||
}
|
||||
|
||||
export interface ThirdPartyDeck {
|
||||
type: string;
|
||||
name: string;
|
||||
author: string;
|
||||
desc: string;
|
||||
url: string;
|
||||
}
|
||||
|
|
22
src/views/ThirdPartyDecks.vue
Normal file
22
src/views/ThirdPartyDecks.vue
Normal file
|
@ -0,0 +1,22 @@
|
|||
<template>
|
||||
<div class="thirdpartydecks">
|
||||
<ThirdPartyDeckSelection />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import Vue from "vue";
|
||||
import Component from "vue-class-component";
|
||||
|
||||
import ThirdPartyDeckSelection from "../components/thirdparty/ThirdPartyDeckSelection.vue";
|
||||
|
||||
@Component({
|
||||
components: {
|
||||
ThirdPartyDeckSelection
|
||||
}
|
||||
})
|
||||
export default class ThirdPartyDecks extends Vue {}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
</style>
|
9
third-party-decks.json
Normal file
9
third-party-decks.json
Normal file
|
@ -0,0 +1,9 @@
|
|||
[
|
||||
{
|
||||
"type": "official",
|
||||
"name": "Official Test Deck",
|
||||
"author": "Niko Lockenvitz",
|
||||
"desc": "Example Deck created with FFC CLI",
|
||||
"url": "https://raw.githubusercontent.com/fancy-flashcard/ffc/master/cli/test.json"
|
||||
}
|
||||
]
|
Loading…
Reference in a new issue