Add announcement
This commit is contained in:
parent
7ac2e1c208
commit
594ac544dd
|
@ -48,8 +48,8 @@ fun checkConstraints(
|
|||
if (
|
||||
schedule != s &&
|
||||
leader in s.workGroup.leader &&
|
||||
start <= s.getAbsoluteEndTime() &&
|
||||
s.getAbsoluteStartTime() <= end
|
||||
start < s.getAbsoluteEndTime() &&
|
||||
s.getAbsoluteStartTime() < end
|
||||
) {
|
||||
errors += ConstraintError("Work group with leader $leader cannot be at same time with ${s.workGroup.name}!")
|
||||
}
|
||||
|
|
|
@ -25,7 +25,7 @@ enum class MessageType {
|
|||
}
|
||||
|
||||
enum class RepositoryType {
|
||||
ROOM, SCHEDULE, TRACK, USER, WORK_GROUP, POST
|
||||
ROOM, SCHEDULE, TRACK, USER, WORK_GROUP, POST, ANNOUNCEMENT
|
||||
}
|
||||
|
||||
object Serialization {
|
||||
|
|
|
@ -95,7 +95,8 @@ class WebSocketClient {
|
|||
TrackRepository.handler,
|
||||
UserRepository.handler,
|
||||
WorkGroupRepository.handler,
|
||||
PostRepository.handler
|
||||
PostRepository.handler,
|
||||
AnnouncementRepository.handler
|
||||
)
|
||||
|
||||
init {
|
||||
|
|
|
@ -2,6 +2,7 @@ package de.kif.frontend
|
|||
|
||||
import de.kif.frontend.views.board.initBoard
|
||||
import de.kif.frontend.views.calendar.initCalendar
|
||||
import de.kif.frontend.views.initAnnouncement
|
||||
import de.kif.frontend.views.table.initTableLayout
|
||||
import de.kif.frontend.views.initWorkGroupConstraints
|
||||
import de.kif.frontend.views.overview.initOverviewMain
|
||||
|
@ -34,4 +35,7 @@ fun main() = init {
|
|||
if (document.getElementsByClassName("board").length > 0) {
|
||||
initBoard()
|
||||
}
|
||||
if (document.getElementsByClassName("announcement").length > 0) {
|
||||
initAnnouncement()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,40 @@
|
|||
package de.kif.frontend.repository
|
||||
|
||||
import de.kif.common.Message
|
||||
import de.kif.common.Repository
|
||||
import de.kif.common.RepositoryType
|
||||
import de.kif.common.Serialization
|
||||
import de.kif.common.model.Room
|
||||
import de.kif.frontend.MessageHandler
|
||||
import de.westermann.kobserve.event.EventHandler
|
||||
import kotlinx.serialization.DynamicObjectParser
|
||||
import kotlinx.serialization.list
|
||||
import kotlinx.serialization.serializer
|
||||
|
||||
object AnnouncementRepository {
|
||||
|
||||
private val prefix = js("prefix")
|
||||
|
||||
val onUpdate = EventHandler<Unit>()
|
||||
|
||||
private val parser = DynamicObjectParser()
|
||||
|
||||
suspend fun getAnnouncement(): String {
|
||||
val json = repositoryGet("$prefix/api/announcement") ?: return ""
|
||||
return parser.parse(json, String.serializer())
|
||||
}
|
||||
|
||||
suspend fun setAnnouncement(value: String){
|
||||
return repositoryPost("$prefix/api/announcement", Serialization.stringify(String.serializer(), value))
|
||||
?: throw IllegalStateException("Cannot set announcement!")
|
||||
}
|
||||
|
||||
val handler = object : MessageHandler(RepositoryType.ROOM) {
|
||||
|
||||
override fun onCreate(id: Long) {}
|
||||
|
||||
override fun onUpdate(id: Long) = onUpdate.emit(Unit)
|
||||
|
||||
override fun onDelete(id: Long) {}
|
||||
}
|
||||
}
|
21
src/jsMain/kotlin/de/kif/frontend/views/Announcements.kt
Normal file
21
src/jsMain/kotlin/de/kif/frontend/views/Announcements.kt
Normal file
|
@ -0,0 +1,21 @@
|
|||
package de.kif.frontend.views
|
||||
|
||||
import de.kif.frontend.launch
|
||||
import de.kif.frontend.repository.AnnouncementRepository
|
||||
import org.w3c.dom.HTMLElement
|
||||
import org.w3c.dom.get
|
||||
import kotlin.browser.document
|
||||
|
||||
fun initAnnouncement() {
|
||||
val announcement = document.getElementsByClassName("announcement")[0] as? HTMLElement ?: return
|
||||
val span = announcement.children[0] as? HTMLElement ?: return
|
||||
|
||||
AnnouncementRepository.onUpdate {
|
||||
launch {
|
||||
val text = AnnouncementRepository.getAnnouncement()
|
||||
|
||||
announcement.classList.toggle("announcement-blank", text.isBlank())
|
||||
span.textContent = text
|
||||
}
|
||||
}
|
||||
}
|
|
@ -205,3 +205,10 @@
|
|||
margin-top: -1px !important;
|
||||
}
|
||||
}
|
||||
|
||||
.board-announcement {
|
||||
position: fixed;
|
||||
bottom: 3rem;
|
||||
z-index: 10;
|
||||
margin-bottom: 0 !important;
|
||||
}
|
||||
|
|
|
@ -181,4 +181,21 @@ body.offline {
|
|||
.offline-banner {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.announcement {
|
||||
height: 3rem;
|
||||
line-height: 3rem;
|
||||
text-align: center;
|
||||
width: 100%;
|
||||
font-size: 1.2rem;
|
||||
background-color: var(--primary-color);
|
||||
color: var(--primary-text-color);
|
||||
box-shadow: 0 1px 4px var(--shadow-color);
|
||||
margin-bottom: 1rem;
|
||||
display: block;
|
||||
|
||||
&.announcement-blank {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -90,6 +90,7 @@ fun Application.main() {
|
|||
workGroupApi()
|
||||
constraintsApi()
|
||||
postApi()
|
||||
announcementApi()
|
||||
|
||||
// Web socket push notifications
|
||||
pushService()
|
||||
|
|
|
@ -42,6 +42,7 @@ object Configuration {
|
|||
val sessions by required<String>()
|
||||
val uploads by required<String>()
|
||||
val database by required<String>()
|
||||
val announcement by required<String>()
|
||||
}
|
||||
|
||||
object Path {
|
||||
|
@ -56,6 +57,9 @@ object Configuration {
|
|||
|
||||
val database by c(PathSpec.database)
|
||||
val databasePath: java.nio.file.Path by lazy { Paths.get(database).toAbsolutePath() }
|
||||
|
||||
val announcement by c(PathSpec.announcement)
|
||||
val announcementPath: java.nio.file.Path by lazy { Paths.get(announcement).toAbsolutePath() }
|
||||
}
|
||||
|
||||
private object ScheduleSpec : ConfigSpec("schedule") {
|
||||
|
|
|
@ -0,0 +1,53 @@
|
|||
package de.kif.backend.repository
|
||||
|
||||
import de.kif.backend.Configuration
|
||||
import de.kif.backend.util.PushService
|
||||
import de.kif.common.MessageType
|
||||
import de.kif.common.RepositoryType
|
||||
import de.westermann.kobserve.event.EventHandler
|
||||
import kotlinx.coroutines.runBlocking
|
||||
|
||||
object AnnouncementRepository {
|
||||
|
||||
val onUpdate = EventHandler<Unit>()
|
||||
|
||||
private val file = Configuration.Path.announcementPath.toFile()
|
||||
|
||||
private var announcement = ""
|
||||
|
||||
fun setAnnouncement(value: String) {
|
||||
val str = value.replace("\n", " ").trim()
|
||||
if (announcement == str) return
|
||||
|
||||
announcement = str
|
||||
try {
|
||||
file.writeText(str)
|
||||
} catch (e: Exception) {
|
||||
|
||||
}
|
||||
|
||||
onUpdate.emit(Unit)
|
||||
}
|
||||
|
||||
fun getAnnouncement(): String {
|
||||
return announcement
|
||||
}
|
||||
|
||||
fun registerPushService() {
|
||||
onUpdate {
|
||||
runBlocking {
|
||||
PushService.notify(MessageType.UPDATE, RepositoryType.ANNOUNCEMENT, 0L)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
init {
|
||||
try {
|
||||
if (!file.exists()) {
|
||||
file.createNewFile()
|
||||
}
|
||||
announcement = file.readText().trim()
|
||||
} catch (e: Exception) {
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,6 +1,8 @@
|
|||
package de.kif.backend.route
|
||||
|
||||
import de.kif.backend.Configuration
|
||||
import de.kif.backend.prefix
|
||||
import de.kif.backend.repository.AnnouncementRepository
|
||||
import de.kif.backend.repository.RoomRepository
|
||||
import de.kif.backend.repository.ScheduleRepository
|
||||
import de.kif.backend.view.respondMain
|
||||
|
@ -87,6 +89,18 @@ fun Route.board() {
|
|||
val nowLocale = now.time + timeOffset
|
||||
respondMain(true, true) { theme ->
|
||||
content {
|
||||
|
||||
val announcement = AnnouncementRepository.getAnnouncement()
|
||||
var classes = "board-announcement announcement"
|
||||
if (announcement.isBlank()) {
|
||||
classes += " announcement-blank"
|
||||
}
|
||||
div(classes) {
|
||||
span {
|
||||
+announcement
|
||||
}
|
||||
}
|
||||
|
||||
div("board") {
|
||||
div("board-header") {
|
||||
div("board-running") {
|
||||
|
@ -142,7 +156,7 @@ fun Route.board() {
|
|||
}
|
||||
}
|
||||
div("board-logo") {
|
||||
img("KIF 47.0", "/static/images/logo.svg")
|
||||
img("KIF 47.0", "$prefix/static/images/logo.svg")
|
||||
div("board-header-date") {
|
||||
attributes["data-now"] = (now.time - timeOffset).toString()
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package de.kif.backend.route
|
||||
|
||||
import de.kif.backend.*
|
||||
import de.kif.backend.repository.AnnouncementRepository
|
||||
import de.kif.backend.repository.PostRepository
|
||||
import de.kif.backend.util.markdownToHtml
|
||||
import de.kif.backend.view.respondMain
|
||||
|
@ -13,11 +14,13 @@ import io.ktor.http.content.PartData
|
|||
import io.ktor.http.content.forEachPart
|
||||
import io.ktor.http.content.streamProvider
|
||||
import io.ktor.request.receiveMultipart
|
||||
import io.ktor.request.receiveParameters
|
||||
import io.ktor.response.respond
|
||||
import io.ktor.response.respondRedirect
|
||||
import io.ktor.routing.Route
|
||||
import io.ktor.routing.get
|
||||
import io.ktor.routing.post
|
||||
import io.ktor.util.toMap
|
||||
import kotlinx.html.*
|
||||
import java.io.File
|
||||
|
||||
|
@ -75,9 +78,12 @@ fun Route.overview() {
|
|||
content {
|
||||
if (editable) {
|
||||
div("overview-new") {
|
||||
a("post/new", classes = "form-btn") {
|
||||
a("$prefix/post/new", classes = "form-btn") {
|
||||
+"Neuer Eintrag"
|
||||
}
|
||||
a("$prefix/announcement", classes = "form-btn") {
|
||||
+"Ankündigungsbanner bearbeiten"
|
||||
}
|
||||
}
|
||||
}
|
||||
div("overview") {
|
||||
|
@ -545,4 +551,57 @@ fun Route.overview() {
|
|||
call.respondRedirect("$prefix/")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
get("/announcement") {
|
||||
authenticateOrRedirect(Permission.POST) {
|
||||
respondMain {
|
||||
content {
|
||||
h1 { +"Ankündigungsbanner bearbeiten" }
|
||||
form(method = FormMethod.post) {
|
||||
div("form-group") {
|
||||
label {
|
||||
htmlFor = "announcement"
|
||||
+"Ankündigung"
|
||||
}
|
||||
input(
|
||||
name = "announcement",
|
||||
classes = "form-control"
|
||||
) {
|
||||
id = "announcement"
|
||||
placeholder = "Ankündigung"
|
||||
value = AnnouncementRepository.getAnnouncement()
|
||||
}
|
||||
}
|
||||
|
||||
div("form-group") {
|
||||
a("$prefix/") {
|
||||
button(classes = "form-btn") {
|
||||
+"Abbrechen"
|
||||
}
|
||||
}
|
||||
button(type = ButtonType.submit, classes = "form-btn btn-primary") {
|
||||
+"Speichern"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
post("/announcement") {
|
||||
authenticateOrRedirect(Permission.POST) {
|
||||
val params = call.receiveParameters().toMap().mapValues { (_, list) ->
|
||||
list.firstOrNull()
|
||||
}
|
||||
|
||||
val announcement = params["announcement"] ?: return@post
|
||||
|
||||
AnnouncementRepository.setAnnouncement(announcement)
|
||||
|
||||
call.respondRedirect("$prefix/")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
39
src/jvmMain/kotlin/de/kif/backend/route/api/Announcement.kt
Normal file
39
src/jvmMain/kotlin/de/kif/backend/route/api/Announcement.kt
Normal file
|
@ -0,0 +1,39 @@
|
|||
package de.kif.backend.route.api
|
||||
|
||||
import de.kif.backend.authenticate
|
||||
import de.kif.backend.repository.AnnouncementRepository
|
||||
import de.kif.backend.repository.RoomRepository
|
||||
import de.kif.common.model.Permission
|
||||
import de.kif.common.model.Room
|
||||
import io.ktor.application.call
|
||||
import io.ktor.http.HttpStatusCode
|
||||
import io.ktor.request.receive
|
||||
import io.ktor.routing.Route
|
||||
import io.ktor.routing.get
|
||||
import io.ktor.routing.post
|
||||
|
||||
fun Route.announcementApi() {
|
||||
get("/api/announcement") {
|
||||
try {
|
||||
call.success(AnnouncementRepository.getAnnouncement())
|
||||
} catch (_: Exception) {
|
||||
call.error(HttpStatusCode.InternalServerError)
|
||||
}
|
||||
}
|
||||
|
||||
post("/api/announcement") {
|
||||
try {
|
||||
authenticate(Permission.POST) {
|
||||
val announcement = call.receive<String>()
|
||||
|
||||
AnnouncementRepository.setAnnouncement(announcement)
|
||||
|
||||
call.success()
|
||||
} onFailure {
|
||||
call.error(HttpStatusCode.Unauthorized)
|
||||
}
|
||||
} catch (_: Exception) {
|
||||
call.error(HttpStatusCode.InternalServerError)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -40,6 +40,7 @@ data class Backup(
|
|||
RepositoryType.USER -> backup.copy(users = UserRepository.all())
|
||||
RepositoryType.WORK_GROUP -> backup.copy(workGroups = WorkGroupRepository.all())
|
||||
RepositoryType.POST -> backup.copy(posts = PostRepository.all())
|
||||
else -> backup
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -32,7 +32,7 @@ object PushService {
|
|||
fun getMessages(timestamp: Long?): MessageBox {
|
||||
return MessageBox(
|
||||
System.currentTimeMillis(),
|
||||
true,
|
||||
timestamp != null && timestamp > leastValidTimestamp,
|
||||
emptyList()
|
||||
)
|
||||
}
|
||||
|
@ -64,4 +64,5 @@ fun Route.pushService() {
|
|||
UserRepository.registerPushService()
|
||||
WorkGroupRepository.registerPushService()
|
||||
PostRepository.registerPushService()
|
||||
AnnouncementRepository.registerPushService()
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@ package de.kif.backend.view
|
|||
import de.kif.backend.PortalSession
|
||||
import de.kif.backend.Resources
|
||||
import de.kif.backend.prefix
|
||||
import de.kif.backend.repository.AnnouncementRepository
|
||||
import de.kif.common.model.User
|
||||
import io.ktor.application.ApplicationCall
|
||||
import io.ktor.application.call
|
||||
|
@ -79,6 +80,19 @@ class MainTemplate(
|
|||
insert(MenuTemplate(url, user)) {}
|
||||
}
|
||||
|
||||
if (!noMenu) {
|
||||
val announcement = AnnouncementRepository.getAnnouncement()
|
||||
var classes = "announcement"
|
||||
if (announcement.isBlank()) {
|
||||
classes += " announcement-blank"
|
||||
}
|
||||
div(classes) {
|
||||
span {
|
||||
+announcement
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val containerClasses = if (stretch) "container-full" else "container"
|
||||
div(containerClasses) {
|
||||
div("main") {
|
||||
|
|
|
@ -9,6 +9,7 @@ web = "web"
|
|||
sessions = "data/sessions"
|
||||
uploads = "data/uploads"
|
||||
database = "data/portal.db"
|
||||
announcement = "data/announcement.txt"
|
||||
|
||||
[schedule]
|
||||
reference = "1970-01-01"
|
||||
|
|
Loading…
Reference in a new issue