Skip to content

Commit

Permalink
Merge pull request #5524 from jwarwick/stats_ui
Browse files Browse the repository at this point in the history
Add `Launch Replay` button.
  • Loading branch information
NoahTheDuke authored Feb 1, 2021
2 parents 35beef4 + ca9739c commit 80620a6
Show file tree
Hide file tree
Showing 11 changed files with 180 additions and 99 deletions.
2 changes: 1 addition & 1 deletion project.clj
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@
:compiler {:output-to "resources/public/cljs/app10.js"
:output-dir "resources/public/cljs"
:main "dev.nr"
:asset-path "cljs"
:asset-path "/cljs"
:optimizations :none
:source-map-timestamp true
:npm-deps false
Expand Down
1 change: 1 addition & 0 deletions src/clj/web/api.clj
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@
(GET "/ws" req ws/handshake-handler)
(POST "/ws" req ws/post-handler)

(GET "/replay/:gameid" [] stats/replay-handler)
(GET "/*" [] pages/index-page))

(defroutes public-routes
Expand Down
95 changes: 52 additions & 43 deletions src/clj/web/pages.clj
Original file line number Diff line number Diff line change
Expand Up @@ -12,50 +12,59 @@
[web.config :refer [server-config frontend-version]]
[web.utils :refer [response]]))

(defn index-page [{:keys [user] :as req} & content]
(hiccup/html5
[:head
[:meta {:charset "utf-8"}]
[:meta {:name "viewport" :content "width=device-width, initial-scale=0.6, minimal-ui"}]
[:meta {:name "apple-mobile-web-app-capable" :content "yes"}]
[:link {:rel "apple-touch-icon" :href "img/icons/jinteki_167.png"}]
[:title "Jinteki"]
(hiccup/include-css "/css/carousel.css")
(hiccup/include-css (str "/css/netrunner.css?v=" @frontend-version))
(hiccup/include-css "/lib/toastr/toastr.min.css")
(hiccup/include-css "/lib/jqueryui/themes/base/jquery-ui.min.css")]
[:body
[:div {:style {:display "hidden"}
:id "server-originated-data"
:data-version @frontend-version}]
[:div#main-content]
[:audio#ting
[:source {:src "/sound/ting.mp3" :type "audio/mp3"}]
[:source {:src "/sound/ting.ogg" :type "audio/ogg"}]]
(hiccup/include-js "/lib/jquery/jquery.min.js")
(hiccup/include-js "/lib/jqueryui/jquery-ui.min.js")
(hiccup/include-js "/lib/bootstrap/dist/js/bootstrap.js")
(hiccup/include-js "/lib/moment/min/moment.min.js")
(hiccup/include-js "/lib/toastr/toastr.min.js")
(hiccup/include-js "/lib/howler/dist/howler.min.js")
(when user
[:div#sente-csrf-token {:data-csrf-token anti-forgery/*anti-forgery-token*}])
[:script {:type "text/javascript"}
(str "var user=" (json/generate-string user) ";")]
(defn index-page
([req] (index-page req nil nil))
([{:keys [user] :as req} og replay-id]
(hiccup/html5
[:head
[:meta {:charset "utf-8"}]
[:meta {:name "viewport" :content "width=device-width, initial-scale=0.6, minimal-ui"}]
[:meta {:name "apple-mobile-web-app-capable" :content "yes"}]
[:meta {:property "og:type" :content (:type og "website")}]
[:meta {:property "og:url" :content (:url og "https://jinteki.net")}]
[:meta {:property "og:image" :content (:image og "https://www.jinteki.net/img/icons/jinteki_167.png")}]
[:meta {:property "og:title" :content (:title og "Play Android: Netrunner in your browser")}]
[:meta {:property "og:site_name" :content (:site_name og "jinteki.net")}]
[:meta {:property "og:description" :content (:description og "Build Netrunner decks and test them online against other players.")}]
[:link {:rel "apple-touch-icon" :href "/img/icons/jinteki_167.png"}]
[:title "Jinteki"]
(hiccup/include-css "/css/carousel.css")
(hiccup/include-css (str "/css/netrunner.css?v=" @frontend-version))
(hiccup/include-css "/lib/toastr/toastr.min.css")
(hiccup/include-css "/lib/jqueryui/themes/base/jquery-ui.min.css")]
[:body
[:div {:style {:display "hidden"}
:id "server-originated-data"
:data-version @frontend-version
:data-replay-id replay-id}]
[:div#main-content]
[:audio#ting
[:source {:src "/sound/ting.mp3" :type "audio/mp3"}]
[:source {:src "/sound/ting.ogg" :type "audio/ogg"}]]
(hiccup/include-js "/lib/jquery/jquery.min.js")
(hiccup/include-js "/lib/jqueryui/jquery-ui.min.js")
(hiccup/include-js "/lib/bootstrap/dist/js/bootstrap.js")
(hiccup/include-js "/lib/moment/min/moment.min.js")
(hiccup/include-js "/lib/toastr/toastr.min.js")
(hiccup/include-js "/lib/howler/dist/howler.min.js")
(when user
[:div#sente-csrf-token {:data-csrf-token anti-forgery/*anti-forgery-token*}])
[:script {:type "text/javascript"}
(str "var user=" (json/generate-string user) ";")]

(if (= "dev" @web.config/server-mode)
(list (hiccup/include-js "/cljs/goog/base.js")
(hiccup/include-js (str "cljs/app10.js?v=" @frontend-version))
[:script
(for [req ["dev.figwheel"]]
(str "goog.require(\"" req "\");"))])
(list (hiccup/include-js (str "js/app10.js?v=" @frontend-version))
[:script
"(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)})(window,document,'script','//www.google-analytics.com/analytics.js','ga');
ga('create', 'UA-20250150-2', 'www.jinteki.net');"
(when user
(str "ga('set', '&uid', '" (:username user) "');"))
"ga('send', 'pageview');"]))]))
(if (= "dev" @web.config/server-mode)
(list (hiccup/include-js "/cljs/goog/base.js")
(hiccup/include-js (str "/cljs/app10.js?v=" @frontend-version))
[:script
(for [req ["dev.figwheel"]]
(str "goog.require(\"" req "\");"))])
(list (hiccup/include-js (str "/js/app10.js?v=" @frontend-version))
[:script
"(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)})(window,document,'script','//www.google-analytics.com/analytics.js','ga');
ga('create', 'UA-20250150-2', 'www.jinteki.net');"
(when user
(str "ga('set', '&uid', '" (:username user) "');"))
"ga('send', 'pageview');"]))])))

(defn reset-password-page
[{{:keys [token]} :params}]
Expand Down
36 changes: 35 additions & 1 deletion src/clj/web/stats.clj
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,17 @@
[monger.result :refer [acknowledged?]]
[monger.operators :refer :all]
[monger.query :as mq]
[web.pages :as pages]
[web.ws :as ws]
[web.utils :refer [response json-response]]
[game.utils :refer [dissoc-in]]
[jinteki.cards :refer [all-cards]]
[clojure.set :refer [rename-keys]]
[clojure.string :refer [lower-case]]
[clj-time.core :as t]
[cheshire.core :as json])
[cheshire.core :as json]
[hiccup.page :as hiccup]
[ring.util.request :refer [request-url]])

(:import org.bson.types.ObjectId))

Expand Down Expand Up @@ -250,3 +254,33 @@
(println "Caught exception sharing game: " (.getMessage e))
(response 500 {:message "Server error"}))))
(response 401 {:message "Unauthorized"})))

(defn- get-winner-card
[winner corp runner host]
(let [win-id (:identity ((keyword winner) {:corp corp :runner runner}))
win-card (:code (@all-cards win-id))]
(if win-card
(str host "img/cards/" win-card ".png")
(str host "img/icons/jinteki_167.png"))))

(defn replay-handler [{{:keys [gameid n d]} :params
scheme :scheme
headers :headers
:as req}]
(let [{:keys [replay winner corp runner title]} (mc/find-one-as-map db :game-logs {:gameid gameid})
replay (or replay {})
gameid-str (if (and n d) (str gameid "?n=" n "&d=" d) gameid)]
(if (empty? replay)
(response 404 {:message "Replay not found"})
(let [corp-user (get-in corp [:player :username] "Unknown")
corp-id (:identity corp)
runner-user (get-in runner [:player :username] "Unknown")
runner-id (:identity runner)
host (str (name scheme) "://" (get headers "host") "/")
og {:type "website"
:url (request-url req)
:image (get-winner-card winner corp runner host)
:title (str "REPLAY: " title)
:site_name "jinteki.net"
:description (str corp-user " (" corp-id ") vs. " runner-user " (" runner-id ")")}]
(pages/index-page req og gameid-str)))))
8 changes: 1 addition & 7 deletions src/cljs/nr/chat.cljs
Original file line number Diff line number Diff line change
Expand Up @@ -290,12 +290,6 @@
[:div
[msg-input-view (:channel @s)]])]]])})))

(defn get-data
[tag]
(-> (.getElementById js/document "server-originated-data")
(.getAttribute (str "data-" tag))
(cljs.reader/read-string)))

(defn chat-page []
(let [active (r/cursor app-state [:active-page])
s (r/atom {:channel :general
Expand All @@ -315,4 +309,4 @@
[:h1 (tr [:chat.title "Play Android: Netrunner in your browser"])]
[news]
[chat s old scroll-top]
[:div#version [:span (str "Version " (get-data "version"))]]]))))
[:div#version [:span (str "Version " (:app-version @app-state "Unknown"))]]]))))
4 changes: 2 additions & 2 deletions src/cljs/nr/gameboard.cljs
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@
(let [n (:n @replay-status)
d (- (count (get-in @replay-timeline [n :diffs]))
(count (:diffs @replay-status)))]
(str origin "/play?" (:gameid @game-state) "&n=" n "&d=" d)))
(str origin "/replay/" (:gameid @game-state) "?n=" n "&d=" d)))

(defn set-replay-side [side]
(reset! replay-side side)
Expand Down Expand Up @@ -2227,4 +2227,4 @@
[:button {:on-click #(swap! show-replay-link not)} "Share"])
(when-not (= "local-replay" (:gameid @game-state))
[:input {:style (if @show-replay-link {:display "block"} {:display "none"})
:type "text" :value (generate-replay-link (.-origin (.-location js/window)))}])]]])])))})))))
:type "text" :read-only true :value (generate-replay-link (.-origin (.-location js/window)))}])]]])])))})))))
30 changes: 15 additions & 15 deletions src/cljs/nr/gamelobby.cljs
Original file line number Diff line number Diff line change
Expand Up @@ -361,21 +361,21 @@

(defn games-list-panel [s games gameid password-gameid user]
[:div.games
(let [params (.-search (.-location js/window))]
(when (not-empty params)
(let [id-match (re-find #"([0-9a-f\-]+)" params)
n-match (re-find #"n=(\d+)" params)
d-match (re-find #"d=(\d+)" params)
replay-id (nth id-match 1)
n (when n-match (js/parseInt (nth n-match 1)))
d (when d-match (js/parseInt (nth d-match 1)))]
(when replay-id
(.replaceState (.-history js/window) {} "" "/play") ; remove GET parameters from url
(if (and n d)
(start-shared-replay s replay-id {:n n :d d})
(start-shared-replay s replay-id))
(resume-sound)
nil))))
(when-let [params (:replay-id @app-state)]
(swap! app-state dissoc :replay-id)
(let [id-match (re-find #"([0-9a-f\-]+)" params)
n-match (re-find #"n=(\d+)" params)
d-match (re-find #"d=(\d+)" params)
replay-id (nth id-match 1)
n (when n-match (js/parseInt (nth n-match 1)))
d (when d-match (js/parseInt (nth d-match 1)))]
(when replay-id
(.replaceState (.-history js/window) {} "" "/play") ; remove query parameters from url
(if (and n d)
(start-shared-replay s replay-id {:n n :d d})
(start-shared-replay s replay-id))
(resume-sound)
nil)))
[:div.button-bar
[:div.rooms
[room-tab user s games "tournament" (tr [:lobby.tournament "Tournament"])]
Expand Down
16 changes: 14 additions & 2 deletions src/cljs/nr/main.cljs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
[nr.gameboard :refer [concede gameboard game-state mute-spectators stack-servers flip-runner-board set-replay-side]]
[nr.gamelobby :refer [filter-blocked-games game-lobby leave-game]]
[nr.help :refer [help]]
[nr.history :refer [navigate-to-current]]
[nr.history :refer [navigate-to-current navigate]]
[nr.navbar :refer [navbar]]
[nr.player-view :refer [player-view]]
[nr.stats :refer [stats]]
Expand Down Expand Up @@ -66,12 +66,24 @@
^{:key (get-in p [:user :_id])}
[player-view p game])]]))))]))

(defn- get-server-data
[tag]
(-> (.getElementById js/document "server-originated-data")
(.getAttribute (str "data-" tag))))

(defn pages []
(r/create-class
{:display-name "main-pages"

:component-did-mount
(fn [] (navigate-to-current))
(fn []
(let [ver (get-server-data "version")
rid (get-server-data "replay-id")]
(swap! app-state assoc :app-version ver)
(swap! app-state assoc :replay-id rid)
(if rid
(navigate "/play")
(navigate-to-current))))

:reagent-render
(fn []
Expand Down
57 changes: 35 additions & 22 deletions src/cljs/nr/stats.cljs
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,11 @@
(def state (r/atom {:games nil}))

(defn- fetch-game-history []
(go (let [{:keys [status json]} (<! (GET "/profile/history"))
games (mapv #(assoc % :start-date (js/Date. (:start-date %))
:end-date (js/Date. (:end-date %))) json)]
(go (let [{:keys [status json]} (<! (GET "/profile/history"))]
(when (= 200 status)
(swap! state assoc :games games)))))
(let [games (mapv #(assoc % :start-date (js/Date. (:start-date %))
:end-date (js/Date. (:end-date %))) json)]
(swap! state assoc :games games))))))

(defn update-deck-stats
"Update the local app-state with a new version of deck stats"
Expand All @@ -43,28 +43,37 @@
(swap! state assoc :view-game
(assoc (:view-game @state) :replay-shared true))))))

(defn- replay-link [game]
(str (.-origin (.-location js/window)) "/replay/" (:gameid game)))

(defn launch-replay [game] (set! (.-location js/window) (replay-link game)))

(defn game-details [state]
(let [game (:view-game @state)]
[:div.games.panel
[:p.return-button [:button {:on-click #(swap! state dissoc :view-game)} (tr [:stats.view-games "Return to stats screen"])]]
[:h4 (:title game) (when (:replay-shared game) "")]
[:div
[:div (str (tr [:stats.lobby "Lobby"]) ": " (capitalize (tr-lobby (:room game))))]
[:div (str (tr [:stats.format "Format"]) ": " (capitalize (tr-format (:format game))))]
[:div (str (tr [:stats.winner "Winner"]) ": " (capitalize (tr-side (:winner game))))]
[:div (str (tr [:stats.win-method "Win method"]) ": " (:reason game))]
[:div (str (tr [:stats.started "Started"]) ": " (:start-date game))]
[:div (str (tr [:stats.ended "Ended"]) ": " (:end-date game))]
[:div.game-details-table
[:div (str (tr [:stats.lobby "Lobby"]) ": " (capitalize (tr-lobby (:room game))))]
[:div (str (tr [:stats.format "Format"]) ": " (capitalize (tr-format (:format game))))]
[:div (str (tr [:stats.winner "Winner"]) ": " (capitalize (tr-side (:winner game))))]
[:div (str (tr [:stats.win-method "Win method"]) ": " (:reason game))]
[:div (str (tr [:stats.started "Started"]) ": " (:start-date game))]
[:div (str (tr [:stats.ended "Ended"]) ": " (:end-date game))]]
(when (:stats game)
[build-game-stats (get-in game [:stats :corp]) (get-in game [:stats :runner])])
[:p [:button {:on-click #(swap! state dissoc :view-game)} (tr [:stats.view-games "View games"])]
[:p
(when (and (:replay game)
(not (:replay-shared game)))
[:button {:on-click #(share-replay state (:gameid game))} (tr [:stats.share "Share replay"])])
(if (:replay game)
[:a.button {:href (str "/profile/history/full/" (:gameid game)) :download (str (:title game) ".json")} (tr [:stats.download "Download replay"])]
[:span
[:button {:on-click #(launch-replay game)} (tr [:stats.launch "Launch Replay"])]
[:a.button {:href (str "/profile/history/full/" (:gameid game)) :download (str (:title game) ".json")} (tr [:stats.download "Download replay"])]]
(tr [:stats.unavailable "Replay unavailable"]))]
(when (:replay-shared game)
[:p [:input.share-link {:type "text" :read-only true :value (str (.-origin (.-location js/window)) "/play?" (:gameid game))}]])]]))
[:p [:input.share-link {:type "text" :read-only true :value (replay-link game)}]])]]))

(defn clear-user-stats []
(authenticated
Expand Down Expand Up @@ -150,7 +159,7 @@
(swap! state assoc :view-game (assoc game :log json))))))

(defn game-row
[state {:keys [title corp runner turn winner reason replay-shared] :as game} log-scroll-top]
[state {:keys [title corp runner turn winner reason replay-shared start-date] :as game} log-scroll-top]
(let [corp-id (first (filter #(= (:title %) (:identity corp)) @all-cards))
runner-id (first (filter #(= (:title %) (:identity runner)) @all-cards))]
[:div.gameline {:style {:min-height "auto"}}
Expand All @@ -159,10 +168,12 @@
(fetch-log state game)
(reset! log-scroll-top 0))}
(tr [:stats.view-log "View log"])]
[:h4
[:h4.log-title
{:title (when replay-shared "Replay shared")}
title " (" (tr [:stats.turn-count] turn) ")" (when replay-shared "")]

[:div.log-date (-> start-date js/Date. js/moment (.format "dddd MMM Do - HH:mm"))]

[:div
[:span.player
[avatar (:player corp) {:opts {:size 24}}]
Expand All @@ -185,21 +196,23 @@
:component-will-unmount #(store-scroll-top % list-scroll-top)
:reagent-render
(fn [state list-scroll-top log-scroll-top]
(let [games (reverse (:games @state))]
(let [rev-games (reverse (:games @state))
games (if (:filter-replays @state) (filter #(:replay-shared %) rev-games) rev-games)
cnt (count games)]
[:div.game-list
[:div.controls
[:button {:on-click #(swap! state update :filter-replays not)}
[:button {:on-click #(swap! state update :filter-replays not)}
(if (:filter-replays @state)
(tr [:stats.all-games "Show all games"])
(tr [:stats.shared-games "Only show shared"]))]]
(tr [:stats.shared-games "Only show shared"]))]
[:span.log-count (str (tr [:stats.log-count] cnt) (when (:filter-replays @state)
(str " " (tr [:stats.filtered "(filtered)"]))))]]
(if (empty? games)
[:h4 (tr [:stats.no-games "No games"])]
(doall
(for [game games]
(when (or (not (:filter-replays @state))
(:replay-shared game))
^{:key (:gameid game)}
[game-row state game log-scroll-top]))))]))}))
^{:key (:gameid game)}
[game-row state game log-scroll-top])))]))}))

(defn right-panel [state list-scroll-top log-scroll-top]
(if (:view-game @state)
Expand Down
Loading

0 comments on commit 80620a6

Please sign in to comment.