Add dynamic resizing of calendar

This commit is contained in:
Lars Westermann 2019-06-07 21:19:52 +02:00
parent 990cdaf1a4
commit dce2567160
Signed by: lars.westermann
GPG key ID: 9D417FA5BB9D5E1D
28 changed files with 476 additions and 254 deletions

View file

@ -14,10 +14,14 @@ class WebSocketClient() {
private val url = "ws://${window.location.host}/"
private lateinit var ws: WebSocket
private var reconnect = false
@Suppress("UNUSED_PARAMETER")
private fun onOpen(event: Event) {
console.log("Connected!")
if (reconnect) {
window.location.reload()
}
}
private fun onMessage(messageEvent: MessageEvent) {
@ -37,6 +41,7 @@ class WebSocketClient() {
@Suppress("UNUSED_PARAMETER")
private fun onClose(event: Event) {
console.log("Disconnected!")
reconnect = true
async(1000) {
connect()
}

View file

@ -6,31 +6,6 @@ import kotlin.coroutines.CoroutineContext
import kotlin.coroutines.EmptyCoroutineContext
import kotlin.coroutines.startCoroutine
operator fun HTMLCollection.iterator() = object : Iterator<HTMLElement> {
private var index = 0
override fun hasNext(): Boolean {
return index < this@iterator.length
}
override fun next(): HTMLElement {
return this@iterator.get(index++) as HTMLElement
}
}
operator fun NodeList.iterator() = object : Iterator<Node> {
private var index = 0
override fun hasNext(): Boolean {
return index < this@iterator.length
}
override fun next(): Node {
return this@iterator.get(index++)!!
}
}
fun launch(context: CoroutineContext = EmptyCoroutineContext, block: suspend () -> Unit) =
block.startCoroutine(Continuation(context) { result ->
result.onFailure { exception ->

View file

@ -1,6 +1,5 @@
package de.kif.frontend.views
import de.kif.frontend.iterator
import de.kif.frontend.launch
import de.kif.frontend.repository.WorkGroupRepository
import de.westermann.kobserve.event.EventListener
@ -8,6 +7,7 @@ import de.westermann.kwebview.View
import de.westermann.kwebview.async
import de.westermann.kwebview.components.*
import de.westermann.kwebview.createHtmlView
import de.westermann.kwebview.iterator
import org.w3c.dom.*
import kotlin.browser.document

View file

@ -1,7 +1,7 @@
package de.kif.frontend.views.board
import de.kif.common.formatDateTime
import de.kif.frontend.iterator
import de.westermann.kwebview.iterator
import de.westermann.kwebview.interval
import org.w3c.dom.HTMLElement
import org.w3c.dom.get

View file

@ -1,22 +1,23 @@
package de.kif.frontend.views.calendar
import de.kif.frontend.iterator
import de.kif.frontend.launch
import de.kif.frontend.repository.RoomRepository
import de.kif.frontend.repository.ScheduleRepository
import de.westermann.kwebview.View
import de.westermann.kwebview.createHtmlView
import de.westermann.kwebview.iterator
import org.w3c.dom.*
import kotlin.browser.document
import kotlin.browser.window
class Calendar(calendar: HTMLElement) : View(calendar) {
var calendarEntries: List<CalendarEntry> = emptyList()
var calendarCells: List<CalendarCell> = emptyList()
val day: Int
val day: Int = calendar.dataset["day"]?.toIntOrNull() ?: -1
val htmlTag = document.body as HTMLElement
private val htmlTag = document.body as HTMLElement
val calendarTable = calendar.getElementsByClassName("calendar-table")[0] as HTMLElement
val calendarTableHeader = calendar.getElementsByClassName("calendar-header")[0] as HTMLElement
fun scrollVerticalBy(pixel: Double, scrollBehavior: ScrollBehavior = ScrollBehavior.SMOOTH) {
htmlTag.scrollBy(ScrollToOptions(0.0, pixel, scrollBehavior))
@ -34,58 +35,22 @@ class Calendar(calendar: HTMLElement) : View(calendar) {
calendarTable.scrollTo(ScrollToOptions(pixel, 0.0, scrollBehavior))
}
val editable = calendar.dataset["editable"]?.toBoolean() ?: false
val body = CalendarBody(this, calendar.getElementsByClassName("calendar-body")[0] as HTMLElement)
init {
val editable = calendar.dataset["editable"]?.toBoolean() ?: false
day = calendar.dataset["day"]?.toIntOrNull() ?: -1
calendarEntries = document.getElementsByClassName("calendar-entry")
.iterator().asSequence().map { CalendarEntry(this, it) }.onEach { it.editable = editable }.toList()
calendarCells = document.getElementsByClassName("calendar-cell")
.iterator().asSequence().filter { it.dataset["time"] != null }.map(::CalendarCell).toList()
if (editable) {
CalendarEdit(this, calendar.querySelector(".calendar-edit") as HTMLElement)
}
ScheduleRepository.onCreate {
launch {
val schedule = ScheduleRepository.get(it) ?: throw NoSuchElementException()
calendarEntries += CalendarEntry.create(this, schedule).also { it.editable = editable }
}
}
ScheduleRepository.onUpdate {
launch {
val schedule = ScheduleRepository.get(it) ?: throw NoSuchElementException()
var found = false
for (entry in calendarEntries) {
if (entry.scheduleId == it) {
entry.load(schedule)
found = true
}
}
if (!found) {
calendarEntries += CalendarEntry.create(this, schedule).also { it.editable = editable }
}
}
}
ScheduleRepository.onDelete {
for (entry in calendarEntries) {
if (entry.scheduleId == it) {
entry.html.remove()
calendarEntries -= entry
}
}
}
(document.getElementById("calendar-check-constraints") as? HTMLElement)?.let { wrap(it) }?.onClick?.addListener {
(document.getElementById("calendar-check-constraints") as? HTMLElement)?.let { wrap(it) }
?.onClick?.addListener {
launch {
val errors = ScheduleRepository.checkConstraints()
println(errors)
for ((s, l) in errors.map) {
for (entry in calendarEntries) {
for (entry in body.calendarEntries) {
if (entry.scheduleId == s) {
entry.error = l.isNotEmpty()
}
@ -106,6 +71,29 @@ class Calendar(calendar: HTMLElement) : View(calendar) {
it.preventDefault()
}
RoomRepository.onCreate {
val cell = createHtmlView<HTMLElement>()
cell.dataset["room"] = it.toString()
cell.classList.add("calendar-cell")
calendarTableHeader.appendChild(cell)
launch {
val room = RoomRepository.get(it) ?: return@launch
val span = createHtmlView<HTMLSpanElement>()
span.textContent = room.name
cell.appendChild(span)
}
}
RoomRepository.onDelete {
val str = it.toString()
for (element in calendarTableHeader.children.iterator()) {
if (element.dataset["room"] == str) {
element.remove()
}
}
}
}
}

View file

@ -0,0 +1,161 @@
package de.kif.frontend.views.calendar
import de.kif.frontend.launch
import de.kif.frontend.repository.ScheduleRepository
import de.westermann.kwebview.ViewCollection
import de.westermann.kwebview.interval
import de.westermann.kwebview.iterator
import org.w3c.dom.HTMLElement
import kotlin.browser.document
import kotlin.js.Date
import kotlin.math.max
import kotlin.math.min
class CalendarBody(val calendar: Calendar, view: HTMLElement) : ViewCollection<CalendarRow>(view) {
val editable = calendar.editable
val day = calendar.day
var calendarEntries: List<CalendarEntry> = emptyList()
val calendarCells: List<CalendarCell>
get() = iterator().asSequence().flatten().toList()
private suspend fun updateRows(startTime: Int? = null, length: Int = 0) {
if (calendarEntries.isEmpty() && startTime == null && !editable) {
for (row in iterator().asSequence().toList()) {
remove(row)
}
return
}
var max: Int
var min: Int
if (startTime != null) {
min = startTime
max = startTime + length
} else {
min = calendarEntries.first().startTime
max = calendarEntries.first().startTime + calendarEntries.first().length
}
for (entry in calendarEntries) {
max = max(max, entry.startTime + entry.length)
min = min(min, entry.startTime)
}
if (min > max) {
val h1 = max
max = min
min = h1
}
if (editable) {
min = min(min, 0)
max = max(max, 24 * 60)
}
min = (min / 60 - 1) * 60
max = (max / 60 + 2) * 60
while (isNotEmpty() && min > first().time) {
remove(first())
}
while (isNotEmpty() && max < last().time) {
remove(last())
}
if (isEmpty()) {
+CalendarRow.create(this, min)
}
while (min < first().time) {
prepand(CalendarRow.create(this, first().time - 15))
}
while (max > last().time + 15) {
append(CalendarRow.create(this, last().time + 15))
}
}
init {
calendarEntries = document.getElementsByClassName("calendar-entry")
.iterator().asSequence().map { CalendarEntry(this, it) }.toList()
wrapContent {
CalendarRow(this, it)
}
ScheduleRepository.onCreate {
launch {
val schedule = ScheduleRepository.get(it) ?: throw NoSuchElementException()
updateRows(schedule.time, schedule.workGroup.length)
calendarEntries += CalendarEntry.create(this, schedule)
}
}
ScheduleRepository.onUpdate {
launch {
val schedule = ScheduleRepository.get(it) ?: throw NoSuchElementException()
updateRows(schedule.time, schedule.workGroup.length)
var found = false
for (entry in calendarEntries) {
if (entry.scheduleId == it) {
entry.load(schedule)
found = true
}
}
if (!found) {
calendarEntries += CalendarEntry.create(this, schedule)
}
updateRows()
}
}
ScheduleRepository.onDelete {
for (entry in calendarEntries) {
if (entry.scheduleId == it) {
entry.html.remove()
calendarEntries -= entry
launch {
updateRows()
}
}
}
}
interval(1000) {
val currentTime = Date().let {
it.getHours() * 60 + it.getMinutes()
}
val rowTime = (currentTime / 15) * 15
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}"
} else {
for (str in row.classList) {
if ("now" in str) {
row.classList -= str
}
}
}
}
}
}
}

View file

@ -3,12 +3,17 @@ package de.kif.frontend.views.calendar
import de.kif.common.model.Room
import de.kif.frontend.repository.RoomRepository
import de.westermann.kwebview.ViewCollection
import de.westermann.kwebview.createHtmlView
import org.w3c.dom.HTMLDivElement
import org.w3c.dom.HTMLElement
import org.w3c.dom.get
import org.w3c.dom.set
import kotlin.browser.document
class CalendarCell(row: CalendarRow, view: HTMLElement) : ViewCollection<CalendarEntry>(view) {
val day = row.day
val time = row.time
class CalendarCell(view: HTMLElement) : ViewCollection<CalendarEntry>(view) {
val day = dataset["day"]?.toIntOrNull() ?: 0
val time = dataset["time"]?.toIntOrNull() ?: 0
val roomId = dataset["room"]?.toLongOrNull() ?: 0
private lateinit var room: Room
@ -22,7 +27,13 @@ class CalendarCell(view: HTMLElement) : ViewCollection<CalendarEntry>(view) {
return room
}
init {
(view.getElementsByClassName("calendar-link")[0] as? HTMLElement)?.remove()
companion object {
fun create(row: CalendarRow, roomId: Long): CalendarCell {
val view = createHtmlView<HTMLDivElement>()
view.classList.add("calendar-cell")
view.dataset["room"] = roomId.toString()
return CalendarCell(row, view)
}
}
}

View file

@ -1,10 +1,9 @@
package de.kif.frontend.views.calendar
import de.kif.common.CALENDAR_GRID_WIDTH
import de.kif.common.model.Room
import de.kif.common.model.Schedule
import de.kif.common.model.WorkGroup
import de.kif.frontend.iterator
import de.westermann.kwebview.iterator
import de.kif.frontend.launch
import de.kif.frontend.repository.RepositoryDelegate
import de.kif.frontend.repository.ScheduleRepository
@ -17,7 +16,7 @@ import kotlin.dom.appendText
import kotlin.dom.isText
import kotlin.js.Date
class CalendarEntry(private val calendar: Calendar, view: HTMLElement) : View(view) {
class CalendarEntry(private val calendar: CalendarBody, view: HTMLElement) : View(view) {
private lateinit var mouseDelta: Point
private var newCell: CalendarCell? = null
@ -31,11 +30,15 @@ class CalendarEntry(private val calendar: Calendar, view: HTMLElement) : View(vi
private lateinit var workGroup: WorkGroup
var startTime = dataset["time"]?.toIntOrNull() ?: 0
var length = dataset["length"]?.toIntOrNull() ?: 0
var pending by classList.property("pending")
var error by classList.property("error")
private var nextScroll = 0.0
var editable: Boolean = false
val editable: Boolean
get() = calendar.editable
var moveLookRoom: Long? = null
var moveLookTime: Int? = null
@ -48,7 +51,7 @@ class CalendarEntry(private val calendar: Calendar, view: HTMLElement) : View(vi
}
if (cell != null) {
if (moveLookRoom != null && cell.roomId != moveLookRoom) {
if (moveLookRoom != null && cell.roomId != moveLookRoom) {
return
}
if (moveLookTime != null && cell.time != moveLookTime) {
@ -70,29 +73,29 @@ class CalendarEntry(private val calendar: Calendar, view: HTMLElement) : View(vi
return@async
}
val width = calendar.calendarTable.clientWidth
val width = calendar.calendar.calendarTable.clientWidth
val height = window.innerHeight
val rect = html.getBoundingClientRect()
if (rect.left < 0.0) {
nextScroll = now + 500.0
calendar.scrollHorizontalBy(rect.left - 80.0)
calendar.calendar.scrollHorizontalBy(rect.left - 80.0)
} else if (rect.right > width) {
nextScroll = now + 0.500
calendar.scrollHorizontalBy(rect.right - width + 50.0)
calendar.calendar.scrollHorizontalBy(rect.right - width + 50.0)
}
if (rect.top < 20.0) {
nextScroll = now + 500.0
calendar.scrollVerticalBy(rect.top - 50.0)
calendar.calendar.scrollVerticalBy(rect.top - 50.0)
} else if (rect.bottom > height - 20.0) {
nextScroll = now + 500.0
calendar.scrollVerticalBy(rect.bottom - height + 50.0)
calendar.calendar.scrollVerticalBy(rect.bottom - height + 50.0)
}
}
launch {
calendarTools.setName(cell.getRoom(), cell.time)
calendarTools?.setName(cell.getRoom(), cell.time)
}
newCell = cell
@ -153,7 +156,7 @@ class CalendarEntry(private val calendar: Calendar, view: HTMLElement) : View(vi
)
}
private val calendarTools = CalendarTools(this)
private val calendarTools = if (editable) CalendarTools(this) else null
init {
onMouseDown { event ->
@ -181,11 +184,13 @@ class CalendarEntry(private val calendar: Calendar, view: HTMLElement) : View(vi
event.stopPropagation()
}
html.appendChild(calendarTools.html)
if (calendarTools != null) {
html.appendChild(calendarTools.html)
launch {
val s = schedule.get()
calendarTools.update(s)
launch {
val s = schedule.get()
calendarTools.update(s)
}
}
}
@ -205,7 +210,10 @@ class CalendarEntry(private val calendar: Calendar, view: HTMLElement) : View(vi
}
load(schedule.workGroup)
calendarTools.update(schedule)
calendarTools?.update(schedule)
startTime = schedule.time
length = schedule.workGroup.length
val time = schedule.time / CALENDAR_GRID_WIDTH * CALENDAR_GRID_WIDTH
val cell = calendar.calendarCells.find {
@ -247,7 +255,7 @@ class CalendarEntry(private val calendar: Calendar, view: HTMLElement) : View(vi
}
companion object {
fun create(calendar: Calendar, schedule: Schedule): CalendarEntry {
fun create(calendar: CalendarBody, schedule: Schedule): CalendarEntry {
val entry = CalendarEntry(calendar, createHtmlView())
entry.load(schedule)
@ -255,7 +263,7 @@ class CalendarEntry(private val calendar: Calendar, view: HTMLElement) : View(vi
return entry
}
fun create(calendar: Calendar, workGroup: WorkGroup): CalendarEntry {
fun create(calendar: CalendarBody, workGroup: WorkGroup): CalendarEntry {
val entry = CalendarEntry(calendar, createHtmlView())
entry.load(workGroup)

View file

@ -0,0 +1,65 @@
package de.kif.frontend.views.calendar
import de.kif.frontend.repository.RoomRepository
import de.westermann.kwebview.ViewCollection
import de.westermann.kwebview.createHtmlView
import org.w3c.dom.HTMLDivElement
import org.w3c.dom.HTMLElement
import org.w3c.dom.HTMLSpanElement
import org.w3c.dom.set
class CalendarRow(calendar: CalendarBody, view: HTMLElement) : ViewCollection<CalendarCell>(view) {
val day = calendar.day
val time = dataset["time"]?.toIntOrNull() ?: 0
init {
wrapContent {
CalendarCell(this, it)
}
RoomRepository.onCreate {
+CalendarCell.create(this, it)
}
RoomRepository.onDelete { id ->
find { it.roomId == id }?.let(this@CalendarRow::remove)
}
}
companion object {
suspend fun create(calendar: CalendarBody, time: Int): CalendarRow {
val view = createHtmlView<HTMLDivElement>()
view.classList.add("calendar-row")
view.dataset["time"] = time.toString()
val row = CalendarRow(calendar, view)
val rowHeader = createHtmlView<HTMLElement>()
rowHeader.classList.add("calendar-cell")
if (time % 60 == 0) {
val span = createHtmlView<HTMLSpanElement>()
val t = (time % (60 * 24)).let {
if (it < 0) it + 60 * 24 else it
}
val hours = (t / 60).toString().padStart(2, '0')
span.textContent = "$hours:00"
rowHeader.appendChild(span)
}
row.html.appendChild(rowHeader)
row.html
val rooms = RoomRepository.all()
for (room in rooms) {
if (room.id != null) {
row += CalendarCell.create(row, room.id)
}
}
return row
}
}
}

View file

@ -50,7 +50,7 @@ class CalendarWorkGroup(
}
onMouseDown {
CalendarEntry.create(calendar, workGroup)
CalendarEntry.create(calendar.body, workGroup)
}
}
}

View file

@ -1,6 +1,6 @@
package de.kif.frontend.views.overview
import de.kif.frontend.iterator
import de.westermann.kwebview.iterator
import de.kif.frontend.launch
import de.kif.frontend.repository.PostRepository
import de.westermann.kobserve.event.subscribe

View file

@ -1,7 +1,7 @@
package de.kif.frontend.views.table
import de.kif.common.SearchElement
import de.kif.frontend.iterator
import de.westermann.kwebview.iterator
import de.kif.frontend.launch
import de.kif.frontend.repository.RepositoryDelegate
import de.kif.frontend.repository.RoomRepository

View file

@ -1,6 +1,6 @@
package de.kif.frontend.views.table
import de.kif.frontend.iterator
import de.westermann.kwebview.iterator
import de.westermann.kwebview.components.InputView
import org.w3c.dom.HTMLFormElement
import org.w3c.dom.HTMLInputElement

View file

@ -3,7 +3,7 @@ package de.kif.frontend.views.table
import de.kif.common.SearchElement
import de.kif.common.model.Language
import de.kif.common.model.Track
import de.kif.frontend.iterator
import de.westermann.kwebview.iterator
import de.kif.frontend.launch
import de.kif.frontend.repository.RepositoryDelegate
import de.kif.frontend.repository.TrackRepository

View file

@ -1,10 +1,17 @@
package de.westermann.kwebview
import de.westermann.kobserve.event.EventListener
import de.westermann.kobserve.Property
import de.westermann.kobserve.ReadOnlyProperty
import de.westermann.kobserve.event.EventListener
import de.westermann.kobserve.property.property
import org.w3c.dom.DOMTokenList
import kotlin.collections.Iterable
import kotlin.collections.Iterator
import kotlin.collections.MutableMap
import kotlin.collections.contains
import kotlin.collections.minusAssign
import kotlin.collections.mutableMapOf
import kotlin.collections.set
/**
* Represents the css classes of an html element.
@ -12,7 +19,7 @@ import org.w3c.dom.DOMTokenList
* @author lars
*/
class ClassList(
private val list: DOMTokenList
private val list: DOMTokenList
) : Iterable<String> {
private val bound: MutableMap<String, Bound> = mutableMapOf()
@ -74,11 +81,11 @@ class ClassList(
* Set css class present.
*/
operator fun set(clazz: String, present: Boolean) =
if (present) {
add(clazz)
} else {
remove(clazz)
}
if (present) {
add(clazz)
} else {
remove(clazz)
}
/**
* Toggle css class.
@ -92,9 +99,9 @@ class ClassList(
set(clazz, property.value)
bound[clazz] = Bound(property,
property.onChange.reference {
list.toggle(clazz, property.value)
}
property.onChange.reference {
list.toggle(clazz, property.value)
}
)
}
@ -130,8 +137,14 @@ class ClassList(
override fun toString(): String = list.value
fun clear() {
for (element in this) {
remove(element)
}
}
private data class Bound(
val property: ReadOnlyProperty<Boolean>,
val reference: EventListener<Unit>?
val property: ReadOnlyProperty<Boolean>,
val reference: EventListener<Unit>?
)
}

View file

@ -8,7 +8,15 @@ import kotlin.dom.clear
*/
abstract class ViewCollection<V : View>(view: HTMLElement = createHtmlView()) : View(view), Collection<V> {
private val children: MutableList<V> = mutableListOf()
protected val children: MutableList<V> = mutableListOf()
protected inline fun <reified T : HTMLElement> wrapContent(transform: (T) -> V) {
for (element in html.children.iterator()) {
children += transform(element as T)
}
}
protected inline fun wrapContent(transform: (HTMLElement) -> V) = wrapContent<HTMLElement>(transform)
fun append(view: V) {
children += view

View file

@ -1,8 +1,7 @@
package de.westermann.kwebview
import de.westermann.kobserve.event.EventHandler
import org.w3c.dom.DOMRect
import org.w3c.dom.HTMLElement
import org.w3c.dom.*
import org.w3c.dom.events.Event
import org.w3c.dom.events.EventListener
import org.w3c.dom.events.MouseEvent
@ -11,6 +10,30 @@ import org.w3c.xhr.XMLHttpRequest
import kotlin.browser.document
import kotlin.browser.window
operator fun HTMLCollection.iterator() = object : Iterator<HTMLElement> {
private var index = 0
override fun hasNext(): Boolean {
return index < this@iterator.length
}
override fun next(): HTMLElement {
return this@iterator.get(index++) as HTMLElement
}
}
operator fun NodeList.iterator() = object : Iterator<Node> {
private var index = 0
override fun hasNext(): Boolean {
return index < this@iterator.length
}
override fun next(): Node {
return this@iterator.get(index++)!!
}
}
inline fun <reified V : HTMLElement> createHtmlView(tag: String? = null): V {
var tagName: String
if (tag != null) {

View file

@ -384,7 +384,7 @@
border-left: none;
}
&:nth-child(4n + 2) .calendar-cell::before {
&:nth-child(4n + 1) .calendar-cell::before {
border-top: solid 1px var(--table-border-color);
}
@ -424,6 +424,10 @@
&.time-to-room {
flex-direction: row;
.calendar-body {
display: flex;
}
.calendar-header, .calendar-row {
flex-direction: column;
line-height: 3rem;
@ -468,7 +472,7 @@
}
}
&:nth-child(2n + 2) .calendar-cell::before {
&:nth-child(4n + 1) .calendar-cell::before {
content: '';
height: 100%;
left: 0;

View file

@ -40,10 +40,6 @@ fun Route.account() {
val wikiSections = WikiImporter.loadSections()
respondMain {
menuTemplate {
this.user = user
active = MenuTemplate.Tab.ACCOUNT
}
content {
h1 { +"Account" }
div {

View file

@ -6,7 +6,6 @@ import de.kif.backend.Configuration
import de.kif.backend.isAuthenticated
import de.kif.backend.repository.RoomRepository
import de.kif.backend.repository.ScheduleRepository
import de.kif.backend.view.MenuTemplate
import de.kif.backend.view.respondMain
import de.kif.common.CALENDAR_GRID_WIDTH
import de.kif.common.model.Permission
@ -51,6 +50,8 @@ private fun DIV.calendarCell(schedule: Schedule?) {
}.toString()
attributes["data-language"] = schedule.workGroup.language.code
attributes["data-id"] = schedule.id.toString()
attributes["data-time"] = schedule.time.toString()
attributes["data-length"] = schedule.workGroup.length.toString()
+schedule.workGroup.name
}
@ -81,6 +82,8 @@ private fun DIV.renderCalendar(
for (room in rooms) {
div("calendar-cell") {
attributes["data-room"] = room.id.toString()
span {
+room.name
}
@ -88,43 +91,46 @@ private fun DIV.renderCalendar(
}
}
for (i in 0 until minutesOfDay / CALENDAR_GRID_WIDTH) {
val time = ((i * CALENDAR_GRID_WIDTH + from) % MINUTES_OF_DAY).let {
if (it < 0) it + MINUTES_OF_DAY else it
}
val minutes = (time % 60).toString().padStart(2, '0')
val hours = (time / 60).toString().padStart(2, '0')
val timeString = "$hours:$minutes"
div("calendar-body") {
for (i in 0 until minutesOfDay / CALENDAR_GRID_WIDTH) {
val time = ((i * CALENDAR_GRID_WIDTH + from) % MINUTES_OF_DAY).let {
if (it < 0) it + MINUTES_OF_DAY else it
}
val minutes = (time % 60).toString().padStart(2, '0')
val hours = (time / 60).toString().padStart(2, '0')
val timeString = "$hours:$minutes"
val start = i * CALENDAR_GRID_WIDTH + from
val end = (i + 1) * CALENDAR_GRID_WIDTH + from - 1
val start = i * CALENDAR_GRID_WIDTH + from
val end = (i + 1) * CALENDAR_GRID_WIDTH + from - 1
var rowClass = "calendar-row"
var rowClass = "calendar-row"
if (currentTime in start..end) {
rowClass += " calendar-now calendar-now-${currentTime - start}"
}
div(rowClass) {
div("calendar-cell") {
if (time % gridLabelWidth == 0) {
span {
+timeString
}
}
if (currentTime in start..end) {
rowClass += " calendar-now calendar-now-${currentTime - start}"
}
for (room in rooms) {
div(rowClass) {
attributes["data-time"] = start.toString()
attributes["data-day"] = day.toString()
div("calendar-cell") {
attributes["data-time"] = start.toString()
attributes["data-room"] = room.id.toString()
attributes["data-day"] = day.toString()
title = room.name + " - " + timeString
if (time % gridLabelWidth == 0) {
span {
+timeString
}
}
}
val schedule = (start..end).mapNotNull { schedules[room]?.get(it) }.firstOrNull()
for (room in rooms) {
div("calendar-cell") {
attributes["data-room"] = room.id.toString()
calendarCell(schedule)
title = room.name + " - " + timeString
val schedule = (start..end).mapNotNull { schedules[room]?.get(it) }.firstOrNull()
calendarCell(schedule)
}
}
}
}
@ -167,9 +173,12 @@ fun Route.calendar() {
val day = call.parameters["day"]?.toIntOrNull() ?: return@get
val range = ScheduleRepository.getDayRange()
/*
if (!editable && day !in range) {
return@get
}
*/
val rooms = RoomRepository.all()
@ -177,8 +186,8 @@ fun Route.calendar() {
CalendarOrientation.values().find { it.name == name }
} ?: CalendarOrientation.ROOM_TO_TIME
val h = ScheduleRepository.getByDay(day)
val schedules = h.groupBy { it.room }.mapValues { (_, it) ->
val list = ScheduleRepository.getByDay(day)
val schedules = list.groupBy { it.room }.mapValues { (_, it) ->
it.associateBy {
it.time
}
@ -186,7 +195,7 @@ fun Route.calendar() {
var max = 0
var min = 24 * 60
for (s in h) {
for (s in list) {
max = max(max, s.time + s.workGroup.length)
min = min(min, s.time)
}
@ -205,6 +214,11 @@ fun Route.calendar() {
min = (min / 60 - 1) * 60
max = (max / 60 + 2) * 60
if (!editable && list.isEmpty()) {
min = 0
max = 0
}
val refDate = DateTime(Configuration.Schedule.referenceDate.time)
val date = refDate + day.days
val dateString = DateFormat("EEEE, d. MMMM")
@ -212,15 +226,7 @@ fun Route.calendar() {
.format(date)
respondMain {
menuTemplate {
this.user = user
active = MenuTemplate.Tab.CALENDAR
}
content {
if (rooms.isEmpty()) {
return@content
}
div("header") {
div("header-left") {
if (editable || day - 1 > range.start) {

View file

@ -2,12 +2,10 @@ package de.kif.backend.route
import de.kif.backend.PortalSession
import de.kif.backend.UserPrinciple
import de.kif.backend.view.MainTemplate
import de.kif.backend.view.respondMain
import io.ktor.application.call
import io.ktor.auth.authenticate
import io.ktor.auth.principal
import io.ktor.html.respondHtmlTemplate
import io.ktor.response.respondRedirect
import io.ktor.routing.Route
import io.ktor.routing.get

View file

@ -78,10 +78,6 @@ fun Route.overview() {
val postList = PostRepository.all().asReversed()
respondMain {
menuTemplate {
this.user = user
active = MenuTemplate.Tab.BOARD
}
content {
div("overview") {
div("overview-main") {
@ -118,10 +114,6 @@ fun Route.overview() {
}
respondMain {
menuTemplate {
this.user = user
active = MenuTemplate.Tab.BOARD
}
content {
div("overview") {
createPost(post, editable)
@ -135,10 +127,6 @@ fun Route.overview() {
val postId = call.parameters["id"]?.toLongOrNull() ?: return@get
val editPost = PostRepository.get(postId) ?: return@get
respondMain {
menuTemplate {
this.user = user
active = MenuTemplate.Tab.BOARD
}
content {
h1 { +"Edit post" }
div("post-edit-container") {
@ -365,10 +353,6 @@ fun Route.overview() {
get("/post/new") {
authenticateOrRedirect(Permission.POST) { user ->
respondMain {
menuTemplate {
this.user = user
active = MenuTemplate.Tab.BOARD
}
content {
h1 { +"Create post" }
div("post-edit-container") {

View file

@ -33,10 +33,6 @@ fun Route.room() {
val list = RoomRepository.all()
respondMain {
menuTemplate {
this.user = user
active = MenuTemplate.Tab.ROOM
}
content {
insert(TableTemplate()) {
searchValue = search
@ -113,10 +109,6 @@ fun Route.room() {
val roomId = call.parameters["id"]?.toLongOrNull() ?: return@get
val editRoom = RoomRepository.get(roomId) ?: return@get
respondMain {
menuTemplate {
this.user = user
active = MenuTemplate.Tab.ROOM
}
content {
h1 { +"Edit room" }
form(method = FormMethod.post) {
@ -276,10 +268,6 @@ fun Route.room() {
get("/room/new") {
authenticateOrRedirect(Permission.ROOM) { user ->
respondMain {
menuTemplate {
this.user = user
active = MenuTemplate.Tab.ROOM
}
content {
h1 { +"Create room" }
form(method = FormMethod.post) {

View file

@ -90,10 +90,6 @@ fun Route.track() {
val search = call.parameters["search"] ?: ""
val list = TrackRepository.all()
respondMain {
menuTemplate {
this.user = user
active = MenuTemplate.Tab.WORK_GROUP
}
content {
insert(TableTemplate()) {
searchValue = search
@ -150,10 +146,6 @@ fun Route.track() {
val trackId = call.parameters["id"]?.toLongOrNull() ?: return@get
val editTrack = TrackRepository.get(trackId) ?: return@get
respondMain {
menuTemplate {
this.user = user
active = MenuTemplate.Tab.WORK_GROUP
}
content {
h1 { +"Edit track" }
form(method = FormMethod.post) {
@ -219,10 +211,6 @@ fun Route.track() {
get("/track/new") {
authenticateOrRedirect(Permission.WORK_GROUP) { user ->
respondMain {
menuTemplate {
this.user = user
active = MenuTemplate.Tab.WORK_GROUP
}
content {
h1 { +"Create track" }
form(method = FormMethod.post) {

View file

@ -33,10 +33,6 @@ fun Route.user() {
val search = call.parameters["search"] ?: ""
val list = UserRepository.all()
respondMain {
menuTemplate {
this.user = user
active = MenuTemplate.Tab.USER
}
content {
insert(TableTemplate()) {
searchValue = search
@ -92,10 +88,6 @@ fun Route.user() {
val userId = call.parameters["id"]?.toLongOrNull() ?: return@get
val editUser = UserRepository.get(userId) ?: return@get
respondMain {
menuTemplate {
this.user = user
active = MenuTemplate.Tab.USER
}
content {
h1 { +"Edit user" }
form(method = FormMethod.post) {
@ -185,10 +177,6 @@ fun Route.user() {
get("/user/new") {
authenticateOrRedirect(Permission.USER) { user ->
respondMain {
menuTemplate {
this.user = user
active = MenuTemplate.Tab.USER
}
content {
h1 { +"Create user" }
form(method = FormMethod.post) {

View file

@ -29,10 +29,6 @@ fun Route.workGroup() {
val search = call.parameters["search"] ?: ""
val list = WorkGroupRepository.all()
respondMain {
menuTemplate {
this.user = user
active = MenuTemplate.Tab.WORK_GROUP
}
content {
insert(TableTemplate()) {
searchValue = search
@ -166,10 +162,6 @@ fun Route.workGroup() {
}
respondMain {
menuTemplate {
this.user = user
active = MenuTemplate.Tab.WORK_GROUP
}
content {
h1 { +"Edit work group" }
form(method = FormMethod.post) {
@ -529,10 +521,6 @@ fun Route.workGroup() {
authenticateOrRedirect(Permission.WORK_GROUP) { user ->
val tracks = TrackRepository.all()
respondMain {
menuTemplate {
this.user = user
active = MenuTemplate.Tab.WORK_GROUP
}
content {
h1 { +"Create work group" }
form(method = FormMethod.post) {

View file

@ -1,21 +1,28 @@
package de.kif.backend.view
import de.kif.backend.PortalSession
import de.kif.backend.Resources
import de.kif.backend.authenticate
import de.kif.common.model.User
import io.ktor.application.ApplicationCall
import io.ktor.application.call
import io.ktor.html.*
import io.ktor.request.path
import io.ktor.request.uri
import io.ktor.response.respondRedirect
import io.ktor.sessions.get
import io.ktor.sessions.sessions
import io.ktor.util.pipeline.PipelineContext
import kotlinx.html.*
class MainTemplate(
private val theme: Theme,
private val url: String,
private val user: User?,
private val noMenu: Boolean,
private val stretch: Boolean
) : Template<HTML> {
val content = Placeholder<HtmlBlockTag>()
val menuTemplate = TemplatePlaceholder<MenuTemplate>()
override fun HTML.apply() {
head {
@ -56,7 +63,7 @@ class MainTemplate(
}
body {
if (!noMenu) {
insert(MenuTemplate(), menuTemplate)
insert(MenuTemplate(url, user)) {}
}
val containerClasses = if (stretch) "container-full" else "container"
@ -88,12 +95,16 @@ class MainTemplate(
}
enum class Theme {
LIGHT, DARK, PRINCESS
}
LIGHT, DARK, PRINCESS;
private fun String?.toTheme() = this?.let { str ->
Theme.values().find { str == it.name }
} ?: Theme.LIGHT
companion object {
private val loopup = values().toList().associateBy { it.name }
fun lookup(name: String?): Theme {
return loopup[(name ?: return LIGHT).toUpperCase()] ?: LIGHT
}
}
}
suspend fun PipelineContext<Unit, ApplicationCall>.respondMain(
noMenu: Boolean = false,
@ -101,11 +112,13 @@ suspend fun PipelineContext<Unit, ApplicationCall>.respondMain(
body: MainTemplate.() -> Unit
) {
val param = call.request.queryParameters["theme"]
val url = call.request.uri.substring(1)
val user = call.sessions.get<PortalSession>()?.getUser(call)
if (param != null) {
call.response.cookies.append(
name = "theme",
value = param.toTheme().name,
value = Theme.lookup(param).name,
maxAge = Int.MAX_VALUE,
path = "/"
)
@ -113,7 +126,9 @@ suspend fun PipelineContext<Unit, ApplicationCall>.respondMain(
} else {
call.respondHtmlTemplate(
MainTemplate(
call.request.cookies["theme"].toTheme(),
Theme.lookup(call.request.cookies["theme"]),
url,
user,
noMenu,
stretch
),

View file

@ -5,19 +5,21 @@ import de.kif.common.model.User
import io.ktor.html.Template
import kotlinx.html.*
class MenuTemplate() : Template<FlowContent> {
var active: Tab = Tab.BOARD
var user: User? = null
class MenuTemplate(
private val url: String,
private val user: User?
) : Template<FlowContent> {
override fun FlowContent.apply() {
val tab = Tab.lookup(url)
nav("menu") {
div("container") {
div("menu-left") {
a("/", classes = if (active == Tab.BOARD) "active" else null) {
+"Dashboard"
a("/", classes = if (tab == null) "active" else null) {
+"News"
}
a("/calendar", classes = if (active == Tab.CALENDAR) "active" else null) {
a("/calendar", classes = if (tab == Tab.CALENDAR) "active" else null) {
+"Calendar"
}
}
@ -28,26 +30,26 @@ class MenuTemplate() : Template<FlowContent> {
val user = user
div("menu-content") {
if (user == null) {
a("/account", classes = if (active == Tab.ACCOUNT) "active" else null) {
a("/account", classes = if (tab == Tab.LOGIN) "active" else null) {
+"Login"
}
} else {
if (user.checkPermission(Permission.WORK_GROUP)) {
a("/workgroups", classes = if (active == Tab.WORK_GROUP) "active" else null) {
a("/workgroups", classes = if (tab == Tab.WORK_GROUP) "active" else null) {
+"Work groups"
}
}
if (user.checkPermission(Permission.ROOM)) {
a("/rooms", classes = if (active == Tab.ROOM) "active" else null) {
a("/rooms", classes = if (tab == Tab.ROOM) "active" else null) {
+"Rooms"
}
}
if (user.checkPermission(Permission.USER)) {
a("/users", classes = if (active == Tab.USER) "active" else null) {
a("/users", classes = if (tab == Tab.USER) "active" else null) {
+"Users"
}
}
a("/account", classes = if (active == Tab.ACCOUNT) "active" else null) {
a("/account", classes = if (tab == Tab.ACCOUNT) "active" else null) {
+user.username
}
}
@ -58,6 +60,14 @@ class MenuTemplate() : Template<FlowContent> {
}
enum class Tab {
BOARD, CALENDAR, ACCOUNT, WORK_GROUP, ROOM, PERSON, USER
NEWS, CALENDAR, ACCOUNT, WORK_GROUP, ROOM, USER, LOGIN;
companion object {
private val lookup = values().toList().associateBy { it.name.take(4).toLowerCase() }
fun lookup(url: String): Tab? {
return lookup[url.take(4)]
}
}
}
}