Skip to content
GitLab
Menu
Projects
Groups
Snippets
/
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
Menu
Open sidebar
Björn Eyselein
wuekabel
Commits
1ed6483d
Commit
1ed6483d
authored
May 24, 2019
by
Björn Eyselein
Browse files
Update: Fix Choice-Questions
parent
6908970c
Changes
10
Hide whitespace changes
Inline
Side-by-side
app/assets/scripts/learnRepeat.ts
View file @
1ed6483d
...
...
@@ -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
();
}
}
});
}
...
...
app/assets/scripts/questionMaker.ts
View file @
1ed6483d
...
...
@@ -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>
...
...
app/model/Corrector.scala
View file @
1ed6483d
...
...
@@ -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
)
=>
...
...
app/model/Importer.scala
View file @
1ed6483d
...
...
@@ -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
(
correct
nessCellStringValue
.
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
(
(
hint
CellIndex
to
row
.
getLastCellNum
).
map
{
cellIndex
=>
val
(
_
,
answers
)
:
(
Seq
[
String
],
Seq
[
BlanksAnswerFragment
])
=
partitionEitherSeq
(
back
CellIndex
.
to
(
row
.
getLastCellNum
).
map
{
cellIndex
=>
readStringCell
(
row
,
cellIndex
)
map
{
answer
=>
val
isAnswer
:
Boolean
=
(
cellIndex
-
hint
CellIndex
)
%
2
==
0
BlanksAnswerFragment
(
cellIndex
-
hint
CellIndex
,
cardId
,
collId
,
courseId
,
answer
,
isAnswer
)
val
isAnswer
:
Boolean
=
(
cellIndex
-
back
CellIndex
)
%
2
==
0
BlanksAnswerFragment
(
cellIndex
-
back
CellIndex
,
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
)
questi
on
<-
readStringCell
(
row
,
questi
onCellIndex
)
fr
on
t
<-
readStringCell
(
row
,
fr
on
t
CellIndex
)
flashcard
<-
cardType
match
{
case
CardType
.
Vocable
|
CardType
.
Text
=>
readTextualRow
(
row
,
courseId
,
collId
,
cardType
,
questi
on
)
case
CardType
.
Blank
=>
readBlankRow
(
row
,
courseId
,
collId
,
questi
on
)
case
CardType
.
Choice
=>
readChoiceRow
(
row
,
courseId
,
collId
,
questi
on
)
case
CardType
.
Word
|
CardType
.
Text
=>
readTextualRow
(
row
,
courseId
,
collId
,
cardType
,
fr
on
t
)
case
CardType
.
Blank
=>
readBlankRow
(
row
,
courseId
,
collId
,
fr
on
t
)
case
CardType
.
Choice
=>
readChoiceRow
(
row
,
courseId
,
collId
,
fr
on
t
)
}
}
yield
flashcard
// Helper methods
private
def
readStringCell
(
row
:
ExcelRow
,
index
:
Int
)
:
Either
[
String
,
String
]
=
{
val
cell
=
row
.
getCell
(
index
,
MissingCellPolicy
.
CREATE_NULL_AS_BLANK
)
...
...
app/model/Models.scala
View file @
1ed6483d
...
...
@@ -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
)
...
...
app/model/persistence/CoursesCollectionsFlashcardsTableDefs.scala
View file @
1ed6483d
...
...
@@ -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
)
...
...
app/model/persistence/TableDefs.scala
View file @
1ed6483d
...
...
@@ -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
)
}
...
...
app/views/cardPreview.scala.html
View file @
1ed6483d
...
...
@@ -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>
}
}
conf/evolutions/default/1.sql
View file @
1ed6483d
...
...
@@ -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
)
...
...
other_resources/vokabeln.xlsx
deleted
100644 → 0
View file @
6908970c
File deleted
Write
Preview
Supports
Markdown
0%
Try again
or
attach a new file
.
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment