From a669221ad76c318f36126188147ce7a9cb1814a5 Mon Sep 17 00:00:00 2001 From: gered Date: Sun, 31 Jul 2016 15:58:53 -0400 Subject: [PATCH] add initial lists UI with list/add/delete and some update support currently doesn't show cards contained in any lists nor allow adding or removing of cards to/from any lists --- resources/public/css/app.css | 18 +++ src/mtgcoll/client/auth.cljs | 5 + src/mtgcoll/client/core.cljs | 6 +- src/mtgcoll/client/page.cljs | 1 + src/mtgcoll/client/routes/lists.cljs | 162 +++++++++++++++++++++++++++ src/mtgcoll/client/utils.cljs | 5 + 6 files changed, 196 insertions(+), 1 deletion(-) create mode 100644 src/mtgcoll/client/routes/lists.cljs diff --git a/resources/public/css/app.css b/resources/public/css/app.css index 81601f1..a5602b2 100644 --- a/resources/public/css/app.css +++ b/resources/public/css/app.css @@ -25,6 +25,24 @@ html, body { margin: 20px 0; } +.context +{ + position: relative; +} + +.absolute { + position: absolute; +} + +.absolute.top-right { + top: 0; + right: 0; +} + +.large-font { + font-size: 20px; +} + div.popover.card-image { max-width: none; } diff --git a/src/mtgcoll/client/auth.cljs b/src/mtgcoll/client/auth.cljs index b32af4f..079eabd 100644 --- a/src/mtgcoll/client/auth.cljs +++ b/src/mtgcoll/client/auth.cljs @@ -19,6 +19,11 @@ [] (not (nil? @user-profile))) +(defn can-modify-data? + [] + (or (authenticated?) + (not (auth-required?)))) + (defn get-username [] (:username @user-profile)) diff --git a/src/mtgcoll/client/core.cljs b/src/mtgcoll/client/core.cljs index f85d612..9a28123 100644 --- a/src/mtgcoll/client/core.cljs +++ b/src/mtgcoll/client/core.cljs @@ -8,8 +8,10 @@ [mtgcoll.client.page :as page] [mtgcoll.client.routes.cards :as cards] [mtgcoll.client.routes.collection :as collection] + [mtgcoll.client.routes.lists :as lists] [mtgcoll.client.routes.sets :as sets] - [mtgcoll.client.routes.stats :as stats])) + [mtgcoll.client.routes.stats :as stats] + [mtgcoll.client.utils :refer [parse-int]])) (defroute "/" [] (page/page [collection/owned-cards-list])) (defroute "/owned" [] (page/page [collection/owned-cards-list])) @@ -17,6 +19,8 @@ (defroute "/sets" [] (page/page [sets/sets-list])) (defroute "/set/:code" [code] (page/page [sets/set-details code])) (defroute "/card/:id" [id] (page/page [cards/card-details id 0])) +(defroute "/lists" [] (page/page [lists/lists-list])) +(defroute "/list/:id" [id] (page/page [lists/list-details (parse-int id)])) (defroute "/stats" [] (page/page [stats/stats-page])) (defroute "*" [] (page/barebones-page [:div "not found"])) diff --git a/src/mtgcoll/client/page.cljs b/src/mtgcoll/client/page.cljs index dbbda3b..7ad6558 100644 --- a/src/mtgcoll/client/page.cljs +++ b/src/mtgcoll/client/page.cljs @@ -39,6 +39,7 @@ [bs/Nav [bs/NavItem {:href "#/owned" :active (= :owned active-breadcrumb)} "Owned"] [bs/NavItem {:href "#/all" :active (= :all active-breadcrumb)} "All"] + [bs/NavItem {:href "#/lists" :active (= :lists active-breadcrumb)} "Lists"] [bs/NavItem {:href "#/sets" :active (= :sets active-breadcrumb)} "Sets"] [bs/NavItem {:href "#/stats" :active (= :stats active-breadcrumb)} "Statistics"]] (if (auth/auth-required?) diff --git a/src/mtgcoll/client/routes/lists.cljs b/src/mtgcoll/client/routes/lists.cljs new file mode 100644 index 0000000..d390c0c --- /dev/null +++ b/src/mtgcoll/client/routes/lists.cljs @@ -0,0 +1,162 @@ +(ns mtgcoll.client.routes.lists + (:require + [clojure.string :as string] + [reagent.core :as r] + [views.reagent.client.component :as vc :refer [view-cursor] :refer-macros [defvc]] + [webtools.reagent.bootstrap :as bs] + [webtools.cljs.ajax :as ajax] + [webtools.cljs.utils :refer [->url redirect!]] + [mtgcoll.client.auth :as auth] + [mtgcoll.client.page :refer [set-active-breadcrumb! set-error!]] + [mtgcoll.client.utils :refer [get-field-value]] + [mtgcoll.client.components.utils :refer [click-to-edit-textarea markdown confirm-modal]])) + +(defn create-list-form + [visibility-atom] + (let [values (r/atom nil) + error (r/atom nil) + on-close (fn [] + (reset! values nil) + (reset! error nil) + (reset! visibility-atom false)) + on-submit (fn [] + (reset! error nil) + (let [{:keys [name requires-qualities? public?]} @values] + (if (string/blank? name) + (reset! error "List name must be provided.") + (ajax/POST (->url "/lists/add") + :params {:name name :public? public? :requires-qualities? requires-qualities?} + :on-error #(reset! error "Could not create list. Make sure list name is unique.") + :on-success (fn [response] + (let [new-list-id (:id (clojure.walk/keywordize-keys response))] + (redirect! (str "#/list/" new-list-id)))))))) + on-key-up #(if (= 13 (.-keyCode %)) + (on-submit))] + (fn [] + [bs/Modal + {:show (boolean @visibility-atom) + :on-hide #(reset! visibility-atom false)} + [bs/Modal.Header [bs/Modal.Title "Create List"]] + [bs/Modal.Body + (if @error + [bs/Alert {:bsStyle "danger"} @error]) + [bs/Form {:horizontal true} + [bs/FormGroup + [bs/Col {:class "text-right" :sm 4} [bs/ControlLabel "List Name"]] + [bs/Col {:sm 6} + [bs/FormControl + {:type "text" + :default-value (or (:name @values) "") + :on-change #(swap! values assoc :name (get-field-value %)) + :on-key-up on-key-up}]]] + [bs/FormGroup + [bs/Col {:class "text-right" :sm 4} [bs/ControlLabel "Card Qualities"]] + [bs/Col {:sm 6} + [bs/Checkbox + {:on-change (fn [e] + (let [checked? (-> e .-target .-checked)] + (swap! values assoc :requires-qualities? + (if checked? true false))))}]]] + [bs/FormGroup + [bs/Col {:class "text-right" :sm 4} [bs/ControlLabel "Public"]] + [bs/Col {:sm 6} + [bs/Checkbox + {:on-change (fn [e] + (let [checked? (-> e .-target .-checked)] + (swap! values assoc :public? + (if checked? true false))))}]]]]] + [bs/Modal.Footer + [bs/Button {:bsStyle "primary" :on-click on-submit} "OK"] + [bs/Button {:on-click on-close} "Cancel"]]]))) + +(defvc lists-list + [] + (let [show-create-form? (r/atom false)] + (fn [] + (let [lists (view-cursor :lists-list (auth/get-username))] + (set-active-breadcrumb! :lists) + [:div.context + (if (auth/can-modify-data?) + [:div.absolute.top-right + [bs/Button {:on-click #(reset! show-create-form? true)} "Create List"]]) + [bs/PageHeader "Lists"] + [create-list-form show-create-form?] + (if (vc/loading? lists) + [:div "Loading ..."] + (if (empty? @lists) + [bs/Alert {:bsStyle "warning"} "No lists found."] + [bs/Table + {:bordered true :striped true :condensed true :hover true} + [:thead + [:tr + [:th "Name"] + [:th "Cards"]]] + [:tbody + (doall + (map + (fn [{:keys [id name is_public]}] + ^{:key id} + [:tr + (if (and (auth/authenticated?) (not is_public)) + {:class "warning"}) + [:td [:a {:href (->url "#/list/" id)} [:div name]]] + [:td "--"]]) + @lists))]]))])))) + +(defn on-update-list-notes! + [list-id notes] + (ajax/POST (->url "/lists/update-note") + :params {:list-id list-id :note notes} + :on-error #(set-error! "Server error while updating list notes."))) + +(defn change-list-visibility! + [list-id public?] + (ajax/POST (->url "/lists/update-visibility") + :params {:list-id list-id :public? public?} + :on-error #(set-error! "Server error while updating list public/private visibility."))) + +(defn delete-list! + [list-id] + (ajax/POST (->url "/lists/remove") + :params {:list-id list-id} + :on-error #(set-error! "Server error while deleting the list.") + :on-success #(redirect! "#/lists"))) + +(defvc list-details + [list-id] + (let [show-delete-confirm? (r/atom false)] + (fn [list-id] + (set-active-breadcrumb! :lists) + (let [list (view-cursor :list-info list-id (auth/get-username))] + (cond + (and (not (vc/loading? list)) + (nil? @list)) + [:div "List not found."] + + (vc/loading? list) + [:div "Loading ..."] + + :else + [:div.context + (if (auth/can-modify-data?) + [:div.absolute.top-right + (if-not (:is_public @list) [:span.large-font [bs/Label {:bsStyle "danger"} "Private"] " "]) + (if (:require_qualities @list) [:span.large-font [bs/Label {:bsStyle "primary"} "Card Qualities"] " "]) + [bs/DropdownButton {:title "Actions"} + [bs/MenuItem {:on-click #(js/alert "TODO: Copy to Owned")} "Copy to Owned"] + [bs/MenuItem {:on-click #(change-list-visibility! list-id (not (:is_public @list)))} (if (:is_public @list) "Make Private" "Make Public")] + [bs/MenuItem {:on-click #(reset! show-delete-confirm? true)} "Delete"]]]) + [bs/PageHeader (:name @list)] + (if (auth/can-modify-data?) + [click-to-edit-textarea + (:notes @list) + {:placeholder "List Notes" + :rows 10 + :on-update #(on-update-list-notes! list-id %) + :render markdown}] + [markdown (:notes @list)]) + [confirm-modal + show-delete-confirm? + {:title "Confirm Delete" + :body [:p "Are you sure you want to delete the " [:strong (:name @list)] " list? This cannot be undone."] + :on-yes #(delete-list! list-id)}]]))))) \ No newline at end of file diff --git a/src/mtgcoll/client/utils.cljs b/src/mtgcoll/client/utils.cljs index 7b9643d..4d02f63 100644 --- a/src/mtgcoll/client/utils.cljs +++ b/src/mtgcoll/client/utils.cljs @@ -63,3 +63,8 @@ [n & [number-only?]] (if n (str (if-not number-only? "$") (pprint/cl-format nil "~,2f" n)))) + +(defn parse-int + [s] + (if s + (js/parseInt (string/trim s) 10)))