Compare commits

...

2 commits

Author SHA1 Message Date
Lars Westermann
2b29093086
V1 von Brett her 2019-06-08 18:12:01 +02:00
Lars Westermann
17f81e8952
Split account pages 2019-06-08 14:56:47 +02:00
15 changed files with 257 additions and 253 deletions

View file

@ -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'

View file

@ -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;

View file

@ -0,0 +1 @@

View file

@ -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;
}

View file

@ -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;

View file

@ -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);

View file

@ -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);

View file

@ -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%;

View file

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

@ -2,18 +2,15 @@ package de.kif.backend.route
import de.kif.backend.authenticate
import de.kif.backend.authenticateOrRedirect
import de.kif.backend.util.Backup
import de.kif.backend.repository.TrackRepository
import de.kif.backend.repository.WorkGroupRepository
import de.kif.backend.route.api.error
import de.kif.backend.util.Backup
import de.kif.backend.util.WikiImporter
import de.kif.backend.view.MainTemplate
import de.kif.backend.view.MenuTemplate
import de.kif.backend.view.respondMain
import de.kif.common.RepositoryType
import de.kif.common.model.Permission
import io.ktor.application.call
import io.ktor.html.respondHtmlTemplate
import io.ktor.http.ContentType
import io.ktor.http.HttpStatusCode
import io.ktor.http.content.PartData
@ -35,10 +32,6 @@ private val logger = KotlinLogging.logger {}
fun Route.account() {
get("/account") {
authenticateOrRedirect { user ->
val tracks = TrackRepository.all()
val wikiSections = WikiImporter.loadSections()
respondMain {
content {
h1 { +"Account" }
@ -54,6 +47,26 @@ fun Route.account() {
}
}
a(href = "/account/backup", classes = "form-btn") {
+"Backup"
}
if (user.checkPermission(Permission.ADMIN)) {
a(href = "/account/import", classes = "form-btn") {
+"Import wiki"
}
}
}
}
}
}
get("/account/backup") {
authenticateOrRedirect { user ->
respondMain {
content {
h1 { +"Backup" }
div("account-backup") {
if (user.checkPermission(Permission.ROOM)) {
a("/account/backup/rooms.json", classes = "form-btn") {
@ -99,9 +112,11 @@ fun Route.account() {
}
if (user.checkPermission(Permission.ADMIN)) {
h1 { +"Restore" }
div("account-import") {
form(
action = "/account/import",
action = "/account/restore",
method = FormMethod.post,
encType = FormEncType.multipartFormData
) {
@ -146,12 +161,25 @@ fun Route.account() {
}
}
}
}
}
}
}
get("/account/import") {
authenticateOrRedirect(Permission.ADMIN) { user ->
val tracks = TrackRepository.all()
val wikiSections = WikiImporter.loadSections()
respondMain {
content {
h1 { +"Import wiki" }
if (user.checkPermission(Permission.ADMIN)) {
div("account-import-wiki") {
span { +"Import work group data from the kif wiki" }
form(action = "/account/import-wiki", method = FormMethod.post) {
form(action = "/account/import", method = FormMethod.post) {
for ((index, section) in wikiSections.withIndex()) {
div("form-group") {
label {
@ -277,7 +305,7 @@ fun Route.account() {
}
}
post("/account/import") {
post("/account/restore") {
authenticate(Permission.ADMIN) {
var reset = false
var import = ""
@ -308,7 +336,7 @@ fun Route.account() {
}
}
post("/account/import-wiki") {
post("/account/import") {
authenticate(Permission.ADMIN) {
val params = call.receiveParameters().toMap().mapValues { (_, list) ->
list.firstOrNull()

View file

@ -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(
"""
<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()
respondMain {
respondMain { theme ->
content {
div("overview") {
div("overview-main") {
@ -94,7 +94,21 @@ fun Route.overview() {
}
}
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.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<Unit, ApplicationCall>.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<Unit, ApplicationCall>.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)
}
}
}

View file

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