├── .gitignore
├── .idea
├── kotlin-react-sample.iml
├── kotlinc.xml
├── libraries
│ ├── KotlinJavaScript.xml
│ ├── kotlin_extensions.xml
│ ├── kotlin_react.xml
│ ├── kotlin_react_dom.xml
│ ├── kotlinx_coroutines_core.xml
│ └── kotlinx_html_js.xml
├── misc.xml
├── modules.xml
├── runConfigurations
│ ├── Debug_in_Chrome.xml
│ └── npm_start.xml
├── vcs.xml
└── workspace.xml
├── LICENSE
├── README.md
├── docs
├── asset-manifest.json
├── favicon.ico
├── index.html
├── manifest.json
└── static
│ ├── css
│ ├── main.0aea1c60.css
│ └── main.0aea1c60.css.map
│ └── js
│ ├── main.9a137a24.js
│ └── main.9a137a24.js.map
├── kotlin-react-sample.iml
├── package-lock.json
├── package.json
├── public
├── favicon.ico
├── index.html
└── manifest.json
└── src
├── app
├── App.css
└── App.kt
├── axios
└── Axios.kt
├── common
├── FunComp.kt
├── SearchBar.kt
├── funcomp.css
└── search_bar.css
├── giphy
├── GiphyAPI.kt
├── GiphyDetails.kt
├── GiphyList.kt
├── GiphyListItem.kt
└── giphy.css
├── index
├── index.css
└── index.kt
└── lodash
└── Lodash.kt
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/ignore-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 |
6 | # testing
7 | /coverage
8 |
9 | # production
10 | /build
11 |
12 | # misc
13 | .DS_Store
14 | .env.local
15 | .env.development.local
16 | .env.test.local
17 | .env.production.local
18 | .idea/workspace.xml
19 |
20 | npm-debug.log*
21 | yarn-debug.log*
22 | yarn-error.log*
23 |
24 |
--------------------------------------------------------------------------------
/.idea/kotlin-react-sample.iml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/.idea/kotlinc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/.idea/libraries/KotlinJavaScript.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/.idea/libraries/kotlin_extensions.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/.idea/libraries/kotlin_react.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/.idea/libraries/kotlin_react_dom.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/.idea/libraries/kotlinx_coroutines_core.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/.idea/libraries/kotlinx_html_js.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/.idea/misc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/modules.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/.idea/runConfigurations/Debug_in_Chrome.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/.idea/runConfigurations/npm_start.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/workspace.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 | "/react-kotlin-sample/static
21 |
22 |
23 | "/react-kotlin-sample/static
24 | "/kotlin-react-sample/static
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 | true
51 | DEFINITION_ORDER
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 |
126 |
127 |
128 |
129 |
130 |
131 |
132 |
133 |
134 |
135 |
136 |
137 |
138 |
139 |
140 |
141 |
142 |
143 |
144 |
145 |
146 |
147 |
148 |
149 |
150 |
151 |
152 |
153 |
154 |
155 |
156 |
157 |
158 |
159 |
160 |
161 |
162 |
163 | 1525531835847
164 |
165 |
166 | 1525531835847
167 |
168 |
169 |
170 |
171 |
172 |
173 | 1525534164138
174 |
175 |
176 |
177 | 1525534164138
178 |
179 |
180 |
181 |
182 |
183 |
184 |
185 |
186 |
187 |
188 |
189 |
190 |
191 |
192 |
193 |
194 |
195 |
196 |
197 |
198 |
199 |
200 |
201 |
202 |
203 |
204 |
205 |
206 |
207 |
208 |
209 |
210 |
211 |
212 |
213 |
214 |
215 |
216 |
217 |
218 |
219 |
220 |
221 |
222 |
223 |
224 |
225 |
226 |
227 |
228 |
229 |
230 |
231 |
232 |
233 |
234 |
235 |
236 |
237 |
238 |
239 |
240 |
241 |
242 |
243 |
244 |
245 |
246 |
247 |
248 |
249 |
250 |
251 |
252 |
253 |
254 |
255 |
256 |
257 |
258 |
259 |
260 |
261 |
262 |
263 |
264 |
265 |
266 |
267 |
268 |
269 |
270 |
271 |
272 |
273 |
274 |
275 |
276 |
277 |
278 | 1.8
279 |
280 |
281 |
282 |
283 |
284 |
285 |
286 |
287 |
288 |
289 |
290 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2018 Ralf Stuckert
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # kotlin-react-sample
2 | This repository provides the source code for the Kotlin-React
3 | sample app of the article [Getting Started With Kotlin-React](https://medium.com/@ralf.stuckert/getting-started-with-kotlin-react-c5f3b079a8bf).
4 | The app was set up using [create-react-kotlin-app](https://github.com/JetBrains/create-react-kotlin-app)
5 | which scaffolds a runnable app including an IntelliJ project file.
6 |
7 | A prebuilt version of the app is hosted on GitHub Pages
8 | https://ralfstuckert.github.io/kotlin-react-sample/
9 |
10 | To run it locally, just clone the project and call
11 | * npm install
12 | * npm start
13 |
14 |
--------------------------------------------------------------------------------
/docs/asset-manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "main.css": "static/css/main.0aea1c60.css",
3 | "main.css.map": "static/css/main.0aea1c60.css.map",
4 | "main.js": "static/js/main.9a137a24.js",
5 | "main.js.map": "static/js/main.9a137a24.js.map"
6 | }
--------------------------------------------------------------------------------
/docs/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ralfstuckert/kotlin-react-sample/3e17e4cbe06cdccf4b90e7be90523819373d49a5/docs/favicon.ico
--------------------------------------------------------------------------------
/docs/index.html:
--------------------------------------------------------------------------------
1 |
React Kotlin App You need to enable JavaScript to run this app.
--------------------------------------------------------------------------------
/docs/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "Giphy React Kotlin App",
3 | "name": "Giphy React Kotlin App Sample",
4 | "icons": [
5 | {
6 | "src": "favicon.ico",
7 | "sizes": "192x192",
8 | "type": "image/png"
9 | }
10 | ],
11 | "start_url": "/index.html",
12 | "display": "standalone",
13 | "theme_color": "#000000",
14 | "background_color": "#ffffff"
15 | }
16 |
--------------------------------------------------------------------------------
/docs/static/css/main.0aea1c60.css:
--------------------------------------------------------------------------------
1 | body{text-align:center}.App-header{background-color:#000;height:160px;padding:20px;color:#fff}.spinner{display:inline-block;border:.2em solid #f3f3f3;border-top:.2em solid #3498db;border-radius:50%;width:1em;height:1em;-webkit-animation:spin 2s linear infinite;animation:spin 2s linear infinite}@-webkit-keyframes spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}to{-webkit-transform:rotate(1turn);transform:rotate(1turn)}}@keyframes spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}to{-webkit-transform:rotate(1turn);transform:rotate(1turn)}}.search-bar{margin:20px;text-align:center}.search-bar input{width:75%}.giphy-item img{max-width:80%}.giphy-detail .details{margin:10px 0}.list-group-item{cursor:pointer}.list-group-item:hover{background-color:#eee}body{margin:0;padding:0;font-family:sans-serif}
2 | /*# sourceMappingURL=main.0aea1c60.css.map*/
--------------------------------------------------------------------------------
/docs/static/css/main.0aea1c60.css.map:
--------------------------------------------------------------------------------
1 | {"version":3,"sources":["app/App.css","common/funcomp.css","common/search_bar.css","giphy/giphy.css","index/index.css"],"names":[],"mappings":"AAAA,KACI,iBAAmB,CAGvB,YACI,sBACA,aACA,aACA,UAAa,CCPjB,SACI,qBACA,0BACA,8BACA,kBACA,UACA,WACA,0CACQ,iCAAmC,CAG/C,wBACI,GACI,+BACQ,sBAAwB,CAEpC,GACI,gCACQ,uBAA0B,CACrC,CAGL,gBACI,GACI,+BACQ,sBAAwB,CAEpC,GACI,gCACQ,uBAA0B,CACrC,CC9BL,YACI,YACA,iBAAmB,CAGvB,kBACI,SAAW,CCNf,gBACI,aAAe,CAGnB,uBACI,aAAsB,CAG1B,iBACI,cAAgB,CAGpB,uBACI,qBAAuB,CCd3B,KACI,SACA,UACA,sBAAwB","file":"static/css/main.0aea1c60.css","sourcesContent":["body {\n text-align: center;\n}\n\n.App-header {\n background-color: #000;\n height: 160px;\n padding: 20px;\n color: white;\n}\n\n\n\n\n// WEBPACK FOOTER //\n// ./src/app/App.css","\n.spinner {\n display: inline-block;\n border: 0.2em solid #f3f3f3;\n border-top: 0.2em solid #3498db;\n border-radius: 50%;\n width: 1em;\n height: 1em;\n -webkit-animation: spin 2s linear infinite;\n animation: spin 2s linear infinite;\n}\n\n@-webkit-keyframes spin {\n 0% {\n -webkit-transform: rotate(0deg);\n transform: rotate(0deg);\n }\n 100% {\n -webkit-transform: rotate(360deg);\n transform: rotate(360deg);\n }\n}\n\n@keyframes spin {\n 0% {\n -webkit-transform: rotate(0deg);\n transform: rotate(0deg);\n }\n 100% {\n -webkit-transform: rotate(360deg);\n transform: rotate(360deg);\n }\n}\n\n\n// WEBPACK FOOTER //\n// ./src/common/funcomp.css","\n.search-bar {\n margin: 20px;\n text-align: center;\n}\n\n.search-bar input {\n width: 75%;\n}\n\n\n\n\n// WEBPACK FOOTER //\n// ./src/common/search_bar.css","\n.giphy-item img {\n max-width: 80%;\n}\n\n.giphy-detail .details {\n margin: 10px 0 10px 0;\n}\n\n.list-group-item {\n cursor: pointer;\n}\n\n.list-group-item:hover {\n background-color: #eee;\n}\n\n\n\n\n// WEBPACK FOOTER //\n// ./src/giphy/giphy.css","body {\n margin: 0;\n padding: 0;\n font-family: sans-serif;\n}\n\n\n\n// WEBPACK FOOTER //\n// ./src/index/index.css"],"sourceRoot":""}
--------------------------------------------------------------------------------
/kotlin-react-sample.iml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "kotlin-react-sample",
3 | "version": "0.1.0",
4 | "private": true,
5 | "dependencies": {
6 | "axios": "^0.18.0",
7 | "kotlinx-coroutines-core": "^0.22.5",
8 | "lodash": "^4.17.10",
9 | "react": "^16.3.2",
10 | "react-bootstrap": "^0.32.1",
11 | "react-dom": "^16.3.2"
12 | },
13 | "devDependencies": {
14 | "react-scripts-kotlin": "2.1.9"
15 | },
16 | "scripts": {
17 | "start": "react-scripts-kotlin start",
18 | "build": "react-scripts-kotlin build",
19 | "eject": "react-scripts-kotlin eject",
20 | "gen-idea-libs": "react-scripts-kotlin gen-idea-libs",
21 | "get-types": "react-scripts-kotlin get-types --dest=src/types",
22 | "postinstall": "npm run gen-idea-libs"
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ralfstuckert/kotlin-react-sample/3e17e4cbe06cdccf4b90e7be90523819373d49a5/public/favicon.ico
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
11 |
12 |
13 |
22 |
24 |
25 | React Kotlin App
26 |
27 |
28 |
29 | You need to enable JavaScript to run this app.
30 |
31 |
32 |
42 |
43 |
44 |
--------------------------------------------------------------------------------
/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "Giphy React Kotlin App",
3 | "name": "Giphy React Kotlin App Sample",
4 | "icons": [
5 | {
6 | "src": "favicon.ico",
7 | "sizes": "192x192",
8 | "type": "image/png"
9 | }
10 | ],
11 | "start_url": "/index.html",
12 | "display": "standalone",
13 | "theme_color": "#000000",
14 | "background_color": "#ffffff"
15 | }
16 |
--------------------------------------------------------------------------------
/src/app/App.css:
--------------------------------------------------------------------------------
1 | body {
2 | text-align: center;
3 | }
4 |
5 | .App-header {
6 | background-color: #000;
7 | height: 160px;
8 | padding: 20px;
9 | color: white;
10 | }
11 |
12 |
--------------------------------------------------------------------------------
/src/app/App.kt:
--------------------------------------------------------------------------------
1 | package app
2 |
3 | import common.alert
4 | import common.loading
5 | import common.searchBar
6 | import giphy.*
7 | import kotlinx.coroutines.experimental.async
8 | import lodash.lodash
9 | import react.*
10 | import react.dom.div
11 | import react.dom.h1
12 |
13 |
14 | interface AppState : RState {
15 | var giphies: Array
16 | var selectedGiphy: Giphy
17 | var errorMessage: String
18 | var loading: Boolean
19 | }
20 |
21 |
22 | class App : RComponent() {
23 |
24 | override fun AppState.init() {
25 | giphies = emptyArray()
26 | selectedGiphy = DummyGiphy
27 | errorMessage = ""
28 | loading = false
29 | }
30 |
31 | override fun RBuilder.render() {
32 | div("container") {
33 | h1("text-center") {
34 | +"Giphy Search"
35 | }
36 | searchBar(onSearchTermChange = lodash.debounce(::startSearchCoroutines, 300))
37 | alert(state.errorMessage)
38 | loading(state.loading)
39 | giphyDetails(state.selectedGiphy)
40 | giphyList(state.giphies, ::selectGiphy)
41 | }
42 | }
43 |
44 | fun selectGiphy(giphy: Giphy) {
45 | setState {
46 | selectedGiphy = giphy
47 | }
48 | }
49 |
50 | fun startSearch(term: String) {
51 | setState {
52 | loading = true
53 | errorMessage = ""
54 | }
55 |
56 | giphySearch(term).then { result: Array ->
57 | setState {
58 | selectedGiphy = result[0]
59 | giphies = result
60 | loading = false
61 | }
62 | }.catch { throwable: Throwable ->
63 | setState {
64 | errorMessage = throwable.toString()
65 | loading = false
66 | }
67 | }
68 | }
69 |
70 | fun startSearchCoroutines(term: String) = async {
71 | setState {
72 | loading = true
73 | errorMessage = ""
74 | }
75 |
76 | try {
77 | val result: Array = giphySearchCoroutines(term)
78 | setState {
79 | selectedGiphy = result[0]
80 | giphies = result
81 | loading = false
82 | }
83 | } catch (t: Throwable) {
84 | setState {
85 | errorMessage = t.toString()
86 | loading = false
87 | }
88 | }
89 | }
90 |
91 | }
92 |
93 | fun RBuilder.app() = child(App::class) {}
94 |
--------------------------------------------------------------------------------
/src/axios/Axios.kt:
--------------------------------------------------------------------------------
1 | @file:Suppress("INTERFACE_WITH_SUPERCLASS", "OVERRIDING_FINAL_MEMBER", "RETURN_TYPE_MISMATCH_ON_OVERRIDE", "CONFLICTING_OVERLOADS", "EXTERNAL_DELEGATION", "NESTED_CLASS_IN_EXTERNAL_INTERFACE")
2 |
3 | package axios
4 |
5 | import kotlin.js.Promise
6 |
7 | external interface AxiosTransformer {
8 | @nativeInvoke
9 | operator fun invoke(data: Any, headers: Any? = definedExternally /* null */): Any
10 | }
11 | external interface AxiosAdapter {
12 | @nativeInvoke
13 | operator fun invoke(config: AxiosRequestConfig): AxiosPromise
14 | }
15 | external interface AxiosBasicCredentials {
16 | var username: String
17 | var password: String
18 | }
19 | external interface `T$0` {
20 | var username: String
21 | var password: String
22 | }
23 | external interface AxiosProxyConfig {
24 | var host: String
25 | var port: Number
26 | var auth: `T$0`? get() = definedExternally; set(value) = definedExternally
27 | }
28 | external interface AxiosRequestConfig {
29 | var url: String? get() = definedExternally; set(value) = definedExternally
30 | var method: String? get() = definedExternally; set(value) = definedExternally
31 | var baseURL: String? get() = definedExternally; set(value) = definedExternally
32 | var transformRequest: dynamic /* AxiosTransformer | Array */ get() = definedExternally; set(value) = definedExternally
33 | var transformResponse: dynamic /* AxiosTransformer | Array */ get() = definedExternally; set(value) = definedExternally
34 | var headers: Any? get() = definedExternally; set(value) = definedExternally
35 | var params: Any? get() = definedExternally; set(value) = definedExternally
36 | var paramsSerializer: ((params: Any) -> String)? get() = definedExternally; set(value) = definedExternally
37 | var data: Any? get() = definedExternally; set(value) = definedExternally
38 | var timeout: Number? get() = definedExternally; set(value) = definedExternally
39 | var withCredentials: Boolean? get() = definedExternally; set(value) = definedExternally
40 | var adapter: AxiosAdapter? get() = definedExternally; set(value) = definedExternally
41 | var auth: AxiosBasicCredentials? get() = definedExternally; set(value) = definedExternally
42 | var responseType: String? get() = definedExternally; set(value) = definedExternally
43 | var xsrfCookieName: String? get() = definedExternally; set(value) = definedExternally
44 | var xsrfHeaderName: String? get() = definedExternally; set(value) = definedExternally
45 | var onUploadProgress: ((progressEvent: Any) -> Unit)? get() = definedExternally; set(value) = definedExternally
46 | var onDownloadProgress: ((progressEvent: Any) -> Unit)? get() = definedExternally; set(value) = definedExternally
47 | var maxContentLength: Number? get() = definedExternally; set(value) = definedExternally
48 | var validateStatus: ((status: Number) -> Boolean)? get() = definedExternally; set(value) = definedExternally
49 | var maxRedirects: Number? get() = definedExternally; set(value) = definedExternally
50 | var httpAgent: Any? get() = definedExternally; set(value) = definedExternally
51 | var httpsAgent: Any? get() = definedExternally; set(value) = definedExternally
52 | var proxy: dynamic /* Boolean | AxiosProxyConfig */ get() = definedExternally; set(value) = definedExternally
53 | var cancelToken: CancelToken? get() = definedExternally; set(value) = definedExternally
54 | }
55 | external interface AxiosResponse {
56 | var data: T
57 | var status: Number
58 | var statusText: String
59 | var headers: Any
60 | var config: AxiosRequestConfig
61 | var request: Any? get() = definedExternally; set(value) = definedExternally
62 | }
63 |
64 | external interface Error
65 |
66 | external interface AxiosError : Error {
67 | var config: AxiosRequestConfig
68 | var code: String? get() = definedExternally; set(value) = definedExternally
69 | var request: Any? get() = definedExternally; set(value) = definedExternally
70 | var response: AxiosResponse? get() = definedExternally; set(value) = definedExternally
71 | }
72 | external interface AxiosPromise : Promise>
73 | external interface CancelStatic
74 | external interface Cancel {
75 | var message: String
76 | }
77 | external interface Canceler {
78 | @nativeInvoke
79 | operator fun invoke(message: String? = definedExternally /* null */)
80 | }
81 | external interface CancelTokenStatic {
82 | fun source(): CancelTokenSource
83 | }
84 | external interface CancelToken {
85 | var promise: Promise
86 | var reason: Cancel? get() = definedExternally; set(value) = definedExternally
87 | fun throwIfRequested()
88 | }
89 | external interface CancelTokenSource {
90 | var token: CancelToken
91 | var cancel: Canceler
92 | }
93 | external interface AxiosInterceptorManager {
94 | fun use(onFulfilled: ((value: V) -> dynamic /* V | Promise */)? = definedExternally /* null */, onRejected: ((error: Any) -> Any)? = definedExternally /* null */): Number
95 | fun eject(id: Number)
96 | }
97 | external interface `T$1` {
98 | var request: AxiosInterceptorManager
99 | var response: AxiosInterceptorManager>
100 | }
101 | external interface AxiosInstance {
102 | @nativeInvoke
103 | operator fun invoke(config: AxiosRequestConfig): AxiosPromise
104 | @nativeInvoke
105 | operator fun invoke(url: String, config: AxiosRequestConfig? = definedExternally /* null */): AxiosPromise
106 | var defaults: AxiosRequestConfig
107 | var interceptors: `T$1`
108 | fun request(config: AxiosRequestConfig): AxiosPromise
109 | fun get(url: String, config: AxiosRequestConfig? = definedExternally /* null */): AxiosPromise
110 | fun delete(url: String, config: AxiosRequestConfig? = definedExternally /* null */): AxiosPromise
111 | fun head(url: String, config: AxiosRequestConfig? = definedExternally /* null */): AxiosPromise
112 | fun post(url: String, data: Any? = definedExternally /* null */, config: AxiosRequestConfig? = definedExternally /* null */): AxiosPromise
113 | fun put(url: String, data: Any? = definedExternally /* null */, config: AxiosRequestConfig? = definedExternally /* null */): AxiosPromise
114 | fun patch(url: String, data: Any? = definedExternally /* null */, config: AxiosRequestConfig? = definedExternally /* null */): AxiosPromise
115 | }
116 | external interface AxiosStatic : AxiosInstance {
117 | fun create(config: AxiosRequestConfig? = definedExternally /* null */): AxiosInstance
118 | var Cancel: CancelStatic
119 | var CancelToken: CancelTokenStatic
120 | fun isCancel(value: Any): Boolean
121 | fun all(values: Array */>): Promise>
122 | fun spread(callback: (args: T) -> R): (array: Array) -> R
123 | }
124 |
125 | @JsModule("axios")
126 | //@JsName("default")
127 | external val Axios: AxiosStatic = definedExternally
128 |
129 |
--------------------------------------------------------------------------------
/src/common/FunComp.kt:
--------------------------------------------------------------------------------
1 | package common
2 |
3 | import react.RBuilder
4 | import react.RProps
5 | import react.ReactElement
6 | import react.dom.div
7 | import react.dom.h3
8 |
9 | fun RBuilder.alert(message: String = "") = if (message.isNotEmpty()) {
10 | div("alert alert-danger") {
11 | +message
12 | }
13 | } else {
14 | empty
15 | }
16 |
17 |
18 | fun RBuilder.loading(isLoading: Boolean) = if (isLoading) {
19 | h3 {
20 | +"Loading... "
21 | spinner()
22 | }
23 | } else {
24 | empty
25 | }
26 |
27 |
28 | fun RBuilder.spinner() = div("spinner") {}
29 |
30 |
31 | object empty : ReactElement {
32 | override val props = object : RProps {}
33 | }
34 |
35 |
36 |
--------------------------------------------------------------------------------
/src/common/SearchBar.kt:
--------------------------------------------------------------------------------
1 | package common
2 |
3 | import kotlinx.html.InputType
4 | import kotlinx.html.js.onChangeFunction
5 | import org.w3c.dom.HTMLInputElement
6 | import org.w3c.dom.events.Event
7 | import react.*
8 | import react.dom.div
9 | import react.dom.input
10 |
11 | interface SearchBarProps : RProps {
12 | var onSearchTermChange: (String) -> Any
13 | }
14 |
15 | interface SearchBarState : RState {
16 | var term: String
17 | }
18 |
19 | class SearchBar(props: SearchBarProps) : RComponent(props) {
20 |
21 | override fun SearchBarState.init(props: SearchBarProps) {
22 | term = ""
23 | }
24 |
25 | override fun RBuilder.render() {
26 | div("search-bar") {
27 | input(InputType.search) {
28 | attrs {
29 | value = state.term
30 | onChangeFunction = ::onInputChange
31 | placeholder = "enter search term"
32 | }
33 | }
34 | }
35 | }
36 |
37 | fun onInputChange(event: Event) {
38 | val target = event.target as HTMLInputElement
39 | val searchTerm = target.value
40 | setState {
41 | term = searchTerm
42 | }
43 | this.props.onSearchTermChange(searchTerm)
44 | }
45 | }
46 |
47 |
48 | fun RBuilder.searchBar(onSearchTermChange: (String) -> Any) = child(SearchBar::class) {
49 | attrs.onSearchTermChange = onSearchTermChange
50 | }
51 |
52 |
53 |
54 |
--------------------------------------------------------------------------------
/src/common/funcomp.css:
--------------------------------------------------------------------------------
1 |
2 | .spinner {
3 | display: inline-block;
4 | border: 0.2em solid #f3f3f3;
5 | border-top: 0.2em solid #3498db;
6 | border-radius: 50%;
7 | width: 1em;
8 | height: 1em;
9 | animation: spin 2s linear infinite;
10 | }
11 |
12 | @keyframes spin {
13 | 0% {
14 | transform: rotate(0deg);
15 | }
16 | 100% {
17 | transform: rotate(360deg);
18 | }
19 | }
--------------------------------------------------------------------------------
/src/common/search_bar.css:
--------------------------------------------------------------------------------
1 |
2 | .search-bar {
3 | margin: 20px;
4 | text-align: center;
5 | }
6 |
7 | .search-bar input {
8 | width: 75%;
9 | }
10 |
11 |
--------------------------------------------------------------------------------
/src/giphy/GiphyAPI.kt:
--------------------------------------------------------------------------------
1 | package giphy
2 |
3 | import axios.Axios
4 | import kotlinx.coroutines.experimental.await
5 | import kotlin.js.Promise
6 |
7 | // some kotlin interfaces for the giphy API
8 | external interface GiphyImg {
9 | var url: String
10 | }
11 |
12 | external interface GiphyImgContainer {
13 | var original: GiphyImg
14 | var fixed_height_small_still: GiphyImg
15 | }
16 |
17 | external interface Giphy {
18 | var id: String
19 | var embed_url: String
20 | var images: GiphyImgContainer
21 | var slug: String
22 | }
23 |
24 | // some extensions for our application
25 | val Giphy.fileName: String
26 | get() = "${this.slug}.gif"
27 |
28 | val Giphy.giphyUrl: String
29 | get() = this.embed_url
30 |
31 | val Giphy.downloadUrl: String
32 | get() = "https://media.giphy.com/media/${this.id}/giphy.gif"
33 |
34 | val Giphy.previewUrl: String
35 | get() = this.images.fixed_height_small_still.url;
36 |
37 |
38 | object DummyGiphyImg : GiphyImg {
39 | override var url: String = ""
40 | }
41 |
42 | object DummyGiphyImgContainer : GiphyImgContainer {
43 | override var original: GiphyImg = DummyGiphyImg
44 | override var fixed_height_small_still: GiphyImg = DummyGiphyImg
45 | }
46 |
47 | object DummyGiphy : Giphy {
48 | override var id: String = ""
49 | override var embed_url: String = ""
50 | override var images: GiphyImgContainer = DummyGiphyImgContainer
51 | override var slug: String = ""
52 | }
53 |
54 | /**
55 | * Do not use for production, see https://giphy.api-docs.io/1.0/welcome/access-and-api-keys
56 | */
57 | val PUBLIC_BETA_API_KEY = "dc6zaTOxFJmzC"
58 |
59 | val GIPHY_SEARCH = "https://api.giphy.com/v1/gifs/search"
60 |
61 | data class GiphySearchResponse(val data:Array)
62 |
63 | fun giphyUrl(searchTerm: String) = "${GIPHY_SEARCH}?q=${searchTerm}&limit=7&api_key=${PUBLIC_BETA_API_KEY}"
64 |
65 | fun giphySearch(searchTerm: String):Promise> {
66 |
67 | return Axios.get(giphyUrl(searchTerm)).then { result ->
68 | result.data.data
69 | }
70 | }
71 |
72 | suspend fun giphySearchCoroutines(searchTerm: String): Array {
73 |
74 | val result = Axios.get(giphyUrl(searchTerm)).await()
75 | return result.data.data
76 | }
77 |
--------------------------------------------------------------------------------
/src/giphy/GiphyDetails.kt:
--------------------------------------------------------------------------------
1 | package giphy
2 |
3 | import kotlinx.html.role
4 | import kotlinx.html.title
5 | import react.RBuilder
6 | import react.RComponent
7 | import react.RProps
8 | import react.RState
9 | import react.dom.a
10 | import react.dom.div
11 | import react.dom.iframe
12 |
13 |
14 | interface GiphyProps : RProps {
15 | var giphy: Giphy
16 | }
17 |
18 |
19 | class GiphyDetails(props: GiphyProps) : RComponent(props) {
20 |
21 | override fun RBuilder.render() {
22 | val giphy = props.giphy
23 |
24 | if (giphy != DummyGiphy) {
25 | div("giphy-detail col-md-8") {
26 | div("embed-responsive embed-responsive-16by9") {
27 | iframe(classes = "embed-responsive-item") {
28 | attrs {
29 | src = giphy.giphyUrl
30 | title = giphy.fileName
31 | }
32 | }
33 | }
34 | div("details text-center") {
35 | a(giphy.downloadUrl, classes = "btn btn-primary") {
36 | attrs {
37 | title = giphy.fileName
38 | role = "button"
39 | target = "_blank"
40 | }
41 | +"Download from Giphy"
42 | }
43 | }
44 | }
45 | }
46 | }
47 |
48 | }
49 |
50 |
51 | fun RBuilder.giphyDetails(giphy: Giphy = DummyGiphy) = child(GiphyDetails::class) {
52 | attrs.giphy = giphy
53 | }
54 |
55 |
56 |
--------------------------------------------------------------------------------
/src/giphy/GiphyList.kt:
--------------------------------------------------------------------------------
1 | package giphy
2 |
3 | import react.RBuilder
4 | import react.RComponent
5 | import react.RProps
6 | import react.RState
7 | import react.dom.ul
8 |
9 | interface GiphyListProps : RProps {
10 | var giphies: Array
11 | var onSelect: (Giphy) -> Unit
12 | }
13 |
14 | class GiphyList(props: GiphyListProps) : RComponent(props) {
15 |
16 | override fun RBuilder.render() {
17 | ul("col-md-4 list-group") {
18 | props.giphies.map { giphy ->
19 | giphyListItem(giphy, props.onSelect)
20 | }
21 | }
22 | }
23 |
24 | }
25 |
26 | fun RBuilder.giphyList(giphies: Array, onSelect: (Giphy) -> Unit) = child(GiphyList::class) {
27 | attrs.giphies = giphies
28 | attrs.onSelect = onSelect
29 | }
30 |
31 |
32 |
--------------------------------------------------------------------------------
/src/giphy/GiphyListItem.kt:
--------------------------------------------------------------------------------
1 | package giphy
2 |
3 | import kotlinx.html.js.onClickFunction
4 | import kotlinx.html.title
5 | import react.*
6 | import react.dom.div
7 | import react.dom.img
8 | import react.dom.li
9 |
10 | interface GiphyListItemProps : RProps {
11 | var giphy: Giphy
12 | var onSelect: (Giphy) -> Unit
13 | }
14 |
15 | class GiphyListItem(props: GiphyListItemProps) : RComponent(props) {
16 |
17 | override fun RBuilder.render() {
18 | val giphy = props.giphy
19 |
20 | li("list-group-item") {
21 | attrs.onClickFunction = { props.onSelect(giphy) }
22 |
23 | div("giphy-item media") {
24 | div("media-left") {
25 | img("media-object") {
26 | attrs {
27 | src = giphy.previewUrl
28 | alt = giphy.fileName
29 | title = giphy.fileName
30 | }
31 | }
32 | }
33 | }
34 | }
35 | }
36 |
37 | }
38 |
39 |
40 | fun RBuilder.giphyListItem(giphy: Giphy, onSelect: (Giphy) -> Unit) = child(GiphyListItem::class) {
41 | attrs.giphy = giphy
42 | attrs.key = giphy.id
43 | attrs.onSelect = onSelect
44 | }
45 |
46 |
--------------------------------------------------------------------------------
/src/giphy/giphy.css:
--------------------------------------------------------------------------------
1 |
2 | .giphy-item img {
3 | max-width: 80%;
4 | }
5 |
6 | .giphy-detail .details {
7 | margin: 10px 0 10px 0;
8 | }
9 |
10 | .list-group-item {
11 | cursor: pointer;
12 | }
13 |
14 | .list-group-item:hover {
15 | background-color: #eee;
16 | }
17 |
18 |
--------------------------------------------------------------------------------
/src/index/index.css:
--------------------------------------------------------------------------------
1 | body {
2 | margin: 0;
3 | padding: 0;
4 | font-family: sans-serif;
5 | }
6 |
--------------------------------------------------------------------------------
/src/index/index.kt:
--------------------------------------------------------------------------------
1 | package index
2 |
3 | import app.app
4 | import kotlinext.js.require
5 | import kotlinext.js.requireAll
6 | import react.dom.render
7 | import kotlin.browser.document
8 |
9 | fun main(args: Array) {
10 | requireAll(require.context("src", true, js("/\\.css$/")))
11 |
12 | render(document.getElementById("root")) {
13 | app()
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/src/lodash/Lodash.kt:
--------------------------------------------------------------------------------
1 | package lodash
2 |
3 | @JsModule("lodash")
4 | external val lodash: Lodash
5 |
6 | external interface Lodash {
7 | fun debounce(functionToDebounce: (K) -> V, debounceMillis: Int): (K) -> V
8 | }
9 |
10 |
11 |
--------------------------------------------------------------------------------