Merge pull request #55 from PatilShreyas/sp/web-app

[Web] Design ReactJS Web app with Material Designing components
This commit is contained in:
John O'Reilly 2021-06-06 20:12:53 +01:00 committed by GitHub
commit 86cc905034
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
17 changed files with 327 additions and 22 deletions

View file

@ -87,6 +87,10 @@ Compose for Web
<br/>
<img width="564" alt="Screenshot 2021-05-31 at 21 29 53" src="https://user-images.githubusercontent.com/6302/120240074-9dc7ea80-c257-11eb-9884-5870a3f4ef95.png">
Web App (Kotlin/JS + React)
<br/>
<img width="612" alt="Screenshot 2021-06-06 at 23 50 00" src="https://user-images.githubusercontent.com/19620536/120935764-eda82500-c721-11eb-9042-f15ade7473f7.png">

View file

@ -7,13 +7,19 @@ dependencies {
implementation(kotlin("stdlib-js"))
implementation("org.jetbrains.kotlinx:kotlinx-html-js:0.7.3")
implementation("org.jetbrains:kotlin-react:16.13.1-pre.110-kotlin-1.4.0")
implementation("org.jetbrains:kotlin-react-dom:16.13.1-pre.110-kotlin-1.4.0")
implementation("org.jetbrains:kotlin-styled:5.2.1-pre.146-kotlin-1.4.30")
implementation("org.jetbrains:kotlin-react:17.0.1-pre.146-kotlin-1.4.30")
implementation("org.jetbrains:kotlin-react-dom:17.0.1-pre.146-kotlin-1.4.30")
implementation("org.jetbrains:kotlin-react-router-dom:5.1.2-pre.110-kotlin-1.4.0")
implementation(npm("react", "16.13.0"))
implementation(npm("react-dom", "16.13.0"))
// Material Design Components for React
implementation(npm("@material-ui/core", "4.11.1"))
// ReactJS Maps
implementation(npm("pigeon-maps", "0.19.6"))
implementation(project(":common"))
}

View file

@ -1,9 +1,22 @@
import com.surrus.common.remote.Assignment
import com.surrus.common.remote.IssPosition
import react.*
import react.dom.*
import components.IssLocationMap
import components.PeopleList
import components.PersonDetails
import components.Space
import components.Typography
import components.materialui.AppBar
import components.materialui.Card
import components.materialui.Grid
import components.materialui.Toolbar
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.collect
import kotlinx.css.margin
import kotlinx.css.padding
import kotlinx.css.px
import model.Person
import react.*
import react.dom.*
import styled.css
@InternalCoroutinesApi
@ -11,14 +24,24 @@ val App = functionalComponent<RProps> {
val appDependencies = useContext(AppDependenciesContext)
val repository = appDependencies.repository
val (people, setPeople) = useState(emptyList<Assignment>())
val (issPosition, setIssPosition) = useState(IssPosition(0.0,0.0))
val (people, setPeople) = useState(emptyList<Person>())
val (issPosition, setIssPosition) = useState(IssPosition(0.0, 0.0))
val (selectedPerson, setSelectedPerson) = useState<Person?>(null)
useEffectWithCleanup(dependencies = listOf()) {
val mainScope = MainScope()
mainScope.launch {
setPeople(repository.fetchPeople())
repository.fetchPeople()
.map {
val bio = repository.getPersonBio(it.name)
val imageUrl = repository.getPersonImage(it.name)
Person(it, bio, imageUrl)
}.let {
setPeople(it)
setSelectedPerson(it.first())
}
repository.pollISSPosition().collect {
setIssPosition(it)
@ -26,19 +49,70 @@ val App = functionalComponent<RProps> {
}
return@useEffectWithCleanup { mainScope.cancel() }
}
h1 {
+"People In Space"
}
h2 {
+"$issPosition"
}
ul {
people.forEach { item ->
li {
+"${item.name} (${item.craft})"
Fragment {
AppBar {
css {
margin(0.px)
}
Toolbar {
Typography("h6", "People In Space")
}
}
}
}
Toolbar {
// Empty toolbar to avoid below content to be overlapped by AppBar
}
Grid {
attrs {
container = true
spacing = 4
justify = "flex-start"
alignItems = "stretch"
}
Grid {
attrs {
item = true
md = 4
xs = 12
}
PeopleList(
people = people,
selectedPerson = selectedPerson,
onSelect = {
setSelectedPerson(it)
}
)
}
Grid {
attrs {
item = true
md = 8
xs = 12
}
selectedPerson?.let { person ->
Card {
css {
padding(16.px)
}
PersonDetails(person)
}
}
}
}
if (issPosition.latitude != 0.0 && issPosition.longitude != 0.0) {
Space(16.px)
Typography("subtitle1", buildString {
append("ISS Position: ")
append("Latitude = ${issPosition.latitude}, ")
append("Longitude = ${issPosition.longitude}")
})
Space(4.px)
IssLocationMap(issPosition)
}
}
}

View file

@ -0,0 +1,24 @@
package components
import com.surrus.common.remote.IssPosition
import components.materialui.Map
import components.materialui.Marker
import react.RBuilder
fun RBuilder.IssLocationMap(issPosition: IssPosition) {
val locationCoordinates = arrayOf(issPosition.latitude, issPosition.longitude)
Map {
attrs {
height = 300
defaultZoom = 4
defaultCenter = locationCoordinates
}
Marker {
attrs {
width = 50
anchor = locationCoordinates
}
}
}
}

View file

@ -0,0 +1,37 @@
package components
import components.materialui.Avatar
import components.materialui.ListItem
import components.materialui.ListItemAvatar
import components.materialui.ListItemText
import model.Person
import react.RBuilder
fun RBuilder.PeopleList(
people: List<Person>,
selectedPerson: Person?,
onSelect: (Person) -> Unit
) {
components.materialui.List {
people.forEach { item ->
ListItem {
attrs {
button = true
key = item.assignment.name
selected = item == selectedPerson
onClick = {
onSelect(item)
}
}
ListItemAvatar {
Avatar {
attrs.src = item.imageUrl
}
}
ListItemText {
+"${item.assignment.name} (${item.assignment.craft})"
}
}
}
}
}

View file

@ -0,0 +1,50 @@
package components
import kotlinx.css.Align
import kotlinx.css.alignSelf
import kotlinx.css.margin
import kotlinx.css.padding
import kotlinx.css.px
import model.Person
import react.RBuilder
import react.dom.img
import styled.css
import styled.styledDiv
fun RBuilder.PersonDetails(person: Person) {
styledDiv {
css {
padding(32.px)
}
img(alt = person.assignment.name, src = person.imageUrl) {
attrs {
height = "128"
width = "128"
}
}
styledDiv {
css {
margin(top = 16.px)
alignSelf = Align.center
}
Typography("h4", person.assignment.name)
}
styledDiv {
css {
margin(top = 8.px)
alignSelf = Align.center
}
Typography("h6", person.assignment.craft)
}
styledDiv {
css {
margin(top = 24.px)
}
Typography("body1", person.bio)
}
}
}

View file

@ -0,0 +1,9 @@
package components
import kotlinx.css.LinearDimension
import kotlinx.css.margin
import react.RBuilder
import styled.css
import styled.styledDiv
fun RBuilder.Space(space: LinearDimension) = styledDiv { css { margin(space) } }

View file

@ -0,0 +1,8 @@
package components
import react.RBuilder
fun RBuilder.Typography(variant: String, text: String) = components.materialui.Typography {
attrs.variant = variant
+text
}

View file

@ -0,0 +1,9 @@
@file:JsModule("@material-ui/core")
@file:JsNonModule
package components.materialui
import react.*
@JsName("AppBar")
external val AppBar: RClass<dynamic>

View file

@ -0,0 +1,9 @@
@file:JsModule("@material-ui/core")
@file:JsNonModule
package components.materialui
import react.*
@JsName("Avatar")
external val Avatar: RClass<dynamic>

View file

@ -0,0 +1,9 @@
@file:JsModule("@material-ui/core")
@file:JsNonModule
package components.materialui
import react.*
@JsName("Card")
external val Card: RClass<dynamic>

View file

@ -0,0 +1,9 @@
@file:JsModule("@material-ui/core")
@file:JsNonModule
package components.materialui
import react.RClass
@JsName("Grid")
external val Grid: RClass<dynamic>

View file

@ -0,0 +1,18 @@
@file:JsModule("@material-ui/core")
@file:JsNonModule
package components.materialui
import react.*
@JsName("List")
external val List: RClass<dynamic>
@JsName("ListItem")
external val ListItem: RClass<dynamic>
@JsName("ListItemAvatar")
external val ListItemAvatar: RClass<dynamic>
@JsName("ListItemText")
external val ListItemText: RClass<dynamic>

View file

@ -0,0 +1,12 @@
@file:JsModule("pigeon-maps")
@file:JsNonModule
package components.materialui
import react.*
@JsName("Map")
external val Map: RClass<dynamic>
@JsName("Marker")
external val Marker: RClass<dynamic>

View file

@ -0,0 +1,9 @@
@file:JsModule("@material-ui/core")
@file:JsNonModule
package components.materialui
import react.*
@JsName("Toolbar")
external val Toolbar: RClass<dynamic>

View file

@ -0,0 +1,9 @@
@file:JsModule("@material-ui/core")
@file:JsNonModule
package components.materialui
import react.RClass
@JsName("Typography")
external val Typography: RClass<dynamic>

View file

@ -0,0 +1,9 @@
package model
import com.surrus.common.remote.Assignment
data class Person(
val assignment: Assignment,
val bio: String,
val imageUrl: String
)