Add drag and drop creation of schedule
This commit is contained in:
parent
b7d6476a70
commit
84f8e71239
38 changed files with 1277 additions and 548 deletions
|
@ -29,6 +29,7 @@ repositories {
|
||||||
}
|
}
|
||||||
def ktor_version = '1.1.5'
|
def ktor_version = '1.1.5'
|
||||||
def serialization_version = '0.11.0'
|
def serialization_version = '0.11.0'
|
||||||
|
def observable_version = '0.9.3'
|
||||||
|
|
||||||
kotlin {
|
kotlin {
|
||||||
jvm() {
|
jvm() {
|
||||||
|
@ -53,7 +54,7 @@ kotlin {
|
||||||
commonMain {
|
commonMain {
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation kotlin('stdlib-common')
|
implementation kotlin('stdlib-common')
|
||||||
implementation "de.westermann:KObserve-metadata:0.9.1"
|
implementation "de.westermann:KObserve-metadata:$observable_version"
|
||||||
|
|
||||||
implementation "org.jetbrains.kotlinx:kotlinx-serialization-runtime-common:$serialization_version"
|
implementation "org.jetbrains.kotlinx:kotlinx-serialization-runtime-common:$serialization_version"
|
||||||
}
|
}
|
||||||
|
@ -84,7 +85,7 @@ kotlin {
|
||||||
|
|
||||||
implementation 'org.mindrot:jbcrypt:0.4'
|
implementation 'org.mindrot:jbcrypt:0.4'
|
||||||
|
|
||||||
implementation "de.westermann:KObserve-jvm:0.9.1"
|
implementation "de.westermann:KObserve-jvm:$observable_version"
|
||||||
|
|
||||||
api 'io.github.microutils:kotlin-logging:1.6.23'
|
api 'io.github.microutils:kotlin-logging:1.6.23'
|
||||||
api 'ch.qos.logback:logback-classic:1.2.3'
|
api 'ch.qos.logback:logback-classic:1.2.3'
|
||||||
|
@ -103,7 +104,7 @@ kotlin {
|
||||||
|
|
||||||
implementation "org.jetbrains.kotlinx:kotlinx-serialization-runtime-js:$serialization_version"
|
implementation "org.jetbrains.kotlinx:kotlinx-serialization-runtime-js:$serialization_version"
|
||||||
|
|
||||||
implementation "de.westermann:KObserve-js:0.9.1"
|
implementation "de.westermann:KObserve-js:$observable_version"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
jsTest {
|
jsTest {
|
||||||
|
|
120
src/commonMain/kotlin/de/kif/common/Search.kt
Normal file
120
src/commonMain/kotlin/de/kif/common/Search.kt
Normal file
|
@ -0,0 +1,120 @@
|
||||||
|
package de.kif.common
|
||||||
|
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class SearchElement(
|
||||||
|
val fields: Map<String, String> = emptyMap(),
|
||||||
|
val flags: Map<String, Boolean> = emptyMap(),
|
||||||
|
val numbers: Map<String, Double> = emptyMap()
|
||||||
|
) {
|
||||||
|
|
||||||
|
fun stringify(): String {
|
||||||
|
return Message.json.stringify(serializer(), this)
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun parse(data: String): SearchElement {
|
||||||
|
return Message.json.parse(serializer(), data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
object Search {
|
||||||
|
|
||||||
|
private val regex = """
|
||||||
|
((\w+)\s?[:=]\s?)?
|
||||||
|
((\[.*]|\+\w+|-\w+|!\w+)|
|
||||||
|
((\w+)\s?([<=>]+)\s?(\d+))|
|
||||||
|
(\w+|"(.*)"))
|
||||||
|
""".trimIndent().replace("\n", "").toRegex()
|
||||||
|
|
||||||
|
fun match(search: String, element: SearchElement): Boolean {
|
||||||
|
val matches = regex.findAll(search)
|
||||||
|
|
||||||
|
val fields = mutableMapOf<String, String>()
|
||||||
|
val flags = mutableMapOf<String, Boolean>()
|
||||||
|
val numbers = mutableMapOf<String, ClosedRange<Double>>()
|
||||||
|
|
||||||
|
for (match in matches) {
|
||||||
|
val name = match.groups[2]?.value ?: ""
|
||||||
|
val field = match.groups[10]?.value ?: match.groups[9]?.value
|
||||||
|
val flag = match.groups[4]?.value
|
||||||
|
val numberName = match.groups[6]?.value
|
||||||
|
val numberRelation = match.groups[7]?.value
|
||||||
|
val numberDigits = match.groups[8]?.value?.toDoubleOrNull()
|
||||||
|
|
||||||
|
if (flag != null) {
|
||||||
|
val h = flag.replace("[\\[\\]+!\\-]".toRegex(), "")
|
||||||
|
val b = ("-" !in flag && "!" !in flag)
|
||||||
|
flags[h] = b
|
||||||
|
} else if (numberName != null && numberRelation != null && numberDigits != null) {
|
||||||
|
when (numberRelation) {
|
||||||
|
"<" -> numbers[numberName] = Double.NEGATIVE_INFINITY..(numberDigits - Double.MIN_VALUE)
|
||||||
|
"<=" -> numbers[numberName] = Double.NEGATIVE_INFINITY..numberDigits
|
||||||
|
"==" -> numbers[numberName] = numberDigits..numberDigits
|
||||||
|
">" -> numbers[numberName] = (numberDigits + Double.MIN_VALUE)..Double.POSITIVE_INFINITY
|
||||||
|
">=" -> numbers[numberName] = numberDigits..Double.POSITIVE_INFINITY
|
||||||
|
}
|
||||||
|
} else if (field != null) {
|
||||||
|
val old = fields[name]
|
||||||
|
fields[name] = if (old == null) field else "$old $field"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val fieldRadio = if (fields.isEmpty()) 1.0 else fields.count { (searchKey, searchValue) ->
|
||||||
|
for ((elementKey, elementValue) in element.fields) {
|
||||||
|
if (elementKey.contains(searchKey, true) && elementValue.contains(searchValue, true)) {
|
||||||
|
return@count true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for ((elementKey, elementValue) in element.flags) {
|
||||||
|
if (elementValue && elementKey.contains(searchValue, true)) {
|
||||||
|
return@count true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for ((elementKey, _) in element.numbers) {
|
||||||
|
if (elementKey.contains(searchValue, true)) {
|
||||||
|
return@count true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
false
|
||||||
|
} / fields.size.toDouble()
|
||||||
|
|
||||||
|
for ((searchKey, searchValue) in flags) {
|
||||||
|
for ((elementKey, elementValue) in element.flags) {
|
||||||
|
if (elementKey.contains(searchKey, true) && searchValue != elementValue)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for ((searchKey, searchValue) in numbers) {
|
||||||
|
for ((elementKey, elementValue) in element.numbers) {
|
||||||
|
if (elementKey.contains(searchKey, true) && elementValue !in searchValue)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//println("$fieldRadio (${fieldRadio >= 0.5}) for $element")
|
||||||
|
return fieldRadio >= 0.5
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
fun main() {
|
||||||
|
val element = SearchElement(
|
||||||
|
mapOf(
|
||||||
|
"name" to "lorem"
|
||||||
|
), mapOf(
|
||||||
|
"beamer" to true,
|
||||||
|
"room" to true,
|
||||||
|
"day" to false
|
||||||
|
), mapOf(
|
||||||
|
"places" to 500.0
|
||||||
|
)
|
||||||
|
)
|
||||||
|
println(Search.match("""search text data:"text to search" name = hans""", element))
|
||||||
|
|
||||||
|
println(Search.match("""lorem [beamer] places >= 100 +room -day""", element))
|
||||||
|
}
|
||||||
|
*/
|
|
@ -1,3 +1,7 @@
|
||||||
package de.kif.common.model
|
package de.kif.common.model
|
||||||
|
|
||||||
interface Model
|
import de.kif.common.SearchElement
|
||||||
|
|
||||||
|
interface Model {
|
||||||
|
fun createSearch(): SearchElement
|
||||||
|
}
|
|
@ -1,5 +1,6 @@
|
||||||
package de.kif.common.model
|
package de.kif.common.model
|
||||||
|
|
||||||
|
import de.kif.common.SearchElement
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
|
@ -8,4 +9,15 @@ data class Room(
|
||||||
val name: String,
|
val name: String,
|
||||||
val places: Int,
|
val places: Int,
|
||||||
val projector: Boolean
|
val projector: Boolean
|
||||||
) : Model
|
) : Model {
|
||||||
|
|
||||||
|
override fun createSearch() = SearchElement(
|
||||||
|
mapOf(
|
||||||
|
"name" to name
|
||||||
|
), mapOf(
|
||||||
|
"projector" to projector
|
||||||
|
), mapOf(
|
||||||
|
"places" to places.toDouble()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
package de.kif.common.model
|
package de.kif.common.model
|
||||||
|
|
||||||
|
import de.kif.common.SearchElement
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
|
@ -9,4 +10,16 @@ data class Schedule(
|
||||||
val room: Room,
|
val room: Room,
|
||||||
val day: Int,
|
val day: Int,
|
||||||
val time: Int
|
val time: Int
|
||||||
) : Model
|
) : Model {
|
||||||
|
|
||||||
|
override fun createSearch() = SearchElement(
|
||||||
|
mapOf(
|
||||||
|
"workgroup" to workGroup.name,
|
||||||
|
"room" to room.name,
|
||||||
|
"track" to (workGroup.track?.name ?: ""),
|
||||||
|
"language" to workGroup.language.localeName
|
||||||
|
), mapOf(), mapOf(
|
||||||
|
"day" to day.toDouble()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
|
@ -1,10 +1,18 @@
|
||||||
package de.kif.common.model
|
package de.kif.common.model
|
||||||
|
|
||||||
|
import de.kif.common.SearchElement
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data class Track(
|
data class Track(
|
||||||
val id: Long?,
|
val id: Long?,
|
||||||
var name: String,
|
val name: String,
|
||||||
var color: Color
|
val color: Color
|
||||||
) : Model
|
) : Model {
|
||||||
|
|
||||||
|
override fun createSearch() = SearchElement(
|
||||||
|
mapOf(
|
||||||
|
"name" to name
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
package de.kif.common.model
|
package de.kif.common.model
|
||||||
|
|
||||||
|
import de.kif.common.SearchElement
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
|
@ -13,4 +14,10 @@ data class User(
|
||||||
fun checkPermission(permission: Permission): Boolean {
|
fun checkPermission(permission: Permission): Boolean {
|
||||||
return permission in permissions || Permission.ADMIN in permissions
|
return permission in permissions || Permission.ADMIN in permissions
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun createSearch() = SearchElement(
|
||||||
|
mapOf(
|
||||||
|
"username" to username
|
||||||
|
)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
package de.kif.common.model
|
package de.kif.common.model
|
||||||
|
|
||||||
|
import de.kif.common.SearchElement
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
|
@ -12,4 +13,19 @@ data class WorkGroup(
|
||||||
val resolution: Boolean,
|
val resolution: Boolean,
|
||||||
val length: Int,
|
val length: Int,
|
||||||
val language: Language
|
val language: Language
|
||||||
) : Model
|
) : Model {
|
||||||
|
|
||||||
|
override fun createSearch() = SearchElement(
|
||||||
|
mapOf(
|
||||||
|
"name" to name,
|
||||||
|
"track" to (track?.name ?: ""),
|
||||||
|
"language" to language.localeName
|
||||||
|
), mapOf(
|
||||||
|
"projector" to projector,
|
||||||
|
"resolution" to resolution
|
||||||
|
), mapOf(
|
||||||
|
"interested" to interested.toDouble(),
|
||||||
|
"length" to length.toDouble()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package de.kif.frontend
|
package de.kif.frontend
|
||||||
|
|
||||||
import de.kif.frontend.views.initCalendar
|
import de.kif.frontend.views.calendar.initCalendar
|
||||||
|
import de.kif.frontend.views.initTableLayout
|
||||||
import de.westermann.kwebview.components.init
|
import de.westermann.kwebview.components.init
|
||||||
import kotlin.browser.document
|
import kotlin.browser.document
|
||||||
|
|
||||||
|
@ -10,4 +11,7 @@ fun main() = init {
|
||||||
if (document.getElementsByClassName("calendar").length > 0) {
|
if (document.getElementsByClassName("calendar").length > 0) {
|
||||||
initCalendar()
|
initCalendar()
|
||||||
}
|
}
|
||||||
|
if (document.getElementsByClassName("table-layout").length > 0) {
|
||||||
|
initTableLayout()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,7 +23,7 @@ object RoomRepository : Repository<Room> {
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun create(model: Room): Long {
|
override suspend fun create(model: Room): Long {
|
||||||
return repositoryPost("/api/rooms", Message.json.stringify(Room.serializer(), model))?.toLong()
|
return repositoryPost("/api/rooms", Message.json.stringify(Room.serializer(), model))
|
||||||
?: throw IllegalStateException("Cannot create model!")
|
?: throw IllegalStateException("Cannot create model!")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -23,7 +23,7 @@ object ScheduleRepository : Repository<Schedule> {
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun create(model: Schedule): Long {
|
override suspend fun create(model: Schedule): Long {
|
||||||
return repositoryPost("/api/schedules", Message.json.stringify(Schedule.serializer(), model))?.toLong()
|
return repositoryPost("/api/schedules", Message.json.stringify(Schedule.serializer(), model))
|
||||||
?: throw IllegalStateException("Cannot create model!")
|
?: throw IllegalStateException("Cannot create model!")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -23,7 +23,7 @@ object TrackRepository : Repository<Track> {
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun create(model: Track): Long {
|
override suspend fun create(model: Track): Long {
|
||||||
return repositoryPost("/api/tracks", Message.json.stringify(Track.serializer(), model))?.toLong()
|
return repositoryPost("/api/tracks", Message.json.stringify(Track.serializer(), model))
|
||||||
?: throw IllegalStateException("Cannot create model!")
|
?: throw IllegalStateException("Cannot create model!")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -23,7 +23,7 @@ object UserRepository : Repository<User> {
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun create(model: User): Long {
|
override suspend fun create(model: User): Long {
|
||||||
return repositoryPost("/api/users", Message.json.stringify(User.serializer(), model))?.toLong()
|
return repositoryPost("/api/users", Message.json.stringify(User.serializer(), model))
|
||||||
?: throw IllegalStateException("Cannot create model!")
|
?: throw IllegalStateException("Cannot create model!")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -23,7 +23,7 @@ object WorkGroupRepository : Repository<WorkGroup> {
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun create(model: WorkGroup): Long {
|
override suspend fun create(model: WorkGroup): Long {
|
||||||
return repositoryPost("/api/workgroups", Message.json.stringify(WorkGroup.serializer(), model))?.toLong()
|
return repositoryPost("/api/workgroups", Message.json.stringify(WorkGroup.serializer(), model))
|
||||||
?: throw IllegalStateException("Cannot create model!")
|
?: throw IllegalStateException("Cannot create model!")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,359 +0,0 @@
|
||||||
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()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
32
src/jsMain/kotlin/de/kif/frontend/views/TableLayout.kt
Normal file
32
src/jsMain/kotlin/de/kif/frontend/views/TableLayout.kt
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
package de.kif.frontend.views
|
||||||
|
|
||||||
|
import de.kif.common.Search
|
||||||
|
import de.kif.common.SearchElement
|
||||||
|
import de.kif.frontend.iterator
|
||||||
|
import de.westermann.kwebview.components.InputView
|
||||||
|
import org.w3c.dom.HTMLFormElement
|
||||||
|
import org.w3c.dom.HTMLInputElement
|
||||||
|
import org.w3c.dom.HTMLTableElement
|
||||||
|
import org.w3c.dom.get
|
||||||
|
import kotlin.browser.document
|
||||||
|
|
||||||
|
fun initTableLayout() {
|
||||||
|
val form = document.getElementsByClassName("table-layout-search")[0] as HTMLFormElement
|
||||||
|
form.onsubmit = { false }
|
||||||
|
|
||||||
|
val table = document.getElementsByClassName("table-layout-table")[0] as HTMLTableElement
|
||||||
|
|
||||||
|
val list = table.getElementsByTagName("tr").iterator().asSequence().filter {
|
||||||
|
it.dataset.get("search") != null
|
||||||
|
}.associateWith {
|
||||||
|
SearchElement.parse(it.dataset.get("search")!!)
|
||||||
|
}
|
||||||
|
|
||||||
|
val input = form.getElementsByTagName("input")[0] as HTMLInputElement
|
||||||
|
val search = InputView.wrap(input)
|
||||||
|
search.valueProperty.onChange {
|
||||||
|
for ((row, s) in list) {
|
||||||
|
row.style.display = if (Search.match(search.value, s)) "table-row" else "none"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
65
src/jsMain/kotlin/de/kif/frontend/views/calendar/Calendar.kt
Normal file
65
src/jsMain/kotlin/de/kif/frontend/views/calendar/Calendar.kt
Normal file
|
@ -0,0 +1,65 @@
|
||||||
|
package de.kif.frontend.views.calendar
|
||||||
|
|
||||||
|
import de.kif.frontend.iterator
|
||||||
|
import de.kif.frontend.launch
|
||||||
|
import de.kif.frontend.repository.ScheduleRepository
|
||||||
|
import de.westermann.kwebview.View
|
||||||
|
import org.w3c.dom.HTMLElement
|
||||||
|
import org.w3c.dom.get
|
||||||
|
import kotlin.browser.document
|
||||||
|
|
||||||
|
|
||||||
|
class Calendar(calendar: HTMLElement): View(calendar) {
|
||||||
|
var calendarEntries: List<CalendarEntry> = emptyList()
|
||||||
|
var calendarCells: List<CalendarCell> = emptyList()
|
||||||
|
|
||||||
|
val day: Int
|
||||||
|
|
||||||
|
init {
|
||||||
|
val editable = calendar.dataset["editable"]?.toBoolean() ?: false
|
||||||
|
day = calendar.dataset["day"]?.toIntOrNull() ?: -1
|
||||||
|
|
||||||
|
calendarEntries = document.getElementsByClassName("calendar-entry")
|
||||||
|
.iterator().asSequence().map{ CalendarEntry(this, it) }.onEach { it.editable = editable }.toList()
|
||||||
|
|
||||||
|
calendarCells = document.getElementsByClassName("calendar-cell")
|
||||||
|
.iterator().asSequence().filter { it.dataset["time"] != null }.map(::CalendarCell).toList()
|
||||||
|
|
||||||
|
if (editable) {
|
||||||
|
CalendarEdit(this, calendar.querySelector(".calendar-edit") as HTMLElement)
|
||||||
|
}
|
||||||
|
|
||||||
|
ScheduleRepository.onCreate {
|
||||||
|
launch {
|
||||||
|
val schedule = ScheduleRepository.get(it) ?: throw NoSuchElementException()
|
||||||
|
calendarEntries += CalendarEntry.create(this, schedule).also { it.editable = editable }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ScheduleRepository.onUpdate {
|
||||||
|
launch {
|
||||||
|
val schedule = ScheduleRepository.get(it) ?: throw NoSuchElementException()
|
||||||
|
var found = false
|
||||||
|
for (entry in calendarEntries) {
|
||||||
|
if (entry.scheduleId == it) {
|
||||||
|
entry.load(schedule)
|
||||||
|
found = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!found) {
|
||||||
|
calendarEntries += CalendarEntry.create(this, schedule).also { it.editable = editable }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ScheduleRepository.onDelete {
|
||||||
|
for (entry in calendarEntries) {
|
||||||
|
if (entry.scheduleId == it) {
|
||||||
|
entry.html.remove()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun initCalendar() {
|
||||||
|
Calendar(document.getElementsByClassName("calendar")[0] as? HTMLElement ?: return)
|
||||||
|
}
|
|
@ -0,0 +1,28 @@
|
||||||
|
package de.kif.frontend.views.calendar
|
||||||
|
|
||||||
|
import de.kif.common.model.Room
|
||||||
|
import de.kif.frontend.repository.RoomRepository
|
||||||
|
import de.westermann.kwebview.ViewCollection
|
||||||
|
import org.w3c.dom.HTMLElement
|
||||||
|
import org.w3c.dom.get
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
init {
|
||||||
|
(view.getElementsByClassName("calendar-link")[0] as? HTMLElement)?.remove()
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,76 @@
|
||||||
|
package de.kif.frontend.views.calendar
|
||||||
|
|
||||||
|
import de.kif.common.Search
|
||||||
|
import de.kif.frontend.launch
|
||||||
|
import de.kif.frontend.repository.WorkGroupRepository
|
||||||
|
import de.westermann.kobserve.list.filterObservable
|
||||||
|
import de.westermann.kobserve.list.observableListOf
|
||||||
|
import de.westermann.kobserve.list.sortObservable
|
||||||
|
import de.westermann.kwebview.View
|
||||||
|
import de.westermann.kwebview.components.Button
|
||||||
|
import de.westermann.kwebview.components.InputView
|
||||||
|
import de.westermann.kwebview.components.ListView
|
||||||
|
import de.westermann.kwebview.extra.listFactory
|
||||||
|
import org.w3c.dom.HTMLButtonElement
|
||||||
|
import org.w3c.dom.HTMLElement
|
||||||
|
import org.w3c.dom.HTMLInputElement
|
||||||
|
|
||||||
|
class CalendarEdit(
|
||||||
|
private val calendar: Calendar, view: HTMLElement
|
||||||
|
) : View(view) {
|
||||||
|
|
||||||
|
private val toggleEditButton =
|
||||||
|
Button.wrap(view.querySelector(".calendar-edit-top button") as HTMLButtonElement)
|
||||||
|
|
||||||
|
val search =
|
||||||
|
InputView.wrap(view.querySelector(".calendar-edit-search input") as HTMLInputElement)
|
||||||
|
|
||||||
|
val listView = ListView.wrap<CalendarWorkGroup>(
|
||||||
|
view.querySelector(".calendar-edit-list") as HTMLElement
|
||||||
|
)
|
||||||
|
|
||||||
|
private var loaded = false
|
||||||
|
|
||||||
|
val workGroupList = observableListOf<CalendarWorkGroup>()
|
||||||
|
private val sortedList = workGroupList.sortObservable(compareBy {
|
||||||
|
it.workGroup.name
|
||||||
|
}).filterObservable(search.valueProperty) { entry, search ->
|
||||||
|
val s = entry.workGroup.createSearch()
|
||||||
|
Search.match(search, s)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun load() {
|
||||||
|
if (loaded) return
|
||||||
|
loaded = true
|
||||||
|
|
||||||
|
launch {
|
||||||
|
for (workGroup in WorkGroupRepository.all()) {
|
||||||
|
workGroupList += CalendarWorkGroup(calendar, this, workGroup)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
init {
|
||||||
|
toggleEditButton.onClick {
|
||||||
|
calendar.classList.toggle("edit")
|
||||||
|
|
||||||
|
if (!loaded) {
|
||||||
|
load()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
WorkGroupRepository.onCreate {
|
||||||
|
if (loaded) {
|
||||||
|
launch {
|
||||||
|
workGroupList += CalendarWorkGroup(
|
||||||
|
calendar,
|
||||||
|
this,
|
||||||
|
WorkGroupRepository.get(it)!!
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
listView.listFactory(sortedList)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,222 @@
|
||||||
|
package de.kif.frontend.views.calendar
|
||||||
|
|
||||||
|
import de.kif.common.CALENDAR_GRID_WIDTH
|
||||||
|
import de.kif.common.model.Schedule
|
||||||
|
import de.kif.common.model.WorkGroup
|
||||||
|
import de.kif.frontend.iterator
|
||||||
|
import de.kif.frontend.launch
|
||||||
|
import de.kif.frontend.repository.RepositoryDelegate
|
||||||
|
import de.kif.frontend.repository.ScheduleRepository
|
||||||
|
import de.westermann.kwebview.Point
|
||||||
|
import de.westermann.kwebview.View
|
||||||
|
import de.westermann.kwebview.components.Body
|
||||||
|
import de.westermann.kwebview.createHtmlView
|
||||||
|
import de.westermann.kwebview.toPoint
|
||||||
|
import org.w3c.dom.HTMLElement
|
||||||
|
import org.w3c.dom.events.MouseEvent
|
||||||
|
import kotlin.dom.appendText
|
||||||
|
import kotlin.dom.isText
|
||||||
|
|
||||||
|
class CalendarEntry(private val calendar: Calendar, view: HTMLElement) : View(view) {
|
||||||
|
|
||||||
|
private lateinit var mouseDelta: Point
|
||||||
|
private var newCell: CalendarCell? = null
|
||||||
|
|
||||||
|
private var language by dataset.property("language")
|
||||||
|
|
||||||
|
var scheduleId = dataset["id"]?.toLongOrNull() ?: -1
|
||||||
|
|
||||||
|
val schedule =
|
||||||
|
RepositoryDelegate(ScheduleRepository, scheduleId)
|
||||||
|
|
||||||
|
private lateinit var workGroup: WorkGroup
|
||||||
|
|
||||||
|
var pending by classList.property("pending")
|
||||||
|
|
||||||
|
var editable: Boolean = false
|
||||||
|
|
||||||
|
private fun onMove(event: MouseEvent) {
|
||||||
|
val position = event.toPoint() - mouseDelta
|
||||||
|
|
||||||
|
val cell = calendar.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
|
||||||
|
|
||||||
|
if (scheduleId < 0) {
|
||||||
|
ScheduleRepository.create(
|
||||||
|
Schedule(
|
||||||
|
null,
|
||||||
|
workGroup,
|
||||||
|
newRoom,
|
||||||
|
calendar.day,
|
||||||
|
newTime
|
||||||
|
)
|
||||||
|
)
|
||||||
|
html.remove()
|
||||||
|
} else {
|
||||||
|
ScheduleRepository.update(schedule.get().copy(room = newRoom, time = newTime))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
newCell = null
|
||||||
|
|
||||||
|
for (it in listeners) {
|
||||||
|
it.detach()
|
||||||
|
}
|
||||||
|
listeners = emptyList()
|
||||||
|
|
||||||
|
event.preventDefault()
|
||||||
|
event.stopPropagation()
|
||||||
|
}
|
||||||
|
|
||||||
|
private var listeners: List<de.westermann.kobserve.event.EventListener<*>> = emptyList()
|
||||||
|
|
||||||
|
fun startDrag() {
|
||||||
|
listeners = listOf(
|
||||||
|
Body.onMouseMove.reference(this::onMove),
|
||||||
|
Body.onMouseUp.reference(this::onFinishMove),
|
||||||
|
Body.onMouseLeave.reference(this::onFinishMove)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
init {
|
||||||
|
onMouseDown { event ->
|
||||||
|
if (!editable || 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 = calendar.calendarCells.find {
|
||||||
|
it.day == s.day && it.time == time && it.roomId == s.room.id
|
||||||
|
}?.dimension?.center ?: dimension.center
|
||||||
|
|
||||||
|
mouseDelta = event.toPoint() - p
|
||||||
|
|
||||||
|
startDrag()
|
||||||
|
}
|
||||||
|
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
|
||||||
|
|
||||||
|
if (schedule.id != null) scheduleId = schedule.id
|
||||||
|
this.schedule.set(schedule)
|
||||||
|
|
||||||
|
style {
|
||||||
|
val pos = (schedule.time % CALENDAR_GRID_WIDTH) / CALENDAR_GRID_WIDTH.toDouble()
|
||||||
|
|
||||||
|
val ps = "${pos * 100}%"
|
||||||
|
|
||||||
|
left = ps
|
||||||
|
top = "calc($ps + 0.1rem)"
|
||||||
|
}
|
||||||
|
|
||||||
|
load(schedule.workGroup)
|
||||||
|
|
||||||
|
val time = schedule.time / CALENDAR_GRID_WIDTH * CALENDAR_GRID_WIDTH
|
||||||
|
val cell = calendar.calendarCells.find {
|
||||||
|
it.day == schedule.day && it.time == time && it.roomId == schedule.room.id
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cell != null && cell.html != html.parentElement) {
|
||||||
|
cell += this
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun load(workGroup: WorkGroup) {
|
||||||
|
pending = false
|
||||||
|
|
||||||
|
language = workGroup.language.code
|
||||||
|
this.workGroup = workGroup
|
||||||
|
|
||||||
|
style {
|
||||||
|
val size = workGroup.length / CALENDAR_GRID_WIDTH.toDouble()
|
||||||
|
|
||||||
|
val sz = "${size * 100}%"
|
||||||
|
|
||||||
|
width = sz
|
||||||
|
height = "calc($sz - 0.2rem)"
|
||||||
|
|
||||||
|
if (workGroup.track?.color != null) {
|
||||||
|
backgroundColor = workGroup.track.color.toString()
|
||||||
|
color = workGroup.track.color.calcTextColor().toString()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (element in html.childNodes) {
|
||||||
|
if (element.isText) {
|
||||||
|
html.removeChild(element)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
html.appendText(workGroup.name)
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun create(calendar: Calendar, schedule: Schedule): CalendarEntry {
|
||||||
|
val entry = CalendarEntry(calendar, createHtmlView())
|
||||||
|
|
||||||
|
entry.load(schedule)
|
||||||
|
|
||||||
|
return entry
|
||||||
|
}
|
||||||
|
|
||||||
|
fun create(calendar: Calendar, workGroup: WorkGroup): CalendarEntry {
|
||||||
|
val entry = CalendarEntry(calendar, createHtmlView())
|
||||||
|
|
||||||
|
entry.load(workGroup)
|
||||||
|
entry.mouseDelta = Point.ZERO
|
||||||
|
entry.startDrag()
|
||||||
|
|
||||||
|
return entry
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,129 @@
|
||||||
|
package de.kif.frontend.views.calendar
|
||||||
|
|
||||||
|
import de.kif.common.CALENDAR_GRID_WIDTH
|
||||||
|
import de.kif.frontend.iterator
|
||||||
|
import de.kif.frontend.launch
|
||||||
|
import de.kif.frontend.repository.ScheduleRepository
|
||||||
|
import de.westermann.kwebview.View
|
||||||
|
import de.westermann.kwebview.createHtmlView
|
||||||
|
import org.w3c.dom.HTMLAnchorElement
|
||||||
|
import org.w3c.dom.HTMLElement
|
||||||
|
import org.w3c.dom.events.EventListener
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,56 @@
|
||||||
|
package de.kif.frontend.views.calendar
|
||||||
|
|
||||||
|
import de.kif.common.model.WorkGroup
|
||||||
|
import de.kif.frontend.launch
|
||||||
|
import de.kif.frontend.repository.WorkGroupRepository
|
||||||
|
import de.westermann.kwebview.View
|
||||||
|
|
||||||
|
class CalendarWorkGroup(
|
||||||
|
private val calendar: Calendar,
|
||||||
|
private val calendarEdit: CalendarEdit,
|
||||||
|
workGroup: WorkGroup
|
||||||
|
) : View() {
|
||||||
|
private var language by dataset.property("language")
|
||||||
|
|
||||||
|
lateinit var workGroup: WorkGroup
|
||||||
|
private set
|
||||||
|
|
||||||
|
private fun load(workGroup: WorkGroup) {
|
||||||
|
this.workGroup = workGroup
|
||||||
|
|
||||||
|
html.textContent = workGroup.name
|
||||||
|
|
||||||
|
language = workGroup.language.code
|
||||||
|
|
||||||
|
style {
|
||||||
|
if (workGroup.track?.color != null) {
|
||||||
|
backgroundColor = workGroup.track.color.toString()
|
||||||
|
color = workGroup.track.color.calcTextColor().toString()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
init {
|
||||||
|
load(workGroup)
|
||||||
|
|
||||||
|
val references: MutableList<de.westermann.kobserve.event.EventListener<*>> = mutableListOf()
|
||||||
|
|
||||||
|
references += WorkGroupRepository.onUpdate.reference {
|
||||||
|
if (it == workGroup.id) {
|
||||||
|
launch {
|
||||||
|
load(WorkGroupRepository.get(it)!!)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
references += WorkGroupRepository.onDelete.reference {
|
||||||
|
if (it == workGroup.id) {
|
||||||
|
calendarEdit.workGroupList -= this
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onMouseDown {
|
||||||
|
CalendarEntry.create(calendar, workGroup)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -6,7 +6,7 @@ import kotlin.dom.clear
|
||||||
/**
|
/**
|
||||||
* @author lars
|
* @author lars
|
||||||
*/
|
*/
|
||||||
abstract class ViewCollection<V : View>(view: HTMLElement = createHtmlView()) : View(view), Iterable<V> {
|
abstract class ViewCollection<V : View>(view: HTMLElement = createHtmlView()) : View(view), Collection<V> {
|
||||||
|
|
||||||
private val children: MutableList<V> = mutableListOf()
|
private val children: MutableList<V> = mutableListOf()
|
||||||
|
|
||||||
|
@ -29,6 +29,36 @@ abstract class ViewCollection<V : View>(view: HTMLElement = createHtmlView()) :
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun replace(oldView: V, newView: V) {
|
||||||
|
if (children.contains(oldView)) {
|
||||||
|
children.add(children.indexOf(oldView), newView)
|
||||||
|
html.insertBefore(newView.html, oldView.html)
|
||||||
|
children -= oldView
|
||||||
|
html.removeChild(oldView.html)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun add(view: V) = append(view)
|
||||||
|
|
||||||
|
fun add(index: Int, view: V) {
|
||||||
|
if (index >= size) {
|
||||||
|
append(view)
|
||||||
|
} else {
|
||||||
|
html.insertBefore(view.html, children[index].html)
|
||||||
|
children.add(index, view)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
operator fun get(index: Int): V {
|
||||||
|
return children[index]
|
||||||
|
}
|
||||||
|
|
||||||
|
fun removeAt(index: Int) {
|
||||||
|
if (index in 0 until size) {
|
||||||
|
remove(children[index])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun toForeground(view: V) {
|
fun toForeground(view: V) {
|
||||||
if (view in children && children.indexOf(view) < children.size - 1) {
|
if (view in children && children.indexOf(view) < children.size - 1) {
|
||||||
remove(view)
|
remove(view)
|
||||||
|
@ -48,8 +78,7 @@ abstract class ViewCollection<V : View>(view: HTMLElement = createHtmlView()) :
|
||||||
|
|
||||||
operator fun minusAssign(view: V) = remove(view)
|
operator fun minusAssign(view: V) = remove(view)
|
||||||
|
|
||||||
val isEmpty: Boolean
|
override fun isEmpty(): Boolean = children.isEmpty()
|
||||||
get() = children.isEmpty()
|
|
||||||
|
|
||||||
fun clear() {
|
fun clear() {
|
||||||
children.clear()
|
children.clear()
|
||||||
|
@ -58,16 +87,14 @@ abstract class ViewCollection<V : View>(view: HTMLElement = createHtmlView()) :
|
||||||
|
|
||||||
override fun iterator(): Iterator<V> = children.iterator()
|
override fun iterator(): Iterator<V> = children.iterator()
|
||||||
|
|
||||||
val size: Int
|
override val size: Int
|
||||||
get() = children.size
|
get() = children.size
|
||||||
|
|
||||||
operator fun contains(view: V) = children.contains(view)
|
override fun contains(element: V) = children.contains(element)
|
||||||
|
|
||||||
|
override fun containsAll(elements: Collection<V>): Boolean = children.containsAll(elements)
|
||||||
|
|
||||||
operator fun V.unaryPlus() {
|
operator fun V.unaryPlus() {
|
||||||
append(this)
|
append(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
|
||||||
fun <V : View> wrap(htmlElement: HTMLElement) = object : ViewCollection<V>(htmlElement) {}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,7 +5,9 @@ import org.w3c.dom.HTMLInputElement
|
||||||
import kotlin.math.abs
|
import kotlin.math.abs
|
||||||
import kotlin.random.Random
|
import kotlin.random.Random
|
||||||
|
|
||||||
abstract class ViewForLabel : View(createHtmlView<HTMLInputElement>()) {
|
abstract class ViewForLabel(
|
||||||
|
view: HTMLInputElement = createHtmlView()
|
||||||
|
) : View(view) {
|
||||||
override val html = super.html as HTMLInputElement
|
override val html = super.html as HTMLInputElement
|
||||||
|
|
||||||
private var label: Label? = null
|
private var label: Label? = null
|
||||||
|
|
|
@ -14,7 +14,7 @@ import org.w3c.dom.HTMLButtonElement
|
||||||
*
|
*
|
||||||
* @author lars
|
* @author lars
|
||||||
*/
|
*/
|
||||||
class Button() : ViewCollection<View>(createHtmlView<HTMLButtonElement>()) {
|
class Button(view: HTMLButtonElement = createHtmlView()) : ViewCollection<View>(view) {
|
||||||
|
|
||||||
constructor(text: String) : this() {
|
constructor(text: String) : this() {
|
||||||
this.text = text
|
this.text = text
|
||||||
|
@ -38,6 +38,10 @@ class Button() : ViewCollection<View>(createHtmlView<HTMLButtonElement>()) {
|
||||||
}
|
}
|
||||||
|
|
||||||
val textProperty: Property<String> = property(this::text)
|
val textProperty: Property<String> = property(this::text)
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun wrap(view: HTMLButtonElement) = Button(view)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@KWebViewDsl
|
@KWebViewDsl
|
||||||
|
|
|
@ -3,8 +3,8 @@ package de.westermann.kwebview.components
|
||||||
import de.westermann.kobserve.Property
|
import de.westermann.kobserve.Property
|
||||||
import de.westermann.kobserve.ReadOnlyProperty
|
import de.westermann.kobserve.ReadOnlyProperty
|
||||||
import de.westermann.kobserve.ValidationProperty
|
import de.westermann.kobserve.ValidationProperty
|
||||||
import de.westermann.kobserve.property.property
|
|
||||||
import de.westermann.kobserve.not
|
import de.westermann.kobserve.not
|
||||||
|
import de.westermann.kobserve.property.property
|
||||||
import de.westermann.kwebview.*
|
import de.westermann.kwebview.*
|
||||||
import org.w3c.dom.HTMLInputElement
|
import org.w3c.dom.HTMLInputElement
|
||||||
import org.w3c.dom.events.Event
|
import org.w3c.dom.events.Event
|
||||||
|
@ -12,9 +12,10 @@ import org.w3c.dom.events.EventListener
|
||||||
import org.w3c.dom.events.KeyboardEvent
|
import org.w3c.dom.events.KeyboardEvent
|
||||||
|
|
||||||
class InputView(
|
class InputView(
|
||||||
type: InputType,
|
type: InputType,
|
||||||
initValue: String = ""
|
initValue: String = "",
|
||||||
) : ViewForLabel() {
|
view: HTMLInputElement = createHtmlView()
|
||||||
|
) : ViewForLabel(view) {
|
||||||
|
|
||||||
fun bind(property: ReadOnlyProperty<String>) {
|
fun bind(property: ReadOnlyProperty<String>) {
|
||||||
valueProperty.bind(property)
|
valueProperty.bind(property)
|
||||||
|
@ -108,12 +109,17 @@ class InputView(
|
||||||
html.addEventListener("keyup", changeListener)
|
html.addEventListener("keyup", changeListener)
|
||||||
html.addEventListener("keypress", changeListener)
|
html.addEventListener("keypress", changeListener)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun wrap(view: HTMLInputElement) = InputView(InputType.SEARCH, view.value, view)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
enum class InputType(val html: String) {
|
enum class InputType(val html: String) {
|
||||||
TEXT("text"),
|
TEXT("text"),
|
||||||
NUMBER("number"),
|
NUMBER("number"),
|
||||||
PASSWORD("password");
|
PASSWORD("password"),
|
||||||
|
SEARCH("search");
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
fun find(html: String): InputType? = values().find { it.html == html }
|
fun find(html: String): InputType? = values().find { it.html == html }
|
||||||
|
@ -122,33 +128,49 @@ enum class InputType(val html: String) {
|
||||||
|
|
||||||
@KWebViewDsl
|
@KWebViewDsl
|
||||||
fun ViewCollection<in InputView>.inputView(text: String = "", init: InputView.() -> Unit = {}) =
|
fun ViewCollection<in InputView>.inputView(text: String = "", init: InputView.() -> Unit = {}) =
|
||||||
InputView(InputType.TEXT, text).also(this::append).also(init)
|
InputView(InputType.TEXT, text).also(this::append).also(init)
|
||||||
|
|
||||||
@KWebViewDsl
|
@KWebViewDsl
|
||||||
fun ViewCollection<in InputView>.inputView(text: ReadOnlyProperty<String>, init: InputView.() -> Unit = {}) =
|
fun ViewCollection<in InputView>.inputView(text: ReadOnlyProperty<String>, init: InputView.() -> Unit = {}) =
|
||||||
InputView(InputType.TEXT, text.value).also(this::append).also { it.bind(text) }.also(init)
|
InputView(InputType.TEXT, text.value).also(this::append).also { it.bind(text) }.also(init)
|
||||||
|
|
||||||
@KWebViewDsl
|
@KWebViewDsl
|
||||||
fun ViewCollection<in InputView>.inputView(text: Property<String>, init: InputView.() -> Unit = {}) =
|
fun ViewCollection<in InputView>.inputView(text: Property<String>, init: InputView.() -> Unit = {}) =
|
||||||
InputView(InputType.TEXT, text.value).also(this::append).also { it.bind(text) }.also(init)
|
InputView(InputType.TEXT, text.value).also(this::append).also { it.bind(text) }.also(init)
|
||||||
|
|
||||||
@KWebViewDsl
|
@KWebViewDsl
|
||||||
fun ViewCollection<in InputView>.inputView(text: ValidationProperty<String>, init: InputView.() -> Unit = {}) =
|
fun ViewCollection<in InputView>.inputView(text: ValidationProperty<String>, init: InputView.() -> Unit = {}) =
|
||||||
InputView(InputType.TEXT, text.value).also(this::append).also { it.bind(text) }.also(init)
|
InputView(InputType.TEXT, text.value).also(this::append).also { it.bind(text) }.also(init)
|
||||||
|
|
||||||
|
|
||||||
@KWebViewDsl
|
@KWebViewDsl
|
||||||
fun ViewCollection<in InputView>.inputView(type: InputType = InputType.TEXT, text: String = "", init: InputView.() -> Unit = {}) =
|
fun ViewCollection<in InputView>.inputView(
|
||||||
InputView(type, text).also(this::append).also(init)
|
type: InputType = InputType.TEXT,
|
||||||
|
text: String = "",
|
||||||
|
init: InputView.() -> Unit = {}
|
||||||
|
) =
|
||||||
|
InputView(type, text).also(this::append).also(init)
|
||||||
|
|
||||||
@KWebViewDsl
|
@KWebViewDsl
|
||||||
fun ViewCollection<in InputView>.inputView(type: InputType = InputType.TEXT, text: ReadOnlyProperty<String>, init: InputView.() -> Unit = {}) =
|
fun ViewCollection<in InputView>.inputView(
|
||||||
InputView(type, text.value).also(this::append).also { it.bind(text) }.also(init)
|
type: InputType = InputType.TEXT,
|
||||||
|
text: ReadOnlyProperty<String>,
|
||||||
|
init: InputView.() -> Unit = {}
|
||||||
|
) =
|
||||||
|
InputView(type, text.value).also(this::append).also { it.bind(text) }.also(init)
|
||||||
|
|
||||||
@KWebViewDsl
|
@KWebViewDsl
|
||||||
fun ViewCollection<in InputView>.inputView(type: InputType = InputType.TEXT, text: Property<String>, init: InputView.() -> Unit = {}) =
|
fun ViewCollection<in InputView>.inputView(
|
||||||
InputView(type, text.value).also(this::append).also { it.bind(text) }.also(init)
|
type: InputType = InputType.TEXT,
|
||||||
|
text: Property<String>,
|
||||||
|
init: InputView.() -> Unit = {}
|
||||||
|
) =
|
||||||
|
InputView(type, text.value).also(this::append).also { it.bind(text) }.also(init)
|
||||||
|
|
||||||
@KWebViewDsl
|
@KWebViewDsl
|
||||||
fun ViewCollection<in InputView>.inputView(type: InputType = InputType.TEXT, text: ValidationProperty<String>, init: InputView.() -> Unit = {}) =
|
fun ViewCollection<in InputView>.inputView(
|
||||||
InputView(type, text.value).also(this::append).also { it.bind(text) }.also(init)
|
type: InputType = InputType.TEXT,
|
||||||
|
text: ValidationProperty<String>,
|
||||||
|
init: InputView.() -> Unit = {}
|
||||||
|
) =
|
||||||
|
InputView(type, text.value).also(this::append).also { it.bind(text) }.also(init)
|
||||||
|
|
|
@ -0,0 +1,30 @@
|
||||||
|
package de.westermann.kwebview.components
|
||||||
|
|
||||||
|
import de.westermann.kwebview.KWebViewDsl
|
||||||
|
import de.westermann.kwebview.View
|
||||||
|
import de.westermann.kwebview.ViewCollection
|
||||||
|
import de.westermann.kwebview.createHtmlView
|
||||||
|
import org.w3c.dom.HTMLDivElement
|
||||||
|
import org.w3c.dom.HTMLElement
|
||||||
|
|
||||||
|
class ListView<T : View>(view: HTMLElement = createHtmlView()) : ViewCollection<T>(view) {
|
||||||
|
override val html = super.html as HTMLDivElement
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun <T : View> wrap(view: HTMLElement) = ListView<T>(view)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@KWebViewDsl
|
||||||
|
fun <T : View> ViewCollection<in ListView<T>>.listView(
|
||||||
|
vararg classes: String,
|
||||||
|
init: ListView<T>.() -> Unit = {}
|
||||||
|
): ListView<T> {
|
||||||
|
val view = ListView<T>()
|
||||||
|
for (c in classes) {
|
||||||
|
view.classList += c
|
||||||
|
}
|
||||||
|
append(view)
|
||||||
|
init(view)
|
||||||
|
return view
|
||||||
|
}
|
|
@ -0,0 +1,62 @@
|
||||||
|
package de.westermann.kwebview.extra
|
||||||
|
|
||||||
|
import de.westermann.kobserve.list.ObservableReadOnlyList
|
||||||
|
import de.westermann.kwebview.View
|
||||||
|
import de.westermann.kwebview.ViewCollection
|
||||||
|
import de.westermann.kwebview.async
|
||||||
|
|
||||||
|
fun <T, V : View> ViewCollection<in V>.listFactory(
|
||||||
|
list: ObservableReadOnlyList<T>,
|
||||||
|
factory: (T) -> V,
|
||||||
|
animateAdd: Int? = null,
|
||||||
|
animateRemove: Int? = null
|
||||||
|
) {
|
||||||
|
for (element in list) {
|
||||||
|
+factory(element)
|
||||||
|
}
|
||||||
|
list.onAdd { (index, element) ->
|
||||||
|
val view = factory(element)
|
||||||
|
add(index, view)
|
||||||
|
|
||||||
|
if (animateAdd != null) {
|
||||||
|
classList += "animate-add"
|
||||||
|
view.classList += "active"
|
||||||
|
|
||||||
|
async(animateAdd) {
|
||||||
|
classList -= "animate-add"
|
||||||
|
view.classList -= "active"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
list.onRemove { (index) ->
|
||||||
|
@Suppress("UNCHECKED_CAST") val view = this[index] as V
|
||||||
|
|
||||||
|
if (animateRemove == null) {
|
||||||
|
remove(view)
|
||||||
|
} else {
|
||||||
|
classList += "animate-remove"
|
||||||
|
view.classList += "active"
|
||||||
|
|
||||||
|
async(animateRemove) {
|
||||||
|
classList -= "animate-remove"
|
||||||
|
view.classList -= "active"
|
||||||
|
remove(view)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
list.onUpdate { (oldIndex, newIndex, element) ->
|
||||||
|
removeAt(oldIndex)
|
||||||
|
add(newIndex, factory(element))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun <V : View> ViewCollection<in V>.listFactory(
|
||||||
|
list: ObservableReadOnlyList<V>,
|
||||||
|
animateAdd: Int? = null,
|
||||||
|
animateRemove: Int? = null
|
||||||
|
) = listFactory(
|
||||||
|
list,
|
||||||
|
{ it },
|
||||||
|
animateAdd,
|
||||||
|
animateRemove
|
||||||
|
)
|
|
@ -220,28 +220,6 @@ a {
|
||||||
.table-layout-search {
|
.table-layout-search {
|
||||||
float: left;
|
float: left;
|
||||||
padding-bottom: 0.5rem !important;
|
padding-bottom: 0.5rem !important;
|
||||||
|
|
||||||
input {
|
|
||||||
padding-right: 4rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-search {
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
height: 2.5rem;
|
|
||||||
line-height: 2.5rem;
|
|
||||||
right: -3px;
|
|
||||||
border-bottom-left-radius: 0;
|
|
||||||
border-top-left-radius: 0;
|
|
||||||
|
|
||||||
margin-top: 1px;
|
|
||||||
margin-right: 2px;
|
|
||||||
}
|
|
||||||
|
|
||||||
input:focus ~ .btn-search {
|
|
||||||
border-color: $primary-color;
|
|
||||||
border-width: 2px;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.table-layout-action {
|
.table-layout-action {
|
||||||
|
@ -470,13 +448,106 @@ form {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
position: relative;
|
position: relative;
|
||||||
margin-top: 1rem;
|
margin-top: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
& > div {
|
.calendar-table {
|
||||||
width: calc(100% - 6rem);
|
float: left;
|
||||||
overflow-x: scroll;
|
width: 100%;
|
||||||
overflow-y: visible;
|
overflow-x: scroll;
|
||||||
margin-left: 6rem;
|
overflow-y: visible;
|
||||||
padding-bottom: 1rem;
|
padding-bottom: 1rem;
|
||||||
|
position: relative;
|
||||||
|
transition: width $transitionTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
.calendar-edit {
|
||||||
|
width: 16rem;
|
||||||
|
display: block;
|
||||||
|
position: absolute;
|
||||||
|
right: 0;
|
||||||
|
top: -3rem;
|
||||||
|
padding-top: 3rem;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
.calendar-edit-main {
|
||||||
|
position: relative;
|
||||||
|
left: 16rem;
|
||||||
|
opacity: 0;
|
||||||
|
transition: left $transitionTime, opacity$transitionTime;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.calendar-edit-top {
|
||||||
|
position: absolute;
|
||||||
|
right: 0;
|
||||||
|
top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.calendar-edit-search {
|
||||||
|
padding: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.calendar-work-group {
|
||||||
|
position: relative;
|
||||||
|
display: block;
|
||||||
|
border-radius: 0.2rem;
|
||||||
|
line-height: 2rem;
|
||||||
|
font-size: 0.8rem;
|
||||||
|
white-space: nowrap;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
|
||||||
|
background-color: $primary-color;
|
||||||
|
color: $primary-text-color;
|
||||||
|
|
||||||
|
padding: 0 0.5rem;
|
||||||
|
margin: 0.5rem;
|
||||||
|
|
||||||
|
&::after {
|
||||||
|
content: attr(data-language);
|
||||||
|
bottom: 0;
|
||||||
|
right: 1rem;
|
||||||
|
opacity: 0.6;
|
||||||
|
position: absolute;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover .calendar-tools {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.drag {
|
||||||
|
box-shadow: 0 0.1rem 0.2rem rgba($text-primary-color, 0.8);
|
||||||
|
z-index: 2;
|
||||||
|
|
||||||
|
.calendar-tools {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.pending::before {
|
||||||
|
content: '';
|
||||||
|
display: block;
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background-color: $background-primary-color;
|
||||||
|
opacity: 0.6;
|
||||||
|
}
|
||||||
|
|
||||||
|
@include no-select()
|
||||||
|
}
|
||||||
|
|
||||||
|
.calendar[data-editable = "true"].edit {
|
||||||
|
.calendar-table {
|
||||||
|
width: calc(100% - 16rem);
|
||||||
|
border-right: solid 1px rgba($text-primary-color, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.calendar-edit-main {
|
||||||
|
left: 0;
|
||||||
|
opacity: 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -507,6 +578,10 @@ form {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.calendar[data-editable=false] .calendar-tools {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
.calendar-entry {
|
.calendar-entry {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
display: block;
|
display: block;
|
||||||
|
@ -555,6 +630,8 @@ form {
|
||||||
background-color: $background-primary-color;
|
background-color: $background-primary-color;
|
||||||
opacity: 0.6;
|
opacity: 0.6;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@include no-select()
|
||||||
}
|
}
|
||||||
|
|
||||||
.calendar-table-time-to-room {
|
.calendar-table-time-to-room {
|
||||||
|
@ -618,7 +695,6 @@ form {
|
||||||
}
|
}
|
||||||
|
|
||||||
.calendar-cell:first-child {
|
.calendar-cell:first-child {
|
||||||
position: absolute;
|
|
||||||
width: 6rem;
|
width: 6rem;
|
||||||
left: 0;
|
left: 0;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
@ -663,13 +739,6 @@ form {
|
||||||
line-height: 2rem;
|
line-height: 2rem;
|
||||||
height: 1.3rem;
|
height: 1.3rem;
|
||||||
|
|
||||||
&:nth-child(4n + 2)::before {
|
|
||||||
content: '';
|
|
||||||
position: absolute;
|
|
||||||
width: calc(100% - 6rem);
|
|
||||||
border-top: solid 1px rgba($text-primary-color, 0.1);
|
|
||||||
}
|
|
||||||
|
|
||||||
.calendar-cell {
|
.calendar-cell {
|
||||||
position: relative;
|
position: relative;
|
||||||
width: 12rem;
|
width: 12rem;
|
||||||
|
@ -677,6 +746,7 @@ form {
|
||||||
&::before {
|
&::before {
|
||||||
content: '';
|
content: '';
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
left: 0;
|
left: 0;
|
||||||
top: 0;
|
top: 0;
|
||||||
border-left: solid 1px rgba($text-primary-color, 0.1);
|
border-left: solid 1px rgba($text-primary-color, 0.1);
|
||||||
|
@ -694,18 +764,22 @@ form {
|
||||||
left: 0.1rem !important;
|
left: 0.1rem !important;
|
||||||
right: 0.1rem;
|
right: 0.1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.calendar-cell:first-child:not(:empty)::before {
|
.calendar-cell:first-child::before {
|
||||||
width: 100%;
|
|
||||||
top: 0;
|
|
||||||
border-left: none;
|
border-left: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:nth-child(4n + 2) .calendar-cell::before {
|
||||||
border-top: solid 1px rgba($text-primary-color, 0.1);
|
border-top: solid 1px rgba($text-primary-color, 0.1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.calendar-cell:first-child {
|
.calendar-cell:first-child {
|
||||||
position: absolute;
|
|
||||||
width: 6rem;
|
width: 6rem;
|
||||||
left: 0;
|
left: 0;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
|
|
@ -7,9 +7,7 @@ import de.kif.common.model.User
|
||||||
import io.ktor.application.Application
|
import io.ktor.application.Application
|
||||||
import io.ktor.server.engine.embeddedServer
|
import io.ktor.server.engine.embeddedServer
|
||||||
import io.ktor.server.netty.Netty
|
import io.ktor.server.netty.Netty
|
||||||
import kotlinx.coroutines.coroutineScope
|
|
||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.runBlocking
|
||||||
import kotlin.coroutines.suspendCoroutine
|
|
||||||
|
|
||||||
object Main {
|
object Main {
|
||||||
@Suppress("UnusedMainParameter")
|
@Suppress("UnusedMainParameter")
|
||||||
|
@ -34,6 +32,8 @@ object Main {
|
||||||
password = System.console()?.readPassword()?.toString() ?: readLine()
|
password = System.console()?.readPassword()?.toString() ?: readLine()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
println("Create root user '$username' with pw '${"*".repeat(password.length)}'")
|
||||||
|
|
||||||
UserRepository.create(
|
UserRepository.create(
|
||||||
User(
|
User(
|
||||||
null,
|
null,
|
||||||
|
|
|
@ -5,11 +5,11 @@ 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.repository.WorkGroupRepository
|
import de.kif.backend.repository.WorkGroupRepository
|
||||||
import de.kif.backend.util.Search
|
|
||||||
import de.kif.backend.view.MainTemplate
|
import de.kif.backend.view.MainTemplate
|
||||||
import de.kif.backend.view.MenuTemplate
|
import de.kif.backend.view.MenuTemplate
|
||||||
import de.kif.backend.view.TableTemplate
|
import de.kif.backend.view.TableTemplate
|
||||||
import de.kif.common.CALENDAR_GRID_WIDTH
|
import de.kif.common.CALENDAR_GRID_WIDTH
|
||||||
|
import de.kif.common.Search
|
||||||
import de.kif.common.model.Permission
|
import de.kif.common.model.Permission
|
||||||
import de.kif.common.model.Room
|
import de.kif.common.model.Room
|
||||||
import de.kif.common.model.Schedule
|
import de.kif.common.model.Schedule
|
||||||
|
@ -304,19 +304,21 @@ fun Route.calendar() {
|
||||||
a("/calendar/${day + 1}") { +">" }
|
a("/calendar/${day + 1}") { +">" }
|
||||||
}
|
}
|
||||||
div("header-right") {
|
div("header-right") {
|
||||||
a("/calendar/$day/room-to-time") {
|
a("/calendar/$day/rtt") {
|
||||||
+"Room to time"
|
+"Room to time"
|
||||||
}
|
}
|
||||||
a("/calendar/$day/time-to-room") {
|
a("/calendar/$day/ttr") {
|
||||||
+"Time to room"
|
+"Time to room"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
div("calendar") {
|
div("calendar") {
|
||||||
|
val editable = user != null
|
||||||
attributes["data-day"] = day.toString()
|
attributes["data-day"] = day.toString()
|
||||||
|
attributes["data-editable"] = editable.toString()
|
||||||
|
|
||||||
div {
|
div("calendar-table") {
|
||||||
when (orientation) {
|
when (orientation) {
|
||||||
CalendarOrientation.ROOM_TO_TIME -> renderRoomToTime(
|
CalendarOrientation.ROOM_TO_TIME -> renderRoomToTime(
|
||||||
day,
|
day,
|
||||||
|
@ -324,7 +326,7 @@ fun Route.calendar() {
|
||||||
max,
|
max,
|
||||||
rooms,
|
rooms,
|
||||||
schedules,
|
schedules,
|
||||||
user != null
|
editable
|
||||||
)
|
)
|
||||||
CalendarOrientation.TIME_TO_ROOM -> renderTimeToRoom(
|
CalendarOrientation.TIME_TO_ROOM -> renderTimeToRoom(
|
||||||
day,
|
day,
|
||||||
|
@ -332,13 +334,33 @@ fun Route.calendar() {
|
||||||
max,
|
max,
|
||||||
rooms,
|
rooms,
|
||||||
schedules,
|
schedules,
|
||||||
user != null
|
editable
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (editable) {
|
||||||
|
div("calendar-edit") {
|
||||||
|
div("calendar-edit-top") {
|
||||||
|
button(classes = "form-btn") {
|
||||||
|
+"Edit"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
div("calendar-edit-main") {
|
||||||
|
div("calendar-edit-search") {
|
||||||
|
input(InputType.search, name = "search", classes = "form-control") {
|
||||||
|
placeholder = "Search"
|
||||||
|
value = ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
div("calendar-edit-list") {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -392,10 +414,11 @@ fun Route.calendar() {
|
||||||
}
|
}
|
||||||
|
|
||||||
for (u in list) {
|
for (u in list) {
|
||||||
if (Search.match(search, u.name)) {
|
val s = u.createSearch()
|
||||||
|
if (Search.match(search, s)) {
|
||||||
val href = "/calendar/$day/${room.id}/$time/${u.id}"
|
val href = "/calendar/$day/${room.id}/$time/${u.id}"
|
||||||
entry {
|
entry {
|
||||||
attributes["data-search"] = Search.pack(u.name)
|
attributes["data-search"] = s.stringify()
|
||||||
td {
|
td {
|
||||||
a(href) {
|
a(href) {
|
||||||
+u.name
|
+u.name
|
||||||
|
|
|
@ -2,7 +2,7 @@ package de.kif.backend.route
|
||||||
|
|
||||||
import de.kif.backend.authenticateOrRedirect
|
import de.kif.backend.authenticateOrRedirect
|
||||||
import de.kif.backend.repository.RoomRepository
|
import de.kif.backend.repository.RoomRepository
|
||||||
import de.kif.backend.util.Search
|
import de.kif.common.Search
|
||||||
import de.kif.backend.view.MainTemplate
|
import de.kif.backend.view.MainTemplate
|
||||||
import de.kif.backend.view.MenuTemplate
|
import de.kif.backend.view.MenuTemplate
|
||||||
import de.kif.backend.view.TableTemplate
|
import de.kif.backend.view.TableTemplate
|
||||||
|
@ -64,9 +64,10 @@ fun Route.room() {
|
||||||
}
|
}
|
||||||
|
|
||||||
for (u in list) {
|
for (u in list) {
|
||||||
if (Search.match(search, u.name)) {
|
val s = u.createSearch()
|
||||||
|
if (Search.match(search, s)) {
|
||||||
entry {
|
entry {
|
||||||
attributes["data-search"] = Search.pack(u.name)
|
attributes["data-search"] = s.stringify()
|
||||||
td {
|
td {
|
||||||
+u.name
|
+u.name
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,7 +3,7 @@ package de.kif.backend.route
|
||||||
|
|
||||||
import de.kif.backend.authenticateOrRedirect
|
import de.kif.backend.authenticateOrRedirect
|
||||||
import de.kif.backend.repository.TrackRepository
|
import de.kif.backend.repository.TrackRepository
|
||||||
import de.kif.backend.util.Search
|
import de.kif.common.Search
|
||||||
import de.kif.backend.view.MainTemplate
|
import de.kif.backend.view.MainTemplate
|
||||||
import de.kif.backend.view.MenuTemplate
|
import de.kif.backend.view.MenuTemplate
|
||||||
import de.kif.backend.view.TableTemplate
|
import de.kif.backend.view.TableTemplate
|
||||||
|
@ -118,9 +118,10 @@ fun Route.track() {
|
||||||
}
|
}
|
||||||
|
|
||||||
for (u in list) {
|
for (u in list) {
|
||||||
if (Search.match(search, u.name)) {
|
val s = u.createSearch()
|
||||||
|
if (Search.match(search, s)) {
|
||||||
entry {
|
entry {
|
||||||
attributes["data-search"] = Search.pack(u.name)
|
attributes["data-search"] = s.stringify()
|
||||||
td {
|
td {
|
||||||
+u.name
|
+u.name
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,7 +4,7 @@ package de.kif.backend.route
|
||||||
import de.kif.backend.authenticateOrRedirect
|
import de.kif.backend.authenticateOrRedirect
|
||||||
import de.kif.backend.hashPassword
|
import de.kif.backend.hashPassword
|
||||||
import de.kif.backend.repository.UserRepository
|
import de.kif.backend.repository.UserRepository
|
||||||
import de.kif.backend.util.Search
|
import de.kif.common.Search
|
||||||
import de.kif.backend.view.MainTemplate
|
import de.kif.backend.view.MainTemplate
|
||||||
import de.kif.backend.view.MenuTemplate
|
import de.kif.backend.view.MenuTemplate
|
||||||
import de.kif.backend.view.TableTemplate
|
import de.kif.backend.view.TableTemplate
|
||||||
|
@ -66,9 +66,10 @@ fun Route.user() {
|
||||||
}
|
}
|
||||||
|
|
||||||
for (u in list) {
|
for (u in list) {
|
||||||
if (Search.match(search, u.username)) {
|
val s = u.createSearch()
|
||||||
|
if (Search.match(search, s)) {
|
||||||
entry {
|
entry {
|
||||||
attributes["data-search"] = Search.pack(u.username)
|
attributes["data-search"] = s.stringify()
|
||||||
td {
|
td {
|
||||||
+u.username
|
+u.username
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,10 +4,10 @@ package de.kif.backend.route
|
||||||
import de.kif.backend.authenticateOrRedirect
|
import de.kif.backend.authenticateOrRedirect
|
||||||
import de.kif.backend.repository.TrackRepository
|
import de.kif.backend.repository.TrackRepository
|
||||||
import de.kif.backend.repository.WorkGroupRepository
|
import de.kif.backend.repository.WorkGroupRepository
|
||||||
import de.kif.backend.util.Search
|
|
||||||
import de.kif.backend.view.MainTemplate
|
import de.kif.backend.view.MainTemplate
|
||||||
import de.kif.backend.view.MenuTemplate
|
import de.kif.backend.view.MenuTemplate
|
||||||
import de.kif.backend.view.TableTemplate
|
import de.kif.backend.view.TableTemplate
|
||||||
|
import de.kif.common.Search
|
||||||
import de.kif.common.model.Language
|
import de.kif.common.model.Language
|
||||||
import de.kif.common.model.Permission
|
import de.kif.common.model.Permission
|
||||||
import de.kif.common.model.WorkGroup
|
import de.kif.common.model.WorkGroup
|
||||||
|
@ -20,12 +20,9 @@ import io.ktor.routing.Route
|
||||||
import io.ktor.routing.get
|
import io.ktor.routing.get
|
||||||
import io.ktor.routing.post
|
import io.ktor.routing.post
|
||||||
import io.ktor.util.toMap
|
import io.ktor.util.toMap
|
||||||
|
import kotlinx.css.CSSBuilder
|
||||||
|
import kotlinx.css.Display
|
||||||
import kotlinx.html.*
|
import kotlinx.html.*
|
||||||
import kotlin.collections.component1
|
|
||||||
import kotlin.collections.component2
|
|
||||||
import kotlin.collections.find
|
|
||||||
import kotlin.collections.firstOrNull
|
|
||||||
import kotlin.collections.mapValues
|
|
||||||
import kotlin.collections.set
|
import kotlin.collections.set
|
||||||
|
|
||||||
fun Route.workGroup() {
|
fun Route.workGroup() {
|
||||||
|
@ -84,34 +81,36 @@ fun Route.workGroup() {
|
||||||
}
|
}
|
||||||
|
|
||||||
for (u in list) {
|
for (u in list) {
|
||||||
if (Search.match(search, u.name)) {
|
val s = u.createSearch()
|
||||||
entry {
|
entry {
|
||||||
attributes["data-search"] = Search.pack(u.name)
|
attributes["style"] = CSSBuilder().apply {
|
||||||
td {
|
display = if (Search.match(search, s)) Display.tableRow else Display.none
|
||||||
+u.name
|
}.toString()
|
||||||
}
|
attributes["data-search"] = s.stringify()
|
||||||
td {
|
td {
|
||||||
+u.interested.toString()
|
+u.name
|
||||||
}
|
}
|
||||||
td {
|
td {
|
||||||
+(u.track?.name ?: "")
|
+u.interested.toString()
|
||||||
}
|
}
|
||||||
td {
|
td {
|
||||||
+u.projector.toString()
|
+(u.track?.name ?: "")
|
||||||
}
|
}
|
||||||
td {
|
td {
|
||||||
+u.resolution.toString()
|
+u.projector.toString()
|
||||||
}
|
}
|
||||||
td {
|
td {
|
||||||
+u.length.toString()
|
+u.resolution.toString()
|
||||||
}
|
}
|
||||||
td {
|
td {
|
||||||
+u.language.toString()
|
+u.length.toString()
|
||||||
}
|
}
|
||||||
td(classes = "action") {
|
td {
|
||||||
a("/workgroup/${u.id}") {
|
+u.language.localeName
|
||||||
i("material-icons") { +"edit" }
|
}
|
||||||
}
|
td(classes = "action") {
|
||||||
|
a("/workgroup/${u.id}") {
|
||||||
|
i("material-icons") { +"edit" }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -171,30 +170,31 @@ fun Route.workGroup() {
|
||||||
htmlFor = "track"
|
htmlFor = "track"
|
||||||
+"Track"
|
+"Track"
|
||||||
}
|
}
|
||||||
div("input-group") {
|
//div("input-group") {
|
||||||
select(
|
select(
|
||||||
classes = "form-control"
|
classes = "form-control"
|
||||||
) {
|
) {
|
||||||
name = "track"
|
name = "track"
|
||||||
|
|
||||||
|
option {
|
||||||
|
selected = (editWorkGroup.track?.id ?: -1) < 0
|
||||||
|
value = "-1"
|
||||||
|
+"None"
|
||||||
|
}
|
||||||
|
for (track in tracks) {
|
||||||
option {
|
option {
|
||||||
selected = (editWorkGroup.track?.id ?: -1) < 0
|
selected = editWorkGroup.track?.id == track.id
|
||||||
value = "-1"
|
value = track.id.toString()
|
||||||
+"None"
|
+track.name
|
||||||
}
|
|
||||||
for (track in tracks) {
|
|
||||||
option {
|
|
||||||
selected = editWorkGroup.track?.id == track.id
|
|
||||||
value = track.id.toString()
|
|
||||||
+track.name
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
/*
|
||||||
a("/tracks", classes = "form-btn") {
|
a("/tracks", classes = "form-btn") {
|
||||||
i("material-icons") { +"edit" }
|
i("material-icons") { +"edit" }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
}
|
}
|
||||||
div("form-group") {
|
div("form-group") {
|
||||||
label {
|
label {
|
||||||
|
|
|
@ -1,29 +0,0 @@
|
||||||
package de.kif.backend.util
|
|
||||||
|
|
||||||
object Search {
|
|
||||||
private fun permute(list: List<String>): List<List<String>> {
|
|
||||||
if (list.size <= 1) return listOf(list)
|
|
||||||
|
|
||||||
val perm = mutableListOf<List<String>>()
|
|
||||||
|
|
||||||
for (elem in list) {
|
|
||||||
val p = permute(list - elem)
|
|
||||||
|
|
||||||
for (x in p) {
|
|
||||||
perm += x + elem
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return perm
|
|
||||||
}
|
|
||||||
|
|
||||||
fun match(search: String, vararg params: String): Boolean {
|
|
||||||
val s = search.toLowerCase().replace(" ", "")
|
|
||||||
|
|
||||||
return permute(params.map { it.toLowerCase().replace(" ", "") }).any {
|
|
||||||
it.joinToString("").contains(s)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun pack(vararg params: String): String = params.joinToString("|") { it.toLowerCase() }
|
|
||||||
}
|
|
|
@ -15,12 +15,14 @@ class TableTemplate() : Template<FlowContent> {
|
||||||
override fun FlowContent.apply() {
|
override fun FlowContent.apply() {
|
||||||
div("table-layout") {
|
div("table-layout") {
|
||||||
form(classes = "form-group table-layout-search") {
|
form(classes = "form-group table-layout-search") {
|
||||||
input(InputType.search, name = "search", classes = "form-control") {
|
div("input-group") {
|
||||||
placeholder = "Search"
|
input(InputType.search, name = "search", classes = "form-control") {
|
||||||
value = searchValue
|
placeholder = "Search"
|
||||||
}
|
value = searchValue
|
||||||
button(type = ButtonType.submit, classes = "form-btn btn-search") {
|
}
|
||||||
i("material-icons") { +"search" }
|
button(type = ButtonType.submit, classes = "form-btn btn-search") {
|
||||||
|
i("material-icons") { +"search" }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
div("table-layout-action") {
|
div("table-layout-action") {
|
||||||
|
|
75
src/jvmTest/resources/sample.http
Normal file
75
src/jvmTest/resources/sample.http
Normal file
|
@ -0,0 +1,75 @@
|
||||||
|
POST http://localhost:8080/api/authenticate
|
||||||
|
Content-Type: application/json
|
||||||
|
|
||||||
|
{
|
||||||
|
"username": "admin",
|
||||||
|
"password": "admin"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
> {%
|
||||||
|
client.assert(response.body.OK === true, "Login failed!");
|
||||||
|
client.global.set("auth_token", response.headers.valueOf("Set-Cookie"));
|
||||||
|
%}
|
||||||
|
|
||||||
|
###
|
||||||
|
|
||||||
|
POST http://localhost:8080/api/rooms
|
||||||
|
Content-Type: application/json
|
||||||
|
Cookie: {{auth_token}}
|
||||||
|
|
||||||
|
{
|
||||||
|
"name": "E006",
|
||||||
|
"places": 20,
|
||||||
|
"projector": true
|
||||||
|
}
|
||||||
|
|
||||||
|
###
|
||||||
|
|
||||||
|
POST http://localhost:8080/api/rooms
|
||||||
|
Content-Type: application/json
|
||||||
|
Cookie: {{auth_token}}
|
||||||
|
|
||||||
|
{
|
||||||
|
"name": "E007",
|
||||||
|
"places": 20,
|
||||||
|
"projector": true
|
||||||
|
}
|
||||||
|
|
||||||
|
###
|
||||||
|
|
||||||
|
POST http://localhost:8080/api/rooms
|
||||||
|
Content-Type: application/json
|
||||||
|
Cookie: {{auth_token}}
|
||||||
|
|
||||||
|
{
|
||||||
|
"name": "E008",
|
||||||
|
"places": 20,
|
||||||
|
"projector": true
|
||||||
|
}
|
||||||
|
|
||||||
|
###
|
||||||
|
|
||||||
|
POST http://localhost:8080/api/rooms
|
||||||
|
Content-Type: application/json
|
||||||
|
Cookie: {{auth_token}}
|
||||||
|
|
||||||
|
{
|
||||||
|
"name": "E009",
|
||||||
|
"places": 20,
|
||||||
|
"projector": true
|
||||||
|
}
|
||||||
|
|
||||||
|
###
|
||||||
|
|
||||||
|
POST http://localhost:8080/api/rooms
|
||||||
|
Content-Type: application/json
|
||||||
|
Cookie: {{auth_token}}
|
||||||
|
|
||||||
|
{
|
||||||
|
"name": "E010",
|
||||||
|
"places": 20,
|
||||||
|
"projector": true
|
||||||
|
}
|
||||||
|
|
||||||
|
###
|
Loading…
Reference in a new issue