From d17f9ba3cf9d2fd6adaf1aae4a78a0b3a13edbb9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mikl=C3=B3s=20T=C3=B3th?= <tothmiklostibor@gmail.com> Date: Sat, 5 Mar 2022 18:41:43 +0100 Subject: [PATCH] Add offline notifier --- mosogepsch/src/jsMain/kotlin/Main.kt | 71 ++------------- mosogepsch/src/jsMain/kotlin/api/Api.kt | 3 +- .../src/jsMain/kotlin/components/AppBar.kt | 90 +++++++++++++++++++ .../src/jsMain/kotlin/components/Content.kt | 36 +++++--- .../src/jsMain/kotlin/components/Error.kt | 48 ++++++++++ .../src/jsMain/kotlin/components/FloorCard.kt | 1 - .../src/jsMain/kotlin/components/Offline.kt | 45 ++++++++++ .../src/jsMain/kotlin/localization/English.kt | 4 + .../jsMain/kotlin/localization/Hungarian.kt | 2 + mosogepsch/src/jsMain/kotlin/styles/Style.kt | 5 +- mosogepsch/src/jsMain/resources/offline.svg | 1 + 11 files changed, 224 insertions(+), 82 deletions(-) create mode 100644 mosogepsch/src/jsMain/kotlin/components/AppBar.kt create mode 100644 mosogepsch/src/jsMain/kotlin/components/Error.kt create mode 100644 mosogepsch/src/jsMain/kotlin/components/Offline.kt create mode 100644 mosogepsch/src/jsMain/resources/offline.svg diff --git a/mosogepsch/src/jsMain/kotlin/Main.kt b/mosogepsch/src/jsMain/kotlin/Main.kt index 0733e18..b9bda91 100644 --- a/mosogepsch/src/jsMain/kotlin/Main.kt +++ b/mosogepsch/src/jsMain/kotlin/Main.kt @@ -35,6 +35,7 @@ fun main() { var mosogepStyle by mutableStateOf(Style(dark)) var lang by mutableStateOf<Localization>(Hungarian()) + var offline by mutableStateOf(false) renderComposable(rootElementId = "root") { Style(mosogepStyle) Style(CardStyle) @@ -43,63 +44,10 @@ fun main() { Style(SpinnerStyle) CompositionLocalProvider(LocalStyle provides mosogepStyle, LocalLang provides lang) { Header { - Navbar( - attrs = { - style { - backgroundColor(mosogepStyle.colors.surface) - color(mosogepStyle.colors.onSurface) - property("box-shadow", "0 0 .7em #00000033") - } - }, - placement = NavbarPlacement.StickyTop, - collapseBehavior = NavbarCollapseBehavior.AtBreakpoint(Breakpoint.Large), - colorScheme = Color.Dark, - ) { - DomSideEffect { - it.setImportantBg(mosogepStyle.colors.surface) - } - Div( - attrs = { - style { - display(DisplayStyle.Flex) - flexDirection(FlexDirection.Row) - gap(.5.em) - alignItems(AlignItems.Center) - } - } - ) { - icon( - src = MachineKind.Washer.toIcon(), - color = mosogepStyle.colors.onSurface, - width = 1.5.em - ) - Text("MosógépSCH") - } - Div( - attrs = { classes(mosogepStyle.options) } - ) { - switch( - label = "Sötét téma", - value = mosogepStyle.dark, - onSet = { - mosogepStyle = Style(it) - localStorage["darkMode"] = "$it" - } - ) - // todo make this a dropdown menu - switch( - label = "magyar", - value = lang is Hungarian, - onSet = { - if (lang is Hungarian) { - lang = English() - } else { - lang = Hungarian() - } - } - ) - } - } + appBar( + setStyle = { mosogepStyle = it }, + setLang = { lang = it }, + ) } Main(attrs = { classes(mosogepStyle.content) @@ -107,7 +55,8 @@ fun main() { backgroundColor(mosogepStyle.colors.background) } }) { - content() + offline(offline) + content() { offline = it } } Footer( attrs = { classes("mt-auto", mosogepStyle.footer) } @@ -116,10 +65,4 @@ fun main() { } } } -} - -@NoLiveLiterals -private fun HTMLDivElement.setImportantBg(color: CSSColorValue) { - val alma: dynamic = parentElement - alma.style.setProperty("background-color", color.toString(), "important") } \ No newline at end of file diff --git a/mosogepsch/src/jsMain/kotlin/api/Api.kt b/mosogepsch/src/jsMain/kotlin/api/Api.kt index 12c605b..56e1775 100644 --- a/mosogepsch/src/jsMain/kotlin/api/Api.kt +++ b/mosogepsch/src/jsMain/kotlin/api/Api.kt @@ -32,9 +32,8 @@ object Api { @OptIn(ExperimentalSerializationApi::class) suspend fun getOnce(): Response { - lastRequestTime = Clock.System.now() - val resp: Response = client.request(url) + lastRequestTime = Clock.System.now() val floors = resp.floors.map { floor -> floor.copy(machines = floor.machines.sortedBy { it.kindOf }) diff --git a/mosogepsch/src/jsMain/kotlin/components/AppBar.kt b/mosogepsch/src/jsMain/kotlin/components/AppBar.kt new file mode 100644 index 0000000..5a4f875 --- /dev/null +++ b/mosogepsch/src/jsMain/kotlin/components/AppBar.kt @@ -0,0 +1,90 @@ +package components + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.NoLiveLiterals +import app.softwork.bootstrapcompose.* +import app.softwork.bootstrapcompose.Color +import kotlinx.browser.localStorage +import localization.English +import localization.Hungarian +import localization.LocalLang +import localization.Localization +import org.jetbrains.compose.web.css.* +import org.jetbrains.compose.web.dom.Div +import org.jetbrains.compose.web.dom.Text +import org.w3c.dom.HTMLDivElement +import org.w3c.dom.set +import styles.LocalStyle +import styles.Style + +@Composable +fun appBar(setStyle: (Style) -> Unit, setLang: (Localization) -> Unit) { + val mosogepStyle = LocalStyle.current + val lang = LocalLang.current + Navbar( + attrs = { + style { + backgroundColor(mosogepStyle.colors.surface) + color(mosogepStyle.colors.onSurface) + property("box-shadow", "0 0 .7em #00000033") + } + }, + placement = NavbarPlacement.StickyTop, + collapseBehavior = NavbarCollapseBehavior.AtBreakpoint(Breakpoint.Large), + colorScheme = Color.Dark, + ) { + DomSideEffect { + it.setImportantBg(mosogepStyle.colors.surface) + } + Div( + attrs = { + style { + display(DisplayStyle.Flex) + flexDirection(FlexDirection.Row) + gap(.5.em) + alignItems(AlignItems.Center) + } + } + ) { + icon( + src = api.MachineKind.Washer.toIcon(), + color = mosogepStyle.colors.onSurface, + width = 1.5.em + ) + Text("MosógépSCH") + + } + Div( + attrs = { classes(mosogepStyle.options) } + ) { + switch( + label = "Sötét téma", + value = mosogepStyle.dark, + onSet = { + setStyle(styles.Style(it)) + localStorage["darkMode"] = "$it" + } + ) + // todo make this a dropdown menu + switch( + label = "magyar", + value = lang is Hungarian, + onSet = { + setLang( + if (lang is Hungarian) { + English() + } else { + Hungarian() + } + ) + } + ) + } + } +} + +@NoLiveLiterals +private fun HTMLDivElement.setImportantBg(color: CSSColorValue) { + val alma: dynamic = parentElement + alma.style.setProperty("background-color", color.toString(), "important") +} \ No newline at end of file diff --git a/mosogepsch/src/jsMain/kotlin/components/Content.kt b/mosogepsch/src/jsMain/kotlin/components/Content.kt index eb303af..a6b77d5 100644 --- a/mosogepsch/src/jsMain/kotlin/components/Content.kt +++ b/mosogepsch/src/jsMain/kotlin/components/Content.kt @@ -9,13 +9,13 @@ import kotlinx.coroutines.delay import kotlinx.coroutines.launch import org.jetbrains.compose.web.css.* import org.jetbrains.compose.web.dom.Div -import org.jetbrains.compose.web.dom.Text @Composable -fun content() { +fun content(offline: (Boolean) -> Unit = {}) { var data by remember { mutableStateOf<Response?>(null) } var machine by remember { mutableStateOf<Machine?>(null) } val subscriptions = remember { mutableStateListOf<Machine>() } + var error by remember { mutableStateOf<Throwable?>(null) } val scope = rememberCoroutineScope() LaunchedEffect(Unit) { @@ -24,14 +24,21 @@ fun content() { while (true) { // todo optimize delay(5000) - data = Api.getOnce() - val machines = data!!.floors.map{ it.machines }.flatten() - val nextMachine = machines.find { it.id == machine?.id } - machine = nextMachine - subscriptions.forEachIndexed { i, machine -> - val newMachine = machines.find { it.id == machine.id } - ?: return@forEachIndexed // skip if not found - subscriptions[i] = newMachine + try { + data = Api.getOnce() + val machines = data!!.floors.map { it.machines }.flatten() + val nextMachine = machines.find { it.id == machine?.id } + machine = nextMachine + subscriptions.forEachIndexed { i, machine -> + val newMachine = machines.find { it.id == machine.id } + ?: return@forEachIndexed // skip if not found + subscriptions[i] = newMachine + } + error = null + offline(false) + } catch (e: dynamic) { + error = if (e is Throwable) e else Error(e.toString()) + if (error?.isNetwork == true) { offline(true) } } } } @@ -44,12 +51,13 @@ fun content() { flexWrap(FlexWrap.Wrap) justifyContent(JustifyContent.Center) paddingTop(1.em) + position(Position.Relative) } }) { - if (data == null ) { - spinner() - } else { - data!!.floors.forEach { + when { + error != null && data == null -> errorScreen(error!!) + data == null -> spinner() + else -> data!!.floors.forEach { floor(it, machine, subscriptions) { m -> machine = m } diff --git a/mosogepsch/src/jsMain/kotlin/components/Error.kt b/mosogepsch/src/jsMain/kotlin/components/Error.kt new file mode 100644 index 0000000..7fda70f --- /dev/null +++ b/mosogepsch/src/jsMain/kotlin/components/Error.kt @@ -0,0 +1,48 @@ +package components + +import androidx.compose.runtime.* +import localization.LocalLang +import org.jetbrains.compose.web.css.* +import org.jetbrains.compose.web.dom.* +import styles.LocalStyle + +external class TypeError: Throwable + +@Composable +fun errorScreen(e: Throwable) { + val colors = LocalStyle.current.colors + val lang = LocalLang.current + console.log(e) + + var clickCnt by remember { mutableStateOf(0) } + + Div( + attrs = { + style { + display(DisplayStyle.Flex) + flexDirection(FlexDirection.Column) + gap(1.em) + color(colors.onBackground) + alignItems(AlignItems.Center) + } + } + ) { + H2(attrs = { + onClick { clickCnt++ } + }) { Text(lang.error) } + if (e.isNetwork) { + H4 { Text(lang.noConn) } + } + Div(attrs = { + style { + if (clickCnt < 5) { + display(DisplayStyle.None) + } + } + }) { + Text(e.toString()) + } + } +} + +val Throwable.isNetwork get() = (cause as? TypeError)?.message?.contains("NetworkError") ?: false \ No newline at end of file diff --git a/mosogepsch/src/jsMain/kotlin/components/FloorCard.kt b/mosogepsch/src/jsMain/kotlin/components/FloorCard.kt index 8b62174..a38dae9 100644 --- a/mosogepsch/src/jsMain/kotlin/components/FloorCard.kt +++ b/mosogepsch/src/jsMain/kotlin/components/FloorCard.kt @@ -18,7 +18,6 @@ object FloorCardStyle: StyleSheet() { padding(0.5.em) bottom(0.px) right(0.px) - property("transition", "${styles.Style.baseTransition}, opacity 0.75s") } val machine by style { position(Position.Relative) diff --git a/mosogepsch/src/jsMain/kotlin/components/Offline.kt b/mosogepsch/src/jsMain/kotlin/components/Offline.kt new file mode 100644 index 0000000..c3ae83d --- /dev/null +++ b/mosogepsch/src/jsMain/kotlin/components/Offline.kt @@ -0,0 +1,45 @@ +package components + +import androidx.compose.runtime.Composable +import localization.LocalLang +import org.jetbrains.compose.web.css.* +import org.jetbrains.compose.web.dom.Div +import org.jetbrains.compose.web.dom.Text +import styles.LocalStyle + +@Composable +fun offline(offline: Boolean) { + val lang = LocalLang.current + val colors = LocalStyle.current.colors + + Div( attrs = { + style { + display(DisplayStyle.Flex) + flexDirection(FlexDirection.Row) + width(100.percent) + alignItems(AlignItems.Center) + justifyContent(JustifyContent.Center) + overflow("clip") + property("transition", "${styles.Style.baseTransition}, max-height 0.75s") + if (!offline) { + maxHeight(0.px) + } else { + maxHeight(5.em) + } + } + }) { + Div(attrs = { + style { + padding(1.em) + display(DisplayStyle.Flex) + flexDirection(FlexDirection.Row) + gap(.5.em) + alignItems(AlignItems.Center) + color(colors.onSurface) + } + }) { + icon("offline.svg", colors.onSurface, 1.25.em) + Text(lang.offline) + } + } +} \ No newline at end of file diff --git a/mosogepsch/src/jsMain/kotlin/localization/English.kt b/mosogepsch/src/jsMain/kotlin/localization/English.kt index f420af1..86a659d 100644 --- a/mosogepsch/src/jsMain/kotlin/localization/English.kt +++ b/mosogepsch/src/jsMain/kotlin/localization/English.kt @@ -35,6 +35,10 @@ abstract class Localization { } return "${diff.inWholeDays} $daysAgo" } + + open val error = "An error occured" + open val noConn = "Cannot load data" + open val offline = "Offline" } class English: Localization() diff --git a/mosogepsch/src/jsMain/kotlin/localization/Hungarian.kt b/mosogepsch/src/jsMain/kotlin/localization/Hungarian.kt index f68ed56..ae38be0 100644 --- a/mosogepsch/src/jsMain/kotlin/localization/Hungarian.kt +++ b/mosogepsch/src/jsMain/kotlin/localization/Hungarian.kt @@ -25,4 +25,6 @@ class Hungarian: Localization() { return "$nevelo $floor. emeleten" } + override val error = "Hiba történt" + override val noConn = "Nem sikerült az adatokat frissíteni" } \ No newline at end of file diff --git a/mosogepsch/src/jsMain/kotlin/styles/Style.kt b/mosogepsch/src/jsMain/kotlin/styles/Style.kt index 84cba8f..1301056 100644 --- a/mosogepsch/src/jsMain/kotlin/styles/Style.kt +++ b/mosogepsch/src/jsMain/kotlin/styles/Style.kt @@ -8,7 +8,7 @@ class Style(val dark: Boolean = false): StyleSheet() { val colors = if (dark) DarkThemeColors else LightThemeColors companion object { - val baseTransition = "background-color 0.75s, color 0.75s, border 0.75s, border-top 0.75s" + val baseTransition = "background-color 0.75s, color 0.75s, border 0.75s, border-top 0.75s, opacity 0.75s" private val footHeight = 4.em } @@ -31,6 +31,9 @@ class Style(val dark: Boolean = false): StyleSheet() { } val content by style { + display(DisplayStyle.Flex) + flexDirection(FlexDirection.Column) + alignItems(AlignItems.FlexStart) flexGrow(1) } val footer by style { diff --git a/mosogepsch/src/jsMain/resources/offline.svg b/mosogepsch/src/jsMain/resources/offline.svg new file mode 100644 index 0000000..691bc1e --- /dev/null +++ b/mosogepsch/src/jsMain/resources/offline.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24" width="24px" fill="#000000"><path d="M0 0h24v24H0V0z" fill="none"/><path d="M24 15c0-2.64-2.05-4.78-4.65-4.96C18.67 6.59 15.64 4 12 4c-1.33 0-2.57.36-3.65.97l1.49 1.49C10.51 6.17 11.23 6 12 6c3.04 0 5.5 2.46 5.5 5.5v.5H19c1.66 0 3 1.34 3 3 0 .99-.48 1.85-1.21 2.4l1.41 1.41c1.09-.92 1.8-2.27 1.8-3.81zM4.41 3.86L3 5.27l2.77 2.77h-.42C2.34 8.36 0 10.91 0 14c0 3.31 2.69 6 6 6h11.73l2 2 1.41-1.41L4.41 3.86zM6 18c-2.21 0-4-1.79-4-4s1.79-4 4-4h1.73l8 8H6z"/></svg> \ No newline at end of file -- GitLab