Update brett

This commit is contained in:
Lars Westermann 2019-06-10 17:56:21 +02:00
parent d52ada1726
commit 0026643a0a
Signed by: lars.westermann
GPG key ID: 9D417FA5BB9D5E1D
11 changed files with 197 additions and 71 deletions

View file

@ -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"

View file

@ -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 -> "---"
}
}

View file

@ -26,7 +26,7 @@ object ScheduleRepository : Repository<Schedule> {
}
suspend fun getUpcoming(count: Int = 8): List<Schedule> {
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)
}

View file

@ -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<HTMLSpanElement>("board-header-date-time")
val dateView = dateContainer.getByClassOrCreate<HTMLSpanElement>("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<BoardSchedule>()
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()
}
}

View file

@ -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<HTMLDivElement>("board-schedule-color")
private val timeView = view.getByClassOrCreate<HTMLDivElement>("board-schedule-time")
private val nameView = view.getByClassOrCreate<HTMLDivElement>("board-schedule-name")
private val roomView = view.getByClassOrCreate<HTMLDivElement>("board-schedule-room")
val colorViewContainer = view.getByClassOrCreate<HTMLDivElement>("board-schedule-color")
val colorView = colorViewContainer.getByClassOrCreate<HTMLSpanElement>("bsc")
val timeView = view.getByClassOrCreate<HTMLDivElement>("board-schedule-time")
val nameView = view.getByClassOrCreate<HTMLDivElement>("board-schedule-name")
val roomView = view.getByClassOrCreate<HTMLDivElement>("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<HTMLDivElement>("board-schedule-clock")
val clockView = clockViewContainer.getByClassOrCreate<HTMLElement>("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<Unit>()
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
}
}
}

View file

@ -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 {

View file

@ -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);

View file

@ -60,6 +60,7 @@ object Configuration {
private object ScheduleSpec : ConfigSpec("schedule") {
val reference by required<String>()
val offset by required<Long>()
}
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") {

View file

@ -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<Schedule> {
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<Schedule> {
)
}
.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()
}
}
}

View file

@ -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(
"""
<a
class="twitter-timeline"
href="${Configuration.Twitter.timeline}"
@ -108,7 +102,8 @@ fun Route.overview() {
data-dnt="true"
>Twitter wall</a>
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
""".trimIndent())
""".trimIndent()
)
}
}
}

View file

@ -12,6 +12,7 @@ database = "data/portal.db"
[schedule]
reference = "1970-01-01"
offset = 0
[security]
session_name = "SESSION"