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

Web UI done

parent 8a009b1f
No related branches found
No related tags found
No related merge requests found
Showing
with 151 additions and 20 deletions
...@@ -4,6 +4,8 @@ import app.softwork.bootstrapcompose.Color ...@@ -4,6 +4,8 @@ import app.softwork.bootstrapcompose.Color
import components.* import components.*
import kotlinx.browser.localStorage import kotlinx.browser.localStorage
import kotlinx.browser.window import kotlinx.browser.window
import localization.Hungarian
import localization.LocalLang
import org.jetbrains.compose.web.css.* import org.jetbrains.compose.web.css.*
import org.jetbrains.compose.web.dom.* import org.jetbrains.compose.web.dom.*
import org.jetbrains.compose.web.renderComposable import org.jetbrains.compose.web.renderComposable
...@@ -23,11 +25,12 @@ fun main() { ...@@ -23,11 +25,12 @@ fun main() {
?: window.matchMedia("(prefers-color-scheme: dark)").matches ?: window.matchMedia("(prefers-color-scheme: dark)").matches
var mosogepStyle by mutableStateOf(Style(dark)) var mosogepStyle by mutableStateOf(Style(dark))
val lang = Hungarian()
renderComposable(rootElementId = "root") { renderComposable(rootElementId = "root") {
Style(mosogepStyle) Style(mosogepStyle)
Style(CardStyle) Style(CardStyle)
Style(FloorStyle) Style(FloorStyle)
CompositionLocalProvider(LocalStyle provides mosogepStyle) { CompositionLocalProvider(LocalStyle provides mosogepStyle, LocalLang provides lang) {
Header { Header {
Navbar( Navbar(
attrs = { attrs = {
...@@ -66,7 +69,7 @@ fun main() { ...@@ -66,7 +69,7 @@ fun main() {
Footer( Footer(
attrs = { classes("mt-auto", mosogepStyle.footer) } attrs = { classes("mt-auto", mosogepStyle.footer) }
) { ) {
Container { credits() } credits()
} }
} }
} }
......
...@@ -43,5 +43,5 @@ object UnixDateSerializer : KSerializer<Instant> { ...@@ -43,5 +43,5 @@ object UnixDateSerializer : KSerializer<Instant> {
override fun serialize(encoder: Encoder, value: Instant) = encoder.encodeLong(value.epochSeconds) 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
...@@ -5,6 +5,7 @@ import api.Api ...@@ -5,6 +5,7 @@ import api.Api
import api.Machine import api.Machine
import app.softwork.bootstrapcompose.Container import app.softwork.bootstrapcompose.Container
import org.jetbrains.compose.web.css.* import org.jetbrains.compose.web.css.*
import org.jetbrains.compose.web.dom.Div
@Composable @Composable
fun content() { fun content() {
...@@ -17,6 +18,7 @@ fun content() { ...@@ -17,6 +18,7 @@ fun content() {
flexWrap(FlexWrap.Wrap) flexWrap(FlexWrap.Wrap)
justifyContent(JustifyContent.Center) justifyContent(JustifyContent.Center)
paddingTop(1.em) paddingTop(1.em)
paddingBottom(12.em)
} }
}) { }) {
var machine by remember { mutableStateOf<Machine?>(null) } var machine by remember { mutableStateOf<Machine?>(null) }
......
...@@ -17,6 +17,7 @@ fun credits() { ...@@ -17,6 +17,7 @@ fun credits() {
P(attrs = { P(attrs = {
style { style {
textAlign("center") textAlign("center")
margin(0.px)
} }
}) { }) {
Text("Made by KSZK and SEM") Text("Made by KSZK and SEM")
......
...@@ -6,6 +6,9 @@ import api.Floor ...@@ -6,6 +6,9 @@ import api.Floor
import api.Machine import api.Machine
import api.MachineKind import api.MachineKind
import api.MachineStatus import api.MachineStatus
import localization.LocalLang
import localization.machineKind
import localization.machineStatus
import org.jetbrains.compose.web.css.* import org.jetbrains.compose.web.css.*
import org.jetbrains.compose.web.dom.* import org.jetbrains.compose.web.dom.*
import styles.ColorPair import styles.ColorPair
...@@ -30,11 +33,13 @@ object FloorStyle: StyleSheet() { ...@@ -30,11 +33,13 @@ object FloorStyle: StyleSheet() {
val floorNum by style { val floorNum by style {
minWidth(2.75.ch) minWidth(2.75.ch)
marginBottom(0.px)
textAlign("right") textAlign("right")
} }
val machine by style { val machine by style {
flexGrow(1) flexGrow(1)
cursor("pointer")
} }
val absCnt by style { val absCnt by style {
...@@ -59,18 +64,27 @@ object FloorStyle: StyleSheet() { ...@@ -59,18 +64,27 @@ object FloorStyle: StyleSheet() {
property("transition", "${styles.Style.baseTransition}, max-height 0.75s") property("transition", "${styles.Style.baseTransition}, max-height 0.75s")
} }
val underflowCard by style { val underflowCard by style {
flexDirection(FlexDirection.Column) justifyContent(JustifyContent.SpaceBetween)
flexDirection(FlexDirection.Row)
gap(1.5.em)
padding(1.5.em)
margin(0.px) margin(0.px)
width(100.percent) width(100.percent)
paddingTop(5.em) paddingTop(5.em)
property("transition", "${styles.Style.baseTransition}, box-shadow 0.5s") property("transition", "${styles.Style.baseTransition}, box-shadow 0.5s")
property("box-shadow", "0 0 1em #00000044") property("box-shadow", "0 0 1em #00000044")
} }
val infoDiv by style {
display(DisplayStyle.Flex)
flexDirection(FlexDirection.Column)
flexGrow(1)
}
} }
@Composable @Composable
fun floor(floor: Floor, selectedMachine: Machine?, setMachine: (Machine?)->Unit) { fun floor(floor: Floor, selectedMachine: Machine?, setMachine: (Machine?)->Unit) {
val lang = LocalLang.current
val colors = LocalStyle.current.colors val colors = LocalStyle.current.colors
fun MachineStatus.toColor(): ColorPair = when (this) { fun MachineStatus.toColor(): ColorPair = when (this) {
MachineStatus.Available -> ColorPair(colors.primary, colors.onPrimary) MachineStatus.Available -> ColorPair(colors.primary, colors.onPrimary)
...@@ -80,7 +94,10 @@ fun floor(floor: Floor, selectedMachine: Machine?, setMachine: (Machine?)->Unit) ...@@ -80,7 +94,10 @@ fun floor(floor: Floor, selectedMachine: Machine?, setMachine: (Machine?)->Unit)
MachineStatus.Available -> ColorPair(colors.primaryContainer, colors.onPrimaryContainer) MachineStatus.Available -> ColorPair(colors.primaryContainer, colors.onPrimaryContainer)
MachineStatus.NotAvailable -> ColorPair(colors.errorContainer, colors.onErrorContainer) 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 val zIndex = 200 - floor.id*2
...@@ -113,11 +130,22 @@ fun floor(floor: Floor, selectedMachine: Machine?, setMachine: (Machine?)->Unit) ...@@ -113,11 +130,22 @@ fun floor(floor: Floor, selectedMachine: Machine?, setMachine: (Machine?)->Unit)
setMachine(machine) 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 = { Div(attrs = {
classes(FloorStyle.absCnt) classes(FloorStyle.absCnt)
}) { }) {
...@@ -127,7 +155,7 @@ fun floor(floor: Floor, selectedMachine: Machine?, setMachine: (Machine?)->Unit) ...@@ -127,7 +155,7 @@ fun floor(floor: Floor, selectedMachine: Machine?, setMachine: (Machine?)->Unit)
Div(attrs = { Div(attrs = {
classes(FloorStyle.underflowCnt) classes(FloorStyle.underflowCnt)
style { style {
if (selectedMachine == it) { if (selectedMachine == machine) {
maxHeight(20.em) maxHeight(20.em)
} else { } else {
maxHeight(0.px) maxHeight(0.px)
...@@ -137,13 +165,29 @@ fun floor(floor: Floor, selectedMachine: Machine?, setMachine: (Machine?)->Unit) ...@@ -137,13 +165,29 @@ fun floor(floor: Floor, selectedMachine: Machine?, setMachine: (Machine?)->Unit)
card(attrs = { card(attrs = {
classes(FloorStyle.underflowCard) classes(FloorStyle.underflowCard)
style { style {
applyColorPair(it.status.toUnderColor()) applyColorPair(machine.status.toUnderColor())
if (selectedMachine != it) { if (selectedMachine != machine) {
property("box-shadow", "none") 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)) }
}
} }
} }
} }
......
...@@ -10,11 +10,13 @@ import org.jetbrains.compose.web.dom.Div ...@@ -10,11 +10,13 @@ import org.jetbrains.compose.web.dom.Div
import org.jetbrains.compose.web.dom.Label import org.jetbrains.compose.web.dom.Label
import org.w3c.dom.HTMLLabelElement import org.w3c.dom.HTMLLabelElement
import org.w3c.dom.Text import org.w3c.dom.Text
import styles.LocalStyle
private var maxId = 0 private var maxId = 0
@Composable @Composable
fun switch(label: String, value: Boolean = false, onSet: (Boolean) -> Unit) { fun switch(label: String, value: Boolean = false, onSet: (Boolean) -> Unit) {
val colors = LocalStyle.current.colors
val id = remember { "mosogep-check-${maxId++}" } val id = remember { "mosogep-check-${maxId++}" }
Div(attrs = { Div(attrs = {
classes("form-check", "form-switch") classes("form-check", "form-switch")
...@@ -29,6 +31,10 @@ fun switch(label: String, value: Boolean = false, onSet: (Boolean) -> Unit) { ...@@ -29,6 +31,10 @@ fun switch(label: String, value: Boolean = false, onSet: (Boolean) -> Unit) {
classes("form-check-input") classes("form-check-input")
id(id) id(id)
onChange { onSet(it.value) } onChange { onSet(it.value) }
style {
backgroundColor(if (value) colors.primary else colors.background)
property("border-color", colors.primary)
}
} }
) )
Label( Label(
......
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
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
...@@ -4,7 +4,6 @@ import org.jetbrains.compose.web.css.CSSColorValue ...@@ -4,7 +4,6 @@ import org.jetbrains.compose.web.css.CSSColorValue
import org.jetbrains.compose.web.css.Color import org.jetbrains.compose.web.css.Color
fun color(hex: Long): CSSColorValue { fun color(hex: Long): CSSColorValue {
println(hex.toString())
val hexStr = hex.toString(16).padStart(6, '0') val hexStr = hex.toString(16).padStart(6, '0')
return Color("#$hexStr") return Color("#$hexStr")
} }
......
...@@ -3,6 +3,7 @@ package styles ...@@ -3,6 +3,7 @@ package styles
import androidx.compose.runtime.compositionLocalOf import androidx.compose.runtime.compositionLocalOf
import org.jetbrains.compose.web.css.* import org.jetbrains.compose.web.css.*
import org.jetbrains.compose.web.css.keywords.auto
class Style(val dark: Boolean = false): StyleSheet() { class Style(val dark: Boolean = false): StyleSheet() {
val colors = if (dark) DarkThemeColors else LightThemeColors val colors = if (dark) DarkThemeColors else LightThemeColors
...@@ -16,10 +17,11 @@ class Style(val dark: Boolean = false): StyleSheet() { ...@@ -16,10 +17,11 @@ class Style(val dark: Boolean = false): StyleSheet() {
"html, body, #root" { "html, body, #root" {
position(Position.Relative) position(Position.Relative)
height(100.percent) height(100.percent)
overflowY("hidden") display(DisplayStyle.Flex)
flexDirection(FlexDirection.Column)
} }
"h1, h2, h3, h4, h5, h6" { "#root" {
marginBottom(0.px) overflowY("auto")
} }
"*" { "*" {
property("transition", baseTransition) property("transition", baseTransition)
...@@ -27,18 +29,15 @@ class Style(val dark: Boolean = false): StyleSheet() { ...@@ -27,18 +29,15 @@ class Style(val dark: Boolean = false): StyleSheet() {
} }
val content by style { val content by style {
overflow("auto") flexGrow(1)
height(100.percent)
paddingBottom(footHeight * 2)
} }
val footer by style { val footer by style {
position(Position.Absolute)
bottom(0.px)
width(100.percent) width(100.percent)
height(footHeight) height(footHeight)
lineHeight(footHeight) lineHeight(footHeight)
backgroundColor(colors.surface) backgroundColor(colors.surface)
color(colors.onSurface) color(colors.onSurface)
flexGrow(0)
property("z-index", 2000) property("z-index", 2000)
} }
} }
......
<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
<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
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment