Update brett
This commit is contained in:
parent
d52ada1726
commit
0026643a0a
11 changed files with 197 additions and 71 deletions
|
@ -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"
|
||||
|
|
|
@ -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 -> "---"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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") {
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,6 +12,7 @@ database = "data/portal.db"
|
|||
|
||||
[schedule]
|
||||
reference = "1970-01-01"
|
||||
offset = 0
|
||||
|
||||
[security]
|
||||
session_name = "SESSION"
|
||||
|
|
Loading…
Reference in a new issue