summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJoffrey Bion <joffrey.bion@booking.com>2020-03-21 19:16:06 +0100
committerJoffrey Bion <joffrey.bion@booking.com>2020-03-21 19:16:06 +0100
commitd122a07affc084c255fd67ab3685b0f0ccea7e87 (patch)
treed860ac8b0d5424e06ddf428241ac7ddffdc260c6
parentCreate basic components for game list (diff)
downloadseven-wonders-d122a07affc084c255fd67ab3685b0f0ccea7e87.tar.gz
seven-wonders-d122a07affc084c255fd67ab3685b0f0ccea7e87.tar.bz2
seven-wonders-d122a07affc084c255fd67ab3685b0f0ccea7e87.zip
Convert Lobby from TypeScript react
-rw-r--r--sw-ui-kt/src/main/kotlin/org/luxons/sevenwonders/ui/components/Application.kt2
-rw-r--r--sw-ui-kt/src/main/kotlin/org/luxons/sevenwonders/ui/components/lobby/Lobby.kt68
-rw-r--r--sw-ui-kt/src/main/kotlin/org/luxons/sevenwonders/ui/components/lobby/RadialList.kt121
-rw-r--r--sw-ui-kt/src/main/kotlin/org/luxons/sevenwonders/ui/components/lobby/RadialMath.kt54
-rw-r--r--sw-ui-kt/src/main/kotlin/org/luxons/sevenwonders/ui/components/lobby/RadialPlayerList.kt106
-rw-r--r--sw-ui-kt/src/main/kotlin/org/luxons/sevenwonders/ui/redux/sagas/GameBrowserSagas.kt2
-rw-r--r--sw-ui-kt/src/main/kotlin/org/luxons/sevenwonders/ui/router/Router.kt4
-rw-r--r--sw-ui-kt/src/main/resources/images/round-table.pngbin0 -> 18527 bytes
8 files changed, 347 insertions, 10 deletions
diff --git a/sw-ui-kt/src/main/kotlin/org/luxons/sevenwonders/ui/components/Application.kt b/sw-ui-kt/src/main/kotlin/org/luxons/sevenwonders/ui/components/Application.kt
index 68ec1409..5e602ae5 100644
--- a/sw-ui-kt/src/main/kotlin/org/luxons/sevenwonders/ui/components/Application.kt
+++ b/sw-ui-kt/src/main/kotlin/org/luxons/sevenwonders/ui/components/Application.kt
@@ -20,7 +20,7 @@ fun RBuilder.application() = hashRouter {
switch {
route("/games") { gameBrowser() }
route<IdProps>("/game/:id") { props -> gameScene(props.match.params.id) }
- route<IdProps>("/lobby/:id") { props -> lobby(props.match.params.id) }
+ route<IdProps>("/lobby") { lobby() }
route("/", exact = true) { home() }
redirect(from = "*", to = "/")
}
diff --git a/sw-ui-kt/src/main/kotlin/org/luxons/sevenwonders/ui/components/lobby/Lobby.kt b/sw-ui-kt/src/main/kotlin/org/luxons/sevenwonders/ui/components/lobby/Lobby.kt
index 9804f954..360937ed 100644
--- a/sw-ui-kt/src/main/kotlin/org/luxons/sevenwonders/ui/components/lobby/Lobby.kt
+++ b/sw-ui-kt/src/main/kotlin/org/luxons/sevenwonders/ui/components/lobby/Lobby.kt
@@ -1,13 +1,69 @@
package org.luxons.sevenwonders.ui.components.lobby
+import kotlinx.html.js.onClickFunction
+import org.luxons.sevenwonders.model.api.LobbyDTO
+import org.luxons.sevenwonders.model.api.PlayerDTO
+import org.luxons.sevenwonders.ui.redux.RequestStartGameAction
+import org.luxons.sevenwonders.ui.redux.connect
import react.RBuilder
+import react.RComponent
+import react.RProps
+import react.RState
import react.dom.*
-fun RBuilder.lobby(lobbyId: Long) = div {
- h1 {
- +"Lobby $lobbyId"
- }
- p {
- +"Can't wait to play!"
+interface LobbyStateProps : RProps {
+ var currentGame: LobbyDTO?
+ var currentPlayer: PlayerDTO?
+ var players: List<PlayerDTO>
+}
+
+interface LobbyDispatchProps : RProps {
+ var startGame: () -> Unit
+}
+
+interface LobbyProps : LobbyDispatchProps, LobbyStateProps
+
+class LobbyPresenter(props: LobbyProps) : RComponent<LobbyProps, RState>(props) {
+
+ override fun RBuilder.render() {
+ val currentGame = props.currentGame
+ val currentPlayer = props.currentPlayer
+ if (currentGame == null || currentPlayer == null) {
+ div { +"Error: no current game." }
+ return
+ }
+ div {
+ h2 { +"${currentGame.name} — Lobby" }
+ radialPlayerList(props.players)
+ if (currentPlayer.isGameOwner) {
+ // <Button text="START"
+ // className={Classes.LARGE}
+ // intent={Intent.PRIMARY}
+ // icon='play'
+ // onClick={startGame}
+ // disabled={players.size < 3}/>
+ button {
+ attrs {
+ onClickFunction = { props.startGame() }
+ disabled = props.players.size < 3
+ }
+ +"START"
+ }
+ }
+ }
}
}
+
+fun RBuilder.lobby() = lobby {}
+
+val lobby = connect<LobbyStateProps, LobbyDispatchProps, LobbyProps>(
+ clazz = LobbyPresenter::class,
+ mapStateToProps = { state, _ ->
+ currentGame = state.lobby
+ currentPlayer = state.player
+ players = state.lobby?.players ?: emptyList()
+ },
+ mapDispatchToProps = { dispatch, _ ->
+ startGame = { dispatch(RequestStartGameAction()) }
+ }
+)
diff --git a/sw-ui-kt/src/main/kotlin/org/luxons/sevenwonders/ui/components/lobby/RadialList.kt b/sw-ui-kt/src/main/kotlin/org/luxons/sevenwonders/ui/components/lobby/RadialList.kt
new file mode 100644
index 00000000..be3bb1de
--- /dev/null
+++ b/sw-ui-kt/src/main/kotlin/org/luxons/sevenwonders/ui/components/lobby/RadialList.kt
@@ -0,0 +1,121 @@
+package org.luxons.sevenwonders.ui.components.lobby
+
+import kotlinx.css.CSSBuilder
+import kotlinx.css.Display
+import kotlinx.css.ListStyleType
+import kotlinx.css.Position
+import kotlinx.css.display
+import kotlinx.css.height
+import kotlinx.css.left
+import kotlinx.css.listStyleType
+import kotlinx.css.margin
+import kotlinx.css.padding
+import kotlinx.css.pct
+import kotlinx.css.position
+import kotlinx.css.properties.Timing
+import kotlinx.css.properties.ms
+import kotlinx.css.properties.transform
+import kotlinx.css.properties.transition
+import kotlinx.css.properties.translate
+import kotlinx.css.px
+import kotlinx.css.top
+import kotlinx.css.width
+import kotlinx.css.zIndex
+import react.RBuilder
+import react.ReactElement
+import react.dom.*
+import styled.css
+import styled.styledDiv
+import styled.styledLi
+import styled.styledUl
+
+typealias ElementBuilder = RBuilder.() -> ReactElement
+
+fun RBuilder.radialList(
+ itemBuilders: List<ElementBuilder>,
+ centerElementBuilder: ElementBuilder,
+ itemWidth: Int,
+ itemHeight: Int,
+ options: RadialConfig = RadialConfig()
+): ReactElement {
+ val containerWidth = options.diameter + itemWidth
+ val containerHeight = options.diameter + itemHeight
+
+ return styledDiv {
+ css {
+ zeroMargins()
+ position = Position.relative
+ width = containerWidth.px
+ height = containerHeight.px
+ }
+ radialListItems(itemBuilders, options)
+ radialListCenter(centerElementBuilder)
+ }
+}
+
+private fun RBuilder.radialListItems(itemBuilders: List<ElementBuilder>, radialConfig: RadialConfig): ReactElement {
+ val offsets = offsetsFromCenter(itemBuilders.size, radialConfig)
+ return styledUl {
+ css {
+ zeroMargins()
+ transition(property = "all", duration = 500.ms, timing = Timing.easeInOut)
+ zIndex = 1
+ width = radialConfig.diameter.px
+ height = radialConfig.diameter.px
+ absoluteCenter()
+ }
+ itemBuilders.forEachIndexed { i, itemBuilder ->
+ radialListItem(itemBuilder, i, offsets[i])
+ }
+ }
+}
+
+private fun RBuilder.radialListItem(itemBuilder: ElementBuilder, i: Int, offset: CartesianCoords): ReactElement {
+ return styledLi {
+ css {
+ display = Display.block
+ position = Position.absolute
+ top = 50.pct
+ left = 50.pct
+ zeroMargins()
+ listStyleType = ListStyleType.unset
+ transition("all", 500.ms, Timing.easeInOut)
+ zIndex = 1
+ transform {
+ translate(offset.x.px, offset.y.px)
+ translate((-50).pct, (-50).pct)
+ }
+ }
+ attrs {
+ key = "$i"
+ }
+ itemBuilder()
+ }
+}
+
+private fun RBuilder.radialListCenter(centerElement: ElementBuilder?): ReactElement? {
+ if (centerElement == null) {
+ return null
+ }
+ return styledDiv {
+ css {
+ zIndex = 0
+ absoluteCenter()
+ }
+ centerElement()
+ }
+}
+
+private fun CSSBuilder.absoluteCenter() {
+ position = Position.absolute
+ left = 50.pct
+ top = 50.pct
+ transform {
+ translate((-50).pct, (-50).pct)
+ }
+}
+
+private fun CSSBuilder.zeroMargins() {
+ margin = "0"
+ padding = "0"
+}
diff --git a/sw-ui-kt/src/main/kotlin/org/luxons/sevenwonders/ui/components/lobby/RadialMath.kt b/sw-ui-kt/src/main/kotlin/org/luxons/sevenwonders/ui/components/lobby/RadialMath.kt
new file mode 100644
index 00000000..e4d51cf8
--- /dev/null
+++ b/sw-ui-kt/src/main/kotlin/org/luxons/sevenwonders/ui/components/lobby/RadialMath.kt
@@ -0,0 +1,54 @@
+package org.luxons.sevenwonders.ui.components.lobby
+
+import kotlin.math.PI
+import kotlin.math.cos
+import kotlin.math.roundToInt
+import kotlin.math.sin
+
+data class CartesianCoords(
+ val x: Int,
+ val y: Int
+)
+
+data class PolarCoords(
+ val radius: Int,
+ val angleDeg: Int
+)
+
+private fun Int.toRadians() = (this * PI / 180.0)
+private fun Double.project(angleRad: Double, trigFn: (Double) -> Double) = (this * trigFn(angleRad)).roundToInt()
+private fun Double.xProjection(angleRad: Double) = project(angleRad, ::cos)
+private fun Double.yProjection(angleRad: Double) = project(angleRad, ::sin)
+
+private fun PolarCoords.toCartesian() = CartesianCoords(
+ x = radius.toDouble().xProjection(angleDeg.toRadians()),
+ y = radius.toDouble().yProjection(angleDeg.toRadians())
+)
+
+enum class Direction(private val value: Int) {
+ CLOCKWISE(-1),
+ COUNTERCLOCKWISE(1);
+
+ fun toOrientedDegrees(deg: Int) = value * deg
+}
+
+data class RadialConfig(
+ val radius: Int = 120,
+ val arcDegrees: Int = 360, // full circle
+ val offsetDegrees: Int = 0, // 12 o'clock
+ val direction: Direction = Direction.CLOCKWISE
+) {
+ val diameter: Int = radius * 2
+}
+
+private const val DEFAULT_START = 90; // Up
+
+fun offsetsFromCenter(nbItems: Int, radialConfig: RadialConfig = RadialConfig()): List<CartesianCoords> =
+ (0 until nbItems).map { itemCartesianOffsets(it, nbItems, radialConfig) }
+
+private fun itemCartesianOffsets(index: Int, nbItems: Int, config: RadialConfig): CartesianCoords {
+ val startAngle = DEFAULT_START + config.direction.toOrientedDegrees(config.offsetDegrees)
+ val angleStep = config.arcDegrees / nbItems
+ val itemAngle = startAngle + config.direction.toOrientedDegrees(angleStep) * index
+ return PolarCoords(config.radius, itemAngle).toCartesian()
+}
diff --git a/sw-ui-kt/src/main/kotlin/org/luxons/sevenwonders/ui/components/lobby/RadialPlayerList.kt b/sw-ui-kt/src/main/kotlin/org/luxons/sevenwonders/ui/components/lobby/RadialPlayerList.kt
new file mode 100644
index 00000000..de6eabbd
--- /dev/null
+++ b/sw-ui-kt/src/main/kotlin/org/luxons/sevenwonders/ui/components/lobby/RadialPlayerList.kt
@@ -0,0 +1,106 @@
+package org.luxons.sevenwonders.ui.components.lobby
+
+import kotlinx.css.Align
+import kotlinx.css.FlexDirection
+import kotlinx.css.alignContent
+import kotlinx.css.flexDirection
+import kotlinx.css.margin
+import kotlinx.css.opacity
+import org.luxons.sevenwonders.model.api.PlayerDTO
+import react.RBuilder
+import react.ReactElement
+import react.dom.*
+import styled.css
+import styled.styledDiv
+import styled.styledH5
+
+fun RBuilder.radialPlayerList(players: List<PlayerDTO>): ReactElement {
+ val playerItemBuilders = players
+ .withUserFirst()
+ .growTo(targetSize = 3)
+ .map { p -> p.elementBuilder() }
+
+ val tableImgBuilder: ElementBuilder = { roundTableImg() }
+
+ return radialList(
+ itemBuilders = playerItemBuilders,
+ centerElementBuilder = tableImgBuilder,
+ itemWidth = 120,
+ itemHeight = 100,
+ options = RadialConfig(
+ radius = 175,
+ offsetDegrees = 180
+ )
+ )
+}
+
+private fun RBuilder.roundTableImg(): ReactElement = img {
+ attrs {
+ src = "images/round-table.png"
+ alt = "Round table"
+ width = "200"
+ height = "200"
+ }
+}
+
+private fun List<PlayerDTO>.withUserFirst(): List<PlayerDTO> {
+ val nonUsersBeginning = takeWhile { !it.isUser }
+ val userToEnd = subList(nonUsersBeginning.size, size)
+ return userToEnd + nonUsersBeginning
+}
+
+private fun <T> List<T>.growTo(targetSize: Int): List<T?> {
+ if (size >= targetSize) return this
+ return this + List(targetSize - size) { null }
+}
+
+private fun PlayerDTO?.elementBuilder(): ElementBuilder {
+ if (this == null) {
+ return { playerPlaceholder() }
+ } else {
+ return { playerItem(this@elementBuilder) }
+ }
+}
+
+private fun RBuilder.playerItem(player: PlayerDTO): ReactElement = styledDiv {
+ css {
+ flexDirection = FlexDirection.column
+ alignContent = Align.center
+ }
+ val title = if (player.isGameOwner) "Game owner" else null
+ userIcon(isUser = player.isUser, isOwner = player.isGameOwner, title = title)
+ styledH5 {
+ css {
+ margin = "0"
+ }
+ +player.displayName
+ }
+}
+
+private fun RBuilder.playerPlaceholder(): ReactElement = styledDiv {
+ css {
+ flexDirection = FlexDirection.column
+ alignContent = Align.center
+ opacity = 0.3
+ }
+ userIcon(isUser = false, isOwner = false, title = "Waiting for player...")
+ styledH5 {
+ css {
+ margin = "0"
+ }
+ +"?"
+ }
+}
+
+private fun RBuilder.userIcon(isUser: Boolean, isOwner: Boolean, title: String?): ReactElement {
+ // TODO
+ // const icon: IconName = isOwner ? 'badge' : 'user';
+ // const intent: Intent = isUser ? Intent.WARNING : Intent.NONE;
+ // return <Icon icon={icon} iconSize={50} intent={intent} title={title}/>;
+ val owner = if (isOwner) "(owner)" else ""
+ val user = if (isUser) "(me)" else ""
+ val hint = listOf(owner, user).joinToString(" ")
+ return span {
+ +hint
+ }
+}
diff --git a/sw-ui-kt/src/main/kotlin/org/luxons/sevenwonders/ui/redux/sagas/GameBrowserSagas.kt b/sw-ui-kt/src/main/kotlin/org/luxons/sevenwonders/ui/redux/sagas/GameBrowserSagas.kt
index 02c8f5f0..f2dd4e0f 100644
--- a/sw-ui-kt/src/main/kotlin/org/luxons/sevenwonders/ui/redux/sagas/GameBrowserSagas.kt
+++ b/sw-ui-kt/src/main/kotlin/org/luxons/sevenwonders/ui/redux/sagas/GameBrowserSagas.kt
@@ -52,6 +52,6 @@ private suspend fun SwSagaContext.handleGameJoined(
dispatch(EnterLobbyAction(lobby.id))
coroutineScope {
launch { lobbySaga(session, lobby.id) }
- Router.lobby(lobby.id)
+ Router.lobby()
}
}
diff --git a/sw-ui-kt/src/main/kotlin/org/luxons/sevenwonders/ui/router/Router.kt b/sw-ui-kt/src/main/kotlin/org/luxons/sevenwonders/ui/router/Router.kt
index fa768cbf..25cb2ec1 100644
--- a/sw-ui-kt/src/main/kotlin/org/luxons/sevenwonders/ui/router/Router.kt
+++ b/sw-ui-kt/src/main/kotlin/org/luxons/sevenwonders/ui/router/Router.kt
@@ -12,8 +12,8 @@ object Router {
push("/game/$id")
}
- fun lobby(id: Long) {
- push("/lobby/$id")
+ fun lobby() {
+ push("/lobby")
}
private fun push(path: String) {
diff --git a/sw-ui-kt/src/main/resources/images/round-table.png b/sw-ui-kt/src/main/resources/images/round-table.png
new file mode 100644
index 00000000..f277376d
--- /dev/null
+++ b/sw-ui-kt/src/main/resources/images/round-table.png
Binary files differ
bgstack15