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

Google Sheets init, big refactor

parent e5cbc2ca
No related branches found
No related tags found
No related merge requests found
Pipeline #15018 failed
Showing
with 5390 additions and 43 deletions
...@@ -4,6 +4,10 @@ ...@@ -4,6 +4,10 @@
<component name="FrameworkDetectionExcludesConfiguration"> <component name="FrameworkDetectionExcludesConfiguration">
<file type="web" url="file://$PROJECT_DIR$" /> <file type="web" url="file://$PROJECT_DIR$" />
</component> </component>
<component name="PWA">
<option name="enabled" value="true" />
<option name="wasEnabledAtLeastOnce" value="true" />
</component>
<component name="ProjectRootManager" version="2" languageLevel="JDK_16_PREVIEW" project-jdk-name="16" project-jdk-type="JavaSDK"> <component name="ProjectRootManager" version="2" languageLevel="JDK_16_PREVIEW" project-jdk-name="16" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/out" /> <output url="file://$PROJECT_DIR$/out" />
</component> </component>
......
...@@ -14,6 +14,15 @@ repositories { ...@@ -14,6 +14,15 @@ repositories {
maven("https://maven.pkg.jetbrains.space/public/p/compose/dev") maven("https://maven.pkg.jetbrains.space/public/p/compose/dev")
} }
tasks.register("fixGapiBindingBug") {
doFirst {
exec {
// TODO, make it cross platform, maybe not exec
commandLine("bash", "-c", "sed -i 's/@file:Suppress(/@file:Suppress(\"NAME_CONTAINS_ILLEGAL_CHARS\", /g' build/externals/kemence/src/*.kt")
}
}
}
kotlin { kotlin {
js(IR) { js(IR) {
browser { browser {
...@@ -26,7 +35,11 @@ kotlin { ...@@ -26,7 +35,11 @@ kotlin {
implementation(compose.web.core) implementation(compose.web.core)
implementation(compose.runtime) implementation(compose.runtime)
implementation("com.github.doyaaaaaken:kotlin-csv-js:1.2.0") implementation("com.github.doyaaaaaken:kotlin-csv-js:1.2.0")
//implementation(npm("@types/gapi", "0.0.41", generateExternals = true))
//implementation(npm("@types/gapi.client.sheets", "4.0.20201029", generateExternals = true))
//implementation(npm("@types/gapi.auth2", "0.0.55", generateExternals = true))
} }
//project.tasks.named("jsGenerateExternalsIntegrated") { finalizedBy("fixGapiBindingBug") }
} }
} }
} }
...@@ -2,3 +2,4 @@ kotlin.code.style=official ...@@ -2,3 +2,4 @@ kotlin.code.style=official
kotlin.mpp.enableGranularSourceSetsMetadata=true kotlin.mpp.enableGranularSourceSetsMetadata=true
kotlin.native.enableDependencyPropagation=false kotlin.native.enableDependencyPropagation=false
kotlin.js.webpack.major.version=4 kotlin.js.webpack.major.version=4
kotlin.js.generate.externals=true
\ 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.*
import androidx.compose.runtime.mutableStateOf import gapi.initGapi
import androidx.compose.runtime.remember import gapi.loadGapi
import androidx.compose.runtime.setValue
import kotlinx.browser.document import kotlinx.browser.document
import kotlinx.coroutines.launch
import org.jetbrains.compose.web.attributes.disabled import org.jetbrains.compose.web.attributes.disabled
import org.jetbrains.compose.web.dom.* import org.jetbrains.compose.web.dom.*
import org.jetbrains.compose.web.renderComposable import org.jetbrains.compose.web.renderComposable
import processing.format
import ui.*
import ui.State
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")
...@@ -21,13 +21,20 @@ fun main() { ...@@ -21,13 +21,20 @@ fun main() {
document.title = document.title.replace("PéK", "PéK \uD83D\uDC68\u200D\uD83C\uDF73") document.title = document.title.replace("PéK", "PéK \uD83D\uDC68\u200D\uD83C\uDF73")
renderComposable(root = rootDiv) { renderComposable(root = rootDiv) {
var google by remember { mutableStateOf(false) }
var state by remember { mutableStateOf<State>(Ready) } var state by remember { mutableStateOf<State>(Ready) }
val scope = rememberCoroutineScope()
scope.launch {
loadGapi()
initGapi()
google = true
}
divFloatRight { divFloatRight {
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-ui.modal", "{ target: '#$magicId' }")
if (state != Ready) if (state != Ready)
disabled() disabled()
}){ }){
...@@ -44,9 +51,35 @@ fun main() { ...@@ -44,9 +51,35 @@ fun main() {
} }
Div({ Div({
id(magicId) id(magicId)
classes("uk-modal") classes("uk-ui.modal")
}) {
csvModal { state = it }
}
}
divFloatRight {
val magicId = "google-magic"
Button({
classes("uk-button", "uk-button-small", "uk-button-success")
attr("data-uk-ui.modal", "{ target: '#$magicId' }")
if (state != Ready || !google)
disabled()
}){
var txt = "..."
val curState = state
if (curState == Ready) {
txt = "Google Sheets 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({
id(magicId)
classes("uk-ui.modal")
}) { }) {
modal { state = it } sheetsModal { state = it }
} }
} }
} }
......
package datasources
import com.github.doyaaaaaken.kotlincsv.dsl.csvReader import com.github.doyaaaaaken.kotlincsv.dsl.csvReader
import pekstuff.trimAsPrinciple
class CSVTable(data: String) { class CSVTable(data: String) {
private val rows = csvReader().readAll(data) private val rows = csvReader().readAll(data)
......
package datasources
import gapi.gapi
import gapi.isSignedIn
import gapi.sheets
import kotlin.coroutines.resume
import kotlin.coroutines.resumeWithException
import kotlin.coroutines.suspendCoroutine
class GSheet private constructor() {
companion object {
suspend fun new(url: String): GSheet {
if (!isSignedIn()) throw Error("You must sign in to get gapi.sheets")
val sheet = GSheet()
sheet.setUrl(url)
return sheet
}
}
private var _url = ""
suspend fun setUrl(value: String){
_url = value
loadSheet()
}
val url: String get() = _url
private suspend fun getSheetFromUrl() = suspendCoroutine<sheets.Spreadsheet> { cont ->
gapi.client.sheets.spreadsheets.get(object: sheets.SpreadsheetGetType {
override var spreadsheetId = url
.removePrefix("https://docs.google.com/spreadsheets/d/")
.split("/")[0]
}).execute {
if (it.status != 200) {
cont.resumeWithException(Error("unable to load sheet: ${it.statusText}"))
return@execute Unit
}
cont.resume(it.result)
}
}
private suspend fun loadSheet() {
sheet = getSheetFromUrl()
}
private lateinit var sheet: sheets.Spreadsheet
}
package gapi
import kotlin.coroutines.resume
import kotlin.coroutines.suspendCoroutine
object authObj: client.AuthType {
override var apiKey: String? = "AIzaSyAllJ7f4_uUKUUhbYg6U6edtJHah94HJow"
override var clientId: String? = "418498906920-jom9gd0his9sr91843qeqekf90h1hocj.apps.googleusercontent.com"
override var scope: String? = "https://www.googleapis.com/auth/spreadsheets.readonly"
override var discoveryDocs: Array<String>? = arrayOf("https://sheets.googleapis.com/\$discovery/rest?version=v4")
}
private val listeners = mutableListOf<(Boolean) -> Unit>()
fun listenForLoginEvents(callback: (Boolean) -> Unit) {
listeners += callback
}
suspend fun loadGapi() = suspendCoroutine<Unit> { cont ->
gapi.load(apiName = "gapi.client:gapi.auth2") { cont.resume(Unit) }
}
private fun handleLoginEvent(isSignedIn: Boolean) {
listeners.forEach {
it(isSignedIn)
}
}
suspend fun initGapi() = suspendCoroutine<Unit> { cont ->
gapi.client.init(authObj).then({
cont.resume(Unit)
gapi.auth2.getAuthInstance().isSignedIn.listen { isSignedIn: Boolean ->
handleLoginEvent(isSignedIn)
println(isSignedIn)
}
logIn = {
gapi.auth2.getAuthInstance().signIn()
}
logOut = {
gapi.auth2.getAuthInstance().signOut()
}
isSignedIn = {
gapi.auth2.getAuthInstance().isSignedIn.get()
}
handleLoginEvent(isSignedIn())
}, { error: Throwable ->
println(error)
})
Unit
}
var logIn: () -> Unit = { throw Error("gapi.getGapi has not been initialized yet") }
var logOut: () -> Unit = { throw Error("gapi.getGapi has not been initialized yet") }
var isSignedIn: () -> Boolean = { false }
\ No newline at end of file
This diff is collapsed.
import kotlinx.atomicfu.AtomicInt package pekstuff
import kotlinx.browser.document import kotlinx.browser.document
import kotlinx.coroutines.delay
import org.w3c.dom.HTMLDivElement import org.w3c.dom.HTMLDivElement
import org.w3c.dom.MutationObserver import org.w3c.dom.MutationObserver
import org.w3c.dom.MutationObserverInit import org.w3c.dom.MutationObserverInit
...@@ -21,7 +21,6 @@ object PekDelay { ...@@ -21,7 +21,6 @@ object PekDelay {
private val observer = MutationObserver { _, _ -> private val observer = MutationObserver { _, _ ->
if (!isUpdating) { if (!isUpdating) {
println("run called")
val conts = continuations.toList() val conts = continuations.toList()
conts.forEach { it.resume(Unit) } conts.forEach { it.resume(Unit) }
continuations.removeAll(conts) continuations.removeAll(conts)
......
package pekstuff
import kotlinx.browser.document import kotlinx.browser.document
import kotlinx.coroutines.* import kotlinx.coroutines.*
import org.w3c.dom.Element import org.w3c.dom.Element
......
package pekstuff
import processing.calcProgress
import kotlinx.browser.document import kotlinx.browser.document
import org.w3c.dom.* import org.w3c.dom.*
import org.w3c.files.Blob import org.w3c.files.Blob
...@@ -90,8 +93,3 @@ suspend fun PekTable.clear( ...@@ -90,8 +93,3 @@ suspend fun PekTable.clear(
} }
} }
} }
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
package processing
import datasources.CSVTable
import pekstuff.PekDelay
import pekstuff.PekTable
import kotlin.math.max import kotlin.math.max
class ProcessingError(private val errors: List<String>): Exception(errors.toString()) { class ProcessingError(private val errors: List<String>): Exception(errors.toString()) {
......
package processing
\ No newline at end of file
package processing
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
package ui
import datasources.CSVTable
import processing.ProcessingError
import androidx.compose.runtime.*
import pekstuff.getFileContents
import kotlinx.browser.document
import kotlinx.browser.window
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import org.jetbrains.compose.web.attributes.InputType
import org.jetbrains.compose.web.attributes.disabled
import org.jetbrains.compose.web.dom.*
import org.w3c.dom.HTMLElement
import processing.process
private object csvModal: ModalType {
override val title = "CSV import"
override val tutorial = "Válassz ki egy CSV filet, ami megfelel a következő követelményeknek:"
override @Composable fun content(
closeId: String,
setState: (State) -> Unit,
hint: String,
setHint: (String) -> Unit
) {
val scope = rememberCoroutineScope()
var csvTable by remember { mutableStateOf<CSVTable?>(null) }
val valassz = "hint: válassz ki fájlt"
Input(type = InputType.File, attrs = {
classes("uk-button", "uk-button-small")
onChange {
if (it.value.isBlank()) {
setHint(valassz)
return@onChange
}
scope.launch {
val content = it.target.getFileContents()
content?.let { csv ->
try {
csvTable = CSVTable(csv)
} catch (e: Exception) {
setHint("hibás CSV fájl (infó a console-ban)")
e.printStackTrace()
}
setHint("")
}
}
}
})
Hr { }
divFloatRight {
Button({
classes("uk-button", "uk-button-primary")
onClick {
scope.launch {
try {
csvTable?.process { setState(ImportInProgress(it)) }
} catch (e: ProcessingError) {
delay(100)
window.alert(e.toString())
}
setState(Ready)
}
(document.getElementById(closeId) as HTMLElement).click()
}
if (hint.isNotBlank()) {
disabled()
}
}) {
Text("Zsa!")
}
}
}
}
@Composable
fun csvModal(setState: (State) -> Unit) {
modal(csvModal, setState)
}
\ No newline at end of file
package ui
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import org.jetbrains.compose.web.dom.Div import org.jetbrains.compose.web.dom.Div
......
package ui
import pekstuff.PekTable
import androidx.compose.runtime.* import androidx.compose.runtime.*
import clear
import kotlinx.browser.document import kotlinx.browser.document
import kotlinx.browser.window
import kotlinx.coroutines.DelicateCoroutinesApi
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import org.jetbrains.compose.web.attributes.InputType
import org.jetbrains.compose.web.attributes.disabled import org.jetbrains.compose.web.attributes.disabled
import org.jetbrains.compose.web.css.background import org.jetbrains.compose.web.css.background
import org.jetbrains.compose.web.css.color import org.jetbrains.compose.web.css.color
import org.jetbrains.compose.web.css.rgb import org.jetbrains.compose.web.css.rgb
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.dom.HTMLElement
import org.w3c.files.get
interface ModalType {
val title: String
val tutorial: String
@Composable fun content(closeId: String, setState: (State) -> Unit, hint: String, setHint: (String) -> Unit)
}
@Composable @Composable
fun modal(setState: (State) -> Unit) { fun modal(type: ModalType, setState: (State) -> Unit) {
Div({ Div({
classes("uk-modal-dialog", "uk-text-justify") classes("uk-ui.modal-dialog", "uk-text-justify")
}) { }) {
val closeId = "kemence-modal-close" val closeId = "kemence${type::class.simpleName}-ui.modal-close"
A(attrs = { A(attrs = {
id(closeId) id(closeId)
classes("uk-modal-close", "uk-close") classes("uk-ui.modal-close", "uk-close")
}) { } }) { }
H2 { H2 {
Text("CSV import") Text(type.title)
} }
H4 { H4 {
Text("Mi is ez?") Text("Mi is ez?")
} }
Text("Válassz ki egy CSV filet, ami megfelel a következő követelményeknek:") Text(type.tutorial)
Ul { Ul {
Li { Text("az első sorában a pontozási elvek szerepelnek") } Li { Text("az első sorában a pontozási elvek szerepelnek") }
} }
...@@ -67,57 +70,34 @@ fun modal(setState: (State) -> Unit) { ...@@ -67,57 +70,34 @@ fun modal(setState: (State) -> Unit) {
} } } }
} }
Hr { } Hr { }
val scope = rememberCoroutineScope() var hint by remember { mutableStateOf("") }
var csvTable by remember { mutableStateOf<CSVTable?>(null) }
val valassz = "hint: válassz ki fájlt" type.content(closeId, setState, hint) { hint = it }
var hint by remember { mutableStateOf(valassz) }
pekClear(closeId) { setState(it) }
Input(type = InputType.File, attrs = {
classes("uk-button", "uk-button-small")
onChange {
if (it.value.isBlank()) {
hint = valassz
return@onChange
}
scope.launch {
val content = it.target.getFileContents()
content?.let { csv ->
try {
csvTable = CSVTable(csv)
} catch (e: Exception) {
hint = "hibás CSV fájl (infó a console-ban)"
e.printStackTrace()
}
hint = ""
}
}
}
})
Hr { }
divFloatRight { divFloatRight {
Button({ Button({
classes("uk-button", "uk-button-primary") classes("uk-button")
onClick { style {
scope.launch { background("#0000")
try { color(rgb(0x55, 0x55, 0x55))
csvTable?.process { setState(ImportInProgress(it)) }
} catch (e: ProcessingError) {
delay(100)
window.alert(e.toString())
}
setState(Ready)
}
(document.getElementById(closeId) as HTMLElement).click()
} }
if (hint.isNotBlank()) {
disabled() disabled()
}
}) { }) {
Text("Zsa!") Text(hint)
} }
} }
Br { }
}
}
@Composable
fun pekClear(closeId: String, setState: (State) -> Unit) {
val scope = rememberCoroutineScope()
divFloatRight { divFloatRight {
Button({ Button({
classes("uk-button", "uk-button-danger") classes("uk-button", "uk-button-danger")
...@@ -132,20 +112,4 @@ fun modal(setState: (State) -> Unit) { ...@@ -132,20 +112,4 @@ fun modal(setState: (State) -> Unit) {
Text("PéKen lévő adatok törlése") Text("PéKen lévő adatok törlése")
} }
} }
divFloatRight {
Button({
classes("uk-button")
style {
background("#0000")
color(rgb(0x55, 0x55, 0x55))
}
disabled()
}) {
Text(hint)
}
}
Br { }
}
} }
\ No newline at end of file
package ui
import androidx.compose.runtime.* import androidx.compose.runtime.*
import org.jetbrains.compose.web.css.maxWidth
import org.jetbrains.compose.web.dom.* import org.jetbrains.compose.web.dom.*
@Composable
fun params() {
var test by mutableStateOf("")
Table({
classes("uk-table")
}) {
inputWithLabel("test", test) { test = it }
}
}
@Composable @Composable
fun inputWithLabel( fun inputWithLabel(
name: String, name: String,
......
package ui
import datasources.GSheet
import androidx.compose.runtime.*
import gapi.isSignedIn
import kotlinx.browser.document
import kotlinx.coroutines.launch
import gapi.listenForLoginEvents
import gapi.logIn
import gapi.logOut
import org.jetbrains.compose.web.attributes.InputType
import org.jetbrains.compose.web.attributes.disabled
import org.jetbrains.compose.web.dom.*
import org.w3c.dom.HTMLElement
private object SheetsModal: ModalType {
override val title = "Google Sheets import"
override val tutorial = "Jelentkezz be, majd illesz be egy linket egy Google Sheetre, ami megfelel a következő követelményeknek:"
override @Composable fun content(
closeId: String,
setState: (State) -> Unit,
hint: String,
setHint: (String) -> Unit
) {
val scope = rememberCoroutineScope()
var signedIn by remember { mutableStateOf(isSignedIn()) }
SideEffect {
listenForLoginEvents { signedIn = it }
}
var sheet by remember { mutableStateOf<GSheet?>(null) }
setHint(
if (!signedIn) "Jelentkezz be"
else if (sheet == null) "Adj meg egy Google Sheet URL-t"
else ""
)
Button({
if (signedIn) {
classes("uk-button", "uk-button-danger")
} else {
classes("uk-button", "uk-button-primary")
}
onClick {
if(signedIn) {
logOut()
} else {
logIn()
}
}
}) {
Text(
if(signedIn) { "Kijelentkezés" } else { "Bejelentkezés" }
)
}
Br {}
val inputId = "kemence-sheet-input"
Label(inputId, {
classes("form-label-required", "uk-margin-right")
}) {
Text("Google Sheet URL")
}
var sheetUrl by remember { mutableStateOf("") }
Input(type = InputType.Text, attrs = {
id(inputId)
classes("uk-input")
onChange { sheetUrl = it.value }
value(sheetUrl)
})
Hr { }
divFloatRight {
Button({
classes("uk-button", "uk-button-primary")
onClick {
scope.launch {
println("zsa")
}
(document.getElementById(closeId) as HTMLElement).click()
}
if (hint.isNotBlank()) {
disabled()
}
}) {
Text("Zsa!")
}
}
}
}
@Composable
fun sheetsModal(setState: (State) -> Unit) {
modal(SheetsModal, setState)
}
\ 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