V1 von Brett her

This commit is contained in:
Lars Westermann 2019-06-08 18:12:01 +02:00
parent 17f81e8952
commit 2b29093086
Signed by: lars.westermann
GPG key ID: 9D417FA5BB9D5E1D
15 changed files with 223 additions and 244 deletions

View file

@ -99,10 +99,6 @@ kotlin {
implementation "com.soywiz:klock-jvm:$klockVersion" implementation "com.soywiz:klock-jvm:$klockVersion"
implementation "com.soywiz:klock-locale-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.github.uchuhimo:konf:master-SNAPSHOT'
implementation 'com.vladsch.flexmark:flexmark-all:0.42.10' implementation 'com.vladsch.flexmark:flexmark-all:0.42.10'
api 'io.github.microutils:kotlin-logging:1.6.23' api 'io.github.microutils:kotlin-logging:1.6.23'

View file

@ -1,5 +1,6 @@
$background-primary-color: #fff; $background-primary-color: #fff;
$background-secondary-color: #fcfcfc; $background-secondary-color: #f5f5f5;
$background-card-color: #fff;
$text-primary-color: #333; $text-primary-color: #333;
$text-secondary-color: rgba($text-primary-color, 0.5); $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-border-color: rgba($text-primary-color, 0.1);
$table-header-color: rgba($text-primary-color, 0.06); $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-focused: rgba($text-primary-color, 0.87);
$icon-color: rgba($text-primary-color, 0.54); $icon-color: rgba($text-primary-color, 0.54);
@ -30,6 +31,7 @@ $lever-enabled-color: $primary-color;
:root { :root {
--background-primary-color: $background-primary-color; --background-primary-color: $background-primary-color;
--background-secondary-color: $background-secondary-color; --background-secondary-color: $background-secondary-color;
--background-card-color: $background-card-color;
--text-primary-color: $text-primary-color; --text-primary-color: $text-primary-color;
--text-secondary-color: $text-secondary-color; --text-secondary-color: $text-secondary-color;

View file

@ -0,0 +1 @@

View file

@ -1,73 +1,124 @@
@import "../config"; @import "../config";
.board-header { .board-header {
line-height: 6rem; line-height: 3rem;
display: flex; flex-grow: 1;
padding: 0 2rem; font-family: "Bungee", sans-serif;
font-weight: normal;
font-size: 1.1rem;
padding-left: 0.3rem;
}
& > div { .board-card {
flex-grow: 1; background-color: var(--background-card-color);
font-family: "Bungee", sans-serif; box-shadow: 0 0.1rem 0.2rem var(--shadow-color);
font-weight: normal; border-radius: $border-radius;
font-size: 1.1rem; overflow: hidden;
text-align: center;
&:first-child {
text-align: left;
}
&:last-child {
text-align: right;
}
}
} }
.board { .board {
padding: 1rem 1rem 2rem; padding: 1rem 0.4rem 2rem;
display: flex; display: flex;
overflow: hidden; overflow: hidden;
height: calc(100vh - 6rem); height: calc(100vh - 0.5rem);
& > div { & > div {
flex-grow: 1; flex-grow: 1;
flex-basis: 0; 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 { .board-twitter {
width: 100%; .board-card {
position: relative; 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) { &:not(:last-child) {
border-bottom: solid 1px var(--table-border-color) border-bottom: solid 1px var(--table-border-color)
} }
} }
.board-schedule-room { .board-schedule-bottom {
display: block; display: block;
padding-left: 1rem; padding-left: 1rem;
line-height: 2rem; padding-right: 0.5rem;
} line-height: 1rem;
clear: both;
.board-schedule-time { & > span {
position: absolute;
top: 1rem; &:first-child {
right: 1rem; float: left;
line-height: 2rem; }
&:last-child {
float: right;
}
}
} }
.board-schedule-color { .board-schedule-color {
background-color: var(--primary-color); background-color: var(--primary-color);
position: absolute; position: absolute;
top: 3.25rem; top: 1.25rem;
left: 1rem; left: 0.6rem;
width: 0.5rem; width: 0.8rem;
height: 1.5rem; height: 0.8rem;
border-radius: 100%;
} }
.board-schedule-name { .board-schedule-name {
display: block; display: block;
padding-left: 1rem; 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;
} }

View file

@ -12,12 +12,10 @@
} }
.overview-side { .overview-side {
min-width: 20%; min-width: 30%;
} }
.overview-twitter { .overview-twitter {
height: 20rem;
background-color: #b3e6f9;
} }
.post { .post {
@ -34,6 +32,8 @@
color: var(--primary-color); color: var(--primary-color);
line-height: 2rem; line-height: 2rem;
padding: 0 1rem; padding: 0 1rem;
font-weight: bold;
font-family: 'Montserrat', sans-serif;
&:empty::before { &:empty::before {
display: block; display: block;

View file

@ -2,11 +2,12 @@
$background-primary-color: #2d2d2d; $background-primary-color: #2d2d2d;
$background-secondary-color: #373737; $background-secondary-color: #373737;
$background-card-color: rgba($text-primary-color, 0.06);
$text-primary-color: #fff; $text-primary-color: #fff;
$text-secondary-color: rgba($text-primary-color, 0.5); $text-secondary-color: rgba($text-primary-color, 0.5);
$primary-color: #dd213d; $primary-color: #ef5350;
$primary-text-color: #fff; $primary-text-color: #fff;
$error-color: #F00; $error-color: #F00;
@ -17,7 +18,7 @@ $input-border-color: #888;
$table-border-color: rgba($text-primary-color, 0.1); $table-border-color: rgba($text-primary-color, 0.1);
$table-header-color: rgba($text-primary-color, 0.06); $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-focused: rgba($text-primary-color, 1.0);
$icon-color: rgba($text-primary-color, 0.7); $icon-color: rgba($text-primary-color, 0.7);

View file

@ -2,6 +2,7 @@
$background-primary-color: #ffc3e1; $background-primary-color: #ffc3e1;
$background-secondary-color: #ffa5d2; $background-secondary-color: #ffa5d2;
$background-card-color: rgba($text-primary-color, 0.06);
$text-primary-color: #333; $text-primary-color: #333;
$text-secondary-color: rgba($text-primary-color, 0.5); $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-border-color: rgba($text-primary-color, 0.1);
$table-header-color: rgba($text-primary-color, 0.06); $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-focused: rgba($text-primary-color, 0.87);
$icon-color: rgba($text-primary-color, 0.54); $icon-color: rgba($text-primary-color, 0.54);

View file

@ -14,8 +14,8 @@ body, html {
color: var(--text-primary-color); color: var(--text-primary-color);
background: var(--background-secondary-color); background: var(--background-secondary-color);
font-family: 'Montserrat', Roboto, Arial, sans-serif; font-family: 'Raleway', 'Montserrat', Roboto, Arial, sans-serif;
font-weight: 600; font-weight: 500;
width: 100%; width: 100%;
height: 100%; height: 100%;

View file

@ -91,13 +91,11 @@ object Configuration {
} }
private object TwitterSpec : ConfigSpec("twitter") { private object TwitterSpec : ConfigSpec("twitter") {
val username by required<String>("username") val timeline by required<String>("timeline")
val password by required<String>("password")
} }
object Twitter { object Twitter {
val username by c(TwitterSpec.username) val timeline by c(TwitterSpec.timeline)
val password by c(TwitterSpec.password)
} }
init { init {

View file

@ -1,142 +0,0 @@
package de.kif.backend
import twitter4j.*
import twitter4j.conf.ConfigurationBuilder
/*
fun twitter() {
val msgQueue = LinkedBlockingQueue<String>(100000)
val eventQueue = LinkedBlockingQueue<Event>(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()
}

View file

@ -50,8 +50,11 @@ fun Route.account() {
a(href = "/account/backup", classes = "form-btn") { a(href = "/account/backup", classes = "form-btn") {
+"Backup" +"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") { get("/account/import") {
authenticateOrRedirect { user -> authenticateOrRedirect(Permission.ADMIN) { user ->
val tracks = TrackRepository.all() val tracks = TrackRepository.all()
val wikiSections = WikiImporter.loadSections() val wikiSections = WikiImporter.loadSections()

View file

@ -4,7 +4,6 @@ import de.kif.backend.Configuration
import de.kif.backend.repository.PostRepository import de.kif.backend.repository.PostRepository
import de.kif.backend.repository.ScheduleRepository import de.kif.backend.repository.ScheduleRepository
import de.kif.backend.view.respondMain import de.kif.backend.view.respondMain
import de.kif.common.formatDateTime
import de.kif.common.model.Schedule import de.kif.common.model.Schedule
import io.ktor.routing.Route import io.ktor.routing.Route
import io.ktor.routing.get import io.ktor.routing.get
@ -12,10 +11,11 @@ import kotlinx.css.CSSBuilder
import kotlinx.css.Color import kotlinx.css.Color
import kotlinx.html.div import kotlinx.html.div
import kotlinx.html.span import kotlinx.html.span
import kotlinx.html.unsafe
import java.util.* import java.util.*
fun Route.board() { fun Route.board() {
get("/board") { get("/brett") {
val postList = PostRepository.all().asReversed() val postList = PostRepository.all().asReversed()
val scheduleList = ScheduleRepository.all().map { val scheduleList = ScheduleRepository.all().map {
it to it.getAbsoluteStartTime() * 60 it to it.getAbsoluteStartTime() * 60
@ -24,8 +24,9 @@ fun Route.board() {
val referenceTime = Configuration.Schedule.referenceDate.time / 1000 val referenceTime = Configuration.Schedule.referenceDate.time / 1000
val now = referenceTime - (Date().time / 1000) val now = referenceTime - (Date().time / 1000)
respondMain(true, true) { respondMain(true, true) { theme ->
content { content {
/*
div("board-header") { div("board-header") {
div { div {
+"KIF 47.0" +"KIF 47.0"
@ -34,46 +35,96 @@ fun Route.board() {
+formatDateTime(Date().time) +formatDateTime(Date().time)
} }
} }
*/
div("board") { div("board") {
div("board-schedules") { div("board-schedules") {
attributes["data-reference"] = referenceTime.toString() attributes["data-reference"] = referenceTime.toString()
for ((schedule, time) in scheduleList) { div("board-header") {
div("board-schedule") { +"AKs"
attributes["data-id"] = schedule.id.toString() }
span("board-schedule-room") { div("board-schedule-box") {
+schedule.room.name for ((schedule, time) in scheduleList) {
} div("board-card board-schedule") {
span("board-schedule-time") { attributes["data-id"] = schedule.id.toString()
attributes["data-time"] = time.toString()
attributes["data-duration"] = schedule.workGroup.length.toString()
+Schedule.timeDifferenceToString(time + now) span("board-schedule-color") {
} attributes["style"] = CSSBuilder().apply {
span("board-schedule-color") { val c = schedule.workGroup.track?.color
attributes["style"] = CSSBuilder().apply { if (c != null) {
val c = schedule.workGroup.track?.color backgroundColor = Color(c.toString())
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-room") {
span("board-schedule-name") { +schedule.room.name
+schedule.workGroup.name }
}
} }
} }
} }
} }
div("board-posts") { div("board-posts") {
div("board-header") {
+"News"
}
for (post in postList) { for (post in postList) {
createPost(post, false, "board-post overview-post") createPost(post, false, "board-card board-post")
} }
} }
div("board-twitter") { div("board-twitter") {
div("board-header") {
+"Tweets"
}
div("board-card") {
unsafe {
raw(
"""
<a
class="twitter-timeline"
href="${Configuration.Twitter.timeline}"
data-chrome="transparent noheader nofooter"
data-theme="${if (theme.dark) "dark" else "light"}"
data-link-color="${theme.primaryColor}"
data-cards="hidden"
data-lang="de"
data-dnt="true"
>Tweets by kiforbiter</a>
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
""".trimIndent()
)
}
}
} }
} }
} }

View file

@ -77,7 +77,7 @@ fun Route.overview() {
val postList = PostRepository.all().asReversed() val postList = PostRepository.all().asReversed()
respondMain { respondMain { theme ->
content { content {
div("overview") { div("overview") {
div("overview-main") { div("overview-main") {
@ -94,7 +94,21 @@ fun Route.overview() {
} }
} }
div("overview-twitter") { div("overview-twitter") {
+"The Twitter Wall" unsafe {
raw("""
<a
class="twitter-timeline"
href="${Configuration.Twitter.timeline}"
data-chrome="transparent"
data-theme="${if (theme.dark) "dark" else "light"}"
data-link-color="${theme.primaryColor}"
data-cards="hidden"
data-lang="de"
data-dnt="true"
>Tweets by kiforbiter</a>
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
""".trimIndent())
}
} }
} }
} }

View file

@ -2,7 +2,6 @@ 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.authenticate
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
@ -34,7 +33,7 @@ class MainTemplate(
link(href = "/static/external/material-icons.css", type = LinkType.textCss, rel = LinkRel.stylesheet) 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 = "/static/external/font/Montserrat.css", type = LinkType.textCss, rel = LinkRel.stylesheet)
link( link(
href = "https://fonts.googleapis.com/css?family=Bungee|Oswald", href = "https://fonts.googleapis.com/css?family=Bungee|Oswald|Raleway",
type = LinkType.textCss, type = LinkType.textCss,
rel = LinkRel.stylesheet rel = LinkRel.stylesheet
) )
@ -50,6 +49,9 @@ class MainTemplate(
Theme.PRINCESS -> { Theme.PRINCESS -> {
link(href = "/static/style/princess.css", type = LinkType.textCss, rel = LinkRel.stylesheet) 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") {} script(src = "/static/require.min.js") {}
@ -79,8 +81,7 @@ class MainTemplate(
div("footer-credit") { div("footer-credit") {
} }
div("footer-theme") { div("footer-theme") {
for (it in Theme.values()) { for ((it, name) in Theme.displayThemes) {
val name = it.name.toLowerCase()
a("?theme=${it.name}", classes = if (theme == it) "selected" else "") { a("?theme=${it.name}", classes = if (theme == it) "selected" else "") {
id = "theme-$name" id = "theme-$name"
+name.capitalize() +name.capitalize()
@ -94,14 +95,19 @@ class MainTemplate(
} }
} }
enum class Theme { enum class Theme(val display: Boolean, val dark: Boolean, val primaryColor: String) {
LIGHT, DARK, PRINCESS; LIGHT(true, false, "#B11D33"),
DARK(true, true, "#ef5350"),
PRINCESS(true, false, "#B11D33"),
BRETT(false, false, "#B11D33");
companion object { 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 { 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<Unit, ApplicationCall>.respondMain( suspend fun PipelineContext<Unit, ApplicationCall>.respondMain(
noMenu: Boolean = false, noMenu: Boolean = false,
stretch: Boolean = false, stretch: Boolean = false,
body: MainTemplate.() -> Unit body: MainTemplate.(Theme) -> Unit
) { ) {
val param = call.request.queryParameters["theme"] val param = call.request.queryParameters["theme"]
val url = call.request.uri.substring(1) val url = call.request.uri.substring(1)
@ -124,15 +130,17 @@ suspend fun PipelineContext<Unit, ApplicationCall>.respondMain(
) )
call.respondRedirect(call.request.path()) call.respondRedirect(call.request.path())
} else { } else {
val theme = Theme.lookup(call.request.cookies["theme"])
call.respondHtmlTemplate( call.respondHtmlTemplate(
MainTemplate( MainTemplate(
Theme.lookup(call.request.cookies["theme"]), theme,
url, url,
user, user,
noMenu, noMenu,
stretch stretch
), )
body = body ) {
) body(theme)
}
} }
} }

View file

@ -1,5 +0,0 @@
package twitter4j
fun addTwitterStreamListener(stream: TwitterStream, listener: StatusListener) {
stream.addListener(listener)
}