Fix import, add wall

This commit is contained in:
Lars Westermann 2019-06-09 19:21:20 +02:00
parent 8a926aeb35
commit 19956ebafb
Signed by: lars.westermann
GPG key ID: 9D417FA5BB9D5E1D
19 changed files with 749 additions and 218 deletions

View file

@ -23,6 +23,37 @@ data class Post(
) )
) )
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other == null || this::class != other::class) return false
other as Post
if (id != other.id) return false
if (name != other.name) return false
if (content != other.content) return false
if (url != other.url) return false
if (image != other.image) return false
if (pinned != other.pinned) return false
if (hideOnProjector != other.hideOnProjector) return false
return true
}
fun equalsIgnoreId(other: Post): Boolean = copy(id = null) == other.copy(id = null)
override fun hashCode(): Int {
var result = id?.hashCode() ?: 0
result = 31 * result + name.hashCode()
result = 31 * result + content.hashCode()
result = 31 * result + url.hashCode()
result = 31 * result + (image?.hashCode() ?: 0)
result = 31 * result + pinned.hashCode()
result = 31 * result + hideOnProjector.hashCode()
return result
}
companion object { companion object {
private const val chars = "abcdefghijklmnopqrstuvwxyz" private const val chars = "abcdefghijklmnopqrstuvwxyz"
private const val length = 32 private const val length = 32

View file

@ -17,6 +17,8 @@ data class Room(
override val updateAt: Long = 0 override val updateAt: Long = 0
) : Model { ) : Model {
override fun createSearch() = SearchElement( override fun createSearch() = SearchElement(
mapOf( mapOf(
"name" to name "name" to name
@ -26,4 +28,36 @@ data class Room(
"places" to places.toDouble() "places" to places.toDouble()
) )
) )
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other == null || this::class != other::class) return false
other as Room
if (id != other.id) return false
if (name != other.name) return false
if (places != other.places) return false
if (projector != other.projector) return false
if (internet != other.internet) return false
if (whiteboard != other.whiteboard) return false
if (blackboard != other.blackboard) return false
if (accessible != other.accessible) return false
return true
}
fun equalsIgnoreId(other: Room): Boolean = copy(id = null) == other.copy(id = null)
override fun hashCode(): Int {
var result = id?.hashCode() ?: 0
result = 31 * result + name.hashCode()
result = 31 * result + places
result = 31 * result + projector.hashCode()
result = 31 * result + internet.hashCode()
result = 31 * result + whiteboard.hashCode()
result = 31 * result + blackboard.hashCode()
result = 31 * result + accessible.hashCode()
return result
}
} }

View file

@ -16,6 +16,7 @@ data class Schedule(
override val updateAt: Long = 0 override val updateAt: Long = 0
) : Model { ) : Model {
override fun createSearch() = SearchElement( override fun createSearch() = SearchElement(
mapOf( mapOf(
"workgroup" to workGroup.name, "workgroup" to workGroup.name,
@ -29,6 +30,35 @@ data class Schedule(
fun getAbsoluteStartTime(): Int = day * 60 * 24 + time fun getAbsoluteStartTime(): Int = day * 60 * 24 + time
fun getAbsoluteEndTime(): Int = getAbsoluteStartTime() + workGroup.length fun getAbsoluteEndTime(): Int = getAbsoluteStartTime() + workGroup.length
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other == null || this::class != other::class) return false
other as Schedule
if (id != other.id) return false
if (workGroup != other.workGroup) return false
if (room != other.room) return false
if (day != other.day) return false
if (time != other.time) return false
if (lockRoom != other.lockRoom) return false
if (lockTime != other.lockTime) return false
return true
}
fun equalsIgnoreId(other: Schedule): Boolean = copy(id = null) == other.copy(id = null)
override fun hashCode(): Int {
var result = id?.hashCode() ?: 0
result = 31 * result + workGroup.hashCode()
result = 31 * result + room.hashCode()
result = 31 * result + day
result = 31 * result + time
result = 31 * result + lockRoom.hashCode()
result = 31 * result + lockTime.hashCode()
return result
}
companion object { companion object {
fun timeOfDayToString(time: Int): String { fun timeOfDayToString(time: Int): String {

View file

@ -12,9 +12,33 @@ data class Track(
override val updateAt: Long = 0 override val updateAt: Long = 0
) : Model { ) : Model {
override fun createSearch() = SearchElement( override fun createSearch() = SearchElement(
mapOf( mapOf(
"name" to name "name" to name
) )
) )
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other == null || this::class != other::class) return false
other as Track
if (id != other.id) return false
if (name != other.name) return false
if (color != other.color) return false
return true
}
fun equalsIgnoreId(other: Track): Boolean = copy(id = null) == other.copy(id = null)
override fun hashCode(): Int {
var result = id?.hashCode() ?: 0
result = 31 * result + name.hashCode()
result = 31 * result + color.hashCode()
return result
}
} }

View file

@ -13,6 +13,8 @@ data class User(
override val updateAt: Long = 0 override val updateAt: Long = 0
) : Model { ) : Model {
fun checkPermission(permission: Permission): Boolean { fun checkPermission(permission: Permission): Boolean {
return permission in permissions || Permission.ADMIN in permissions return permission in permissions || Permission.ADMIN in permissions
} }
@ -22,4 +24,28 @@ data class User(
"username" to username "username" to username
) )
) )
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other == null || this::class != other::class) return false
other as User
if (id != other.id) return false
if (username != other.username) return false
if (password != other.password) return false
if (permissions != other.permissions) return false
return true
}
fun equalsIgnoreId(other: User): Boolean = copy(id = null) == other.copy(id = null)
override fun hashCode(): Int {
var result = id?.hashCode() ?: 0
result = 31 * result + username.hashCode()
result = 31 * result + password.hashCode()
result = 31 * result + permissions.hashCode()
return result
}
} }

View file

@ -37,4 +37,50 @@ data class WorkGroup(
"length" to length.toDouble() "length" to length.toDouble()
) )
) )
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other == null || this::class != other::class) return false
other as WorkGroup
if (id != other.id) return false
if (name != other.name) return false
if (description != other.description) return false
if (interested != other.interested) return false
if (track != other.track) return false
if (projector != other.projector) return false
if (resolution != other.resolution) return false
if (internet != other.internet) return false
if (whiteboard != other.whiteboard) return false
if (blackboard != other.blackboard) return false
if (accessible != other.accessible) return false
if (length != other.length) return false
if (language != other.language) return false
if (leader != other.leader) return false
if (constraints != other.constraints) return false
return true
}
fun equalsIgnoreId(other: WorkGroup): Boolean = copy(id = null) == other.copy(id = null)
override fun hashCode(): Int {
var result = id?.hashCode() ?: 0
result = 31 * result + name.hashCode()
result = 31 * result + description.hashCode()
result = 31 * result + interested
result = 31 * result + (track?.hashCode() ?: 0)
result = 31 * result + projector.hashCode()
result = 31 * result + resolution.hashCode()
result = 31 * result + internet.hashCode()
result = 31 * result + whiteboard.hashCode()
result = 31 * result + blackboard.hashCode()
result = 31 * result + accessible.hashCode()
result = 31 * result + length
result = 31 * result + language.hashCode()
result = 31 * result + leader.hashCode()
result = 31 * result + constraints.hashCode()
return result
}
} }

View file

@ -1,7 +1,8 @@
package de.kif.frontend.views.board package de.kif.frontend.views.board
import de.kif.common.formatDateTime import com.soywiz.klock.DateFormat
import de.westermann.kwebview.iterator import com.soywiz.klock.KlockLocale
import com.soywiz.klock.locale.german
import de.westermann.kwebview.interval import de.westermann.kwebview.interval
import org.w3c.dom.HTMLElement import org.w3c.dom.HTMLElement
import org.w3c.dom.get import org.w3c.dom.get
@ -9,21 +10,29 @@ import kotlin.browser.document
import kotlin.js.Date import kotlin.js.Date
fun initBoard() { fun initBoard() {
val boardSchedules = document.getElementsByClassName("board-schedules")[0] as HTMLElement //val boardSchedules = document.getElementsByClassName("board-schedules")[0] as HTMLElement
val dateView = document.getElementsByClassName("board-header-date")[0] as HTMLElement val dateView = document.getElementsByClassName("board-header-date")[0] as HTMLElement
val referenceTime = boardSchedules.dataset["reference"]?.toLongOrNull() ?: 0L //val referenceTime = boardSchedules.dataset["reference"]?.toLongOrNull() ?: 0L
/*
val scheduleList = mutableListOf<BoardSchedule>() val scheduleList = mutableListOf<BoardSchedule>()
for (bs in boardSchedules.getElementsByClassName("board-schedule").iterator()) { for (bs in boardSchedules.getElementsByClassName("board-schedule").iterator()) {
scheduleList += BoardSchedule(bs) scheduleList += BoardSchedule(bs)
} }
*/
interval(1000) { interval(1000) {
val currentTime = Date.now().toLong()
dateView.innerText = formatDateTime(currentTime)
val currentTime = Date().let {
it.getHours().toString().padStart(2, '0') + ":" + it.getMinutes().toString().padStart(2, '0')
}
dateView.innerText = currentTime
/*
val now = referenceTime - currentTime / 1000 val now = referenceTime - currentTime / 1000
scheduleList.forEach { it.updateTime(now) } scheduleList.forEach { it.updateTime(now) }
*/
} }
} }

View file

@ -13,33 +13,43 @@ import kotlin.browser.window
class Calendar(calendar: HTMLElement) : View(calendar) { class Calendar(calendar: HTMLElement) : View(calendar) {
var autoScroll = true
val day: Int = calendar.dataset["day"]?.toIntOrNull() ?: -1 val day: Int = calendar.dataset["day"]?.toIntOrNull() ?: -1
private val htmlTag = document.body as HTMLElement
val calendarTable = calendar.getElementsByClassName("calendar-table")[0] as HTMLElement val calendarTable = calendar.getElementsByClassName("calendar-table")[0] as HTMLElement
val calendarTableHeader = calendar.getElementsByClassName("calendar-header")[0] as HTMLElement private val calendarTableHeader = calendar.getElementsByClassName("calendar-header")[0] as HTMLElement
fun scrollVerticalBy(pixel: Double, scrollBehavior: ScrollBehavior = ScrollBehavior.SMOOTH) { fun scrollVerticalBy(pixel: Double, scrollBehavior: ScrollBehavior = ScrollBehavior.SMOOTH) {
htmlTag.scrollBy(ScrollToOptions(0.0, pixel, scrollBehavior)) scrollAllVerticalBy(pixel, scrollBehavior)
} }
fun scrollHorizontalBy(pixel: Double, scrollBehavior: ScrollBehavior = ScrollBehavior.SMOOTH) { fun scrollHorizontalBy(pixel: Double, scrollBehavior: ScrollBehavior = ScrollBehavior.SMOOTH) {
calendarTable.scrollBy(ScrollToOptions(pixel, 0.0, scrollBehavior)) scrollAllHorizontalBy(pixel, scrollBehavior)
} }
fun scrollVerticalTo(pixel: Double, scrollBehavior: ScrollBehavior = ScrollBehavior.SMOOTH) { fun scrollVerticalTo(pixel: Double, scrollBehavior: ScrollBehavior = ScrollBehavior.SMOOTH) {
htmlTag.scrollTo(ScrollToOptions(0.0, pixel, scrollBehavior)) scrollAllVerticalTo(pixel, scrollBehavior)
} }
fun scrollHorizontalTo(pixel: Double, scrollBehavior: ScrollBehavior = ScrollBehavior.SMOOTH) { fun scrollHorizontalTo(pixel: Double, scrollBehavior: ScrollBehavior = ScrollBehavior.SMOOTH) {
calendarTable.scrollTo(ScrollToOptions(pixel, 0.0, scrollBehavior)) scrollAllHorizontalTo(pixel, scrollBehavior)
} }
val editable = calendar.dataset["editable"]?.toBoolean() ?: false val editable = calendar.dataset["editable"]?.toBoolean() ?: false
val body = CalendarBody(this, calendar.getElementsByClassName("calendar-body")[0] as HTMLElement) val body = CalendarBody(this, calendar.getElementsByClassName("calendar-body")[0] as HTMLElement)
val orientation: Orientation = if (document.getElementsByClassName("time-to-room").length > 0) {
Orientation.TIME_TO_ROOM
} else {
Orientation.ROOM_TO_TIME
}
init { init {
scroll += calendarTable
if (editable) { if (editable) {
CalendarEdit(this, calendar.querySelector(".calendar-edit") as HTMLElement) CalendarEdit(this, calendar.querySelector(".calendar-edit") as HTMLElement)
} }
@ -60,6 +70,8 @@ class Calendar(calendar: HTMLElement) : View(calendar) {
} }
onWheel { onWheel {
autoScroll = false
val multiplier = when (it.deltaMode) { val multiplier = when (it.deltaMode) {
1 -> 16.0 1 -> 16.0
2 -> window.innerHeight.toDouble() 2 -> window.innerHeight.toDouble()
@ -95,8 +107,56 @@ class Calendar(calendar: HTMLElement) : View(calendar) {
} }
} }
} }
enum class Orientation {
/**
* Columns contains time
* Rows contains rooms
*
* Like the old kif tool
*/
TIME_TO_ROOM,
/**
* Columns contains rooms
* Rows contains time
*
* Like the congress schedule
*/
ROOM_TO_TIME
}
companion object {
private var scroll = listOf<HTMLElement>()
private fun scrollAllVerticalBy(pixel: Double, scrollBehavior: ScrollBehavior = ScrollBehavior.SMOOTH) {
println("scroll ${scroll.size} elemenets")
for (calendarTable in scroll) {
calendarTable.scrollBy(ScrollToOptions(0.0, pixel, scrollBehavior))
}
}
private fun scrollAllHorizontalBy(pixel: Double, scrollBehavior: ScrollBehavior = ScrollBehavior.SMOOTH) {
for (calendarTable in scroll) {
calendarTable.scrollBy(ScrollToOptions(pixel, 0.0, scrollBehavior))
}
}
private fun scrollAllVerticalTo(pixel: Double, scrollBehavior: ScrollBehavior = ScrollBehavior.SMOOTH) {
for (calendarTable in scroll) {
calendarTable.scrollTo(ScrollToOptions(0.0, pixel, scrollBehavior))
}
}
private fun scrollAllHorizontalTo(pixel: Double, scrollBehavior: ScrollBehavior = ScrollBehavior.SMOOTH) {
for (calendarTable in scroll) {
calendarTable.scrollTo(ScrollToOptions(pixel, 0.0, scrollBehavior))
}
}
}
} }
fun initCalendar() { fun initCalendar() {
Calendar(document.getElementsByClassName("calendar")[0] as? HTMLElement ?: return) document.getElementsByClassName("calendar").iterator().forEach { Calendar(it) }
} }

View file

@ -3,9 +3,13 @@ package de.kif.frontend.views.calendar
import de.kif.frontend.launch import de.kif.frontend.launch
import de.kif.frontend.repository.ScheduleRepository import de.kif.frontend.repository.ScheduleRepository
import de.westermann.kwebview.ViewCollection import de.westermann.kwebview.ViewCollection
import de.westermann.kwebview.async
import de.westermann.kwebview.interval import de.westermann.kwebview.interval
import de.westermann.kwebview.iterator import de.westermann.kwebview.iterator
import org.w3c.dom.HTMLElement import org.w3c.dom.HTMLElement
import org.w3c.dom.INSTANT
import org.w3c.dom.SMOOTH
import org.w3c.dom.ScrollBehavior
import kotlin.browser.document import kotlin.browser.document
import kotlin.js.Date import kotlin.js.Date
import kotlin.math.max import kotlin.math.max
@ -81,6 +85,44 @@ class CalendarBody(val calendar: Calendar, view: HTMLElement) : ViewCollection<C
} }
} }
fun update(scroll: ScrollBehavior) {
val currentTime = Date().let {
it.getHours() * 60 + it.getMinutes()
}
val rowTime = (currentTime / 15) * 15
var activeRow: CalendarRow? = null
for (row in this) {
if (row.time == rowTime) {
row.classList.clear()
for (str in row.classList) {
if ("now" in str) {
row.classList -= str
}
}
row.classList += "calendar-row"
row.classList += "calendar-now"
row.classList += "calendar-now-${currentTime - rowTime}"
activeRow = row
} else {
for (str in row.classList) {
if ("now" in str) {
row.classList -= str
}
}
}
}
if (calendar.autoScroll && activeRow != null) {
if (calendar.orientation == Calendar.Orientation.ROOM_TO_TIME) {
calendar.scrollVerticalTo((activeRow.offsetTop).toDouble(), scroll)
} else {
calendar.scrollHorizontalTo((activeRow.offsetLeft - 100).toDouble(), scroll)
}
}
}
init { init {
calendarEntries = document.getElementsByClassName("calendar-entry") calendarEntries = document.getElementsByClassName("calendar-entry")
.iterator().asSequence().map { CalendarEntry(this, it) }.toList() .iterator().asSequence().map { CalendarEntry(this, it) }.toList()
@ -118,6 +160,7 @@ class CalendarBody(val calendar: Calendar, view: HTMLElement) : ViewCollection<C
updateRows() updateRows()
} }
} }
ScheduleRepository.onDelete { ScheduleRepository.onDelete {
for (entry in calendarEntries) { for (entry in calendarEntries) {
if (entry.scheduleId == it) { if (entry.scheduleId == it) {
@ -131,31 +174,12 @@ class CalendarBody(val calendar: Calendar, view: HTMLElement) : ViewCollection<C
} }
} }
interval(1000) { async {
val currentTime = Date().let { update(ScrollBehavior.INSTANT)
it.getHours() * 60 + it.getMinutes()
} }
val rowTime = (currentTime / 15) * 15
for (row in this) { interval(1000) {
if (row.time == rowTime) { update(ScrollBehavior.SMOOTH)
row.classList.clear()
for (str in row.classList) {
if ("now" in str) {
row.classList -= str
}
}
row.classList += "calendar-row"
row.classList += "calendar-now"
row.classList += "calendar-now-${currentTime - rowTime}"
} else {
for (str in row.classList) {
if ("now" in str) {
row.classList -= str
}
}
}
}
} }
} }
} }

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 13 KiB

View file

@ -1,91 +1,74 @@
@import "../config"; @import "../config";
.board-header {
line-height: 3rem;
flex-grow: 1;
font-family: "Bungee", sans-serif;
font-weight: normal;
font-size: 1.1rem;
padding-left: 0.3rem;
}
.board-card {
background-color: var(--background-card-color);
box-shadow: 0 0.1rem 0.2rem var(--shadow-color);
border-radius: $border-radius;
overflow: hidden;
}
.board { .board {
padding: 1rem 0.4rem 2rem; top: 0;
display: flex; left: 0;
position: absolute;
width: 100%;
height: 100%;
overflow: hidden; overflow: hidden;
height: calc(100vh - 0.5rem);
& > div { & > div {
flex-grow: 1; position: absolute;
flex-basis: 0; left: 0;
padding: 0 0.4rem; top: 0;
&:nth-child(1) {
flex-grow: 5;
}
&:nth-child(2) {
flex-grow: 4;
}
&:nth-child(3) {
flex-grow: 4;
}
}
}
.board-twitter {
.board-card {
height: calc(100% - 1.3rem);
& > * {
margin-top: -1px !important;
}
}
}
.board-post {
margin-bottom: 0.5rem;
.post-name {
top: 0.5rem;
}
padding-top: 2.5rem;
padding-bottom: 0.5rem;
}
.board-schedule-box {
margin-bottom: 0.5rem;
padding: 1rem 1rem 0.5rem;
table {
border-collapse: collapse;
width: 100%; width: 100%;
height: 100%;
& > div {
position: relative;
overflow: hidden;
&:first-child {
width: 70%;
float: left;
}
&:last-child {
width: 30%;
float: right;
}
}
} }
} }
.board-card-header { .board-header {
font-family: 'Montserrat', sans-serif; height: 8rem !important;
font-weight: 600;
line-height: 1.5rem; & > div {
height: 100%;
}
}
.board-content {
top: 8rem !important;
bottom: 0;
height: auto !important;
}
.board-logo {
img {
height: 6rem;
width: 6rem;
position: absolute;
top: 50%;
left: 4rem;
margin-top: -3rem;
margin-left: -3rem;
}
}
.board-running {
column-count: 2;
} }
.board-schedule { .board-schedule {
line-height: 1.3rem; line-height: 1.3rem;
height: 2rem; height: 2rem;
min-width: 10rem;
&:not(:last-child) { display: flex;
border-bottom: solid 1px var(--table-border-color) border-bottom: solid 1px var(--table-border-color)
} }
}
.board-schedule-color { .board-schedule-color {
width: 1.2rem; width: 1.2rem;
@ -113,3 +96,45 @@
text-align: right; text-align: right;
color: var(--text-secondary-color) color: var(--text-secondary-color)
} }
.board-calendar {
height: 100% !important;
margin-top: 0 !important;
.calendar-table {
overflow: hidden;
}
.calendar-table-box {
width: 100%;
}
.calendar-row {
width: 100% !important;
height: 0.7rem !important;
}
.calendar-cell {
&:not(:first-child) {
flex-grow: 1;
flex-shrink: 1;
flex-basis: 0;
}
}
.calendar-entry::after {
content: none;
}
}
.board-header-date {
position: absolute;
line-height: 2rem;
font-size: 2rem;
top: 50%;
margin-top: -1rem;
left: 8rem;
}
.board-twitter {
& > * {
margin-top: -1px !important;
}
}

View file

@ -50,15 +50,16 @@
width: 100%; width: 100%;
position: relative; position: relative;
margin-top: 1rem; margin-top: 1rem;
height: 100vh;
} }
.calendar-table { .calendar-table {
width: 100%; width: 100%;
overflow-x: scroll; overflow: scroll;
overflow-y: visible;
padding-bottom: 1rem; padding-bottom: 1rem;
position: relative; position: relative;
transition: width $transitionTime; transition: width $transitionTime;
height: 100%;
} }
.calendar-edit { .calendar-edit {
@ -301,7 +302,6 @@
flex-wrap: nowrap; flex-wrap: nowrap;
flex-direction: column; flex-direction: column;
width: max-content; width: max-content;
height: max-content;
.calendar-header, .calendar-row { .calendar-header, .calendar-row {
display: flex; display: flex;
@ -325,10 +325,15 @@
line-height: 2rem; line-height: 2rem;
height: 2rem; height: 2rem;
width: 100%; width: 100%;
position: sticky;
top: 0;
background-color: var(--background-secondary-color);
z-index: 5;
border-bottom: solid 1px var(--table-border-color);
.calendar-cell:first-child { .calendar-cell:first-child {
flex-grow: 1;
text-align: center; text-align: center;
width: 6rem;
} }
.calendar-cell:not(:first-child) { .calendar-cell:not(:first-child) {
@ -347,6 +352,10 @@
} }
} }
.calendar-body {
margin-top: -1px;
}
.calendar-row { .calendar-row {
line-height: 2rem; line-height: 2rem;
height: 1.3rem; height: 1.3rem;
@ -438,6 +447,7 @@
} }
.calendar-header { .calendar-header {
.calendar-cell { .calendar-cell {
position: relative; position: relative;
width: 6rem; width: 6rem;

View file

@ -0,0 +1,78 @@
@import "../config";
.wall {
top: 0;
left: 0;
position: absolute;
width: 100%;
height: 100%;
overflow: hidden;
}
.wall-box {
width: 100%;
height: calc(33.3333% - 1rem);
overflow: hidden;
border-bottom: solid 2px var(--input-border-color);
&:first-child {
height: calc(33.3333% + 2rem);
}
&:not(:first-child) {
.calendar-row, .calendar-header {
.calendar-cell:first-child {
display: none;
}
}
}
.calendar-row, .calendar-header {
height: 100%;
display: flex;
flex-direction: column;
line-height: 2rem;
}
.calendar-entry {
top: 0 !important;
margin-top: -0.5rem !important;
bottom: 0 !important;
}
.calendar-entry::after {
content: none;
}
.calendar-table-box {
height: 100%;
}
.calendar-body {
display: flex;
}
.calendar-row {
width: 100%;
flex-basis: 0;
flex-grow: 1;
flex-shrink: 1;
}
.calendar-cell {
flex-basis: 0;
flex-grow: 1;
flex-shrink: 1;
line-height: 2rem;
width: 100%;
}
}
.wall-calendar {
margin-top: 0 !important;
height: 100% !important;
.calendar-table {
overflow: hidden;
padding: 0;
}
}

View file

@ -3,12 +3,13 @@
@include color-setting; @include color-setting;
@import "components/board";
@import "components/calendar"; @import "components/calendar";
@import "components/form"; @import "components/form";
@import "components/menu"; @import "components/menu";
@import "components/overview"; @import "components/overview";
@import "components/table-layout"; @import "components/table-layout";
@import "components/board";
@import "components/wall";
body, html { body, html {
color: var(--text-primary-color); color: var(--text-primary-color);
@ -143,6 +144,7 @@ a {
margin-top: -2rem; margin-top: -2rem;
position: absolute; position: absolute;
top: 48%; top: 48%;
left: 0;
span { span {
display: block; display: block;

View file

@ -64,6 +64,7 @@ fun Application.main() {
account() account()
board() board()
wall()
workGroup() workGroup()
track() track()

View file

@ -2,6 +2,7 @@ package de.kif.backend.route
import de.kif.backend.Configuration import de.kif.backend.Configuration
import de.kif.backend.repository.PostRepository import de.kif.backend.repository.PostRepository
import de.kif.backend.repository.RoomRepository
import de.kif.backend.repository.ScheduleRepository import de.kif.backend.repository.ScheduleRepository
import de.kif.backend.view.respondMain import de.kif.backend.view.respondMain
import de.kif.common.model.Schedule import de.kif.common.model.Schedule
@ -11,10 +12,11 @@ import kotlinx.css.CSSBuilder
import kotlinx.css.Color import kotlinx.css.Color
import kotlinx.html.* import kotlinx.html.*
import java.util.* import java.util.*
import kotlin.math.max
import kotlin.math.min
fun Route.board() { fun Route.board() {
get("/brett") { get("/brett") {
val postList = PostRepository.all().asReversed()
val scheduleList = ScheduleRepository.all().map { val scheduleList = ScheduleRepository.all().map {
it to it.getAbsoluteStartTime() * 60 it to it.getAbsoluteStartTime() * 60
}.sortedBy { it.second } }.sortedBy { it.second }
@ -22,53 +24,114 @@ fun Route.board() {
val referenceTime = Configuration.Schedule.referenceDate.time / 1000 val referenceTime = Configuration.Schedule.referenceDate.time / 1000
val now = referenceTime - (Date().time / 1000) val now = referenceTime - (Date().time / 1000)
val refDate = Configuration.Schedule.referenceDate
val todayDate = Date()
val refDay = refDate.time / (1000 * 60 * 60 * 24)
val todayDay = todayDate.time / (1000 * 60 * 60 * 24)
val day = (todayDay - refDay).toInt()
val list = ScheduleRepository.getByDay(day)
val rooms = RoomRepository.all()
val schedules = list.groupBy { it.room }.mapValues { (_, it) ->
it.associateBy {
it.time
}
}
var max = 0
var min = 24 * 60
for (s in list) {
max = max(max, s.time + s.workGroup.length)
min = min(min, s.time)
}
if (min > max) {
val h1 = max
max = min
min = h1
}
if (true) {
min = min(min, 0)
max = max(max, 24 * 60)
}
min = (min / 60 - 1) * 60
max = (max / 60 + 2) * 60
respondMain(true, true) { theme -> respondMain(true, true) { theme ->
content { content {
div("board") { div("board") {
div("board-schedules") {
attributes["data-reference"] = referenceTime.toString()
div("board-header") { div("board-header") {
+"Arbeitskreise" div("board-running") {
}
div("board-card board-schedule-box") {
div("board-card-header") {
+"Aktuell"
}
table {
for ((schedule, time) in scheduleList) { for ((schedule, time) in scheduleList) {
createBoardSchedule(schedule, time) div("board-schedule") {
attributes["data-id"] = schedule.id.toString()
div("board-schedule-color") {
span {
attributes["style"] = CSSBuilder().apply {
val c = schedule.workGroup.track?.color
if (c != null) {
backgroundColor = Color(c.toString())
} }
}.toString()
} }
} }
div("board-card board-schedule-box") { div("board-schedule-time") {
div("board-card-header") { attributes["data-time"] = time.toString()
+"Später" attributes["data-duration"] = schedule.workGroup.length.toString()
}
table {
for ((schedule, time) in scheduleList) { val startTime = (time % MINUTES_OF_DAY).let {
createBoardSchedule(schedule, time) if (it < 0) it + MINUTES_OF_DAY else it
}
} }
val sm = (startTime % 60).toString().padStart(2, '0')
val sh = (startTime / 60).toString().padStart(2, '0')
val startTimeString = "$sh:$sm"
val endTime = ((time + schedule.workGroup.length) % MINUTES_OF_DAY).let {
if (it < 0) it + MINUTES_OF_DAY else it
} }
val em = (endTime % 60).toString().padStart(2, '0')
val eh = (endTime / 60).toString().padStart(2, '0')
val endTimeString = "$eh:$em"
+"$startTimeString - $endTimeString"
} }
div("board-posts") { div("board-schedule-name") {
div("board-header") { +schedule.workGroup.name
+"Neuigkeiten"
}
for (post in postList) {
createPost(post, false, "board-card board-post")
}
} }
div("board-schedule-room") {
+schedule.room.name
}
}
}
}
div("board-logo") {
img("KIF 47.0", "/static/images/logo.svg")
div("board-header-date") {
}
}
}
div("board-content") {
div("board-calendar calendar") {
div("calendar-table") {
renderCalendar(
CalendarOrientation.ROOM_TO_TIME,
day,
min,
max,
rooms,
schedules
)
}
}
div("board-twitter") { div("board-twitter") {
div("board-header") {
+"Twitter"
}
div("board-card") {
unsafe { unsafe {
raw(""" raw("""
<a <a
@ -91,50 +154,3 @@ fun Route.board() {
} }
} }
} }
private fun TABLE.createBoardSchedule(schedule: Schedule, time: Int) {
tr("board-schedule") {
attributes["data-id"] = schedule.id.toString()
td("board-schedule-color") {
span {
attributes["style"] = CSSBuilder().apply {
val c = schedule.workGroup.track?.color
if (c != null) {
backgroundColor = Color(c.toString())
}
}.toString()
}
}
td("board-schedule-time") {
attributes["data-time"] = time.toString()
attributes["data-duration"] = schedule.workGroup.length.toString()
val startTime = (time % MINUTES_OF_DAY).let {
if (it < 0) it + MINUTES_OF_DAY else it
}
val sm = (startTime % 60).toString().padStart(2, '0')
val sh = (startTime / 60).toString().padStart(2, '0')
val startTimeString = "$sh:$sm"
val endTime = ((time + schedule.workGroup.length) % MINUTES_OF_DAY).let {
if (it < 0) it + MINUTES_OF_DAY else it
}
val em = (endTime % 60).toString().padStart(2, '0')
val eh = (endTime / 60).toString().padStart(2, '0')
val endTimeString = "$eh:$em"
+"$startTimeString - $endTimeString"
}
td("board-schedule-name") {
+schedule.workGroup.name
}
td("board-schedule-room") {
+schedule.room.name
}
}
}

View file

@ -58,7 +58,7 @@ private fun DIV.calendarCell(schedule: Schedule?) {
} }
} }
private fun DIV.renderCalendar( fun DIV.renderCalendar(
orientation: CalendarOrientation, orientation: CalendarOrientation,
day: Int, day: Int,
from: Int, from: Int,

View file

@ -0,0 +1,79 @@
package de.kif.backend.route
import de.kif.backend.repository.RoomRepository
import de.kif.backend.repository.ScheduleRepository
import de.kif.backend.view.respondMain
import de.kif.common.model.Room
import de.kif.common.model.Schedule
import io.ktor.routing.Route
import io.ktor.routing.get
import kotlinx.html.div
import kotlin.math.max
import kotlin.math.min
data class WallData(
val number: Int,
val schedules: Map<Room, Map<Int, Schedule>>,
val max: Int,
val min: Int
)
suspend fun genWallData(day: Int): WallData {
val list = ScheduleRepository.getByDay(day)
val schedules = RoomRepository.all().associateWith { emptyMap<Int, Schedule>() } + list.groupBy { it.room }.mapValues { (_, it) ->
it.associateBy {
it.time
}
}
var max = 0
var min = 24 * 60
for (s in list) {
max = max(max, s.time + s.workGroup.length)
min = min(min, s.time)
}
if (min > max) {
val h1 = max
max = min
min = h1
}
min = (min / 60 - 1) * 60
max = (max / 60 + 2) * 60
return WallData(day, schedules, max, min)
}
fun Route.wall() {
get("/wand") {
val days = (0..2).map { genWallData(it) }
val min = days.map { it.min }.min() ?: days.first().min
val max = days.map { it.max }.max() ?: days.first().max
respondMain(true, true) {
content {
div("wall") {
for (day in days) {
div("wall-box") {
div("wall-calendar calendar") {
div("calendar-table") {
renderCalendar(
CalendarOrientation.TIME_TO_ROOM,
day.number,
min,
max,
day.schedules.keys.toList().sortedBy { it.id },
day.schedules
)
}
}
}
}
}
}
}
}
}

View file

@ -49,33 +49,62 @@ data class Backup(
suspend fun import(data: String) { suspend fun import(data: String) {
val backup = Message.json.parse(serializer(), data) val backup = Message.json.parse(serializer(), data)
backup.users.forEach { UserRepository.create(it) } backup.users.forEach { UserRepository.create(it); println("Import user ${it.username}") }
backup.posts.forEach { PostRepository.create(it) } backup.posts.forEach { PostRepository.create(it); println("Import post") }
backup.rooms.forEach { RoomRepository.create(it) } val oldRooms = RoomRepository.all()
val roomMap = RoomRepository.all().associateWith { it.id!! } val newRooms = backup.rooms.filterNot { oldRooms.any { i -> i.equalsIgnoreId(it) } }
val roomMap = (oldRooms.associateWith { it.id!! } +
newRooms.map {
val id = RoomRepository.create(it)
println("Import room ${it.name}")
it to id
}).toList()
backup.tracks.forEach { TrackRepository.create(it) } val oldTracks = TrackRepository.all()
val trackMap =TrackRepository.all().associateWith { it.id!! } val newTracks = backup.tracks.filterNot { oldTracks.any { i -> i.equalsIgnoreId(it) } }
val trackMap = (oldTracks.associateWith { it.id!! } +
newTracks.map {
val id = TrackRepository.create(it)
println("Import track ${it.name}")
it to id
}).toList()
backup.workGroups.forEach { val oldWorkGroups = WorkGroupRepository.all()
val newWorkGroups = backup.workGroups.filterNot { oldWorkGroups.any { i -> i.equalsIgnoreId(it) } }
val workGroupMap = (oldWorkGroups.associateWith { it.id!! } +
newWorkGroups.mapNotNull {
var workGroup = it var workGroup = it
val track = workGroup.track val track = workGroup.track
if (track != null) { if (track != null) {
workGroup = workGroup.copy(track = track.copy(id = trackMap[track] ?: return@forEach)) workGroup = workGroup.copy(track = track.copy(id = trackMap.firstOrNull { (i,_) -> i.equalsIgnoreId(track) }?.second ?: run {
println("Cannot import work group, due to missing track")
return@mapNotNull null
}))
} }
WorkGroupRepository.create(workGroup) val id = WorkGroupRepository.create(workGroup)
} println("Import work group ${it.name}")
val workGroupMap = WorkGroupRepository.all().associateWith { it.id!! } it to id
}).toList()
backup.schedules.forEach { val oldSchedules = ScheduleRepository.all()
val newSchedules = backup.schedules.filterNot { oldSchedules.any { i -> i.equalsIgnoreId(it) } }
newSchedules.forEach {
ScheduleRepository.create( ScheduleRepository.create(
it.copy( it.copy(
room = it.room.copy(id = roomMap[it.room] ?: return@forEach), room = it.room.copy(id = roomMap.firstOrNull { (i,_) -> i.equalsIgnoreId(it.room) }?.second ?: run {
workGroup = it.workGroup.copy(id = workGroupMap[it.workGroup] ?: return@forEach) println("Cannot import schedule, due to missing room")
return@forEach
}),
workGroup = it.workGroup.copy(id = workGroupMap.firstOrNull { (i,_) -> i.equalsIgnoreId(it.workGroup) }?.second ?: run {
println("Cannot import schedule, due to missing work group")
return@forEach
})
) )
) )
println("Import schedule day=${it.day}, time=${it.time}, work group=${it.workGroup.name}")
} }
} }