Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
99 changes: 99 additions & 0 deletions src/main/kotlin/App.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
import kotlinx.coroutines.async
import kotlinx.css.*
import react.*
import react.dom.*
import styled.css
import styled.styledDiv
import kotlinx.browser.window
import kotlinx.coroutines.*

suspend fun fetchVideo(id: Int): Video {
val response = window
.fetch("https://my-json-server.typicode.com/kotlin-hands-on/kotlinconf-json/videos/$id")
.await()
.json()
.await()
return response as Video
}

suspend fun fetchVideos(): List<Video> = coroutineScope {
(1..25).map { id ->
async {
fetchVideo(id)
}
}.awaitAll()
}

external interface AppState : RState {
var currentVideo: Video?
var unwatchedVideos: List<Video>
var watchedVideos: List<Video>
}

@JsExport
class App : RComponent<RProps, AppState>() {
override fun AppState.init() {
unwatchedVideos = listOf()
watchedVideos = listOf()

val mainScope = MainScope()
mainScope.launch {
val videos = fetchVideos()
setState {
unwatchedVideos = videos
}
}
}

override fun RBuilder.render() {
h1 {
+"KotlinConf Explorer"
}
div {
h3 {
+"Videos to watch"
}
videoList {
videos = state.unwatchedVideos
selectedVideo = state.currentVideo
onSelectVideo = { video ->
setState {
currentVideo = video
}
}
}

h3 {
+"Videos watched"
}
videoList {
videos = state.watchedVideos
selectedVideo = state.currentVideo
onSelectVideo = { video ->
setState {
currentVideo = video
}
}
}
}
state.currentVideo?.let { currentVideo ->
videoPlayer {
video = currentVideo
unwatchedVideo = currentVideo in state.unwatchedVideos
onWatchedButtonPressed = {
if (video in state.unwatchedVideos) {
setState {
unwatchedVideos -= video
watchedVideos += video
}
} else {
setState {
watchedVideos -= video
unwatchedVideos += video
}
}
}
}
}
}
}
21 changes: 20 additions & 1 deletion src/main/kotlin/Main.kt
Original file line number Diff line number Diff line change
@@ -1,5 +1,24 @@
import react.dom.*
import kotlinx.browser.document
import kotlinx.css.*
import styled.*

external interface Video {
val id: Int
val title: String
val speaker: String
val videoUrl: String
}

data class KotlinVideo(
override val id: Int,
override val title: String,
override val speaker: String,
override val videoUrl: String
) : Video

fun main() {
document.bgColor = "blue"
render(document.getElementById("root")) {
child(App::class) {}
}
}
26 changes: 26 additions & 0 deletions src/main/kotlin/ReactShare.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
@file:JsModule("react-share")
@file:JsNonModule

import react.RClass
import react.RProps

@JsName("EmailIcon")
external val emailIcon: RClass<IconProps>

@JsName("EmailShareButton")
external val emailShareButton: RClass<ShareButtonProps>

@JsName("TelegramIcon")
external val telegramIcon: RClass<IconProps>

@JsName("TelegramShareButton")
external val telegramShareButton: RClass<ShareButtonProps>

external interface ShareButtonProps : RProps {
var url: String
}

external interface IconProps : RProps {
var size: Int
var round: Boolean
}
11 changes: 11 additions & 0 deletions src/main/kotlin/ReactYouTube.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
@file:JsModule("react-youtube-lite")
@file:JsNonModule

import react.*

@JsName("ReactYouTubeLite")
external val reactPlayer: RClass<ReactYouTubeProps>

external interface ReactYouTubeProps : RProps {
var url: String
}
37 changes: 37 additions & 0 deletions src/main/kotlin/VideoList.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import kotlinx.html.js.onClickFunction
import kotlinx.browser.window
import react.*
import react.dom.*


external interface VideoListProps: RProps {
var videos: List<Video>
var selectedVideo: Video?
var onSelectVideo: (Video) -> Unit
}

@JsExport
class VideoList: RComponent<VideoListProps, RState>() {
override fun RBuilder.render() {
for (video in props.videos) {
p {
key = video.id.toString()
attrs {
onClickFunction = {
props.onSelectVideo(video)
}
}
if(video == props.selectedVideo) {
+"▶ "
}
+"${video.speaker}: ${video.title}"
}
}
}
}

fun RBuilder.videoList(handler: VideoListProps.() -> Unit): ReactElement {
return child(VideoList::class) {
this.attrs(handler)
}
}
73 changes: 73 additions & 0 deletions src/main/kotlin/VideoPlayer.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import kotlinx.css.*
import kotlinx.html.js.onClickFunction
import react.*
import react.dom.*
import styled.*

external interface VideoPlayerProps : RProps {
var video: Video
var onWatchedButtonPressed: (Video) -> Unit
var unwatchedVideo: Boolean
}

@JsExport
class VideoPlayer(props: VideoPlayerProps) : RComponent<VideoPlayerProps, RState>(props) {
override fun RBuilder.render() {
styledDiv {
css {
position = Position.absolute
top = 10.px
right = 10.px
}
h3 {
+"${props.video.speaker}: ${props.video.title}"
}
styledButton {
css {
display = Display.block
backgroundColor = if (props.unwatchedVideo) Color.lightGreen else Color.red
}
attrs {
onClickFunction = {
props.onWatchedButtonPressed(props.video)
}
}
if (props.unwatchedVideo) {
+"Mark as watched"
} else {
+"Mark as unwatched"
}
}
styledDiv {
css {
display = Display.flex
marginBottom = 10.px
}
emailShareButton {
attrs.url = props.video.videoUrl
emailIcon {
attrs.size = 32
attrs.round = true
}
}

telegramShareButton {
attrs.url = props.video.videoUrl
telegramIcon {
attrs.size = 32
attrs.round = true
}
}
}
reactPlayer {
attrs.url = props.video.videoUrl
}
}
}
}

fun RBuilder.videoPlayer(handler: VideoPlayerProps.() -> Unit): ReactElement {
return child(VideoPlayer::class) {
this.attrs(handler)
}
}