diff --git a/src/commonMain/kotlin/de/kif/common/DateFormat.kt b/src/commonMain/kotlin/de/kif/common/DateFormat.kt index 1a40648..bea3c5b 100644 --- a/src/commonMain/kotlin/de/kif/common/DateFormat.kt +++ b/src/commonMain/kotlin/de/kif/common/DateFormat.kt @@ -5,7 +5,7 @@ import com.soywiz.klock.KlockLocale import com.soywiz.klock.format import com.soywiz.klock.locale.german -fun formatDate(unix: Long) = +fun formatDateTime(unix: Long) = DateFormat("EEEE, d. MMMM y HH:mm") .withLocale(KlockLocale.german) .format(unix) diff --git a/src/jsMain/kotlin/de/kif/frontend/views/calendar/Calendar.kt b/src/jsMain/kotlin/de/kif/frontend/views/calendar/Calendar.kt index 379651d..2330319 100644 --- a/src/jsMain/kotlin/de/kif/frontend/views/calendar/Calendar.kt +++ b/src/jsMain/kotlin/de/kif/frontend/views/calendar/Calendar.kt @@ -80,9 +80,7 @@ class Calendar(calendar: HTMLElement) : View(calendar) { val cont = document.getElementsByClassName("header-right")[0] as HTMLElement - val view = View.wrap(createHtmlView()) - cont.appendChild(view.html) - view.html.textContent = "Check" + val view = View.wrap(document.getElementById("calendar-check-constraints") as HTMLElement) view.onClick { launch { val errors = ScheduleRepository.checkConstraints() diff --git a/src/jsMain/kotlin/de/kif/frontend/views/calendar/CalendarEdit.kt b/src/jsMain/kotlin/de/kif/frontend/views/calendar/CalendarEdit.kt index 11fd68b..59f8066 100644 --- a/src/jsMain/kotlin/de/kif/frontend/views/calendar/CalendarEdit.kt +++ b/src/jsMain/kotlin/de/kif/frontend/views/calendar/CalendarEdit.kt @@ -14,13 +14,14 @@ import de.westermann.kwebview.extra.listFactory import org.w3c.dom.HTMLButtonElement import org.w3c.dom.HTMLElement import org.w3c.dom.HTMLInputElement +import kotlin.browser.document class CalendarEdit( private val calendar: Calendar, view: HTMLElement ) : View(view) { private val toggleEditButton = - Button.wrap(view.querySelector(".calendar-edit-top button") as HTMLButtonElement) + Button.wrap(document.getElementById("calendar-edit-button") as HTMLButtonElement) val search = InputView.wrap(view.querySelector(".calendar-edit-search input") as HTMLInputElement) diff --git a/src/jsMain/kotlin/de/kif/frontend/views/overview/PostView.kt b/src/jsMain/kotlin/de/kif/frontend/views/overview/PostView.kt index 8161782..df2f59c 100644 --- a/src/jsMain/kotlin/de/kif/frontend/views/overview/PostView.kt +++ b/src/jsMain/kotlin/de/kif/frontend/views/overview/PostView.kt @@ -1,17 +1,15 @@ package de.kif.frontend.views.overview -import de.kif.common.formatDate +import de.kif.common.formatDateTime import de.kif.frontend.launch import de.kif.frontend.repository.PostRepository import de.westermann.kobserve.event.emit import de.westermann.kwebview.View import de.westermann.kwebview.components.Link import de.westermann.kwebview.createHtmlView -import org.w3c.dom.HTMLAnchorElement import org.w3c.dom.HTMLElement import org.w3c.dom.get import org.w3c.dom.set -import kotlin.browser.document class PostView( view: HTMLElement @@ -47,7 +45,7 @@ class PostView( } contentView.innerHTML = PostRepository.htmlByUrl(p.url) - footerView.innerText = formatDate(p.createdAt) + footerView.innerText = formatDateTime(p.createdAt) emit(PostChangeEvent(postId)) } diff --git a/src/jsMain/resources/style/style.scss b/src/jsMain/resources/style/style.scss index d0994bb..7524785 100644 --- a/src/jsMain/resources/style/style.scss +++ b/src/jsMain/resources/style/style.scss @@ -303,8 +303,10 @@ a { position: absolute; z-index: 1; background: $background-primary-color; - width: 100%; - border: solid 1px $table-border-color; + width: 10rem; + border: solid 1px $input-border-color; + border-radius: $border-radius; + padding: 0.5rem 0; span { padding: 0 0.5rem; @@ -507,6 +509,33 @@ form { } } +.header { + height: 2.2rem; + line-height: 2.2rem; +} + +.header-left { + float: left; + + & > * { + display: block; + float: left; + } + + i { + font-size: 1.5rem; + padding: 0 0.5rem; + } +} + +.header-right { + float: right; + + &::after { + clear: both; + } +} + .calendar { width: 100%; position: relative; @@ -528,8 +557,7 @@ form { display: block; position: absolute; right: 0; - top: -3rem; - padding-top: 3rem; + top: 0; .calendar-edit-main { position: sticky; @@ -538,19 +566,20 @@ form { transition: margin-left $transitionTime, opacity $transitionTime, visibility $transitionTime; top: 1rem; visibility: hidden; + height: 100vh; } } -.calendar-edit-top { - position: absolute; - right: 0; - top: 0; -} - .calendar-edit-search { padding: 0.5rem; } +.calendar-edit-list { + height: calc(100% - 4.5rem); + overflow-x: hidden; + overflow-y: scroll; +} + .calendar-work-group { position: relative; display: block; diff --git a/src/jvmMain/kotlin/de/kif/backend/Configuration.kt b/src/jvmMain/kotlin/de/kif/backend/Configuration.kt index b7e1f2e..5c8efc5 100644 --- a/src/jvmMain/kotlin/de/kif/backend/Configuration.kt +++ b/src/jvmMain/kotlin/de/kif/backend/Configuration.kt @@ -6,6 +6,7 @@ import com.uchuhimo.konf.Item import java.io.FileNotFoundException import java.nio.file.Paths import java.text.SimpleDateFormat +import java.time.ZoneId import java.util.* import kotlin.reflect.KProperty @@ -58,7 +59,11 @@ object Configuration { object Schedule { val reference by c(ScheduleSpec.reference) - val referenceDate: Date by lazy { SimpleDateFormat("yyyy-MM-dd").parse(reference) } + val referenceDate: Date by lazy { + val sdf = SimpleDateFormat("yyyy-MM-dd") + sdf.timeZone = TimeZone.getTimeZone(ZoneId.of("UTC")) + sdf.parse(reference) + } } private object SecuritySpec : ConfigSpec("security") { diff --git a/src/jvmMain/kotlin/de/kif/backend/repository/ScheduleRepository.kt b/src/jvmMain/kotlin/de/kif/backend/repository/ScheduleRepository.kt index 6a23a8c..9b54c54 100644 --- a/src/jvmMain/kotlin/de/kif/backend/repository/ScheduleRepository.kt +++ b/src/jvmMain/kotlin/de/kif/backend/repository/ScheduleRepository.kt @@ -3,7 +3,9 @@ package de.kif.backend.repository import de.kif.backend.database.DbSchedule import de.kif.backend.database.dbQuery import de.kif.backend.util.PushService -import de.kif.common.* +import de.kif.common.MessageType +import de.kif.common.Repository +import de.kif.common.RepositoryType import de.kif.common.model.Schedule import de.westermann.kobserve.event.EventHandler import kotlinx.coroutines.runBlocking @@ -139,6 +141,15 @@ object ScheduleRepository : Repository { } } + suspend fun getDayRange(): IntRange { + val schedules = all() + + val min = schedules.minBy { it.day }?.day ?: 0 + val max = schedules.maxBy { it.day }?.day ?: 0 + + return min..max + } + init { RoomRepository.onUpdate { roomId -> runBlocking { diff --git a/src/jvmMain/kotlin/de/kif/backend/route/Account.kt b/src/jvmMain/kotlin/de/kif/backend/route/Account.kt index f00adf2..6502b64 100644 --- a/src/jvmMain/kotlin/de/kif/backend/route/Account.kt +++ b/src/jvmMain/kotlin/de/kif/backend/route/Account.kt @@ -2,7 +2,7 @@ package de.kif.backend.route import de.kif.backend.authenticate import de.kif.backend.authenticateOrRedirect -import de.kif.backend.backup.Backup +import de.kif.backend.util.Backup import de.kif.backend.repository.TrackRepository import de.kif.backend.repository.WorkGroupRepository import de.kif.backend.route.api.error @@ -60,25 +60,25 @@ fun Route.account() { div("account-backup") { if (user.checkPermission(Permission.ROOM)) { a("/account/backup/rooms.json", classes = "form-btn") { - attributes["download"] = "rooms-backup" + attributes["download"] = "rooms-backup.json" +"Create room backup" } } if (user.checkPermission(Permission.USER)) { a("/account/backup/users.json", classes = "form-btn") { - attributes["download"] = "users-backup" + attributes["download"] = "users-backup.json" +"Create user backup" } } if (user.checkPermission(Permission.POST)) { a("/account/backup/posts.json", classes = "form-btn") { - attributes["download"] = "posts-backup" + attributes["download"] = "posts-backup.json" +"Create post backup" } } if (user.checkPermission(Permission.WORK_GROUP)) { a("/account/backup/work-groups.json", classes = "form-btn") { - attributes["download"] = "work-groups-backup" + attributes["download"] = "work-groups-backup.json" +"Create work group backup" } } @@ -88,7 +88,7 @@ fun Route.account() { user.checkPermission(Permission.SCHEDULE) ) { a("/account/backup/schedules.json", classes = "form-btn") { - attributes["download"] = "schedules-backup" + attributes["download"] = "schedules-backup.json" +"Create schedule backup" } } @@ -205,6 +205,20 @@ fun Route.account() { +"Skip existing work groups" } } + div("form-group form-switch") { + input( + name = "delete-existing", + classes = "form-control", + type = InputType.checkBox + ) { + id = "delete-existing" + checked = false + } + label { + htmlFor = "delete-existing" + +"Delete existing work groups" + } + } } button(type = ButtonType.submit, classes = "form-btn btn-primary") { @@ -304,6 +318,7 @@ fun Route.account() { } val skipExisting = params["skip-existing"] == "on" + val deleteExisting = params["delete-existing"] == "on" val map = mutableMapOf>() @@ -324,12 +339,22 @@ fun Route.account() { val sections = map.values.toMap() - val existingWorkGroups = WorkGroupRepository.all().map { it.name }.toSet() + var existingWorkGroups = WorkGroupRepository.all() + + if (deleteExisting) { + for (wg in existingWorkGroups) { + if (wg.id != null) WorkGroupRepository.delete(wg.id) + } + existingWorkGroups = emptyList() + } + + val existingWorkGroupNames = existingWorkGroups.map { it.name }.toSet() + val importedWorkGroups = WikiImporter.import(sections = sections) var counter = 0 for (workGroup in importedWorkGroups) { - if (skipExisting && workGroup.name in existingWorkGroups) continue + if (skipExisting && workGroup.name in existingWorkGroupNames) continue WorkGroupRepository.create(workGroup) counter++ diff --git a/src/jvmMain/kotlin/de/kif/backend/route/Calendar.kt b/src/jvmMain/kotlin/de/kif/backend/route/Calendar.kt index 86f00bc..c44e877 100644 --- a/src/jvmMain/kotlin/de/kif/backend/route/Calendar.kt +++ b/src/jvmMain/kotlin/de/kif/backend/route/Calendar.kt @@ -1,5 +1,8 @@ package de.kif.backend.route +import com.soywiz.klock.* +import com.soywiz.klock.locale.german +import de.kif.backend.Configuration import de.kif.backend.authenticateOrRedirect import de.kif.backend.isAuthenticated import de.kif.backend.repository.RoomRepository @@ -255,6 +258,11 @@ fun Route.calendar() { val day = call.parameters["day"]?.toIntOrNull() ?: return@get + val range = ScheduleRepository.getDayRange() + if (!editable && day !in range) { + return@get + } + val rooms = RoomRepository.all() val orientation = call.request.cookies["orientation"]?.let { name -> @@ -289,6 +297,12 @@ fun Route.calendar() { min = (min / 60 - 1) * 60 max = (max / 60 + 2) * 60 + val refDate = DateTime(Configuration.Schedule.referenceDate.time) + val date = refDate + day.days + val dateString = DateFormat("EEEE, d. MMMM") + .withLocale(KlockLocale.german) + .format(date) + call.respondHtmlTemplate(MainTemplate()) { menuTemplate { this.user = user @@ -301,19 +315,33 @@ fun Route.calendar() { div("header") { div("header-left") { - a("/calendar/${day - 1}") { +"<" } - span { - +"Day $day" + if (day - 1 in range) { + a("/calendar/${day - 1}") { i("material-icons") { +"chevron_left" } } + } + span { + +dateString + } + if (day + 1 in range) { + a("/calendar/${day + 1}") { i("material-icons") { +"chevron_right" } } } - a("/calendar/${day + 1}") { +">" } } div("header-right") { - a("/calendar/$day/rtt") { + a("/calendar/$day/rtt", classes = "form-btn") { +"Room to time" } - a("/calendar/$day/ttr") { + a("/calendar/$day/ttr", classes = "form-btn") { +"Time to room" } + if (editable) { + button(classes = "form-btn") { + id = "calendar-check-constraints" + +"Check constraints" + } + button(classes = "form-btn") { + id = "calendar-edit-button" + +"Edit" + } + } } } @@ -344,11 +372,6 @@ fun Route.calendar() { 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") { diff --git a/src/jvmMain/kotlin/de/kif/backend/route/Overview.kt b/src/jvmMain/kotlin/de/kif/backend/route/Overview.kt index 5411979..6b13b81 100644 --- a/src/jvmMain/kotlin/de/kif/backend/route/Overview.kt +++ b/src/jvmMain/kotlin/de/kif/backend/route/Overview.kt @@ -8,7 +8,7 @@ import de.kif.backend.repository.PostRepository import de.kif.backend.util.markdownToHtml import de.kif.backend.view.MainTemplate import de.kif.backend.view.MenuTemplate -import de.kif.common.formatDate +import de.kif.common.formatDateTime import de.kif.common.model.Permission import de.kif.common.model.Post import io.ktor.application.call @@ -62,7 +62,7 @@ fun DIV.createPost(post: Post, editable: Boolean = false, additionalClasses: Str } } div("post-footer") { - +formatDate(post.createdAt) + +formatDateTime(post.createdAt) } } } diff --git a/src/jvmMain/kotlin/de/kif/backend/backup/Backup.kt b/src/jvmMain/kotlin/de/kif/backend/util/Backup.kt similarity index 99% rename from src/jvmMain/kotlin/de/kif/backend/backup/Backup.kt rename to src/jvmMain/kotlin/de/kif/backend/util/Backup.kt index bb2c28c..9c0d72c 100644 --- a/src/jvmMain/kotlin/de/kif/backend/backup/Backup.kt +++ b/src/jvmMain/kotlin/de/kif/backend/util/Backup.kt @@ -1,4 +1,4 @@ -package de.kif.backend.backup +package de.kif.backend.util import de.kif.backend.database.Connection import de.kif.backend.repository.* diff --git a/src/jvmMain/kotlin/de/kif/backend/util/WikiImporter.kt b/src/jvmMain/kotlin/de/kif/backend/util/WikiImporter.kt index e8490fa..cb0fb67 100644 --- a/src/jvmMain/kotlin/de/kif/backend/util/WikiImporter.kt +++ b/src/jvmMain/kotlin/de/kif/backend/util/WikiImporter.kt @@ -84,8 +84,6 @@ object WikiImporter { } .toMap() - println(ak) - val name = ak["name"]?.trim() ?: continue var desc = ak["beschreibung"]?.trim() ?: "" val participant = ak["wieviele"]?.trim() ?: "" @@ -96,9 +94,11 @@ object WikiImporter { if (name.isBlank() && desc.isBlank()) continue if (who.isNotBlank() || time.isNotBlank() || length.isNotBlank()) { + desc += "\n" desc += "\n" desc += "--- Aus dem Wiki übernommen ---" desc += "\n" + desc += "\n" if (who.isNotBlank()) { desc += "Wer = $who\n" @@ -118,7 +118,7 @@ object WikiImporter { val match = regex.find(length) if (match != null) { - akLength = max(akLength, match.groupValues.getOrNull(1)?.toIntOrNull() ?: 0) + akLength = max(akLength, (match.groupValues.getOrNull(1)?.toIntOrNull() ?: 0) * 60) } } @@ -133,8 +133,6 @@ object WikiImporter { } } - println(1) - workGroups += WorkGroup( id = null, name = name,