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
|
/node_modules
|
||||||
/target
|
/target
|
||||||
/.shadow-cljs
|
/.shadow-cljs
|
||||||
/*.iml
|
/*.iml
|
||||||
/.nrepl-port
|
/.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.
|
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.
|
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 charset="UTF-8">
|
||||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<link rel="stylesheet" href="/css/main.css">
|
<link rel="stylesheet" href="/main.css">
|
||||||
<title>Browser Starter</title>
|
<title>Mardika</title>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<h1>shadow-cljs - Browser</h1>
|
|
||||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||||
<div id="app"></div>
|
<div id="app"></div>
|
||||||
<script src="/js/main.js"></script>
|
<script src="/js/main.js"></script>
|
||||||
@@ -21,10 +21,10 @@ body {
|
|||||||
.overlay {
|
.overlay {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
bottom: 8%;
|
bottom: 1%;
|
||||||
left: 0;
|
left: 0;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 50%;
|
height: 40%;
|
||||||
background-color: rgba(255, 255, 255, 0.8);
|
background-color: rgba(255, 255, 255, 0.8);
|
||||||
z-index: 100;
|
z-index: 100;
|
||||||
}
|
}
|
||||||
@@ -5,15 +5,16 @@
|
|||||||
"src/test"]
|
"src/test"]
|
||||||
|
|
||||||
:dependencies
|
: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)]
|
:build-hooks [(shadow-env.core/hook)]
|
||||||
:dev-http
|
:dev-http
|
||||||
{8020 "public"}
|
{8020 "resources"}
|
||||||
|
|
||||||
:builds
|
:builds
|
||||||
{:app
|
{:app
|
||||||
{:target :browser
|
{:target :browser
|
||||||
:output-dir "public/js"
|
:output-dir "resources/js"
|
||||||
:asset-path "/js"
|
:asset-path "/js"
|
||||||
:closures-defines {
|
:closures-defines {
|
||||||
"process.env.API_KEY" (System/getenv "API_KEY")
|
"process.env.API_KEY" (System/getenv "API_KEY")
|
||||||
@@ -22,4 +23,4 @@
|
|||||||
}
|
}
|
||||||
:modules
|
:modules
|
||||||
{:main ; becomes public/js/main.js
|
{:main ; becomes public/js/main.js
|
||||||
{:init-fn starter.browser/init}}}}}
|
{:init-fn browser/main}}}}}
|
||||||
|
|||||||
@@ -1,19 +1,83 @@
|
|||||||
(ns server
|
(ns server
|
||||||
(:require [ring.middleware.resource :refer [wrap-resource]]
|
(: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]]
|
[ring.middleware.defaults :refer [wrap-defaults site-defaults]]
|
||||||
[compojure.core :refer [routes defroutes GET POST]]
|
[compojure.core :refer [routes defroutes GET POST]] [clj-http.client :as client]
|
||||||
[org.httpkit.server :as server]))
|
[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
|
(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 "\"}")})
|
(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
|
(def app
|
||||||
(-> (routes app-routes)
|
(-> (routes app-routes)
|
||||||
(wrap-resource "")
|
(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 []
|
(defn -main []
|
||||||
(println "Server running at http://localhost:8080")
|
(println "Server running at http://localhost:3030")
|
||||||
(server/run-server app {:port 8080}))
|
(server/run-server app {:port 3030}))
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -2,10 +2,33 @@
|
|||||||
(:require [re-frame.core :as rf]
|
(:require [re-frame.core :as rf]
|
||||||
[reagent.core :as r]
|
[reagent.core :as r]
|
||||||
[reagent.dom :as dom]
|
[reagent.dom :as dom]
|
||||||
[ajax.core :refer [GET json-response-format]]
|
[ajax.core :refer [GET raw-response-format json-response-format]]
|
||||||
[cljs.core.async :refer [chan timeout put! <! go-loop]]
|
[cljs.core.async :refer [chan timeout put! go <! go-loop]]
|
||||||
[env :refer [env]]))
|
[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]
|
(defn get-url [url]
|
||||||
(let [c (chan)]
|
(let [c (chan)]
|
||||||
(println "Fetching from URL:" url)
|
(println "Fetching from URL:" url)
|
||||||
@@ -82,8 +105,8 @@
|
|||||||
(rf/reg-event-db
|
(rf/reg-event-db
|
||||||
:search-movies
|
:search-movies
|
||||||
(fn [db [_ query]]
|
(fn [db [_ query]]
|
||||||
(let [url (str (env :base-url) "/search/movie?api_key=" (env :api-key) "&query=" query)]
|
(let [url (str (env :base-url) "/search/multi?api_key=" (env :api-key) "&query=" query)]
|
||||||
(println "Searching movies for query:" query "with URL:" url)
|
(println "Searching for query:" query "with URL:" url)
|
||||||
(get-url url)
|
(get-url url)
|
||||||
(when-not (:loading? db)
|
(when-not (:loading? db)
|
||||||
{:db (assoc db :loading? true)
|
{:db (assoc db :loading? true)
|
||||||
@@ -92,27 +115,72 @@
|
|||||||
(defn search-bar []
|
(defn search-bar []
|
||||||
(let [query (r/atom "")]
|
(let [query (r/atom "")]
|
||||||
(fn []
|
(fn []
|
||||||
[:div
|
[:div {:style {:display "flex"
|
||||||
|
:align-items "center"
|
||||||
|
:justify-content "center"
|
||||||
|
:margin-top "50px"}}
|
||||||
[:input {:type "text"
|
[:input {:type "text"
|
||||||
|
:class "search-input wide"
|
||||||
:placeholder "Search for title..."
|
:placeholder "Search for title..."
|
||||||
:value @query
|
:value @query
|
||||||
:on-change #(reset! query (-> % .-target .-value))}]
|
:on-change #(reset! query (-> % .-target .-value))
|
||||||
[:button {:on-click #(rf/dispatch [:search-movies @query])} "Search"]])))
|
: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]
|
(defn vid-src-handle [props]
|
||||||
(let [tv? (= "tv" (:media_type props))]
|
(let [tv? (= "tv" (:media_type props))]
|
||||||
(str (env :vid-src) (if tv? "tv" "movie") "/" (:id props) (if tv? "/1/1" ""))))
|
(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]
|
(defn expanded-interface [props]
|
||||||
[:div
|
(let [vidya @(rf/subscribe [:provider])]
|
||||||
{:style {:position "fixed" :top 0 :left 0 :width "100%" :height "100%" :background-color "white" :z-index 1000 :overflow "auto"}}
|
[:div
|
||||||
[:button {:on-click #(rf/dispatch [:collapse-item])} "Close"]
|
{:style {:position "fixed"
|
||||||
[:h3 (:title props)]
|
:bottom 0
|
||||||
[:iframe {:allowFullScreen true
|
:left 0
|
||||||
:autoPlay true
|
:width "100%"
|
||||||
:src (vid-src-handle props)
|
:height "100%"
|
||||||
:style {:width "100%"
|
:background-color "black"
|
||||||
:height "85%"}}]])
|
: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
|
(rf/reg-sub
|
||||||
:expanded-item
|
:expanded-item
|
||||||
@@ -129,6 +197,8 @@
|
|||||||
(fn [db _]
|
(fn [db _]
|
||||||
(dissoc db :expanded-item)))
|
(dissoc db :expanded-item)))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
;; Subscriptions
|
;; Subscriptions
|
||||||
(rf/reg-sub
|
(rf/reg-sub
|
||||||
:items
|
:items
|
||||||
@@ -159,8 +229,13 @@
|
|||||||
;; Show hover interface
|
;; Show hover interface
|
||||||
(if @hovered
|
(if @hovered
|
||||||
[:div
|
[:div
|
||||||
{:on-click #((println (:id props))
|
{:on-click
|
||||||
(rf/dispatch [:expand-item (:id props)]))}
|
#_(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
|
;; Hover overlay content
|
||||||
[:div {:class "overlay"}
|
[:div {:class "overlay"}
|
||||||
;; Title
|
;; Title
|
||||||
@@ -183,9 +258,11 @@
|
|||||||
[(with-hover item-component) item])])
|
[(with-hover item-component) item])])
|
||||||
|
|
||||||
(defn chunk-items [items chunk-size]
|
(defn chunk-items [items chunk-size]
|
||||||
(map-indexed (fn [index chunk]
|
(let [filtered-items (filter :poster_path items)]
|
||||||
{:id index :items chunk})
|
(map-indexed (fn [index chunk]
|
||||||
(partition-all chunk-size items)))
|
{:id index :items chunk})
|
||||||
|
(partition-all chunk-size filtered-items))))
|
||||||
|
|
||||||
|
|
||||||
;; Components
|
;; Components
|
||||||
(defn main-page []
|
(defn main-page []
|
||||||
@@ -204,7 +281,7 @@
|
|||||||
loading? @(rf/subscribe [:loading?])]
|
loading? @(rf/subscribe [:loading?])]
|
||||||
(println "Rendering items:" items)
|
(println "Rendering items:" items)
|
||||||
[:div
|
[:div
|
||||||
[:h1 "Endless Scrolling TMDB"]
|
[:h1 {:style {:text-align "center"}} "Mardika"]
|
||||||
[:div [search-bar]]
|
[:div [search-bar]]
|
||||||
(for [chunk (chunk-items items 4)] ;; Chunk items into groups of 4
|
(for [chunk (chunk-items items 4)] ;; Chunk items into groups of 4
|
||||||
^{:key (:id chunk)} ;; Use the unique :id for the key
|
^{: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