diff --git a/build.gradle b/build.gradle index 97fff2d..c5e9b34 100644 --- a/build.gradle +++ b/build.gradle @@ -99,10 +99,6 @@ kotlin { implementation "com.soywiz:klock-jvm:$klockVersion" implementation "com.soywiz:klock-locale-jvm:$klockVersion" - //implementation 'com.twitter:hbc-core:2.2.0' - //implementation 'com.twitter:hbc-twitter4j:2.2.0' - api 'org.twitter4j:twitter4j-stream:4.0.1' - implementation 'com.github.uchuhimo:konf:master-SNAPSHOT' implementation 'com.vladsch.flexmark:flexmark-all:0.42.10' api 'io.github.microutils:kotlin-logging:1.6.23' diff --git a/src/jsMain/resources/style/_color.scss b/src/jsMain/resources/style/_color.scss index d00ab4c..2e1c177 100644 --- a/src/jsMain/resources/style/_color.scss +++ b/src/jsMain/resources/style/_color.scss @@ -1,5 +1,6 @@ $background-primary-color: #fff; -$background-secondary-color: #fcfcfc; +$background-secondary-color: #f5f5f5; +$background-card-color: #fff; $text-primary-color: #333; $text-secondary-color: rgba($text-primary-color, 0.5); @@ -15,7 +16,7 @@ $input-border-color: #888; $table-border-color: rgba($text-primary-color, 0.1); $table-header-color: rgba($text-primary-color, 0.06); -$shadow-color: rgba($text-primary-color, 0.8); +$shadow-color: rgba(#000, 0.3); $icon-color-focused: rgba($text-primary-color, 0.87); $icon-color: rgba($text-primary-color, 0.54); @@ -30,6 +31,7 @@ $lever-enabled-color: $primary-color; :root { --background-primary-color: $background-primary-color; --background-secondary-color: $background-secondary-color; + --background-card-color: $background-card-color; --text-primary-color: $text-primary-color; --text-secondary-color: $text-secondary-color; diff --git a/src/jsMain/resources/style/board.scss b/src/jsMain/resources/style/board.scss new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/src/jsMain/resources/style/board.scss @@ -0,0 +1 @@ + diff --git a/src/jsMain/resources/style/components/_board.scss b/src/jsMain/resources/style/components/_board.scss index 4718443..bff8f29 100644 --- a/src/jsMain/resources/style/components/_board.scss +++ b/src/jsMain/resources/style/components/_board.scss @@ -1,73 +1,124 @@ @import "../config"; .board-header { - line-height: 6rem; - display: flex; - padding: 0 2rem; + line-height: 3rem; + flex-grow: 1; + font-family: "Bungee", sans-serif; + font-weight: normal; + font-size: 1.1rem; + padding-left: 0.3rem; +} - & > div { - flex-grow: 1; - font-family: "Bungee", sans-serif; - font-weight: normal; - font-size: 1.1rem; - text-align: center; - - &:first-child { - text-align: left; - } - &:last-child { - text-align: right; - } - } +.board-card { + background-color: var(--background-card-color); + box-shadow: 0 0.1rem 0.2rem var(--shadow-color); + border-radius: $border-radius; + overflow: hidden; } .board { - padding: 1rem 1rem 2rem; + padding: 1rem 0.4rem 2rem; display: flex; overflow: hidden; - height: calc(100vh - 6rem); + height: calc(100vh - 0.5rem); & > div { flex-grow: 1; flex-basis: 0; + padding: 0 0.4rem; + + &:nth-child(1) { + flex-grow: 4; + + padding-left: 0.15rem; + padding-right: 0.15rem; + } + + &:nth-child(2) { + flex-grow: 3; + } + + &:nth-child(3) { + flex-grow: 3; + } } } -.board-schedule { - width: 100%; - position: relative; +.board-twitter { + .board-card { + height: calc(100% - 1.3rem); - padding: 1rem 1rem; + & > * { + margin-top: -1px !important; + } + } +} + +.board-post { + margin-bottom: 0.5rem; + + .post-name { + top: 0.5rem; + } + + padding-top: 2.5rem; + padding-bottom: 0.5rem; +} + +.board-schedule-box { + display: flex; + flex-wrap: wrap; +} + +.board-schedule { + position: relative; + flex-grow: 1; + flex-basis: 0; + min-width: 15rem; + + padding: 0.6rem; + margin: 0 0.25rem 0.5rem; &:not(:last-child) { border-bottom: solid 1px var(--table-border-color) } } -.board-schedule-room { +.board-schedule-bottom { display: block; padding-left: 1rem; - line-height: 2rem; -} + padding-right: 0.5rem; + line-height: 1rem; + clear: both; -.board-schedule-time { - position: absolute; - top: 1rem; - right: 1rem; - line-height: 2rem; + & > span { + + &:first-child { + float: left; + } + &:last-child { + float: right; + } + } } .board-schedule-color { background-color: var(--primary-color); position: absolute; - top: 3.25rem; - left: 1rem; - width: 0.5rem; - height: 1.5rem; + top: 1.25rem; + left: 0.6rem; + width: 0.8rem; + height: 0.8rem; + border-radius: 100%; } .board-schedule-name { display: block; padding-left: 1rem; - line-height: 2rem; + line-height: 1.3rem; + + font-family: 'Montserrat', sans-serif; + font-weight: 600; + padding-top: 0.35rem; + padding-bottom: 0.35rem; } \ No newline at end of file diff --git a/src/jsMain/resources/style/components/_overview.scss b/src/jsMain/resources/style/components/_overview.scss index 30d7a96..8ab9330 100644 --- a/src/jsMain/resources/style/components/_overview.scss +++ b/src/jsMain/resources/style/components/_overview.scss @@ -12,12 +12,10 @@ } .overview-side { - min-width: 20%; + min-width: 30%; } .overview-twitter { - height: 20rem; - background-color: #b3e6f9; } .post { @@ -34,6 +32,8 @@ color: var(--primary-color); line-height: 2rem; padding: 0 1rem; + font-weight: bold; + font-family: 'Montserrat', sans-serif; &:empty::before { display: block; diff --git a/src/jsMain/resources/style/dark.scss b/src/jsMain/resources/style/dark.scss index 4d7bf52..2d79406 100644 --- a/src/jsMain/resources/style/dark.scss +++ b/src/jsMain/resources/style/dark.scss @@ -2,11 +2,12 @@ $background-primary-color: #2d2d2d; $background-secondary-color: #373737; +$background-card-color: rgba($text-primary-color, 0.06); $text-primary-color: #fff; $text-secondary-color: rgba($text-primary-color, 0.5); -$primary-color: #dd213d; +$primary-color: #ef5350; $primary-text-color: #fff; $error-color: #F00; @@ -17,7 +18,7 @@ $input-border-color: #888; $table-border-color: rgba($text-primary-color, 0.1); $table-header-color: rgba($text-primary-color, 0.06); -$shadow-color: rgba($text-primary-color, 0.8); +$shadow-color: rgba(#000, 0.3); $icon-color-focused: rgba($text-primary-color, 1.0); $icon-color: rgba($text-primary-color, 0.7); diff --git a/src/jsMain/resources/style/princess.scss b/src/jsMain/resources/style/princess.scss index 28720dd..bbd22d0 100644 --- a/src/jsMain/resources/style/princess.scss +++ b/src/jsMain/resources/style/princess.scss @@ -2,6 +2,7 @@ $background-primary-color: #ffc3e1; $background-secondary-color: #ffa5d2; +$background-card-color: rgba($text-primary-color, 0.06); $text-primary-color: #333; $text-secondary-color: rgba($text-primary-color, 0.5); @@ -17,7 +18,7 @@ $input-border-color: #888; $table-border-color: rgba($text-primary-color, 0.1); $table-header-color: rgba($text-primary-color, 0.06); -$shadow-color: rgba($text-primary-color, 0.8); +$shadow-color: rgba(#000, 0.3); $icon-color-focused: rgba($text-primary-color, 0.87); $icon-color: rgba($text-primary-color, 0.54); diff --git a/src/jsMain/resources/style/style.scss b/src/jsMain/resources/style/style.scss index 021ae19..66b9dbd 100644 --- a/src/jsMain/resources/style/style.scss +++ b/src/jsMain/resources/style/style.scss @@ -14,8 +14,8 @@ body, html { color: var(--text-primary-color); background: var(--background-secondary-color); - font-family: 'Montserrat', Roboto, Arial, sans-serif; - font-weight: 600; + font-family: 'Raleway', 'Montserrat', Roboto, Arial, sans-serif; + font-weight: 500; width: 100%; height: 100%; diff --git a/src/jvmMain/kotlin/de/kif/backend/Configuration.kt b/src/jvmMain/kotlin/de/kif/backend/Configuration.kt index 53b4395..efcb9f7 100644 --- a/src/jvmMain/kotlin/de/kif/backend/Configuration.kt +++ b/src/jvmMain/kotlin/de/kif/backend/Configuration.kt @@ -91,13 +91,11 @@ object Configuration { } private object TwitterSpec : ConfigSpec("twitter") { - val username by required("username") - val password by required("password") + val timeline by required("timeline") } object Twitter { - val username by c(TwitterSpec.username) - val password by c(TwitterSpec.password) + val timeline by c(TwitterSpec.timeline) } init { diff --git a/src/jvmMain/kotlin/de/kif/backend/Twitter.kt b/src/jvmMain/kotlin/de/kif/backend/Twitter.kt deleted file mode 100644 index dd09637..0000000 --- a/src/jvmMain/kotlin/de/kif/backend/Twitter.kt +++ /dev/null @@ -1,142 +0,0 @@ -package de.kif.backend - -import twitter4j.* -import twitter4j.conf.ConfigurationBuilder - - -/* -fun twitter() { - val msgQueue = LinkedBlockingQueue(100000) - val eventQueue = LinkedBlockingQueue(1000) - - val hosts = HttpHosts(Constants.STREAM_HOST) - - val filterEndpoint = StatusesFilterEndpoint() - filterEndpoint.trackTerms(listOf("kif")) - - println(Configuration.Twitter.username) - println(Configuration.Twitter.password) - - val authentication = BasicAuth( - Configuration.Twitter.username, - Configuration.Twitter.password - ) - - val builder = ClientBuilder() - .name("kif-portal") - .hosts(hosts) - .authentication(authentication) - .endpoint(filterEndpoint) - .processor(StringDelimitedProcessor(msgQueue)) - .eventMessageQueue(eventQueue) - - val client = builder.build() - - val listener = object: StatusStreamHandler { - override fun onUnknownMessageType(msg: String?) { - println("onUnknownMessageType") - println(msg) - } - - override fun onDisconnectMessage(message: DisconnectMessage?) { - println("onDisconnectMessage") - println(message?.disconnectReason) - } - - override fun onStallWarningMessage(warning: StallWarningMessage?) { - println("onStallWarningMessage") - println(warning?.message) - } - - override fun onTrackLimitationNotice(numberOfLimitedStatuses: Int) { - println("onTrackLimitationNotice") - println(numberOfLimitedStatuses) - } - - override fun onStallWarning(warning: StallWarning?) { - println("onStallWarning") - println(warning?.message) - } - - override fun onException(ex: Exception?) { - println("onException") - ex?.printStackTrace() - } - - override fun onDeletionNotice(statusDeletionNotice: StatusDeletionNotice?) { - println("onDeletionNotice") - println(statusDeletionNotice?.statusId) - } - - override fun onStatus(status: Status?) { - println("onStatus") - println(status?.text) - } - - override fun onScrubGeo(userId: Long, upToStatusId: Long) { - println("onScrubGeo") - } - - } - - val t4jClient = Twitter4jStatusClient( - client, - msgQueue, - listOf(listener), - Executors.newFixedThreadPool(4) - ) - - t4jClient.connect() - - t4jClient.process() - - while (true) { - - } -} - */ - -fun twitter() { - val cb = ConfigurationBuilder() - cb.setDebugEnabled(true) - .setUser(Configuration.Twitter.username) - .setPassword(Configuration.Twitter.password) - - val listener = object : StatusListener { - - override fun onTrackLimitationNotice(numberOfLimitedStatuses: Int) { - println("onTrackLimitationNotice") - println(numberOfLimitedStatuses) - } - - override fun onStallWarning(warning: StallWarning?) { - println("onStallWarning") - println(warning?.message) - } - - override fun onException(ex: Exception?) { - println("onException") - ex?.printStackTrace() - } - - override fun onDeletionNotice(statusDeletionNotice: StatusDeletionNotice?) { - println("onDeletionNotice") - println(statusDeletionNotice?.statusId) - } - - override fun onStatus(status: Status?) { - println("onStatus") - println(status?.text) - } - - override fun onScrubGeo(userId: Long, upToStatusId: Long) { - println("onScrubGeo") - } - } - - val twitterStream = TwitterStreamFactory(cb.build()).instance - - addTwitterStreamListener(twitterStream, listener) - - twitterStream.sample() -} \ No newline at end of file diff --git a/src/jvmMain/kotlin/de/kif/backend/route/Account.kt b/src/jvmMain/kotlin/de/kif/backend/route/Account.kt index 784f4d8..d44c97a 100644 --- a/src/jvmMain/kotlin/de/kif/backend/route/Account.kt +++ b/src/jvmMain/kotlin/de/kif/backend/route/Account.kt @@ -50,8 +50,11 @@ fun Route.account() { a(href = "/account/backup", classes = "form-btn") { +"Backup" } - a(href = "/account/import", classes = "form-btn") { - +"Import wiki" + + if (user.checkPermission(Permission.ADMIN)) { + a(href = "/account/import", classes = "form-btn") { + +"Import wiki" + } } } } @@ -164,7 +167,7 @@ fun Route.account() { } get("/account/import") { - authenticateOrRedirect { user -> + authenticateOrRedirect(Permission.ADMIN) { user -> val tracks = TrackRepository.all() val wikiSections = WikiImporter.loadSections() diff --git a/src/jvmMain/kotlin/de/kif/backend/route/Board.kt b/src/jvmMain/kotlin/de/kif/backend/route/Board.kt index 59e9f5b..dc9fdad 100644 --- a/src/jvmMain/kotlin/de/kif/backend/route/Board.kt +++ b/src/jvmMain/kotlin/de/kif/backend/route/Board.kt @@ -4,7 +4,6 @@ import de.kif.backend.Configuration import de.kif.backend.repository.PostRepository import de.kif.backend.repository.ScheduleRepository import de.kif.backend.view.respondMain -import de.kif.common.formatDateTime import de.kif.common.model.Schedule import io.ktor.routing.Route import io.ktor.routing.get @@ -12,10 +11,11 @@ import kotlinx.css.CSSBuilder import kotlinx.css.Color import kotlinx.html.div import kotlinx.html.span +import kotlinx.html.unsafe import java.util.* fun Route.board() { - get("/board") { + get("/brett") { val postList = PostRepository.all().asReversed() val scheduleList = ScheduleRepository.all().map { it to it.getAbsoluteStartTime() * 60 @@ -24,8 +24,9 @@ fun Route.board() { val referenceTime = Configuration.Schedule.referenceDate.time / 1000 val now = referenceTime - (Date().time / 1000) - respondMain(true, true) { + respondMain(true, true) { theme -> content { + /* div("board-header") { div { +"KIF 47.0" @@ -34,46 +35,96 @@ fun Route.board() { +formatDateTime(Date().time) } } + */ div("board") { div("board-schedules") { attributes["data-reference"] = referenceTime.toString() - for ((schedule, time) in scheduleList) { - div("board-schedule") { - attributes["data-id"] = schedule.id.toString() + div("board-header") { + +"AKs" + } - span("board-schedule-room") { - +schedule.room.name - } - span("board-schedule-time") { - attributes["data-time"] = time.toString() - attributes["data-duration"] = schedule.workGroup.length.toString() + div("board-schedule-box") { + for ((schedule, time) in scheduleList) { + div("board-card board-schedule") { + attributes["data-id"] = schedule.id.toString() - +Schedule.timeDifferenceToString(time + now) - } - span("board-schedule-color") { - attributes["style"] = CSSBuilder().apply { - val c = schedule.workGroup.track?.color - if (c != null) { - backgroundColor = Color(c.toString()) + span("board-schedule-color") { + attributes["style"] = CSSBuilder().apply { + val c = schedule.workGroup.track?.color + if (c != null) { + backgroundColor = Color(c.toString()) + } + }.toString() + } + span("board-schedule-name") { + +schedule.workGroup.name + } + + div("board-schedule-bottom") { + span("board-schedule-time") { + attributes["data-time"] = time.toString() + attributes["data-duration"] = schedule.workGroup.length.toString() + + + val startTime = (time % MINUTES_OF_DAY).let { + if (it < 0) it + MINUTES_OF_DAY else it + } + val sm = (startTime % 60).toString().padStart(2, '0') + val sh = (startTime / 60).toString().padStart(2, '0') + val startTimeString = "$sh:$sm" + + val endTime = ((time + schedule.workGroup.length) % MINUTES_OF_DAY).let { + if (it < 0) it + MINUTES_OF_DAY else it + } + val em = (endTime % 60).toString().padStart(2, '0') + val eh = (endTime / 60).toString().padStart(2, '0') + val endTimeString = "$eh:$em" + + +"$startTimeString - $endTimeString" } - }.toString() - } - span("board-schedule-name") { - +schedule.workGroup.name + + span("board-schedule-room") { + +schedule.room.name + } + } } } } } div("board-posts") { + div("board-header") { + +"News" + } for (post in postList) { - createPost(post, false, "board-post overview-post") + createPost(post, false, "board-card board-post") } } div("board-twitter") { - + div("board-header") { + +"Tweets" + } + div("board-card") { + unsafe { + raw( + """ + + + """.trimIndent() + ) + } + } } } } diff --git a/src/jvmMain/kotlin/de/kif/backend/route/Overview.kt b/src/jvmMain/kotlin/de/kif/backend/route/News.kt similarity index 96% rename from src/jvmMain/kotlin/de/kif/backend/route/Overview.kt rename to src/jvmMain/kotlin/de/kif/backend/route/News.kt index e280ca4..a993680 100644 --- a/src/jvmMain/kotlin/de/kif/backend/route/Overview.kt +++ b/src/jvmMain/kotlin/de/kif/backend/route/News.kt @@ -77,7 +77,7 @@ fun Route.overview() { val postList = PostRepository.all().asReversed() - respondMain { + respondMain { theme -> content { div("overview") { div("overview-main") { @@ -94,7 +94,21 @@ fun Route.overview() { } } div("overview-twitter") { - +"The Twitter Wall" + unsafe { + raw(""" + + + """.trimIndent()) + } } } } diff --git a/src/jvmMain/kotlin/de/kif/backend/view/MainTemplate.kt b/src/jvmMain/kotlin/de/kif/backend/view/MainTemplate.kt index 52bfbe5..a0ce39c 100644 --- a/src/jvmMain/kotlin/de/kif/backend/view/MainTemplate.kt +++ b/src/jvmMain/kotlin/de/kif/backend/view/MainTemplate.kt @@ -2,7 +2,6 @@ package de.kif.backend.view import de.kif.backend.PortalSession import de.kif.backend.Resources -import de.kif.backend.authenticate import de.kif.common.model.User import io.ktor.application.ApplicationCall import io.ktor.application.call @@ -34,7 +33,7 @@ class MainTemplate( link(href = "/static/external/material-icons.css", type = LinkType.textCss, rel = LinkRel.stylesheet) link(href = "/static/external/font/Montserrat.css", type = LinkType.textCss, rel = LinkRel.stylesheet) link( - href = "https://fonts.googleapis.com/css?family=Bungee|Oswald", + href = "https://fonts.googleapis.com/css?family=Bungee|Oswald|Raleway", type = LinkType.textCss, rel = LinkRel.stylesheet ) @@ -50,6 +49,9 @@ class MainTemplate( Theme.PRINCESS -> { link(href = "/static/style/princess.css", type = LinkType.textCss, rel = LinkRel.stylesheet) } + Theme.BRETT -> { + link(href = "/static/style/board.css", type = LinkType.textCss, rel = LinkRel.stylesheet) + } } script(src = "/static/require.min.js") {} @@ -79,8 +81,7 @@ class MainTemplate( div("footer-credit") { } div("footer-theme") { - for (it in Theme.values()) { - val name = it.name.toLowerCase() + for ((it, name) in Theme.displayThemes) { a("?theme=${it.name}", classes = if (theme == it) "selected" else "") { id = "theme-$name" +name.capitalize() @@ -94,14 +95,19 @@ class MainTemplate( } } -enum class Theme { - LIGHT, DARK, PRINCESS; +enum class Theme(val display: Boolean, val dark: Boolean, val primaryColor: String) { + LIGHT(true, false, "#B11D33"), + DARK(true, true, "#ef5350"), + PRINCESS(true, false, "#B11D33"), + BRETT(false, false, "#B11D33"); companion object { - private val loopup = values().toList().associateBy { it.name } + private val lookup = values().toList().associateBy { it.name } + + val displayThemes = values().filter { it.display }.map { it to it.name.toLowerCase() } fun lookup(name: String?): Theme { - return loopup[(name ?: return LIGHT).toUpperCase()] ?: LIGHT + return lookup[(name ?: return LIGHT).toUpperCase()] ?: LIGHT } } } @@ -109,7 +115,7 @@ enum class Theme { suspend fun PipelineContext.respondMain( noMenu: Boolean = false, stretch: Boolean = false, - body: MainTemplate.() -> Unit + body: MainTemplate.(Theme) -> Unit ) { val param = call.request.queryParameters["theme"] val url = call.request.uri.substring(1) @@ -124,15 +130,17 @@ suspend fun PipelineContext.respondMain( ) call.respondRedirect(call.request.path()) } else { + val theme = Theme.lookup(call.request.cookies["theme"]) call.respondHtmlTemplate( MainTemplate( - Theme.lookup(call.request.cookies["theme"]), + theme, url, user, noMenu, stretch - ), - body = body - ) + ) + ) { + body(theme) + } } } diff --git a/src/jvmMain/kotlin/twitter4j/AddListener.kt b/src/jvmMain/kotlin/twitter4j/AddListener.kt deleted file mode 100644 index 567207f..0000000 --- a/src/jvmMain/kotlin/twitter4j/AddListener.kt +++ /dev/null @@ -1,5 +0,0 @@ -package twitter4j - -fun addTwitterStreamListener(stream: TwitterStream, listener: StatusListener) { - stream.addListener(listener) -}