Skip to content
Snippets Groups Projects
Commit f2428548 authored by Tóth Miklós Tibor's avatar Tóth Miklós Tibor :shrug:
Browse files

CSV import done

parent c1143e85
Branches master
No related tags found
No related merge requests found
...@@ -25,6 +25,7 @@ kotlin { ...@@ -25,6 +25,7 @@ kotlin {
dependencies { dependencies {
implementation(compose.web.core) implementation(compose.web.core)
implementation(compose.runtime) implementation(compose.runtime)
implementation("com.github.doyaaaaaken:kotlin-csv-js:1.2.0")
} }
} }
} }
......
import com.github.doyaaaaaken.kotlincsv.dsl.csvReader
class CSVTable(data: String) {
private val rows = csvReader().readAll(data)
companion object {
private val filterList = listOf(
"szumma", "színes belépő"
)
}
private val principleIndexes = rows[0]
.mapIndexed { i, p -> p.trimAsPrinciple() to i }
.drop(1)
.filter {
filterList.all { filter -> !it.first.lowercase().contains(filter) } &&
it.first.isNotBlank()
}
.toMap()
val principles get() = principleIndexes.keys
private val peopleIndexes = rows
.mapIndexed { i, p -> p[0] to i }
.drop(1)
.filter { it.first.isNotBlank() }
.toMap()
val people: List<String>
get() {
val ret = mutableListOf<String>()
rows.drop(1)
.filter { it[0].isNotEmpty() }
.forEach { ret.add(it[0]) }
return ret
}
operator fun get(person: String, principle: String): Int {
val pers = peopleIndexes[person]!!
val princ = principleIndexes[principle]!!
return rows[pers][princ].toIntOrNull() ?: 0
}
}
\ No newline at end of file
object DataInjector {
@JsName("Test")
var test = ""
@JsName("alma")
var alma = ""
}
operator fun DataInjector.get(key: String): String? {
return DataInjector.asDynamic()[key] as? String
}
operator fun DataInjector.set(key: String, value: String) {
DataInjector.asDynamic()[key] = value
}
\ No newline at end of file
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import kotlinx.browser.document import kotlinx.browser.document
import org.jetbrains.compose.web.css.* import org.jetbrains.compose.web.attributes.disabled
import org.jetbrains.compose.web.css.Color.black
import org.jetbrains.compose.web.css.Color.white
import org.jetbrains.compose.web.dom.* import org.jetbrains.compose.web.dom.*
import org.jetbrains.compose.web.renderComposable import org.jetbrains.compose.web.renderComposable
sealed class State
class ImportInProgress(val progress: Double): State()
object Ready: State()
fun main() { fun main() {
val row = document.querySelector("#content-main > div.uk-clearfix.uk-margin-bottom") val row = document.querySelector("#content-main > div.uk-clearfix.uk-margin-bottom")
?: throw Error("couldn't find button row") ?: throw Error("couldn't find button row")
...@@ -14,20 +18,35 @@ fun main() { ...@@ -14,20 +18,35 @@ fun main() {
rootDiv.id = "kemence-root" rootDiv.id = "kemence-root"
row.appendChild(rootDiv) row.appendChild(rootDiv)
document.title = document.title.replace("PéK", "PéK \uD83D\uDC68\u200D\uD83C\uDF73")
renderComposable(root = rootDiv) { renderComposable(root = rootDiv) {
var state by remember { mutableStateOf<State>(Ready) }
Div({ classes("uk-float-right", "uk-margin-right") }) { Div({ classes("uk-float-right", "uk-margin-right") }) {
val magicId = "csv-magic" val magicId = "csv-magic"
Button({ Button({
classes("uk-button", "uk-button-small", "uk-button-success") classes("uk-button", "uk-button-small", "uk-button-success")
attr("data-uk-modal", "{ target: '#$magicId' }") attr("data-uk-modal", "{ target: '#$magicId' }")
if (state != Ready)
disabled()
}){ }){
Text("CSV import \uD83D\uDC68\u200D\uD83C\uDF73") var txt = "..."
val curState = state
if (curState == Ready) {
txt = "CSV import \uD83D\uDC68\u200D\uD83C\uDF73"
} else if (curState is ImportInProgress) {
val progress = (curState.progress * 100.0).format()
txt = "Importálás ($progress%)"
}
Text(txt)
} }
Div({ Div({
id(magicId) id(magicId)
classes("uk-modal") classes("uk-modal")
}) { }) {
modal() modal { state = it }
} }
} }
} }
......
import androidx.compose.runtime.Composable import androidx.compose.runtime.*
import kotlinx.browser.document
import kotlinx.browser.window
import kotlinx.coroutines.DelicateCoroutinesApi
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import org.jetbrains.compose.web.attributes.InputType
import org.jetbrains.compose.web.dom.* import org.jetbrains.compose.web.dom.*
import org.w3c.dom.HTMLAnchorElement
import org.w3c.dom.HTMLElement
import org.w3c.files.get
@Composable @Composable
fun modal() { fun modal(setState: (State) -> Unit) {
Div({ Div({
classes("uk-modal-dialog", "uk-text-justify") classes("uk-modal-dialog", "uk-text-justify")
}) { }) {
val closeId = "kemence-modal-close"
A(attrs = { A(attrs = {
id(closeId)
classes("uk-modal-close", "uk-close") classes("uk-modal-close", "uk-close")
}) { } }) { }
H2 { H2 {
...@@ -17,14 +28,46 @@ fun modal() { ...@@ -17,14 +28,46 @@ fun modal() {
} }
params() params()
Hr { } Hr { }
Button({ val scope = rememberCoroutineScope()
var csvTable by remember { mutableStateOf<CSVTable?>(null) }
Input(type = InputType.File, attrs = {
classes("uk-button", "uk-button-small") classes("uk-button", "uk-button-small")
}) { onChange {
Text("Fájl tallózása") scope.launch {
val content = it.target.getFileContents()
content?.let { csv ->
csvTable = CSVTable(csv)
}
}
} }
})
Hr { } Hr { }
Button({
classes("uk-button", "uk-button-small", "uk-button-danger")
onClick {
scope.launch {
PekTable.clear { setState(ImportInProgress(it)) }
setState(Ready)
}
(document.getElementById(closeId) as HTMLElement).click()
}
}) {
Text("PéKen lévő adatok törlése")
}
Button({ Button({
classes("uk-button", "uk-button-small", "uk-button-primary") classes("uk-button", "uk-button-small", "uk-button-primary")
onClick {
scope.launch {
try {
csvTable?.process { setState(ImportInProgress(it)) }
} catch (e: ProcessingError) {
window.alert(e.toString())
}
setState(Ready)
}
(document.getElementById(closeId) as HTMLElement).click()
}
}) { }) {
Text("Zsa!") Text("Zsa!")
} }
......
import kotlinx.browser.document
import kotlinx.coroutines.*
import org.w3c.dom.Element
import org.w3c.dom.HTMLInputElement
import org.w3c.dom.HTMLTableElement
fun getPeople() =
document.querySelectorAll("#name-list > li > a")
.toList()
.map { it.textContent!! }
fun getPrinciples() =
document.querySelector("#points-table > tbody > tr")!!
.querySelectorAll("td > div > span.tooltiptext")
.toList()
.map { it.textContent!!.trimAsPrinciple() }
object PekTable {
val people by lazy { getPeople() }
val principles by lazy { getPrinciples() }
private val tableElem = document.querySelector("#points-table") as HTMLTableElement
private val table by lazy {
document.querySelectorAll("#points-table > tbody > tr")
.toList()
.map {
(it as Element)
.querySelectorAll("td > div > input")
.toList()
}
}
operator fun get(person: String, principle: String): Int {
val pers = people.indexOf(person)
val princ = principles.indexOf(principle)
val elem = table[pers][princ] as HTMLInputElement
val txt = elem.value
if (txt.isBlank()) {
return 0
}
return txt.toInt()
}
private val scope = CoroutineScope(Job())
operator fun set(person: String, principle: String, value: Int) {
val pers = people.indexOf(person)
val princ = principles.indexOf(principle)
val elem = table[pers][princ] as HTMLInputElement
elem.style.transition = "background 0.5s ease"
val origBg = elem.style.background
elem.style.background = "#fb8c00"
scope.launch {
delay(1000)
elem.style.background = origBg
}
if (elem.value.toIntOrNull() == value) return
elem.click()
elem.value = value.toString()
elem.trigger()
tableElem.click()
}
}
import kotlinx.coroutines.delay
class ProcessingError(val errors: List<String>): Exception(errors.toString()) {
override fun toString(): String {
return "Hiba történt az importálás közben:\n"+
errors.joinToString("\n")
}
}
suspend fun CSVTable.process(
progress: (Double) -> Unit = {}
) {
progress(0.0)
val principleMappings = principles.map {
it to PekTable.principles.findClosestMatch(it)
}.toMap()
val errors = mutableListOf<String>()
people.forEachIndexed { i, person ->
val pekPerson = PekTable.people.findClosestMatch(person)
if (pekPerson == null) {
errors += "HIBA: Nem található '$person'"
return@forEachIndexed
}
principles.forEachIndexed { j, principle ->
val point = this[person, principle]
val pekPrinciple = principleMappings[principle]!!
PekTable[pekPerson, pekPrinciple] = point
pekDelay()
progress(calcProgress(i, j, people.size, principles.size))
if (person != pekPerson) {
errors += "WARN: assuming '$person' == '$pekPerson'"
}
if (principle != pekPrinciple) {
errors += "WARN: assuming '$principle' == '$pekPrinciple'"
}
}
}
if (errors.size != 0) throw ProcessingError(errors.distinct())
}
fun List<String>.findClosestMatch(
string: String,
): String? {
val parts = string.split(" ", "-")
return this.map {
it to when {
it == string -> Int.MAX_VALUE
else -> parts.count { part -> it.contains(part) }
}
}.filter { it.second != 0 }.maxByOrNull { it.second }?.first
}
\ No newline at end of file
import kotlinx.browser.document
import kotlinx.browser.window
import kotlinx.coroutines.delay
import org.w3c.dom.*
import org.w3c.files.Blob
import org.w3c.files.FileReader
import org.w3c.files.get
import kotlin.coroutines.resume
import kotlin.coroutines.suspendCoroutine
fun String.trimAsPrinciple(): String {
var ret = ""
var parenDepth = 0
var inParen = ""
this.forEach {
when {
it == '(' -> {
parenDepth++
inParen += it
}
it == ')' -> {
parenDepth--
inParen += it
val parenTxt = inParen.trim { char ->
when (char) {
'(', ')' -> true
else -> false
}
}.trim()
if (parenDepth == 0) {
if (
!parenTxt.contains("szumma") &&
!parenTxt.contains("max") &&
parenTxt.contains(Regex("[A-Z]|[a-z]"))
) {
ret += inParen
}
inParen = ""
}
}
parenDepth == 0 -> {
ret += it
}
parenDepth != 0 -> {
inParen += it
}
}
}
return ret.trim()
}
fun NodeList.toList(): List<Node> {
val ret = mutableListOf<Node>()
for (i in 0 until this.length) {
val elem = this[i]
elem?.let {
ret.add(it)
}
}
return ret
}
fun Node.trigger() {
val event = document.createEvent("HTMLEvents")
event.initEvent("change", false, true)
this.dispatchEvent(event)
}
suspend fun HTMLInputElement.getFileContents(): String? = suspendCoroutine { cont ->
files?.get(0)?.let { file ->
val reader = FileReader()
reader.onload = {
val read = it.target as FileReader
cont.resume(read.result)
}
reader.readAsText(file as Blob)
return@suspendCoroutine
}
cont.resume(null)
}
suspend fun PekTable.clear(
progress: (Double) -> Unit = {}
) {
progress(0.0)
people.forEachIndexed { i, person ->
principles.forEachIndexed { j, principle ->
PekTable[person, principle] = 0
pekDelay()
progress(calcProgress(i, j, people.size, principles.size))
}
}
}
private val saveIconElem = document.getElementById("save-icon") as HTMLDivElement
val isUpdating get() = saveIconElem.style.display == "block"
suspend fun pekDelay() {
delay(10)
while (isUpdating) { // még sül
delay(100)
}
}
fun Double.format() = this.asDynamic().toFixed(1) as String
fun calcProgress(i: Int, j: Int, iMax: Int, jMax: Int) =
(i*jMax + j).toDouble() / (iMax*jMax).toDouble()
\ No newline at end of file
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment