summaryrefslogtreecommitdiff
path: root/sw-ui/src/main/kotlin
diff options
context:
space:
mode:
authorJoffrey Bion <joffrey.bion@gmail.com>2023-02-01 03:48:39 +0100
committerJoffrey Bion <joffrey.bion@gmail.com>2023-02-01 03:48:39 +0100
commitd09c3e7128fbb8b9f1500153b12ef657dcb76694 (patch)
tree343a2ed29b989d1205490b38e30b43b68b0992ef /sw-ui/src/main/kotlin
parentUpgrade to BlueprintJS 4 (wrapper 7) (diff)
downloadseven-wonders-d09c3e7128fbb8b9f1500153b12ef657dcb76694.tar.gz
seven-wonders-d09c3e7128fbb8b9f1500153b12ef657dcb76694.tar.bz2
seven-wonders-d09c3e7128fbb8b9f1500153b12ef657dcb76694.zip
Migrate to new Kotlin/React API and Emotion styles
Diffstat (limited to 'sw-ui/src/main/kotlin')
-rw-r--r--sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/SevenWondersUi.kt36
-rw-r--r--sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/components/Application.kt65
-rw-r--r--sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/components/GlobalStyles.kt47
-rw-r--r--sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/components/errors/ErrorDialog.kt74
-rw-r--r--sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/components/game/Board.kt168
-rw-r--r--sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/components/game/BoardSummary.kt172
-rw-r--r--sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/components/game/CardImage.kt99
-rw-r--r--sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/components/game/GameScene.kt454
-rw-r--r--sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/components/game/GameStyles.kt102
-rw-r--r--sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/components/game/Hand.kt335
-rw-r--r--sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/components/game/HandRotationIndicator.kt45
-rw-r--r--sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/components/game/PlayerPreparedCard.kt82
-rw-r--r--sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/components/game/PlayerPreparedCardPresenter.kt79
-rw-r--r--sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/components/game/PreparedMove.kt73
-rw-r--r--sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/components/game/ScoreTable.kt195
-rw-r--r--sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/components/game/Tokens.kt145
-rw-r--r--sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/components/game/TransactionsSelector.kt189
-rw-r--r--sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/components/gameBrowser/CreateGameForm.kt83
-rw-r--r--sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/components/gameBrowser/GameBrowser.kt90
-rw-r--r--sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/components/gameBrowser/GameBrowserStyles.kt19
-rw-r--r--sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/components/gameBrowser/GameList.kt148
-rw-r--r--sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/components/gameBrowser/PlayerInfo.kt119
-rw-r--r--sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/components/home/ChooseNameForm.kt134
-rw-r--r--sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/components/home/Home.kt27
-rw-r--r--sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/components/home/HomeStyles.kt10
-rw-r--r--sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/components/lobby/Lobby.kt416
-rw-r--r--sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/components/lobby/LobbyStyles.kt14
-rw-r--r--sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/components/lobby/RadialList.kt84
-rw-r--r--sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/components/lobby/RadialPlayerList.kt108
-rw-r--r--sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/components/lobby/Table.kt115
-rw-r--r--sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/redux/Utils.kt54
-rw-r--r--sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/utils/StyleUtils.kt44
32 files changed, 1908 insertions, 1917 deletions
diff --git a/sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/SevenWondersUi.kt b/sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/SevenWondersUi.kt
index 44faede8..0bd3400e 100644
--- a/sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/SevenWondersUi.kt
+++ b/sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/SevenWondersUi.kt
@@ -5,35 +5,33 @@ import kotlinx.coroutines.*
import org.luxons.sevenwonders.ui.components.*
import org.luxons.sevenwonders.ui.redux.*
import org.luxons.sevenwonders.ui.redux.sagas.*
-import react.dom.*
+import react.*
+import react.dom.client.*
import react.redux.*
import redux.*
-import web.dom.*
import web.dom.document
+import web.html.*
fun main() {
- window.onload = {
- val rootElement = document.getElementById("root")
- if (rootElement != null) {
- initializeAndRender(rootElement)
- } else {
- console.error("Element with ID 'root' was not found, cannot bootstrap react app")
- }
+ window.onload = { init() }
+}
+
+private fun init() {
+ val rootElement = document.getElementById("root")
+ if (rootElement == null) {
+ console.error("Element with ID 'root' was not found, cannot bootstrap react app")
+ return
}
+ renderRoot(rootElement)
}
-private fun initializeAndRender(rootElement: Element) {
+private fun renderRoot(rootElement: HTMLElement) {
val store = initRedux()
-
- // With the new API this might look something like:
- // createRoot(rootElement).render(FC<Props> { .. }.create())
- // See: https://github.com/karakum-team/kotlin-mui-showcase/blob/main/src/main/kotlin/team/karakum/App.kt
- @Suppress("DEPRECATION")
- render(rootElement) {
- provider(store) {
- application()
- }
+ val connectedApp = Provider.create {
+ this.store = store
+ Application()
}
+ createRoot(rootElement).render(connectedApp)
}
@OptIn(DelicateCoroutinesApi::class)
diff --git a/sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/components/Application.kt b/sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/components/Application.kt
index 51e7e78f..9c210538 100644
--- a/sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/components/Application.kt
+++ b/sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/components/Application.kt
@@ -1,40 +1,43 @@
package org.luxons.sevenwonders.ui.components
-import org.luxons.sevenwonders.ui.components.errors.errorDialog
-import org.luxons.sevenwonders.ui.components.game.gameScene
-import org.luxons.sevenwonders.ui.components.gameBrowser.gameBrowser
-import org.luxons.sevenwonders.ui.components.home.home
-import org.luxons.sevenwonders.ui.components.lobby.lobby
-import org.luxons.sevenwonders.ui.router.SwRoute
-import react.Props
-import react.RBuilder
-import react.RElementBuilder
-import react.createElement
-import react.router.Navigate
-import react.router.Route
-import react.router.Routes
-import react.router.RoutesProps
+import org.luxons.sevenwonders.ui.components.errors.*
+import org.luxons.sevenwonders.ui.components.game.*
+import org.luxons.sevenwonders.ui.components.gameBrowser.*
+import org.luxons.sevenwonders.ui.components.home.*
+import org.luxons.sevenwonders.ui.components.lobby.*
+import org.luxons.sevenwonders.ui.router.*
+import react.*
+import react.router.*
import react.router.dom.*
-fun RBuilder.application() = HashRouter {
- errorDialog()
- Routes {
- route(SwRoute.GAME_BROWSER.path) { gameBrowser() }
- route(SwRoute.GAME.path) { gameScene() }
- route(SwRoute.LOBBY.path) { lobby() }
- route(SwRoute.HOME.path) { home() }
- route("*") {
- Navigate {
- attrs.to = "/"
- attrs.replace = true
+val Application = VFC("Application") {
+ HashRouter {
+ ErrorDialog()
+ Routes {
+ Route {
+ path = SwRoute.GAME_BROWSER.path
+ element = GameBrowser.create()
+ }
+ Route {
+ path = SwRoute.GAME.path
+ element = GameScene.create()
+ }
+ Route {
+ path = SwRoute.LOBBY.path
+ element = Lobby.create()
+ }
+ Route {
+ path = SwRoute.HOME.path
+ element = Home.create()
+ }
+ Route {
+ path = "*"
+ element = Navigate.create {
+ to = "/"
+ replace = true
+ }
}
}
}
}
-private fun RElementBuilder<RoutesProps>.route(path: String, render: RBuilder.() -> Unit) {
- Route {
- attrs.path = path
- attrs.element = createElement<Props>(render)
- }
-}
diff --git a/sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/components/GlobalStyles.kt b/sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/components/GlobalStyles.kt
index 9ba5951a..bd12537e 100644
--- a/sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/components/GlobalStyles.kt
+++ b/sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/components/GlobalStyles.kt
@@ -1,19 +1,20 @@
package org.luxons.sevenwonders.ui.components
-import kotlinx.css.*
-import kotlinx.css.properties.*
-import styled.StyleSheet
+import csstype.*
+import emotion.css.*
+import org.luxons.sevenwonders.ui.utils.*
-object GlobalStyles : StyleSheet("GlobalStyles", isStatic = true) {
+
+object GlobalStyles {
val preGameWidth = 60.rem
- val zeusBackground by css {
- background = "url('images/backgrounds/zeus-temple.jpg') center no-repeat"
- backgroundSize = "cover"
+ val zeusBackground = ClassName {
+ background = "url('images/backgrounds/zeus-temple.jpg') center no-repeat".unsafeCast<Background>()
+ backgroundSize = BackgroundSize.cover
}
- val fullscreen by css {
+ val fullscreen = ClassName {
position = Position.fixed
top = 0.px
left = 0.px
@@ -22,30 +23,26 @@ object GlobalStyles : StyleSheet("GlobalStyles", isStatic = true) {
overflow = Overflow.hidden
}
- val papyrusBackground by css {
- background = "url('images/backgrounds/papyrus.jpg')"
- backgroundSize = "cover"
+ val papyrusBackground = ClassName {
+ background = "url('images/backgrounds/papyrus.jpg')".unsafeCast<Background>()
+ backgroundSize = BackgroundSize.cover
}
- val fixedCenter by css {
- position = Position.fixed
- +centerLeftTopTransform
+ val centerLeftTopTransform = ClassName {
+ left = 50.pct
+ top = 50.pct
+ transform = translate((-50).pct, (-50).pct)
}
- val centerInPositionedParent by css {
- position = Position.absolute
- +centerLeftTopTransform
+ val fixedCenter = ClassName(centerLeftTopTransform) {
+ position = Position.fixed
}
- val centerLeftTopTransform by css {
- left = 50.pct
- top = 50.pct
- transform {
- translate((-50).pct, (-50).pct)
- }
+ val centerInPositionedParent = ClassName(centerLeftTopTransform) {
+ position = Position.absolute
}
- val noPadding by css {
- padding(all = 0.px)
+ val noPadding = ClassName {
+ padding = Padding(all = 0.px)
}
}
diff --git a/sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/components/errors/ErrorDialog.kt b/sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/components/errors/ErrorDialog.kt
index 5ff56055..5399f60d 100644
--- a/sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/components/errors/ErrorDialog.kt
+++ b/sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/components/errors/ErrorDialog.kt
@@ -6,44 +6,46 @@ import kotlinx.browser.*
import org.luxons.sevenwonders.ui.redux.*
import org.luxons.sevenwonders.ui.router.*
import react.*
-import react.dom.p
-import styled.*
+import react.dom.html.ReactHTML.p
+import react.redux.*
+import redux.*
-external interface ErrorDialogStateProps : PropsWithChildren {
- var errorMessage: String?
+val ErrorDialog = VFC {
+ val dispatch = useDispatch<RAction, WrapperAction>()
+
+ ErrorDialogPresenter {
+ errorMessage = useSwSelector { it.fatalError }
+ goHome = { dispatch(Navigate(SwRoute.HOME)) }
+ }
}
-external interface ErrorDialogDispatchProps : PropsWithChildren {
+private external interface ErrorDialogProps : Props {
+ var errorMessage: String?
var goHome: () -> Unit
}
-external interface ErrorDialogProps : ErrorDialogDispatchProps, ErrorDialogStateProps
+private val ErrorDialogPresenter = FC<ErrorDialogProps>("ErrorDialogPresenter") { props ->
+ val errorMessage = props.errorMessage
+ BpDialog {
+ isOpen = errorMessage != null
+ titleText = "Oops!"
+ icon = BpIcon.create {
+ icon = IconNames.ERROR
+ intent = Intent.DANGER
+ }
+ onClose = { goHomeAndRefresh() }
-class ErrorDialogPresenter(props: ErrorDialogProps) : RComponent<ErrorDialogProps, State>(props) {
- override fun RBuilder.render() {
- val errorMessage = props.errorMessage
- bpDialog(
- isOpen = errorMessage != null,
- title = "Oops!",
- icon = IconNames.ERROR,
- iconIntent = Intent.DANGER,
- onClose = { goHomeAndRefresh() }
- ) {
- styledDiv {
- css {
- classes.add(Classes.DIALOG_BODY)
- }
- p {
- +(errorMessage ?: "fatal error")
- }
+ BpDialogBody {
+ p {
+ +(errorMessage ?: "fatal error")
}
- styledDiv {
- css {
- classes.add(Classes.DIALOG_FOOTER)
- }
- bpButton(icon = IconNames.LOG_OUT, onClick = { goHomeAndRefresh() }) {
- +"HOME"
- }
+ }
+ BpDialogFooter {
+ BpButton {
+ icon = IconNames.LOG_OUT
+ onClick = { goHomeAndRefresh() }
+
+ +"HOME"
}
}
}
@@ -53,15 +55,3 @@ private fun goHomeAndRefresh() {
// we don't use a redux action here because we actually want to redirect and refresh the page
window.location.href = SwRoute.HOME.path
}
-
-fun RBuilder.errorDialog() = errorDialog {}
-
-private val errorDialog = connectStateAndDispatch<ErrorDialogStateProps, ErrorDialogDispatchProps, ErrorDialogProps>(
- clazz = ErrorDialogPresenter::class,
- mapStateToProps = { state, _ ->
- errorMessage = state.fatalError
- },
- mapDispatchToProps = { dispatch, _ ->
- goHome = { dispatch(Navigate(SwRoute.HOME)) }
- },
-)
diff --git a/sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/components/game/Board.kt b/sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/components/game/Board.kt
index c3dc6460..9ca56efa 100644
--- a/sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/components/game/Board.kt
+++ b/sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/components/game/Board.kt
@@ -1,37 +1,34 @@
package org.luxons.sevenwonders.ui.components.game
-import kotlinx.css.*
-import kotlinx.css.properties.*
-import kotlinx.html.DIV
-import kotlinx.html.HTMLTag
-import kotlinx.html.IMG
-import kotlinx.html.title
-import org.luxons.sevenwonders.model.boards.Board
-import org.luxons.sevenwonders.model.boards.Military
-import org.luxons.sevenwonders.model.cards.TableCard
-import org.luxons.sevenwonders.model.wonders.ApiWonder
-import org.luxons.sevenwonders.model.wonders.ApiWonderStage
-import react.RBuilder
-import react.dom.*
-import styled.StyledDOMBuilder
-import styled.css
-import styled.styledDiv
-import styled.styledImg
+import csstype.*
+import emotion.react.*
+import org.luxons.sevenwonders.model.boards.*
+import org.luxons.sevenwonders.model.cards.*
+import org.luxons.sevenwonders.model.wonders.*
+import react.*
+import react.dom.html.*
+import react.dom.html.ReactHTML.div
+import react.dom.html.ReactHTML.img
+import web.html.*
// card offsets in % of their size when displayed in columns
private const val xOffset = 20
private const val yOffset = 21
-fun RBuilder.boardComponent(board: Board, block: StyledDOMBuilder<DIV>.() -> Unit = {}) {
- styledDiv {
- block()
- tableCards(cardColumns = board.playedCards)
- wonderComponent(wonder = board.wonder, military = board.military)
+external interface BoardComponentProps : PropsWithClassName {
+ var board: Board
+}
+
+val BoardComponent = FC<BoardComponentProps>("Board") { props ->
+ div {
+ className = props.className
+ tableCards(cardColumns = props.board.playedCards)
+ wonderComponent(wonder = props.board.wonder, military = props.board.military)
}
}
-private fun RBuilder.tableCards(cardColumns: List<List<TableCard>>) {
- styledDiv {
+private fun ChildrenBuilder.tableCards(cardColumns: List<List<TableCard>>) {
+ div {
css {
display = Display.flex
justifyContent = JustifyContent.spaceAround
@@ -39,101 +36,109 @@ private fun RBuilder.tableCards(cardColumns: List<List<TableCard>>) {
width = 100.pct
}
cardColumns.forEach { cards ->
- tableCardColumn(cards = cards) {
- attrs {
- key = cards.first().color.toString()
- }
+ TableCardColumn {
+ this.key = cards.first().color.toString()
+ this.cards = cards
}
}
}
}
-private fun RBuilder.tableCardColumn(cards: List<TableCard>, block: StyledDOMBuilder<DIV>.() -> Unit = {}) {
- styledDiv {
+private external interface TableCardColumnProps : PropsWithClassName {
+ var cards: List<TableCard>
+}
+
+private val TableCardColumn = FC<TableCardColumnProps>("TableCardColumn") { props ->
+ div {
css {
height = 100.pct
width = 13.pct
marginRight = 4.pct
position = Position.relative
}
- block()
- cards.forEachIndexed { index, card ->
- tableCard(card = card, indexInColumn = index) {
- attrs { key = card.name }
+ props.cards.forEachIndexed { index, card ->
+ TableCard {
+ this.card = card
+ this.indexInColumn = index
+ this.key = card.name
}
}
}
}
-private fun RBuilder.tableCard(card: TableCard, indexInColumn: Int, block: StyledDOMBuilder<IMG>.() -> Unit = {}) {
- val highlightColor = if (card.playedDuringLastMove) Color.gold else null
- cardImage(card = card, highlightColor = highlightColor) {
+private external interface TableCardProps : PropsWithClassName {
+ var card: TableCard
+ var indexInColumn: Int
+}
+
+private val TableCard = FC<TableCardProps>("TableCard") { props ->
+ val highlightColor = if (props.card.playedDuringLastMove) NamedColor.gold else null
+ CardImage {
+ this.card = props.card
+ this.highlightColor = highlightColor
+
css {
position = Position.absolute
- zIndex = indexInColumn + 2 // go above the board and the built wonder cards
- transform {
- translate(
- tx = (indexInColumn * xOffset).pct,
- ty = (indexInColumn * yOffset).pct,
- )
- }
+ zIndex = integer(props.indexInColumn + 2) // go above the board and the built wonder cards
+ transform = translate(
+ tx = (props.indexInColumn * xOffset).pct,
+ ty = (props.indexInColumn * yOffset).pct,
+ )
maxWidth = 100.pct
maxHeight = 70.pct
hover {
- zIndex = 1000
+ zIndex = integer(1000)
maxWidth = 110.pct
maxHeight = 75.pct
hoverHighlightStyle()
}
}
- block()
}
}
-private fun RBuilder.wonderComponent(wonder: ApiWonder, military: Military) {
- styledDiv {
+private fun ChildrenBuilder.wonderComponent(wonder: ApiWonder, military: Military) {
+ div {
css {
position = Position.relative
width = 100.pct
height = 40.pct
}
- styledDiv {
+ div {
css {
position = Position.absolute
left = 50.pct
top = 0.px
- transform { translateX((-50).pct) }
+ transform = translatex((-50).pct)
height = 100.pct
maxWidth = 95.pct // same as wonder
// bring to the foreground on hover
- hover { zIndex = 1000 }
+ hover { zIndex = integer(1000) }
}
- styledImg(src = "/images/wonders/${wonder.image}") {
+ img {
+ src = "/images/wonders/${wonder.image}"
+ title = wonder.name
+ alt = "Wonder ${wonder.name}"
+
css {
- classes.add("wonder-image")
- declarations["border-radius"] = "0.5%/1.5%"
- boxShadow(color = Color.black, offsetX = 0.2.rem, offsetY = 0.2.rem, blurRadius = 0.5.rem)
+ borderRadius = "0.5%/1.5%".unsafeCast<BorderRadius>()
+ boxShadow = BoxShadow(color = NamedColor.black, offsetX = 0.2.rem, offsetY = 0.2.rem, blurRadius = 0.5.rem)
maxHeight = 100.pct
maxWidth = 100.pct
- zIndex = 1 // go above the built wonder cards, but below the table cards
+ zIndex = integer(1) // go above the built wonder cards, but below the table cards
hover { hoverHighlightStyle() }
}
- attrs {
- this.title = wonder.name
- this.alt = "Wonder ${wonder.name}"
- }
}
- styledDiv {
+ div {
css {
position = Position.absolute
top = 20.pct
right = (-80).px
display = Display.flex
flexDirection = FlexDirection.column
- alignItems = Align.start
+ alignItems = AlignItems.start
}
victoryPoints(military.victoryPoints) {
css {
@@ -147,7 +152,8 @@ private fun RBuilder.wonderComponent(wonder: ApiWonder, military: Military) {
}
}
wonder.stages.forEachIndexed { index, stage ->
- wonderStageElement(stage) {
+ WonderStageElement {
+ this.stage = stage
css {
wonderCardStyle(index, wonder.stages.size)
}
@@ -157,15 +163,15 @@ private fun RBuilder.wonderComponent(wonder: ApiWonder, military: Military) {
}
}
-private fun RBuilder.victoryPoints(points: Int, block: StyledDOMBuilder<DIV>.() -> Unit = {}) {
+private fun ChildrenBuilder.victoryPoints(points: Int, block: HTMLAttributes<HTMLDivElement>.() -> Unit = {}) {
boardToken("military/victory1", points, block)
}
-private fun RBuilder.defeatTokenCount(nbDefeatTokens: Int, block: StyledDOMBuilder<DIV>.() -> Unit = {}) {
+private fun ChildrenBuilder.defeatTokenCount(nbDefeatTokens: Int, block: HTMLAttributes<HTMLDivElement>.() -> Unit = {}) {
boardToken("military/defeat1", nbDefeatTokens, block)
}
-private fun RBuilder.boardToken(tokenName: String, count: Int, block: StyledDOMBuilder<DIV>.() -> Unit) {
+private fun ChildrenBuilder.boardToken(tokenName: String, count: Int, block: HTMLAttributes<HTMLDivElement>.() -> Unit) {
tokenWithCount(
tokenName = tokenName,
count = count,
@@ -173,34 +179,40 @@ private fun RBuilder.boardToken(tokenName: String, count: Int, block: StyledDOMB
brightText = true,
) {
css {
- filter = "drop-shadow(0.2rem 0.2rem 0.5rem black)"
+ filter = dropShadow(0.2.rem, 0.2.rem, 0.5.rem, NamedColor.black)
height = 15.pct
}
block()
}
}
-private fun RBuilder.wonderStageElement(stage: ApiWonderStage, block: StyledDOMBuilder<HTMLTag>.() -> Unit) {
- val back = stage.cardBack
+private external interface WonderStageElementProps : PropsWithClassName {
+ var stage: ApiWonderStage
+}
+
+private val WonderStageElement = FC<WonderStageElementProps>("WonderStageElement") { props ->
+ val back = props.stage.cardBack
if (back != null) {
- val highlightColor = if (stage.builtDuringLastMove) Color.gold else null
- cardBackImage(cardBack = back, highlightColor = highlightColor) {
- block()
+ val highlightColor = if (props.stage.builtDuringLastMove) NamedColor.gold else null
+ CardBackImage {
+ this.cardBack = back
+ this.highlightColor = highlightColor
+ this.className = props.className
}
} else {
- cardPlaceholderImage {
- block()
+ CardPlaceholderImage {
+ this.className = props.className
}
}
}
-private fun CssBuilder.wonderCardStyle(stageIndex: Int, nbStages: Int) {
+private fun PropertiesBuilder.wonderCardStyle(stageIndex: Int, nbStages: Int) {
position = Position.absolute
top = 60.pct // makes the cards stick out of the bottom of the wonder
left = stagePositionPercent(stageIndex, nbStages).pct
maxWidth = 24.pct // ratio of card width to wonder width
maxHeight = 90.pct // ratio of card height to wonder height
- zIndex = -1 // below wonder (somehow 0 is not sufficient)
+ zIndex = integer(-1) // below wonder (somehow 0 is not sufficient)
}
private fun stagePositionPercent(stageIndex: Int, nbStages: Int): Double = when (nbStages) {
@@ -209,6 +221,6 @@ private fun stagePositionPercent(stageIndex: Int, nbStages: Int): Double = when
else -> 7.9 + stageIndex * 30.0
}
-private fun CssBuilder.hoverHighlightStyle() {
- highlightStyle(Color.paleGoldenrod)
+private fun PropertiesBuilder.hoverHighlightStyle() {
+ highlightStyle(NamedColor.palegoldenrod)
}
diff --git a/sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/components/game/BoardSummary.kt b/sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/components/game/BoardSummary.kt
index ec7ea464..76487db5 100644
--- a/sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/components/game/BoardSummary.kt
+++ b/sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/components/game/BoardSummary.kt
@@ -1,109 +1,134 @@
package org.luxons.sevenwonders.ui.components.game
-import blueprintjs.core.PopoverPosition
-import blueprintjs.core.bpDivider
-import blueprintjs.core.bpPopover
-import kotlinx.css.*
-import kotlinx.html.*
-import org.luxons.sevenwonders.model.api.PlayerDTO
-import org.luxons.sevenwonders.model.boards.Board
-import org.luxons.sevenwonders.ui.components.gameBrowser.playerInfo
+import blueprintjs.core.*
+import csstype.*
+import emotion.css.*
+import emotion.react.*
+import org.luxons.sevenwonders.model.api.*
+import org.luxons.sevenwonders.model.boards.*
+import org.luxons.sevenwonders.ui.components.gameBrowser.*
+import org.luxons.sevenwonders.ui.utils.*
import react.*
-import styled.*
+import react.dom.html.ReactHTML.div
+import react.dom.html.ReactHTML.hr
enum class BoardSummarySide(
val tokenCountPosition: TokenCountPosition,
- val alignment: Align,
+ val alignment: AlignItems,
val popoverPosition: PopoverPosition,
) {
- LEFT(TokenCountPosition.RIGHT, Align.flexStart, PopoverPosition.RIGHT),
- TOP(TokenCountPosition.OVER, Align.flexStart, PopoverPosition.BOTTOM),
- RIGHT(TokenCountPosition.LEFT, Align.flexEnd, PopoverPosition.LEFT),
- BOTTOM(TokenCountPosition.OVER, Align.flexStart, PopoverPosition.TOP),
+ LEFT(TokenCountPosition.RIGHT, AlignItems.flexStart, PopoverPosition.RIGHT),
+ TOP(TokenCountPosition.OVER, AlignItems.flexStart, PopoverPosition.BOTTOM),
+ RIGHT(TokenCountPosition.LEFT, AlignItems.flexEnd, PopoverPosition.LEFT),
+ BOTTOM(TokenCountPosition.OVER, AlignItems.flexStart, PopoverPosition.TOP),
}
-fun RBuilder.boardSummaryWithPopover(
- player: PlayerDTO,
- board: Board,
- boardSummarySide: BoardSummarySide,
- block: StyledDOMBuilder<DIV>.() -> Unit = {},
-) {
- val popoverClass = GameStyles.getClassName { it::fullBoardPreviewPopover }
- bpPopover(
- content = createFullBoardPreview(board),
- position = boardSummarySide.popoverPosition,
- popoverClassName = popoverClass,
- ) {
- boardSummary(
- player = player,
- board = board,
- side = boardSummarySide,
- block = block,
- )
- }
+external interface BoardSummaryWithPopoverProps : PropsWithClassName {
+ var player: PlayerDTO
+ var board: Board
+ var side: BoardSummarySide
}
-private fun createFullBoardPreview(board: Board): ReactElement<*> = buildElement {
- boardComponent(board = board) {
- css {
- +GameStyles.fullBoardPreview
+val BoardSummaryWithPopover = FC<BoardSummaryWithPopoverProps>("BoardSummaryWithPopover") { props ->
+ BpPopover {
+ content = BoardComponent.create {
+ className = GameStyles.fullBoardPreview
+ board = props.board
+ }
+ position = props.side.popoverPosition
+ interactionKind = PopoverInteractionKind.HOVER
+ popoverClassName = ClassName {
+ val bgColor = GameStyles.sandBgColor.withAlpha(0.7)
+ backgroundColor = bgColor
+ borderRadius = 0.5.rem
+ padding = Padding(all = 0.5.rem)
+
+ children(".bp4-popover-content") {
+ background = None.none // overrides default white background
+ }
+ descendants(".bp4-popover-arrow-fill") {
+ set(Variable("fill"), bgColor.toString()) // overrides default white arrow
+ }
+ descendants(".bp4-popover-arrow::before") {
+ // The popover arrow is implemented with a simple square rotated 45 degrees (like a rhombus).
+ // Since we use a semi-transparent background, we can see the box shadow of the rest of the arrow through
+ // the popover, and thus we see the square. This boxShadow(transparent) is to avoid that.
+ boxShadow = BoxShadow(0.px, 0.px, 0.px, 0.px, NamedColor.transparent)
+ }
+ }.toString()
+
+ BoardSummary {
+ this.className = props.className
+ this.player = props.player
+ this.board = props.board
+ this.side = props.side
}
}
}
-fun RBuilder.boardSummary(
- player: PlayerDTO,
- board: Board,
- side: BoardSummarySide,
- showPreparationStatus: Boolean = true,
- block: StyledDOMBuilder<DIV>.() -> Unit = {},
-) {
- styledDiv {
- css {
+external interface BoardSummaryProps : PropsWithClassName {
+ var player: PlayerDTO
+ var board: Board
+ var side: BoardSummarySide
+ var showPreparationStatus: Boolean?
+}
+
+val BoardSummary = FC<BoardSummaryProps>("BoardSummary") { props ->
+ div {
+ css(props.className) {
display = Display.flex
flexDirection = FlexDirection.column
- alignItems = side.alignment
- padding(all = 0.5.rem)
- backgroundColor = Color.paleGoldenrod.withAlpha(0.5)
- zIndex = 50 // above table cards
+ alignItems = props.side.alignment
+ padding = Padding(all = 0.5.rem)
+ backgroundColor = NamedColor.palegoldenrod.withAlpha(0.5)
+ zIndex = integer(50) // above table cards
hover {
- backgroundColor = Color.paleGoldenrod
+ backgroundColor = NamedColor.palegoldenrod
}
}
- topBar(player, side, showPreparationStatus)
- styledHr {
+ val showPreparationStatus = props.showPreparationStatus ?: true
+ topBar(props.player, props.side, showPreparationStatus)
+ hr {
css {
- margin(vertical = 0.5.rem)
+ margin = Margin(vertical = 0.5.rem, horizontal = 0.rem)
width = 100.pct
}
}
- bottomBar(side, board, player, showPreparationStatus)
- block()
+ bottomBar(props.side, props.board, props.player, showPreparationStatus)
}
}
-private fun RBuilder.topBar(player: PlayerDTO, side: BoardSummarySide, showPreparationStatus: Boolean) {
+private fun ChildrenBuilder.topBar(player: PlayerDTO, side: BoardSummarySide, showPreparationStatus: Boolean) {
val playerIconSize = 25
if (showPreparationStatus && side == BoardSummarySide.TOP) {
- styledDiv {
+ div {
css {
display = Display.flex
flexDirection = FlexDirection.row
justifyContent = JustifyContent.spaceBetween
width = 100.pct
}
- playerInfo(player, iconSize = playerIconSize)
- playerPreparedCard(player)
+ PlayerInfo {
+ this.player = player
+ this.iconSize = playerIconSize
+ }
+ PlayerPreparedCard {
+ this.playerDisplayName = player.displayName
+ this.username = player.username
+ }
}
} else {
- playerInfo(player, iconSize = playerIconSize)
+ PlayerInfo {
+ this.player = player
+ this.iconSize = playerIconSize
+ }
}
}
-private fun RBuilder.bottomBar(side: BoardSummarySide, board: Board, player: PlayerDTO, showPreparationStatus: Boolean) {
- styledDiv {
+private fun ChildrenBuilder.bottomBar(side: BoardSummarySide, board: Board, player: PlayerDTO, showPreparationStatus: Boolean) {
+ div {
css {
display = Display.flex
flexDirection = if (side == BoardSummarySide.TOP || side == BoardSummarySide.BOTTOM) FlexDirection.row else FlexDirection.column
@@ -114,26 +139,29 @@ private fun RBuilder.bottomBar(side: BoardSummarySide, board: Board, player: Pla
}
val tokenSize = 2.rem
generalCounts(board, tokenSize, side.tokenCountPosition)
- bpDivider()
+ BpDivider()
scienceTokens(board, tokenSize, side.tokenCountPosition)
if (showPreparationStatus && side != BoardSummarySide.TOP) {
- bpDivider()
- styledDiv {
+ BpDivider()
+ div {
css {
width = 100.pct
- alignItems = Align.center
+ alignItems = AlignItems.center
display = Display.flex
flexDirection = FlexDirection.column
}
- playerPreparedCard(player)
+ PlayerPreparedCard {
+ this.playerDisplayName = player.displayName
+ this.username = player.username
+ }
}
}
}
}
-private fun StyledDOMBuilder<DIV>.generalCounts(
+private fun ChildrenBuilder.generalCounts(
board: Board,
- tokenSize: LinearDimension,
+ tokenSize: Length,
countPosition: TokenCountPosition,
) {
goldIndicator(amount = board.gold, imgSize = tokenSize, amountPosition = countPosition)
@@ -153,9 +181,9 @@ private fun StyledDOMBuilder<DIV>.generalCounts(
)
}
-private fun RBuilder.scienceTokens(
+private fun ChildrenBuilder.scienceTokens(
board: Board,
- tokenSize: LinearDimension,
+ tokenSize: Length,
sciencePosition: TokenCountPosition,
) {
tokenWithCount(
diff --git a/sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/components/game/CardImage.kt b/sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/components/game/CardImage.kt
index 409c4dac..093384a2 100644
--- a/sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/components/game/CardImage.kt
+++ b/sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/components/game/CardImage.kt
@@ -1,77 +1,72 @@
package org.luxons.sevenwonders.ui.components.game
-import kotlinx.css.*
-import kotlinx.css.properties.*
-import kotlinx.html.IMG
-import kotlinx.html.title
-import org.luxons.sevenwonders.model.cards.Card
-import org.luxons.sevenwonders.model.cards.CardBack
-import react.RBuilder
-import react.dom.attrs
-import styled.StyledDOMBuilder
-import styled.css
-import styled.styledImg
+import csstype.*
+import csstype.Color
+import emotion.react.*
+import org.luxons.sevenwonders.model.cards.*
+import react.*
+import react.dom.html.ReactHTML.img
-fun RBuilder.cardImage(
- card: Card,
- faceDown: Boolean = false,
- highlightColor: Color? = null,
- block: StyledDOMBuilder<IMG>.() -> Unit = {},
-) {
- if (faceDown) {
- cardBackImage(card.back, highlightColor, block)
- return
- }
- styledImg(src = "/images/cards/${card.image}") {
- css {
- cardImageStyle(highlightColor)
+external interface CardImageProps : PropsWithClassName {
+ var card: Card
+ var faceDown: Boolean?
+ var highlightColor: Color?
+}
+
+val CardImage = FC<CardImageProps>("CardImage") { props ->
+ if (props.faceDown == true) {
+ CardBackImage {
+ cardBack = props.card.back
+ highlightColor = props.highlightColor
}
- attrs {
- title = card.name
- alt = "Card ${card.name}"
+ } else {
+ img {
+ src = "/images/cards/${props.card.image}"
+ title = props.card.name
+ alt = "Card ${props.card.name}"
+
+ css(props.className) {
+ cardImageStyle(props.highlightColor)
+ }
}
- block()
}
}
-fun RBuilder.cardBackImage(
- cardBack: CardBack,
- highlightColor: Color? = null,
- block: StyledDOMBuilder<IMG>.() -> Unit = {},
-) {
- styledImg(src = "/images/cards/back/${cardBack.image}") {
- css {
- cardImageStyle(highlightColor)
- }
- attrs {
- alt = "Card back (${cardBack.image})"
+external interface CardBackImageProps : PropsWithClassName {
+ var cardBack: CardBack
+ var highlightColor: Color?
+}
+
+val CardBackImage = FC<CardBackImageProps>("CardBackImage") { props ->
+ img {
+ src = "/images/cards/back/${props.cardBack.image}"
+ alt = "Card back (${props.cardBack.image})"
+ css(props.className) {
+ cardImageStyle(props.highlightColor)
}
- block()
}
}
-fun RBuilder.cardPlaceholderImage(block: StyledDOMBuilder<IMG>.() -> Unit = {}) {
- styledImg(src = "/images/cards/back/placeholder.png") {
- css {
- opacity = 0.20
+val CardPlaceholderImage = FC<PropsWithClassName>("CardPlaceholderImage") { props ->
+ img {
+ src = "/images/cards/back/placeholder.png"
+ alt = "Card placeholder"
+ css(props.className) {
+ opacity = number(0.20)
borderRadius = 5.pct
}
- attrs {
- alt = "Card placeholder"
- }
- block()
}
}
-private fun CssBuilder.cardImageStyle(highlightColor: Color?) {
+private fun PropertiesBuilder.cardImageStyle(highlightColor: Color?) {
borderRadius = 5.pct
- boxShadow(offsetX = 2.px, offsetY = 2.px, blurRadius = 5.px, color = Color.black)
+ boxShadow = BoxShadow(offsetX = 2.px, offsetY = 2.px, blurRadius = 5.px, color = NamedColor.black)
highlightStyle(highlightColor)
}
-internal fun CssBuilder.highlightStyle(highlightColor: Color?) {
+internal fun PropertiesBuilder.highlightStyle(highlightColor: Color?) {
if (highlightColor != null) {
- boxShadow(
+ boxShadow = BoxShadow(
offsetX = 0.px,
offsetY = 0.px,
blurRadius = 1.rem,
diff --git a/sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/components/game/GameScene.kt b/sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/components/game/GameScene.kt
index cec41d6a..843caaf7 100644
--- a/sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/components/game/GameScene.kt
+++ b/sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/components/game/GameScene.kt
@@ -1,309 +1,309 @@
package org.luxons.sevenwonders.ui.components.game
import blueprintjs.core.*
-import blueprintjs.icons.IconNames
-import kotlinx.css.*
-import kotlinx.css.Position
-import kotlinx.css.properties.transform
-import kotlinx.css.properties.translate
-import org.luxons.sevenwonders.client.GameState
-import org.luxons.sevenwonders.client.getBoard
-import org.luxons.sevenwonders.client.getNonNeighbourBoards
-import org.luxons.sevenwonders.client.getOwnBoard
-import org.luxons.sevenwonders.model.MoveType
-import org.luxons.sevenwonders.model.PlayerMove
-import org.luxons.sevenwonders.model.TurnAction
-import org.luxons.sevenwonders.model.api.PlayerDTO
-import org.luxons.sevenwonders.model.boards.Board
-import org.luxons.sevenwonders.model.boards.RelativeBoardPosition
-import org.luxons.sevenwonders.model.cards.HandCard
-import org.luxons.sevenwonders.model.resources.ResourceTransactionOptions
-import org.luxons.sevenwonders.ui.components.GlobalStyles
+import blueprintjs.icons.*
+import csstype.*
+import csstype.Position
+import emotion.react.*
+import org.luxons.sevenwonders.client.*
+import org.luxons.sevenwonders.model.*
+import org.luxons.sevenwonders.model.api.*
+import org.luxons.sevenwonders.model.boards.*
+import org.luxons.sevenwonders.model.cards.*
+import org.luxons.sevenwonders.model.resources.*
+import org.luxons.sevenwonders.ui.components.*
import org.luxons.sevenwonders.ui.redux.*
import org.luxons.sevenwonders.ui.utils.*
import react.*
-import styled.css
-import styled.styledDiv
+import react.dom.html.ReactHTML.div
-external interface GameSceneStateProps : PropsWithChildren {
+external interface GameSceneProps : Props {
var currentPlayer: PlayerDTO?
var players: List<PlayerDTO>
- var game: GameState?
+ var game: GameState
var preparedMove: PlayerMove?
var preparedCard: HandCard?
-}
-
-external interface GameSceneDispatchProps : PropsWithChildren {
var sayReady: () -> Unit
var prepareMove: (move: PlayerMove) -> Unit
var unprepareMove: () -> Unit
var leaveGame: () -> Unit
}
-interface GameSceneProps : GameSceneStateProps, GameSceneDispatchProps
-
-data class GameSceneState(
- var transactionSelector: TransactionSelectorState?
-) : State
-
data class TransactionSelectorState(
val moveType: MoveType,
val card: HandCard,
val transactionsOptions: ResourceTransactionOptions,
)
-private class GameScene(props: GameSceneProps) : RComponent<GameSceneProps, GameSceneState>(props) {
+val GameScene = VFC("GameScene") {
- override fun GameSceneState.init() {
- transactionSelector = null
- }
+ val player = useSwSelector { it.currentPlayer }
+ val gameState = useSwSelector { it.gameState }
+ val dispatch = useSwDispatch()
- override fun RBuilder.render() {
- styledDiv {
- css {
- +GlobalStyles.papyrusBackground
- +GlobalStyles.fullscreen
+ div {
+ css(GlobalStyles.papyrusBackground, GlobalStyles.fullscreen) {}
+
+ if (gameState == null) {
+ BpNonIdealState {
+ icon = IconNames.ERROR
+ titleText = "Error: no game data"
}
- val game = props.game
- if (game == null) {
- bpNonIdealState(icon = IconNames.ERROR, title = "Error: no game data")
- } else {
- boardScene(game)
+ } else {
+ GameScenePresenter {
+ currentPlayer = player
+ players = gameState.players
+ game = gameState
+ preparedMove = gameState.currentPreparedMove
+ preparedCard = gameState.currentPreparedCard
+
+ prepareMove = { move -> dispatch(RequestPrepareMove(move)) }
+ unprepareMove = { dispatch(RequestUnprepareMove()) }
+ sayReady = { dispatch(RequestSayReady()) }
+ leaveGame = { dispatch(RequestLeaveGame()) }
}
}
}
+}
- private fun RBuilder.boardScene(game: GameState) {
- val board = game.getOwnBoard()
- styledDiv {
+private val GameScenePresenter = FC<GameSceneProps>("GameScenePresenter") { props ->
+ var transactionSelectorState by useState<TransactionSelectorState>()
+
+ val game = props.game
+ val board = game.getOwnBoard()
+ div {
+ val maybeRed = GameStyles.pulsatingRed.takeIf { game.everyoneIsWaitingForMe() }
+ css(maybeRed) {
+ height = 100.pct
+ }
+ val action = game.action
+ if (action is TurnAction.WatchScore) {
+ scoreTableOverlay(action.scoreBoard, props.players, props.leaveGame)
+ }
+ actionInfo(game.action.message)
+ BoardComponent {
+ this.board = board
css {
+ padding = Padding(vertical = 7.rem, horizontal = 7.rem) // to fit the action info message & board summaries
+ width = 100.pct
height = 100.pct
- if (everyoneIsWaitingForMe()) {
- +GameStyles.pulsatingRed
- }
}
- val action = game.action
- if (action is TurnAction.WatchScore) {
- scoreTableOverlay(action.scoreBoard, props.players, props.leaveGame)
+ }
+ transactionsSelectorDialog(
+ state = transactionSelectorState,
+ neighbours = playerNeighbours(props.currentPlayer, props.players),
+ prepareMove = { move ->
+ props.prepareMove(move)
+ transactionSelectorState = null
+ },
+ cancelTransactionSelection = { transactionSelectorState = null },
+ )
+ boardSummaries(game)
+ handRotationIndicator(game.handRotationDirection)
+ handCards(
+ game = game,
+ prepareMove = props.prepareMove,
+ startTransactionsSelection = {
+ transactionSelectorState = it
}
- actionInfo(game.action.message)
- boardComponent(board = board) {
- css {
- padding(all = 7.rem) // to fit the action info message & board summaries
- width = 100.pct
- height = 100.pct
+ )
+ val card = props.preparedCard
+ val move = props.preparedMove
+ if (card != null && move != null) {
+ BpOverlay {
+ isOpen = true
+ onClose = { props.unprepareMove() }
+
+ preparedMove(card, move, props.unprepareMove) {
+ css(GlobalStyles.fixedCenter) {}
}
}
- transactionsSelectorDialog(
- state = state.transactionSelector,
- neighbours = playerNeighbours(),
- prepareMove = ::prepareMoveAndCloseTransactions,
- cancelTransactionSelection = ::resetTransactionSelector,
- )
- boardSummaries(game)
- handRotationIndicator(game.handRotationDirection)
- handCards(game, props.prepareMove, ::startTransactionSelection)
- val card = props.preparedCard
- val move = props.preparedMove
- if (card != null && move != null) {
- preparedMove(card, move)
- }
- if (game.action is TurnAction.SayReady) {
- sayReadyButton()
+ }
+ if (game.action is TurnAction.SayReady) {
+ SayReadyButton {
+ currentPlayer = props.currentPlayer
+ players = props.players
+ sayReady = props.sayReady
}
}
}
+}
- private fun prepareMoveAndCloseTransactions(move: PlayerMove) {
- props.prepareMove(move)
- setState { transactionSelector = null }
+private fun GameState.everyoneIsWaitingForMe(): Boolean {
+ val onlyMeInTheGame = players.count { it.isHuman } == 1
+ if (onlyMeInTheGame || currentPreparedMove != null) {
+ return false
}
+ return preparedCardsByUsername.values.count { it != null } == players.size - 1
+}
- private fun startTransactionSelection(selectorState: TransactionSelectorState) {
- setState { transactionSelector = selectorState }
- }
+private fun playerNeighbours(currentPlayer: PlayerDTO?, players: List<PlayerDTO>): Pair<PlayerDTO, PlayerDTO> {
+ val me = currentPlayer?.username ?: error("we shouldn't be trying to display this if there is no player")
+ val size = players.size
+ val myIndex = players.indexOfFirst { it.username == me }
+ return players[(myIndex - 1 + size) % size] to players[(myIndex + 1) % size]
+}
- private fun resetTransactionSelector() {
- setState { transactionSelector = null }
- }
+private fun ChildrenBuilder.actionInfo(message: String) {
+ div {
+ css(ClassName(Classes.DARK)) {
+ position = Position.fixed
+ top = 0.pct
+ left = 0.pct
+ margin = Margin(vertical = 0.4.rem, horizontal = 0.4.rem)
+ maxWidth = 25.pct // leave space for 4 board summaries when there are 7 players
+ }
+ BpCard {
+ elevation = Elevation.TWO
+ css {
+ padding = Padding(all = 0.px)
+ }
- private fun everyoneIsWaitingForMe(): Boolean {
- val onlyMeInTheGame = props.players.count { it.isHuman } == 1
- if (onlyMeInTheGame || props.preparedMove != null) {
- return false
+ BpCallout {
+ intent = Intent.PRIMARY
+ icon = IconNames.INFO_SIGN
+ +message
+ }
}
- val gameState = props.game ?: return false
- return gameState.preparedCardsByUsername.values.count { it != null } == props.players.size - 1
}
+}
- private fun playerNeighbours(): Pair<PlayerDTO, PlayerDTO> {
- val me = props.currentPlayer?.username ?: error("we shouldn't be trying to display this if there is no player")
- val players = props.players
- val size = players.size
- val myIndex = players.indexOfFirst { it.username == me }
- return players[(myIndex - 1 + size) % size] to players[(myIndex + 1) % size]
+private fun ChildrenBuilder.boardSummaries(game: GameState) {
+ val leftBoard = game.getBoard(RelativeBoardPosition.LEFT)
+ val rightBoard = game.getBoard(RelativeBoardPosition.RIGHT)
+ val topBoards = game.getNonNeighbourBoards().reversed()
+ selfBoardSummary(game.getOwnBoard(), game.players)
+ leftPlayerBoardSummary(leftBoard, game.players)
+ rightPlayerBoardSummary(rightBoard, game.players)
+ if (topBoards.isNotEmpty()) {
+ topPlayerBoardsSummaries(topBoards, game.players)
}
+}
+
+private fun ChildrenBuilder.leftPlayerBoardSummary(board: Board, players: List<PlayerDTO>) {
+ div {
+ css {
+ position = Position.absolute
+ left = 0.px
+ bottom = 40.pct
+ }
+ BoardSummaryWithPopover {
+ this.player = players[board.playerIndex]
+ this.board = board
+ this.side = BoardSummarySide.LEFT
- private fun RBuilder.actionInfo(message: String) {
- styledDiv {
css {
- classes.add(Classes.DARK)
- position = Position.fixed
- top = 0.pct
- left = 0.pct
- margin(all = 0.4.rem)
- maxWidth = 25.pct // leave space for 4 board summaries when there are 7 players
- }
- bpCard(elevation = Elevation.TWO) {
- attrs {
- this.className = GlobalStyles.getTypedClassName { it::noPadding }
- }
- bpCallout(intent = Intent.PRIMARY, icon = IconNames.INFO_SIGN) { +message }
+ borderTopRightRadius = 0.4.rem
+ borderBottomRightRadius = 0.4.rem
}
}
}
+}
- private fun RBuilder.boardSummaries(game: GameState) {
- val leftBoard = game.getBoard(RelativeBoardPosition.LEFT)
- val rightBoard = game.getBoard(RelativeBoardPosition.RIGHT)
- val topBoards = game.getNonNeighbourBoards().reversed()
- selfBoardSummary(game.getOwnBoard())
- leftPlayerBoardSummary(leftBoard)
- rightPlayerBoardSummary(rightBoard)
- if (topBoards.isNotEmpty()) {
- topPlayerBoardsSummaries(topBoards)
+private fun ChildrenBuilder.rightPlayerBoardSummary(board: Board, players: List<PlayerDTO>) {
+ div {
+ css {
+ position = Position.absolute
+ right = 0.px
+ bottom = 40.pct
}
- }
+ BoardSummaryWithPopover {
+ this.player = players[board.playerIndex]
+ this.board = board
+ this.side = BoardSummarySide.RIGHT
- private fun RBuilder.leftPlayerBoardSummary(board: Board) {
- styledDiv {
css {
- position = Position.absolute
- left = 0.px
- bottom = 40.pct
- }
- boardSummaryWithPopover(props.players[board.playerIndex], board, BoardSummarySide.LEFT) {
- css {
- borderTopRightRadius = 0.4.rem
- borderBottomRightRadius = 0.4.rem
- }
+ borderTopLeftRadius = 0.4.rem
+ borderBottomLeftRadius = 0.4.rem
}
}
}
+}
+
+private fun ChildrenBuilder.topPlayerBoardsSummaries(boards: List<Board>, players: List<PlayerDTO>) {
+ div {
+ css {
+ position = Position.absolute
+ top = 0.px
+ left = 50.pct
+ transform = translate((-50).pct)
+ display = Display.flex
+ flexDirection = FlexDirection.row
+ }
+ boards.forEach { board ->
+ BoardSummaryWithPopover {
+ this.player = players[board.playerIndex]
+ this.board = board
+ this.side = BoardSummarySide.TOP
- private fun RBuilder.rightPlayerBoardSummary(board: Board) {
- styledDiv {
- css {
- position = Position.absolute
- right = 0.px
- bottom = 40.pct
- }
- boardSummaryWithPopover(props.players[board.playerIndex], board, BoardSummarySide.RIGHT) {
css {
- borderTopLeftRadius = 0.4.rem
borderBottomLeftRadius = 0.4.rem
+ borderBottomRightRadius = 0.4.rem
+ margin = Margin(vertical = 0.rem, horizontal = 2.rem)
}
}
}
}
+}
- private fun RBuilder.topPlayerBoardsSummaries(boards: List<Board>) {
- styledDiv {
- css {
- position = Position.absolute
- top = 0.px
- left = 50.pct
- transform { translate((-50).pct) }
- display = Display.flex
- flexDirection = FlexDirection.row
- }
- boards.forEach { board ->
- boardSummaryWithPopover(props.players[board.playerIndex], board, BoardSummarySide.TOP) {
- css {
- borderBottomLeftRadius = 0.4.rem
- borderBottomRightRadius = 0.4.rem
- margin(horizontal = 2.rem)
- }
- }
- }
+private fun ChildrenBuilder.selfBoardSummary(board: Board, players: List<PlayerDTO>) {
+ div {
+ css {
+ position = Position.absolute
+ bottom = 0.px
+ left = 0.px
}
- }
+ BoardSummary {
+ this.player = players[board.playerIndex]
+ this.board = board
+ this.side = BoardSummarySide.BOTTOM
+ this.showPreparationStatus = false
- private fun RBuilder.selfBoardSummary(board: Board) {
- styledDiv {
css {
- position = Position.absolute
- bottom = 0.px
- left = 0.px
- }
- boardSummary(
- player = props.players[board.playerIndex],
- board = board, BoardSummarySide.BOTTOM,
- showPreparationStatus = false,
- ) {
- css {
- borderTopLeftRadius = 0.4.rem
- borderTopRightRadius = 0.4.rem
- margin(horizontal = 2.rem)
- }
+ borderTopLeftRadius = 0.4.rem
+ borderTopRightRadius = 0.4.rem
+ margin = Margin(vertical = 0.rem, horizontal = 2.rem)
}
}
}
+}
- private fun RBuilder.preparedMove(card: HandCard, move: PlayerMove) {
- bpOverlay(isOpen = true, onClose = props.unprepareMove) {
- preparedMove(card, move, props.unprepareMove) {
- css { +GlobalStyles.fixedCenter }
- }
+private external interface SayReadyButtonProps : Props {
+ var currentPlayer: PlayerDTO?
+ var players: List<PlayerDTO>
+ var sayReady: () -> Unit
+}
+
+private val SayReadyButton = FC<SayReadyButtonProps>("SayReadyButton") { props ->
+ val isReady = props.currentPlayer?.isReady == true
+ val intent = if (isReady) Intent.SUCCESS else Intent.PRIMARY
+ div {
+ css {
+ position = Position.absolute
+ bottom = 6.rem
+ left = 50.pct
+ transform = translate(tx = (-50).pct)
+ zIndex = integer(2) // go above the wonder (1) and wonder-upgrade cards (0)
}
- }
+ BpButtonGroup {
+ BpButton {
+ this.large = true
+ this.disabled = isReady
+ this.intent = intent
+ this.icon = if (isReady) IconNames.TICK_CIRCLE else IconNames.PLAY
+ this.onClick = { props.sayReady() }
- private fun RBuilder.sayReadyButton() {
- val isReady = props.currentPlayer?.isReady == true
- val intent = if (isReady) Intent.SUCCESS else Intent.PRIMARY
- styledDiv {
- css {
- position = Position.absolute
- bottom = 6.rem
- left = 50.pct
- transform { translate(tx = (-50).pct) }
- zIndex = 2 // go above the wonder (1) and wonder-upgrade cards (0)
+ +"READY"
}
- bpButtonGroup {
- bpButton(
- large = true,
- disabled = isReady,
- intent = intent,
- icon = if (isReady) IconNames.TICK_CIRCLE else IconNames.PLAY,
- onClick = { props.sayReady() },
- ) {
- +"READY"
- }
- // not really a button, but nice for style
- bpButton(large = true, icon = IconNames.PEOPLE, disabled = isReady, intent = intent) {
- +"${props.players.count { it.isReady }}/${props.players.size}"
- }
+ // not really a button, but nice for style
+ BpButton {
+ this.large = true
+ this.icon = IconNames.PEOPLE
+ this.disabled = isReady
+ this.intent = intent
+
+ +"${props.players.count { it.isReady }}/${props.players.size}"
}
}
}
}
-
-fun RBuilder.gameScene() = gameScene {}
-
-private val gameScene: ComponentClass<GameSceneProps> =
- connectStateAndDispatch<GameSceneStateProps, GameSceneDispatchProps, GameSceneProps>(
- clazz = GameScene::class,
- mapDispatchToProps = { dispatch, _ ->
- prepareMove = { move -> dispatch(RequestPrepareMove(move)) }
- unprepareMove = { dispatch(RequestUnprepareMove()) }
- sayReady = { dispatch(RequestSayReady()) }
- leaveGame = { dispatch(RequestLeaveGame()) }
- },
- mapStateToProps = { state, _ ->
- currentPlayer = state.currentPlayer
- players = state.gameState?.players ?: emptyList()
- game = state.gameState
- preparedMove = state.gameState?.currentPreparedMove
- preparedCard = state.gameState?.currentPreparedCard
- },
- )
diff --git a/sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/components/game/GameStyles.kt b/sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/components/game/GameStyles.kt
index 86d2d101..bccee3f1 100644
--- a/sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/components/game/GameStyles.kt
+++ b/sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/components/game/GameStyles.kt
@@ -1,100 +1,86 @@
package org.luxons.sevenwonders.ui.components.game
-import kotlinx.css.*
-import kotlinx.css.properties.*
-import styled.StyleSheet
-import styled.animation
+import csstype.*
+import emotion.css.*
+import org.luxons.sevenwonders.ui.utils.*
-object GameStyles : StyleSheet("GameStyles", isStatic = true) {
+object GameStyles {
- val totalScore by css {
+ val totalScore = ClassName {
fontWeight = FontWeight.bold
}
- val civilScore by scoreTagColorCss(Color("#2a73c9"))
- val scienceScore by scoreTagColorCss(Color("#0f9960"))
- val militaryScore by scoreTagColorCss(Color("#d03232"))
- val tradeScore by scoreTagColorCss(Color("#e2c11b"))
- val guildScore by scoreTagColorCss(Color("#663399"))
- val wonderScore by scoreTagColorCss(Color.darkCyan)
- val goldScore by scoreTagColorCss(Color.goldenrod)
+ val civilScore = scoreTagColorCss(Color("#2a73c9"))
+ val scienceScore = scoreTagColorCss(Color("#0f9960"))
+ val militaryScore = scoreTagColorCss(Color("#d03232"))
+ val tradeScore = scoreTagColorCss(Color("#e2c11b"))
+ val guildScore = scoreTagColorCss(Color("#663399"))
+ val wonderScore = scoreTagColorCss(NamedColor.darkcyan)
+ val goldScore = scoreTagColorCss(NamedColor.goldenrod)
- private val sandBgColor = Color.paleGoldenrod
+ val sandBgColor = NamedColor.palegoldenrod
- val fullBoardPreviewPopover by css {
- val bgColor = sandBgColor.withAlpha(0.7)
- backgroundColor = bgColor
- borderRadius = 0.5.rem
- padding(all = 0.5.rem)
- children(".bp4-popover-content") {
- background = "none" // overrides default white background
- }
- descendants(".bp4-popover-arrow-fill") {
- put("fill", bgColor.toString()) // overrides default white arrow
- }
- descendants(".bp4-popover-arrow::before") {
- // The popover arrow is implemented with a simple square rotated 45 degrees (like a rhombus).
- // Since we use a semi-transparent background, we can see the box shadow of the rest of the arrow through
- // the popover, and thus we see the square. This boxShadow(transparent) is to avoid that.
- boxShadow(Color.transparent)
- }
- }
-
- val fullBoardPreview by css {
+ val fullBoardPreview = ClassName {
width = 40.vw
height = 50.vh
}
- val dimmedCard by css {
- filter = "brightness(60%) grayscale(50%)"
+ val dimmedCard = ClassName {
+ filter = brightness(60.pct) + grayscale(50.pct)
}
- val transactionsSelector by css {
+ val transactionsSelector = ClassName {
backgroundColor = sandBgColor
width = 40.rem // default is 500px, we want to fit players on the side
children(".bp4-dialog-header") {
- background = "none" // overrides default white background
+ background = None.none // overrides default white background
}
}
- val bestPrice by css {
+ val bestPrice = ClassName {
fontWeight = FontWeight.bold
color = rgb(50, 120, 50)
- transform {
- rotate((-20).deg)
- }
+ transform = rotate((-20).deg)
}
- val discardMoveText by css {
+ val discardMoveText = ClassName {
display = Display.flex
- alignItems = Align.center
+ alignItems = AlignItems.center
height = 3.rem
fontSize = 2.rem
- color = Color.goldenrod
+ color = NamedColor.goldenrod
fontWeight = FontWeight.bold
- borderTop(0.2.rem, BorderStyle.solid, Color.goldenrod)
- borderBottom(0.2.rem, BorderStyle.solid, Color.goldenrod)
+ borderTop = Border(0.2.rem, LineStyle.solid, NamedColor.goldenrod)
+ borderBottom = Border(0.2.rem, LineStyle.solid, NamedColor.goldenrod)
}
- val scoreBoard by css {
+ val scoreBoard = ClassName {
backgroundColor = sandBgColor
}
- private fun scoreTagColorCss(color: Color) = css {
+ private fun scoreTagColorCss(color: Color) = ClassName {
backgroundColor = color
}
- val pulsatingRed by css {
- animation(
+ val pulsatingRed = ClassName {
+ animation = Animation(
+ name = keyframes {
+ to {
+ boxShadow = BoxShadow(
+ inset = BoxShadowInset.inset,
+ offsetX = 0.px,
+ offsetY = 0.px,
+ blurRadius = 20.px,
+ spreadRadius = 8.px,
+ color = NamedColor.red,
+ )
+ }
+ },
duration = 2.s,
- iterationCount = IterationCount.infinite,
- direction = AnimationDirection.alternate,
- ) {
- to {
- boxShadowInset(color = Color.red, blurRadius = 20.px, spreadRadius = 8.px)
- }
- }
+ )
+ animationIterationCount = AnimationIterationCount.infinite
+ animationDirection = AnimationDirection.alternate
}
}
diff --git a/sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/components/game/Hand.kt b/sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/components/game/Hand.kt
index da17c987..a136465d 100644
--- a/sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/components/game/Hand.kt
+++ b/sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/components/game/Hand.kt
@@ -1,26 +1,38 @@
package org.luxons.sevenwonders.ui.components.game
import blueprintjs.core.*
-import blueprintjs.icons.IconNames
-import kotlinx.css.*
-import kotlinx.css.Position
-import kotlinx.css.properties.*
-import kotlinx.html.DIV
-import org.luxons.sevenwonders.client.GameState
-import org.luxons.sevenwonders.client.getOwnBoard
+import blueprintjs.icons.*
+import csstype.*
+import csstype.Position
+import emotion.react.*
+import org.luxons.sevenwonders.client.*
import org.luxons.sevenwonders.model.*
-import org.luxons.sevenwonders.model.boards.Board
-import org.luxons.sevenwonders.model.cards.CardPlayability
-import org.luxons.sevenwonders.model.cards.HandCard
-import org.luxons.sevenwonders.model.resources.ResourceTransactionOptions
-import org.luxons.sevenwonders.model.wonders.WonderBuildability
+import org.luxons.sevenwonders.model.boards.*
+import org.luxons.sevenwonders.model.cards.*
+import org.luxons.sevenwonders.model.resources.*
+import org.luxons.sevenwonders.model.wonders.*
+import org.luxons.sevenwonders.ui.utils.*
import react.*
-import react.dom.attrs
-import styled.StyledDOMBuilder
-import styled.css
-import styled.styledDiv
-import web.html.*
-import kotlin.math.absoluteValue
+import react.dom.html.ReactHTML.div
+import kotlin.math.*
+
+fun ChildrenBuilder.handCards(
+ game: GameState,
+ prepareMove: (PlayerMove) -> Unit,
+ startTransactionsSelection: (TransactionSelectorState) -> Unit,
+) {
+ HandCards {
+ this.action = game.action
+ this.ownBoard = game.getOwnBoard()
+ this.preparedMove = game.currentPreparedMove
+ this.prepareMove = { moveType: MoveType, card: HandCard, transactionOptions: ResourceTransactionOptions ->
+ when (transactionOptions.size) {
+ 1 -> prepareMove(PlayerMove(moveType, card.name, transactionOptions.single()))
+ else -> startTransactionsSelection(TransactionSelectorState(moveType, card, transactionOptions))
+ }
+ }
+ }
+}
private enum class HandAction(
val buttonTitle: String,
@@ -33,145 +45,154 @@ private enum class HandAction(
COPY_GUILD("Copy this guild card", MoveType.COPY_GUILD, "duplicate")
}
-external interface HandProps : PropsWithChildren {
+external interface HandCardsProps : Props {
var action: TurnAction
var ownBoard: Board
var preparedMove: PlayerMove?
- var prepareMove: (PlayerMove) -> Unit
- var startTransactionsSelection: (TransactionSelectorState) -> Unit
+ var prepareMove: (MoveType, HandCard, ResourceTransactionOptions) -> Unit
}
-class HandComponent(props: HandProps) : RComponent<HandProps, State>(props) {
-
- override fun RBuilder.render() {
- val hand = props.action.cardsToPlay() ?: return
- styledDiv {
- css {
- handStyle()
- }
- hand.filter { it.name != props.preparedMove?.cardName }.forEachIndexed { index, c ->
- handCard(card = c) {
- attrs {
- key = index.toString()
- }
- }
+private val HandCards = FC<HandCardsProps>("HandCards") { props ->
+ val hand = props.action.cardsToPlay() ?: return@FC
+ div {
+ css {
+ handStyle()
+ }
+ hand.filter { it.name != props.preparedMove?.cardName }.forEachIndexed { index, c ->
+ HandCard {
+ card = c
+ action = props.action
+ ownBoard = props.ownBoard
+ prepareMove = props.prepareMove
+ key = index.toString()
}
}
}
+}
- private fun TurnAction.cardsToPlay(): List<HandCard>? = when (this) {
- is TurnAction.PlayFromHand -> hand
- is TurnAction.PlayFromDiscarded -> discardedCards
- is TurnAction.PickNeighbourGuild -> neighbourGuildCards
- is TurnAction.SayReady,
- is TurnAction.Wait,
- is TurnAction.WatchScore -> null
- }
+private fun TurnAction.cardsToPlay(): List<HandCard>? = when (this) {
+ is TurnAction.PlayFromHand -> hand
+ is TurnAction.PlayFromDiscarded -> discardedCards
+ is TurnAction.PickNeighbourGuild -> neighbourGuildCards
+ is TurnAction.SayReady,
+ is TurnAction.Wait,
+ is TurnAction.WatchScore -> null
+}
- private fun RBuilder.handCard(
- card: HandCard,
- block: StyledDOMBuilder<DIV>.() -> Unit,
- ) {
- styledDiv {
+private external interface HandCardProps : Props {
+ var card: HandCard
+ var action: TurnAction
+ var ownBoard: Board
+ var prepareMove: (MoveType, HandCard, ResourceTransactionOptions) -> Unit
+}
+
+private val HandCard = FC<HandCardProps>("HandCard") { props ->
+ div {
+ css(ClassName("hand-card")) {
+ alignItems = AlignItems.flexEnd
+ display = Display.grid
+ margin = Margin(all = 0.2.rem)
+ }
+ CardImage {
css {
- handCardStyle()
- }
- block()
- cardImage(card) {
- css {
- val isPlayable = card.playability.isPlayable || props.ownBoard.canPlayAnyCardForFree
- handCardImgStyle(isPlayable)
- }
+ val isPlayable = props.card.playability.isPlayable || props.ownBoard.canPlayAnyCardForFree
+ handCardImgStyle(isPlayable)
}
- actionButtons(card)
+ this.card = props.card
}
+ actionButtons(props.card, props.action, props.ownBoard, props.prepareMove)
}
+}
- private fun RBuilder.actionButtons(card: HandCard) {
- styledDiv {
- css {
- justifyContent = JustifyContent.center
- alignItems = Align.flexEnd
- display = Display.none
- gridRow = GridRow("1")
- gridColumn = GridColumn("1")
+private fun ChildrenBuilder.actionButtons(
+ card: HandCard,
+ action: TurnAction,
+ ownBoard: Board,
+ prepareMove: (MoveType, HandCard, ResourceTransactionOptions) -> Unit,
+) {
+ div {
+ css {
+ justifyContent = JustifyContent.center
+ alignItems = AlignItems.flexEnd
+ display = None.none
+ gridRow = integer(1)
+ gridColumn = integer(1)
- ancestorHover(".hand-card") {
- display = Display.flex
- }
+ ancestorHover(".hand-card") {
+ display = Display.flex
}
- bpButtonGroup {
- val action = props.action
- when (action) {
- is TurnAction.PlayFromHand -> {
- playCardButton(card, HandAction.PLAY)
- if (props.ownBoard.canPlayAnyCardForFree) {
- playCardButton(card.copy(playability = CardPlayability.SPECIAL_FREE), HandAction.PLAY_FREE)
- }
+ }
+ BpButtonGroup {
+ when (action) {
+ is TurnAction.PlayFromHand -> {
+ playCardButton(card, HandAction.PLAY, prepareMove)
+ if (ownBoard.canPlayAnyCardForFree) {
+ playCardButton(card.copy(playability = CardPlayability.SPECIAL_FREE), HandAction.PLAY_FREE, prepareMove)
}
- is TurnAction.PlayFromDiscarded -> playCardButton(card, HandAction.PLAY_FREE_DISCARDED)
- is TurnAction.PickNeighbourGuild -> playCardButton(card, HandAction.COPY_GUILD)
- is TurnAction.SayReady,
- is TurnAction.Wait,
- is TurnAction.WatchScore -> error("unsupported action in hand card: $action")
- }
-
- if (action.allowsBuildingWonder()) {
- upgradeWonderButton(card)
- }
- if (action.allowsDiscarding()) {
- discardButton(card)
}
+ is TurnAction.PlayFromDiscarded -> playCardButton(card, HandAction.PLAY_FREE_DISCARDED, prepareMove)
+ is TurnAction.PickNeighbourGuild -> playCardButton(card, HandAction.COPY_GUILD, prepareMove)
+ is TurnAction.SayReady,
+ is TurnAction.Wait,
+ is TurnAction.WatchScore -> error("unsupported action in hand card: $action")
}
- }
- }
- private fun RElementBuilder<ButtonGroupProps>.playCardButton(card: HandCard, handAction: HandAction) {
- bpButton(
- title = "${handAction.buttonTitle} (${cardPlayabilityInfo(card.playability)})",
- large = true,
- intent = Intent.SUCCESS,
- disabled = !card.playability.isPlayable,
- onClick = { prepareMove(handAction.moveType, card, card.playability.transactionOptions) },
- ) {
- bpIcon(handAction.icon)
- if (card.playability.isPlayable && !card.playability.isFree) {
- priceInfo(card.playability.minPrice)
+ if (action.allowsBuildingWonder()) {
+ upgradeWonderButton(card, ownBoard.wonder.buildability, prepareMove)
+ }
+ if (action.allowsDiscarding()) {
+ discardButton(card, prepareMove)
}
}
}
+}
- private fun RElementBuilder<ButtonGroupProps>.upgradeWonderButton(card: HandCard) {
- val wonderBuildability = props.ownBoard.wonder.buildability
- bpButton(
- title = "UPGRADE WONDER (${wonderBuildabilityInfo(wonderBuildability)})",
- large = true,
- intent = Intent.PRIMARY,
- disabled = !wonderBuildability.isBuildable,
- onClick = { prepareMove(MoveType.UPGRADE_WONDER, card, wonderBuildability.transactionsOptions) },
- ) {
- bpIcon("key-shift")
- if (wonderBuildability.isBuildable && !wonderBuildability.isFree) {
- priceInfo(wonderBuildability.minPrice)
- }
+private fun ChildrenBuilder.playCardButton(
+ card: HandCard,
+ handAction: HandAction,
+ prepareMove: (MoveType, HandCard, ResourceTransactionOptions) -> Unit,
+) {
+ BpButton {
+ title = "${handAction.buttonTitle} (${cardPlayabilityInfo(card.playability)})"
+ large = true
+ intent = Intent.SUCCESS
+ disabled = !card.playability.isPlayable
+ onClick = { prepareMove(handAction.moveType, card, card.playability.transactionOptions) }
+
+ BpIcon { icon = handAction.icon }
+
+ if (card.playability.isPlayable && !card.playability.isFree) {
+ priceInfo(card.playability.minPrice)
}
}
+}
- private fun prepareMove(moveType: MoveType, card: HandCard, transactionOptions: ResourceTransactionOptions) {
- when (transactionOptions.size) {
- 1 -> props.prepareMove(PlayerMove(moveType, card.name, transactionOptions.single()))
- else -> props.startTransactionsSelection(TransactionSelectorState(moveType, card, transactionOptions))
+private fun ChildrenBuilder.upgradeWonderButton(
+ card: HandCard,
+ wonderBuildability: WonderBuildability,
+ prepareMove: (MoveType, HandCard, ResourceTransactionOptions) -> Unit,
+) {
+ BpButton {
+ title = "UPGRADE WONDER (${wonderBuildabilityInfo(wonderBuildability)})"
+ large = true
+ intent = Intent.PRIMARY
+ disabled = !wonderBuildability.isBuildable
+ onClick = { prepareMove(MoveType.UPGRADE_WONDER, card, wonderBuildability.transactionsOptions) }
+
+ BpIcon { icon = IconNames.KEY_SHIFT }
+ if (wonderBuildability.isBuildable && !wonderBuildability.isFree) {
+ priceInfo(wonderBuildability.minPrice)
}
}
+}
- private fun RElementBuilder<ButtonGroupProps>.discardButton(card: HandCard) {
- bpButton(
- title = "DISCARD (+3 coins)", // TODO remove hardcoded value
- large = true,
- intent = Intent.DANGER,
- icon = IconNames.CROSS,
- onClick = { props.prepareMove(PlayerMove(MoveType.DISCARD, card.name)) },
- )
+private fun ChildrenBuilder.discardButton(card: HandCard, prepareMove: (MoveType, HandCard, ResourceTransactionOptions) -> Unit) {
+ BpButton {
+ title = "DISCARD (+3 coins)" // TODO remove hardcoded value
+ large = true
+ intent = Intent.DANGER
+ icon = IconNames.CROSS
+ onClick = { prepareMove(MoveType.DISCARD, card, emptyList()) }
}
}
@@ -196,15 +217,14 @@ private fun pricePrefix(amount: Int) = when {
else -> ""
}
-private fun RElementBuilder<ButtonProps<HTMLButtonElement>>.priceInfo(amount: Int) {
- val size = 1.rem
+private fun ChildrenBuilder.priceInfo(amount: Int) {
goldIndicator(
amount = amount,
amountPosition = TokenCountPosition.OVER,
- imgSize = size,
+ imgSize = 1.rem,
customCountStyle = {
- fontFamily = "sans-serif"
- fontSize = size * 0.8
+ fontFamily = FontFamily.sansSerif
+ fontSize = 0.8.rem
},
) {
css {
@@ -215,68 +235,41 @@ private fun RElementBuilder<ButtonProps<HTMLButtonElement>>.priceInfo(amount: In
}
}
-private fun CssBuilder.handStyle() {
- alignItems = Align.center
+private fun PropertiesBuilder.handStyle() {
+ alignItems = AlignItems.center
bottom = 0.px
display = Display.flex
height = 345.px
left = 50.pct
maxHeight = 25.vw
position = Position.absolute
- transform {
- translate(tx = (-50).pct, ty = 65.pct)
- }
- transition(duration = 0.5.s)
- zIndex = 30
+ transform = translate(tx = (-50).pct, ty = 65.pct)
+ transition = Transition(TransitionProperty.all, duration = 0.5.s, timingFunction = TransitionTimingFunction.ease)
+ zIndex = integer(30)
hover {
bottom = 1.rem
- zIndex = 60
- transform {
- translate(tx = (-50).pct, ty = 0.pct)
- }
+ zIndex = integer(60)
+ transform = translate(tx = (-50).pct, ty = 0.pct)
}
}
-private fun CssBuilder.handCardStyle() {
- classes.add("hand-card")
- alignItems = Align.flexEnd
- display = Display.grid
- margin(all = 0.2.rem)
-}
-
-private fun CssBuilder.handCardImgStyle(isPlayable: Boolean) {
- gridRow = GridRow("1")
- gridColumn = GridColumn("1")
+private fun PropertiesBuilder.handCardImgStyle(isPlayable: Boolean) {
+ gridRow = integer(1)
+ gridColumn = integer(1)
maxWidth = 13.vw
maxHeight = 60.vh
- transition(duration = 0.1.s)
+ transition = Transition(TransitionProperty.all, duration = 0.1.s, timingFunction = TransitionTimingFunction.ease)
width = 11.rem
ancestorHover(".hand-card") {
- boxShadow(offsetX = 0.px, offsetY = 10.px, blurRadius = 40.px, color = Color.black)
+ boxShadow = BoxShadow(offsetX = 0.px, offsetY = 10.px, blurRadius = 40.px, color = NamedColor.black)
width = 14.rem
maxWidth = 15.vw
maxHeight = 90.vh
}
if (!isPlayable) {
- filter = "grayscale(50%) contrast(50%)"
- }
-}
-
-fun RBuilder.handCards(
- game: GameState,
- prepareMove: (PlayerMove) -> Unit,
- startTransactionsSelection: (TransactionSelectorState) -> Unit,
-) {
- child(HandComponent::class) {
- attrs {
- this.action = game.action
- this.ownBoard = game.getOwnBoard()
- this.preparedMove = game.currentPreparedMove
- this.prepareMove = prepareMove
- this.startTransactionsSelection = startTransactionsSelection
- }
+ filter = grayscale(50.pct) + contrast(50.pct)
}
}
diff --git a/sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/components/game/HandRotationIndicator.kt b/sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/components/game/HandRotationIndicator.kt
index 4761ac13..9e6874d3 100644
--- a/sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/components/game/HandRotationIndicator.kt
+++ b/sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/components/game/HandRotationIndicator.kt
@@ -1,44 +1,51 @@
package org.luxons.sevenwonders.ui.components.game
-import blueprintjs.core.bpIcon
-import kotlinx.css.*
-import kotlinx.html.title
-import org.luxons.sevenwonders.model.cards.HandRotationDirection
-import react.RBuilder
-import react.dom.attrs
-import styled.css
-import styled.styledDiv
-import styled.styledImg
+import blueprintjs.core.*
+import blueprintjs.icons.*
+import csstype.*
+import csstype.Position
+import emotion.react.*
+import org.luxons.sevenwonders.model.cards.*
+import react.*
+import react.dom.html.ReactHTML.div
+import react.dom.html.ReactHTML.img
-fun RBuilder.handRotationIndicator(direction: HandRotationDirection) {
- styledDiv {
+fun ChildrenBuilder.handRotationIndicator(direction: HandRotationDirection) {
+ div {
css {
position = Position.absolute
display = Display.flex
- alignItems = Align.center
+ alignItems = AlignItems.center
bottom = 25.vh
}
- attrs {
- title = "Your hand will be passed to the player on your $direction after playing this card."
- }
+
+ title = "Your hand will be passed to the player on your $direction after playing this card."
+
val sideDistance = 2.rem
when (direction) {
HandRotationDirection.LEFT -> {
css { left = sideDistance }
- bpIcon("arrow-left", size = 25)
+ BpIcon {
+ icon = IconNames.ARROW_LEFT
+ size = 25
+ }
handCardsImg()
}
HandRotationDirection.RIGHT -> {
css { right = sideDistance }
handCardsImg()
- bpIcon("arrow-right", size = 25)
+ BpIcon {
+ icon = IconNames.ARROW_RIGHT
+ size = 25
+ }
}
}
}
}
-private fun RBuilder.handCardsImg() {
- styledImg(src = "images/hand-cards5.png") {
+private fun ChildrenBuilder.handCardsImg() {
+ img {
+ src = "images/hand-cards5.png"
css {
width = 4.rem
}
diff --git a/sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/components/game/PlayerPreparedCard.kt b/sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/components/game/PlayerPreparedCard.kt
deleted file mode 100644
index b42d3b81..00000000
--- a/sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/components/game/PlayerPreparedCard.kt
+++ /dev/null
@@ -1,82 +0,0 @@
-package org.luxons.sevenwonders.ui.components.game
-
-import kotlinx.css.*
-import kotlinx.css.properties.*
-import kotlinx.html.title
-import org.luxons.sevenwonders.model.api.PlayerDTO
-import org.luxons.sevenwonders.model.cards.CardBack
-import org.luxons.sevenwonders.ui.redux.connectStateWithOwnProps
-import react.*
-import react.dom.attrs
-import styled.animation
-import styled.css
-import styled.styledDiv
-import styled.styledImg
-
-external interface PlayerPreparedCardProps : PropsWithChildren {
- var playerDisplayName: String
- var cardBack: CardBack?
-}
-
-external interface PlayerPreparedCardContainerProps : PropsWithChildren {
- var playerDisplayName: String
- var username: String
-}
-
-fun RBuilder.playerPreparedCard(player: PlayerDTO) = playerPreparedCard {
- attrs {
- this.playerDisplayName = player.displayName
- this.username = player.username
- }
-}
-
-private class PlayerPreparedCard(props: PlayerPreparedCardProps) : RComponent<PlayerPreparedCardProps, State>(props) {
-
- override fun RBuilder.render() {
- val cardBack = props.cardBack
- val sideSize = 30.px
- styledDiv {
- css {
- width = sideSize
- height = sideSize
- }
- attrs {
- title = if (cardBack == null) {
- "${props.playerDisplayName} is still thinking…"
- } else {
- "${props.playerDisplayName} is ready to play this turn"
- }
- }
- if (cardBack != null) {
- cardBackImage(cardBack) {
- css {
- maxHeight = sideSize
- }
- }
- } else {
- styledImg(src = "images/gear-50.png") {
- css {
- maxHeight = sideSize
- animation(
- duration = 1.5.s,
- iterationCount = IterationCount.infinite,
- timing = cubicBezier(0.2, 0.9, 0.7, 1.3)
- ) {
- to {
- transform { rotate(360.deg) }
- }
- }
- }
- }
- }
- }
- }
-}
-
-private val playerPreparedCard = connectStateWithOwnProps(
- clazz = PlayerPreparedCard::class,
- mapStateToProps = { state, ownProps: PlayerPreparedCardContainerProps ->
- playerDisplayName = ownProps.playerDisplayName
- cardBack = state.gameState?.preparedCardsByUsername?.get(ownProps.username)
- },
-)
diff --git a/sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/components/game/PlayerPreparedCardPresenter.kt b/sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/components/game/PlayerPreparedCardPresenter.kt
new file mode 100644
index 00000000..26678309
--- /dev/null
+++ b/sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/components/game/PlayerPreparedCardPresenter.kt
@@ -0,0 +1,79 @@
+package org.luxons.sevenwonders.ui.components.game
+
+import csstype.*
+import emotion.css.*
+import emotion.react.*
+import org.luxons.sevenwonders.model.cards.*
+import org.luxons.sevenwonders.ui.redux.*
+import org.luxons.sevenwonders.ui.utils.*
+import react.*
+import react.dom.html.ReactHTML.div
+import react.dom.html.ReactHTML.img
+
+external interface PlayerPreparedCardProps : Props {
+ var playerDisplayName: String
+ var username: String
+}
+
+val PlayerPreparedCard = FC<PlayerPreparedCardProps>("PlayerPreparedCard") { props ->
+ val cardBack = useSwSelector { it.gameState?.preparedCardsByUsername?.get(props.username) }
+
+ PlayerPreparedCardPresenter {
+ this.playerDisplayName = props.playerDisplayName
+ this.cardBack = cardBack
+ }
+}
+
+external interface PlayerPreparedCardPresenterProps : Props {
+ var playerDisplayName: String
+ var cardBack: CardBack?
+}
+
+private val PlayerPreparedCardPresenter = FC<PlayerPreparedCardPresenterProps>("PlayerPreparedCardPresenter") { props ->
+ val cardBack = props.cardBack
+ val sideSize = 30.px
+ div {
+ css {
+ width = sideSize
+ height = sideSize
+ }
+ title = if (cardBack == null) {
+ "${props.playerDisplayName} is still thinking…"
+ } else {
+ "${props.playerDisplayName} is ready to play this turn"
+ }
+
+ if (cardBack != null) {
+ CardBackImage {
+ this.cardBack = cardBack
+ css {
+ maxHeight = sideSize
+ }
+ }
+ } else {
+ RotatingGear {
+ css {
+ maxHeight = sideSize
+ }
+ }
+ }
+ }
+}
+
+private val RotatingGear = FC<PropsWithClassName> { props ->
+ img {
+ src = "images/gear-50.png"
+ css(props.className) {
+ animation = Animation(
+ name = keyframes {
+ to {
+ transform = rotate(360.deg)
+ }
+ },
+ duration = 1.5.s,
+ timingFunction = cubicBezier(0.2, 0.9, 0.7, 1.3),
+ )
+ animationIterationCount = AnimationIterationCount.infinite
+ }
+ }
+}
diff --git a/sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/components/game/PreparedMove.kt b/sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/components/game/PreparedMove.kt
index a56bbc00..b03505d6 100644
--- a/sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/components/game/PreparedMove.kt
+++ b/sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/components/game/PreparedMove.kt
@@ -1,32 +1,31 @@
package org.luxons.sevenwonders.ui.components.game
-import blueprintjs.core.Intent
-import blueprintjs.core.bpButton
-import blueprintjs.icons.IconNames
-import kotlinx.css.*
-import kotlinx.css.properties.*
-import kotlinx.html.DIV
-import org.luxons.sevenwonders.model.MoveType
-import org.luxons.sevenwonders.model.PlayerMove
-import org.luxons.sevenwonders.model.cards.HandCard
-import org.luxons.sevenwonders.ui.components.GlobalStyles
-import react.RBuilder
-import styled.StyledDOMBuilder
-import styled.css
-import styled.styledDiv
-import styled.styledImg
+import blueprintjs.core.*
+import blueprintjs.icons.*
+import csstype.*
+import csstype.Position
+import emotion.react.*
+import org.luxons.sevenwonders.model.*
+import org.luxons.sevenwonders.model.cards.*
+import org.luxons.sevenwonders.ui.components.*
+import react.*
+import react.dom.html.*
+import react.dom.html.ReactHTML.div
+import react.dom.html.ReactHTML.img
+import web.html.*
-fun RBuilder.preparedMove(
+fun ChildrenBuilder.preparedMove(
card: HandCard,
move: PlayerMove,
unprepareMove: () -> Unit,
- block: StyledDOMBuilder<DIV>.() -> Unit,
+ block: HTMLAttributes<HTMLDivElement>.() -> Unit,
) {
- styledDiv {
+ div {
block()
- cardImage(card) {
+ CardImage {
+ this.card = card
if (move.type == MoveType.DISCARD || move.type == MoveType.UPGRADE_WONDER) {
- css { +GameStyles.dimmedCard }
+ this.className = GameStyles.dimmedCard
}
}
if (move.type == MoveType.DISCARD) {
@@ -35,43 +34,39 @@ fun RBuilder.preparedMove(
if (move.type == MoveType.UPGRADE_WONDER) {
upgradeWonderSymbol()
}
- styledDiv {
+ div {
css {
position = Position.absolute
top = 0.px
right = 0.px
}
- bpButton(
- icon = IconNames.CROSS,
- title = "Cancel prepared move",
- small = true,
- intent = Intent.DANGER,
- onClick = { unprepareMove() },
- )
+ BpButton {
+ icon = IconNames.CROSS
+ title = "Cancel prepared move"
+ small = true
+ intent = Intent.DANGER
+ onClick = { unprepareMove() }
+ }
}
}
}
-private fun StyledDOMBuilder<DIV>.discardText() {
- styledDiv {
- css {
- +GlobalStyles.centerInPositionedParent
- +GameStyles.discardMoveText
- }
+private fun ChildrenBuilder.discardText() {
+ div {
+ css(GlobalStyles.centerInPositionedParent, GameStyles.discardMoveText) {}
+"DISCARD"
}
}
-private fun StyledDOMBuilder<DIV>.upgradeWonderSymbol() {
- styledImg(src = "/images/wonder-upgrade-bright.png") {
+private fun ChildrenBuilder.upgradeWonderSymbol() {
+ img {
+ src = "/images/wonder-upgrade-bright.png"
css {
width = 8.rem
position = Position.absolute
left = 10.pct
top = 50.pct
- transform {
- translate((-50).pct, (-50).pct)
- }
+ transform = translate((-50).pct, (-50).pct)
}
}
}
diff --git a/sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/components/game/ScoreTable.kt b/sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/components/game/ScoreTable.kt
index 01bf139b..c9e530ce 100644
--- a/sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/components/game/ScoreTable.kt
+++ b/sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/components/game/ScoreTable.kt
@@ -1,52 +1,55 @@
package org.luxons.sevenwonders.ui.components.game
import blueprintjs.core.*
+import blueprintjs.icons.*
import csstype.*
-import kotlinx.css.*
-import kotlinx.css.Display
-import kotlinx.css.FlexDirection
-import kotlinx.css.TextAlign
-import kotlinx.css.VerticalAlign
-import kotlinx.css.px
-import kotlinx.css.rem
-import kotlinx.html.TD
-import kotlinx.html.TH
-import org.luxons.sevenwonders.model.api.PlayerDTO
-import org.luxons.sevenwonders.model.score.ScoreBoard
-import org.luxons.sevenwonders.model.score.ScoreCategory
-import org.luxons.sevenwonders.ui.components.GlobalStyles
+import emotion.react.*
+import org.luxons.sevenwonders.model.api.*
+import org.luxons.sevenwonders.model.score.*
+import org.luxons.sevenwonders.ui.components.*
import org.luxons.sevenwonders.ui.utils.*
-import react.RBuilder
-import react.dom.*
-import styled.*
-
-fun RBuilder.scoreTableOverlay(scoreBoard: ScoreBoard, players: List<PlayerDTO>, leaveGame: () -> Unit) {
- bpOverlay(isOpen = true) {
- bpCard {
- attrs {
- val fixedCenterClass = GlobalStyles.getClassName { it::fixedCenter }
- val scoreBoardClass = GameStyles.getClassName { it::scoreBoard }
- className = ClassName("$fixedCenterClass $scoreBoardClass")
- }
- styledDiv {
- css {
+import react.*
+import react.dom.html.*
+import react.dom.html.ReactHTML.div
+import react.dom.html.ReactHTML.h1
+import react.dom.html.ReactHTML.sup
+import react.dom.html.ReactHTML.tbody
+import react.dom.html.ReactHTML.td
+import react.dom.html.ReactHTML.th
+import react.dom.html.ReactHTML.thead
+import react.dom.html.ReactHTML.tr
+
+fun ChildrenBuilder.scoreTableOverlay(scoreBoard: ScoreBoard, players: List<PlayerDTO>, leaveGame: () -> Unit) {
+ BpOverlay {
+ isOpen = true
+
+ BpCard {
+ css(GlobalStyles.fixedCenter, GameStyles.scoreBoard) {}
+
+ div {
+ // FIXME this doesn't look right, the scoreBoard class is applied at both levels
+ css(GameStyles.scoreBoard) { // loads the styles so that they can be picked up by bpCard
display = Display.flex
flexDirection = FlexDirection.column
- alignItems = Align.center
- +GameStyles.scoreBoard // loads the styles so that they can be picked up by bpCard
+ alignItems = AlignItems.center
}
- styledH1 {
+ h1 {
css {
marginTop = 0.px
}
+"Score Board"
}
scoreTable(scoreBoard, players)
- styledDiv {
+ div {
css {
marginTop = 1.rem
}
- bpButton(intent = Intent.WARNING, rightIcon = "log-out", large = true, onClick = { leaveGame() }) {
+ BpButton {
+ intent = Intent.WARNING
+ rightIcon = "log-out"
+ large = true
+ onClick = { leaveGame() }
+
+"LEAVE"
}
}
@@ -55,18 +58,32 @@ fun RBuilder.scoreTableOverlay(scoreBoard: ScoreBoard, players: List<PlayerDTO>,
}
}
-private fun RBuilder.scoreTable(scoreBoard: ScoreBoard, players: List<PlayerDTO>) {
- bpHtmlTable(bordered = false, interactive = true) {
+private fun ChildrenBuilder.scoreTable(scoreBoard: ScoreBoard, players: List<PlayerDTO>) {
+ BpHTMLTable {
+ bordered = false
+ interactive = true
+
thead {
tr {
- centeredTh { +"Rank" }
- centeredTh {
- attrs { colSpan = "2" }
+ th {
+ fullCenterInlineStyle()
+ +"Rank"
+ }
+ th {
+ fullCenterInlineStyle()
+ colSpan = 2
+
+"Player"
}
- centeredTh { +"Score" }
+ th {
+ fullCenterInlineStyle()
+ +"Score"
+ }
ScoreCategory.values().forEach {
- centeredTh { +it.title }
+ th {
+ fullCenterInlineStyle()
+ +it.title
+ }
}
}
}
@@ -74,28 +91,44 @@ private fun RBuilder.scoreTable(scoreBoard: ScoreBoard, players: List<PlayerDTO>
scoreBoard.scores.forEachIndexed { index, score ->
val player = players[score.playerIndex]
tr {
- centeredTd { ordinal(scoreBoard.ranks[index]) }
- centeredTd { bpIcon(player.icon?.name ?: "user", size = 25) }
- styledTd {
+ td {
+ fullCenterInlineStyle()
+ ordinal(scoreBoard.ranks[index])
+ }
+ td {
+ fullCenterInlineStyle()
+ BpIcon {
+ icon = player.icon?.name ?: IconNames.USER
+ size = 25
+ }
+ }
+ td {
inlineStyles {
verticalAlign = VerticalAlign.middle
}
+player.displayName
}
- centeredTd {
- bpTag(large = true, round = true, minimal = true) {
- attrs {
- this.className = GameStyles.getTypedClassName { it::totalScore }
- }
+ td {
+ fullCenterInlineStyle()
+ BpTag {
+ large = true
+ round = true
+ minimal = true
+ className = GameStyles.totalScore
+
+"${score.totalPoints}"
}
}
ScoreCategory.values().forEach { cat ->
- centeredTd {
- bpTag(large = true, round = true, icon = cat.icon, fill = true) {
- attrs {
- this.className = classNameForCategory(cat)
- }
+ td {
+ fullCenterInlineStyle()
+ BpTag {
+ large = true
+ round = true
+ fill = true
+ icon = cat.icon
+ className = classNameForCategory(cat)
+
+"${score.pointsByCategory[cat]}"
}
}
@@ -106,7 +139,7 @@ private fun RBuilder.scoreTable(scoreBoard: ScoreBoard, players: List<PlayerDTO>
}
}
-private fun RBuilder.ordinal(value: Int) {
+private fun ChildrenBuilder.ordinal(value: Int) {
+"$value"
sup { +value.ordinalIndicator() }
}
@@ -118,49 +151,33 @@ private fun Int.ordinalIndicator() = when {
else -> "th"
}
-private fun RBuilder.centeredTh(block: RDOMBuilder<TH>.() -> Unit) {
- th {
- // inline styles necessary to overcome blueprintJS overrides
- inlineStyles {
- textAlign = TextAlign.center
- verticalAlign = VerticalAlign.middle
- }
- block()
+private fun HTMLAttributes<*>.fullCenterInlineStyle() {
+ // inline styles necessary to overcome blueprintJS overrides
+ inlineStyles {
+ textAlign = TextAlign.center
+ verticalAlign = VerticalAlign.middle
}
}
-private fun RBuilder.centeredTd(block: RDOMBuilder<TD>.() -> Unit) {
- td {
- // inline styles necessary to overcome blueprintJS overrides
- inlineStyles {
- textAlign = TextAlign.center
- verticalAlign = VerticalAlign.middle
- }
- block()
- }
-}
-
-private fun classNameForCategory(cat: ScoreCategory): ClassName = GameStyles.getTypedClassName {
- when (cat) {
- ScoreCategory.CIVIL -> it::civilScore
- ScoreCategory.SCIENCE -> it::scienceScore
- ScoreCategory.MILITARY -> it::militaryScore
- ScoreCategory.TRADE -> it::tradeScore
- ScoreCategory.GUILD -> it::guildScore
- ScoreCategory.WONDER -> it::wonderScore
- ScoreCategory.GOLD -> it::goldScore
- }
+private fun classNameForCategory(cat: ScoreCategory): ClassName = when (cat) {
+ ScoreCategory.CIVIL -> GameStyles.civilScore
+ ScoreCategory.SCIENCE -> GameStyles.scienceScore
+ ScoreCategory.MILITARY -> GameStyles.militaryScore
+ ScoreCategory.TRADE -> GameStyles.tradeScore
+ ScoreCategory.GUILD -> GameStyles.guildScore
+ ScoreCategory.WONDER -> GameStyles.wonderScore
+ ScoreCategory.GOLD -> GameStyles.goldScore
}
private val ScoreCategory.icon: String
get() = when (this) {
- ScoreCategory.CIVIL -> "office"
- ScoreCategory.SCIENCE -> "lab-test"
- ScoreCategory.MILITARY -> "cut"
- ScoreCategory.TRADE -> "swap-horizontal"
- ScoreCategory.GUILD -> "clean" // stars
- ScoreCategory.WONDER -> "symbol-triangle-up"
- ScoreCategory.GOLD -> "dollar"
+ ScoreCategory.CIVIL -> IconNames.OFFICE
+ ScoreCategory.SCIENCE -> IconNames.LAB_TEST
+ ScoreCategory.MILITARY -> IconNames.CUT
+ ScoreCategory.TRADE -> IconNames.SWAP_HORIZONTAL
+ ScoreCategory.GUILD -> IconNames.CLEAN // stars
+ ScoreCategory.WONDER -> IconNames.SYMBOL_TRIANGLE_UP
+ ScoreCategory.GOLD -> IconNames.DOLLAR
}
// Potentially useful emojis:
diff --git a/sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/components/game/Tokens.kt b/sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/components/game/Tokens.kt
index da5fc5ed..c7942f59 100644
--- a/sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/components/game/Tokens.kt
+++ b/sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/components/game/Tokens.kt
@@ -1,14 +1,15 @@
package org.luxons.sevenwonders.ui.components.game
-import kotlinx.css.*
-import kotlinx.html.DIV
-import kotlinx.html.IMG
-import kotlinx.html.title
-import org.luxons.sevenwonders.model.resources.ResourceType
-import org.luxons.sevenwonders.ui.components.GlobalStyles
-import react.RBuilder
-import react.dom.attrs
-import styled.*
+import csstype.*
+import emotion.react.*
+import org.luxons.sevenwonders.model.resources.*
+import org.luxons.sevenwonders.ui.components.*
+import react.*
+import react.dom.html.*
+import react.dom.html.ReactHTML.div
+import react.dom.html.ReactHTML.img
+import react.dom.html.ReactHTML.span
+import web.html.*
private fun getResourceTokenName(resourceType: ResourceType) = "resources/${resourceType.toString().lowercase()}"
@@ -20,12 +21,12 @@ enum class TokenCountPosition {
OVER,
}
-fun RBuilder.goldIndicator(
+fun ChildrenBuilder.goldIndicator(
amount: Int,
amountPosition: TokenCountPosition = TokenCountPosition.OVER,
- imgSize: LinearDimension = 3.rem,
- customCountStyle: CssBuilder.() -> Unit = {},
- block: StyledDOMBuilder<DIV>.() -> Unit = {},
+ imgSize: Length = 3.rem,
+ customCountStyle: PropertiesBuilder.() -> Unit = {},
+ block: HTMLAttributes<HTMLDivElement>.() -> Unit = {},
) {
tokenWithCount(
tokenName = "coin",
@@ -38,54 +39,39 @@ fun RBuilder.goldIndicator(
)
}
-fun RBuilder.resourceWithCount(
+fun ChildrenBuilder.resourceImage(
resourceType: ResourceType,
- count: Int,
title: String = resourceType.toString(),
- imgSize: LinearDimension? = null,
- countPosition: TokenCountPosition = TokenCountPosition.RIGHT,
- brightText: Boolean = false,
- customCountStyle: CssBuilder.() -> Unit = {},
- block: StyledDOMBuilder<DIV>.() -> Unit = {},
+ size: Length?,
) {
- tokenWithCount(
- tokenName = getResourceTokenName(resourceType),
- count = count,
- title = title,
- imgSize = imgSize,
- countPosition = countPosition,
- brightText = brightText,
- customCountStyle = customCountStyle,
- block = block
- )
-}
-
-fun RBuilder.resourceImage(
- resourceType: ResourceType,
- title: String = resourceType.toString(),
- size: LinearDimension?,
- block: StyledDOMBuilder<IMG>.() -> Unit = {},
-) {
- tokenImage(getResourceTokenName(resourceType), title, size, block)
+ TokenImage {
+ this.tokenName = getResourceTokenName(resourceType)
+ this.title = title
+ this.size = size
+ }
}
-fun RBuilder.tokenWithCount(
+fun ChildrenBuilder.tokenWithCount(
tokenName: String,
count: Int,
title: String = tokenName,
- imgSize: LinearDimension? = null,
+ imgSize: Length? = null,
countPosition: TokenCountPosition = TokenCountPosition.RIGHT,
brightText: Boolean = false,
- customCountStyle: CssBuilder.() -> Unit = {},
- block: StyledDOMBuilder<DIV>.() -> Unit = {},
+ customCountStyle: PropertiesBuilder.() -> Unit = {},
+ block: HTMLAttributes<HTMLDivElement>.() -> Unit = {},
) {
- styledDiv {
+ div {
block()
- val tokenCountSize = if (imgSize != null) imgSize * 0.6 else 1.5.rem
+ val tokenCountSize = if (imgSize != null) 0.6 * imgSize else 1.5.rem
when (countPosition) {
TokenCountPosition.RIGHT -> {
- tokenImage(tokenName, title = title, size = imgSize)
- styledSpan {
+ TokenImage {
+ this.tokenName = tokenName
+ this.title = title
+ this.size = imgSize
+ }
+ span {
css {
tokenCountStyle(tokenCountSize, brightText, customCountStyle)
marginLeft = 0.2.rem
@@ -93,27 +79,36 @@ fun RBuilder.tokenWithCount(
+"× $count"
}
}
+
TokenCountPosition.LEFT -> {
- styledSpan {
+ span {
css {
tokenCountStyle(tokenCountSize, brightText, customCountStyle)
marginRight = 0.2.rem
}
+"$count ×"
}
- tokenImage(tokenName, title = title, size = imgSize)
+ TokenImage {
+ this.tokenName = tokenName
+ this.title = title
+ this.size = imgSize
+ }
}
+
TokenCountPosition.OVER -> {
- styledDiv {
+ div {
css {
position = Position.relative
// if container becomes large, this one stays small so that children stay on top of each other
- width = LinearDimension.fitContent
+ width = Length.fitContent
}
- tokenImage(tokenName, title = title, size = imgSize)
- styledSpan {
- css {
- +GlobalStyles.centerInPositionedParent
+ TokenImage {
+ this.tokenName = tokenName
+ this.title = title
+ this.size = imgSize
+ }
+ span {
+ css(GlobalStyles.centerInPositionedParent) {
tokenCountStyle(tokenCountSize, brightText, customCountStyle)
}
+"$count"
@@ -124,36 +119,36 @@ fun RBuilder.tokenWithCount(
}
}
-fun RBuilder.tokenImage(
- tokenName: String,
- title: String = tokenName,
- size: LinearDimension?,
- block: StyledDOMBuilder<IMG>.() -> Unit = {},
-) {
- styledImg(src = getTokenImagePath(tokenName)) {
+external interface TokenImageProps : Props {
+ var tokenName: String
+ var title: String?
+ var size: Length?
+}
+
+val TokenImage = FC<TokenImageProps> { props ->
+ img {
+ src = getTokenImagePath(props.tokenName)
+ title = props.title ?: props.tokenName
+ alt = props.tokenName
+
css {
- height = size ?: 100.pct
- if (size != null) {
- width = size
+ height = props.size ?: 100.pct
+ if (props.size != null) {
+ width = props.size
}
verticalAlign = VerticalAlign.middle
}
- attrs {
- this.title = title
- this.alt = tokenName
- }
- block()
}
}
-private fun CssBuilder.tokenCountStyle(
- size: LinearDimension,
+private fun PropertiesBuilder.tokenCountStyle(
+ size: Length,
brightText: Boolean,
- customStyle: CssBuilder.() -> Unit = {},
+ customStyle: PropertiesBuilder.() -> Unit = {},
) {
- fontFamily = "Acme"
+ fontFamily = string("Acme")
fontSize = size
verticalAlign = VerticalAlign.middle
- color = if (brightText) Color.white else Color.black
+ color = if (brightText) NamedColor.white else NamedColor.black
customStyle()
}
diff --git a/sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/components/game/TransactionsSelector.kt b/sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/components/game/TransactionsSelector.kt
index 66bc44d3..966a40eb 100644
--- a/sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/components/game/TransactionsSelector.kt
+++ b/sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/components/game/TransactionsSelector.kt
@@ -1,64 +1,65 @@
package org.luxons.sevenwonders.ui.components.game
import blueprintjs.core.*
-import kotlinx.css.*
-import kotlinx.html.DIV
-import kotlinx.html.TBODY
-import kotlinx.html.TD
-import kotlinx.html.classes
-import kotlinx.html.js.onClickFunction
-import org.luxons.sevenwonders.model.PlayerMove
-import org.luxons.sevenwonders.model.api.PlayerDTO
+import blueprintjs.icons.*
+import csstype.*
+import emotion.react.*
+import org.luxons.sevenwonders.model.*
+import org.luxons.sevenwonders.model.api.*
import org.luxons.sevenwonders.model.resources.*
import org.luxons.sevenwonders.model.resources.Provider
-import org.luxons.sevenwonders.ui.components.gameBrowser.playerInfo
+import org.luxons.sevenwonders.ui.components.gameBrowser.*
import org.luxons.sevenwonders.ui.utils.*
import react.*
-import react.dom.*
-import styled.*
+import react.dom.html.*
+import react.dom.html.ReactHTML.div
+import react.dom.html.ReactHTML.p
+import react.dom.html.ReactHTML.tbody
+import react.dom.html.ReactHTML.td
+import react.dom.html.ReactHTML.tr
+import web.html.*
-fun RBuilder.transactionsSelectorDialog(
+fun ChildrenBuilder.transactionsSelectorDialog(
state: TransactionSelectorState?,
neighbours: Pair<PlayerDTO, PlayerDTO>,
prepareMove: (PlayerMove) -> Unit,
cancelTransactionSelection: () -> Unit,
) {
- bpDialog(
- isOpen = state != null,
- title = "Trading time!",
- canEscapeKeyClose = true,
- canOutsideClickClose = true,
- isCloseButtonShown = true,
- onClose = cancelTransactionSelection,
- ) {
- attrs {
- className = GameStyles.getTypedClassName { it::transactionsSelector }
- }
- div {
- attrs {
- classes += Classes.DIALOG_BODY
- }
+ BpDialog {
+ isOpen = state != null
+ titleText = "Trading time!"
+ canEscapeKeyClose = true
+ canOutsideClickClose = true
+ isCloseButtonShown = true
+ onClose = { cancelTransactionSelection() }
+
+ className = GameStyles.transactionsSelector
+
+ BpDialogBody {
p {
+"You don't have enough resources to perform this move, but you can buy them from neighbours. "
+"Please pick an option:"
}
if (state != null) { // should always be true when the dialog is rendered
- styledDiv {
+ div {
css {
- margin(all = LinearDimension.auto)
+ margin = Margin(all = Auto.auto)
display = Display.flex
- alignItems = Align.center
+ alignItems = AlignItems.center
}
neighbour(neighbours.first)
- styledDiv {
+ div {
css {
- flex(Flex.GROW)
- margin(horizontal = 0.5.rem)
+ flexGrow = number(1.0)
+ margin = Margin(vertical = 0.rem, horizontal = 0.5.rem)
display = Display.flex
flexDirection = FlexDirection.column
- alignItems = Align.center
+ alignItems = AlignItems.center
+ }
+ OptionsTable {
+ this.state = state
+ this.prepareMove = prepareMove
}
- optionsTable(state, prepareMove)
}
neighbour(neighbours.second)
}
@@ -67,28 +68,21 @@ fun RBuilder.transactionsSelectorDialog(
}
}
-private fun StyledDOMBuilder<DIV>.neighbour(player: PlayerDTO) {
- styledDiv {
+private fun ChildrenBuilder.neighbour(player: PlayerDTO) {
+ div {
css {
width = 12.rem
// center the icon
display = Display.flex
flexDirection = FlexDirection.column
- alignItems = Align.center
+ alignItems = AlignItems.center
}
- playerInfo(player, iconSize = 40, orientation = FlexDirection.column, ellipsize = false)
- }
-}
-
-private fun RBuilder.optionsTable(
- state: TransactionSelectorState,
- prepareMove: (PlayerMove) -> Unit,
-) {
- child(optionsTable) {
- attrs {
- this.state = state
- this.prepareMove = prepareMove
+ PlayerInfo {
+ this.player = player
+ this.iconSize = 40
+ this.orientation = FlexDirection.column
+ this.ellipsize = false
}
}
}
@@ -98,7 +92,7 @@ private external interface OptionsTableProps : PropsWithChildren {
var prepareMove: (PlayerMove) -> Unit
}
-private val optionsTable = fc<OptionsTableProps> { props ->
+private val OptionsTable = FC<OptionsTableProps> { props ->
val state = props.state
val prepareMove = props.prepareMove
@@ -107,7 +101,8 @@ private val optionsTable = fc<OptionsTableProps> { props ->
val bestPrice = state.transactionsOptions.bestPrice
val (cheapestOptions, otherOptions) = state.transactionsOptions.partition { it.totalPrice == bestPrice }
- bpHtmlTable(interactive = true) {
+ BpHTMLTable {
+ interactive = true
tbody {
cheapestOptions.forEach { transactions ->
transactionsOptionRow(
@@ -130,84 +125,86 @@ private val optionsTable = fc<OptionsTableProps> { props ->
if (otherOptions.isNotEmpty()) {
val icon = if (expanded) "chevron-up" else "chevron-down"
val text = if (expanded) "Hide expensive options" else "Show more expensive options"
- bpButton(
- minimal = true,
- small = true,
- icon = icon,
- rightIcon = icon,
- onClick = { expanded = !expanded },
- ) {
+ BpButton {
+ this.minimal = true
+ this.small = true
+ this.icon = icon
+ this.rightIcon = icon
+ this.onClick = { expanded = !expanded }
+
+text
}
}
}
-private fun RDOMBuilder<TBODY>.transactionsOptionRow(
+private fun ChildrenBuilder.transactionsOptionRow(
transactions: PricedResourceTransactions,
showBestPriceIndicator: Boolean,
onClick: () -> Unit,
) {
- styledTr {
+ tr {
css {
cursor = Cursor.pointer
- alignItems = Align.center
- }
- attrs {
- onClickFunction = { onClick() }
+ alignItems = AlignItems.center
}
+ this.onClick = { onClick() }
// there should be at most one of each
val leftTr = transactions.firstOrNull { it.provider == Provider.LEFT_PLAYER }
val rightTr = transactions.firstOrNull { it.provider == Provider.RIGHT_PLAYER }
- styledTd {
+ td {
transactionCellCss()
- styledDiv {
- css { opacity = if (leftTr == null) 0.5 else 1 }
+ div {
+ css { opacity = number(if (leftTr == null) 0.5 else 1.0) }
transactionCellInnerCss()
- bpIcon(name = "caret-left", size = IconSize.LARGE)
+ BpIcon {
+ icon = IconNames.CARET_LEFT
+ size = IconSize.LARGE
+ }
goldIndicator(leftTr?.totalPrice ?: 0, imgSize = 2.5.rem)
}
}
- styledTd {
+ td {
transactionCellCss()
if (leftTr != null) {
resourceList(leftTr.resources)
}
}
- styledTd {
+ td {
transactionCellCss()
css { width = 1.5.rem }
if (showBestPriceIndicator) {
bestPriceIndicator()
}
}
- styledTd {
+ td {
transactionCellCss()
if (rightTr != null) {
resourceList(rightTr.resources)
}
}
- styledTd {
+ td {
transactionCellCss()
- styledDiv {
- css { opacity = if (rightTr == null) 0.5 else 1 }
+ div {
+ css { opacity = number(if (rightTr == null) 0.5 else 1.0) }
transactionCellInnerCss()
goldIndicator(rightTr?.totalPrice ?: 0, imgSize = 2.5.rem)
- bpIcon(name = "caret-right", size = IconSize.LARGE)
+ BpIcon {
+ icon = IconNames.CARET_RIGHT
+ size = IconSize.LARGE
+ }
}
}
}
}
-private fun StyledDOMBuilder<TD>.bestPriceIndicator() {
- styledDiv {
- css {
- +GameStyles.bestPrice
- }
+private fun ChildrenBuilder.bestPriceIndicator() {
+ div {
+ css(GameStyles.bestPrice){}
+"Best\nprice!"
}
}
-private fun StyledDOMBuilder<TD>.transactionCellCss() {
+private fun HTMLAttributes<HTMLTableCellElement>.transactionCellCss() {
// we need inline styles to win over BlueprintJS's styles (which are more specific than .class)
inlineStyles {
verticalAlign = VerticalAlign.middle
@@ -215,15 +212,15 @@ private fun StyledDOMBuilder<TD>.transactionCellCss() {
}
}
-private fun StyledDOMBuilder<DIV>.transactionCellInnerCss() {
+private fun HTMLAttributes<HTMLDivElement>.transactionCellInnerCss() {
css {
display = Display.flex
flexDirection = FlexDirection.row
- alignItems = Align.center
+ alignItems = AlignItems.center
}
}
-private fun RBuilder.resourceList(countedResources: List<CountedResource>) {
+private fun ChildrenBuilder.resourceList(countedResources: List<CountedResource>) {
val resources = countedResources.toRepeatedTypesList()
// The biggest card is the Palace and requires 7 resources (1 of each).
@@ -231,35 +228,35 @@ private fun RBuilder.resourceList(countedResources: List<CountedResource>) {
// Therefore, 3 by row seems decent. When there are 4 items, it's visually better to have a 2x2 matrix, though.
val rows = resources.chunked(if (resources.size == 4) 2 else 3)
- val imgSize = 1.5.rem
- styledDiv {
+ val imgSize = 1.5
+ div {
css {
display = Display.flex
flexDirection = FlexDirection.column
- alignItems = Align.center
+ alignItems = AlignItems.center
justifyContent = JustifyContent.center
- flex(Flex.GROW)
+ flexGrow = number(1.0)
// this ensures stable dimensions, no matter how many resources (up to 2x3 matrix)
- width = imgSize * 3
- height = imgSize * 2
+ width = (imgSize * 3).rem
+ height = (imgSize * 2).rem
}
rows.forEach { row ->
- styledDiv {
+ div {
resourceRowCss()
row.forEach {
- resourceImage(it, size = imgSize)
+ resourceImage(it, size = imgSize.rem)
}
}
}
}
}
-private fun StyledDOMBuilder<DIV>.resourceRowCss() {
+private fun HTMLAttributes<HTMLDivElement>.resourceRowCss() {
css {
display = Display.flex
flexDirection = FlexDirection.row
- alignItems = Align.center
- margin(horizontal = LinearDimension.auto)
+ alignItems = AlignItems.center
+ margin = Margin(vertical = 0.px, horizontal = Auto.auto)
}
}
diff --git a/sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/components/gameBrowser/CreateGameForm.kt b/sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/components/gameBrowser/CreateGameForm.kt
index 0d148744..ae79125b 100644
--- a/sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/components/gameBrowser/CreateGameForm.kt
+++ b/sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/components/gameBrowser/CreateGameForm.kt
@@ -2,67 +2,48 @@ package org.luxons.sevenwonders.ui.components.gameBrowser
import blueprintjs.core.*
import blueprintjs.icons.*
-import kotlinx.css.*
-import kotlinx.html.js.*
+import csstype.*
+import emotion.react.*
import org.luxons.sevenwonders.ui.redux.*
import react.*
-import react.dom.*
-import styled.*
+import react.dom.html.ReactHTML.div
+import react.dom.html.ReactHTML.form
-private external interface CreateGameFormProps : PropsWithChildren {
- var createGame: (String) -> Unit
-}
-
-private external interface CreateGameFormState : State {
- var gameName: String
-}
-
-private class CreateGameForm(props: CreateGameFormProps) : RComponent<CreateGameFormProps, CreateGameFormState>(props) {
+val CreateGameForm = VFC {
+ var gameName by useState("")
- override fun CreateGameFormState.init(props: CreateGameFormProps) {
- gameName = ""
- }
+ val dispatch = useSwDispatch()
+ val createGame = { dispatch(RequestCreateGame(gameName)) }
- override fun RBuilder.render() {
- styledDiv {
- css {
- display = Display.flex
- flexDirection = FlexDirection.row
- justifyContent = JustifyContent.spaceBetween
+ div {
+ css {
+ display = Display.flex
+ flexDirection = FlexDirection.row
+ justifyContent = JustifyContent.spaceBetween
+ }
+ form {
+ onSubmit = { e ->
+ e.preventDefault()
+ createGame()
}
- form {
- attrs {
- onSubmitFunction = { e ->
- e.preventDefault()
+
+ BpInputGroup {
+ large = true
+ placeholder = "Game name"
+ onChange = { e ->
+ val input = e.currentTarget
+ gameName = input.value
+ }
+ rightElement = BpButton.create {
+ minimal = true
+ intent = Intent.PRIMARY
+ icon = IconNames.ADD
+ onClick = { e ->
+ e.preventDefault() // prevents refreshing the page when pressing Enter
createGame()
}
}
-
- bpInputGroup(
- large = true,
- placeholder = "Game name",
- onChange = { e ->
- val input = e.currentTarget
- state.gameName = input.value
- },
- rightElement = createGameButton(),
- )
}
}
}
-
- private fun createGameButton() = buildElement {
- bpButton(minimal = true, intent = Intent.PRIMARY, icon = IconNames.ADD, onClick = { e ->
- e.preventDefault() // prevents refreshing the page when pressing Enter
- createGame()
- })
- }
-
- private fun createGame() {
- props.createGame(state.gameName)
- }
-}
-
-val createGameForm: ComponentClass<PropsWithChildren> = connectDispatch(CreateGameForm::class) { dispatch, _ ->
- createGame = { name -> dispatch(RequestCreateGame(name)) }
}
diff --git a/sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/components/gameBrowser/GameBrowser.kt b/sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/components/gameBrowser/GameBrowser.kt
index 579e6cb2..16b0965d 100644
--- a/sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/components/gameBrowser/GameBrowser.kt
+++ b/sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/components/gameBrowser/GameBrowser.kt
@@ -1,51 +1,69 @@
package org.luxons.sevenwonders.ui.components.gameBrowser
import blueprintjs.core.*
-import kotlinx.css.*
-import kotlinx.html.*
-import org.luxons.sevenwonders.ui.components.GlobalStyles
+import csstype.*
+import emotion.react.*
+import org.luxons.sevenwonders.ui.components.*
+import org.luxons.sevenwonders.ui.redux.*
import org.luxons.sevenwonders.ui.utils.*
import react.*
-import react.dom.*
-import styled.*
+import react.dom.html.ReactHTML.div
+import react.dom.html.ReactHTML.h1
+import react.dom.html.ReactHTML.h2
-fun RBuilder.gameBrowser() = styledDiv {
- css {
- +GlobalStyles.fullscreen
- +GlobalStyles.zeusBackground
- padding(all = 1.rem)
- }
- styledDiv {
- attrs {
- classes += Classes.DARK
- }
- css {
- margin(horizontal = LinearDimension.auto)
- maxWidth = GlobalStyles.preGameWidth
+val GameBrowser = VFC {
+ div {
+ css(GlobalStyles.fullscreen, GlobalStyles.zeusBackground) {
+ padding = Padding(all = 1.rem)
}
- styledDiv {
- css {
- display = Display.flex
- justifyContent = JustifyContent.spaceBetween
+ div {
+ css(ClassName(Classes.DARK)) {
+ margin = Margin(vertical = 0.px, horizontal = Auto.auto)
+ maxWidth = GlobalStyles.preGameWidth
}
- h1 { +"Games" }
- currentPlayerInfo()
- }
+ div {
+ css {
+ display = Display.flex
+ justifyContent = JustifyContent.spaceBetween
+ }
+ h1 { +"Games" }
+ CurrentPlayerInfo()
+ }
+
+ BpCard {
+ css {
+ marginBottom = 1.rem
+ }
- bpCard(className = GameBrowserStyles.getTypedClassName { it::createGameCard }) {
- styledH2 {
- css { +GameBrowserStyles.cardTitle }
- +"Create a Game"
+ h2 {
+ css {
+ marginTop = 0.px
+ }
+ +"Create a Game"
+ }
+ CreateGameForm()
}
- createGameForm {}
- }
- bpCard {
- styledH2 {
- css { +GameBrowserStyles.cardTitle }
- +"Join a Game"
+ BpCard {
+ h2 {
+ css {
+ marginTop = 0.px
+ }
+ +"Join a Game"
+ }
+ GameList()
}
- gameList()
}
}
}
+
+val CurrentPlayerInfo = VFC {
+ val connectedPlayer = useSwSelector { it.connectedPlayer }
+ PlayerInfo {
+ player = connectedPlayer
+ iconSize = 30
+ showUsername = true
+ orientation = FlexDirection.row
+ ellipsize = false
+ }
+}
diff --git a/sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/components/gameBrowser/GameBrowserStyles.kt b/sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/components/gameBrowser/GameBrowserStyles.kt
deleted file mode 100644
index 611991c2..00000000
--- a/sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/components/gameBrowser/GameBrowserStyles.kt
+++ /dev/null
@@ -1,19 +0,0 @@
-package org.luxons.sevenwonders.ui.components.gameBrowser
-
-import kotlinx.css.*
-import styled.StyleSheet
-
-object GameBrowserStyles : StyleSheet("GameBrowserStyles", isStatic = true) {
-
- val cardTitle by css {
- marginTop = 0.px
- }
-
- val createGameCard by css {
- marginBottom = 1.rem
- }
-
- val gameTable by css {
- width = 100.pct
- }
-}
diff --git a/sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/components/gameBrowser/GameList.kt b/sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/components/gameBrowser/GameList.kt
index a1f47045..2437d9e0 100644
--- a/sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/components/gameBrowser/GameList.kt
+++ b/sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/components/gameBrowser/GameList.kt
@@ -1,41 +1,50 @@
package org.luxons.sevenwonders.ui.components.gameBrowser
import blueprintjs.core.*
-import blueprintjs.icons.IconNames
+import blueprintjs.icons.*
import csstype.*
-import kotlinx.css.*
-import kotlinx.css.Display
-import kotlinx.css.FlexDirection
-import kotlinx.css.JustifyContent
-import kotlinx.css.TextAlign
-import kotlinx.css.VerticalAlign
-import kotlinx.css.rem
-import kotlinx.html.classes
-import kotlinx.html.title
-import org.luxons.sevenwonders.model.api.ConnectedPlayer
-import org.luxons.sevenwonders.model.api.LobbyDTO
+import emotion.react.*
+import org.luxons.sevenwonders.model.api.*
import org.luxons.sevenwonders.model.api.State
-import org.luxons.sevenwonders.ui.redux.RequestJoinGame
-import org.luxons.sevenwonders.ui.redux.connectStateAndDispatch
+import org.luxons.sevenwonders.ui.redux.*
+import org.luxons.sevenwonders.ui.utils.*
import react.*
-import react.dom.*
-import styled.*
+import react.dom.html.ReactHTML.col
+import react.dom.html.ReactHTML.colgroup
+import react.dom.html.ReactHTML.div
+import react.dom.html.ReactHTML.span
+import react.dom.html.ReactHTML.tbody
+import react.dom.html.ReactHTML.td
+import react.dom.html.ReactHTML.th
+import react.dom.html.ReactHTML.thead
+import react.dom.html.ReactHTML.tr
import react.State as RState
-external interface GameListStateProps : PropsWithChildren {
+external interface GameListStateProps : Props {
var connectedPlayer: ConnectedPlayer
var games: List<LobbyDTO>
}
-external interface GameListDispatchProps : PropsWithChildren {
+external interface GameListDispatchProps : Props {
var joinGame: (Long) -> Unit
}
external interface GameListProps : GameListStateProps, GameListDispatchProps
-class GameListPresenter(props: GameListProps) : RComponent<GameListProps, RState>(props) {
+val GameList = connectStateAndDispatch<GameListStateProps, GameListDispatchProps, GameListProps>(
+ clazz = GameListPresenter::class,
+ mapStateToProps = { state, _ ->
+ connectedPlayer = state.connectedPlayer ?: error("there should be a connected player")
+ games = state.games
+ },
+ mapDispatchToProps = { dispatch, _ ->
+ joinGame = { gameId -> dispatch(RequestJoinGame(gameId = gameId)) }
+ },
+)
+
+private class GameListPresenter(props: GameListProps) : Component<GameListProps, RState>(props) {
- override fun RBuilder.render() {
+ override fun render() = Fragment.create {
if (props.games.isEmpty()) {
noGamesInfo()
} else {
@@ -43,16 +52,13 @@ class GameListPresenter(props: GameListProps) : RComponent<GameListProps, RState
}
}
- private fun RBuilder.noGamesInfo() {
- bpNonIdealState(
- icon = IconNames.GEOSEARCH,
- title = "No games to join",
- ) {
- styledDiv {
- attrs {
- classes += Classes.RUNNING_TEXT
- }
- css {
+ private fun ChildrenBuilder.noGamesInfo() {
+ BpNonIdealState {
+ icon = IconNames.GEOSEARCH
+ titleText = "No games to join"
+
+ div {
+ css(ClassName(Classes.RUNNING_TEXT)) {
maxWidth = 35.rem
}
+"Nobody seems to be playing at the moment. "
@@ -61,11 +67,12 @@ class GameListPresenter(props: GameListProps) : RComponent<GameListProps, RState
}
}
- private fun RBuilder.gamesTable() {
- bpHtmlTable {
- attrs {
- className = ClassName(GameBrowserStyles.getClassName { it::gameTable })
+ private fun ChildrenBuilder.gamesTable() {
+ BpHTMLTable {
+ css {
+ width = 100.pct
}
+
columnWidthsSpec()
thead {
gameListHeaderRow()
@@ -78,26 +85,26 @@ class GameListPresenter(props: GameListProps) : RComponent<GameListProps, RState
}
}
- private fun RElementBuilder<HTMLTableProps>.columnWidthsSpec() {
+ private fun ChildrenBuilder.columnWidthsSpec() {
colgroup {
- styledCol {
+ col {
css {
width = 40.rem
}
}
- styledCol {
+ col {
css {
width = 5.rem
textAlign = TextAlign.center
}
}
- styledCol {
+ col {
css {
width = 5.rem
textAlign = TextAlign.center // use inline style on th instead to overcome blueprint style
}
}
- styledCol {
+ col {
css {
width = 3.rem
textAlign = TextAlign.center
@@ -106,7 +113,7 @@ class GameListPresenter(props: GameListProps) : RComponent<GameListProps, RState
}
}
- private fun RBuilder.gameListHeaderRow() = tr {
+ private fun ChildrenBuilder.gameListHeaderRow() = tr {
th {
+"Name"
}
@@ -124,10 +131,8 @@ class GameListPresenter(props: GameListProps) : RComponent<GameListProps, RState
}
}
- private fun RBuilder.gameListItemRow(lobby: LobbyDTO) = styledTr {
- attrs {
- key = lobby.id.toString()
- }
+ private fun ChildrenBuilder.gameListItemRow(lobby: LobbyDTO) = tr {
+ key = lobby.id.toString()
// inline styles necessary to overcome BlueprintJS's verticalAlign=top
td {
inlineStyles { gameTableCellStyle() }
@@ -150,37 +155,41 @@ class GameListPresenter(props: GameListProps) : RComponent<GameListProps, RState
}
}
- private fun StyledElement.gameTableHeaderCellStyle() {
+ private fun PropertiesBuilder.gameTableHeaderCellStyle() {
textAlign = TextAlign.center
}
- private fun StyledElement.gameTableCellStyle() {
+ private fun PropertiesBuilder.gameTableCellStyle() {
verticalAlign = VerticalAlign.middle
}
- private fun RBuilder.gameStatus(state: State) {
+ private fun ChildrenBuilder.gameStatus(state: State) {
val intent = when (state) {
State.LOBBY -> Intent.SUCCESS
State.PLAYING -> Intent.WARNING
State.FINISHED -> Intent.DANGER
}
- bpTag(minimal = true, intent = intent) {
+ BpTag {
+ this.minimal = true
+ this.intent = intent
+
+state.toString()
}
}
- private fun RBuilder.playerCount(nPlayers: Int) {
- styledDiv {
+ private fun ChildrenBuilder.playerCount(nPlayers: Int) {
+ div {
css {
display = Display.flex
flexDirection = FlexDirection.row
justifyContent = JustifyContent.center
}
- attrs {
- title = "Number of players"
+ title = "Number of players"
+ BpIcon {
+ icon = IconNames.PEOPLE
+ title = null
}
- bpIcon(name = "people", title = null)
- styledSpan {
+ span {
css {
marginLeft = 0.3.rem
}
@@ -189,28 +198,15 @@ class GameListPresenter(props: GameListProps) : RComponent<GameListProps, RState
}
}
- private fun RBuilder.joinButton(lobby: LobbyDTO) {
+ private fun ChildrenBuilder.joinButton(lobby: LobbyDTO) {
val joinability = lobby.joinability(props.connectedPlayer.displayName)
- bpButton(
- minimal = true,
- large = true,
- title = joinability.tooltip,
- icon = "arrow-right",
- disabled = !joinability.canDo,
- onClick = { props.joinGame(lobby.id) },
- )
+ BpButton {
+ minimal = true
+ large = true
+ title = joinability.tooltip
+ icon = "arrow-right"
+ disabled = !joinability.canDo
+ onClick = { props.joinGame(lobby.id) }
+ }
}
}
-
-fun RBuilder.gameList() = gameList {}
-
-private val gameList = connectStateAndDispatch<GameListStateProps, GameListDispatchProps, GameListProps>(
- clazz = GameListPresenter::class,
- mapStateToProps = { state, _ ->
- connectedPlayer = state.connectedPlayer ?: error("there should be a connected player")
- games = state.games
- },
- mapDispatchToProps = { dispatch, _ ->
- joinGame = { gameId -> dispatch(RequestJoinGame(gameId = gameId)) }
- },
-)
diff --git a/sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/components/gameBrowser/PlayerInfo.kt b/sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/components/gameBrowser/PlayerInfo.kt
index aa1033a0..cae25ba5 100644
--- a/sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/components/gameBrowser/PlayerInfo.kt
+++ b/sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/components/gameBrowser/PlayerInfo.kt
@@ -1,51 +1,52 @@
package org.luxons.sevenwonders.ui.components.gameBrowser
-import blueprintjs.core.bpIcon
-import kotlinx.css.*
-import kotlinx.html.title
-import org.luxons.sevenwonders.model.api.BasicPlayerInfo
-import org.luxons.sevenwonders.model.api.PlayerDTO
-import org.luxons.sevenwonders.ui.redux.connectState
+import blueprintjs.core.*
+import csstype.*
+import emotion.react.*
+import org.luxons.sevenwonders.model.api.*
import react.*
-import react.dom.attrs
-import styled.css
-import styled.styledDiv
-import styled.styledSpan
+import react.State
+import react.dom.html.ReactHTML.div
+import react.dom.html.ReactHTML.span
external interface PlayerInfoProps : PropsWithChildren {
var player: BasicPlayerInfo?
- var showUsername: Boolean
- var iconSize: Int
- var orientation: FlexDirection
- var ellipsize: Boolean
+ var showUsername: Boolean?
+ var iconSize: Int?
+ var orientation: FlexDirection?
+ var ellipsize: Boolean?
}
-class PlayerInfoPresenter(props: PlayerInfoProps) : RComponent<PlayerInfoProps, State>(props) {
+val PlayerInfo = PlayerInfoPresenter::class.react
- override fun RBuilder.render() {
- styledDiv {
- css {
- display = Display.flex
- alignItems = Align.center
- flexDirection = props.orientation
+private class PlayerInfoPresenter(props: PlayerInfoProps) : Component<PlayerInfoProps, State>(props) {
+
+ override fun render() = div.create {
+ val orientation = props.orientation ?: FlexDirection.row
+ css {
+ display = Display.flex
+ alignItems = AlignItems.center
+ flexDirection = orientation
+ }
+ props.player?.let {
+ BpIcon {
+ icon = it.icon?.name ?: "user"
+ size = props.iconSize ?: 30
}
- props.player?.let {
- bpIcon(name = it.icon?.name ?: "user", size = props.iconSize)
- if (props.showUsername) {
- playerNameWithUsername(it.displayName, it.username) {
- iconSeparationMargin()
- }
- } else {
- playerName(it.displayName) {
- iconSeparationMargin()
- }
+ if (props.showUsername == true) {
+ playerNameWithUsername(it.displayName, it.username) {
+ iconSeparationMargin(orientation)
+ }
+ } else {
+ playerName(it.displayName) {
+ iconSeparationMargin(orientation)
}
}
}
}
- private fun RBuilder.playerName(displayName: String, style: CssBuilder.() -> Unit = {}) {
- styledSpan {
+ private fun ChildrenBuilder.playerName(displayName: String, style: PropertiesBuilder.() -> Unit = {}) {
+ span {
css {
fontSize = 1.rem
if (props.orientation == FlexDirection.column) {
@@ -55,10 +56,9 @@ class PlayerInfoPresenter(props: PlayerInfoProps) : RComponent<PlayerInfoProps,
}
// TODO replace by BlueprintJS's Text elements (built-in ellipsize based on width)
val maxDisplayNameLength = 15
- if (props.ellipsize && displayName.length > maxDisplayNameLength) {
- attrs {
- title = displayName
- }
+ val ellipsize = props.ellipsize ?: true
+ if (ellipsize && displayName.length > maxDisplayNameLength) {
+ title = displayName
+displayName.ellipsize(maxDisplayNameLength)
} else {
+displayName
@@ -68,33 +68,33 @@ class PlayerInfoPresenter(props: PlayerInfoProps) : RComponent<PlayerInfoProps,
private fun String.ellipsize(maxLength: Int) = take(maxLength - 1) + "…"
- private fun CssBuilder.iconSeparationMargin() {
+ private fun PropertiesBuilder.iconSeparationMargin(orientation: FlexDirection) {
val margin = 0.4.rem
- when (props.orientation) {
+ when (orientation) {
FlexDirection.row -> marginLeft = margin
FlexDirection.column -> marginTop = margin
FlexDirection.rowReverse -> marginRight = margin
FlexDirection.columnReverse -> marginBottom = margin
- else -> error("Unsupported orientation '${props.orientation}' for player info component")
+ else -> error("Unsupported orientation '$orientation' for player info component")
}
}
- private fun RBuilder.playerNameWithUsername(
+ private fun ChildrenBuilder.playerNameWithUsername(
displayName: String,
username: String,
- style: CssBuilder.() -> Unit = {}
+ style: PropertiesBuilder.() -> Unit = {}
) {
- styledDiv {
+ div {
css {
display = Display.flex
flexDirection = FlexDirection.column
style()
}
playerName(displayName)
- styledSpan {
+ span {
css {
marginTop = 0.1.rem
- color = Color.lightGray
+ color = NamedColor.lightgray
fontSize = 0.8.rem
}
+"($username)"
@@ -102,32 +102,3 @@ class PlayerInfoPresenter(props: PlayerInfoProps) : RComponent<PlayerInfoProps,
}
}
}
-
-fun RBuilder.playerInfo(
- player: PlayerDTO,
- showUsername: Boolean = false,
- iconSize: Int = 30,
- orientation: FlexDirection = FlexDirection.row,
- ellipsize: Boolean = true,
-) = child(PlayerInfoPresenter::class) {
- attrs {
- this.player = player
- this.showUsername = showUsername
- this.iconSize = iconSize
- this.orientation = orientation
- this.ellipsize = ellipsize
- }
-}
-
-fun RBuilder.currentPlayerInfo() = playerInfo {}
-
-private val playerInfo = connectState(
- clazz = PlayerInfoPresenter::class,
- mapStateToProps = { state, _ ->
- player = state.connectedPlayer
- iconSize = 30
- showUsername = true
- orientation = FlexDirection.row
- ellipsize = false
- },
-)
diff --git a/sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/components/home/ChooseNameForm.kt b/sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/components/home/ChooseNameForm.kt
index 27a2057c..8de9f53e 100644
--- a/sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/components/home/ChooseNameForm.kt
+++ b/sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/components/home/ChooseNameForm.kt
@@ -2,93 +2,89 @@ package org.luxons.sevenwonders.ui.components.home
import blueprintjs.core.*
import blueprintjs.icons.*
-import kotlinx.css.*
-import kotlinx.html.js.*
+import csstype.*
+import emotion.react.*
import org.luxons.sevenwonders.ui.redux.*
+import org.luxons.sevenwonders.ui.utils.*
import react.*
-import styled.*
+import react.dom.events.*
+import react.dom.html.ReactHTML.div
+import react.dom.html.ReactHTML.form
+import web.html.*
-private external interface ChooseNameFormProps : PropsWithChildren {
- var chooseUsername: (String) -> Unit
+val ChooseNameForm = VFC {
+ val dispatch = useSwDispatch()
+ ChooseNameFormPresenter {
+ chooseUsername = { name -> dispatch(RequestChooseName(name)) }
+ }
}
-private external interface ChooseNameFormState : State {
- var username: String
+private external interface ChooseNameFormPresenterProps : PropsWithChildren {
+ var chooseUsername: (String) -> Unit
}
-private class ChooseNameForm(props: ChooseNameFormProps) : RComponent<ChooseNameFormProps, ChooseNameFormState>(props) {
-
- override fun ChooseNameFormState.init(props: ChooseNameFormProps) {
- username = ""
- }
+private val ChooseNameFormPresenter = FC<ChooseNameFormPresenterProps> { props ->
+ var usernameState by useState("")
- override fun RBuilder.render() {
- styledForm {
- css {
- display = Display.flex
- flexDirection = FlexDirection.row
+ form {
+ css {
+ display = Display.flex
+ flexDirection = FlexDirection.row
+ }
+ onSubmit = { e ->
+ e.preventDefault()
+ props.chooseUsername(usernameState)
+ }
+ RandomNameButton {
+ onClick = { usernameState = randomGreekName() }
+ }
+ spacer()
+ BpInputGroup {
+ large = true
+ placeholder = "Username"
+ rightElement = SubmitButton.create {
+ onClick = { e ->
+ e.preventDefault()
+ props.chooseUsername(usernameState)
+ }
}
- attrs.onSubmitFunction = { e ->
- e.preventDefault()
- chooseUsername()
+ value = usernameState
+ onChange = { e ->
+ val input = e.currentTarget
+ usernameState = input.value
}
- randomNameButton()
- spacer()
- bpInputGroup(
- large = true,
- placeholder = "Username",
- rightElement = submitButton(),
- value = state.username,
- onChange = { e ->
- val input = e.currentTarget
- setState {
- username = input.value
- }
- },
- )
}
}
+}
- private fun submitButton(): ReactElement<*> = buildElement {
- bpButton(
- minimal = true,
- icon = IconNames.ARROW_RIGHT,
- intent = Intent.PRIMARY,
- onClick = { e ->
- e.preventDefault()
- chooseUsername()
- },
- )
- }
-
- private fun RBuilder.randomNameButton() {
- bpButton(
- title = "Generate random name",
- large = true,
- icon = IconNames.RANDOM,
- intent = Intent.PRIMARY,
- onClick = { fillRandomUsername() },
- )
- }
+private external interface SpecificButtonProps : Props {
+ var onClick: MouseEventHandler<HTMLElement>?
+}
- private fun fillRandomUsername() {
- setState { username = randomGreekName() }
+private val SubmitButton = FC<SpecificButtonProps> { props ->
+ BpButton {
+ minimal = true
+ icon = IconNames.ARROW_RIGHT
+ intent = Intent.PRIMARY
+ onClick = props.onClick
}
+}
- private fun chooseUsername() {
- props.chooseUsername(state.username)
+private val RandomNameButton = FC<SpecificButtonProps> { props ->
+ BpButton {
+ title = "Generate random name"
+ large = true
+ icon = IconNames.RANDOM
+ intent = Intent.PRIMARY
+ onClick = props.onClick
}
+}
- // TODO this is so bad I'm dying inside
- private fun RBuilder.spacer() {
- styledDiv {
- css {
- margin(2.px)
- }
+// TODO this is so bad I'm dying inside
+private fun ChildrenBuilder.spacer() {
+ div {
+ css {
+ margin = Margin(all = 2.px)
}
}
}
-
-val chooseNameForm: ComponentClass<PropsWithChildren> = connectDispatch(ChooseNameForm::class) { dispatch, _ ->
- chooseUsername = { name -> dispatch(RequestChooseName(name)) }
-}
diff --git a/sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/components/home/Home.kt b/sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/components/home/Home.kt
index 4b209979..b821b23d 100644
--- a/sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/components/home/Home.kt
+++ b/sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/components/home/Home.kt
@@ -1,21 +1,22 @@
package org.luxons.sevenwonders.ui.components.home
-import org.luxons.sevenwonders.ui.components.GlobalStyles
-import react.RBuilder
-import react.dom.*
-import styled.css
-import styled.styledDiv
+import emotion.react.*
+import org.luxons.sevenwonders.ui.components.*
+import react.*
+import react.dom.html.ReactHTML.div
+import react.dom.html.ReactHTML.img
private const val LOGO = "images/logo-7-wonders.png"
-fun RBuilder.home() = styledDiv {
- css {
- +GlobalStyles.fullscreen
- +GlobalStyles.zeusBackground
- +HomeStyles.centerChildren
- }
+val Home = VFC("Home") {
+ div {
+ css(GlobalStyles.fullscreen, GlobalStyles.zeusBackground, HomeStyles.centerChildren) {}
- img(src = LOGO, alt = "Seven Wonders") {}
+ img {
+ src = LOGO
+ alt = "Seven Wonders"
+ }
- chooseNameForm {}
+ ChooseNameForm()
+ }
}
diff --git a/sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/components/home/HomeStyles.kt b/sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/components/home/HomeStyles.kt
index 10037b36..fa0a83ad 100644
--- a/sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/components/home/HomeStyles.kt
+++ b/sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/components/home/HomeStyles.kt
@@ -1,14 +1,14 @@
package org.luxons.sevenwonders.ui.components.home
-import kotlinx.css.*
-import styled.StyleSheet
+import csstype.*
+import emotion.css.*
-object HomeStyles : StyleSheet("HomeStyles", isStatic = true) {
+object HomeStyles {
- val centerChildren by css {
+ val centerChildren = ClassName {
display = Display.flex
flexDirection = FlexDirection.column
- alignItems = Align.center
+ alignItems = AlignItems.center
justifyContent = JustifyContent.center
}
}
diff --git a/sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/components/lobby/Lobby.kt b/sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/components/lobby/Lobby.kt
index 7435ffb0..83f6aa7b 100644
--- a/sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/components/lobby/Lobby.kt
+++ b/sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/components/lobby/Lobby.kt
@@ -3,31 +3,51 @@ package org.luxons.sevenwonders.ui.components.lobby
import blueprintjs.core.*
import blueprintjs.icons.*
import csstype.*
-import kotlinx.css.*
-import kotlinx.css.Display
-import kotlinx.css.JustifyContent
-import kotlinx.css.Position
-import kotlinx.css.pct
-import kotlinx.css.properties.*
-import kotlinx.css.px
-import kotlinx.css.rem
+import csstype.Position
+import emotion.react.*
import org.luxons.sevenwonders.model.api.*
import org.luxons.sevenwonders.model.wonders.*
-import org.luxons.sevenwonders.ui.components.GlobalStyles
+import org.luxons.sevenwonders.ui.components.*
import org.luxons.sevenwonders.ui.redux.*
+import org.luxons.sevenwonders.ui.utils.*
import react.*
-import react.State
-import react.dom.*
-import styled.*
+import react.dom.html.ReactHTML.div
+import react.dom.html.ReactHTML.h1
+import react.dom.html.ReactHTML.h2
+import react.dom.html.ReactHTML.h3
+import react.dom.html.ReactHTML.h4
private val BOT_NAMES = listOf("Wall-E", "B-Max", "Sonny", "T-800", "HAL", "GLaDOS", "R2-D2", "Bender", "AWESOM-O")
-external interface LobbyStateProps : PropsWithChildren {
- var currentGame: LobbyDTO?
- var currentPlayer: PlayerDTO?
+val Lobby = VFC(displayName = "Lobby") {
+ val lobby = useSwSelector { it.currentLobby }
+ val player = useSwSelector { it.currentPlayer }
+
+ val dispatch = useSwDispatch()
+
+ if (lobby == null || player == null) {
+ BpNonIdealState {
+ icon = IconNames.ERROR
+ titleText = "Error: no current game"
+ }
+ } else {
+ LobbyPresenter {
+ currentGame = lobby
+ currentPlayer = player
+
+ startGame = { dispatch(RequestStartGame()) }
+ addBot = { name -> dispatch(RequestAddBot(name)) }
+ leaveLobby = { dispatch(RequestLeaveLobby()) }
+ disbandLobby = { dispatch(RequestDisbandLobby()) }
+ reorderPlayers = { orderedPlayers -> dispatch(RequestReorderPlayers(orderedPlayers)) }
+ reassignWonders = { wonders -> dispatch(RequestReassignWonders(wonders)) }
+ }
+ }
}
-external interface LobbyDispatchProps : PropsWithChildren {
+private external interface LobbyPresenterProps : Props {
+ var currentGame: LobbyDTO
+ var currentPlayer: PlayerDTO
var startGame: () -> Unit
var addBot: (displayName: String) -> Unit
var leaveLobby: () -> Unit
@@ -36,235 +56,217 @@ external interface LobbyDispatchProps : PropsWithChildren {
var reassignWonders: (wonders: List<AssignedWonder>) -> Unit
}
-interface LobbyProps : LobbyDispatchProps, LobbyStateProps
-
-class LobbyPresenter(props: LobbyProps) : RComponent<LobbyProps, State>(props) {
-
- override fun RBuilder.render() {
- val currentGame = props.currentGame
- val currentPlayer = props.currentPlayer
- if (currentGame == null || currentPlayer == null) {
- bpNonIdealState(icon = IconNames.ERROR, title = "Error: no current game")
- return
+private val LobbyPresenter = FC<LobbyPresenterProps> { props ->
+ div {
+ css(GlobalStyles.fullscreen, GlobalStyles.zeusBackground) {
+ padding = Padding(all = 1.rem)
}
- styledDiv {
- css {
- +GlobalStyles.fullscreen
- +GlobalStyles.zeusBackground
- padding(all = 1.rem)
+ div {
+ css(ClassName(Classes.DARK), LobbyStyles.contentContainer) {
+ margin = Margin(vertical = 0.rem, horizontal = Auto.auto)
+ maxWidth = GlobalStyles.preGameWidth
}
- styledDiv {
+ h1 { +"${props.currentGame.name} — Lobby" }
+
+ radialPlayerList(props.currentGame.players, props.currentPlayer) {
css {
- classes.add(Classes.DARK)
- +LobbyStyles.contentContainer
- }
- h1 { +"${currentGame.name} — Lobby" }
-
- radialPlayerList(currentGame.players, currentPlayer) {
- css {
- // to make players more readable on the background
- background = "radial-gradient(closest-side, black 20%, transparent)"
- // make it bigger so the background covers more ground
- width = 40.rem
- height = 40.rem
- }
+ // to make players more readable on the background
+ background = "radial-gradient(closest-side, black 20%, transparent)".unsafeCast<Gradient>()
+ // make it bigger so the background covers more ground
+ width = 40.rem
+ height = 40.rem
}
- actionButtons(currentPlayer, currentGame)
+ }
+ actionButtons(props.currentPlayer, props.currentGame, props.startGame, props.leaveLobby, props.disbandLobby, props.addBot)
- if (currentPlayer.isGameOwner) {
- setupPanel(currentGame)
- }
+ if (props.currentPlayer.isGameOwner) {
+ setupPanel(props.currentGame, props.reorderPlayers, props.reassignWonders)
}
}
}
+}
+
+private fun ChildrenBuilder.actionButtons(
+ currentPlayer: PlayerDTO,
+ currentGame: LobbyDTO,
+ startGame: () -> Unit,
+ leaveLobby: () -> Unit,
+ disbandLobby: () -> Unit,
+ addBot: (String) -> Unit,
+) {
+ div {
+ css {
+ position = Position.absolute
+ bottom = 2.rem
+ left = 50.pct
+ transform = translate((-50).pct)
- private fun RBuilder.actionButtons(currentPlayer: PlayerDTO, currentGame: LobbyDTO) {
- styledDiv {
- css {
- position = Position.absolute
- bottom = 2.rem
- left = 50.pct
- transform { translate((-50).pct) }
-
- width = 70.pct
- display = Display.flex
- justifyContent = JustifyContent.spaceAround
+ width = 70.pct
+ display = Display.flex
+ justifyContent = JustifyContent.spaceAround
+ }
+ if (currentPlayer.isGameOwner) {
+ BpButtonGroup {
+ leaveButton(leaveLobby)
+ disbandButton(disbandLobby)
}
- if (currentPlayer.isGameOwner) {
- bpButtonGroup {
- leaveButton()
- disbandButton()
- }
- bpButtonGroup {
- addBotButton(currentGame)
- startButton(currentGame, currentPlayer)
- }
- } else {
- leaveButton()
+ BpButtonGroup {
+ addBotButton(currentGame, addBot)
+ startButton(currentGame.startability(currentPlayer.username), startGame)
}
+ } else {
+ leaveButton(leaveLobby)
}
}
+}
- private fun RBuilder.startButton(currentGame: LobbyDTO, currentPlayer: PlayerDTO) {
- val startability = currentGame.startability(currentPlayer.username)
- bpButton(
- large = true,
- intent = Intent.PRIMARY,
- icon = IconNames.PLAY,
- title = startability.tooltip,
- disabled = !startability.canDo,
- onClick = { props.startGame() },
- ) {
- +"START"
- }
+private fun ChildrenBuilder.startButton(startability: Actionability, startGame: () -> Unit) {
+ BpButton {
+ large = true
+ intent = Intent.PRIMARY
+ icon = IconNames.PLAY
+ title = startability.tooltip
+ disabled = !startability.canDo
+ onClick = { startGame() }
+
+ +"START"
}
+}
- private fun RBuilder.setupPanel(currentGame: LobbyDTO) {
- styledDiv {
- css {
- +LobbyStyles.setupPanel
- }
- bpCard(Elevation.TWO, className = ClassName(Classes.DARK)) {
- styledH2 {
- css {
- margin(top = 0.px)
- }
- +"Game setup"
- }
- bpDivider()
- h3 {
- +"Players"
- }
- reorderPlayersButton(currentGame)
- h3 {
- +"Wonders"
+private fun ChildrenBuilder.setupPanel(
+ currentGame: LobbyDTO,
+ reorderPlayers: (usernames: List<String>) -> Unit,
+ reassignWonders: (wonders: List<AssignedWonder>) -> Unit,
+) {
+ div {
+ className = LobbyStyles.setupPanel
+
+ BpCard {
+ elevation = Elevation.TWO
+ className = ClassName(Classes.DARK)
+
+ h2 {
+ css {
+ marginTop = 0.px
}
- randomizeWondersButton(currentGame)
- wonderSideSelectionGroup(currentGame)
+ +"Game setup"
+ }
+ BpDivider()
+ h3 {
+ +"Players"
+ }
+ reorderPlayersButton(currentGame, reorderPlayers)
+ h3 {
+ +"Wonders"
+ }
+ WonderSettingsGroup {
+ this.currentGame = currentGame
+ this.reassignWonders = reassignWonders
}
}
}
+}
- private fun RBuilder.addBotButton(currentGame: LobbyDTO) {
- bpButton(
- large = true,
- icon = IconNames.PLUS,
- rightIcon = IconNames.DESKTOP,
- intent = Intent.PRIMARY,
- title = if (currentGame.maxPlayersReached) "Max players reached" else "Add a bot to this game",
- disabled = currentGame.maxPlayersReached,
- onClick = { addBot(currentGame) },
- )
+private fun ChildrenBuilder.addBotButton(currentGame: LobbyDTO, addBot: (String) -> Unit) {
+ BpButton {
+ large = true
+ icon = IconNames.PLUS
+ rightIcon = IconNames.DESKTOP
+ intent = Intent.PRIMARY
+ title = if (currentGame.maxPlayersReached) "Max players reached" else "Add a bot to this game"
+ disabled = currentGame.maxPlayersReached
+ onClick = { addBot(randomBotNameUnusedIn(currentGame)) }
}
+}
- private fun addBot(currentGame: LobbyDTO) {
- val availableBotNames = BOT_NAMES.filter { name ->
- currentGame.players.all { it.displayName != name }
- }
- props.addBot(availableBotNames.random())
+private fun randomBotNameUnusedIn(currentGame: LobbyDTO): String {
+ val availableBotNames = BOT_NAMES.filter { name ->
+ currentGame.players.none { it.displayName == name }
}
+ return availableBotNames.random()
+}
- private fun RBuilder.reorderPlayersButton(currentGame: LobbyDTO) {
- bpButton(
- icon = IconNames.RANDOM,
- rightIcon = IconNames.PEOPLE,
- title = "Re-order players randomly",
- onClick = { reorderPlayers(currentGame) },
- ) {
- +"Reorder players"
- }
- }
+private fun ChildrenBuilder.reorderPlayersButton(currentGame: LobbyDTO, reorderPlayers: (usernames: List<String>) -> Unit) {
+ BpButton {
+ icon = IconNames.RANDOM
+ rightIcon = IconNames.PEOPLE
+ title = "Re-order players randomly"
+ onClick = { reorderPlayers(currentGame.players.map { it.username }.shuffled()) }
- private fun reorderPlayers(currentGame: LobbyDTO) {
- props.reorderPlayers(currentGame.players.map { it.username }.shuffled())
+ +"Reorder players"
}
+}
- private fun RBuilder.randomizeWondersButton(currentGame: LobbyDTO) {
- bpButton(
- icon = IconNames.RANDOM,
- title = "Re-assign wonders to players randomly",
- onClick = { randomizeWonders(currentGame) },
- ) {
- +"Randomize wonders"
- }
+private external interface WonderSettingsGroupProps : Props {
+ var currentGame: LobbyDTO
+ var reassignWonders: (List<AssignedWonder>) -> Unit
+}
+
+private val WonderSettingsGroup = FC<WonderSettingsGroupProps> { props ->
+ val reassignWonders = props.reassignWonders
+
+ BpButton {
+ icon = IconNames.RANDOM
+ title = "Re-assign wonders to players randomly"
+ onClick = { reassignWonders(randomWonderAssignments(props.currentGame)) }
+
+ +"Randomize wonders"
+ }
+ h4 {
+ +"Select wonder sides:"
}
+ BpButtonGroup {
+ BpButton {
+ icon = IconNames.RANDOM
+ title = "Re-roll wonder sides randomly"
+ onClick = { reassignWonders(assignedWondersWithRandomSides(props.currentGame)) }
+ }
+ BpButton {
+ title = "Choose side A for everyone"
+ onClick = { reassignWonders(assignedWondersWithForcedSide(props.currentGame, WonderSide.A)) }
- private fun RBuilder.wonderSideSelectionGroup(currentGame: LobbyDTO) {
- h4 {
- +"Select wonder sides:"
+ +"A"
}
- bpButtonGroup {
- bpButton(
- icon = IconNames.RANDOM,
- title = "Re-roll wonder sides randomly",
- onClick = { randomizeWonderSides(currentGame) },
- )
- bpButton(
- title = "Choose side A for everyone",
- onClick = { setWonderSides(currentGame, WonderSide.A) },
- ) {
- +"A"
- }
- bpButton(
- title = "Choose side B for everyone",
- onClick = { setWonderSides(currentGame, WonderSide.B) },
- ) {
- +"B"
- }
+ BpButton {
+ title = "Choose side B for everyone"
+ onClick = { reassignWonders(assignedWondersWithForcedSide(props.currentGame, WonderSide.B)) }
+
+ +"B"
}
}
+}
- private fun randomizeWonders(currentGame: LobbyDTO) {
- props.reassignWonders(currentGame.allWonders.deal(currentGame.players.size))
- }
+private fun randomWonderAssignments(currentGame: LobbyDTO): List<AssignedWonder> =
+ currentGame.allWonders.deal(currentGame.players.size)
- private fun randomizeWonderSides(currentGame: LobbyDTO) {
- props.reassignWonders(currentGame.players.map { currentGame.findWonder(it.wonder.name).withRandomSide() })
- }
+private fun assignedWondersWithForcedSide(
+ currentGame: LobbyDTO,
+ side: WonderSide
+) = currentGame.players.map { currentGame.findWonder(it.wonder.name).withSide(side) }
- private fun setWonderSides(currentGame: LobbyDTO, side: WonderSide) {
- props.reassignWonders(currentGame.players.map { currentGame.findWonder(it.wonder.name).withSide(side) })
- }
+private fun assignedWondersWithRandomSides(currentGame: LobbyDTO) =
+ currentGame.players.map { currentGame.findWonder(it.wonder.name) }.map { it.withRandomSide() }
- private fun RBuilder.leaveButton() {
- bpButton(
- large = true,
- intent = Intent.WARNING,
- icon = "arrow-left",
- title = "Leave the lobby and go back to the game browser",
- onClick = { props.leaveLobby() },
- ) {
- +"LEAVE"
- }
- }
+private fun ChildrenBuilder.leaveButton(leaveLobby: () -> Unit) {
+ BpButton {
+ large = true
+ intent = Intent.WARNING
+ icon = "arrow-left"
+ title = "Leave the lobby and go back to the game browser"
+ onClick = { leaveLobby() }
- private fun RBuilder.disbandButton() {
- bpButton(
- large = true,
- intent = Intent.DANGER,
- icon = IconNames.DELETE,
- title = "Disband the group and go back to the game browser",
- onClick = { props.disbandLobby() },
- ) {
- +"DISBAND"
- }
+ +"LEAVE"
}
}
-fun RBuilder.lobby() = lobby {}
-
-private val lobby = connectStateAndDispatch<LobbyStateProps, LobbyDispatchProps, LobbyProps>(
- clazz = LobbyPresenter::class,
- mapStateToProps = { state, _ ->
- currentGame = state.currentLobby
- currentPlayer = state.currentPlayer
- },
- mapDispatchToProps = { dispatch, _ ->
- startGame = { dispatch(RequestStartGame()) }
- addBot = { name -> dispatch(RequestAddBot(name)) }
- leaveLobby = { dispatch(RequestLeaveLobby()) }
- disbandLobby = { dispatch(RequestDisbandLobby()) }
- reorderPlayers = { orderedPlayers -> dispatch(RequestReorderPlayers(orderedPlayers)) }
- reassignWonders = { wonders -> dispatch(RequestReassignWonders(wonders)) }
- },
-)
+private fun ChildrenBuilder.disbandButton(disbandLobby: () -> Unit) {
+ BpButton {
+ large = true
+ intent = Intent.DANGER
+ icon = IconNames.DELETE
+ title = "Disband the group and go back to the game browser"
+ onClick = { disbandLobby() }
+
+ +"DISBAND"
+ }
+}
diff --git a/sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/components/lobby/LobbyStyles.kt b/sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/components/lobby/LobbyStyles.kt
index bbfc491f..fe20ac86 100644
--- a/sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/components/lobby/LobbyStyles.kt
+++ b/sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/components/lobby/LobbyStyles.kt
@@ -1,17 +1,17 @@
package org.luxons.sevenwonders.ui.components.lobby
-import kotlinx.css.*
-import org.luxons.sevenwonders.ui.components.GlobalStyles
-import styled.StyleSheet
+import csstype.*
+import emotion.css.*
+import org.luxons.sevenwonders.ui.components.*
-object LobbyStyles : StyleSheet("LobbyStyles", isStatic = true) {
+object LobbyStyles {
- val contentContainer by css {
- margin(horizontal = LinearDimension.auto)
+ val contentContainer = ClassName {
+ margin = Margin(vertical = 0.px, horizontal = Auto.auto)
maxWidth = GlobalStyles.preGameWidth
}
- val setupPanel by css {
+ val setupPanel = ClassName {
position = Position.fixed
top = 2.rem
right = 1.rem
diff --git a/sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/components/lobby/RadialList.kt b/sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/components/lobby/RadialList.kt
index 8e99c23d..e9853588 100644
--- a/sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/components/lobby/RadialList.kt
+++ b/sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/components/lobby/RadialList.kt
@@ -1,19 +1,16 @@
package org.luxons.sevenwonders.ui.components.lobby
-import kotlinx.css.*
-import kotlinx.css.properties.*
-import kotlinx.html.DIV
-import org.luxons.sevenwonders.ui.components.GlobalStyles
-import react.RBuilder
-import react.ReactElement
-import react.dom.*
-import styled.StyledDOMBuilder
-import styled.css
-import styled.styledDiv
-import styled.styledLi
-import styled.styledUl
+import csstype.*
+import emotion.react.*
+import org.luxons.sevenwonders.ui.components.*
+import react.*
+import react.dom.html.*
+import react.dom.html.ReactHTML.div
+import react.dom.html.ReactHTML.li
+import react.dom.html.ReactHTML.ul
+import web.html.*
-fun <T> RBuilder.radialList(
+fun <T> ChildrenBuilder.radialList(
items: List<T>,
centerElement: ReactElement<*>,
renderItem: (T) -> ReactElement<*>,
@@ -21,15 +18,14 @@ fun <T> RBuilder.radialList(
itemWidth: Int,
itemHeight: Int,
options: RadialConfig = RadialConfig(),
- block: StyledDOMBuilder<DIV>.() -> Unit = {},
+ block: HTMLAttributes<HTMLDivElement>.() -> Unit = {},
) {
val containerWidth = options.diameter + itemWidth
val containerHeight = options.diameter + itemHeight
- styledDiv {
- css {
+ div {
+ css(GlobalStyles.fixedCenter) {
zeroMargins()
- +GlobalStyles.fixedCenter
width = containerWidth.px
height = containerHeight.px
}
@@ -39,18 +35,22 @@ fun <T> RBuilder.radialList(
}
}
-private fun <T> RBuilder.radialListItems(
+private fun <T> ChildrenBuilder.radialListItems(
items: List<T>,
renderItem: (T) -> ReactElement<*>,
getKey: (T) -> String,
radialConfig: RadialConfig,
) {
val offsets = offsetsFromCenter(items.size, radialConfig)
- styledUl {
+ ul {
css {
zeroMargins()
- transition(property = "all", duration = 500.ms, timing = Timing.easeInOut)
- zIndex = 1
+ transition = Transition(
+ property = TransitionProperty.all,
+ duration = 500.ms,
+ timingFunction = TransitionTimingFunction.easeInOut,
+ )
+ zIndex = integer(1)
width = radialConfig.diameter.px
height = radialConfig.diameter.px
absoluteCenter()
@@ -67,52 +67,50 @@ private fun <T> RBuilder.radialListItems(
}
}
-private fun RBuilder.radialListItem(item: ReactElement<*>, key: String, offset: CartesianCoords) {
- styledLi {
+private fun ChildrenBuilder.radialListItem(item: ReactElement<*>, key: String, offset: CartesianCoords) {
+ li {
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 {
- this.key = key
+ listStyleType = Globals.unset
+ transition = Transition(
+ property = TransitionProperty.all,
+ duration = 500.ms,
+ timingFunction = TransitionTimingFunction.easeInOut,
+ )
+ zIndex = integer(1)
+ transform = translate(offset.x.px - 50.pct, offset.y.px - 50.pct)
}
+ this.key = key
+
child(item)
}
}
-private fun RBuilder.radialListCenter(centerElement: ReactElement<*>?) {
+private fun ChildrenBuilder.radialListCenter(centerElement: ReactElement<*>?) {
if (centerElement == null) {
return
}
- styledDiv {
+ div {
css {
- zIndex = 0
+ zIndex = integer(0)
absoluteCenter()
}
child(centerElement)
}
}
-private fun CssBuilder.absoluteCenter() {
+private fun PropertiesBuilder.absoluteCenter() {
position = Position.absolute
left = 50.pct
top = 50.pct
- transform {
- translate((-50).pct, (-50).pct)
- }
+ transform = translate((-50).pct, (-50).pct)
}
-private fun CssBuilder.zeroMargins() {
- margin(all = 0.px)
- padding(all = 0.px)
+private fun PropertiesBuilder.zeroMargins() {
+ margin = Margin(vertical = 0.px, horizontal = 0.px)
+ padding = Padding(vertical = 0.px, horizontal = 0.px)
}
diff --git a/sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/components/lobby/RadialPlayerList.kt b/sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/components/lobby/RadialPlayerList.kt
index 2084b7c0..290dd83f 100644
--- a/sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/components/lobby/RadialPlayerList.kt
+++ b/sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/components/lobby/RadialPlayerList.kt
@@ -1,39 +1,36 @@
package org.luxons.sevenwonders.ui.components.lobby
-import blueprintjs.core.bpIcon
-import blueprintjs.core.bpTag
+import blueprintjs.core.*
+import blueprintjs.icons.*
import csstype.*
-import kotlinx.css.*
-import kotlinx.css.Color
-import kotlinx.css.Display
-import kotlinx.css.FlexDirection
-import kotlinx.css.px
-import kotlinx.css.rem
-import kotlinx.html.DIV
-import org.luxons.sevenwonders.model.api.PlayerDTO
+import emotion.react.*
+import org.luxons.sevenwonders.model.api.*
import org.luxons.sevenwonders.model.api.actions.Icon
-import org.luxons.sevenwonders.model.wonders.WonderSide
-import react.RBuilder
-import react.ReactElement
-import react.buildElement
-import styled.*
+import org.luxons.sevenwonders.model.wonders.*
+import org.luxons.sevenwonders.ui.utils.*
+import react.*
+import react.dom.html.*
+import react.dom.html.ReactHTML.div
+import react.dom.html.ReactHTML.span
+import web.html.*
-fun RBuilder.radialPlayerList(
+fun ChildrenBuilder.radialPlayerList(
players: List<PlayerDTO>,
currentPlayer: PlayerDTO,
- block: StyledDOMBuilder<DIV>.() -> Unit = {},
+ block: HTMLAttributes<HTMLDivElement>.() -> Unit = {},
) {
val playerItems = players //
.map { PlayerItem.Player(it) }
.growWithPlaceholders(targetSize = 3)
.withUserFirst(currentPlayer)
- val tableImg = buildElement { lobbyWoodenTable(diameter = 200.px, borderSize = 15.px) }
-
radialList(
items = playerItems,
- centerElement = tableImg,
- renderItem = { buildElement { playerElement(it) } },
+ centerElement = LobbyWoodenTable.create {
+ diameter = 200.px
+ borderSize = 15.px
+ },
+ renderItem = { PlayerElement.create { playerItem = it } },
getKey = { it.key },
itemWidth = 120,
itemHeight = 100,
@@ -60,74 +57,79 @@ private fun List<PlayerItem>.withUserFirst(me: PlayerDTO): List<PlayerItem> {
private sealed class PlayerItem {
abstract val key: String
abstract val playerText: String
- abstract val opacity: Double
+ abstract val opacity: Opacity
abstract val icon: ReactElement<*>
data class Player(val player: PlayerDTO) : PlayerItem() {
override val key = player.username
override val playerText = player.displayName
- override val opacity = 1.0
- override val icon = buildElement {
- userIcon(
- icon = player.icon ?: when {
- player.isGameOwner -> Icon("badge")
- else -> Icon("user")
- },
- title = if (player.isGameOwner) "Game owner" else null,
- )
- }
+ override val opacity = number(1.0)
+ override val icon = createUserIcon(
+ icon = player.icon ?: when {
+ player.isGameOwner -> Icon(IconNames.BADGE)
+ else -> Icon(IconNames.USER)
+ },
+ title = if (player.isGameOwner) "Game owner" else null,
+ )
}
data class Placeholder(val index: Int) : PlayerItem() {
override val key = "player-placeholder-$index"
override val playerText = "?"
- override val opacity = 0.4
- override val icon = buildElement {
- userIcon(
- icon = Icon("user"),
- title = "Waiting for player...",
- )
- }
+ override val opacity = number(0.4)
+ override val icon = createUserIcon(
+ icon = Icon(IconNames.USER),
+ title = "Waiting for player...",
+ )
}
}
-private fun RBuilder.userIcon(icon: Icon, title: String?) = bpIcon(
- name = icon.name,
- size = 50,
- title = title,
-)
+private fun createUserIcon(icon: Icon, title: String?) = BpIcon.create {
+ this.icon = icon.name
+ this.size = 50
+ this.title = title
+}
-private fun RBuilder.playerElement(playerItem: PlayerItem) {
- styledDiv {
+private external interface PlayerElementProps : Props {
+ var playerItem: PlayerItem
+}
+
+private val PlayerElement = FC<PlayerElementProps>(displayName = "PlayerElement") { props ->
+ val playerItem = props.playerItem
+ div {
css {
display = Display.flex
flexDirection = FlexDirection.column
- alignItems = Align.center
+ alignItems = AlignItems.center
opacity = playerItem.opacity
}
child(playerItem.icon)
- styledSpan {
+ span {
css {
fontSize = if (playerItem is PlayerItem.Placeholder) 1.5.rem else 0.9.rem
}
+playerItem.playerText
}
if (playerItem is PlayerItem.Player) {
- styledDiv {
+ div {
val wonder = playerItem.player.wonder
+
css {
marginTop = 0.3.rem
- // this is to overcome ".bp4-dark .bp4-tag" on the nested bpTag
children(".wonder-tag") {
color = Color("#f5f8fa") // blueprintjs dark theme color (removed by .bp4-tag)
backgroundColor = when (wonder.side) {
- WonderSide.A -> Color.seaGreen
- WonderSide.B -> Color.darkRed
+ WonderSide.A -> NamedColor.seagreen
+ WonderSide.B -> NamedColor.darkred
}
}
}
- bpTag(round = true, className = ClassName("wonder-tag")) {
+
+ BpTag {
+ round = true
+ className = ClassName("wonder-tag")
+
+"${wonder.name} ${wonder.side}"
}
}
diff --git a/sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/components/lobby/Table.kt b/sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/components/lobby/Table.kt
index b9c799e2..2fa3b246 100644
--- a/sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/components/lobby/Table.kt
+++ b/sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/components/lobby/Table.kt
@@ -1,81 +1,96 @@
package org.luxons.sevenwonders.ui.components.lobby
-import kotlinx.css.*
-import kotlinx.css.properties.*
-import kotlinx.html.DIV
-import react.RBuilder
-import styled.StyledDOMBuilder
-import styled.animation
-import styled.css
-import styled.styledDiv
+import csstype.*
+import emotion.css.*
+import emotion.react.*
+import emotion.styled.*
+import org.luxons.sevenwonders.ui.utils.*
+import react.*
+import react.dom.html.ReactHTML.div
-private const val FIRE_REFLECTION_COLOR = "#b85e00"
+private val FIRE_REFLECTION_COLOR = Color("#b85e00")
+
+private external interface CircleProps : PropsWithChildren, PropsWithClassName {
+ var diameter: Length
+}
+
+private val Circle = FC<CircleProps>("Circle") { props ->
+ div {
+ css(props.className) {
+ width = props.diameter
+ height = props.diameter
+ borderRadius = 50.pct
+ }
+ child(props.children)
+ }
+}
+
+private val OverlayCircle = Circle.styled { _, _ ->
+ position = Position.absolute
+ top = 0.px
+ left = 0.px
+}
+
+external interface LobbyWoodenTableProps : Props {
+ var diameter: Length
+ var borderSize: Length
+}
+
+val LobbyWoodenTable = FC<LobbyWoodenTableProps>("LobbyWoodenTable") { props ->
+ Circle {
+ diameter = props.diameter
-fun RBuilder.lobbyWoodenTable(diameter: LinearDimension, borderSize: LinearDimension = 20.px) {
- circle(diameter) {
css {
backgroundColor = Color("#3d1e0e")
}
- circle(diameter = diameter - borderSize) {
+
+ Circle {
+ diameter = props.diameter - props.borderSize
css {
position = Position.absolute
- top = borderSize / 2
- left = borderSize / 2
- background = "linear-gradient(45deg, #88541e, #995645, #52251a)"
+ top = props.borderSize / 2
+ left = props.borderSize / 2
+ background = linearGradient(45.deg, Color("#88541e"), Color("#995645"), Color("#52251a"))
}
}
// flame reflection coming from bottom-right
- overlayCircle(diameter) {
+ OverlayCircle {
+ diameter = props.diameter
+
css {
- background = "linear-gradient(-45deg, $FIRE_REFLECTION_COLOR 10%, transparent 50%)"
+ background =
+ linearGradient((-45).deg, stop(FIRE_REFLECTION_COLOR, 10.pct), stop(NamedColor.transparent, 50.pct))
opacityAnimation(duration = 1.3.s)
}
}
// flame reflection coming from bottom-left
- overlayCircle(diameter) {
+ OverlayCircle {
+ diameter = props.diameter
+
css {
- background = "linear-gradient(45deg, $FIRE_REFLECTION_COLOR 20%, transparent 40%)"
+ background =
+ linearGradient(45.deg, stop(FIRE_REFLECTION_COLOR, 20.pct), stop(NamedColor.transparent, 40.pct))
opacityAnimation(duration = 0.8.s)
}
}
}
}
-private fun RBuilder.overlayCircle(diameter: LinearDimension, block: StyledDOMBuilder<DIV>.() -> Unit) {
- circle(diameter) {
- css {
- position = Position.absolute
- top = 0.px
- left = 0.px
- }
- block()
- }
-}
-
-private fun RBuilder.circle(diameter: LinearDimension, block: StyledDOMBuilder<DIV>.() -> Unit) {
- styledDiv {
- css {
- width = diameter
- height = diameter
- borderRadius = 50.pct
- }
- block()
- }
-}
-
-private fun CssBuilder.opacityAnimation(duration: Time) {
- animation(
- duration = duration,
- direction = AnimationDirection.alternate,
- iterationCount = IterationCount.infinite,
- timing = cubicBezier(0.4, 0.4, 0.4, 2.0)
- ) {
+private fun PropertiesBuilder.opacityAnimation(duration: Time) {
+ val keyframes = keyframes {
from {
- opacity = 0.0
+ opacity = number(0.0)
}
to {
- opacity = 0.35
+ opacity = number(0.35)
}
}
+ animation = Animation(
+ name = keyframes,
+ duration = duration,
+ timingFunction = cubicBezier(0.4, 0.4, 0.4, 2.0),
+ )
+ animationDirection = AnimationDirection.alternate
+ animationIterationCount = AnimationIterationCount.infinite
}
diff --git a/sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/redux/Utils.kt b/sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/redux/Utils.kt
index d5b3fffd..eb182dc7 100644
--- a/sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/redux/Utils.kt
+++ b/sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/redux/Utils.kt
@@ -1,43 +1,31 @@
package org.luxons.sevenwonders.ui.redux
import react.*
-import react.redux.rConnect
-import redux.RAction
-import redux.WrapperAction
-import kotlin.reflect.KClass
+import react.redux.*
+import redux.*
+import kotlin.reflect.*
-fun <DP : PropsWithChildren> connectDispatch(
- clazz: KClass<out RComponent<DP, out State>>,
- mapDispatchToProps: DP.((RAction) -> WrapperAction, PropsWithChildren) -> Unit,
-): ComponentClass<PropsWithChildren> {
- val connect = rConnect(mapDispatchToProps = mapDispatchToProps)
- return connect.invoke(clazz.js.unsafeCast<ComponentClass<DP>>())
-}
-
-fun <SP : PropsWithChildren> connectState(
- clazz: KClass<out RComponent<SP, out State>>,
- mapStateToProps: SP.(SwState, PropsWithChildren) -> Unit,
-): ComponentClass<PropsWithChildren> {
- val connect = rConnect(mapStateToProps = mapStateToProps)
- return connect.invoke(clazz.js.unsafeCast<ComponentClass<SP>>())
-}
+fun <R> useSwSelector(selector: (SwState) -> R) = useSelector(selector)
+fun useSwDispatch() = useDispatch<RAction, WrapperAction>()
-fun <SP : PropsWithChildren, OP : PropsWithChildren> connectStateWithOwnProps(
- clazz: KClass<out RComponent<SP, out State>>,
- mapStateToProps: SP.(SwState, OP) -> Unit,
-): ComponentClass<OP> {
- val connect = rConnect(mapStateToProps = mapStateToProps)
- return connect.invoke(clazz.js.unsafeCast<ComponentClass<SP>>())
-}
+fun <SP : Props, DP : Props, P : Props> connectStateAndDispatch(
+ clazz: KClass<out Component<P, out State>>,
+ mapStateToProps: SP.(SwState, Props) -> Unit,
+ mapDispatchToProps: DP.((RAction) -> WrapperAction, Props) -> Unit,
+): ComponentClass<Props> = connectStateAndDispatch(
+ component = clazz.react,
+ mapStateToProps = mapStateToProps,
+ mapDispatchToProps = mapDispatchToProps,
+)
-fun <SP : PropsWithChildren, DP : PropsWithChildren, P : PropsWithChildren> connectStateAndDispatch(
- clazz: KClass<out RComponent<P, out State>>,
- mapStateToProps: SP.(SwState, PropsWithChildren) -> Unit,
- mapDispatchToProps: DP.((RAction) -> WrapperAction, PropsWithChildren) -> Unit,
-): ComponentClass<PropsWithChildren> {
- val connect = rConnect<SwState, RAction, WrapperAction, PropsWithChildren, SP, DP, P>(
+fun <SP : Props, DP : Props, P : Props> connectStateAndDispatch(
+ component: ComponentClass<P>,
+ mapStateToProps: SP.(SwState, Props) -> Unit,
+ mapDispatchToProps: DP.((RAction) -> WrapperAction, Props) -> Unit,
+): ComponentClass<Props> {
+ val connect = rConnect<SwState, RAction, WrapperAction, Props, SP, DP, P>(
mapStateToProps = mapStateToProps,
mapDispatchToProps = mapDispatchToProps,
)
- return connect.invoke(clazz.js.unsafeCast<ComponentClass<P>>())
+ return connect.invoke(component)
}
diff --git a/sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/utils/StyleUtils.kt b/sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/utils/StyleUtils.kt
index d438c259..5dd274de 100644
--- a/sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/utils/StyleUtils.kt
+++ b/sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/utils/StyleUtils.kt
@@ -1,10 +1,42 @@
package org.luxons.sevenwonders.ui.utils
-import csstype.ClassName
-import kotlinx.css.*
-import styled.*
-import kotlin.reflect.*
+import csstype.*
+import js.core.*
+import react.dom.html.*
-fun <T : StyleSheet> T.getTypedClassName(getClass: (T) -> KProperty0<RuleSet>): ClassName {
- return ClassName(getClassName(getClass))
+/**
+ * The cubic-bezier() function defines a Cubic Bezier curve.
+ *
+ * A Cubic Bezier curve is defined by four points P0, P1, P2, and P3. P0 and P3 are the start and the end of the curve
+ * and, in CSS these points are fixed as the coordinates are ratios. P0 is (0, 0) and represents the initial time and
+ * the initial state, P3 is (1, 1) and represents the final time and the final state.
+ *
+ * The x coordinates provided here must be between 0 and 1 (the bezier curve points should be between the start time
+ * and end time, giving other values would make the curve go back in the past or further into the future).
+ *
+ * The y coordinates may be any value: the intermediate states can be below or above the start (0) or end (1) values.
+ */
+fun cubicBezier(x1: Double, y1: Double, x2: Double, y2: Double) =
+ "cubic-bezier($x1, $y1, $x2, $y2)".unsafeCast<AnimationTimingFunction>()
+
+fun Margin(all: AutoLength) = Margin(vertical = all, horizontal = all)
+
+fun Padding(all: Length) = Padding(vertical = all, horizontal = all)
+
+// this should work because NamedColor is ultimately a hex string in JS, not the actual name
+fun NamedColor.withAlpha(alpha: Double) = "$this${(alpha * 255).toInt().toString(16)}".unsafeCast<BackgroundColor>()
+
+operator fun FilterFunction.plus(other: FilterFunction) = "$this $other".unsafeCast<FilterFunction>()
+
+fun PropertiesBuilder.ancestorHover(selector: String, block: PropertiesBuilder.() -> Unit) =
+ "$selector:hover &".invoke(block)
+
+fun PropertiesBuilder.children(selector: String, block: PropertiesBuilder.() -> Unit) =
+ "& > $selector".invoke(block)
+
+fun PropertiesBuilder.descendants(selector: String, block: PropertiesBuilder.() -> Unit) =
+ "& $selector".invoke(block)
+
+fun HTMLAttributes<*>.inlineStyles(block: PropertiesBuilder.() -> Unit) {
+ style = jso(block)
}
bgstack15