index(UserSession::user)
})
}
-
-/*
-@Serializable
-data class PrivateMessage(
- @SerialName("_id")
- override val id: Id<PrivateMessage> = Id(),
- val sender: Id<User>,
- val receiver: Id<User>,
- val subject: String,
- val message: String,
- val sentAt: @Contextual Instant,
- val isRead: Boolean
-) : DataDocument<PrivateMessage> {
- companion object Table : DocumentTable<PrivateMessage> by DocumentTable.create({
- index(PrivateMessage::sender)
- index(PrivateMessage::receiver)
- index(PrivateMessage::sentAt)
- })
-}
-*/
@Serializable
sealed class UserTrophy : Comparable<UserTrophy> {
- protected abstract fun ASIDE.render()
- fun renderInto(sidebar: ASIDE) = sidebar.render()
+ protected abstract fun TagConsumer<*>.render()
+ fun renderInto(consumer: TagConsumer<*>) = consumer.render()
// Higher rank = lower on page
protected abstract val rank: Int
}
}
-fun ASIDE.renderTrophy(trophy: UserTrophy) = trophy.renderInto(this)
+fun TagConsumer<*>.renderTrophy(trophy: UserTrophy) = trophy.renderInto(this)
@Serializable
object SiteOwnerTrophy : UserTrophy() {
- override fun ASIDE.render() {
+ override fun TagConsumer<*>.render() {
p {
style = "text-align:center;border:2px solid #a82;padding:3px;background-color:#fc3;color:#541;font-variant:small-caps;font-family:'JetBrains Mono',monospace"
+"Site Owner"
@Serializable
object SiteDeveloperTrophy : UserTrophy() {
- override fun ASIDE.render() {
+ override fun TagConsumer<*>.render() {
p {
style = "text-align:center;border:2px solid #62a;padding:3px;background-color:#93f;color:#315;font-variant:small-caps;font-family:'JetBrains Mono',monospace"
title = "This person helps with coding the game"
@Serializable
data class SiteJanitorTrophy(val isSenior: Boolean) : UserTrophy() {
- override fun ASIDE.render() {
+ override fun TagConsumer<*>.render() {
p {
style = "text-align:center;border:2px solid #840;padding:3px;background-color:#c60;color:#420;font-variant:small-caps;font-family:'JetBrains Mono',monospace"
title = "This person helps with cleaning the poo out of the site"
@Serializable
data class SiteSupporterTrophy(val amountInUsCents: Int) : UserTrophy() {
- override fun ASIDE.render() {
+ override fun TagConsumer<*>.render() {
p {
style = "text-align:center;border:2px solid #694;padding:3px;background-color:#af7;color:#231;font-variant:small-caps;font-family:'JetBrains Mono',monospace"
title = "\"I spent money on an online game and all I got was this lousy trophy!\""
package starshipfights.info
-import kotlinx.html.A
-import kotlinx.html.FORM
-import kotlinx.html.hiddenInput
+import kotlinx.html.*
import starshipfights.auth.CsrfProtector
import starshipfights.data.Id
import starshipfights.data.auth.UserSession
name = CsrfProtector.csrfInputName
value = CsrfProtector.newNonce(cookie, this@csrfToken.action)
}
+
+fun interface SECTIONS {
+ fun section(classes: String?, body: SECTION.() -> Unit)
+ fun section(body: SECTION.() -> Unit) = section(null, body)
+}
+
+fun MAIN.sectioned(): SECTIONS = MainSections(this)
+
+private class MainSections(private val delegate: MAIN) : SECTIONS {
+ override fun section(classes: String?, body: SECTION.() -> Unit) {
+ delegate.section(classes, body)
+ }
+}
import starshipfights.game.getDefiniteShortName
abstract class Sidebar {
- protected abstract fun ASIDE.display()
- fun displayIn(aside: ASIDE) = aside.display()
+ protected abstract fun TagConsumer<*>.display()
+ fun displayIn(aside: ASIDE) = aside.consumer.display()
}
-class CustomSidebar(private val block: ASIDE.() -> Unit) : Sidebar() {
- override fun ASIDE.display() = block()
+class CustomSidebar(private val block: TagConsumer<*>.() -> Unit) : Sidebar() {
+ override fun TagConsumer<*>.display() = block()
}
data class ShipViewSidebar(val shipType: ShipType) : Sidebar() {
- override fun ASIDE.display() {
+ override fun TagConsumer<*>.display() {
p {
img(alt = "Flag of ${shipType.faction.getDefiniteShortName()}", src = shipType.faction.flagUrl)
}
}
data class PageNavSidebar(val contents: List<NavItem>) : Sidebar() {
- override fun ASIDE.display() {
+ override fun TagConsumer<*>.display() {
div(classes = "list") {
contents.forEach {
div(classes = "item") {
}
}
-data class NavLink(val to: String, val text: String, val isPost: Boolean = false, val csrfUserCookie: Id<UserSession>? = null) : NavItem() {
+data class NavLink(val to: String, val text: String, val classes: String? = null, val isPost: Boolean = false, val csrfUserCookie: Id<UserSession>? = null) : NavItem() {
override fun DIV.display() {
- a(href = to) {
+ a(href = to, classes = classes) {
if (isPost)
method = "post"
csrfUserCookie?.let { csrfToken(it) }
listOf(
NavLink("/me", user.profileName),
NavLink("/me/manage", "User Preferences"),
- /*NavLink(
- "/me/inbox", "Inbox (${
- PrivateMessage.number(
- and(
- PrivateMessage::receiver eq user.id,
- PrivateMessage::isRead eq false
- )
- )
- })"
- ),*/
- NavLink("/lobby", "Enter Game Lobby"),
+ NavLink("/lobby", "Enter Game Lobby", classes = "desktop"),
NavLink("/logout", "Log Out", isPost = true, csrfUserCookie = session.id),
)
} + listOf(
import kotlinx.html.*
-fun page(pageTitle: String? = null, navBar: List<NavItem>? = null, sidebar: Sidebar? = null, content: MAIN.() -> Unit): HTML.() -> Unit = {
+fun page(pageTitle: String? = null, navBar: List<NavItem>? = null, sidebar: Sidebar? = null, content: SECTIONS.() -> Unit): HTML.() -> Unit = {
head {
meta(charset = "utf-8")
+ meta(name = "viewport", content = "width=device-width, initial-scale=1.0")
link(rel = "icon", type = "image/svg+xml", href = "/static/images/icon.svg")
link(rel = "preconnect", href = "https://fonts.googleapis.com")
div { id = "bg" }
navBar?.let {
- nav {
+ nav(classes = "desktop") {
div(classes = "list") {
it.forEach {
div(classes = "item") {
}
sidebar?.let {
- aside {
+ aside(classes = "desktop") {
it.displayIn(this)
}
}
main {
- content()
+ sidebar?.let {
+ aside(classes = "mobile") {
+ it.displayIn(this)
+ }
+ }
+
+ with(sectioned()) {
+ content()
+ }
+
+ navBar?.let {
+ nav(classes = "mobile") {
+ div(classes = "list") {
+ it.forEach {
+ div(classes = "item") {
+ it.displayIn(this)
+ }
+ }
+ }
+ }
+ }
}
script(src = "/static/init.js") {}
import kotlinx.html.*
import starshipfights.CurrentConfiguration
-private fun MAIN.devModeCallId(callId: String?) {
+private fun SECTIONS.devModeCallId(callId: String?) {
callId?.let { id ->
section {
style = if (CurrentConfiguration.isDevEnv) "" else "display:none"
html {
margin: 0;
- padding: 20px 5vw;
+ padding: 0;
color: #222;
+ background-color: #aaa;
font-family: 'Noto Sans', sans-serif;
font-size: 100%;
+
+ --h1-size: 1.6em;
+ --h2-size: 1.4em;
+ --h3-size: 1.2em;
}
::selection {
}
div#bg {
- width: 100%;
- height: 100%;
- position: fixed;
- top: 0;
- left: 0;
-
- background-image: url("/static/images/background.svg");
- background-attachment: fixed;
- background-position: center;
- background-size: cover;
-
- filter: blur(4px);
-
- z-index: 0;
+ display: none;
}
h1, h2, h3 {
}
h1 {
- border: 3px solid #888;
- box-shadow: inset 0 0 0 4px #444;
- padding: 5px;
+ border: 0.1875rem solid #888;
+ box-shadow: inset 0 0 0 0.25rem #444;
+ padding: 0.3125rem;
background-color: #aaa;
font-variant: small-caps;
- font-size: 2.6em;
+ font-size: var(--h1-size);
font-weight: 800;
}
h2 {
- border-bottom: 2px solid #666;
- font-size: 2.2em;
+ border-bottom: 0.125rem solid #666;
+ font-size: var(--h2-size);
font-weight: 600;
}
h3 {
text-decoration: underline;
text-decoration-color: #888;
- font-size: 1.8em;
+ font-size: var(--h3-size);
font-weight: 400;
}
-main {
- padding: 5vh 0;
+.desktop {
+ display: none;
}
/*noinspection CssOverwrittenProperties*/
-main > section, nav, aside {
+main > section, main > nav.mobile, main > aside.mobile {
border-image-source: url("/static/images/panel.svg");
border-image-slice: 40% fill;
- border-image-width: 2em;
- border-width: 2em;
+ border-image-width: 1em;
+ border-width: 1em;
+
+ padding: 1.5em 1.5em;
box-sizing: border-box;
- padding: 2.5em 3em;
+ width: 90vw;
+ margin: 5vw 5vw;
position: relative;
z-index: 1;
}
-main > section {
- width: 40%;
- margin: 5vh auto;
-}
+@media only screen and (min-width: 8in) {
+ html {
+ padding: 1.25rem 5vw;
-main > section:first-child {
- margin: 0 auto 5vh;
-}
+ --h1-size: 2.6em;
+ --h2-size: 2.2em;
+ --h3-size: 1.8em;
+ }
+
+ div#bg {
+ display: unset;
+
+ width: 100%;
+ height: 100%;
+ position: fixed;
+ top: 0;
+ left: 0;
+
+ background-image: url("/static/images/background.svg");
+ background-attachment: fixed;
+ background-position: center;
+ background-size: cover;
+
+ filter: blur(0.25rem);
+
+ z-index: 0;
+ }
-nav {
- width: 20%;
- float: left;
- margin: 5vh 5vw;
+ main {
+ padding: 5vh 0;
+ }
+
+ /*noinspection CssOverwrittenProperties*/
+ main > section, nav.desktop, aside.desktop {
+ border-image-source: url("/static/images/panel.svg");
+ border-image-slice: 40% fill;
+ border-image-width: 2em;
+ border-width: 2em;
+
+ box-sizing: border-box;
+ padding: 2.5em 3em;
+
+ position: relative;
+ z-index: 1;
+ }
+
+ main > section {
+ width: 40%;
+ margin: 5vh auto;
+ }
+
+ main > section:first-of-type {
+ margin: 0 auto 5vh;
+ }
+
+ .mobile {
+ display: none;
+ }
+
+ .desktop {
+ display: unset;
+ }
+
+ nav.desktop {
+ width: 20%;
+ float: left;
+ margin: 5vh 5vw;
+ }
+
+ aside.desktop {
+ width: 20%;
+ float: right;
+ margin: 5vh 5vw;
+ }
+
+ aside.desktop img {
+ width: 100%;
+ }
}
div.list {
vertical-align: middle;
text-align: center;
- border-radius: 5px;
+ border-radius: 0.3em;
color: #369;
text-decoration: none;
}
height: 1em;
}
-aside {
- width: 20%;
- float: right;
- margin: 5vh 5vw;
-}
-
-aside img {
- width: 100%;
-}
-
table {
table-layout: fixed;
border-collapse: collapse;
- border: 2pt solid #036;
+ border: 0.125rem solid #036;
background-color: #ccc;
width: 100%;
}
td {
- border: 2pt solid #036;
+ border: 0.125rem solid #036;
background-color: #eee;
font-size: 0.7em;
padding: 0.15em 0;
}
th {
- border: 2pt solid #036;
+ border: 0.125rem solid #036;
background-color: #036;
padding: 0.15em 0;
box-sizing: border-box;
background-color: #aaa;
border: none;
- border-bottom: 2px solid #222;
+ border-bottom: 0.1em solid #222;
color: #024;
font-size: 1.5em;
input[type=submit] {
background-color: #06c;
border: none;
- border-radius: 8pt;
+ border-radius: 0.3em;
color: #bdf;
cursor: pointer;
display: block;
input[type=submit].evil {
background-color: #c66;
border: none;
- border-radius: 8pt;
+ border-radius: 0.3em;
color: #fcc;
cursor: pointer;
display: block;