Skip to content

Commit

Permalink
Merge pull request #34 from crowdproj/wip-5
Browse files Browse the repository at this point in the history
frontend improvements
  • Loading branch information
sszuev authored Apr 20, 2024
2 parents 0bce150 + 4c259eb commit 1fef31b
Show file tree
Hide file tree
Showing 16 changed files with 241 additions and 87 deletions.
2 changes: 1 addition & 1 deletion app-ktor/src/main/kotlin/api/controllers/CardController.kt
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import com.gitlab.sszuev.flashcards.model.domain.CardOperation
import io.ktor.server.application.ApplicationCall
import kotlinx.datetime.Clock

private val logger: ExtLogger = logger("com.gitlab.sszuev.flashcards.api.controllers.CardControllerKt")
private val logger: ExtLogger = logger("com.gitlab.sszuev.flashcards.api.controllers.CardController")

suspend fun ApplicationCall.getResource(
service: CardService,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import com.gitlab.sszuev.flashcards.model.domain.DictionaryOperation
import io.ktor.server.application.ApplicationCall
import kotlinx.datetime.Clock

private val logger: ExtLogger = logger("com.gitlab.sszuev.flashcards.api.controllers.DictionaryControllerKt")
private val logger: ExtLogger = logger("com.gitlab.sszuev.flashcards.api.controllers.DictionaryController")

suspend fun ApplicationCall.getAllDictionaries(
service: DictionaryService,
Expand Down
2 changes: 2 additions & 0 deletions core/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,13 @@ version = rootProject.version

dependencies {
val junitVersion: String by project
val slf4jVersion: String by project
val kotlinCoroutinesVersion: String by project

implementation(project(":cor-lib"))
implementation(project(":db-common"))
implementation(project(":common"))
implementation("org.slf4j:slf4j-api:$slf4jVersion")

testImplementation("org.junit.jupiter:junit-jupiter-api:$junitVersion")
testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:$junitVersion")
Expand Down
8 changes: 7 additions & 1 deletion core/src/main/kotlin/processes/CardProcessWorkers.kt
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ import com.gitlab.sszuev.flashcards.model.domain.DictionaryEntity
import com.gitlab.sszuev.flashcards.model.domain.DictionaryId
import com.gitlab.sszuev.flashcards.model.domain.TTSResourceGet
import com.gitlab.sszuev.flashcards.model.domain.TTSResourceId
import org.slf4j.LoggerFactory

private val logger = LoggerFactory.getLogger("com.gitlab.sszuev.flashcards.core.processes.CardProcessWorkers")

fun ChainDSL<CardContext>.processGetCard() = worker {
this.name = "process get-card request"
Expand Down Expand Up @@ -182,6 +185,9 @@ fun ChainDSL<CardContext>.processUpdateCard() = worker {
} else if (dictionary.userId != userId) {
this.errors.add(forbiddenEntityDataError(CardOperation.UPDATE_CARD, dictionaryId, userId))
} else {
if (logger.isDebugEnabled) {
logger.debug("Update card request: {}", this.normalizedRequestCardEntity)
}
val res = this.repositories.cardRepository
.updateCard(this.normalizedRequestCardEntity.toDbCard()).toCardEntity()
this.responseCardEntity = postProcess(res) { dictionary }
Expand Down Expand Up @@ -339,7 +345,7 @@ private suspend fun CardContext.postProcess(
val cardAudioId = if (words.size == 1) {
words.single().sound
} else {
val cardAudioString = card.words.joinToString(",") { it.word }
val cardAudioString = card.words.joinToString(",") { it.word.split("|")[0].trim() }
val findResourceIdResponse = tts.findResourceId(TTSResourceGet(cardAudioString, sourceLang).normalize())
this.errors.addAll(findResourceIdResponse.errors)
findResourceIdResponse.id
Expand Down
54 changes: 45 additions & 9 deletions core/src/main/kotlin/processes/SearchCardsHelper.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,32 +4,65 @@ import com.gitlab.sszuev.flashcards.CardContext
import com.gitlab.sszuev.flashcards.core.mappers.toCardEntity
import com.gitlab.sszuev.flashcards.model.domain.CardEntity
import com.gitlab.sszuev.flashcards.model.domain.CardWordEntity
import org.slf4j.LoggerFactory

private val logger = LoggerFactory.getLogger("com.gitlab.sszuev.flashcards.core.processes.SearchCardsHelper")

/**
* recent cards go first
*/
private val comparator: Comparator<CardEntity> = Comparator<CardEntity> { left, right ->
left.changedAt.compareTo(right.changedAt)
}.reversed().thenComparing { left, right ->
val la = left.answered ?: 0
val ra = right.answered ?: 0
la.compareTo(ra)
}.thenComparing { lc, rc ->
lc.changedAt.compareTo(rc.changedAt)
}

/**
* Prepares a card deck for a tutor-session.
*/
internal fun CardContext.findCardDeck(): List<CardEntity> {
if (logger.isDebugEnabled) {
logger.debug("Search cards request: {}", this.normalizedRequestCardFilter)
}
val threshold = config.numberOfRightAnswers
var cards = this.repositories.cardRepository
val foundCards = this.repositories.cardRepository
.findCardsByDictionaryIdIn(this.normalizedRequestCardFilter.dictionaryIds.map { it.asString() })
.filter { !this.normalizedRequestCardFilter.onlyUnknown || (it.answered ?: -1) <= threshold }
.filter { !this.normalizedRequestCardFilter.onlyUnknown || (it.answered ?: -1) < threshold }
.map { it.toCardEntity() }
if (this.normalizedRequestCardFilter.random) {
cards = cards.shuffled()

// prepares the collection so that the resulting list contains cards from different dictionaries
val cardsByDictionary = foundCards.groupBy { it.dictionaryId }.mapValues {
var value = it.value
if (this.normalizedRequestCardFilter.random) {
value = value.shuffled()
}
value = value.sortedWith(comparator)
value.toMutableList()
}.toMutableMap()
val selectedCards = mutableListOf<CardEntity>()
while (cardsByDictionary.isNotEmpty()) {
cardsByDictionary.keys.toSet().forEach { k ->
val v = checkNotNull(cardsByDictionary[k])
if (v.isEmpty()) {
cardsByDictionary.remove(k)
} else {
selectedCards.add(v.removeFirst())
}
}
}
cards = cards.sortedWith(comparator)

if (!this.normalizedRequestCardFilter.random && this.normalizedRequestCardFilter.length > 0) {
return cards.take(this.normalizedRequestCardFilter.length).toList()
val res = selectedCards.take(this.normalizedRequestCardFilter.length).toList()
if (logger.isDebugEnabled) {
logger.debug("Search cards response: {}", res)
}
return res
}
var res = cards.toList()

// prepare card's deck so that it contains non-similar words
var res = selectedCards.toList()
if (this.normalizedRequestCardFilter.length > 0) {
val set = mutableSetOf<CardEntity>()
collectCardDeck(res, set, this.normalizedRequestCardFilter.length)
Expand All @@ -38,6 +71,9 @@ internal fun CardContext.findCardDeck(): List<CardEntity> {
res = res.shuffled()
}
}
if (logger.isDebugEnabled) {
logger.debug("Search cards response: {}", res)
}
return res
}

Expand Down
Binary file modified flashcards-run.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
5 changes: 3 additions & 2 deletions frontend/src/main/resources/static/api.js
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ function getCards(dictionaryId, onDone) {
})
}

function getNextCardDeck(dictionaryIds, length, onDone) {
function getNextCardDeck(dictionaryIds, length, unknown, onDone) {
if (length == null) {
length = numberOfWordsToShow
}
Expand All @@ -92,7 +92,8 @@ function getNextCardDeck(dictionaryIds, length, onDone) {
'requestId': uuid(),
'requestType': searchCardsRequestType,
'random': true,
'length': length
'length': length,
'unknown': unknown
}
post(searchCardsURI, data, function (res) {
if (hasResponseErrors(res)) {
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/main/resources/static/cards.js
Original file line number Diff line number Diff line change
Expand Up @@ -264,7 +264,7 @@ function createResourceCardItem(dialogId, cards) {
setCardFirstWordPartOfSpeech(resCard, $('#' + dialogId + '-card-dialog-part-of-speech option:selected').text());
setCardFirstWordExamplesArray(resCard, toArray($('#' + dialogId + '-card-dialog-examples').val(), '\n'));
setCardFirstWordTranslationsArrayArray(resCard,
toArray($('#' + dialogId + '-card-dialog-translation').val(), '\n').map(x => toArray(x, ',')));
toArray($('#' + dialogId + '-card-dialog-translation').val(), '\n').map(x => toArray(x, '[,;]')));
return resCard;
}

Expand Down
45 changes: 34 additions & 11 deletions frontend/src/main/resources/static/data.js
Original file line number Diff line number Diff line change
Expand Up @@ -287,17 +287,6 @@ function getAllTranslationsAsString(card) {
}).join(', ')
}

/**
* Represents an array of card-resources as a string, containing only words.
* **[TODO] For first word only.**
* For report stage.
* @param cards
* @returns {string}
*/
function getCardsWordsAsString(cards) {
return cards.map(it => it.words[0]).map(it => it.word).sort().join(', ')
}

/**
* Finds translation string from the item that starts with the specified substring ignoring case.
* **[TODO] For first word only.**
Expand All @@ -320,6 +309,18 @@ function getTranslationsAsString(card) {
return getCardFirstWordTranslationsAsArray(card).join(', ')
}

function getTranslationsAsHtml(card) {
return card.words
.map(word => {
return getWordTranslationsAsArray(word)
})
.filter(item => {
return 0 < item.length
})
.map(item => item.join(", "))
.join("<br>")
}

/**
* Represents an item translations as a flat array.
* **[TODO] For first word only.**
Expand All @@ -342,12 +343,34 @@ function getWordTranslationsAsArray(word) {
})
}

function getExamplesAsHtml(card) {
return card.words
.map(word => {
return getWordExamplesAsArray(word)
})
.filter(item => {
return 0 < item.length
})
.map(item => item.join(", "))
.join("<br>")
}

function getWordExamplesAsArray(word) {
return word.examples.map(ex => {
const suffix = ex.translation != null ? ` (${ex.translation})` : "";
return ex.example + suffix
})
}

/**
* Returns learning percentage for card.
* @param cardItem - card resource
* @returns {number} - int percentage
*/
function percentage(cardItem) {
if (!cardItem.answered) {
return 0
}
if (cardItem.answered > numberOfRightAnswers) {
return 100
}
Expand Down
7 changes: 6 additions & 1 deletion frontend/src/main/resources/static/dictionaries.js
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,11 @@ function drawDictionariesPage() {
resetRowSelection(tbody);
}
if (event.shiftKey && lastSelectedRow) {
if (isRowSelected($(this))) {
// to exclude rows from the result set
markRowUnselected($(this))
return
}
// multiple rows selected
const start = lastSelectedRow.index();
const end = $(this).index();
Expand Down Expand Up @@ -205,7 +210,7 @@ function drawRunPage(allDictionaries) {
return [dictionary.dictionaryId, dictionary.name];
}));
resetRowSelection($('#dictionaries tbody'));
getNextCardDeck(Array.from(dictionaryMap.keys()), null, function (cards) {
getNextCardDeck(Array.from(dictionaryMap.keys()), numberOfWordsToShow, true, function (cards) {
flashcards = cards;
$.each(cards, function (index, card) {
card.dictionaryName = dictionaryMap.get(card.dictionaryId);
Expand Down
4 changes: 4 additions & 0 deletions frontend/src/main/resources/static/tables.js
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,10 @@ function markRowSelected(row) {
row.addClass('table-success');
}

function markRowUnselected(row) {
row.removeClass('table-success');
}

function isRowSelected(row) {
return row.hasClass('table-success');
}
Expand Down
Loading

0 comments on commit 1fef31b

Please sign in to comment.