Improve calendar header design

This commit is contained in:
Lars Westermann 2019-05-31 16:39:43 +02:00
parent 5c058f7fdc
commit df8f04099a
Signed by: lars.westermann
GPG key ID: 9D417FA5BB9D5E1D
12 changed files with 136 additions and 48 deletions

View file

@ -5,7 +5,7 @@ import com.soywiz.klock.KlockLocale
import com.soywiz.klock.format import com.soywiz.klock.format
import com.soywiz.klock.locale.german import com.soywiz.klock.locale.german
fun formatDate(unix: Long) = fun formatDateTime(unix: Long) =
DateFormat("EEEE, d. MMMM y HH:mm") DateFormat("EEEE, d. MMMM y HH:mm")
.withLocale(KlockLocale.german) .withLocale(KlockLocale.german)
.format(unix) .format(unix)

View file

@ -80,9 +80,7 @@ class Calendar(calendar: HTMLElement) : View(calendar) {
val cont = document.getElementsByClassName("header-right")[0] as HTMLElement val cont = document.getElementsByClassName("header-right")[0] as HTMLElement
val view = View.wrap(createHtmlView<HTMLAnchorElement>()) val view = View.wrap(document.getElementById("calendar-check-constraints") as HTMLElement)
cont.appendChild(view.html)
view.html.textContent = "Check"
view.onClick { view.onClick {
launch { launch {
val errors = ScheduleRepository.checkConstraints() val errors = ScheduleRepository.checkConstraints()

View file

@ -14,13 +14,14 @@ import de.westermann.kwebview.extra.listFactory
import org.w3c.dom.HTMLButtonElement import org.w3c.dom.HTMLButtonElement
import org.w3c.dom.HTMLElement import org.w3c.dom.HTMLElement
import org.w3c.dom.HTMLInputElement import org.w3c.dom.HTMLInputElement
import kotlin.browser.document
class CalendarEdit( class CalendarEdit(
private val calendar: Calendar, view: HTMLElement private val calendar: Calendar, view: HTMLElement
) : View(view) { ) : View(view) {
private val toggleEditButton = private val toggleEditButton =
Button.wrap(view.querySelector(".calendar-edit-top button") as HTMLButtonElement) Button.wrap(document.getElementById("calendar-edit-button") as HTMLButtonElement)
val search = val search =
InputView.wrap(view.querySelector(".calendar-edit-search input") as HTMLInputElement) InputView.wrap(view.querySelector(".calendar-edit-search input") as HTMLInputElement)

View file

@ -1,17 +1,15 @@
package de.kif.frontend.views.overview 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.launch
import de.kif.frontend.repository.PostRepository import de.kif.frontend.repository.PostRepository
import de.westermann.kobserve.event.emit import de.westermann.kobserve.event.emit
import de.westermann.kwebview.View import de.westermann.kwebview.View
import de.westermann.kwebview.components.Link import de.westermann.kwebview.components.Link
import de.westermann.kwebview.createHtmlView import de.westermann.kwebview.createHtmlView
import org.w3c.dom.HTMLAnchorElement
import org.w3c.dom.HTMLElement import org.w3c.dom.HTMLElement
import org.w3c.dom.get import org.w3c.dom.get
import org.w3c.dom.set import org.w3c.dom.set
import kotlin.browser.document
class PostView( class PostView(
view: HTMLElement view: HTMLElement
@ -47,7 +45,7 @@ class PostView(
} }
contentView.innerHTML = PostRepository.htmlByUrl(p.url) contentView.innerHTML = PostRepository.htmlByUrl(p.url)
footerView.innerText = formatDate(p.createdAt) footerView.innerText = formatDateTime(p.createdAt)
emit(PostChangeEvent(postId)) emit(PostChangeEvent(postId))
} }

View file

@ -303,8 +303,10 @@ a {
position: absolute; position: absolute;
z-index: 1; z-index: 1;
background: $background-primary-color; background: $background-primary-color;
width: 100%; width: 10rem;
border: solid 1px $table-border-color; border: solid 1px $input-border-color;
border-radius: $border-radius;
padding: 0.5rem 0;
span { span {
padding: 0 0.5rem; 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 { .calendar {
width: 100%; width: 100%;
position: relative; position: relative;
@ -528,8 +557,7 @@ form {
display: block; display: block;
position: absolute; position: absolute;
right: 0; right: 0;
top: -3rem; top: 0;
padding-top: 3rem;
.calendar-edit-main { .calendar-edit-main {
position: sticky; position: sticky;
@ -538,19 +566,20 @@ form {
transition: margin-left $transitionTime, opacity $transitionTime, visibility $transitionTime; transition: margin-left $transitionTime, opacity $transitionTime, visibility $transitionTime;
top: 1rem; top: 1rem;
visibility: hidden; visibility: hidden;
height: 100vh;
} }
} }
.calendar-edit-top {
position: absolute;
right: 0;
top: 0;
}
.calendar-edit-search { .calendar-edit-search {
padding: 0.5rem; padding: 0.5rem;
} }
.calendar-edit-list {
height: calc(100% - 4.5rem);
overflow-x: hidden;
overflow-y: scroll;
}
.calendar-work-group { .calendar-work-group {
position: relative; position: relative;
display: block; display: block;

View file

@ -6,6 +6,7 @@ import com.uchuhimo.konf.Item
import java.io.FileNotFoundException import java.io.FileNotFoundException
import java.nio.file.Paths import java.nio.file.Paths
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
import java.time.ZoneId
import java.util.* import java.util.*
import kotlin.reflect.KProperty import kotlin.reflect.KProperty
@ -58,7 +59,11 @@ object Configuration {
object Schedule { object Schedule {
val reference by c(ScheduleSpec.reference) 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") { private object SecuritySpec : ConfigSpec("security") {

View file

@ -3,7 +3,9 @@ package de.kif.backend.repository
import de.kif.backend.database.DbSchedule import de.kif.backend.database.DbSchedule
import de.kif.backend.database.dbQuery import de.kif.backend.database.dbQuery
import de.kif.backend.util.PushService 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.kif.common.model.Schedule
import de.westermann.kobserve.event.EventHandler import de.westermann.kobserve.event.EventHandler
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
@ -139,6 +141,15 @@ object ScheduleRepository : Repository<Schedule> {
} }
} }
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 { init {
RoomRepository.onUpdate { roomId -> RoomRepository.onUpdate { roomId ->
runBlocking { runBlocking {

View file

@ -2,7 +2,7 @@ package de.kif.backend.route
import de.kif.backend.authenticate import de.kif.backend.authenticate
import de.kif.backend.authenticateOrRedirect 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.TrackRepository
import de.kif.backend.repository.WorkGroupRepository import de.kif.backend.repository.WorkGroupRepository
import de.kif.backend.route.api.error import de.kif.backend.route.api.error
@ -60,25 +60,25 @@ fun Route.account() {
div("account-backup") { div("account-backup") {
if (user.checkPermission(Permission.ROOM)) { if (user.checkPermission(Permission.ROOM)) {
a("/account/backup/rooms.json", classes = "form-btn") { a("/account/backup/rooms.json", classes = "form-btn") {
attributes["download"] = "rooms-backup" attributes["download"] = "rooms-backup.json"
+"Create room backup" +"Create room backup"
} }
} }
if (user.checkPermission(Permission.USER)) { if (user.checkPermission(Permission.USER)) {
a("/account/backup/users.json", classes = "form-btn") { a("/account/backup/users.json", classes = "form-btn") {
attributes["download"] = "users-backup" attributes["download"] = "users-backup.json"
+"Create user backup" +"Create user backup"
} }
} }
if (user.checkPermission(Permission.POST)) { if (user.checkPermission(Permission.POST)) {
a("/account/backup/posts.json", classes = "form-btn") { a("/account/backup/posts.json", classes = "form-btn") {
attributes["download"] = "posts-backup" attributes["download"] = "posts-backup.json"
+"Create post backup" +"Create post backup"
} }
} }
if (user.checkPermission(Permission.WORK_GROUP)) { if (user.checkPermission(Permission.WORK_GROUP)) {
a("/account/backup/work-groups.json", classes = "form-btn") { a("/account/backup/work-groups.json", classes = "form-btn") {
attributes["download"] = "work-groups-backup" attributes["download"] = "work-groups-backup.json"
+"Create work group backup" +"Create work group backup"
} }
} }
@ -88,7 +88,7 @@ fun Route.account() {
user.checkPermission(Permission.SCHEDULE) user.checkPermission(Permission.SCHEDULE)
) { ) {
a("/account/backup/schedules.json", classes = "form-btn") { a("/account/backup/schedules.json", classes = "form-btn") {
attributes["download"] = "schedules-backup" attributes["download"] = "schedules-backup.json"
+"Create schedule backup" +"Create schedule backup"
} }
} }
@ -205,6 +205,20 @@ fun Route.account() {
+"Skip existing work groups" +"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") { button(type = ButtonType.submit, classes = "form-btn btn-primary") {
@ -304,6 +318,7 @@ fun Route.account() {
} }
val skipExisting = params["skip-existing"] == "on" val skipExisting = params["skip-existing"] == "on"
val deleteExisting = params["delete-existing"] == "on"
val map = mutableMapOf<Int, Pair<String, Long?>>() val map = mutableMapOf<Int, Pair<String, Long?>>()
@ -324,12 +339,22 @@ fun Route.account() {
val sections = map.values.toMap() 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) val importedWorkGroups = WikiImporter.import(sections = sections)
var counter = 0 var counter = 0
for (workGroup in importedWorkGroups) { for (workGroup in importedWorkGroups) {
if (skipExisting && workGroup.name in existingWorkGroups) continue if (skipExisting && workGroup.name in existingWorkGroupNames) continue
WorkGroupRepository.create(workGroup) WorkGroupRepository.create(workGroup)
counter++ counter++

View file

@ -1,5 +1,8 @@
package de.kif.backend.route 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.authenticateOrRedirect
import de.kif.backend.isAuthenticated import de.kif.backend.isAuthenticated
import de.kif.backend.repository.RoomRepository import de.kif.backend.repository.RoomRepository
@ -255,6 +258,11 @@ fun Route.calendar() {
val day = call.parameters["day"]?.toIntOrNull() ?: return@get val day = call.parameters["day"]?.toIntOrNull() ?: return@get
val range = ScheduleRepository.getDayRange()
if (!editable && day !in range) {
return@get
}
val rooms = RoomRepository.all() val rooms = RoomRepository.all()
val orientation = call.request.cookies["orientation"]?.let { name -> val orientation = call.request.cookies["orientation"]?.let { name ->
@ -289,6 +297,12 @@ fun Route.calendar() {
min = (min / 60 - 1) * 60 min = (min / 60 - 1) * 60
max = (max / 60 + 2) * 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()) { call.respondHtmlTemplate(MainTemplate()) {
menuTemplate { menuTemplate {
this.user = user this.user = user
@ -301,19 +315,33 @@ fun Route.calendar() {
div("header") { div("header") {
div("header-left") { div("header-left") {
a("/calendar/${day - 1}") { +"<" } if (day - 1 in range) {
span { a("/calendar/${day - 1}") { i("material-icons") { +"chevron_left" } }
+"Day $day" }
span {
+dateString
}
if (day + 1 in range) {
a("/calendar/${day + 1}") { i("material-icons") { +"chevron_right" } }
} }
a("/calendar/${day + 1}") { +">" }
} }
div("header-right") { div("header-right") {
a("/calendar/$day/rtt") { a("/calendar/$day/rtt", classes = "form-btn") {
+"Room to time" +"Room to time"
} }
a("/calendar/$day/ttr") { a("/calendar/$day/ttr", classes = "form-btn") {
+"Time to room" +"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) { if (editable) {
div("calendar-edit") { div("calendar-edit") {
div("calendar-edit-top") {
button(classes = "form-btn") {
+"Edit"
}
}
div("calendar-edit-main") { div("calendar-edit-main") {
div("calendar-edit-search") { div("calendar-edit-search") {
input(InputType.search, name = "search", classes = "form-control") { input(InputType.search, name = "search", classes = "form-control") {

View file

@ -8,7 +8,7 @@ import de.kif.backend.repository.PostRepository
import de.kif.backend.util.markdownToHtml import de.kif.backend.util.markdownToHtml
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.common.formatDate import de.kif.common.formatDateTime
import de.kif.common.model.Permission import de.kif.common.model.Permission
import de.kif.common.model.Post import de.kif.common.model.Post
import io.ktor.application.call import io.ktor.application.call
@ -62,7 +62,7 @@ fun DIV.createPost(post: Post, editable: Boolean = false, additionalClasses: Str
} }
} }
div("post-footer") { div("post-footer") {
+formatDate(post.createdAt) +formatDateTime(post.createdAt)
} }
} }
} }

View file

@ -1,4 +1,4 @@
package de.kif.backend.backup package de.kif.backend.util
import de.kif.backend.database.Connection import de.kif.backend.database.Connection
import de.kif.backend.repository.* import de.kif.backend.repository.*

View file

@ -84,8 +84,6 @@ object WikiImporter {
} }
.toMap() .toMap()
println(ak)
val name = ak["name"]?.trim() ?: continue val name = ak["name"]?.trim() ?: continue
var desc = ak["beschreibung"]?.trim() ?: "" var desc = ak["beschreibung"]?.trim() ?: ""
val participant = ak["wieviele"]?.trim() ?: "" val participant = ak["wieviele"]?.trim() ?: ""
@ -96,9 +94,11 @@ object WikiImporter {
if (name.isBlank() && desc.isBlank()) continue if (name.isBlank() && desc.isBlank()) continue
if (who.isNotBlank() || time.isNotBlank() || length.isNotBlank()) { if (who.isNotBlank() || time.isNotBlank() || length.isNotBlank()) {
desc += "\n"
desc += "\n" desc += "\n"
desc += "--- Aus dem Wiki übernommen ---" desc += "--- Aus dem Wiki übernommen ---"
desc += "\n" desc += "\n"
desc += "\n"
if (who.isNotBlank()) { if (who.isNotBlank()) {
desc += "Wer = $who\n" desc += "Wer = $who\n"
@ -118,7 +118,7 @@ object WikiImporter {
val match = regex.find(length) val match = regex.find(length)
if (match != null) { 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( workGroups += WorkGroup(
id = null, id = null,
name = name, name = name,