diff --git a/mosogepsch/build.gradle.kts b/mosogepsch/build.gradle.kts index 46e18fe1f499e789573e9bc993e2e69f959b7abc..2a5fd2eb0392cab44fb1385e22e4db70527f07d3 100644 --- a/mosogepsch/build.gradle.kts +++ b/mosogepsch/build.gradle.kts @@ -39,7 +39,6 @@ kotlin { implementation(compose.runtime) implementation("app.softwork:bootstrap-compose:0.0.49") implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.2.2") - implementation(npm("@js-joda/core", "1.11.0")) implementation(npm("@js-joda/timezone", "2.3.0")) implementation("org.jetbrains.kotlinx:kotlinx-datetime:0.2.1") } diff --git a/mosogepsch/src/jsMain/kotlin/Main.kt b/mosogepsch/src/jsMain/kotlin/Main.kt index e60a5fdd8e64185efb71856eb2f23ac2aefe79e7..be10abf2e4c145f7be516c12522010abb6d1af10 100644 --- a/mosogepsch/src/jsMain/kotlin/Main.kt +++ b/mosogepsch/src/jsMain/kotlin/Main.kt @@ -1,30 +1,27 @@ -import androidx.compose.runtime.CompositionLocalProvider -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.setValue +import androidx.compose.runtime.* import app.softwork.bootstrapcompose.* import app.softwork.bootstrapcompose.Color -import components.CardStyle -import components.content -import components.credits -import components.switch +import components.* import kotlinx.browser.localStorage import kotlinx.browser.window import org.jetbrains.compose.web.css.* import org.jetbrains.compose.web.dom.* import org.jetbrains.compose.web.renderComposable +import org.w3c.dom.HTMLDivElement import org.w3c.dom.get import org.w3c.dom.set import styles.LocalStyle import styles.Style -//@JsModule("@js-joda/timezone") -//@JsNonModule -//external object JsJodaTimeZoneModule - -//private val jsJodaTz = JsJodaTimeZoneModule +@JsModule("@js-joda/timezone") +@JsNonModule +external object JsJodaTimeZoneModule +private val jsJodaTz: dynamic = JsJodaTimeZoneModule fun main() { + val alma: dynamic = window + alma.alma = jsJodaTz + val dark = localStorage["darkMode"]?.equals("true") ?: window.matchMedia("(prefers-color-scheme: dark)").matches @@ -33,14 +30,22 @@ fun main() { val mosogepStyle = Style(darkTheme) Style(mosogepStyle) Style(CardStyle) + Style(FloorStyle) CompositionLocalProvider(LocalStyle provides mosogepStyle) { Header { Navbar( - attrs = { classes("bg-secondary") }, + attrs = { + style { + backgroundColor(mosogepStyle.colors.appBarColor) + } + }, placement = NavbarPlacement.StickyTop, collapseBehavior = NavbarCollapseBehavior.AtBreakpoint(Breakpoint.Large), colorScheme = Color.Dark, ) { + DomSideEffect { + it.setImportantBg(mosogepStyle.colors.appBarColor) + } Brand { Text("MosógépSCH") } switch( label = "Sötét téma", @@ -56,7 +61,7 @@ fun main() { content() } Footer( - attrs = { classes("footer", "mt-auto") } + attrs = { classes("footer", "mt-auto", mosogepStyle.footer) } ) { Container { credits() } } @@ -64,3 +69,8 @@ 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 2f4d59ccae67175ef0c47f3581094ff03e397321..72ecb6bf62d5d48ae8fcb954aa909873f07603a1 100644 --- a/mosogepsch/src/jsMain/kotlin/api/Api.kt +++ b/mosogepsch/src/jsMain/kotlin/api/Api.kt @@ -5,9 +5,14 @@ import kotlinx.serialization.decodeFromString import kotlinx.serialization.json.Json -@OptIn(ExperimentalSerializationApi::class) -fun Api(): Response { - val jsonStr = """ +object Api { + private val json = Json { + ignoreUnknownKeys = true + } + + @OptIn(ExperimentalSerializationApi::class) + fun getOnce(): Response { + val jsonStr = """ { "floors": [ { @@ -184,5 +189,10 @@ fun Api(): Response { ] } """.trimIndent() - return Json.decodeFromString(jsonStr) + val dec = json.decodeFromString<Response>(jsonStr) + val floors = dec.floors.map { floor -> + floor.copy(machines = floor.machines.sortedBy { it.kindOf }) + }.sortedBy { it.id } + return dec.copy(floors = floors) + } } \ No newline at end of file diff --git a/mosogepsch/src/jsMain/kotlin/api/Types.kt b/mosogepsch/src/jsMain/kotlin/api/Types.kt index e1617a2086ad14a38c5a714fa1e71af9f6ecb18e..5fe877e25852d21725b05f3777e73745dfb29a5f 100644 --- a/mosogepsch/src/jsMain/kotlin/api/Types.kt +++ b/mosogepsch/src/jsMain/kotlin/api/Types.kt @@ -1,7 +1,14 @@ package api import kotlinx.datetime.Instant +import kotlinx.serialization.KSerializer +import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable +import kotlinx.serialization.descriptors.PrimitiveKind +import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor +import kotlinx.serialization.descriptors.SerialDescriptor +import kotlinx.serialization.encoding.Decoder +import kotlinx.serialization.encoding.Encoder @Serializable data class Response( @@ -18,12 +25,22 @@ data class Floor( data class Machine( val id: Int, val kindOf: MachineKind, - val status: MachineStatus, - val lastQueryTime: Instant, - val unixTimeStamp: Int, + var status: MachineStatus, + + @SerialName("unixTimeStamp") + @Serializable(with = UnixDateSerializer::class) + var lastQueryTime: Instant, ) @Serializable enum class MachineKind { Dryer, Washer } @Serializable -enum class MachineStatus { NotAvailable, Available } \ No newline at end of file +enum class MachineStatus { NotAvailable, Available } + +object UnixDateSerializer : KSerializer<Instant> { + override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("Instant", PrimitiveKind.LONG) + + override fun serialize(encoder: Encoder, value: Instant) = encoder.encodeLong(value.epochSeconds) + + override fun deserialize(decoder: Decoder): Instant = Instant.fromEpochSeconds(decoder.decodeLong()) +} \ No newline at end of file diff --git a/mosogepsch/src/jsMain/kotlin/components/Card.kt b/mosogepsch/src/jsMain/kotlin/components/Card.kt index f768d773f4a270d25bd1b235ae33da0206a23f96..0bd53f1ba068e654265c89b47c99bfd71e4a8b25 100644 --- a/mosogepsch/src/jsMain/kotlin/components/Card.kt +++ b/mosogepsch/src/jsMain/kotlin/components/Card.kt @@ -1,8 +1,6 @@ package components import androidx.compose.runtime.Composable -import kotlinx.browser.document -import org.jetbrains.compose.web.attributes.AttrsBuilder import org.jetbrains.compose.web.css.* import org.jetbrains.compose.web.dom.AttrBuilderContext import org.jetbrains.compose.web.dom.Div diff --git a/mosogepsch/src/jsMain/kotlin/components/Content.kt b/mosogepsch/src/jsMain/kotlin/components/Content.kt index f36d380b3dbc0ea0654db0754be237ab6565125d..cb91f97dc74f6991a85c676b4b6506cc6ca9576b 100644 --- a/mosogepsch/src/jsMain/kotlin/components/Content.kt +++ b/mosogepsch/src/jsMain/kotlin/components/Content.kt @@ -1,21 +1,25 @@ package components -import androidx.compose.runtime.Composable +import androidx.compose.runtime.* +import api.Api import app.softwork.bootstrapcompose.Container import org.jetbrains.compose.web.css.* @Composable fun content() { + val data by remember { mutableStateOf(Api.getOnce()) } + Container(attrs = { style { display(DisplayStyle.Flex) flexDirection(FlexDirection.Row) flexWrap(FlexWrap.Wrap) justifyContent(JustifyContent.Center) + paddingTop(1.em) } }) { - repeat(50) { - mosogep(it) + data.floors.forEach { + floor(it) } } } \ No newline at end of file diff --git a/mosogepsch/src/jsMain/kotlin/components/Floor.kt b/mosogepsch/src/jsMain/kotlin/components/Floor.kt new file mode 100644 index 0000000000000000000000000000000000000000..2afb63a5ca63ed98930aaf5ec4b0eeb1b7f24108 --- /dev/null +++ b/mosogepsch/src/jsMain/kotlin/components/Floor.kt @@ -0,0 +1,145 @@ +package components + +import styles.LocalStyle +import androidx.compose.runtime.* +import api.Floor +import api.Machine +import api.MachineKind +import api.MachineStatus +import org.jetbrains.compose.web.css.* +import org.jetbrains.compose.web.dom.* + +object FloorStyle: StyleSheet() { + val container by style { + display(DisplayStyle.Flex) + flexDirection(FlexDirection.Column) + justifyContent(JustifyContent.SpaceAround) + alignItems(AlignItems.Center) + position(Position.Relative) + } + + val card by style { + maxWidth(20.em) + width(20.em) + flexDirection(FlexDirection.Row) + justifyContent(JustifyContent.SpaceAround) + } + + val floorNum by style { + minWidth(2.75.ch) + textAlign("right") + } + + val machine by style { + flexGrow(1) + } + + val absCnt by style { + position(Position.Absolute) + left(0.px) + bottom(4.em) + width(0.px) + height(0.px) + property("z-index", -1) + } + val relCnt by style { + position(Position.Relative) + width(0.px) + height(0.px) + } + val underflowCnt by style { + margin(0.px) + padding(0.px) + marginLeft(1.em) + width(20.em) + property("transition", "max-height 0.75s") + overflow("hidden") + } + val underflowCard by style { + flexDirection(FlexDirection.Column) + margin(0.px) + width(100.percent) + paddingTop(5.em) + } +} + +@Composable +fun floor(floor: Floor) { + val colors = LocalStyle.current.colors + fun MachineStatus.toColor(): CSSColorValue = when (this) { + MachineStatus.Available -> colors.offColor + MachineStatus.NotAvailable -> colors.onColor + } + fun MachineKind.toIcon(): String { return "${this.name[0]}" } + + val zIndex = 200 - floor.id*2 + + Div(attrs = { + classes(FloorStyle.container) + style { + property("z-index", zIndex) + } + }) { + var selectedMachine by remember { mutableStateOf<Machine>(floor.machines[0]) } + var machineSelected by remember { mutableStateOf(false) } + + card(attrs = { + classes(FloorStyle.card) + style { + backgroundColor(colors.cardBg) + } + }) { + H3(attrs = { + classes(FloorStyle.floorNum) + }) { Text("${floor.id}.") } + floor.machines.forEach { machine -> + card(attrs = { + classes(FloorStyle.machine) + style { + backgroundColor(machine.status.toColor()) + } + onClick { + if (selectedMachine == machine) { + machineSelected = !machineSelected + } else { + machineSelected = true + } + selectedMachine = machine + } + }) { H3 { Text(machine.kindOf.toIcon()) } } + } + } + + floor.machines.forEach { + Div(attrs = { + classes(FloorStyle.absCnt) + }) { + Div(attrs = { + classes(FloorStyle.relCnt) + }) { + Div(attrs = { + classes(FloorStyle.underflowCnt) + style { + if (machineSelected && selectedMachine == it) { + maxHeight(20.em) + } else { + maxHeight(0.px) + } + } + }) { + card(attrs = { + classes(FloorStyle.underflowCard) + style { + backgroundColor(it.status.toColor()) + } + }) { + P { Text(it.status.name) } + } + } + } + } + } + + } +} + diff --git a/mosogepsch/src/jsMain/kotlin/components/Mosogep.kt b/mosogepsch/src/jsMain/kotlin/components/Mosogep.kt deleted file mode 100644 index 5d1e8ab208a32cbd1301a55acac3390c0bcaec95..0000000000000000000000000000000000000000 --- a/mosogepsch/src/jsMain/kotlin/components/Mosogep.kt +++ /dev/null @@ -1,38 +0,0 @@ -package components - -import styles.LocalStyle -import androidx.compose.runtime.Composable -import app.softwork.bootstrapcompose.Card -import org.jetbrains.compose.web.css.* -import org.jetbrains.compose.web.dom.* - -@Composable -fun mosogep(floor: Int) { - card(attrs = { - style { - maxWidth(20.em) - width(20.em) - flexDirection(FlexDirection.Row) - justifyContent(JustifyContent.SpaceAround) - } - }) { - H3(attrs = { - style { - minWidth(2.75.ch) - textAlign("right") - } - }) { Text("$floor.") } - card(attrs = { - style { - backgroundColor(Color("#ff8a65cc")) - flexGrow(1) - } - }) { H3 { Text("M") } } - card(attrs = { - style { - backgroundColor(Color("#aed581cc")) - flexGrow(1) - } - }) { H3 { Text("Sz") } } - } -} \ 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 26c6a4a2a7e3aa9ee81211193f00f2b71535b8c7..bf32c50bafdf6ba8bdfedcdc5392117a9405a831 100644 --- a/mosogepsch/src/jsMain/kotlin/styles/Style.kt +++ b/mosogepsch/src/jsMain/kotlin/styles/Style.kt @@ -1,11 +1,27 @@ +@file:Suppress("NonAsciiCharacters") package styles import androidx.compose.runtime.compositionLocalOf import org.jetbrains.compose.web.css.* + + +private val kszkék = Color("#3051bf") +private val vilagosKszkék = Color("#3faef7") + + +class Colors(val dark: Boolean) { + val appBarColor = kszkék + val cardBg = if (dark) kszkék else vilagosKszkék + val onColor = Color(if (dark) "#bd5b40" else "#DB6748") + val offColor = Color(if (dark) "#57af00" else "#94DB40") +} + class Style(val dark: Boolean = false): StyleSheet() { private val footHeight = 4.em + val colors = Colors(dark) + init { "html, body, #root" style { position(Position.Relative) @@ -16,12 +32,9 @@ class Style(val dark: Boolean = false): StyleSheet() { marginBottom(0.px) } ".footer" style { - position(Position.Absolute) - bottom(0.px) - width(100.percent) - height(footHeight) - lineHeight(footHeight) - backgroundColor(Color("#0000007f")) + } + "*" { + property("transition", "background-color 0.75s") } } @@ -30,6 +43,15 @@ class Style(val dark: Boolean = false): StyleSheet() { height(100.percent) paddingBottom(footHeight * 2) } + val footer by style { + position(Position.Absolute) + bottom(0.px) + width(100.percent) + height(footHeight) + lineHeight(footHeight) + backgroundColor(Color("#0000007f")) + property("z-index", 2000) + } } val LocalStyle = compositionLocalOf { Style() } \ No newline at end of file