From 0026643a0ac53b69a1ec85ef9ca22e40249bb4de Mon Sep 17 00:00:00 2001 From: Lars Westermann Date: Mon, 10 Jun 2019 17:56:21 +0200 Subject: [PATCH] Update brett --- portal.toml | 1 + .../kotlin/de/kif/common/DateFormat.kt | 47 +++++++++----- .../frontend/repository/ScheduleRepository.kt | 2 +- .../de/kif/frontend/views/board/Board.kt | 39 +++++++++++- .../kif/frontend/views/board/BoardSchedule.kt | 61 ++++++++++++++----- .../resources/style/components/_board.scss | 34 +++++++++++ .../resources/style/components/_calendar.scss | 4 +- .../kotlin/de/kif/backend/Configuration.kt | 3 + .../kotlin/de/kif/backend/route/Board.kt | 47 ++++++++------ .../kotlin/de/kif/backend/route/News.kt | 29 ++++----- src/jvmMain/resources/portal.toml | 1 + 11 files changed, 197 insertions(+), 71 deletions(-) diff --git a/portal.toml b/portal.toml index caf34b3..3d4bb02 100644 --- a/portal.toml +++ b/portal.toml @@ -6,6 +6,7 @@ debug = false [schedule] reference = "2019-06-10" +offset = 7200000 [general] wiki_url = "https://wiki.kif.rocks/w/index.php?title=KIF470:Arbeitskreise&action=raw" diff --git a/src/commonMain/kotlin/de/kif/common/DateFormat.kt b/src/commonMain/kotlin/de/kif/common/DateFormat.kt index 15be4fd..50cedd6 100644 --- a/src/commonMain/kotlin/de/kif/common/DateFormat.kt +++ b/src/commonMain/kotlin/de/kif/common/DateFormat.kt @@ -4,37 +4,52 @@ import com.soywiz.klock.DateFormat import com.soywiz.klock.KlockLocale import com.soywiz.klock.format import com.soywiz.klock.locale.german -import com.soywiz.klock.min fun formatDateTime(unix: Long) = DateFormat("EEEE, d. MMMM y HH:mm") .withLocale(KlockLocale.german) .format(unix) -fun formatTimeDiff(diff: Long): String { - var time = diff / 1000 +fun formatTimeDiff(time: Long, now: Long): String { + var dt = (time - now) / 1000 - val seconds = time % 60 - time /= 60 - val minutes = time % 60 - time /= 60 - val hours = time % 24 - time /= 24 - val days = time + val seconds = dt % 60 + dt /= 60 + val minutes = dt % 60 + dt /= 60 + val hours = dt % 24 + dt /= 24 + val days = dt - return when { + return when { + days > 1L -> { + "in $days Tagen" + } days > 0L -> { - if (days == 1L) "1 Tag" else "$days Tagen" + "morgen" + } + hours > 1L -> { + val nowHour = DateFormat("HH") + .withLocale(KlockLocale.german) + .format(now).toInt() ?: 0 + + val ht = DateFormat("HH:mm") + .withLocale(KlockLocale.german) + .format(time) + + if ((ht.substringBefore(":").toIntOrNull() ?: 0) < nowHour ) { + "morgen" + } else "um $ht" } hours > 0L -> { - hours.toString().padStart(2, '0')+":"+ (minutes + if (seconds > 0) 1 else 0).toString().padStart(2,'0') + "in " +hours.toString().padStart(2, '0') + ":" + (minutes + if (seconds > 0) 1 else 0).toString().padStart(2, '0') } minutes > 0L -> { - "00:"+ (minutes+ if (seconds > 0) 1 else 0).toString().padStart(2,'0') + "in 00:" + (minutes + if (seconds > 0) 1 else 0).toString().padStart(2, '0') } seconds > 0L -> { - "> 1 Minute" + "in > 1 Minute" } - else -> "vor $minutes Minuten" + else -> "---" } } diff --git a/src/jsMain/kotlin/de/kif/frontend/repository/ScheduleRepository.kt b/src/jsMain/kotlin/de/kif/frontend/repository/ScheduleRepository.kt index 2acc2a3..60a2b73 100644 --- a/src/jsMain/kotlin/de/kif/frontend/repository/ScheduleRepository.kt +++ b/src/jsMain/kotlin/de/kif/frontend/repository/ScheduleRepository.kt @@ -26,7 +26,7 @@ object ScheduleRepository : Repository { } suspend fun getUpcoming(count: Int = 8): List { - val json = repositoryGet("$prefix/api/schedule/upcoming?count=$count") ?: return emptyList() + val json = repositoryGet("$prefix/api/schedules/upcoming?count=$count") ?: return emptyList() return parser.parse(json, Schedule.serializer().list) } diff --git a/src/jsMain/kotlin/de/kif/frontend/views/board/Board.kt b/src/jsMain/kotlin/de/kif/frontend/views/board/Board.kt index ce97daa..bc34286 100644 --- a/src/jsMain/kotlin/de/kif/frontend/views/board/Board.kt +++ b/src/jsMain/kotlin/de/kif/frontend/views/board/Board.kt @@ -3,8 +3,9 @@ package de.kif.frontend.views.board import com.soywiz.klock.DateFormat import com.soywiz.klock.DateTimeTz import com.soywiz.klock.KlockLocale -import com.soywiz.klock.format import com.soywiz.klock.locale.german +import de.kif.frontend.launch +import de.kif.frontend.repository.ScheduleRepository import de.kif.frontend.views.overview.getByClassOrCreate import de.westermann.kwebview.interval import de.westermann.kwebview.iterator @@ -12,6 +13,7 @@ import org.w3c.dom.HTMLElement import org.w3c.dom.HTMLSpanElement import org.w3c.dom.get import kotlin.browser.document +import kotlin.dom.clear import kotlin.js.Date fun initBoard() { @@ -20,16 +22,37 @@ fun initBoard() { val timeView = dateContainer.getByClassOrCreate("board-header-date-time") val dateView = dateContainer.getByClassOrCreate("board-header-date-date") - val initTime = Date.now().toLong() + val initTime = Date.now().toLong() val referenceInitTime = dateContainer.dataset["now"]?.toLongOrNull() ?: initTime val diff = initTime - referenceInitTime val boardRunning = document.getElementsByClassName("board-running")[0] as HTMLElement val scheduleList = mutableListOf() + val runningReferenceTime = boardRunning.dataset["reference"]?.toLongOrNull() ?: 0L + + fun update() { + launch { + scheduleList.clear() + val list = ScheduleRepository.getUpcoming() + boardRunning.clear() + + val now = Date.now().toLong() + diff + + for (s in list) { + val v = BoardSchedule.create(s, runningReferenceTime, now) + scheduleList += v + boardRunning.appendChild(v.html) + } + } + } for (bs in boardRunning.getElementsByClassName("board-schedule").iterator()) { - scheduleList += BoardSchedule(bs) + scheduleList += BoardSchedule(bs).also { + it.onRemove { + update() + } + } } interval(1000) { @@ -48,4 +71,14 @@ fun initBoard() { scheduleList.forEach { it.updateTime(now) } } + + ScheduleRepository.onCreate { + update() + } + ScheduleRepository.onUpdate { + update() + } + ScheduleRepository.onDelete { + update() + } } diff --git a/src/jsMain/kotlin/de/kif/frontend/views/board/BoardSchedule.kt b/src/jsMain/kotlin/de/kif/frontend/views/board/BoardSchedule.kt index 31aa3a9..97d391c 100644 --- a/src/jsMain/kotlin/de/kif/frontend/views/board/BoardSchedule.kt +++ b/src/jsMain/kotlin/de/kif/frontend/views/board/BoardSchedule.kt @@ -1,36 +1,67 @@ package de.kif.frontend.views.board -import de.kif.common.formatDateTime import de.kif.common.formatTimeDiff import de.kif.common.model.Schedule import de.kif.frontend.views.overview.getByClassOrCreate +import de.westermann.kobserve.event.EventHandler import de.westermann.kwebview.View +import de.westermann.kwebview.createHtmlView import org.w3c.dom.HTMLDivElement import org.w3c.dom.HTMLElement import org.w3c.dom.HTMLSpanElement import org.w3c.dom.get -import kotlin.js.Date class BoardSchedule( - view: HTMLElement + view: HTMLElement, + startTime: Long = 0L, + endTime: Long = 0L ) : View(view) { - private val colorView = view.getByClassOrCreate("board-schedule-color") - private val timeView = view.getByClassOrCreate("board-schedule-time") - private val nameView = view.getByClassOrCreate("board-schedule-name") - private val roomView = view.getByClassOrCreate("board-schedule-room") + val colorViewContainer = view.getByClassOrCreate("board-schedule-color") + val colorView = colorViewContainer.getByClassOrCreate("bsc") + val timeView = view.getByClassOrCreate("board-schedule-time") + val nameView = view.getByClassOrCreate("board-schedule-name") + val roomView = view.getByClassOrCreate("board-schedule-room") - private val schedule: Long = view.dataset["id"]?.toLongOrNull() ?: 0L - private val startTime: Long = timeView.dataset["startTime"]?.toLongOrNull() ?: 0L - private val endTime: Long = timeView.dataset["endTime"]?.toLongOrNull() ?: 0L + + val clockViewContainer = view.getByClassOrCreate("board-schedule-clock") + val clockView = clockViewContainer.getByClassOrCreate("material-icons", "i").also { + it.textContent = "alarm" + } + + private val startTime: Long = timeView.dataset["startTime"]?.toLongOrNull() ?: startTime + private val endTime: Long = timeView.dataset["endTime"]?.toLongOrNull() ?: endTime + + val onRemove = EventHandler() fun updateTime(now: Long) { - timeView.textContent = if (startTime >= now) { - "Start in ${formatTimeDiff(startTime - now)}" - } else { - "Ende in ${formatTimeDiff(endTime - now)}" + timeView.textContent = when { + startTime >= now -> "Start ${formatTimeDiff(startTime, now)}" + endTime >= now -> "Ende ${formatTimeDiff(endTime, now)}" + else -> { + onRemove.emit(Unit) + "---" + } } + + classList["board-schedule-running"] = now in startTime..endTime } - init { + companion object { + fun create(schedule: Schedule, referenceTime: Long, now: Long): BoardSchedule { + val startTime = ((schedule.getAbsoluteStartTime() * 60 * 1000) + referenceTime) + val endTime = ((schedule.getAbsoluteEndTime() * 60 * 1000) + referenceTime) + + val entry = BoardSchedule(createHtmlView(), startTime, endTime) + + if (schedule.workGroup.track?.color != null) { + entry.colorView.style.backgroundColor = schedule.workGroup.track.color.toString() + } + entry.nameView.textContent = schedule.workGroup.name + entry.roomView.textContent = schedule.room.name + + entry.updateTime(now) + + return entry + } } } diff --git a/src/jsMain/resources/style/components/_board.scss b/src/jsMain/resources/style/components/_board.scss index 773207c..62a38a1 100644 --- a/src/jsMain/resources/style/components/_board.scss +++ b/src/jsMain/resources/style/components/_board.scss @@ -22,6 +22,17 @@ &:first-child { width: 70%; float: left; + overflow: visible; + + &:after { + content: ''; + position: absolute; + top: -1rem; + bottom: 0; + left: 100%; + margin-left: 1rem; + border-right: solid 1px var(--table-border-color); + } } &:last-child { @@ -38,6 +49,16 @@ & > div { height: 100%; } + + &:after { + content: ''; + position: absolute; + left: 0; + width: 100%; + top: 100%; + margin-top: 0.5rem; + border-bottom: solid 1px var(--table-border-color); + } } .board-content { @@ -71,10 +92,23 @@ border-bottom: solid 1px var(--table-border-color); width: calc(50% - 1rem); margin-left: 1rem; + position: relative; &:nth-last-child(1), &:nth-last-child(2) { border-bottom: none; } + + .board-schedule-clock { + position: absolute; + left: 1rem; + top: 0; + display: none; + color: var(--primary-color); + } + + &.board-schedule-running .board-schedule-clock { + display: block; + } } .board-schedule-color { diff --git a/src/jsMain/resources/style/components/_calendar.scss b/src/jsMain/resources/style/components/_calendar.scss index d3b7775..1f448b8 100644 --- a/src/jsMain/resources/style/components/_calendar.scss +++ b/src/jsMain/resources/style/components/_calendar.scss @@ -504,7 +504,7 @@ content: ''; display: block; position: absolute; - top: 3rem; + top: 0.1rem; bottom: 0; width: 1px; border-right: solid 1px var(--primary-color); @@ -514,7 +514,7 @@ content: ''; display: block; position: absolute; - top: 3rem; + top: 0.1rem; transform: scale(0.5, 1) rotate(45deg); transform-origin: right; border-bottom: solid 0.4rem var(--primary-color); diff --git a/src/jvmMain/kotlin/de/kif/backend/Configuration.kt b/src/jvmMain/kotlin/de/kif/backend/Configuration.kt index 6057655..21ffe5e 100644 --- a/src/jvmMain/kotlin/de/kif/backend/Configuration.kt +++ b/src/jvmMain/kotlin/de/kif/backend/Configuration.kt @@ -60,6 +60,7 @@ object Configuration { private object ScheduleSpec : ConfigSpec("schedule") { val reference by required() + val offset by required() } object Schedule { @@ -69,6 +70,8 @@ object Configuration { sdf.timeZone = TimeZone.getTimeZone(ZoneId.of("UTC")) sdf.parse(reference) } + + val offset by c(ScheduleSpec.offset) } private object SecuritySpec : ConfigSpec("security") { diff --git a/src/jvmMain/kotlin/de/kif/backend/route/Board.kt b/src/jvmMain/kotlin/de/kif/backend/route/Board.kt index ff31923..14479d0 100644 --- a/src/jvmMain/kotlin/de/kif/backend/route/Board.kt +++ b/src/jvmMain/kotlin/de/kif/backend/route/Board.kt @@ -10,10 +10,7 @@ import io.ktor.routing.Route import io.ktor.routing.get import kotlinx.css.CSSBuilder import kotlinx.css.Color -import kotlinx.html.div -import kotlinx.html.img -import kotlinx.html.span -import kotlinx.html.unsafe +import kotlinx.html.* import java.util.* import kotlin.math.max import kotlin.math.min @@ -25,8 +22,10 @@ data class BoardSchedule( ) suspend fun getUpcoming(limit: Int = 8): List { - val now = (Date().time / (1000 * 60)).toInt() - (Configuration.Schedule.referenceDate.time / (1000 * 60)) - return ScheduleRepository.all().asSequence() + val now = + ((Date().time + Configuration.Schedule.offset) / (1000 * 60)).toInt() - + (Configuration.Schedule.referenceDate.time / (1000 * 60)) + return ScheduleRepository.all() .map { BoardSchedule( it, @@ -35,10 +34,11 @@ suspend fun getUpcoming(limit: Int = 8): List { ) } .filter { it.endTime > now } - .sortedBy { it.startTime } + .partition { it.startTime < now }.let { (running, upcoming) -> + running.sortedBy { it.endTime } + upcoming.sortedBy { it.startTime } + } .take(limit) .map { it.schedule } - .toList() } fun Route.board() { @@ -47,6 +47,7 @@ fun Route.board() { val now = Date() val referenceTime = Configuration.Schedule.referenceDate.time + val timeOffset = Configuration.Schedule.offset val refDate = Configuration.Schedule.referenceDate @@ -83,17 +84,28 @@ fun Route.board() { min = (min / 60 - 1) * 60 max = (max / 60 + 2) * 60 + val nowLocale = now.time + timeOffset respondMain(true, true) { theme -> content { div("board") { div("board-header") { div("board-running") { + attributes["data-reference"] = referenceTime.toString() + for (schedule in scheduleList) { - div("board-schedule") { + val startTime = ((schedule.getAbsoluteStartTime() * 60 * 1000) + referenceTime) + val endTime = ((schedule.getAbsoluteEndTime() * 60 * 1000) + referenceTime) + + var classes = "board-schedule" + if (nowLocale in startTime..endTime) { + classes += " board-schedule-running" + } + + div(classes) { attributes["data-id"] = schedule.id.toString() div("board-schedule-color") { - span { + span("bsc") { attributes["style"] = CSSBuilder().apply { val c = schedule.workGroup.track?.color if (c != null) { @@ -104,16 +116,14 @@ fun Route.board() { } div("board-schedule-time") { - val startTime = ((schedule.getAbsoluteStartTime() * 60 * 1000) + referenceTime) - val endTime = ((schedule.getAbsoluteEndTime() * 60 * 1000) + referenceTime) attributes["data-start-time"] = startTime.toString() attributes["data-end-time"] = endTime.toString() - if (startTime >= now.time) { - +"Start in ${formatTimeDiff(startTime - now.time)}" + if (startTime >= nowLocale) { + +"Start ${formatTimeDiff(startTime, nowLocale)}" } else { - +"Ende in ${formatTimeDiff(endTime - now.time)}" + +"Ende ${formatTimeDiff(endTime, nowLocale)}" } } @@ -124,14 +134,17 @@ fun Route.board() { div("board-schedule-room") { +schedule.room.name } + + div("board-schedule-clock") { + i("material-icons") { +"alarm" } + } } } } div("board-logo") { img("KIF 47.0", "/static/images/logo.svg") div("board-header-date") { - attributes["data-now"] = - (now.time - 2 * 60 * 60 * 1000).toString() + attributes["data-now"] = (now.time - timeOffset).toString() } } } diff --git a/src/jvmMain/kotlin/de/kif/backend/route/News.kt b/src/jvmMain/kotlin/de/kif/backend/route/News.kt index 0972f8a..ccb2aa6 100644 --- a/src/jvmMain/kotlin/de/kif/backend/route/News.kt +++ b/src/jvmMain/kotlin/de/kif/backend/route/News.kt @@ -1,19 +1,13 @@ package de.kif.backend.route -import de.kif.backend.Configuration -import de.kif.backend.Resources -import de.kif.backend.authenticateOrRedirect -import de.kif.backend.isAuthenticated +import de.kif.backend.* import de.kif.backend.repository.PostRepository import de.kif.backend.util.markdownToHtml -import de.kif.backend.view.MainTemplate -import de.kif.backend.view.MenuTemplate import de.kif.backend.view.respondMain import de.kif.common.formatDateTime import de.kif.common.model.Permission import de.kif.common.model.Post import io.ktor.application.call -import io.ktor.html.respondHtmlTemplate import io.ktor.http.HttpStatusCode import io.ktor.http.content.PartData import io.ktor.http.content.forEachPart @@ -26,7 +20,6 @@ import io.ktor.routing.get import io.ktor.routing.post import kotlinx.html.* import java.io.File -import de.kif.backend.prefix fun DIV.createPost(post: Post, editable: Boolean = false, additionalClasses: String = "") { var classes = "post" @@ -80,6 +73,13 @@ fun Route.overview() { respondMain { theme -> content { + if (editable) { + div("overview-new") { + a("post/new", classes = "form-btn") { + +"Neuer Eintrag" + } + } + } div("overview") { div("overview-main") { for (post in postList) { @@ -87,16 +87,10 @@ fun Route.overview() { } } div("overview-side") { - if (editable) { - div("overview-new") { - a("post/new", classes = "form-btn") { - +"Neuer Eintrag" - } - } - } div("overview-twitter") { unsafe { - raw(""" + raw( + """ - """.trimIndent()) + """.trimIndent() + ) } } } diff --git a/src/jvmMain/resources/portal.toml b/src/jvmMain/resources/portal.toml index c50259a..02fc07d 100644 --- a/src/jvmMain/resources/portal.toml +++ b/src/jvmMain/resources/portal.toml @@ -12,6 +12,7 @@ database = "data/portal.db" [schedule] reference = "1970-01-01" +offset = 0 [security] session_name = "SESSION"