Add dynamic resizing of calendar
This commit is contained in:
parent
990cdaf1a4
commit
dce2567160
|
@ -14,10 +14,14 @@ class WebSocketClient() {
|
||||||
private val url = "ws://${window.location.host}/"
|
private val url = "ws://${window.location.host}/"
|
||||||
|
|
||||||
private lateinit var ws: WebSocket
|
private lateinit var ws: WebSocket
|
||||||
|
private var reconnect = false
|
||||||
|
|
||||||
@Suppress("UNUSED_PARAMETER")
|
@Suppress("UNUSED_PARAMETER")
|
||||||
private fun onOpen(event: Event) {
|
private fun onOpen(event: Event) {
|
||||||
console.log("Connected!")
|
console.log("Connected!")
|
||||||
|
if (reconnect) {
|
||||||
|
window.location.reload()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun onMessage(messageEvent: MessageEvent) {
|
private fun onMessage(messageEvent: MessageEvent) {
|
||||||
|
@ -37,6 +41,7 @@ class WebSocketClient() {
|
||||||
@Suppress("UNUSED_PARAMETER")
|
@Suppress("UNUSED_PARAMETER")
|
||||||
private fun onClose(event: Event) {
|
private fun onClose(event: Event) {
|
||||||
console.log("Disconnected!")
|
console.log("Disconnected!")
|
||||||
|
reconnect = true
|
||||||
async(1000) {
|
async(1000) {
|
||||||
connect()
|
connect()
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,31 +6,6 @@ import kotlin.coroutines.CoroutineContext
|
||||||
import kotlin.coroutines.EmptyCoroutineContext
|
import kotlin.coroutines.EmptyCoroutineContext
|
||||||
import kotlin.coroutines.startCoroutine
|
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) =
|
fun launch(context: CoroutineContext = EmptyCoroutineContext, block: suspend () -> Unit) =
|
||||||
block.startCoroutine(Continuation(context) { result ->
|
block.startCoroutine(Continuation(context) { result ->
|
||||||
result.onFailure { exception ->
|
result.onFailure { exception ->
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
package de.kif.frontend.views
|
package de.kif.frontend.views
|
||||||
|
|
||||||
import de.kif.frontend.iterator
|
|
||||||
import de.kif.frontend.launch
|
import de.kif.frontend.launch
|
||||||
import de.kif.frontend.repository.WorkGroupRepository
|
import de.kif.frontend.repository.WorkGroupRepository
|
||||||
import de.westermann.kobserve.event.EventListener
|
import de.westermann.kobserve.event.EventListener
|
||||||
|
@ -8,6 +7,7 @@ import de.westermann.kwebview.View
|
||||||
import de.westermann.kwebview.async
|
import de.westermann.kwebview.async
|
||||||
import de.westermann.kwebview.components.*
|
import de.westermann.kwebview.components.*
|
||||||
import de.westermann.kwebview.createHtmlView
|
import de.westermann.kwebview.createHtmlView
|
||||||
|
import de.westermann.kwebview.iterator
|
||||||
import org.w3c.dom.*
|
import org.w3c.dom.*
|
||||||
import kotlin.browser.document
|
import kotlin.browser.document
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
package de.kif.frontend.views.board
|
package de.kif.frontend.views.board
|
||||||
|
|
||||||
import de.kif.common.formatDateTime
|
import de.kif.common.formatDateTime
|
||||||
import de.kif.frontend.iterator
|
import de.westermann.kwebview.iterator
|
||||||
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
|
||||||
|
|
|
@ -1,22 +1,23 @@
|
||||||
package de.kif.frontend.views.calendar
|
package de.kif.frontend.views.calendar
|
||||||
|
|
||||||
import de.kif.frontend.iterator
|
|
||||||
import de.kif.frontend.launch
|
import de.kif.frontend.launch
|
||||||
|
import de.kif.frontend.repository.RoomRepository
|
||||||
import de.kif.frontend.repository.ScheduleRepository
|
import de.kif.frontend.repository.ScheduleRepository
|
||||||
import de.westermann.kwebview.View
|
import de.westermann.kwebview.View
|
||||||
|
import de.westermann.kwebview.createHtmlView
|
||||||
|
import de.westermann.kwebview.iterator
|
||||||
import org.w3c.dom.*
|
import org.w3c.dom.*
|
||||||
import kotlin.browser.document
|
import kotlin.browser.document
|
||||||
import kotlin.browser.window
|
import kotlin.browser.window
|
||||||
|
|
||||||
|
|
||||||
class Calendar(calendar: HTMLElement) : View(calendar) {
|
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 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) {
|
fun scrollVerticalBy(pixel: Double, scrollBehavior: ScrollBehavior = ScrollBehavior.SMOOTH) {
|
||||||
htmlTag.scrollBy(ScrollToOptions(0.0, pixel, scrollBehavior))
|
htmlTag.scrollBy(ScrollToOptions(0.0, pixel, scrollBehavior))
|
||||||
|
@ -34,58 +35,22 @@ class Calendar(calendar: HTMLElement) : View(calendar) {
|
||||||
calendarTable.scrollTo(ScrollToOptions(pixel, 0.0, scrollBehavior))
|
calendarTable.scrollTo(ScrollToOptions(pixel, 0.0, scrollBehavior))
|
||||||
}
|
}
|
||||||
|
|
||||||
init {
|
|
||||||
val editable = calendar.dataset["editable"]?.toBoolean() ?: false
|
val editable = calendar.dataset["editable"]?.toBoolean() ?: false
|
||||||
day = calendar.dataset["day"]?.toIntOrNull() ?: -1
|
|
||||||
|
|
||||||
calendarEntries = document.getElementsByClassName("calendar-entry")
|
val body = CalendarBody(this, calendar.getElementsByClassName("calendar-body")[0] as HTMLElement)
|
||||||
.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()
|
|
||||||
|
|
||||||
|
init {
|
||||||
if (editable) {
|
if (editable) {
|
||||||
CalendarEdit(this, calendar.querySelector(".calendar-edit") as HTMLElement)
|
CalendarEdit(this, calendar.querySelector(".calendar-edit") as HTMLElement)
|
||||||
}
|
}
|
||||||
|
|
||||||
ScheduleRepository.onCreate {
|
(document.getElementById("calendar-check-constraints") as? HTMLElement)?.let { wrap(it) }
|
||||||
launch {
|
?.onClick?.addListener {
|
||||||
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 {
|
|
||||||
launch {
|
launch {
|
||||||
val errors = ScheduleRepository.checkConstraints()
|
val errors = ScheduleRepository.checkConstraints()
|
||||||
|
|
||||||
println(errors)
|
|
||||||
|
|
||||||
for ((s, l) in errors.map) {
|
for ((s, l) in errors.map) {
|
||||||
for (entry in calendarEntries) {
|
for (entry in body.calendarEntries) {
|
||||||
if (entry.scheduleId == s) {
|
if (entry.scheduleId == s) {
|
||||||
entry.error = l.isNotEmpty()
|
entry.error = l.isNotEmpty()
|
||||||
}
|
}
|
||||||
|
@ -106,6 +71,29 @@ class Calendar(calendar: HTMLElement) : View(calendar) {
|
||||||
|
|
||||||
it.preventDefault()
|
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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
161
src/jsMain/kotlin/de/kif/frontend/views/calendar/CalendarBody.kt
Normal file
161
src/jsMain/kotlin/de/kif/frontend/views/calendar/CalendarBody.kt
Normal 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -3,12 +3,17 @@ package de.kif.frontend.views.calendar
|
||||||
import de.kif.common.model.Room
|
import de.kif.common.model.Room
|
||||||
import de.kif.frontend.repository.RoomRepository
|
import de.kif.frontend.repository.RoomRepository
|
||||||
import de.westermann.kwebview.ViewCollection
|
import de.westermann.kwebview.ViewCollection
|
||||||
|
import de.westermann.kwebview.createHtmlView
|
||||||
|
import org.w3c.dom.HTMLDivElement
|
||||||
import org.w3c.dom.HTMLElement
|
import org.w3c.dom.HTMLElement
|
||||||
import org.w3c.dom.get
|
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
|
val roomId = dataset["room"]?.toLongOrNull() ?: 0
|
||||||
|
|
||||||
private lateinit var room: Room
|
private lateinit var room: Room
|
||||||
|
@ -22,7 +27,13 @@ class CalendarCell(view: HTMLElement) : ViewCollection<CalendarEntry>(view) {
|
||||||
return room
|
return room
|
||||||
}
|
}
|
||||||
|
|
||||||
init {
|
companion object {
|
||||||
(view.getElementsByClassName("calendar-link")[0] as? HTMLElement)?.remove()
|
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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,10 +1,9 @@
|
||||||
package de.kif.frontend.views.calendar
|
package de.kif.frontend.views.calendar
|
||||||
|
|
||||||
import de.kif.common.CALENDAR_GRID_WIDTH
|
import de.kif.common.CALENDAR_GRID_WIDTH
|
||||||
import de.kif.common.model.Room
|
|
||||||
import de.kif.common.model.Schedule
|
import de.kif.common.model.Schedule
|
||||||
import de.kif.common.model.WorkGroup
|
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.launch
|
||||||
import de.kif.frontend.repository.RepositoryDelegate
|
import de.kif.frontend.repository.RepositoryDelegate
|
||||||
import de.kif.frontend.repository.ScheduleRepository
|
import de.kif.frontend.repository.ScheduleRepository
|
||||||
|
@ -17,7 +16,7 @@ import kotlin.dom.appendText
|
||||||
import kotlin.dom.isText
|
import kotlin.dom.isText
|
||||||
import kotlin.js.Date
|
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 lateinit var mouseDelta: Point
|
||||||
private var newCell: CalendarCell? = null
|
private var newCell: CalendarCell? = null
|
||||||
|
@ -31,11 +30,15 @@ class CalendarEntry(private val calendar: Calendar, view: HTMLElement) : View(vi
|
||||||
|
|
||||||
private lateinit var workGroup: WorkGroup
|
private lateinit var workGroup: WorkGroup
|
||||||
|
|
||||||
|
var startTime = dataset["time"]?.toIntOrNull() ?: 0
|
||||||
|
var length = dataset["length"]?.toIntOrNull() ?: 0
|
||||||
|
|
||||||
var pending by classList.property("pending")
|
var pending by classList.property("pending")
|
||||||
var error by classList.property("error")
|
var error by classList.property("error")
|
||||||
private var nextScroll = 0.0
|
private var nextScroll = 0.0
|
||||||
|
|
||||||
var editable: Boolean = false
|
val editable: Boolean
|
||||||
|
get() = calendar.editable
|
||||||
|
|
||||||
var moveLookRoom: Long? = null
|
var moveLookRoom: Long? = null
|
||||||
var moveLookTime: Int? = null
|
var moveLookTime: Int? = null
|
||||||
|
@ -70,29 +73,29 @@ class CalendarEntry(private val calendar: Calendar, view: HTMLElement) : View(vi
|
||||||
return@async
|
return@async
|
||||||
}
|
}
|
||||||
|
|
||||||
val width = calendar.calendarTable.clientWidth
|
val width = calendar.calendar.calendarTable.clientWidth
|
||||||
val height = window.innerHeight
|
val height = window.innerHeight
|
||||||
val rect = html.getBoundingClientRect()
|
val rect = html.getBoundingClientRect()
|
||||||
|
|
||||||
if (rect.left < 0.0) {
|
if (rect.left < 0.0) {
|
||||||
nextScroll = now + 500.0
|
nextScroll = now + 500.0
|
||||||
calendar.scrollHorizontalBy(rect.left - 80.0)
|
calendar.calendar.scrollHorizontalBy(rect.left - 80.0)
|
||||||
} else if (rect.right > width) {
|
} else if (rect.right > width) {
|
||||||
nextScroll = now + 0.500
|
nextScroll = now + 0.500
|
||||||
calendar.scrollHorizontalBy(rect.right - width + 50.0)
|
calendar.calendar.scrollHorizontalBy(rect.right - width + 50.0)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (rect.top < 20.0) {
|
if (rect.top < 20.0) {
|
||||||
nextScroll = now + 500.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) {
|
} else if (rect.bottom > height - 20.0) {
|
||||||
nextScroll = now + 500.0
|
nextScroll = now + 500.0
|
||||||
calendar.scrollVerticalBy(rect.bottom - height + 50.0)
|
calendar.calendar.scrollVerticalBy(rect.bottom - height + 50.0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
launch {
|
launch {
|
||||||
calendarTools.setName(cell.getRoom(), cell.time)
|
calendarTools?.setName(cell.getRoom(), cell.time)
|
||||||
}
|
}
|
||||||
|
|
||||||
newCell = cell
|
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 {
|
init {
|
||||||
onMouseDown { event ->
|
onMouseDown { event ->
|
||||||
|
@ -181,6 +184,7 @@ class CalendarEntry(private val calendar: Calendar, view: HTMLElement) : View(vi
|
||||||
event.stopPropagation()
|
event.stopPropagation()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (calendarTools != null) {
|
||||||
html.appendChild(calendarTools.html)
|
html.appendChild(calendarTools.html)
|
||||||
|
|
||||||
launch {
|
launch {
|
||||||
|
@ -188,6 +192,7 @@ class CalendarEntry(private val calendar: Calendar, view: HTMLElement) : View(vi
|
||||||
calendarTools.update(s)
|
calendarTools.update(s)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun load(schedule: Schedule) {
|
fun load(schedule: Schedule) {
|
||||||
pending = false
|
pending = false
|
||||||
|
@ -205,7 +210,10 @@ class CalendarEntry(private val calendar: Calendar, view: HTMLElement) : View(vi
|
||||||
}
|
}
|
||||||
|
|
||||||
load(schedule.workGroup)
|
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 time = schedule.time / CALENDAR_GRID_WIDTH * CALENDAR_GRID_WIDTH
|
||||||
val cell = calendar.calendarCells.find {
|
val cell = calendar.calendarCells.find {
|
||||||
|
@ -247,7 +255,7 @@ class CalendarEntry(private val calendar: Calendar, view: HTMLElement) : View(vi
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
fun create(calendar: Calendar, schedule: Schedule): CalendarEntry {
|
fun create(calendar: CalendarBody, schedule: Schedule): CalendarEntry {
|
||||||
val entry = CalendarEntry(calendar, createHtmlView())
|
val entry = CalendarEntry(calendar, createHtmlView())
|
||||||
|
|
||||||
entry.load(schedule)
|
entry.load(schedule)
|
||||||
|
@ -255,7 +263,7 @@ class CalendarEntry(private val calendar: Calendar, view: HTMLElement) : View(vi
|
||||||
return entry
|
return entry
|
||||||
}
|
}
|
||||||
|
|
||||||
fun create(calendar: Calendar, workGroup: WorkGroup): CalendarEntry {
|
fun create(calendar: CalendarBody, workGroup: WorkGroup): CalendarEntry {
|
||||||
val entry = CalendarEntry(calendar, createHtmlView())
|
val entry = CalendarEntry(calendar, createHtmlView())
|
||||||
|
|
||||||
entry.load(workGroup)
|
entry.load(workGroup)
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -50,7 +50,7 @@ class CalendarWorkGroup(
|
||||||
}
|
}
|
||||||
|
|
||||||
onMouseDown {
|
onMouseDown {
|
||||||
CalendarEntry.create(calendar, workGroup)
|
CalendarEntry.create(calendar.body, workGroup)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
package de.kif.frontend.views.overview
|
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.launch
|
||||||
import de.kif.frontend.repository.PostRepository
|
import de.kif.frontend.repository.PostRepository
|
||||||
import de.westermann.kobserve.event.subscribe
|
import de.westermann.kobserve.event.subscribe
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
package de.kif.frontend.views.table
|
package de.kif.frontend.views.table
|
||||||
|
|
||||||
import de.kif.common.SearchElement
|
import de.kif.common.SearchElement
|
||||||
import de.kif.frontend.iterator
|
import de.westermann.kwebview.iterator
|
||||||
import de.kif.frontend.launch
|
import de.kif.frontend.launch
|
||||||
import de.kif.frontend.repository.RepositoryDelegate
|
import de.kif.frontend.repository.RepositoryDelegate
|
||||||
import de.kif.frontend.repository.RoomRepository
|
import de.kif.frontend.repository.RoomRepository
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
package de.kif.frontend.views.table
|
package de.kif.frontend.views.table
|
||||||
|
|
||||||
import de.kif.frontend.iterator
|
import de.westermann.kwebview.iterator
|
||||||
import de.westermann.kwebview.components.InputView
|
import de.westermann.kwebview.components.InputView
|
||||||
import org.w3c.dom.HTMLFormElement
|
import org.w3c.dom.HTMLFormElement
|
||||||
import org.w3c.dom.HTMLInputElement
|
import org.w3c.dom.HTMLInputElement
|
||||||
|
|
|
@ -3,7 +3,7 @@ package de.kif.frontend.views.table
|
||||||
import de.kif.common.SearchElement
|
import de.kif.common.SearchElement
|
||||||
import de.kif.common.model.Language
|
import de.kif.common.model.Language
|
||||||
import de.kif.common.model.Track
|
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.launch
|
||||||
import de.kif.frontend.repository.RepositoryDelegate
|
import de.kif.frontend.repository.RepositoryDelegate
|
||||||
import de.kif.frontend.repository.TrackRepository
|
import de.kif.frontend.repository.TrackRepository
|
||||||
|
|
|
@ -1,10 +1,17 @@
|
||||||
package de.westermann.kwebview
|
package de.westermann.kwebview
|
||||||
|
|
||||||
import de.westermann.kobserve.event.EventListener
|
|
||||||
import de.westermann.kobserve.Property
|
import de.westermann.kobserve.Property
|
||||||
import de.westermann.kobserve.ReadOnlyProperty
|
import de.westermann.kobserve.ReadOnlyProperty
|
||||||
|
import de.westermann.kobserve.event.EventListener
|
||||||
import de.westermann.kobserve.property.property
|
import de.westermann.kobserve.property.property
|
||||||
import org.w3c.dom.DOMTokenList
|
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.
|
* Represents the css classes of an html element.
|
||||||
|
@ -130,6 +137,12 @@ class ClassList(
|
||||||
|
|
||||||
override fun toString(): String = list.value
|
override fun toString(): String = list.value
|
||||||
|
|
||||||
|
fun clear() {
|
||||||
|
for (element in this) {
|
||||||
|
remove(element)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private data class Bound(
|
private data class Bound(
|
||||||
val property: ReadOnlyProperty<Boolean>,
|
val property: ReadOnlyProperty<Boolean>,
|
||||||
val reference: EventListener<Unit>?
|
val reference: EventListener<Unit>?
|
||||||
|
|
|
@ -8,7 +8,15 @@ import kotlin.dom.clear
|
||||||
*/
|
*/
|
||||||
abstract class ViewCollection<V : View>(view: HTMLElement = createHtmlView()) : View(view), Collection<V> {
|
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) {
|
fun append(view: V) {
|
||||||
children += view
|
children += view
|
||||||
|
|
|
@ -1,8 +1,7 @@
|
||||||
package de.westermann.kwebview
|
package de.westermann.kwebview
|
||||||
|
|
||||||
import de.westermann.kobserve.event.EventHandler
|
import de.westermann.kobserve.event.EventHandler
|
||||||
import org.w3c.dom.DOMRect
|
import org.w3c.dom.*
|
||||||
import org.w3c.dom.HTMLElement
|
|
||||||
import org.w3c.dom.events.Event
|
import org.w3c.dom.events.Event
|
||||||
import org.w3c.dom.events.EventListener
|
import org.w3c.dom.events.EventListener
|
||||||
import org.w3c.dom.events.MouseEvent
|
import org.w3c.dom.events.MouseEvent
|
||||||
|
@ -11,6 +10,30 @@ import org.w3c.xhr.XMLHttpRequest
|
||||||
import kotlin.browser.document
|
import kotlin.browser.document
|
||||||
import kotlin.browser.window
|
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 {
|
inline fun <reified V : HTMLElement> createHtmlView(tag: String? = null): V {
|
||||||
var tagName: String
|
var tagName: String
|
||||||
if (tag != null) {
|
if (tag != null) {
|
||||||
|
|
|
@ -384,7 +384,7 @@
|
||||||
border-left: none;
|
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);
|
border-top: solid 1px var(--table-border-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -424,6 +424,10 @@
|
||||||
&.time-to-room {
|
&.time-to-room {
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
|
|
||||||
|
.calendar-body {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
.calendar-header, .calendar-row {
|
.calendar-header, .calendar-row {
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
line-height: 3rem;
|
line-height: 3rem;
|
||||||
|
@ -468,7 +472,7 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&:nth-child(2n + 2) .calendar-cell::before {
|
&:nth-child(4n + 1) .calendar-cell::before {
|
||||||
content: '';
|
content: '';
|
||||||
height: 100%;
|
height: 100%;
|
||||||
left: 0;
|
left: 0;
|
||||||
|
|
|
@ -40,10 +40,6 @@ fun Route.account() {
|
||||||
val wikiSections = WikiImporter.loadSections()
|
val wikiSections = WikiImporter.loadSections()
|
||||||
|
|
||||||
respondMain {
|
respondMain {
|
||||||
menuTemplate {
|
|
||||||
this.user = user
|
|
||||||
active = MenuTemplate.Tab.ACCOUNT
|
|
||||||
}
|
|
||||||
content {
|
content {
|
||||||
h1 { +"Account" }
|
h1 { +"Account" }
|
||||||
div {
|
div {
|
||||||
|
|
|
@ -6,7 +6,6 @@ import de.kif.backend.Configuration
|
||||||
import de.kif.backend.isAuthenticated
|
import de.kif.backend.isAuthenticated
|
||||||
import de.kif.backend.repository.RoomRepository
|
import de.kif.backend.repository.RoomRepository
|
||||||
import de.kif.backend.repository.ScheduleRepository
|
import de.kif.backend.repository.ScheduleRepository
|
||||||
import de.kif.backend.view.MenuTemplate
|
|
||||||
import de.kif.backend.view.respondMain
|
import de.kif.backend.view.respondMain
|
||||||
import de.kif.common.CALENDAR_GRID_WIDTH
|
import de.kif.common.CALENDAR_GRID_WIDTH
|
||||||
import de.kif.common.model.Permission
|
import de.kif.common.model.Permission
|
||||||
|
@ -51,6 +50,8 @@ private fun DIV.calendarCell(schedule: Schedule?) {
|
||||||
}.toString()
|
}.toString()
|
||||||
attributes["data-language"] = schedule.workGroup.language.code
|
attributes["data-language"] = schedule.workGroup.language.code
|
||||||
attributes["data-id"] = schedule.id.toString()
|
attributes["data-id"] = schedule.id.toString()
|
||||||
|
attributes["data-time"] = schedule.time.toString()
|
||||||
|
attributes["data-length"] = schedule.workGroup.length.toString()
|
||||||
|
|
||||||
+schedule.workGroup.name
|
+schedule.workGroup.name
|
||||||
}
|
}
|
||||||
|
@ -81,6 +82,8 @@ private fun DIV.renderCalendar(
|
||||||
|
|
||||||
for (room in rooms) {
|
for (room in rooms) {
|
||||||
div("calendar-cell") {
|
div("calendar-cell") {
|
||||||
|
attributes["data-room"] = room.id.toString()
|
||||||
|
|
||||||
span {
|
span {
|
||||||
+room.name
|
+room.name
|
||||||
}
|
}
|
||||||
|
@ -88,6 +91,7 @@ private fun DIV.renderCalendar(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
div("calendar-body") {
|
||||||
for (i in 0 until minutesOfDay / CALENDAR_GRID_WIDTH) {
|
for (i in 0 until minutesOfDay / CALENDAR_GRID_WIDTH) {
|
||||||
val time = ((i * CALENDAR_GRID_WIDTH + from) % MINUTES_OF_DAY).let {
|
val time = ((i * CALENDAR_GRID_WIDTH + from) % MINUTES_OF_DAY).let {
|
||||||
if (it < 0) it + MINUTES_OF_DAY else it
|
if (it < 0) it + MINUTES_OF_DAY else it
|
||||||
|
@ -106,6 +110,8 @@ private fun DIV.renderCalendar(
|
||||||
}
|
}
|
||||||
|
|
||||||
div(rowClass) {
|
div(rowClass) {
|
||||||
|
attributes["data-time"] = start.toString()
|
||||||
|
attributes["data-day"] = day.toString()
|
||||||
|
|
||||||
div("calendar-cell") {
|
div("calendar-cell") {
|
||||||
if (time % gridLabelWidth == 0) {
|
if (time % gridLabelWidth == 0) {
|
||||||
|
@ -117,9 +123,8 @@ private fun DIV.renderCalendar(
|
||||||
|
|
||||||
for (room in rooms) {
|
for (room in rooms) {
|
||||||
div("calendar-cell") {
|
div("calendar-cell") {
|
||||||
attributes["data-time"] = start.toString()
|
|
||||||
attributes["data-room"] = room.id.toString()
|
attributes["data-room"] = room.id.toString()
|
||||||
attributes["data-day"] = day.toString()
|
|
||||||
title = room.name + " - " + timeString
|
title = room.name + " - " + timeString
|
||||||
|
|
||||||
val schedule = (start..end).mapNotNull { schedules[room]?.get(it) }.firstOrNull()
|
val schedule = (start..end).mapNotNull { schedules[room]?.get(it) }.firstOrNull()
|
||||||
|
@ -131,6 +136,7 @@ private fun DIV.renderCalendar(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun Route.calendar() {
|
fun Route.calendar() {
|
||||||
|
|
||||||
|
@ -167,9 +173,12 @@ fun Route.calendar() {
|
||||||
val day = call.parameters["day"]?.toIntOrNull() ?: return@get
|
val day = call.parameters["day"]?.toIntOrNull() ?: return@get
|
||||||
|
|
||||||
val range = ScheduleRepository.getDayRange()
|
val range = ScheduleRepository.getDayRange()
|
||||||
|
|
||||||
|
/*
|
||||||
if (!editable && day !in range) {
|
if (!editable && day !in range) {
|
||||||
return@get
|
return@get
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
val rooms = RoomRepository.all()
|
val rooms = RoomRepository.all()
|
||||||
|
|
||||||
|
@ -177,8 +186,8 @@ fun Route.calendar() {
|
||||||
CalendarOrientation.values().find { it.name == name }
|
CalendarOrientation.values().find { it.name == name }
|
||||||
} ?: CalendarOrientation.ROOM_TO_TIME
|
} ?: CalendarOrientation.ROOM_TO_TIME
|
||||||
|
|
||||||
val h = ScheduleRepository.getByDay(day)
|
val list = ScheduleRepository.getByDay(day)
|
||||||
val schedules = h.groupBy { it.room }.mapValues { (_, it) ->
|
val schedules = list.groupBy { it.room }.mapValues { (_, it) ->
|
||||||
it.associateBy {
|
it.associateBy {
|
||||||
it.time
|
it.time
|
||||||
}
|
}
|
||||||
|
@ -186,7 +195,7 @@ fun Route.calendar() {
|
||||||
|
|
||||||
var max = 0
|
var max = 0
|
||||||
var min = 24 * 60
|
var min = 24 * 60
|
||||||
for (s in h) {
|
for (s in list) {
|
||||||
max = max(max, s.time + s.workGroup.length)
|
max = max(max, s.time + s.workGroup.length)
|
||||||
min = min(min, s.time)
|
min = min(min, s.time)
|
||||||
}
|
}
|
||||||
|
@ -205,6 +214,11 @@ fun Route.calendar() {
|
||||||
min = (min / 60 - 1) * 60
|
min = (min / 60 - 1) * 60
|
||||||
max = (max / 60 + 2) * 60
|
max = (max / 60 + 2) * 60
|
||||||
|
|
||||||
|
if (!editable && list.isEmpty()) {
|
||||||
|
min = 0
|
||||||
|
max = 0
|
||||||
|
}
|
||||||
|
|
||||||
val refDate = DateTime(Configuration.Schedule.referenceDate.time)
|
val refDate = DateTime(Configuration.Schedule.referenceDate.time)
|
||||||
val date = refDate + day.days
|
val date = refDate + day.days
|
||||||
val dateString = DateFormat("EEEE, d. MMMM")
|
val dateString = DateFormat("EEEE, d. MMMM")
|
||||||
|
@ -212,15 +226,7 @@ fun Route.calendar() {
|
||||||
.format(date)
|
.format(date)
|
||||||
|
|
||||||
respondMain {
|
respondMain {
|
||||||
menuTemplate {
|
|
||||||
this.user = user
|
|
||||||
active = MenuTemplate.Tab.CALENDAR
|
|
||||||
}
|
|
||||||
content {
|
content {
|
||||||
if (rooms.isEmpty()) {
|
|
||||||
return@content
|
|
||||||
}
|
|
||||||
|
|
||||||
div("header") {
|
div("header") {
|
||||||
div("header-left") {
|
div("header-left") {
|
||||||
if (editable || day - 1 > range.start) {
|
if (editable || day - 1 > range.start) {
|
||||||
|
|
|
@ -2,12 +2,10 @@ package de.kif.backend.route
|
||||||
|
|
||||||
import de.kif.backend.PortalSession
|
import de.kif.backend.PortalSession
|
||||||
import de.kif.backend.UserPrinciple
|
import de.kif.backend.UserPrinciple
|
||||||
import de.kif.backend.view.MainTemplate
|
|
||||||
import de.kif.backend.view.respondMain
|
import de.kif.backend.view.respondMain
|
||||||
import io.ktor.application.call
|
import io.ktor.application.call
|
||||||
import io.ktor.auth.authenticate
|
import io.ktor.auth.authenticate
|
||||||
import io.ktor.auth.principal
|
import io.ktor.auth.principal
|
||||||
import io.ktor.html.respondHtmlTemplate
|
|
||||||
import io.ktor.response.respondRedirect
|
import io.ktor.response.respondRedirect
|
||||||
import io.ktor.routing.Route
|
import io.ktor.routing.Route
|
||||||
import io.ktor.routing.get
|
import io.ktor.routing.get
|
||||||
|
|
|
@ -78,10 +78,6 @@ fun Route.overview() {
|
||||||
val postList = PostRepository.all().asReversed()
|
val postList = PostRepository.all().asReversed()
|
||||||
|
|
||||||
respondMain {
|
respondMain {
|
||||||
menuTemplate {
|
|
||||||
this.user = user
|
|
||||||
active = MenuTemplate.Tab.BOARD
|
|
||||||
}
|
|
||||||
content {
|
content {
|
||||||
div("overview") {
|
div("overview") {
|
||||||
div("overview-main") {
|
div("overview-main") {
|
||||||
|
@ -118,10 +114,6 @@ fun Route.overview() {
|
||||||
}
|
}
|
||||||
|
|
||||||
respondMain {
|
respondMain {
|
||||||
menuTemplate {
|
|
||||||
this.user = user
|
|
||||||
active = MenuTemplate.Tab.BOARD
|
|
||||||
}
|
|
||||||
content {
|
content {
|
||||||
div("overview") {
|
div("overview") {
|
||||||
createPost(post, editable)
|
createPost(post, editable)
|
||||||
|
@ -135,10 +127,6 @@ fun Route.overview() {
|
||||||
val postId = call.parameters["id"]?.toLongOrNull() ?: return@get
|
val postId = call.parameters["id"]?.toLongOrNull() ?: return@get
|
||||||
val editPost = PostRepository.get(postId) ?: return@get
|
val editPost = PostRepository.get(postId) ?: return@get
|
||||||
respondMain {
|
respondMain {
|
||||||
menuTemplate {
|
|
||||||
this.user = user
|
|
||||||
active = MenuTemplate.Tab.BOARD
|
|
||||||
}
|
|
||||||
content {
|
content {
|
||||||
h1 { +"Edit post" }
|
h1 { +"Edit post" }
|
||||||
div("post-edit-container") {
|
div("post-edit-container") {
|
||||||
|
@ -365,10 +353,6 @@ fun Route.overview() {
|
||||||
get("/post/new") {
|
get("/post/new") {
|
||||||
authenticateOrRedirect(Permission.POST) { user ->
|
authenticateOrRedirect(Permission.POST) { user ->
|
||||||
respondMain {
|
respondMain {
|
||||||
menuTemplate {
|
|
||||||
this.user = user
|
|
||||||
active = MenuTemplate.Tab.BOARD
|
|
||||||
}
|
|
||||||
content {
|
content {
|
||||||
h1 { +"Create post" }
|
h1 { +"Create post" }
|
||||||
div("post-edit-container") {
|
div("post-edit-container") {
|
||||||
|
|
|
@ -33,10 +33,6 @@ fun Route.room() {
|
||||||
val list = RoomRepository.all()
|
val list = RoomRepository.all()
|
||||||
|
|
||||||
respondMain {
|
respondMain {
|
||||||
menuTemplate {
|
|
||||||
this.user = user
|
|
||||||
active = MenuTemplate.Tab.ROOM
|
|
||||||
}
|
|
||||||
content {
|
content {
|
||||||
insert(TableTemplate()) {
|
insert(TableTemplate()) {
|
||||||
searchValue = search
|
searchValue = search
|
||||||
|
@ -113,10 +109,6 @@ fun Route.room() {
|
||||||
val roomId = call.parameters["id"]?.toLongOrNull() ?: return@get
|
val roomId = call.parameters["id"]?.toLongOrNull() ?: return@get
|
||||||
val editRoom = RoomRepository.get(roomId) ?: return@get
|
val editRoom = RoomRepository.get(roomId) ?: return@get
|
||||||
respondMain {
|
respondMain {
|
||||||
menuTemplate {
|
|
||||||
this.user = user
|
|
||||||
active = MenuTemplate.Tab.ROOM
|
|
||||||
}
|
|
||||||
content {
|
content {
|
||||||
h1 { +"Edit room" }
|
h1 { +"Edit room" }
|
||||||
form(method = FormMethod.post) {
|
form(method = FormMethod.post) {
|
||||||
|
@ -276,10 +268,6 @@ fun Route.room() {
|
||||||
get("/room/new") {
|
get("/room/new") {
|
||||||
authenticateOrRedirect(Permission.ROOM) { user ->
|
authenticateOrRedirect(Permission.ROOM) { user ->
|
||||||
respondMain {
|
respondMain {
|
||||||
menuTemplate {
|
|
||||||
this.user = user
|
|
||||||
active = MenuTemplate.Tab.ROOM
|
|
||||||
}
|
|
||||||
content {
|
content {
|
||||||
h1 { +"Create room" }
|
h1 { +"Create room" }
|
||||||
form(method = FormMethod.post) {
|
form(method = FormMethod.post) {
|
||||||
|
|
|
@ -90,10 +90,6 @@ fun Route.track() {
|
||||||
val search = call.parameters["search"] ?: ""
|
val search = call.parameters["search"] ?: ""
|
||||||
val list = TrackRepository.all()
|
val list = TrackRepository.all()
|
||||||
respondMain {
|
respondMain {
|
||||||
menuTemplate {
|
|
||||||
this.user = user
|
|
||||||
active = MenuTemplate.Tab.WORK_GROUP
|
|
||||||
}
|
|
||||||
content {
|
content {
|
||||||
insert(TableTemplate()) {
|
insert(TableTemplate()) {
|
||||||
searchValue = search
|
searchValue = search
|
||||||
|
@ -150,10 +146,6 @@ fun Route.track() {
|
||||||
val trackId = call.parameters["id"]?.toLongOrNull() ?: return@get
|
val trackId = call.parameters["id"]?.toLongOrNull() ?: return@get
|
||||||
val editTrack = TrackRepository.get(trackId) ?: return@get
|
val editTrack = TrackRepository.get(trackId) ?: return@get
|
||||||
respondMain {
|
respondMain {
|
||||||
menuTemplate {
|
|
||||||
this.user = user
|
|
||||||
active = MenuTemplate.Tab.WORK_GROUP
|
|
||||||
}
|
|
||||||
content {
|
content {
|
||||||
h1 { +"Edit track" }
|
h1 { +"Edit track" }
|
||||||
form(method = FormMethod.post) {
|
form(method = FormMethod.post) {
|
||||||
|
@ -219,10 +211,6 @@ fun Route.track() {
|
||||||
get("/track/new") {
|
get("/track/new") {
|
||||||
authenticateOrRedirect(Permission.WORK_GROUP) { user ->
|
authenticateOrRedirect(Permission.WORK_GROUP) { user ->
|
||||||
respondMain {
|
respondMain {
|
||||||
menuTemplate {
|
|
||||||
this.user = user
|
|
||||||
active = MenuTemplate.Tab.WORK_GROUP
|
|
||||||
}
|
|
||||||
content {
|
content {
|
||||||
h1 { +"Create track" }
|
h1 { +"Create track" }
|
||||||
form(method = FormMethod.post) {
|
form(method = FormMethod.post) {
|
||||||
|
|
|
@ -33,10 +33,6 @@ fun Route.user() {
|
||||||
val search = call.parameters["search"] ?: ""
|
val search = call.parameters["search"] ?: ""
|
||||||
val list = UserRepository.all()
|
val list = UserRepository.all()
|
||||||
respondMain {
|
respondMain {
|
||||||
menuTemplate {
|
|
||||||
this.user = user
|
|
||||||
active = MenuTemplate.Tab.USER
|
|
||||||
}
|
|
||||||
content {
|
content {
|
||||||
insert(TableTemplate()) {
|
insert(TableTemplate()) {
|
||||||
searchValue = search
|
searchValue = search
|
||||||
|
@ -92,10 +88,6 @@ fun Route.user() {
|
||||||
val userId = call.parameters["id"]?.toLongOrNull() ?: return@get
|
val userId = call.parameters["id"]?.toLongOrNull() ?: return@get
|
||||||
val editUser = UserRepository.get(userId) ?: return@get
|
val editUser = UserRepository.get(userId) ?: return@get
|
||||||
respondMain {
|
respondMain {
|
||||||
menuTemplate {
|
|
||||||
this.user = user
|
|
||||||
active = MenuTemplate.Tab.USER
|
|
||||||
}
|
|
||||||
content {
|
content {
|
||||||
h1 { +"Edit user" }
|
h1 { +"Edit user" }
|
||||||
form(method = FormMethod.post) {
|
form(method = FormMethod.post) {
|
||||||
|
@ -185,10 +177,6 @@ fun Route.user() {
|
||||||
get("/user/new") {
|
get("/user/new") {
|
||||||
authenticateOrRedirect(Permission.USER) { user ->
|
authenticateOrRedirect(Permission.USER) { user ->
|
||||||
respondMain {
|
respondMain {
|
||||||
menuTemplate {
|
|
||||||
this.user = user
|
|
||||||
active = MenuTemplate.Tab.USER
|
|
||||||
}
|
|
||||||
content {
|
content {
|
||||||
h1 { +"Create user" }
|
h1 { +"Create user" }
|
||||||
form(method = FormMethod.post) {
|
form(method = FormMethod.post) {
|
||||||
|
|
|
@ -29,10 +29,6 @@ fun Route.workGroup() {
|
||||||
val search = call.parameters["search"] ?: ""
|
val search = call.parameters["search"] ?: ""
|
||||||
val list = WorkGroupRepository.all()
|
val list = WorkGroupRepository.all()
|
||||||
respondMain {
|
respondMain {
|
||||||
menuTemplate {
|
|
||||||
this.user = user
|
|
||||||
active = MenuTemplate.Tab.WORK_GROUP
|
|
||||||
}
|
|
||||||
content {
|
content {
|
||||||
insert(TableTemplate()) {
|
insert(TableTemplate()) {
|
||||||
searchValue = search
|
searchValue = search
|
||||||
|
@ -166,10 +162,6 @@ fun Route.workGroup() {
|
||||||
}
|
}
|
||||||
|
|
||||||
respondMain {
|
respondMain {
|
||||||
menuTemplate {
|
|
||||||
this.user = user
|
|
||||||
active = MenuTemplate.Tab.WORK_GROUP
|
|
||||||
}
|
|
||||||
content {
|
content {
|
||||||
h1 { +"Edit work group" }
|
h1 { +"Edit work group" }
|
||||||
form(method = FormMethod.post) {
|
form(method = FormMethod.post) {
|
||||||
|
@ -529,10 +521,6 @@ fun Route.workGroup() {
|
||||||
authenticateOrRedirect(Permission.WORK_GROUP) { user ->
|
authenticateOrRedirect(Permission.WORK_GROUP) { user ->
|
||||||
val tracks = TrackRepository.all()
|
val tracks = TrackRepository.all()
|
||||||
respondMain {
|
respondMain {
|
||||||
menuTemplate {
|
|
||||||
this.user = user
|
|
||||||
active = MenuTemplate.Tab.WORK_GROUP
|
|
||||||
}
|
|
||||||
content {
|
content {
|
||||||
h1 { +"Create work group" }
|
h1 { +"Create work group" }
|
||||||
form(method = FormMethod.post) {
|
form(method = FormMethod.post) {
|
||||||
|
|
|
@ -1,21 +1,28 @@
|
||||||
package de.kif.backend.view
|
package de.kif.backend.view
|
||||||
|
|
||||||
|
import de.kif.backend.PortalSession
|
||||||
import de.kif.backend.Resources
|
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.ApplicationCall
|
||||||
import io.ktor.application.call
|
import io.ktor.application.call
|
||||||
import io.ktor.html.*
|
import io.ktor.html.*
|
||||||
import io.ktor.request.path
|
import io.ktor.request.path
|
||||||
|
import io.ktor.request.uri
|
||||||
import io.ktor.response.respondRedirect
|
import io.ktor.response.respondRedirect
|
||||||
|
import io.ktor.sessions.get
|
||||||
|
import io.ktor.sessions.sessions
|
||||||
import io.ktor.util.pipeline.PipelineContext
|
import io.ktor.util.pipeline.PipelineContext
|
||||||
import kotlinx.html.*
|
import kotlinx.html.*
|
||||||
|
|
||||||
class MainTemplate(
|
class MainTemplate(
|
||||||
private val theme: Theme,
|
private val theme: Theme,
|
||||||
|
private val url: String,
|
||||||
|
private val user: User?,
|
||||||
private val noMenu: Boolean,
|
private val noMenu: Boolean,
|
||||||
private val stretch: Boolean
|
private val stretch: Boolean
|
||||||
) : Template<HTML> {
|
) : Template<HTML> {
|
||||||
val content = Placeholder<HtmlBlockTag>()
|
val content = Placeholder<HtmlBlockTag>()
|
||||||
val menuTemplate = TemplatePlaceholder<MenuTemplate>()
|
|
||||||
|
|
||||||
override fun HTML.apply() {
|
override fun HTML.apply() {
|
||||||
head {
|
head {
|
||||||
|
@ -56,7 +63,7 @@ class MainTemplate(
|
||||||
}
|
}
|
||||||
body {
|
body {
|
||||||
if (!noMenu) {
|
if (!noMenu) {
|
||||||
insert(MenuTemplate(), menuTemplate)
|
insert(MenuTemplate(url, user)) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
val containerClasses = if (stretch) "container-full" else "container"
|
val containerClasses = if (stretch) "container-full" else "container"
|
||||||
|
@ -88,12 +95,16 @@ class MainTemplate(
|
||||||
}
|
}
|
||||||
|
|
||||||
enum class Theme {
|
enum class Theme {
|
||||||
LIGHT, DARK, PRINCESS
|
LIGHT, DARK, PRINCESS;
|
||||||
}
|
|
||||||
|
|
||||||
private fun String?.toTheme() = this?.let { str ->
|
companion object {
|
||||||
Theme.values().find { str == it.name }
|
private val loopup = values().toList().associateBy { it.name }
|
||||||
} ?: Theme.LIGHT
|
|
||||||
|
fun lookup(name: String?): Theme {
|
||||||
|
return loopup[(name ?: return LIGHT).toUpperCase()] ?: LIGHT
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
suspend fun PipelineContext<Unit, ApplicationCall>.respondMain(
|
suspend fun PipelineContext<Unit, ApplicationCall>.respondMain(
|
||||||
noMenu: Boolean = false,
|
noMenu: Boolean = false,
|
||||||
|
@ -101,11 +112,13 @@ suspend fun PipelineContext<Unit, ApplicationCall>.respondMain(
|
||||||
body: MainTemplate.() -> Unit
|
body: MainTemplate.() -> Unit
|
||||||
) {
|
) {
|
||||||
val param = call.request.queryParameters["theme"]
|
val param = call.request.queryParameters["theme"]
|
||||||
|
val url = call.request.uri.substring(1)
|
||||||
|
val user = call.sessions.get<PortalSession>()?.getUser(call)
|
||||||
|
|
||||||
if (param != null) {
|
if (param != null) {
|
||||||
call.response.cookies.append(
|
call.response.cookies.append(
|
||||||
name = "theme",
|
name = "theme",
|
||||||
value = param.toTheme().name,
|
value = Theme.lookup(param).name,
|
||||||
maxAge = Int.MAX_VALUE,
|
maxAge = Int.MAX_VALUE,
|
||||||
path = "/"
|
path = "/"
|
||||||
)
|
)
|
||||||
|
@ -113,7 +126,9 @@ suspend fun PipelineContext<Unit, ApplicationCall>.respondMain(
|
||||||
} else {
|
} else {
|
||||||
call.respondHtmlTemplate(
|
call.respondHtmlTemplate(
|
||||||
MainTemplate(
|
MainTemplate(
|
||||||
call.request.cookies["theme"].toTheme(),
|
Theme.lookup(call.request.cookies["theme"]),
|
||||||
|
url,
|
||||||
|
user,
|
||||||
noMenu,
|
noMenu,
|
||||||
stretch
|
stretch
|
||||||
),
|
),
|
||||||
|
|
|
@ -5,19 +5,21 @@ import de.kif.common.model.User
|
||||||
import io.ktor.html.Template
|
import io.ktor.html.Template
|
||||||
import kotlinx.html.*
|
import kotlinx.html.*
|
||||||
|
|
||||||
class MenuTemplate() : Template<FlowContent> {
|
class MenuTemplate(
|
||||||
|
private val url: String,
|
||||||
var active: Tab = Tab.BOARD
|
private val user: User?
|
||||||
var user: User? = null
|
) : Template<FlowContent> {
|
||||||
|
|
||||||
override fun FlowContent.apply() {
|
override fun FlowContent.apply() {
|
||||||
|
val tab = Tab.lookup(url)
|
||||||
|
|
||||||
nav("menu") {
|
nav("menu") {
|
||||||
div("container") {
|
div("container") {
|
||||||
div("menu-left") {
|
div("menu-left") {
|
||||||
a("/", classes = if (active == Tab.BOARD) "active" else null) {
|
a("/", classes = if (tab == null) "active" else null) {
|
||||||
+"Dashboard"
|
+"News"
|
||||||
}
|
}
|
||||||
a("/calendar", classes = if (active == Tab.CALENDAR) "active" else null) {
|
a("/calendar", classes = if (tab == Tab.CALENDAR) "active" else null) {
|
||||||
+"Calendar"
|
+"Calendar"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -28,26 +30,26 @@ class MenuTemplate() : Template<FlowContent> {
|
||||||
val user = user
|
val user = user
|
||||||
div("menu-content") {
|
div("menu-content") {
|
||||||
if (user == null) {
|
if (user == null) {
|
||||||
a("/account", classes = if (active == Tab.ACCOUNT) "active" else null) {
|
a("/account", classes = if (tab == Tab.LOGIN) "active" else null) {
|
||||||
+"Login"
|
+"Login"
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (user.checkPermission(Permission.WORK_GROUP)) {
|
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"
|
+"Work groups"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (user.checkPermission(Permission.ROOM)) {
|
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"
|
+"Rooms"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (user.checkPermission(Permission.USER)) {
|
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"
|
+"Users"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
a("/account", classes = if (active == Tab.ACCOUNT) "active" else null) {
|
a("/account", classes = if (tab == Tab.ACCOUNT) "active" else null) {
|
||||||
+user.username
|
+user.username
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -58,6 +60,14 @@ class MenuTemplate() : Template<FlowContent> {
|
||||||
}
|
}
|
||||||
|
|
||||||
enum class Tab {
|
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)]
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue