Compare commits
10 Commits
1217c428b0
...
35d279618a
| Author | SHA1 | Date | |
|---|---|---|---|
| 35d279618a | |||
| 98c98b843b | |||
| 7caccd764f | |||
| 45c8cecc34 | |||
| e9ab029584 | |||
| e1b7c5acc0 | |||
| 36055e8a02 | |||
| da9b1fb550 | |||
| 32c3e7259f | |||
| 6f5fc74aaa |
4
.gitignore
vendored
4
.gitignore
vendored
@@ -1,7 +1,7 @@
|
||||
/public/js
|
||||
/resources/js
|
||||
/node_modules
|
||||
/target
|
||||
/.shadow-cljs
|
||||
/*.iml
|
||||
/.nrepl-port
|
||||
/.idea
|
||||
/.idea
|
||||
21
README.md
21
README.md
@@ -126,3 +126,24 @@ Use `CTRL+C` to stop the `watch` process and instead run `npx shadow-cljs releas
|
||||
When done you can open `http://localhost:8020` and see the `release` build in action. At this point you would usually copy the `public` directory to the "production" web server.
|
||||
|
||||
Note that in the default config we overwrote the `public/js/main.js` created by the `watch`. You can also configure a different path to use for release builds but writing the output to the same file means we do not have to change the `index.html` and test everything as is.
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
export $(cat ./.env | grep -v ^# | xargs) >/dev/null
|
||||
npx shadow-cljs watch app
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
|
||||
color: green;
|
||||
}
|
||||
@@ -4,11 +4,10 @@
|
||||
<meta charset="UTF-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link rel="stylesheet" href="/css/main.css">
|
||||
<title>Browser Starter</title>
|
||||
<link rel="stylesheet" href="/main.css">
|
||||
<title>Mardika</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>shadow-cljs - Browser</h1>
|
||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||
<div id="app"></div>
|
||||
<script src="/js/main.js"></script>
|
||||
@@ -21,10 +21,10 @@ body {
|
||||
.overlay {
|
||||
text-align: center;
|
||||
position: absolute;
|
||||
bottom: 8%;
|
||||
bottom: 1%;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 50%;
|
||||
height: 40%;
|
||||
background-color: rgba(255, 255, 255, 0.8);
|
||||
z-index: 100;
|
||||
}
|
||||
@@ -5,15 +5,16 @@
|
||||
"src/test"]
|
||||
|
||||
:dependencies
|
||||
[[reagent "1.1.1"] [re-frame "1.4.3"] [cljs-ajax "0.8.1"] [environ "1.2.0"] [adzerk/env "0.4.0"]]
|
||||
[[reagent "1.1.1"] [re-frame "1.4.3"] [cljs-ajax "0.8.1"]
|
||||
]
|
||||
:build-hooks [(shadow-env.core/hook)]
|
||||
:dev-http
|
||||
{8020 "public"}
|
||||
{8020 "resources"}
|
||||
|
||||
:builds
|
||||
{:app
|
||||
{:target :browser
|
||||
:output-dir "public/js"
|
||||
:output-dir "resources/js"
|
||||
:asset-path "/js"
|
||||
:closures-defines {
|
||||
"process.env.API_KEY" (System/getenv "API_KEY")
|
||||
@@ -22,4 +23,4 @@
|
||||
}
|
||||
:modules
|
||||
{:main ; becomes public/js/main.js
|
||||
{:init-fn starter.browser/init}}}}}
|
||||
{:init-fn browser/main}}}}}
|
||||
|
||||
@@ -1,19 +1,83 @@
|
||||
(ns server
|
||||
(:require [ring.middleware.resource :refer [wrap-resource]]
|
||||
[clojure.core.async :refer [go]] [clojure.java.io :as io]
|
||||
[ring.middleware.cors :refer [wrap-cors]] [clojure.string :as str]
|
||||
[ring.middleware.defaults :refer [wrap-defaults site-defaults]]
|
||||
[compojure.core :refer [routes defroutes GET POST]]
|
||||
[org.httpkit.server :as server]))
|
||||
[compojure.core :refer [routes defroutes GET POST]] [clj-http.client :as client]
|
||||
[org.httpkit.server :as server])
|
||||
(:import [java.io FileOutputStream]))
|
||||
|
||||
(defn download-file [url destination]
|
||||
(println "Downloading from:" url)
|
||||
(let [content (slurp url)
|
||||
response (client/get url)]
|
||||
;; Ensure parent directories exist
|
||||
(io/make-parents destination)
|
||||
;; Save the content to the destination file
|
||||
(spit destination content)
|
||||
{:status 200
|
||||
:headers {"Content-Type" (get-in response [:headers "content-type"] "text/plain")}
|
||||
:body (:body response)}))
|
||||
|
||||
|
||||
|
||||
(defn proxy-url [url]
|
||||
(if url
|
||||
(try
|
||||
(let [response (client/get url)]
|
||||
{:status 200
|
||||
:headers {"Content-Type" (get-in response [:headers "content-type"] "text/plain")
|
||||
"Access-Control-Allow-Origin" "*"
|
||||
"User-Agent" "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:93.0) Gecko/20100101 Firefox/93.0"
|
||||
"Access-Control-Expose-Headers" "*"
|
||||
"X-Final-Destination" url
|
||||
"X-Set-Cookie" (get-in response [:headers "set-cookie"] "")
|
||||
"Vary" "Origin"
|
||||
"Redirect" "follow"}
|
||||
:body (:body response)})
|
||||
(catch Exception e
|
||||
{:status 500
|
||||
:headers {"Content-Type" "application/json"}
|
||||
:body (str "{\"error\": \"" (.getMessage e) "\"}")}))
|
||||
{:status 400
|
||||
:headers {"Content-Type" "application/json"}
|
||||
:body "{\"error\": \"Missing 'url' query parameter\"}"}))
|
||||
|
||||
|
||||
(defroutes app-routes
|
||||
(GET "/api/data" [] {:status 200 :headers {"Content-Type" "application/json"} :body "{\"message\": \"Something I guess\"}"})
|
||||
(GET "/" [] (slurp (clojure.java.io/resource "index.html")))
|
||||
(GET "/api" {query-params :query-params}
|
||||
(if (not= nil query-params)
|
||||
(do
|
||||
(println "Has /api so we will use the query param \n\n")
|
||||
(let [url (get query-params "url")]
|
||||
(proxy-url url)))
|
||||
)
|
||||
)
|
||||
#_(GET "/player" [] {:status 200
|
||||
:headers {"Content-Type" (get-in response [:headers "content-type"] "text/plain")}
|
||||
:body (:body response)})
|
||||
(POST "/api/data" [data] {:status 200 :headers {"Content-Type" "application/json"} :body (str "{\"received\": \"" data "\"}")})
|
||||
(GET "*" [] {:status 404 :headers {"Content-Type" "text/plain"} :body "Not Found"}))
|
||||
(GET "/*" {uri :uri}
|
||||
(if (not (str/starts-with? uri "/api"))
|
||||
(do
|
||||
(println "Not /api so we are going to use the URI \n\n")
|
||||
(println uri)
|
||||
(download-file (str "https://player.vidbinge.com" uri) (str "resources" uri))))
|
||||
)
|
||||
#_(GET "*" [] {:status 404 :headers {"Content-Type" "text/plain"} :body "Not Found"}))
|
||||
|
||||
(def app
|
||||
(-> (routes app-routes)
|
||||
(wrap-resource "")
|
||||
(wrap-defaults site-defaults)))
|
||||
(wrap-defaults site-defaults)
|
||||
(wrap-cors :access-control-allow-origin [#".*"]
|
||||
:access-control-allow-methods [:get :post :put :delete :options]
|
||||
:access-control-allow-headers ["Content-Type"])))
|
||||
|
||||
|
||||
(defn -main []
|
||||
(println "Server running at http://localhost:8080")
|
||||
(server/run-server app {:port 8080}))
|
||||
(println "Server running at http://localhost:3030")
|
||||
(server/run-server app {:port 3030}))
|
||||
|
||||
|
||||
|
||||
@@ -2,10 +2,33 @@
|
||||
(:require [re-frame.core :as rf]
|
||||
[reagent.core :as r]
|
||||
[reagent.dom :as dom]
|
||||
[ajax.core :refer [GET json-response-format]]
|
||||
[cljs.core.async :refer [chan timeout put! <! go-loop]]
|
||||
[ajax.core :refer [GET raw-response-format json-response-format]]
|
||||
[cljs.core.async :refer [chan timeout put! go <! go-loop]]
|
||||
[env :refer [env]]))
|
||||
|
||||
|
||||
(defn temp-url [url callback]
|
||||
(let [c (chan)]
|
||||
(println "Fetching from URL:" url)
|
||||
(GET url
|
||||
{:response-format (raw-response-format)
|
||||
:handler #(do
|
||||
(println "Received response:" (js->clj %))
|
||||
(put! c %))
|
||||
:error-handler #(do
|
||||
(js/console.error "Error occurred:" %)
|
||||
(put! c nil))})
|
||||
(go-loop []
|
||||
(let [response (<! c)]
|
||||
(println "processing " response)
|
||||
(if (nil? response)
|
||||
(do
|
||||
(println "Response is nil, retrying in 1 second...")
|
||||
(<! (timeout 1000)) ; Wait for 1 second before retrying
|
||||
(recur)) ; Retry
|
||||
)
|
||||
(callback response)))))
|
||||
|
||||
(defn get-url [url]
|
||||
(let [c (chan)]
|
||||
(println "Fetching from URL:" url)
|
||||
@@ -82,8 +105,8 @@
|
||||
(rf/reg-event-db
|
||||
:search-movies
|
||||
(fn [db [_ query]]
|
||||
(let [url (str (env :base-url) "/search/movie?api_key=" (env :api-key) "&query=" query)]
|
||||
(println "Searching movies for query:" query "with URL:" url)
|
||||
(let [url (str (env :base-url) "/search/multi?api_key=" (env :api-key) "&query=" query)]
|
||||
(println "Searching for query:" query "with URL:" url)
|
||||
(get-url url)
|
||||
(when-not (:loading? db)
|
||||
{:db (assoc db :loading? true)
|
||||
@@ -92,27 +115,72 @@
|
||||
(defn search-bar []
|
||||
(let [query (r/atom "")]
|
||||
(fn []
|
||||
[:div
|
||||
[:div {:style {:display "flex"
|
||||
:align-items "center"
|
||||
:justify-content "center"
|
||||
:margin-top "50px"}}
|
||||
[:input {:type "text"
|
||||
:class "search-input wide"
|
||||
:placeholder "Search for title..."
|
||||
:value @query
|
||||
:on-change #(reset! query (-> % .-target .-value))}]
|
||||
[:button {:on-click #(rf/dispatch [:search-movies @query])} "Search"]])))
|
||||
:on-change #(reset! query (-> % .-target .-value))
|
||||
:on-key-down #(when (= (.-key %) "Enter")
|
||||
(rf/dispatch [:search-movies @query]))}]
|
||||
[:button {:class "search-button small"
|
||||
:on-click #(rf/dispatch [:search-movies @query])} "Search"]])))
|
||||
|
||||
(defn vid-src-handle [props]
|
||||
(let [tv? (= "tv" (:media_type props))]
|
||||
(str (env :vid-src) (if tv? "tv" "movie") "/" (:id props) (if tv? "/1/1" ""))))
|
||||
|
||||
|
||||
(rf/reg-sub
|
||||
:provider
|
||||
(fn [db _]
|
||||
(:provider db)))
|
||||
|
||||
(rf/reg-event-db
|
||||
:load-provider
|
||||
(fn [db [_ provider]]
|
||||
(assoc db :provider provider)))
|
||||
|
||||
(def iframe-ref (r/atom nil))
|
||||
|
||||
(defn expanded-interface [props]
|
||||
[:div
|
||||
{:style {:position "fixed" :top 0 :left 0 :width "100%" :height "100%" :background-color "white" :z-index 1000 :overflow "auto"}}
|
||||
[:button {:on-click #(rf/dispatch [:collapse-item])} "Close"]
|
||||
[:h3 (:title props)]
|
||||
[:iframe {:allowFullScreen true
|
||||
:autoPlay true
|
||||
:src (vid-src-handle props)
|
||||
:style {:width "100%"
|
||||
:height "85%"}}]])
|
||||
(let [vidya @(rf/subscribe [:provider])]
|
||||
[:div
|
||||
{:style {:position "fixed"
|
||||
:bottom 0
|
||||
:left 0
|
||||
:width "100%"
|
||||
:height "100%"
|
||||
:background-color "black"
|
||||
:z-index 1000
|
||||
:overflow "auto"}}
|
||||
[:button.close {:on-click #(rf/dispatch [:collapse-item])} "Back"]
|
||||
[:iframe {:allowFullScreen true
|
||||
:autoPlay true
|
||||
:src (vid-src-handle props)
|
||||
:ref #(reset! iframe-ref %)
|
||||
:style {:width "100%"
|
||||
:height "100%"
|
||||
:z-index 1000}
|
||||
:on-load #((go (<! (timeout 8000 ))
|
||||
(js/console.log (.-documentElement js/document))))}]
|
||||
#_(when vidya
|
||||
[:div {:dangerouslySetInnerHTML {:__html vidya}}])]))
|
||||
|
||||
|
||||
|
||||
|
||||
#_[ [:div.vidya
|
||||
{:dangerouslySetInnerHTML {:__html (or @html-content "Loading...")}}]] ; Inject raw HTML content]
|
||||
#_[:iframe {:allowFullScreen true
|
||||
:autoPlay true
|
||||
:src (vid-src-handle props)
|
||||
:style {:width "100%"
|
||||
:height "95%"
|
||||
:z-index 1000}}]
|
||||
|
||||
(rf/reg-sub
|
||||
:expanded-item
|
||||
@@ -129,6 +197,8 @@
|
||||
(fn [db _]
|
||||
(dissoc db :expanded-item)))
|
||||
|
||||
|
||||
|
||||
;; Subscriptions
|
||||
(rf/reg-sub
|
||||
:items
|
||||
@@ -159,8 +229,13 @@
|
||||
;; Show hover interface
|
||||
(if @hovered
|
||||
[:div
|
||||
{:on-click #((println (:id props))
|
||||
(rf/dispatch [:expand-item (:id props)]))}
|
||||
{:on-click
|
||||
#_(fn [e] (set! (.-href js/window.location) "/api?url=https://vidbinge.dev/embed/movie/1104845"))
|
||||
(do
|
||||
|
||||
|
||||
#_(temp-url "http://localhost:8080/api?url=https://vidbinge.dev/embed/movie/1104845" (fn [e] (rf/dispatch [:load-provider e])))
|
||||
#(rf/dispatch [:expand-item (:id props)]))}
|
||||
;; Hover overlay content
|
||||
[:div {:class "overlay"}
|
||||
;; Title
|
||||
@@ -183,9 +258,11 @@
|
||||
[(with-hover item-component) item])])
|
||||
|
||||
(defn chunk-items [items chunk-size]
|
||||
(map-indexed (fn [index chunk]
|
||||
{:id index :items chunk})
|
||||
(partition-all chunk-size items)))
|
||||
(let [filtered-items (filter :poster_path items)]
|
||||
(map-indexed (fn [index chunk]
|
||||
{:id index :items chunk})
|
||||
(partition-all chunk-size filtered-items))))
|
||||
|
||||
|
||||
;; Components
|
||||
(defn main-page []
|
||||
@@ -204,7 +281,7 @@
|
||||
loading? @(rf/subscribe [:loading?])]
|
||||
(println "Rendering items:" items)
|
||||
[:div
|
||||
[:h1 "Endless Scrolling TMDB"]
|
||||
[:h1 {:style {:text-align "center"}} "Mardika"]
|
||||
[:div [search-bar]]
|
||||
(for [chunk (chunk-items items 4)] ;; Chunk items into groups of 4
|
||||
^{:key (:id chunk)} ;; Use the unique :id for the key
|
||||
|
||||
@@ -1,223 +0,0 @@
|
||||
(ns starter.browser
|
||||
(:require [re-frame.core :as rf]
|
||||
[reagent.core :as r]
|
||||
[reagent.dom :as dom]
|
||||
[ajax.core :refer [GET json-response-format]]
|
||||
[cljs.core.async :refer [chan timeout put! <! go-loop]]
|
||||
[starter.env :refer [env]]
|
||||
))
|
||||
|
||||
(println env)
|
||||
|
||||
(defn get-url [url]
|
||||
(let [c (chan)]
|
||||
(js/console.log "Fetching movies for URL:" url)
|
||||
(GET url
|
||||
{:response-format (json-response-format {:keywords? true})
|
||||
:handler #(do
|
||||
(js/console.log "Received response:" (js->clj %))
|
||||
(put! c %))
|
||||
:error-handler #(do
|
||||
(js/console.error "Error occurred:" %)
|
||||
(put! c nil))})
|
||||
(go-loop []
|
||||
(let [response (<! c)]
|
||||
(println "Processing: " (:results response))
|
||||
(if (nil? response)
|
||||
(do
|
||||
(js/console.log "Response is nil, retrying in 1 second...")
|
||||
(<! (timeout 1000)) ; Wait for 1 second before retrying
|
||||
(recur)) ; Retry
|
||||
(let [results (:results response)]
|
||||
(js/console.log "Dispatching results:" results)
|
||||
(rf/dispatch [:update-items results])))))))
|
||||
|
||||
(defn fetch-movies [page]
|
||||
(let [url (str (env :base-url) "/movie/popular?api_key=" (env :api-key) "&page=" page)]
|
||||
(js/console.log "Fetching movies for page" page "with URL:" url)
|
||||
(get-url url)))
|
||||
|
||||
(rf/reg-event-db
|
||||
:initialize
|
||||
(fn [_ _]
|
||||
(js/console.log "Initializing state")
|
||||
(let [initial-state {:items [] :loading? true :page 1}]
|
||||
(fetch-movies 1)
|
||||
initial-state)))
|
||||
|
||||
(rf/reg-event-db
|
||||
:update-items
|
||||
(fn [db [_ new-items]]
|
||||
(js/console.log "Updating items with:" new-items)
|
||||
(if (seq new-items)
|
||||
(-> db
|
||||
(update :items concat new-items)
|
||||
(update :page inc)
|
||||
(assoc :loading? false))
|
||||
(assoc db :loading? false))))
|
||||
|
||||
(rf/reg-event-db
|
||||
:fetch-more-items
|
||||
(fn [db _]
|
||||
(let [page (:page db)]
|
||||
(js/console.log "Dispatching fetch-more-items for page" page)
|
||||
(fetch-movies page)
|
||||
(assoc db :loading? true))))
|
||||
|
||||
;; Trigger fetch-more-items event on scroll
|
||||
(rf/reg-event-fx
|
||||
:scroll-handler
|
||||
(fn [{:keys [db]} _]
|
||||
(js/console.log "Scroll handler triggered")
|
||||
(when (<= (- (.-scrollHeight (.-documentElement js/document))
|
||||
(.-scrollTop (.-documentElement js/document))
|
||||
(.-clientHeight (.-documentElement js/document))) 200) ; Increased threshold
|
||||
(js/console.log "Almost at the bottom of the page")
|
||||
(when-not (:loading? db)
|
||||
{:db (assoc db :loading? true)
|
||||
:dispatch [:fetch-more-items]}))))
|
||||
|
||||
;; Add scroll event listener
|
||||
(defn handle-scroll []
|
||||
(js/console.log "Scroll event detected")
|
||||
(rf/dispatch [:scroll-handler]))
|
||||
|
||||
(rf/reg-event-db
|
||||
:search-movies
|
||||
(fn [db [_ query]]
|
||||
(let [url (str (env :base-url) "/search/movie?api_key=" (env :api-key) "&query=" query)]
|
||||
(js/console.log "Searching movies for query:" query "with URL:" url)
|
||||
(get-url url)
|
||||
(when-not (:loading? db)
|
||||
{:db (assoc db :loading? true)
|
||||
:dispatch [:fetch-more-items]}))))
|
||||
|
||||
(defn search-bar []
|
||||
(let [query (r/atom "")]
|
||||
(fn []
|
||||
[:div
|
||||
[:input {:type "text"
|
||||
:placeholder "Search for a movie..."
|
||||
:value @query
|
||||
:on-change #(reset! query (-> % .-target .-value))}]
|
||||
[:button {:on-click #(rf/dispatch [:search-movies @query])} "Search"]])))
|
||||
|
||||
(defn expanded-interface [props]
|
||||
[:div
|
||||
{:style {:position "fixed" :top 0 :left 0 :width "100%" :height "100%" :background-color "white" :z-index 1000 :overflow "auto"}}
|
||||
[:button {:on-click #(rf/dispatch [:collapse-item])} "Close"]
|
||||
[:h3 (:title props)]
|
||||
[:p (:overview props)]
|
||||
[:iframe {:allowFullScreen true
|
||||
:autoPlay true
|
||||
:src (str (env :vid-src) (:id props))
|
||||
:style {:width "100%"
|
||||
:height "85%"}}]])
|
||||
|
||||
(rf/reg-sub
|
||||
:expanded-item
|
||||
(fn [db _]
|
||||
(println "Getting" (:expanded-item db))
|
||||
(:expanded-item db)))
|
||||
|
||||
(rf/reg-event-db
|
||||
:expand-item
|
||||
(fn [db [_ item-id]]
|
||||
(js/console.log "Setting expanded-item:" item-id)
|
||||
(assoc db :expanded-item item-id)))
|
||||
|
||||
(rf/reg-event-db
|
||||
:collapse-item
|
||||
(fn [db _]
|
||||
(dissoc db :expanded-item)))
|
||||
|
||||
;; Subscriptions
|
||||
(rf/reg-sub
|
||||
:items
|
||||
(fn [db _]
|
||||
(:items db)))
|
||||
|
||||
(rf/reg-sub
|
||||
:loading?
|
||||
(fn [db _]
|
||||
(:loading? db)))
|
||||
|
||||
(defn with-hover [component]
|
||||
(fn [props]
|
||||
(let [hovered (r/atom false)
|
||||
expanded-item @(rf/subscribe [:expanded-item])]
|
||||
(fn [props]
|
||||
(println expanded-item)
|
||||
[:<>
|
||||
;; Render the expanded interface if this item is expanded
|
||||
(if (= (:id props) @(rf/subscribe [:expanded-item]))
|
||||
[expanded-interface props]
|
||||
;; Otherwise, handle hover logic and normal view
|
||||
[:div.item {:on-mouse-enter #(reset! hovered true)
|
||||
:on-mouse-leave #(reset! hovered false)
|
||||
:style {:background-color (if @hovered "lightblue" "lightgray")
|
||||
:position "relative"
|
||||
:width "100%"
|
||||
:height "100%"}}
|
||||
;; Show hover interface
|
||||
(if @hovered
|
||||
[:div
|
||||
{:on-click #((println (:id props))
|
||||
(rf/dispatch [:expand-item (:id props)]))}
|
||||
;; Hover overlay content
|
||||
[:div {:class "overlay"}
|
||||
;; Title
|
||||
[:h3 (:title props)]
|
||||
;; Description
|
||||
[:p (:overview props)]]
|
||||
[:img {:src (str "https://image.tmdb.org/t/p/w342" (:poster_path props))}]]
|
||||
;; Default view for non-hovered state
|
||||
[component props])])]))))
|
||||
|
||||
(defn item-component [props]
|
||||
[:div.item
|
||||
[:img {:src (str "https://image.tmdb.org/t/p/w342" (:poster_path props))
|
||||
:id (str "cover-img" (:id props))}]])
|
||||
|
||||
(defn item-components [items]
|
||||
[:div.items-container
|
||||
(for [item items]
|
||||
^{:key (:id item)}
|
||||
[(with-hover item-component) item])])
|
||||
|
||||
(defn chunk-items [items chunk-size]
|
||||
(map-indexed (fn [index chunk]
|
||||
{:id index :items chunk})
|
||||
(partition-all chunk-size items)))
|
||||
|
||||
;; Components
|
||||
(defn main-page []
|
||||
(r/create-class
|
||||
{:component-did-mount
|
||||
(fn []
|
||||
(js/console.log "Component did mount")
|
||||
(js/addEventListener "scroll" handle-scroll))
|
||||
:component-will-unmount
|
||||
(fn []
|
||||
(js/console.log "Component will unmount")
|
||||
(js/removeEventListener "scroll" handle-scroll))
|
||||
:reagent-render
|
||||
(fn []
|
||||
(let [items @(rf/subscribe [:items])
|
||||
loading? @(rf/subscribe [:loading?])]
|
||||
(js/console.log "Rendering items:" items)
|
||||
[:div
|
||||
[:h1 "Endless Scrolling TMDB Movies"]
|
||||
[:div [search-bar]]
|
||||
(for [chunk (chunk-items items 4)] ;; Chunk items into groups of 4
|
||||
^{:key (:id chunk)} ;; Use the unique :id for the key
|
||||
[item-components (:items chunk)])
|
||||
(when loading?
|
||||
[:div "Loading..."])]))}))
|
||||
|
||||
(defn init []
|
||||
(rf/dispatch-sync [:initialize])
|
||||
(dom/render [main-page] (.getElementById js/document "app")))
|
||||
|
||||
;; Initialize app
|
||||
(init)
|
||||
@@ -1,5 +0,0 @@
|
||||
(ns starter.fake-env)
|
||||
|
||||
(def env {:api-key ""
|
||||
:base-url ""
|
||||
:vid-src ""})
|
||||
Reference in New Issue
Block a user