Commit 1ed6483d authored by Björn Eyselein's avatar Björn Eyselein
Browse files

Update: Fix Choice-Questions

parent 6908970c
......@@ -223,6 +223,17 @@ function initAll(loadNextFlashcard: (string) => void, checkSolution: () => void)
} else {
nextFlashcardBtn.click();
}
} else if (flashcard.cardType === 'Choice' && canSolve) {
const pressedKey: number = parseInt(event.key);
const choiceParagraph: null | Element = document.querySelector<HTMLDivElement>('#answerDiv')
.querySelectorAll('p.choiceParagraph')
.item(pressedKey - 1);
if (choiceParagraph !== null) {
choiceParagraph.querySelector<HTMLInputElement>('input').click();
}
}
});
}
......
......@@ -22,7 +22,7 @@ function buildChoiceAnswers(choiceAnswers: ChoiceAnswer[]): string {
const choiceInputType: string = choiceAnswers.filter(ca => ca.correct).length > 0 ? 'radio' : 'checkbox';
return shuffleArray(choiceAnswers).map(choiceAnswer => `
<p>
<p class="choiceParagraph">
<label for="choice_${choiceAnswer.answerId}">
<input id="choice_${choiceAnswer.answerId}" name="choice_answers" type="${choiceInputType}" data-choiceid="${choiceAnswer.answerId}">
<span>${choiceAnswer.answer}</span>
......
......@@ -25,7 +25,7 @@ object Corrector {
}
private def correctFlashcard(flashcard: Flashcard, solution: Solution): CorrectionResult = flashcard.cardType match {
case CardType.Vocable | CardType.Text =>
case CardType.Word | CardType.Text =>
val sampleSolution = if (solution.frontToBack) flashcard.back else flashcard.front
val editOperations = Levenshtein.calculateBacktrace(solution.solution, sampleSolution)
CorrectionResult(editOperations.isEmpty, operations = editOperations)
......@@ -52,7 +52,8 @@ object Corrector {
val newTries = if (isCorrect) 0 else 1
(
correctionResult.copy(newTriesCount = newTries),
UserAnsweredFlashcard(user.username, flashcard.cardId, flashcard.collId, flashcard.courseId, bucket = 0, today, isCorrect, wrongTries = newTries, solution.frontToBack)
UserAnsweredFlashcard(user.username, flashcard.cardId, flashcard.collId, flashcard.courseId, flashcard.cardType,
bucket = 0, today, isCorrect, wrongTries = newTries, solution.frontToBack)
)
case Some(oldAnswer) =>
......
......@@ -9,16 +9,18 @@ object Importer {
private val stdFile: File = file"conf/vokabeln.xlsx"
private val cardTypeCellIndex: Int = 0
private val questionCellIndex: Int = 1
private val hintCellIndex : Int = 2
private val cardTypeCellIndex : Int = 0
private val frontCellIndex : Int = 1
private val frontHintCellIndex: Int = frontCellIndex + 1
private val backCellIndex : Int = frontHintCellIndex + 1
private val backHintCellIndex : Int = backCellIndex + 1
private def cardTypeFromString(cardTypeStr: String): Either[String, CardType] = cardTypeStr match {
case "Wort" => Right(CardType.Vocable)
case "Text" => Right(CardType.Text)
case "SC" | "MC" => Right(CardType.Choice)
case "Lücke" => Right(CardType.Blank)
case other => Left(s"Der Kartentype $other kann nicht verstanden werden!")
case "Wort" => Right(CardType.Word)
case "Text" => Right(CardType.Text)
case "Auswahl" => Right(CardType.Choice)
case "Lücke" => Right(CardType.Blank)
case other => Left(s"Der Kartentype $other kann nicht verstanden werden!")
}
private def partitionEitherSeq[T, U](a: Seq[Either[T, U]]): (Seq[T], Seq[U]) =
......@@ -37,7 +39,7 @@ object Importer {
val firstRowWithoutHeaderIndex = sheet.getFirstRowNum + 1
// Ignore header ExcelRow
val readFlashcards = (firstRowWithoutHeaderIndex to sheet.getLastRowNum) flatMap { rowIndex =>
val readFlashcards = firstRowWithoutHeaderIndex.to(sheet.getLastRowNum).flatMap { rowIndex =>
Option(sheet.getRow(rowIndex)) match {
case None => None
case Some(row) => Some(readRow(row, courseId, collId))
......@@ -56,13 +58,15 @@ object Importer {
val lastCellNum = row.getLastCellNum
val maxCellIndex = if (lastCellNum % 2 == 1) lastCellNum + 1 else lastCellNum
val (_, answers): (Seq[String], Seq[ChoiceAnswer]) = partitionEitherSeq((hintCellIndex to maxCellIndex by 2).map { cellIndex =>
readStringCell(row, cellIndex) map { answer =>
val id = (cellIndex - hintCellIndex) / 2
val (_, answers): (Seq[String], Seq[ChoiceAnswer]) = partitionEitherSeq(backCellIndex.to(maxCellIndex).by(2).map { cellIndex =>
val correctnessCellStringValue = row.getCell(cellIndex + 1, MissingCellPolicy.CREATE_NULL_AS_BLANK).getStringCellValue
for {
answer <- readStringCell(row, cellIndex)
correct <- readOptionalStringCell(row, cellIndex + 1).map(_.isDefined)
} yield {
val id = (cellIndex - backCellIndex) / 2
val correctness = if (correctnessCellStringValue.nonEmpty) Correctness.Correct else Correctness.Wrong
val correctness = if (correct) Correctness.Correct else Correctness.Wrong
ChoiceAnswer(id, cardId, collId, courseId, answer, correctness)
}
......@@ -72,22 +76,20 @@ object Importer {
}
private def readTextualRow(
row: ExcelRow, courseId: Int, collId: Int, cardType: CardType, question: String
): Either[String, Flashcard] = for {
questionHint <- readOptionalStringCell(row, hintCellIndex)
meaning <- readStringCell(row, hintCellIndex + 1)
meaningHint <- readOptionalStringCell(row, hintCellIndex + 2)
} yield Flashcard(row.getRowNum, collId, courseId, cardType, question, questionHint, meaning, meaningHint)
private def readTextualRow(row: ExcelRow, courseId: Int, collId: Int, cardType: CardType, question: String): Either[String, Flashcard] = for {
frontHint <- readOptionalStringCell(row, frontHintCellIndex)
back <- readStringCell(row, backCellIndex)
backHint <- readOptionalStringCell(row, backHintCellIndex)
} yield Flashcard(row.getRowNum, collId, courseId, cardType, question, frontHint, back, backHint)
private def readBlankRow(row: ExcelRow, courseId: Int, collId: Int, question: String): Either[String, Flashcard] = {
val cardId = row.getRowNum
val (_, answers): (Seq[String], Seq[BlanksAnswerFragment]) = partitionEitherSeq((hintCellIndex to row.getLastCellNum).map { cellIndex =>
val (_, answers): (Seq[String], Seq[BlanksAnswerFragment]) = partitionEitherSeq(backCellIndex.to(row.getLastCellNum).map { cellIndex =>
readStringCell(row, cellIndex) map {
answer =>
val isAnswer: Boolean = (cellIndex - hintCellIndex) % 2 == 0
BlanksAnswerFragment(cellIndex - hintCellIndex, cardId, collId, courseId, answer, isAnswer)
val isAnswer: Boolean = (cellIndex - backCellIndex) % 2 == 0
BlanksAnswerFragment(cellIndex - backCellIndex, cardId, collId, courseId, answer, isAnswer)
}
})
......@@ -97,14 +99,16 @@ object Importer {
private def readRow(row: ExcelRow, courseId: Int, collId: Int): Either[String, Flashcard] = for {
cardTypeString <- readStringCell(row, cardTypeCellIndex)
cardType <- cardTypeFromString(cardTypeString)
question <- readStringCell(row, questionCellIndex)
front <- readStringCell(row, frontCellIndex)
flashcard <- cardType match {
case CardType.Vocable | CardType.Text => readTextualRow(row, courseId, collId, cardType, question)
case CardType.Blank => readBlankRow(row, courseId, collId, question)
case CardType.Choice => readChoiceRow(row, courseId, collId, question)
case CardType.Word | CardType.Text => readTextualRow(row, courseId, collId, cardType, front)
case CardType.Blank => readBlankRow(row, courseId, collId, front)
case CardType.Choice => readChoiceRow(row, courseId, collId, front)
}
} yield flashcard
// Helper methods
private def readStringCell(row: ExcelRow, index: Int): Either[String, String] = {
val cell = row.getCell(index, MissingCellPolicy.CREATE_NULL_AS_BLANK)
......
......@@ -33,7 +33,7 @@ case object CardType extends PlayEnum[CardType] {
val values: immutable.IndexedSeq[CardType] = findValues
case object Vocable extends CardType
case object Word extends CardType
case object Text extends CardType
......@@ -95,7 +95,8 @@ final case class FlashcardIdentifier(cardId: Int, collId: Int, courseId: Int) {
// User answered flashcard
final case class UserAnsweredFlashcard(username: String, cardId: Int, collId: Int, courseId: Int, bucket: Int, dateAnswered: LocalDate, correct: Boolean, wrongTries: Int, frontToBack: Boolean) {
final case class UserAnsweredFlashcard(username: String, cardId: Int, collId: Int, courseId: Int, cardType: CardType,
bucket: Int, dateAnswered: LocalDate, correct: Boolean, wrongTries: Int, frontToBack: Boolean) {
lazy val isActive: Boolean = dateAnswered.until(LocalDate.now(), ChronoUnit.DAYS) < Math.pow(3, bucket - 1)
......
......@@ -101,7 +101,7 @@ trait CoursesCollectionsFlashcardsTableDefs
def courseId: Rep[Int] = column[Int]("course_id")
def flashcardType: Rep[CardType] = column[CardType]("flash_card_type")
def flashcardType: Rep[CardType] = column[CardType]("card_type")
def question: Rep[String] = column[String](frontName)
......
......@@ -39,6 +39,8 @@ class TableDefs @Inject()(override protected val dbConfigProvider: DatabaseConfi
def courseId: Rep[Int] = column[Int]("course_id")
def cardType: Rep[CardType] = column[CardType]("card_type")
def frontToBack: Rep[Boolean] = column[Boolean]("front_to_back", O.Default(true))
......@@ -58,7 +60,8 @@ class TableDefs @Inject()(override protected val dbConfigProvider: DatabaseConfi
def cardFk: ForeignKeyQuery[FlashcardsTable, DBFlashcard] = foreignKey("uaf_card_fk", (cardId, collId), flashcardsTQ)(fc => (fc.id, fc.collId))
override def * : ProvenShape[UserAnsweredFlashcard] = (username, cardId, collId, courseId, bucket, dateAnswered, correct, wrongTries, frontToBack) <> (UserAnsweredFlashcard.tupled, UserAnsweredFlashcard.unapply)
override def * : ProvenShape[UserAnsweredFlashcard] = (username, cardId, collId, courseId, cardType,
bucket, dateAnswered, correct, wrongTries, frontToBack) <> (UserAnsweredFlashcard.tupled, UserAnsweredFlashcard.unapply)
}
......
......@@ -2,77 +2,87 @@
@(user: User, courseId: Int, collection: Collection, cards: Seq[Flashcard], readErrors: Seq[String])
@title = @{
"Vorschau importierte Karteikarten"
}
@title = @{
"Vorschau importierte Karteikarten"
}
@cardsPerRow = @{
3
}
@cardsPerRow = @{
3
}
@main(title, Some(user)) {
@if(cards.nonEmpty) {
@colWidth = @{
12 / cardsPerRow
}
<div class="row">
<div class="col s12">
<a href="@routes.AdminController.collectionAdmin(courseId, collection.id)" class="btn btn-large waves-effect waves-block @accentColor">
Zurück zur Sammlung
</a>
</div>
@main(title, Some(user)) {
@if(cards.nonEmpty) {
<div class="row">
<div class="col s12">
<a href="@routes.AdminController.collectionAdmin(courseId, collection.id)" class="btn btn-large waves-effect waves-block @accentColor">
Zurück zur Sammlung
</a>
</div>
</div>
@for(completeCardGroup <- cards.grouped(cardsPerRow)) {
<div class="row">
@for(completeCard <- completeCardGroup) {
<div class="col m@{
12 / cardsPerRow
} s12">
<div class="card-panel">
<p>
@{
s"${completeCard.collId}.${completeCard.cardId}"
}: @completeCard.front (<i>@completeCard.frontHint</i>)
</p>
@for(completeCardGroup <- cards.grouped(cardsPerRow)) {
<div class="row">
@for(completeCard <- completeCardGroup) {
<div class="col m@colWidth s12">
<div class="card-panel">
<p><b>ID</b>:
@completeCard.cardId</p>
@completeCard.cardType match {
case CardType.Vocable | CardType.Text => {
<p>Lösung: @completeCard.back (<i>@completeCard.backHint</i>)</p>
}
case CardType.Blank => {
<hr>
<p><b>Vorderseite</b>: @completeCard.front</p>
<p><b>Hinwies Vorderseite:</b>
@completeCard.frontHint.map(fh => Html(s"<i>$fh</i>")).getOrElse("--")</p>
@for(answer <- completeCard.blanksAnswers) {
<p>@answer.answer</p>
}
}
case CardType.Choice => {
<hr>
@for(answerGroup <- completeCard.choiceAnswers.grouped(2)) {
<div class="row">
@for(answer <- answerGroup) {
<div class="col m6 s12">
<b>@if(answer.correctness != model.Correctness.Wrong) {
+
} else {
-
}</b>
@answer.answer
</div>
@completeCard.cardType match {
case CardType.Vocable | CardType.Text => {
<p>
<b>Rückseite</b>: @completeCard.back
</p>
<p>
<b>Hinwies Rückseite:</b>
@completeCard.backHint.map(fh => Html(s"<i>$fh</i>")).getOrElse("--")
</p>
}
case CardType.Blank => {
<hr>
@for(answer <- completeCard.blanksAnswers) {
<p>@answer.answer</p>
}
}
case CardType.Choice => {
<hr>
}
@for(answerGroup <- completeCard.choiceAnswers.grouped(2)) {
<div class="row">
@for(answer <- answerGroup) {
<div class="col m6 s12">
<b>@if(answer.correctness != model.Correctness.Wrong) {
+
} else {
-
}</b>
@answer.answer
</div>
}
</div>
}
}
}
</div>
</div>
}
</div>
}
} else {
<p class="red-text">Konnte keine Karteikarten importieren!</p>
</div>
}
} else {
<p class="red-text">Konnte keine Karteikarten importieren!</p>
}
}
......@@ -64,14 +64,14 @@ create table if not exists collections (
-- FlashCards with answers
create table if not exists flashcards (
id int,
coll_id int,
course_id int,
flash_card_type enum ('Vocable', 'Text', 'Blank', 'Choice') not null default 'Vocable',
front text not null,
front_hint text,
back text not null,
back_hint text,
id int,
coll_id int,
course_id int,
card_type enum ('Vocable', 'Text', 'Blank', 'Choice') not null default 'Vocable',
front text not null,
front_hint text,
back text not null,
back_hint text,
primary key (id, coll_id, course_id),
foreign key (coll_id, course_id) references collections (id, course_id)
......@@ -112,12 +112,13 @@ create table if not exists users_answered_flashcards (
card_id int,
coll_id int,
course_id int,
front_to_back bool not null default true,
card_type enum ('Word', 'Text', 'Blank', 'Choice') not null default 'Word',
front_to_back bool not null default true,
bucket int not null,
date_answered date not null,
correct boolean not null default false,
wrong_tries int not null default 0,
bucket int not null,
date_answered date not null,
correct boolean not null default false,
wrong_tries int not null default 0,
constraint bucket_check check (bucket between 0 and 6),
......@@ -131,20 +132,25 @@ create table if not exists users_answered_flashcards (
-- Views
create view fronts_to_learn as
select id as card_id, fcs.coll_id, fcs.course_id, us.username, true as front_to_back
select id as card_id, fcs.coll_id, fcs.course_id, us.username, true as front_to_back, fcs.card_type
from flashcards fcs
join users us
left join (select *from users_answered_flashcards where front_to_back = true) as uaf
left join (select *
from users_answered_flashcards
where front_to_back = true) as uaf
on us.username = uaf.username and uaf.card_id = fcs.id and uaf.coll_id = fcs.coll_id
where card_id is null;
create view backs_to_learn as
select id as card_id, fcs.coll_id, fcs.course_id, us.username, false as front_to_back
select id as card_id, fcs.coll_id, fcs.course_id, us.username, false as front_to_back, fcs.card_type
from flashcards fcs
join users us
left join (select * from users_answered_flashcards where front_to_back = false) as uaf
left join (select *
from users_answered_flashcards
where front_to_back = false) as uaf
on us.username = uaf.username and uaf.card_id = fcs.id and uaf.coll_id = fcs.coll_id
where card_id is null;
where card_id is null
and (fcs.card_type = 'Text' or fcs.card_type = 'Word');
create view flashcards_to_learn as
(select * from fronts_to_learn)
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment