diff --git a/mosogepsch/src/jsMain/kotlin/Main.kt b/mosogepsch/src/jsMain/kotlin/Main.kt index 81161f936458879e230e28976760685541618959..b9548d96e7ad1f633928d8f40c86f4a2e73337da 100644 --- a/mosogepsch/src/jsMain/kotlin/Main.kt +++ b/mosogepsch/src/jsMain/kotlin/Main.kt @@ -4,6 +4,8 @@ import app.softwork.bootstrapcompose.Color import components.* import kotlinx.browser.localStorage import kotlinx.browser.window +import localization.Hungarian +import localization.LocalLang import org.jetbrains.compose.web.css.* import org.jetbrains.compose.web.dom.* import org.jetbrains.compose.web.renderComposable @@ -23,11 +25,12 @@ fun main() { ?: window.matchMedia("(prefers-color-scheme: dark)").matches var mosogepStyle by mutableStateOf(Style(dark)) + val lang = Hungarian() renderComposable(rootElementId = "root") { Style(mosogepStyle) Style(CardStyle) Style(FloorStyle) - CompositionLocalProvider(LocalStyle provides mosogepStyle) { + CompositionLocalProvider(LocalStyle provides mosogepStyle, LocalLang provides lang) { Header { Navbar( attrs = { @@ -66,7 +69,7 @@ fun main() { Footer( attrs = { classes("mt-auto", mosogepStyle.footer) } ) { - Container { credits() } + credits() } } } diff --git a/mosogepsch/src/jsMain/kotlin/api/Types.kt b/mosogepsch/src/jsMain/kotlin/api/Types.kt index a8dae54dcb3b345b7e837695fd3ad2cbf7e563dc..effaf42f689649bba57592ca09f0615c053307c6 100644 --- a/mosogepsch/src/jsMain/kotlin/api/Types.kt +++ b/mosogepsch/src/jsMain/kotlin/api/Types.kt @@ -43,5 +43,5 @@ object UnixDateSerializer : KSerializer<Instant> { override fun serialize(encoder: Encoder, value: Instant) = encoder.encodeLong(value.epochSeconds) - override fun deserialize(decoder: Decoder): Instant = Instant.fromEpochSeconds(decoder.decodeLong()) + override fun deserialize(decoder: Decoder): Instant = Instant.fromEpochMilliseconds(decoder.decodeLong()) } \ 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 fdb03083b44fa631d400642a1675825884fb6eb4..c64aa22844ee8d9bf324bda9c77f801eea47482a 100644 --- a/mosogepsch/src/jsMain/kotlin/components/Content.kt +++ b/mosogepsch/src/jsMain/kotlin/components/Content.kt @@ -5,6 +5,7 @@ import api.Api import api.Machine import app.softwork.bootstrapcompose.Container import org.jetbrains.compose.web.css.* +import org.jetbrains.compose.web.dom.Div @Composable fun content() { @@ -17,6 +18,7 @@ fun content() { flexWrap(FlexWrap.Wrap) justifyContent(JustifyContent.Center) paddingTop(1.em) + paddingBottom(12.em) } }) { var machine by remember { mutableStateOf<Machine?>(null) } diff --git a/mosogepsch/src/jsMain/kotlin/components/Credits.kt b/mosogepsch/src/jsMain/kotlin/components/Credits.kt index 20e524ce2f29e9f5948c661c543122c3faacc368..e1874aaeafefd1349e1a9ae337840db5623bbc78 100644 --- a/mosogepsch/src/jsMain/kotlin/components/Credits.kt +++ b/mosogepsch/src/jsMain/kotlin/components/Credits.kt @@ -17,6 +17,7 @@ fun credits() { P(attrs = { style { textAlign("center") + margin(0.px) } }) { Text("Made by KSZK and SEM") diff --git a/mosogepsch/src/jsMain/kotlin/components/Floor.kt b/mosogepsch/src/jsMain/kotlin/components/Floor.kt index f29c3202b81856b4875d6357218f62a0376b71d2..51a0a8770e498ade13d1d8e806f12b51cd22f743 100644 --- a/mosogepsch/src/jsMain/kotlin/components/Floor.kt +++ b/mosogepsch/src/jsMain/kotlin/components/Floor.kt @@ -6,6 +6,9 @@ import api.Floor import api.Machine import api.MachineKind import api.MachineStatus +import localization.LocalLang +import localization.machineKind +import localization.machineStatus import org.jetbrains.compose.web.css.* import org.jetbrains.compose.web.dom.* import styles.ColorPair @@ -30,11 +33,13 @@ object FloorStyle: StyleSheet() { val floorNum by style { minWidth(2.75.ch) + marginBottom(0.px) textAlign("right") } val machine by style { flexGrow(1) + cursor("pointer") } val absCnt by style { @@ -59,18 +64,27 @@ object FloorStyle: StyleSheet() { property("transition", "${styles.Style.baseTransition}, max-height 0.75s") } val underflowCard by style { - flexDirection(FlexDirection.Column) + justifyContent(JustifyContent.SpaceBetween) + flexDirection(FlexDirection.Row) + gap(1.5.em) + padding(1.5.em) margin(0.px) width(100.percent) paddingTop(5.em) property("transition", "${styles.Style.baseTransition}, box-shadow 0.5s") property("box-shadow", "0 0 1em #00000044") } + val infoDiv by style { + display(DisplayStyle.Flex) + flexDirection(FlexDirection.Column) + flexGrow(1) + } } @Composable fun floor(floor: Floor, selectedMachine: Machine?, setMachine: (Machine?)->Unit) { + val lang = LocalLang.current val colors = LocalStyle.current.colors fun MachineStatus.toColor(): ColorPair = when (this) { MachineStatus.Available -> ColorPair(colors.primary, colors.onPrimary) @@ -80,7 +94,10 @@ fun floor(floor: Floor, selectedMachine: Machine?, setMachine: (Machine?)->Unit) MachineStatus.Available -> ColorPair(colors.primaryContainer, colors.onPrimaryContainer) MachineStatus.NotAvailable -> ColorPair(colors.errorContainer, colors.onErrorContainer) } - fun MachineKind.toIcon(): String { return "${this.name[0]}" } + fun MachineKind.toIcon(): String = when (this) { + MachineKind.Washer -> "washer.svg" + MachineKind.Dryer -> "fan.svg" + } val zIndex = 200 - floor.id*2 @@ -113,11 +130,22 @@ fun floor(floor: Floor, selectedMachine: Machine?, setMachine: (Machine?)->Unit) setMachine(machine) } } - }) { H3 { Text(machine.kindOf.toIcon()) } } + }) { + Div( + attrs = { + style { + backgroundColor(machine.status.toColor().fg) + property("mask", "url(/${machine.kindOf.toIcon()}) no-repeat center / contain") + height(2.em) + width(2.em) + } + } + ) {} + } } } - floor.machines.forEach { + floor.machines.forEach { machine -> Div(attrs = { classes(FloorStyle.absCnt) }) { @@ -127,7 +155,7 @@ fun floor(floor: Floor, selectedMachine: Machine?, setMachine: (Machine?)->Unit) Div(attrs = { classes(FloorStyle.underflowCnt) style { - if (selectedMachine == it) { + if (selectedMachine == machine) { maxHeight(20.em) } else { maxHeight(0.px) @@ -137,13 +165,29 @@ fun floor(floor: Floor, selectedMachine: Machine?, setMachine: (Machine?)->Unit) card(attrs = { classes(FloorStyle.underflowCard) style { - applyColorPair(it.status.toUnderColor()) - if (selectedMachine != it) { + applyColorPair(machine.status.toUnderColor()) + if (selectedMachine != machine) { property("box-shadow", "none") } } }) { - P { Text(it.status.name) } + Div( + attrs = { + style { + backgroundColor(machine.status.toUnderColor().fg) + property("mask", "url(/${machine.kindOf.toIcon()}) no-repeat center / contain") + height(2.em) + width(2.em) + } + } + ) {} + Div(attrs = { + classes(FloorStyle.infoDiv) + }) { + H5 { Text("${lang.machineKind(machine.kindOf)} ${lang.floorFormat(floor.id)}") } + P { Text("${lang.lastUpdated} ${lang.formatInstant(machine.lastQueryTime)}") } + P { Text(lang.machineStatus(machine.status)) } + } } } } diff --git a/mosogepsch/src/jsMain/kotlin/components/Switch.kt b/mosogepsch/src/jsMain/kotlin/components/Switch.kt index 7dbd297cf84bca04587dbbfedf62cbc94afb2c6e..b96df8442c3851659ba93e6899e1bc8c7e661ff0 100644 --- a/mosogepsch/src/jsMain/kotlin/components/Switch.kt +++ b/mosogepsch/src/jsMain/kotlin/components/Switch.kt @@ -10,11 +10,13 @@ import org.jetbrains.compose.web.dom.Div import org.jetbrains.compose.web.dom.Label import org.w3c.dom.HTMLLabelElement import org.w3c.dom.Text +import styles.LocalStyle private var maxId = 0 @Composable fun switch(label: String, value: Boolean = false, onSet: (Boolean) -> Unit) { + val colors = LocalStyle.current.colors val id = remember { "mosogep-check-${maxId++}" } Div(attrs = { classes("form-check", "form-switch") @@ -29,6 +31,10 @@ fun switch(label: String, value: Boolean = false, onSet: (Boolean) -> Unit) { classes("form-check-input") id(id) onChange { onSet(it.value) } + style { + backgroundColor(if (value) colors.primary else colors.background) + property("border-color", colors.primary) + } } ) Label( diff --git a/mosogepsch/src/jsMain/kotlin/localization/English.kt b/mosogepsch/src/jsMain/kotlin/localization/English.kt new file mode 100644 index 0000000000000000000000000000000000000000..24d5163b9a2cc1e14134c2e2a4fa47b345cd312a --- /dev/null +++ b/mosogepsch/src/jsMain/kotlin/localization/English.kt @@ -0,0 +1,51 @@ +package localization + +import androidx.compose.runtime.compositionLocalOf +import api.Floor +import api.MachineKind +import api.MachineStatus +import kotlinx.datetime.* +import kotlin.time.Duration + +@OptIn(kotlin.time.ExperimentalTime::class) +abstract class Localization { + open val washer = "Washer" + open val dryer = "Dryer" + + open val available = "Currently available" + open val notAvailable = "Currently in use" + + open val lastUpdated = "Last updated" + open val daysAgo = "days ago" + + protected open fun timeFormat(dateTime: LocalDateTime): String = "at ${dateTime.hour.padded()}:${dateTime.minute.padded()}" + + open fun floorFormat(floor: Int) = "on floor $floor" + + fun formatInstant(instant: Instant): String { + val tz = TimeZone.currentSystemDefault() + val dateTime = instant.toLocalDateTime(tz) + val now = Clock.System.now() + val diff = now - instant + println(diff.toString()) + if (diff < Duration.Companion.days(1)) { + return timeFormat(dateTime) + } + return "${diff.inWholeDays} $daysAgo" + } +} + +class English: Localization() + +fun Localization.machineKind(kind: MachineKind): String = when(kind) { + MachineKind.Washer -> washer + MachineKind.Dryer -> dryer +} +fun Localization.machineStatus(status: MachineStatus): String = when(status) { + MachineStatus.Available -> available + MachineStatus.NotAvailable -> notAvailable +} + +val LocalLang = compositionLocalOf { Hungarian() } + +fun Int.padded() = this.toString().padStart(2, '0') \ No newline at end of file diff --git a/mosogepsch/src/jsMain/kotlin/localization/Hungarian.kt b/mosogepsch/src/jsMain/kotlin/localization/Hungarian.kt new file mode 100644 index 0000000000000000000000000000000000000000..12b632d5dc5bda4a801d9f120f5a28ef75226e6f --- /dev/null +++ b/mosogepsch/src/jsMain/kotlin/localization/Hungarian.kt @@ -0,0 +1,24 @@ +package localization + +import kotlinx.datetime.LocalDateTime + +class Hungarian: Localization() { + override val washer = "Mosógép" + override val dryer = "Szárító" + + override val available = "Jelenleg szabad" + override val notAvailable = "Jelenleg használatban" + + override val lastUpdated = "Utoljára frissítve" + override val daysAgo = "napja" + + override fun timeFormat(dateTime: LocalDateTime): String = "${dateTime.hour.padded()}:${dateTime.minute.padded()}-kor" + override fun floorFormat(floor: Int): String { + val nevelo = when(floor) { + 1, 5 -> "az" + else -> "a" + } + return "$nevelo $floor. emeleten" + } + +} \ No newline at end of file diff --git a/mosogepsch/src/jsMain/kotlin/styles/Color.kt b/mosogepsch/src/jsMain/kotlin/styles/Color.kt index 27fbe0d6482e2175488740b99b83bd733a53b0ac..e2eaee3ddd216bddc374961e49a72e50cc6fb1ce 100644 --- a/mosogepsch/src/jsMain/kotlin/styles/Color.kt +++ b/mosogepsch/src/jsMain/kotlin/styles/Color.kt @@ -4,7 +4,6 @@ import org.jetbrains.compose.web.css.CSSColorValue import org.jetbrains.compose.web.css.Color fun color(hex: Long): CSSColorValue { - println(hex.toString()) val hexStr = hex.toString(16).padStart(6, '0') return Color("#$hexStr") } diff --git a/mosogepsch/src/jsMain/kotlin/styles/Style.kt b/mosogepsch/src/jsMain/kotlin/styles/Style.kt index f18c646d3ea8f761887a5dc287920ac2eda04f55..3e276ca302037f7c6ae5a77bd2a8bf09e82a6516 100644 --- a/mosogepsch/src/jsMain/kotlin/styles/Style.kt +++ b/mosogepsch/src/jsMain/kotlin/styles/Style.kt @@ -3,6 +3,7 @@ package styles import androidx.compose.runtime.compositionLocalOf import org.jetbrains.compose.web.css.* +import org.jetbrains.compose.web.css.keywords.auto class Style(val dark: Boolean = false): StyleSheet() { val colors = if (dark) DarkThemeColors else LightThemeColors @@ -16,10 +17,11 @@ class Style(val dark: Boolean = false): StyleSheet() { "html, body, #root" { position(Position.Relative) height(100.percent) - overflowY("hidden") + display(DisplayStyle.Flex) + flexDirection(FlexDirection.Column) } - "h1, h2, h3, h4, h5, h6" { - marginBottom(0.px) + "#root" { + overflowY("auto") } "*" { property("transition", baseTransition) @@ -27,18 +29,15 @@ class Style(val dark: Boolean = false): StyleSheet() { } val content by style { - overflow("auto") - height(100.percent) - paddingBottom(footHeight * 2) + flexGrow(1) } val footer by style { - position(Position.Absolute) - bottom(0.px) width(100.percent) height(footHeight) lineHeight(footHeight) backgroundColor(colors.surface) color(colors.onSurface) + flexGrow(0) property("z-index", 2000) } } diff --git a/mosogepsch/src/jsMain/resources/fan.svg b/mosogepsch/src/jsMain/resources/fan.svg new file mode 100644 index 0000000000000000000000000000000000000000..748d5ab88a92e78ebc8a7854db744ebd54de854e --- /dev/null +++ b/mosogepsch/src/jsMain/resources/fan.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><!-- Font Awesome Pro 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) --><path d="M352.57 128c-28.09 0-54.09 4.52-77.06 12.86l12.41-123.11C289 7.31 279.81-1.18 269.33.13 189.63 10.13 128 77.64 128 159.43c0 28.09 4.52 54.09 12.86 77.06L17.75 224.08C7.31 223-1.18 232.19.13 242.67c10 79.7 77.51 141.33 159.3 141.33 28.09 0 54.09-4.52 77.06-12.86l-12.41 123.11c-1.05 10.43 8.11 18.93 18.59 17.62 79.7-10 141.33-77.51 141.33-159.3 0-28.09-4.52-54.09-12.86-77.06l123.11 12.41c10.44 1.05 18.93-8.11 17.62-18.59-10-79.7-77.51-141.33-159.3-141.33zM256 288a32 32 0 1 1 32-32 32 32 0 0 1-32 32z"/></svg> \ No newline at end of file diff --git a/mosogepsch/src/jsMain/resources/washer.svg b/mosogepsch/src/jsMain/resources/washer.svg new file mode 100644 index 0000000000000000000000000000000000000000..c5bb6d64cbe3552cb48fcf61a790aa6addd90227 --- /dev/null +++ b/mosogepsch/src/jsMain/resources/washer.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 0h24v24H0z" fill="none"/><path d="M9.17 16.83c1.56 1.56 4.1 1.56 5.66 0 1.56-1.56 1.56-4.1 0-5.66l-5.66 5.66zM18 2.01L6 2c-1.11 0-2 .89-2 2v16c0 1.11.89 2 2 2h12c1.11 0 2-.89 2-2V4c0-1.11-.89-1.99-2-1.99zM10 4c.55 0 1 .45 1 1s-.45 1-1 1-1-.45-1-1 .45-1 1-1zM7 4c.55 0 1 .45 1 1s-.45 1-1 1-1-.45-1-1 .45-1 1-1zm5 16c-3.31 0-6-2.69-6-6s2.69-6 6-6 6 2.69 6 6-2.69 6-6 6z"/></svg> \ No newline at end of file