flashcards in the frontend!!!

This commit is contained in:
2026-06-26 01:25:25 -07:00
parent f83d4d579c
commit cb60ec8480
14 changed files with 1134 additions and 3 deletions
+88
View File
@@ -0,0 +1,88 @@
<template>
<v-row class="fill-height">
<v-col
cols="12"
md="3"
>
<v-card
height="100%"
class="pa-2"
>
<FlashcardDeckTree
ref="deckTree"
@select="onDeckSelected"
/>
</v-card>
</v-col>
<v-col
cols="12"
md="9"
>
<template v-if="selectedDeck">
<div class="d-flex align-center justify-space-between mb-4">
<v-spacer />
<v-btn
color="primary"
variant="tonal"
prepend-icon="mdi-plus"
text="Add Flashcard"
@click="openFlashcardCreateDialog"
/>
<FlashcardDeckStartReviewBtn :flashcard-deck-id="selectedDeck.id" />
</div>
<v-divider class="my-5" />
<v-card :border="true">
<FlashcardsDataTableServer
ref="flashcardsTable"
:where="{ flashcardDeckId: selectedDeck.id }"
/>
</v-card>
<FlashcardCreateDialog
v-if="selectedDeck"
ref="flashcardCreateDialog"
:flashcard-deck-id="selectedDeck.id"
@created="flashcardsTable?.refresh()"
/>
</template>
<div
v-else
class="d-flex align-center justify-center h-100 text-medium-emphasis"
>
Select a deck to view its flashcards
</div>
</v-col>
</v-row>
</template>
<script setup lang="ts">
import { ref } from "vue"
import useBreadcrumbs from "@/use/use-breadcrumbs"
import FlashcardDeckTree, {
type DeckNode,
} from "@/components/flashcard-decks/FlashcardDeckTree.vue"
import FlashcardsDataTableServer from "@/components/flashcards/FlashcardsDataTableServer.vue"
import FlashcardCreateDialog from "@/components/flashcards/FlashcardCreateDialog.vue"
import FlashcardDeckStartReviewBtn from "@/components/flashcard-decks/FlashcardDeckStartReviewBtn.vue"
const deckTree = ref<InstanceType<typeof FlashcardDeckTree> | null>(null)
const selectedDeck = ref<DeckNode | null>(null)
const flashcardsTable = ref<InstanceType<typeof FlashcardsDataTableServer> | null>(null)
const flashcardCreateDialog = ref<InstanceType<typeof FlashcardCreateDialog> | null>(null)
function onDeckSelected(deck: DeckNode) {
selectedDeck.value = deck
}
function openFlashcardCreateDialog() {
flashcardCreateDialog.value?.show()
}
useBreadcrumbs("Study")
</script>
@@ -0,0 +1,118 @@
<template>
<div>
<div class="d-flex">
<v-spacer />
<v-btn
class="mt-2"
variant="tonal"
prepend-icon="mdi-shuffle"
text="Shuffle"
/>
</div>
<v-skeleton-loader
v-if="isLoading"
type="card"
/>
<div v-else-if="isEmpty(flashcards)"></div>
<div
v-else
class="review-layout"
>
<FlashcardReviewCard
:key="currentIndex"
class="mt-5"
:flashcard="flashcards[currentIndex]"
/>
<div class="review-nav">
<v-btn
icon="mdi-chevron-left"
variant="text"
size="x-large"
:disabled="currentIndex === 0"
@click="prev"
/>
<v-btn
class="mr-3"
color="error"
variant="tonal"
prepend-icon="mdi-close"
>
0
</v-btn>
<v-btn
class="ml-3"
color="success"
variant="tonal"
append-icon="mdi-check"
>
0
</v-btn>
<v-btn
icon="mdi-chevron-right"
variant="text"
size="x-large"
:disabled="currentIndex === flashcards.length - 1"
@click="next"
/>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { isEmpty, isNil } from "lodash"
import { computed, ref } from "vue"
import useFlashcardDeck from "@/use/use-flashcard-deck"
import useFlashcards, { FlashcardQueryOptions } from "@/use/use-flashcards"
import FlashcardReviewCard from "@/components/flashcards/FlashcardReviewCard.vue"
const props = defineProps<{ flashcardDeckId: string }>()
const flashcardDeckIdAsNumber = computed(() => parseInt(props.flashcardDeckId))
const { flashcardDeck } = useFlashcardDeck(flashcardDeckIdAsNumber)
const flashcardsQueryOptions = computed<FlashcardQueryOptions>(() => {
return {
where: {
flashcardDeckId: flashcardDeck.value?.id,
},
}
})
const { flashcards, isLoading } = useFlashcards(flashcardsQueryOptions, {
skipWatchIf: () => isNil(flashcardDeck.value),
})
const currentIndex = ref(0)
function next() {
if (currentIndex.value < flashcards.value.length - 1) {
currentIndex.value++
}
}
function prev() {
if (currentIndex.value > 0) {
currentIndex.value--
}
}
</script>
<style scoped>
.review-layout {
display: flex;
flex-direction: column;
align-items: center;
gap: 16px;
}
.review-nav {
display: flex;
align-items: center;
gap: 8px;
}
</style>