Merge pull request #55 from PatilShreyas/sp/web-app
[Web] Design ReactJS Web app with Material Designing components
This commit is contained in:
commit
86cc905034
17 changed files with 327 additions and 22 deletions
|
@ -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">
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -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"))
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
24
web/src/main/kotlin/components/IssLocationMap.kt
Normal file
24
web/src/main/kotlin/components/IssLocationMap.kt
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
37
web/src/main/kotlin/components/PeopleList.kt
Normal file
37
web/src/main/kotlin/components/PeopleList.kt
Normal 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})"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
50
web/src/main/kotlin/components/PersonDetails.kt
Normal file
50
web/src/main/kotlin/components/PersonDetails.kt
Normal 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)
|
||||
}
|
||||
}
|
||||
}
|
9
web/src/main/kotlin/components/Space.kt
Normal file
9
web/src/main/kotlin/components/Space.kt
Normal 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) } }
|
8
web/src/main/kotlin/components/Typography.kt
Normal file
8
web/src/main/kotlin/components/Typography.kt
Normal file
|
@ -0,0 +1,8 @@
|
|||
package components
|
||||
|
||||
import react.RBuilder
|
||||
|
||||
fun RBuilder.Typography(variant: String, text: String) = components.materialui.Typography {
|
||||
attrs.variant = variant
|
||||
+text
|
||||
}
|
9
web/src/main/kotlin/components/materialui/AppBar.kt
Normal file
9
web/src/main/kotlin/components/materialui/AppBar.kt
Normal file
|
@ -0,0 +1,9 @@
|
|||
@file:JsModule("@material-ui/core")
|
||||
@file:JsNonModule
|
||||
|
||||
package components.materialui
|
||||
|
||||
import react.*
|
||||
|
||||
@JsName("AppBar")
|
||||
external val AppBar: RClass<dynamic>
|
9
web/src/main/kotlin/components/materialui/Avatar.kt
Normal file
9
web/src/main/kotlin/components/materialui/Avatar.kt
Normal file
|
@ -0,0 +1,9 @@
|
|||
@file:JsModule("@material-ui/core")
|
||||
@file:JsNonModule
|
||||
|
||||
package components.materialui
|
||||
|
||||
import react.*
|
||||
|
||||
@JsName("Avatar")
|
||||
external val Avatar: RClass<dynamic>
|
9
web/src/main/kotlin/components/materialui/Card.kt
Normal file
9
web/src/main/kotlin/components/materialui/Card.kt
Normal file
|
@ -0,0 +1,9 @@
|
|||
@file:JsModule("@material-ui/core")
|
||||
@file:JsNonModule
|
||||
|
||||
package components.materialui
|
||||
|
||||
import react.*
|
||||
|
||||
@JsName("Card")
|
||||
external val Card: RClass<dynamic>
|
9
web/src/main/kotlin/components/materialui/Grid.kt
Normal file
9
web/src/main/kotlin/components/materialui/Grid.kt
Normal 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>
|
18
web/src/main/kotlin/components/materialui/List.kt
Normal file
18
web/src/main/kotlin/components/materialui/List.kt
Normal 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>
|
12
web/src/main/kotlin/components/materialui/PigeonMaps.kt
Normal file
12
web/src/main/kotlin/components/materialui/PigeonMaps.kt
Normal 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>
|
9
web/src/main/kotlin/components/materialui/Toolbar.kt
Normal file
9
web/src/main/kotlin/components/materialui/Toolbar.kt
Normal file
|
@ -0,0 +1,9 @@
|
|||
@file:JsModule("@material-ui/core")
|
||||
@file:JsNonModule
|
||||
|
||||
package components.materialui
|
||||
|
||||
import react.*
|
||||
|
||||
@JsName("Toolbar")
|
||||
external val Toolbar: RClass<dynamic>
|
9
web/src/main/kotlin/components/materialui/Typography.kt
Normal file
9
web/src/main/kotlin/components/materialui/Typography.kt
Normal 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>
|
9
web/src/main/kotlin/model/Person.kt
Normal file
9
web/src/main/kotlin/model/Person.kt
Normal 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
|
||||
)
|
Loading…
Reference in a new issue