Add announcement
This commit is contained in:
parent
7ac2e1c208
commit
594ac544dd
|
@ -48,8 +48,8 @@ fun checkConstraints(
|
||||||
if (
|
if (
|
||||||
schedule != s &&
|
schedule != s &&
|
||||||
leader in s.workGroup.leader &&
|
leader in s.workGroup.leader &&
|
||||||
start <= s.getAbsoluteEndTime() &&
|
start < s.getAbsoluteEndTime() &&
|
||||||
s.getAbsoluteStartTime() <= end
|
s.getAbsoluteStartTime() < end
|
||||||
) {
|
) {
|
||||||
errors += ConstraintError("Work group with leader $leader cannot be at same time with ${s.workGroup.name}!")
|
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 {
|
enum class RepositoryType {
|
||||||
ROOM, SCHEDULE, TRACK, USER, WORK_GROUP, POST
|
ROOM, SCHEDULE, TRACK, USER, WORK_GROUP, POST, ANNOUNCEMENT
|
||||||
}
|
}
|
||||||
|
|
||||||
object Serialization {
|
object Serialization {
|
||||||
|
|
|
@ -95,7 +95,8 @@ class WebSocketClient {
|
||||||
TrackRepository.handler,
|
TrackRepository.handler,
|
||||||
UserRepository.handler,
|
UserRepository.handler,
|
||||||
WorkGroupRepository.handler,
|
WorkGroupRepository.handler,
|
||||||
PostRepository.handler
|
PostRepository.handler,
|
||||||
|
AnnouncementRepository.handler
|
||||||
)
|
)
|
||||||
|
|
||||||
init {
|
init {
|
||||||
|
|
|
@ -2,6 +2,7 @@ package de.kif.frontend
|
||||||
|
|
||||||
import de.kif.frontend.views.board.initBoard
|
import de.kif.frontend.views.board.initBoard
|
||||||
import de.kif.frontend.views.calendar.initCalendar
|
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.table.initTableLayout
|
||||||
import de.kif.frontend.views.initWorkGroupConstraints
|
import de.kif.frontend.views.initWorkGroupConstraints
|
||||||
import de.kif.frontend.views.overview.initOverviewMain
|
import de.kif.frontend.views.overview.initOverviewMain
|
||||||
|
@ -34,4 +35,7 @@ fun main() = init {
|
||||||
if (document.getElementsByClassName("board").length > 0) {
|
if (document.getElementsByClassName("board").length > 0) {
|
||||||
initBoard()
|
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;
|
margin-top: -1px !important;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.board-announcement {
|
||||||
|
position: fixed;
|
||||||
|
bottom: 3rem;
|
||||||
|
z-index: 10;
|
||||||
|
margin-bottom: 0 !important;
|
||||||
|
}
|
||||||
|
|
|
@ -182,3 +182,20 @@ body.offline {
|
||||||
display: block;
|
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()
|
workGroupApi()
|
||||||
constraintsApi()
|
constraintsApi()
|
||||||
postApi()
|
postApi()
|
||||||
|
announcementApi()
|
||||||
|
|
||||||
// Web socket push notifications
|
// Web socket push notifications
|
||||||
pushService()
|
pushService()
|
||||||
|
|
|
@ -42,6 +42,7 @@ object Configuration {
|
||||||
val sessions by required<String>()
|
val sessions by required<String>()
|
||||||
val uploads by required<String>()
|
val uploads by required<String>()
|
||||||
val database by required<String>()
|
val database by required<String>()
|
||||||
|
val announcement by required<String>()
|
||||||
}
|
}
|
||||||
|
|
||||||
object Path {
|
object Path {
|
||||||
|
@ -56,6 +57,9 @@ object Configuration {
|
||||||
|
|
||||||
val database by c(PathSpec.database)
|
val database by c(PathSpec.database)
|
||||||
val databasePath: java.nio.file.Path by lazy { Paths.get(database).toAbsolutePath() }
|
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") {
|
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
|
package de.kif.backend.route
|
||||||
|
|
||||||
import de.kif.backend.Configuration
|
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.RoomRepository
|
||||||
import de.kif.backend.repository.ScheduleRepository
|
import de.kif.backend.repository.ScheduleRepository
|
||||||
import de.kif.backend.view.respondMain
|
import de.kif.backend.view.respondMain
|
||||||
|
@ -87,6 +89,18 @@ fun Route.board() {
|
||||||
val nowLocale = now.time + timeOffset
|
val nowLocale = now.time + timeOffset
|
||||||
respondMain(true, true) { theme ->
|
respondMain(true, true) { theme ->
|
||||||
content {
|
content {
|
||||||
|
|
||||||
|
val announcement = AnnouncementRepository.getAnnouncement()
|
||||||
|
var classes = "board-announcement announcement"
|
||||||
|
if (announcement.isBlank()) {
|
||||||
|
classes += " announcement-blank"
|
||||||
|
}
|
||||||
|
div(classes) {
|
||||||
|
span {
|
||||||
|
+announcement
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
div("board") {
|
div("board") {
|
||||||
div("board-header") {
|
div("board-header") {
|
||||||
div("board-running") {
|
div("board-running") {
|
||||||
|
@ -142,7 +156,7 @@ fun Route.board() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
div("board-logo") {
|
div("board-logo") {
|
||||||
img("KIF 47.0", "/static/images/logo.svg")
|
img("KIF 47.0", "$prefix/static/images/logo.svg")
|
||||||
div("board-header-date") {
|
div("board-header-date") {
|
||||||
attributes["data-now"] = (now.time - timeOffset).toString()
|
attributes["data-now"] = (now.time - timeOffset).toString()
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package de.kif.backend.route
|
package de.kif.backend.route
|
||||||
|
|
||||||
import de.kif.backend.*
|
import de.kif.backend.*
|
||||||
|
import de.kif.backend.repository.AnnouncementRepository
|
||||||
import de.kif.backend.repository.PostRepository
|
import de.kif.backend.repository.PostRepository
|
||||||
import de.kif.backend.util.markdownToHtml
|
import de.kif.backend.util.markdownToHtml
|
||||||
import de.kif.backend.view.respondMain
|
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.forEachPart
|
||||||
import io.ktor.http.content.streamProvider
|
import io.ktor.http.content.streamProvider
|
||||||
import io.ktor.request.receiveMultipart
|
import io.ktor.request.receiveMultipart
|
||||||
|
import io.ktor.request.receiveParameters
|
||||||
import io.ktor.response.respond
|
import io.ktor.response.respond
|
||||||
import io.ktor.response.respondRedirect
|
import io.ktor.response.respondRedirect
|
||||||
import io.ktor.routing.Route
|
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 kotlinx.html.*
|
import kotlinx.html.*
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
|
||||||
|
@ -75,9 +78,12 @@ fun Route.overview() {
|
||||||
content {
|
content {
|
||||||
if (editable) {
|
if (editable) {
|
||||||
div("overview-new") {
|
div("overview-new") {
|
||||||
a("post/new", classes = "form-btn") {
|
a("$prefix/post/new", classes = "form-btn") {
|
||||||
+"Neuer Eintrag"
|
+"Neuer Eintrag"
|
||||||
}
|
}
|
||||||
|
a("$prefix/announcement", classes = "form-btn") {
|
||||||
|
+"Ankündigungsbanner bearbeiten"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
div("overview") {
|
div("overview") {
|
||||||
|
@ -545,4 +551,57 @@ fun Route.overview() {
|
||||||
call.respondRedirect("$prefix/")
|
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.USER -> backup.copy(users = UserRepository.all())
|
||||||
RepositoryType.WORK_GROUP -> backup.copy(workGroups = WorkGroupRepository.all())
|
RepositoryType.WORK_GROUP -> backup.copy(workGroups = WorkGroupRepository.all())
|
||||||
RepositoryType.POST -> backup.copy(posts = PostRepository.all())
|
RepositoryType.POST -> backup.copy(posts = PostRepository.all())
|
||||||
|
else -> backup
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -32,7 +32,7 @@ object PushService {
|
||||||
fun getMessages(timestamp: Long?): MessageBox {
|
fun getMessages(timestamp: Long?): MessageBox {
|
||||||
return MessageBox(
|
return MessageBox(
|
||||||
System.currentTimeMillis(),
|
System.currentTimeMillis(),
|
||||||
true,
|
timestamp != null && timestamp > leastValidTimestamp,
|
||||||
emptyList()
|
emptyList()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -64,4 +64,5 @@ fun Route.pushService() {
|
||||||
UserRepository.registerPushService()
|
UserRepository.registerPushService()
|
||||||
WorkGroupRepository.registerPushService()
|
WorkGroupRepository.registerPushService()
|
||||||
PostRepository.registerPushService()
|
PostRepository.registerPushService()
|
||||||
|
AnnouncementRepository.registerPushService()
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,7 @@ package de.kif.backend.view
|
||||||
import de.kif.backend.PortalSession
|
import de.kif.backend.PortalSession
|
||||||
import de.kif.backend.Resources
|
import de.kif.backend.Resources
|
||||||
import de.kif.backend.prefix
|
import de.kif.backend.prefix
|
||||||
|
import de.kif.backend.repository.AnnouncementRepository
|
||||||
import de.kif.common.model.User
|
import de.kif.common.model.User
|
||||||
import io.ktor.application.ApplicationCall
|
import io.ktor.application.ApplicationCall
|
||||||
import io.ktor.application.call
|
import io.ktor.application.call
|
||||||
|
@ -79,6 +80,19 @@ class MainTemplate(
|
||||||
insert(MenuTemplate(url, user)) {}
|
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"
|
val containerClasses = if (stretch) "container-full" else "container"
|
||||||
div(containerClasses) {
|
div(containerClasses) {
|
||||||
div("main") {
|
div("main") {
|
||||||
|
|
|
@ -9,6 +9,7 @@ web = "web"
|
||||||
sessions = "data/sessions"
|
sessions = "data/sessions"
|
||||||
uploads = "data/uploads"
|
uploads = "data/uploads"
|
||||||
database = "data/portal.db"
|
database = "data/portal.db"
|
||||||
|
announcement = "data/announcement.txt"
|
||||||
|
|
||||||
[schedule]
|
[schedule]
|
||||||
reference = "1970-01-01"
|
reference = "1970-01-01"
|
||||||
|
|
Loading…
Reference in a new issue