Add repositories
- Make model classes imutable - Move model classes to common module - Add repositories for backend and frontend - Backend repositories communicates with sqlite - Frontend repositories commuincates with rest api - Add rest api - Authentication via cookies - Add coroutines to forntend - Add change listener to repositories - Transmit change events to frontend repositories via websocket - Switch to server side sessions - Move setup from ui to cli
This commit is contained in:
parent
4e5dc610a3
commit
b7d6476a70
66 changed files with 2854 additions and 2336 deletions
76
src/jsMain/kotlin/de/kif/frontend/WebSocketClient.kt
Normal file
76
src/jsMain/kotlin/de/kif/frontend/WebSocketClient.kt
Normal file
|
@ -0,0 +1,76 @@
|
|||
package de.kif.frontend
|
||||
|
||||
import de.kif.common.Message
|
||||
import de.kif.common.MessageType
|
||||
import de.kif.common.RepositoryType
|
||||
import de.kif.frontend.repository.*
|
||||
import de.westermann.kwebview.async
|
||||
import org.w3c.dom.MessageEvent
|
||||
import org.w3c.dom.WebSocket
|
||||
import org.w3c.dom.events.Event
|
||||
import kotlin.browser.window
|
||||
|
||||
class WebSocketClient() {
|
||||
private val url = "ws://${window.location.host}/"
|
||||
|
||||
private lateinit var ws: WebSocket
|
||||
|
||||
@Suppress("UNUSED_PARAMETER")
|
||||
private fun onOpen(event: Event) {
|
||||
console.log("Connected!")
|
||||
}
|
||||
|
||||
private fun onMessage(messageEvent: MessageEvent) {
|
||||
val message = Message.parse(messageEvent.data?.toString() ?: "")
|
||||
|
||||
for (handler in messageHandlers) {
|
||||
if (handler.repository == message.repository) {
|
||||
when (message.type) {
|
||||
MessageType.CREATE -> handler.onCreate(message.id)
|
||||
MessageType.UPDATE -> handler.onUpdate(message.id)
|
||||
MessageType.DELETE -> handler.onDelete(message.id)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("UNUSED_PARAMETER")
|
||||
private fun onClose(event: Event) {
|
||||
console.log("Disconnected!")
|
||||
async(1000) {
|
||||
connect()
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("UNUSED_PARAMETER")
|
||||
private fun onError(event: Event) {
|
||||
console.log("An error occurred!")
|
||||
}
|
||||
|
||||
private fun connect() {
|
||||
ws = WebSocket(url)
|
||||
|
||||
ws.onopen = this::onOpen
|
||||
ws.onmessage = this::onMessage
|
||||
ws.onclose = this::onClose
|
||||
ws.onerror = this::onError
|
||||
}
|
||||
|
||||
private val messageHandlers: List<MessageHandler> = listOf(
|
||||
RoomRepository.handler,
|
||||
ScheduleRepository.handler,
|
||||
TrackRepository.handler,
|
||||
UserRepository.handler,
|
||||
WorkGroupRepository.handler
|
||||
)
|
||||
|
||||
init {
|
||||
connect()
|
||||
}
|
||||
}
|
||||
|
||||
abstract class MessageHandler(val repository: RepositoryType) {
|
||||
abstract fun onCreate(id: Long)
|
||||
abstract fun onUpdate(id: Long)
|
||||
abstract fun onDelete(id: Long)
|
||||
}
|
|
@ -1,11 +0,0 @@
|
|||
package de.kif.frontend.calendar
|
||||
|
||||
import de.westermann.kwebview.View
|
||||
import de.westermann.kwebview.ViewCollection
|
||||
import de.westermann.kwebview.createHtmlView
|
||||
|
||||
class Calendar : ViewCollection<View>(createHtmlView()) {
|
||||
init {
|
||||
|
||||
}
|
||||
}
|
39
src/jsMain/kotlin/de/kif/frontend/extensions.kt
Normal file
39
src/jsMain/kotlin/de/kif/frontend/extensions.kt
Normal file
|
@ -0,0 +1,39 @@
|
|||
package de.kif.frontend
|
||||
|
||||
import org.w3c.dom.*
|
||||
import kotlin.coroutines.Continuation
|
||||
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 ->
|
||||
console.error(exception)
|
||||
}
|
||||
})
|
|
@ -1,381 +1,13 @@
|
|||
package de.kif.frontend
|
||||
|
||||
import de.westermann.kobserve.property.mapBinding
|
||||
import de.westermann.kwebview.*
|
||||
import de.westermann.kwebview.components.Body
|
||||
import de.kif.frontend.views.initCalendar
|
||||
import de.westermann.kwebview.components.init
|
||||
import kif.common.model.*
|
||||
import org.w3c.dom.*
|
||||
import org.w3c.dom.events.EventListener
|
||||
import org.w3c.dom.events.MouseEvent
|
||||
import kotlin.browser.document
|
||||
import kotlin.browser.window
|
||||
import kotlin.collections.Iterator
|
||||
import kotlin.collections.List
|
||||
import kotlin.collections.emptyList
|
||||
import kotlin.collections.filter
|
||||
import kotlin.collections.find
|
||||
import kotlin.collections.forEach
|
||||
import kotlin.collections.listOf
|
||||
import kotlin.collections.minus
|
||||
import kotlin.collections.plus
|
||||
import kotlin.dom.appendText
|
||||
|
||||
class CalendarTools(entry: CalendarEntry, view: HTMLElement) : View(view) {
|
||||
|
||||
init {
|
||||
var linkM10: HTMLAnchorElement? = null
|
||||
var linkM5: HTMLAnchorElement? = null
|
||||
var linkReset: HTMLAnchorElement? = null
|
||||
var linkP5: HTMLAnchorElement? = null
|
||||
var linkP10: HTMLAnchorElement? = null
|
||||
var linkDel: HTMLAnchorElement? = null
|
||||
|
||||
for (element in html.children) {
|
||||
when {
|
||||
element.classList.contains("calendar-tools-m10") -> linkM10 = element as? HTMLAnchorElement
|
||||
element.classList.contains("calendar-tools-m5") -> linkM5 = element as? HTMLAnchorElement
|
||||
element.classList.contains("calendar-tools-reset") -> linkReset = element as? HTMLAnchorElement
|
||||
element.classList.contains("calendar-tools-p5") -> linkP5 = element as? HTMLAnchorElement
|
||||
element.classList.contains("calendar-tools-p10") -> linkP10 = element as? HTMLAnchorElement
|
||||
element.classList.contains("calendar-tools-del") -> linkDel = element as? HTMLAnchorElement
|
||||
}
|
||||
}
|
||||
|
||||
linkM10 = linkM10 ?: run {
|
||||
val link = createHtmlView<HTMLAnchorElement>()
|
||||
link.classList.add("calendar-tools-m10")
|
||||
link.textContent = "-10"
|
||||
html.appendChild(link)
|
||||
link
|
||||
}
|
||||
linkM10.removeAttribute("href")
|
||||
linkM10.addEventListener("click", EventListener {
|
||||
classList += "pending"
|
||||
get("/calendar/${entry.day}/${entry.room}/${entry.time}/-1") {
|
||||
get("/calendar/${entry.day}/${entry.room}/${entry.timeId - 10}/${entry.workGroup}") {
|
||||
println("success")
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
linkM5 = linkM5 ?: run {
|
||||
val link = createHtmlView<HTMLAnchorElement>()
|
||||
link.classList.add("calendar-tools-m5")
|
||||
link.textContent = "-5"
|
||||
html.appendChild(link)
|
||||
link
|
||||
}
|
||||
linkM5.removeAttribute("href")
|
||||
linkM5.addEventListener("click", EventListener {
|
||||
classList += "pending"
|
||||
get("/calendar/${entry.day}/${entry.room}/${entry.time}/-1") {
|
||||
get("/calendar/${entry.day}/${entry.room}/${entry.timeId - 5}/${entry.workGroup}") {
|
||||
println("success")
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
linkReset = linkReset ?: run {
|
||||
val link = createHtmlView<HTMLAnchorElement>()
|
||||
link.classList.add("calendar-tools-reset")
|
||||
link.textContent = "reset"
|
||||
html.appendChild(link)
|
||||
link
|
||||
}
|
||||
linkReset.removeAttribute("href")
|
||||
linkReset.addEventListener("click", EventListener {
|
||||
classList += "pending"
|
||||
get("/calendar/${entry.day}/${entry.room}/${entry.time}/-1") {
|
||||
get("/calendar/${entry.day}/${entry.room}/${entry.cellTime}/${entry.workGroup}") {
|
||||
println("success")
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
linkP5 = linkP5 ?: run {
|
||||
val link = createHtmlView<HTMLAnchorElement>()
|
||||
link.classList.add("calendar-tools-p5")
|
||||
link.textContent = "+5"
|
||||
html.appendChild(link)
|
||||
link
|
||||
}
|
||||
linkP5.removeAttribute("href")
|
||||
linkP5.addEventListener("click", EventListener {
|
||||
classList += "pending"
|
||||
get("/calendar/${entry.day}/${entry.room}/${entry.time}/-1") {
|
||||
get("/calendar/${entry.day}/${entry.room}/${entry.timeId + 5}/${entry.workGroup}") {
|
||||
println("success")
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
linkP10 = linkP10 ?: run {
|
||||
val link = createHtmlView<HTMLAnchorElement>()
|
||||
link.classList.add("calendar-tools-p10")
|
||||
link.textContent = "+10"
|
||||
html.appendChild(link)
|
||||
link
|
||||
}
|
||||
linkP10.removeAttribute("href")
|
||||
linkP10.addEventListener("click", EventListener {
|
||||
classList += "pending"
|
||||
get("/calendar/${entry.day}/${entry.room}/${entry.time}/-1") {
|
||||
get("/calendar/${entry.day}/${entry.room}/${entry.timeId + 10}/${entry.workGroup}") {
|
||||
println("success")
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
linkDel = linkDel ?: run {
|
||||
val link = createHtmlView<HTMLAnchorElement>()
|
||||
link.classList.add("calendar-tools-del")
|
||||
link.textContent = "del"
|
||||
html.appendChild(link)
|
||||
link
|
||||
}
|
||||
linkDel.removeAttribute("href")
|
||||
linkDel.addEventListener("click", EventListener {
|
||||
classList += "pending"
|
||||
get("/calendar/${entry.day}/${entry.room}/${entry.time}/-1") {
|
||||
println("success")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
class CalendarEntry(view: HTMLElement) : View(view) {
|
||||
|
||||
private lateinit var mouseDelta: Point
|
||||
private var newCell: CalendarCell? = null
|
||||
|
||||
|
||||
var day by dataset.property("day")
|
||||
val dayId by dataset.property("day").mapBinding { it?.toIntOrNull() ?: 0 }
|
||||
|
||||
var time by dataset.property("time")
|
||||
val timeId by dataset.property("time").mapBinding { it?.toIntOrNull() ?: 0 }
|
||||
|
||||
var room by dataset.property("room")
|
||||
val roomId by dataset.property("room").mapBinding { it?.toIntOrNull() ?: 0 }
|
||||
|
||||
var cellTime by dataset.property("cellTime")
|
||||
var language by dataset.property("language")
|
||||
|
||||
var workGroup by dataset.property("workgroup")
|
||||
val workGroupId by dataset.property("workgroup").mapBinding { it?.toIntOrNull() ?: 0 }
|
||||
private fun onMove(event: MouseEvent) {
|
||||
val position = event.toPoint() - mouseDelta
|
||||
|
||||
newCell?.classList?.remove("drag")
|
||||
|
||||
val cell = calendarCells.find {
|
||||
position in it.dimension
|
||||
}
|
||||
|
||||
if (cell != null) {
|
||||
cell.classList.add("drag")
|
||||
cell += this
|
||||
newCell = cell
|
||||
}
|
||||
|
||||
event.preventDefault()
|
||||
event.stopPropagation()
|
||||
}
|
||||
|
||||
private fun onFinishMove(event: MouseEvent) {
|
||||
classList -= "drag"
|
||||
|
||||
newCell?.let { cell ->
|
||||
cell.classList -= "drag"
|
||||
|
||||
val newTime = cell.time
|
||||
val newRoom = cell.room
|
||||
val day =
|
||||
(document.getElementsByClassName("calendar")[0] as? HTMLElement)?.dataset?.get("day")?.toIntOrNull()
|
||||
?: 0
|
||||
|
||||
classList += "pending"
|
||||
get("/calendar/$day/$room/$time/-1") {
|
||||
get("/calendar/$day/$newRoom/$newTime/$workGroup") {
|
||||
println("success")
|
||||
}
|
||||
}
|
||||
}
|
||||
newCell = null
|
||||
|
||||
for (it in listeners) {
|
||||
it.detach()
|
||||
}
|
||||
listeners = emptyList()
|
||||
|
||||
event.preventDefault()
|
||||
event.stopPropagation()
|
||||
}
|
||||
|
||||
private var listeners: List<de.westermann.kobserve.event.EventListener<*>> = emptyList()
|
||||
|
||||
init {
|
||||
onMouseDown { event ->
|
||||
if (event.target != html || "pending" in classList) {
|
||||
event.stopPropagation()
|
||||
return@onMouseDown
|
||||
}
|
||||
|
||||
classList += "drag"
|
||||
|
||||
mouseDelta = event.toPoint() - point
|
||||
|
||||
listeners = listOf(
|
||||
Body.onMouseMove.reference(this::onMove),
|
||||
Body.onMouseUp.reference(this::onFinishMove),
|
||||
Body.onMouseLeave.reference(this::onFinishMove)
|
||||
)
|
||||
|
||||
event.preventDefault()
|
||||
event.stopPropagation()
|
||||
}
|
||||
|
||||
var calendarTools: CalendarTools? = null
|
||||
for (item in html.children) {
|
||||
if (item.classList.contains("calendar-tools")) {
|
||||
calendarTools = CalendarTools(this, item)
|
||||
break
|
||||
}
|
||||
}
|
||||
if (calendarTools == null) {
|
||||
calendarTools = CalendarTools(this, createHtmlView())
|
||||
html.appendChild(calendarTools.html)
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun create(
|
||||
day: Int,
|
||||
time: Int,
|
||||
cellTime: Int,
|
||||
room: Int,
|
||||
workGroupId: Int,
|
||||
workGroupName: String,
|
||||
workGroupLength: Int,
|
||||
workGroupLanguage: String,
|
||||
workGroupColor: Color?
|
||||
): CalendarEntry {
|
||||
val entry = CalendarEntry(createHtmlView())
|
||||
|
||||
entry.day = day.toString()
|
||||
entry.time = time.toString()
|
||||
entry.cellTime = cellTime.toString()
|
||||
entry.room = room.toString()
|
||||
entry.workGroup = workGroupId.toString()
|
||||
entry.language = workGroupLanguage
|
||||
if (workGroupColor != null) {
|
||||
|
||||
entry.style {
|
||||
val size = workGroupLength / CALENDAR_GRID_WIDTH.toDouble()
|
||||
val pos = (time % CALENDAR_GRID_WIDTH) / CALENDAR_GRID_WIDTH.toDouble()
|
||||
|
||||
val pct = "${pos * 100}%"
|
||||
val sz = "${size * 100}%"
|
||||
|
||||
left = pct
|
||||
top = "calc($pct + 0.1rem)"
|
||||
|
||||
width = sz
|
||||
height = "calc($sz - 0.2rem)"
|
||||
|
||||
backgroundColor = workGroupColor.toString()
|
||||
color = workGroupColor.textColor.toString()
|
||||
}
|
||||
}
|
||||
entry.html.appendText(workGroupName)
|
||||
|
||||
return entry
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class CalendarCell(view: HTMLElement) : ViewCollection<CalendarEntry>(view) {
|
||||
var day by dataset.property("day")
|
||||
val dayId by dataset.property("day").mapBinding { it?.toIntOrNull() ?: 0 }
|
||||
|
||||
var time by dataset.property("time")
|
||||
val timeId by dataset.property("time").mapBinding { it?.toIntOrNull() ?: 0 }
|
||||
|
||||
var room by dataset.property("room")
|
||||
val roomId by dataset.property("room").mapBinding { it?.toIntOrNull() ?: 0 }
|
||||
}
|
||||
|
||||
var calendarEntries: List<CalendarEntry> = emptyList()
|
||||
var calendarCells: List<CalendarCell> = emptyList()
|
||||
|
||||
fun main() = init {
|
||||
WebSocketClient()
|
||||
|
||||
val ws = WebSocket("ws://${window.location.host}/".also { println(it) })
|
||||
|
||||
ws.onmessage = {
|
||||
val messageWrapper = Message.parse(it.data?.toString() ?: "")
|
||||
val message = messageWrapper.message
|
||||
|
||||
println(message)
|
||||
|
||||
when (message) {
|
||||
is MessageCreateCalendarEntry -> {
|
||||
val entry = CalendarEntry.create(
|
||||
message.day,
|
||||
message.time,
|
||||
message.cellTime,
|
||||
message.room,
|
||||
message.workGroupId,
|
||||
message.workGroupName,
|
||||
message.workGroupLength,
|
||||
message.workGroupLanguage,
|
||||
message.workGroupColor
|
||||
)
|
||||
for (cell in calendarCells) {
|
||||
if (cell.dayId == message.day && cell.timeId == message.cellTime && cell.roomId == message.room) {
|
||||
cell.html.appendChild(entry.html)
|
||||
calendarEntries += entry
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
is MessageDeleteCalendarEntry -> {
|
||||
val remove = calendarEntries.filter { entry ->
|
||||
entry.dayId == message.day &&
|
||||
entry.timeId == message.time &&
|
||||
entry.roomId == message.roomId &&
|
||||
entry.workGroupId == message.workGroupId
|
||||
}
|
||||
calendarEntries -= remove
|
||||
remove.forEach {
|
||||
it.html.remove()
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
}
|
||||
}
|
||||
if (document.getElementsByClassName("calendar").length > 0) {
|
||||
initCalendar()
|
||||
}
|
||||
|
||||
ws.onopen = {
|
||||
console.log("yes!")
|
||||
}
|
||||
|
||||
calendarEntries = document.getElementsByClassName("calendar-entry")
|
||||
.iterator().asSequence().map(::CalendarEntry).toList()
|
||||
|
||||
calendarCells = document.getElementsByClassName("calendar-cell")
|
||||
.iterator().asSequence().filter { it.dataset["time"] != null }.map(::CalendarCell).toList()
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
}
|
106
src/jsMain/kotlin/de/kif/frontend/repository/Ajax.kt
Normal file
106
src/jsMain/kotlin/de/kif/frontend/repository/Ajax.kt
Normal file
|
@ -0,0 +1,106 @@
|
|||
package de.kif.frontend.repository
|
||||
|
||||
import de.kif.common.Repository
|
||||
import de.kif.common.model.Model
|
||||
import org.w3c.xhr.XMLHttpRequest
|
||||
import kotlin.coroutines.resume
|
||||
import kotlin.coroutines.resumeWithException
|
||||
import kotlin.coroutines.suspendCoroutine
|
||||
import kotlin.js.Promise
|
||||
|
||||
suspend fun repositoryGet(
|
||||
url: String
|
||||
): dynamic {
|
||||
console.log("GET: $url")
|
||||
val promise = Promise<dynamic> { resolve, reject ->
|
||||
val xhttp = XMLHttpRequest()
|
||||
|
||||
xhttp.onreadystatechange = {
|
||||
if (xhttp.readyState == 4.toShort()) {
|
||||
if (xhttp.status == 200.toShort() || xhttp.status == 304.toShort()) {
|
||||
resolve(JSON.parse(xhttp.responseText))
|
||||
} else {
|
||||
reject(NoSuchElementException("${xhttp.status}: ${xhttp.statusText}"))
|
||||
}
|
||||
}
|
||||
}
|
||||
xhttp.open("GET", url, true)
|
||||
|
||||
xhttp.send()
|
||||
}
|
||||
|
||||
try {
|
||||
val d = promise.await()
|
||||
|
||||
return if (d.OK) {
|
||||
d.data
|
||||
} else {
|
||||
null
|
||||
}
|
||||
} catch (e: NoSuchElementException) {
|
||||
console.error(e)
|
||||
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun repositoryPost(
|
||||
url: String,
|
||||
data: String? = null
|
||||
): dynamic {
|
||||
console.log("POST: $url", data)
|
||||
val promise = Promise<dynamic> { resolve, reject ->
|
||||
val xhttp = XMLHttpRequest()
|
||||
|
||||
xhttp.onreadystatechange = {
|
||||
if (xhttp.readyState == 4.toShort()) {
|
||||
if (xhttp.status == 200.toShort() || xhttp.status == 304.toShort()) {
|
||||
resolve(JSON.parse(xhttp.responseText))
|
||||
} else {
|
||||
reject(NoSuchElementException("${xhttp.status}: ${xhttp.statusText}"))
|
||||
}
|
||||
}
|
||||
}
|
||||
xhttp.open("POST", url, true)
|
||||
xhttp.setRequestHeader("Content-type", "application/json");
|
||||
|
||||
xhttp.send(data)
|
||||
}
|
||||
|
||||
try {
|
||||
val d = promise.await()
|
||||
|
||||
return if (d.OK) {
|
||||
d.data
|
||||
} else {
|
||||
null
|
||||
}
|
||||
} catch (e: NoSuchElementException) {
|
||||
console.error(e)
|
||||
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun <T> Promise<T>.await(): T = suspendCoroutine { cont ->
|
||||
then({ cont.resume(it) }, { cont.resumeWithException(it) })
|
||||
}
|
||||
|
||||
class RepositoryDelegate<T : Model>(
|
||||
private val repository: Repository<T>,
|
||||
private val id: Long
|
||||
) {
|
||||
|
||||
private var backing: T? = null
|
||||
|
||||
suspend fun get(): T {
|
||||
if (backing == null) {
|
||||
backing = repository.get(id) ?: throw NoSuchElementException()
|
||||
}
|
||||
return backing!!
|
||||
}
|
||||
|
||||
fun set(value: T) {
|
||||
backing = value
|
||||
}
|
||||
}
|
|
@ -0,0 +1,52 @@
|
|||
package de.kif.frontend.repository
|
||||
|
||||
import de.kif.common.Message
|
||||
import de.kif.common.Repository
|
||||
import de.kif.common.RepositoryType
|
||||
import de.kif.common.model.Room
|
||||
import de.kif.frontend.MessageHandler
|
||||
import de.westermann.kobserve.event.EventHandler
|
||||
import kotlinx.serialization.DynamicObjectParser
|
||||
import kotlinx.serialization.list
|
||||
|
||||
object RoomRepository : Repository<Room> {
|
||||
|
||||
override val onCreate = EventHandler<Long>()
|
||||
override val onUpdate = EventHandler<Long>()
|
||||
override val onDelete = EventHandler<Long>()
|
||||
|
||||
private val parser = DynamicObjectParser()
|
||||
|
||||
override suspend fun get(id: Long): Room? {
|
||||
val json = repositoryGet("/api/room/$id") ?: return null
|
||||
return parser.parse(json, Room.serializer())
|
||||
}
|
||||
|
||||
override suspend fun create(model: Room): Long {
|
||||
return repositoryPost("/api/rooms", Message.json.stringify(Room.serializer(), model))?.toLong()
|
||||
?: throw IllegalStateException("Cannot create model!")
|
||||
}
|
||||
|
||||
override suspend fun update(model: Room) {
|
||||
if (model.id == null) throw IllegalStateException("Cannot update model which was not created!")
|
||||
repositoryPost("/api/room/${model.id}", Message.json.stringify(Room.serializer(), model))
|
||||
}
|
||||
|
||||
override suspend fun delete(id: Long) {
|
||||
repositoryPost("/api/room/$id/delete")
|
||||
}
|
||||
|
||||
override suspend fun all(): List<Room> {
|
||||
val json = repositoryGet("/api/rooms") ?: return emptyList()
|
||||
return parser.parse(json, Room.serializer().list)
|
||||
}
|
||||
|
||||
val handler = object : MessageHandler(RepositoryType.ROOM) {
|
||||
|
||||
override fun onCreate(id: Long) = onCreate.emit(id)
|
||||
|
||||
override fun onUpdate(id: Long) = onUpdate.emit(id)
|
||||
|
||||
override fun onDelete(id: Long) = onDelete.emit(id)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,52 @@
|
|||
package de.kif.frontend.repository
|
||||
|
||||
import de.kif.common.Message
|
||||
import de.kif.common.Repository
|
||||
import de.kif.common.RepositoryType
|
||||
import de.kif.common.model.Schedule
|
||||
import de.kif.frontend.MessageHandler
|
||||
import de.westermann.kobserve.event.EventHandler
|
||||
import kotlinx.serialization.DynamicObjectParser
|
||||
import kotlinx.serialization.list
|
||||
|
||||
object ScheduleRepository : Repository<Schedule> {
|
||||
|
||||
override val onCreate = EventHandler<Long>()
|
||||
override val onUpdate = EventHandler<Long>()
|
||||
override val onDelete = EventHandler<Long>()
|
||||
|
||||
private val parser = DynamicObjectParser()
|
||||
|
||||
override suspend fun get(id: Long): Schedule? {
|
||||
val json = repositoryGet("/api/schedule/$id") ?: return null
|
||||
return parser.parse(json, Schedule.serializer())
|
||||
}
|
||||
|
||||
override suspend fun create(model: Schedule): Long {
|
||||
return repositoryPost("/api/schedules", Message.json.stringify(Schedule.serializer(), model))?.toLong()
|
||||
?: throw IllegalStateException("Cannot create model!")
|
||||
}
|
||||
|
||||
override suspend fun update(model: Schedule) {
|
||||
if (model.id == null) throw IllegalStateException("Cannot update model which was not created!")
|
||||
repositoryPost("/api/schedule/${model.id}", Message.json.stringify(Schedule.serializer(), model))
|
||||
}
|
||||
|
||||
override suspend fun delete(id: Long) {
|
||||
repositoryPost("/api/schedule/$id/delete")
|
||||
}
|
||||
|
||||
override suspend fun all(): List<Schedule> {
|
||||
val json = repositoryGet("/api/schedules") ?: return emptyList()
|
||||
return parser.parse(json, Schedule.serializer().list)
|
||||
}
|
||||
|
||||
val handler = object : MessageHandler(RepositoryType.SCHEDULE) {
|
||||
|
||||
override fun onCreate(id: Long) = onCreate.emit(id)
|
||||
|
||||
override fun onUpdate(id: Long) = onUpdate.emit(id)
|
||||
|
||||
override fun onDelete(id: Long) = onDelete.emit(id)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,52 @@
|
|||
package de.kif.frontend.repository
|
||||
|
||||
import de.kif.common.Message
|
||||
import de.kif.common.Repository
|
||||
import de.kif.common.RepositoryType
|
||||
import de.kif.common.model.Track
|
||||
import de.kif.frontend.MessageHandler
|
||||
import de.westermann.kobserve.event.EventHandler
|
||||
import kotlinx.serialization.DynamicObjectParser
|
||||
import kotlinx.serialization.list
|
||||
|
||||
object TrackRepository : Repository<Track> {
|
||||
|
||||
override val onCreate = EventHandler<Long>()
|
||||
override val onUpdate = EventHandler<Long>()
|
||||
override val onDelete = EventHandler<Long>()
|
||||
|
||||
private val parser = DynamicObjectParser()
|
||||
|
||||
override suspend fun get(id: Long): Track? {
|
||||
val json = repositoryGet("/api/track/$id") ?: return null
|
||||
return parser.parse(json, Track.serializer())
|
||||
}
|
||||
|
||||
override suspend fun create(model: Track): Long {
|
||||
return repositoryPost("/api/tracks", Message.json.stringify(Track.serializer(), model))?.toLong()
|
||||
?: throw IllegalStateException("Cannot create model!")
|
||||
}
|
||||
|
||||
override suspend fun update(model: Track) {
|
||||
if (model.id == null) throw IllegalStateException("Cannot update model which was not created!")
|
||||
repositoryPost("/api/track/${model.id}", Message.json.stringify(Track.serializer(), model))
|
||||
}
|
||||
|
||||
override suspend fun delete(id: Long) {
|
||||
repositoryPost("/api/track/$id/delete")
|
||||
}
|
||||
|
||||
override suspend fun all(): List<Track> {
|
||||
val json = repositoryGet("/api/tracks") ?: return emptyList()
|
||||
return parser.parse(json, Track.serializer().list)
|
||||
}
|
||||
|
||||
val handler = object : MessageHandler(RepositoryType.TRACK) {
|
||||
|
||||
override fun onCreate(id: Long) = onCreate.emit(id)
|
||||
|
||||
override fun onUpdate(id: Long) = onUpdate.emit(id)
|
||||
|
||||
override fun onDelete(id: Long) = onDelete.emit(id)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,52 @@
|
|||
package de.kif.frontend.repository
|
||||
|
||||
import de.kif.common.Message
|
||||
import de.kif.common.Repository
|
||||
import de.kif.common.RepositoryType
|
||||
import de.kif.common.model.User
|
||||
import de.kif.frontend.MessageHandler
|
||||
import de.westermann.kobserve.event.EventHandler
|
||||
import kotlinx.serialization.DynamicObjectParser
|
||||
import kotlinx.serialization.list
|
||||
|
||||
object UserRepository : Repository<User> {
|
||||
|
||||
override val onCreate = EventHandler<Long>()
|
||||
override val onUpdate = EventHandler<Long>()
|
||||
override val onDelete = EventHandler<Long>()
|
||||
|
||||
private val parser = DynamicObjectParser()
|
||||
|
||||
override suspend fun get(id: Long): User? {
|
||||
val json = repositoryGet("/api/user/$id") ?: return null
|
||||
return parser.parse(json, User.serializer())
|
||||
}
|
||||
|
||||
override suspend fun create(model: User): Long {
|
||||
return repositoryPost("/api/users", Message.json.stringify(User.serializer(), model))?.toLong()
|
||||
?: throw IllegalStateException("Cannot create model!")
|
||||
}
|
||||
|
||||
override suspend fun update(model: User) {
|
||||
if (model.id == null) throw IllegalStateException("Cannot update model which was not created!")
|
||||
repositoryPost("/api/user/${model.id}", Message.json.stringify(User.serializer(), model))
|
||||
}
|
||||
|
||||
override suspend fun delete(id: Long) {
|
||||
repositoryPost("/api/user/$id/delete")
|
||||
}
|
||||
|
||||
override suspend fun all(): List<User> {
|
||||
val json = repositoryGet("/api/users") ?: return emptyList()
|
||||
return parser.parse(json, User.serializer().list)
|
||||
}
|
||||
|
||||
val handler = object : MessageHandler(RepositoryType.USER) {
|
||||
|
||||
override fun onCreate(id: Long) = onCreate.emit(id)
|
||||
|
||||
override fun onUpdate(id: Long) = onUpdate.emit(id)
|
||||
|
||||
override fun onDelete(id: Long) = onDelete.emit(id)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,52 @@
|
|||
package de.kif.frontend.repository
|
||||
|
||||
import de.kif.common.Message
|
||||
import de.kif.common.Repository
|
||||
import de.kif.common.RepositoryType
|
||||
import de.kif.common.model.WorkGroup
|
||||
import de.kif.frontend.MessageHandler
|
||||
import de.westermann.kobserve.event.EventHandler
|
||||
import kotlinx.serialization.DynamicObjectParser
|
||||
import kotlinx.serialization.list
|
||||
|
||||
object WorkGroupRepository : Repository<WorkGroup> {
|
||||
|
||||
override val onCreate = EventHandler<Long>()
|
||||
override val onUpdate = EventHandler<Long>()
|
||||
override val onDelete = EventHandler<Long>()
|
||||
|
||||
private val parser = DynamicObjectParser()
|
||||
|
||||
override suspend fun get(id: Long): WorkGroup? {
|
||||
val json = repositoryGet("/api/workgroup/$id") ?: return null
|
||||
return parser.parse(json, WorkGroup.serializer())
|
||||
}
|
||||
|
||||
override suspend fun create(model: WorkGroup): Long {
|
||||
return repositoryPost("/api/workgroups", Message.json.stringify(WorkGroup.serializer(), model))?.toLong()
|
||||
?: throw IllegalStateException("Cannot create model!")
|
||||
}
|
||||
|
||||
override suspend fun update(model: WorkGroup) {
|
||||
if (model.id == null) throw IllegalStateException("Cannot update model which was not created!")
|
||||
repositoryPost("/api/workgroup/${model.id}", Message.json.stringify(WorkGroup.serializer(), model))
|
||||
}
|
||||
|
||||
override suspend fun delete(id: Long) {
|
||||
repositoryPost("/api/workgroup/$id/delete")
|
||||
}
|
||||
|
||||
override suspend fun all(): List<WorkGroup> {
|
||||
val json = repositoryGet("/api/workgroups") ?: return emptyList()
|
||||
return parser.parse(json, WorkGroup.serializer().list)
|
||||
}
|
||||
|
||||
val handler = object : MessageHandler(RepositoryType.WORK_GROUP) {
|
||||
|
||||
override fun onCreate(id: Long) = onCreate.emit(id)
|
||||
|
||||
override fun onUpdate(id: Long) = onUpdate.emit(id)
|
||||
|
||||
override fun onDelete(id: Long) = onDelete.emit(id)
|
||||
}
|
||||
}
|
359
src/jsMain/kotlin/de/kif/frontend/views/Calendar.kt
Normal file
359
src/jsMain/kotlin/de/kif/frontend/views/Calendar.kt
Normal file
|
@ -0,0 +1,359 @@
|
|||
package de.kif.frontend.views
|
||||
|
||||
import de.kif.common.CALENDAR_GRID_WIDTH
|
||||
import de.kif.common.model.Room
|
||||
import de.kif.common.model.Schedule
|
||||
import de.kif.frontend.iterator
|
||||
import de.kif.frontend.launch
|
||||
import de.kif.frontend.repository.RepositoryDelegate
|
||||
import de.kif.frontend.repository.RoomRepository
|
||||
import de.kif.frontend.repository.ScheduleRepository
|
||||
import de.westermann.kwebview.*
|
||||
import de.westermann.kwebview.components.Body
|
||||
import org.w3c.dom.HTMLAnchorElement
|
||||
import org.w3c.dom.HTMLElement
|
||||
import org.w3c.dom.events.EventListener
|
||||
import org.w3c.dom.events.MouseEvent
|
||||
import org.w3c.dom.get
|
||||
import kotlin.browser.document
|
||||
import kotlin.dom.appendText
|
||||
import kotlin.dom.isText
|
||||
|
||||
class CalendarTools(entry: CalendarEntry, view: HTMLElement) : View(view) {
|
||||
|
||||
init {
|
||||
var linkM10: HTMLAnchorElement? = null
|
||||
var linkM5: HTMLAnchorElement? = null
|
||||
var linkReset: HTMLAnchorElement? = null
|
||||
var linkP5: HTMLAnchorElement? = null
|
||||
var linkP10: HTMLAnchorElement? = null
|
||||
var linkDel: HTMLAnchorElement? = null
|
||||
|
||||
for (element in html.children) {
|
||||
when {
|
||||
element.classList.contains("calendar-tools-m10") -> linkM10 = element as? HTMLAnchorElement
|
||||
element.classList.contains("calendar-tools-m5") -> linkM5 = element as? HTMLAnchorElement
|
||||
element.classList.contains("calendar-tools-reset") -> linkReset = element as? HTMLAnchorElement
|
||||
element.classList.contains("calendar-tools-p5") -> linkP5 = element as? HTMLAnchorElement
|
||||
element.classList.contains("calendar-tools-p10") -> linkP10 = element as? HTMLAnchorElement
|
||||
element.classList.contains("calendar-tools-del") -> linkDel = element as? HTMLAnchorElement
|
||||
}
|
||||
}
|
||||
|
||||
linkM10 = linkM10 ?: run {
|
||||
val link = createHtmlView<HTMLAnchorElement>()
|
||||
link.classList.add("calendar-tools-m10")
|
||||
link.textContent = "-10"
|
||||
html.appendChild(link)
|
||||
link
|
||||
}
|
||||
linkM10.removeAttribute("href")
|
||||
linkM10.addEventListener("click", EventListener {
|
||||
entry.pending = true
|
||||
launch {
|
||||
val s = entry.schedule.get()
|
||||
ScheduleRepository.update(s.copy(time = s.time - 10))
|
||||
}
|
||||
})
|
||||
|
||||
linkM5 = linkM5 ?: run {
|
||||
val link = createHtmlView<HTMLAnchorElement>()
|
||||
link.classList.add("calendar-tools-m5")
|
||||
link.textContent = "-5"
|
||||
html.appendChild(link)
|
||||
link
|
||||
}
|
||||
linkM5.removeAttribute("href")
|
||||
linkM5.addEventListener("click", EventListener {
|
||||
entry.pending = true
|
||||
launch {
|
||||
val s = entry.schedule.get()
|
||||
ScheduleRepository.update(s.copy(time = s.time - 5))
|
||||
}
|
||||
})
|
||||
|
||||
linkReset = linkReset ?: run {
|
||||
val link = createHtmlView<HTMLAnchorElement>()
|
||||
link.classList.add("calendar-tools-reset")
|
||||
link.textContent = "reset"
|
||||
html.appendChild(link)
|
||||
link
|
||||
}
|
||||
linkReset.removeAttribute("href")
|
||||
linkReset.addEventListener("click", EventListener {
|
||||
entry.pending = true
|
||||
launch {
|
||||
val s = entry.schedule.get()
|
||||
ScheduleRepository.update(s.copy(time = s.time / CALENDAR_GRID_WIDTH * CALENDAR_GRID_WIDTH))
|
||||
}
|
||||
})
|
||||
|
||||
linkP5 = linkP5 ?: run {
|
||||
val link = createHtmlView<HTMLAnchorElement>()
|
||||
link.classList.add("calendar-tools-p5")
|
||||
link.textContent = "+5"
|
||||
html.appendChild(link)
|
||||
link
|
||||
}
|
||||
linkP5.removeAttribute("href")
|
||||
linkP5.addEventListener("click", EventListener {
|
||||
entry.pending = true
|
||||
launch {
|
||||
val s = entry.schedule.get()
|
||||
ScheduleRepository.update(s.copy(time = s.time + 5))
|
||||
}
|
||||
})
|
||||
|
||||
linkP10 = linkP10 ?: run {
|
||||
val link = createHtmlView<HTMLAnchorElement>()
|
||||
link.classList.add("calendar-tools-p10")
|
||||
link.textContent = "+10"
|
||||
html.appendChild(link)
|
||||
link
|
||||
}
|
||||
linkP10.removeAttribute("href")
|
||||
linkP10.addEventListener("click", EventListener {
|
||||
entry.pending = true
|
||||
launch {
|
||||
val s = entry.schedule.get()
|
||||
ScheduleRepository.update(s.copy(time = s.time + 10))
|
||||
}
|
||||
})
|
||||
|
||||
linkDel = linkDel ?: run {
|
||||
val link = createHtmlView<HTMLAnchorElement>()
|
||||
link.classList.add("calendar-tools-del")
|
||||
link.textContent = "del"
|
||||
html.appendChild(link)
|
||||
link
|
||||
}
|
||||
linkDel.removeAttribute("href")
|
||||
linkDel.addEventListener("click", EventListener {
|
||||
entry.pending = true
|
||||
launch {
|
||||
ScheduleRepository.delete(entry.scheduleId)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
class CalendarEntry(view: HTMLElement) : View(view) {
|
||||
|
||||
private lateinit var mouseDelta: Point
|
||||
private var newCell: CalendarCell? = null
|
||||
|
||||
private var language by dataset.property("language")
|
||||
|
||||
val scheduleId = dataset["id"]?.toLongOrNull() ?: 0
|
||||
|
||||
val schedule = RepositoryDelegate(ScheduleRepository, scheduleId)
|
||||
|
||||
var pending by classList.property("pending")
|
||||
|
||||
private fun onMove(event: MouseEvent) {
|
||||
val position = event.toPoint() - mouseDelta
|
||||
|
||||
val cell = calendarCells.find {
|
||||
position in it.dimension
|
||||
}
|
||||
|
||||
|
||||
if (cell != null) {
|
||||
cell += this
|
||||
|
||||
if (newCell == null) {
|
||||
style {
|
||||
left = "0"
|
||||
top = "0.1rem"
|
||||
}
|
||||
}
|
||||
|
||||
newCell = cell
|
||||
}
|
||||
|
||||
event.preventDefault()
|
||||
event.stopPropagation()
|
||||
}
|
||||
|
||||
private fun onFinishMove(event: MouseEvent) {
|
||||
classList -= "drag"
|
||||
|
||||
newCell?.let { cell ->
|
||||
launch {
|
||||
val newTime = cell.time
|
||||
val newRoom = cell.getRoom()
|
||||
|
||||
pending = true
|
||||
|
||||
val s = schedule.get().copy(room = newRoom, time = newTime)
|
||||
|
||||
ScheduleRepository.update(s)
|
||||
}
|
||||
}
|
||||
newCell = null
|
||||
|
||||
for (it in listeners) {
|
||||
it.detach()
|
||||
}
|
||||
listeners = emptyList()
|
||||
|
||||
event.preventDefault()
|
||||
event.stopPropagation()
|
||||
}
|
||||
|
||||
private var listeners: List<de.westermann.kobserve.event.EventListener<*>> = emptyList()
|
||||
|
||||
init {
|
||||
onMouseDown { event ->
|
||||
if (event.target != html || "pending" in classList) {
|
||||
event.stopPropagation()
|
||||
return@onMouseDown
|
||||
}
|
||||
|
||||
launch {
|
||||
classList += "drag"
|
||||
|
||||
val s = schedule.get()
|
||||
val time = s.time / CALENDAR_GRID_WIDTH * CALENDAR_GRID_WIDTH
|
||||
|
||||
val p = calendarCells.find {
|
||||
it.day == s.day && it.time == time && it.roomId == s.room.id
|
||||
}?.dimension?.center ?: dimension.center
|
||||
|
||||
mouseDelta = event.toPoint() - p
|
||||
|
||||
listeners = listOf(
|
||||
Body.onMouseMove.reference(this::onMove),
|
||||
Body.onMouseUp.reference(this::onFinishMove),
|
||||
Body.onMouseLeave.reference(this::onFinishMove)
|
||||
)
|
||||
}
|
||||
event.preventDefault()
|
||||
event.stopPropagation()
|
||||
}
|
||||
|
||||
var calendarTools: CalendarTools? = null
|
||||
for (item in html.children) {
|
||||
if (item.classList.contains("calendar-tools")) {
|
||||
calendarTools = CalendarTools(this, item)
|
||||
break
|
||||
}
|
||||
}
|
||||
if (calendarTools == null) {
|
||||
calendarTools = CalendarTools(this, createHtmlView())
|
||||
html.appendChild(calendarTools.html)
|
||||
}
|
||||
}
|
||||
|
||||
fun load(schedule: Schedule) {
|
||||
pending = false
|
||||
|
||||
language = schedule.workGroup.language.code
|
||||
|
||||
this.schedule.set(schedule)
|
||||
|
||||
style {
|
||||
val size = schedule.workGroup.length / CALENDAR_GRID_WIDTH.toDouble()
|
||||
val pos = (schedule.time % CALENDAR_GRID_WIDTH) / CALENDAR_GRID_WIDTH.toDouble()
|
||||
|
||||
val ps = "${pos * 100}%"
|
||||
val sz = "${size * 100}%"
|
||||
|
||||
left = ps
|
||||
top = "calc($ps + 0.1rem)"
|
||||
|
||||
width = sz
|
||||
height = "calc($sz - 0.2rem)"
|
||||
|
||||
if (schedule.workGroup.track?.color != null) {
|
||||
backgroundColor = schedule.workGroup.track.color.toString()
|
||||
color = schedule.workGroup.track.color.calcTextColor().toString()
|
||||
}
|
||||
}
|
||||
|
||||
for (element in html.childNodes) {
|
||||
if (element.isText) {
|
||||
html.removeChild(element)
|
||||
}
|
||||
}
|
||||
|
||||
html.appendText(schedule.workGroup.name)
|
||||
|
||||
|
||||
val time = schedule.time / CALENDAR_GRID_WIDTH * CALENDAR_GRID_WIDTH
|
||||
val cell = calendarCells.find {
|
||||
it.day == schedule.day && it.time == time && it.roomId == schedule.room.id
|
||||
}
|
||||
|
||||
if (cell != null && cell.html != html.parentElement) {
|
||||
cell += this
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun create(schedule: Schedule): CalendarEntry {
|
||||
val entry = CalendarEntry(createHtmlView())
|
||||
|
||||
entry.load(schedule)
|
||||
|
||||
return entry
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
suspend fun getRoom(): Room {
|
||||
if (this::room.isInitialized) {
|
||||
return room
|
||||
}
|
||||
|
||||
room = RoomRepository.get(roomId) ?: throw NoSuchElementException()
|
||||
return room
|
||||
}
|
||||
}
|
||||
|
||||
var calendarEntries: List<CalendarEntry> = emptyList()
|
||||
var calendarCells: List<CalendarCell> = emptyList()
|
||||
|
||||
fun initCalendar() {
|
||||
calendarEntries = document.getElementsByClassName("calendar-entry")
|
||||
.iterator().asSequence().map(::CalendarEntry).toList()
|
||||
|
||||
calendarCells = document.getElementsByClassName("calendar-cell")
|
||||
.iterator().asSequence().filter { it.dataset["time"] != null }.map(::CalendarCell).toList()
|
||||
|
||||
ScheduleRepository.onCreate {
|
||||
launch {
|
||||
val schedule = ScheduleRepository.get(it) ?: throw NoSuchElementException()
|
||||
CalendarEntry.create(schedule)
|
||||
}
|
||||
}
|
||||
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) {
|
||||
CalendarEntry.create(schedule)
|
||||
}
|
||||
}
|
||||
}
|
||||
ScheduleRepository.onDelete {
|
||||
for (entry in calendarEntries) {
|
||||
if (entry.scheduleId == it) {
|
||||
entry.html.remove()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -3,6 +3,7 @@ package de.westermann.kwebview
|
|||
import de.westermann.kobserve.event.EventListener
|
||||
import de.westermann.kobserve.Property
|
||||
import de.westermann.kobserve.ReadOnlyProperty
|
||||
import de.westermann.kobserve.property.property
|
||||
import org.w3c.dom.DOMTokenList
|
||||
|
||||
/**
|
||||
|
@ -97,6 +98,23 @@ class ClassList(
|
|||
)
|
||||
}
|
||||
|
||||
fun property(clazz: String): Property<Boolean> {
|
||||
if (clazz in bound) {
|
||||
throw IllegalArgumentException("Class is already bound!")
|
||||
}
|
||||
|
||||
val property = property(get(clazz))
|
||||
|
||||
bound[clazz] = Bound(property,
|
||||
property.onChange.reference {
|
||||
list.toggle(clazz, property.value)
|
||||
}
|
||||
)
|
||||
|
||||
return property
|
||||
}
|
||||
|
||||
|
||||
fun unbind(clazz: String) {
|
||||
if (clazz !in bound) {
|
||||
throw IllegalArgumentException("Class is not bound!")
|
||||
|
|
|
@ -7,10 +7,10 @@ import kotlin.math.min
|
|||
* @author lars
|
||||
*/
|
||||
data class Dimension(
|
||||
val left: Double,
|
||||
val top: Double,
|
||||
val width: Double = 0.0,
|
||||
val height: Double = 0.0
|
||||
val left: Double,
|
||||
val top: Double,
|
||||
val width: Double = 0.0,
|
||||
val height: Double = 0.0
|
||||
) {
|
||||
|
||||
constructor(position: Point, size: Point = Point.ZERO) : this(position.x, position.y, size.x, size.y)
|
||||
|
@ -27,12 +27,15 @@ data class Dimension(
|
|||
val bottom: Double
|
||||
get() = top + height
|
||||
|
||||
val center: Point
|
||||
get() = Point(left + width / 2.0, top + height / 2.0)
|
||||
|
||||
val edges: Set<Point>
|
||||
get() = setOf(
|
||||
Point(left, top),
|
||||
Point(right, top),
|
||||
Point(left, bottom),
|
||||
Point(right, bottom)
|
||||
Point(left, top),
|
||||
Point(right, top),
|
||||
Point(left, bottom),
|
||||
Point(right, bottom)
|
||||
)
|
||||
|
||||
val normalized: Dimension
|
||||
|
|
|
@ -110,7 +110,7 @@ fun get(
|
|||
}
|
||||
}
|
||||
|
||||
fun post(
|
||||
fun postForm(
|
||||
url: String,
|
||||
data: Map<String, String> = emptyMap(),
|
||||
onError: (Int) -> Unit = {},
|
||||
|
@ -141,3 +141,43 @@ fun post(
|
|||
xhttp.send()
|
||||
}
|
||||
}
|
||||
|
||||
fun postJson(
|
||||
url: String,
|
||||
data: dynamic,
|
||||
onError: (Int) -> Unit = {},
|
||||
onSuccess: (String) -> Unit = {}
|
||||
) {
|
||||
val xhttp = XMLHttpRequest()
|
||||
|
||||
xhttp.onreadystatechange = {
|
||||
if (xhttp.readyState == 4.toShort()) {
|
||||
console.log(xhttp.status)
|
||||
if (xhttp.status == 200.toShort() || xhttp.status == 304.toShort()) {
|
||||
onSuccess(xhttp.responseText)
|
||||
} else {
|
||||
onError(xhttp.status.toInt())
|
||||
}
|
||||
}
|
||||
}
|
||||
xhttp.open("POST", url, true)
|
||||
|
||||
if (data.isNotEmpty()) {
|
||||
xhttp.setRequestHeader("Content-type", "application/json");
|
||||
xhttp.send(data)
|
||||
} else {
|
||||
xhttp.send()
|
||||
}
|
||||
}
|
||||
|
||||
fun jsonObject(block: (dynamic) -> Unit): dynamic {
|
||||
val json = js("{}")
|
||||
block(json)
|
||||
return json
|
||||
}
|
||||
|
||||
fun jsonArray(block: (dynamic) -> Unit): dynamic {
|
||||
val json = js("[]")
|
||||
block(json)
|
||||
return json
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue