0674e354 |
(ns dmr.views
|
8a16c975 |
(:require
[re-frame.core :as re-frame]
|
2771a4c3 |
[clojure.string :as string]
|
2fdc7f54 |
[goog.string :as gstr]
|
4d0e5d3f |
[dmr.config :as config]
|
0674e354 |
[dmr.styles :as styles]
[dmr.subs :as subs]
[dmr.events :as events]))
|
8a16c975 |
|
2771a4c3 |
; TODO: move this functionto a separate 'utils' module
(defn ordinal [n]
(let [final-digit (mod n 10)
suffix (case final-digit
0 "th"
1 "st"
2 "nd"
3 "rd"
"th")]
(str n suffix)))
|
c9e74063 |
(defn- join-lines [coll]
|
3f8355d8 |
(string/join \newline coll))
|
2771a4c3 |
|
c9e74063 |
(defn- comma-separated [coll]
|
250fac3a |
(string/join ", " coll))
|
c9e74063 |
(defn- parenthesize [s]
|
250fac3a |
(when s (str "(" s ")")))
|
c9e74063 |
(defn- spaced [& elements]
|
00d462f5 |
(string/join " " (filter identity elements)))
|
80aaf0f7 |
(defn- with-default [default x]
(if (nil? x) default x))
(defn- third [coll] (nth coll 2))
|
b83e55a8 |
(defn- zip [& colls] (apply map vector colls))
(defn- enumerated [& colls] (apply zip (range) colls))
|
a912156b |
(defn prompt []
(let [cmdline (re-frame/subscribe [::subs/cmdline])
suggestions (re-frame/subscribe [::subs/cmdline-suggestions])
update-cmdline #(re-frame/dispatch-sync
[::events/update-cmdline (-> % .-target .-value)])
key-pressed #(re-frame/dispatch-sync [::events/prompt-keypress %])
submit-cmd #(do (.preventDefault %)
(re-frame/dispatch [::events/submit-cmd (-> % .-target)]))]
[:form {:class (styles/prompt-style)
:on-submit submit-cmd}
[:input {:value @cmdline
:on-change update-cmdline
:on-keyDown key-pressed
:type "text"}]
[:ul.suggestions
(let [{:keys [options selected]} @suggestions]
(for [[idx x] (enumerated options)]
^{:key x} [:li
|
6faf0dc6 |
{:class (when (= idx selected) :selected)
|
d0b79894 |
:on-mouse-enter #(re-frame/dispatch [::events/hover-suggestion idx])
:on-click #(do (.preventDefault %)
(re-frame/dispatch [::events/click-suggestion
(-> % .-target .-parentElement .-parentElement)
x]))}
|
a912156b |
x]))]]))
(defn title []
(let [name (re-frame/subscribe [::subs/name])]
[:h1 "Welcome to " @name]))
|
80aaf0f7 |
(defn- property-list [& props]
|
2fdc7f54 |
(into
[:ul {:class (styles/property-list)}]
|
c33e1bff |
(for [[index [prop v]] (->> props (partition 2) (filter second) enumerated)]
^{:key index} [:li [:span prop] [:span v]])))
|
2fdc7f54 |
|
80aaf0f7 |
(defn- property-table [items & props]
(let [pprops (partition 3 props)
headings (map first pprops)
attrs (map second pprops)
getters (map third pprops)]
[:table {:class (styles/property-table)}
|
f4838048 |
[:thead
|
e2b160d0 |
[:tr
(for [[index attr heading] (enumerated attrs headings)]
^{:key index}
[:th attr heading])]]
|
80aaf0f7 |
[:tbody
|
f4838048 |
(for [[row-index item] (enumerated items)]
^{:key row-index}
[:tr
(for [[col-index attr f] (enumerated attrs getters)]
^{:key col-index}
[:td attr (->> item f (with-default "-"))])])]]))
|
80aaf0f7 |
(defn- license-notice [x]
|
557e889d |
(let [l (x :license-notice)]
(when l
[:section.license-notice
(spaced (x :name) "is" (l :content-kind) "subject to the" (l :license-name))])))
|
0ab5b75f |
(defn- listing-license-notice [listing]
[license-notice
{:name "This listing"
:license-notice (-> listing first :license-notice)}])
|
c9e74063 |
(defn- description [x]
|
edb01dde |
(some->> x :desc (map #(vector :p %)) (into [:section.description])))
|
c9e74063 |
(defn- heading [x]
|
bd1831f6 |
[:h1 (get x :name (str x))])
|
edb01dde |
|
3b4ee4ef |
(defn- pounds [w]
(if (= w 1)
(spaced w "lb")
(spaced w "lbs")))
(defn- cost [x]
(when (x :cost)
(spaced (-> x :cost :quantity) (-> x :cost :unit))))
(defn- speed [x]
(when (x :speed)
(spaced (-> x :speed :quantity) (-> x :speed :unit))))
|
9a3f4b6d |
(defn- weight
[x & {:keys [no-quantity unit] :or {no-quantity false unit pounds}}]
(let [unit (if (string? unit) #(spaced % unit) unit)]
(when (x :weight)
(spaced
(-> x :weight unit)
(when (not no-quantity)
(some->> x :quantity (str "for ") parenthesize))))))
|
edb01dde |
|
c9e74063 |
(defn- capacity [x]
(some-> x :capacity))
|
8d09a4ec |
(defn- short-spell-description [spell]
(spaced
(-> spell :level ordinal)
"level"
(-> spell :school :name string/lower-case)
"spell"))
|
99df709b |
(defn license-panel [license]
[:article {:class (styles/license-panel)}
[:h1 (license :license-name)]
[:p (license :preamble)]
|
c9e74063 |
[:ol.clauses
|
6268c11d |
(for [[index clause] (enumerated (license :clauses))]
^{:key index}
[:li clause])]
[:div.copyright-notice
|
0541dee0 |
(for [[index text] (enumerated (license :copyright-notice))]
|
6268c11d |
^{:key index} [:p text])]])
|
99df709b |
|
3f8355d8 |
(defn spell-panel [spell]
|
2771a4c3 |
[:article {:class (styles/spell-panel)}
|
81aad3db |
[heading spell]
|
8d09a4ec |
[:div [:span.kind (short-spell-description spell)]]
|
81aad3db |
[property-list
|
2771a4c3 |
; Refactor these so that we get the "name" from the keyword by replacing
; '-' and '_' and capitalizing
|
00d462f5 |
:Damage
(let [level (-> spell :level str keyword)
damage (some-> spell :damage :damage_at_slot_level level)
damage-type (some-> spell :damage :damage_type :name string/lower-case)]
(when damage (str damage ", " damage-type)))
"Casting Time"
(-> spell :casting_time)
:Range
(some-> spell :range string/lower-case (string/replace " feet" "'"))
:Components
(spaced
(comma-separated (spell :components))
(when (spell :material) (parenthesize (spell :material))))
:Duration
(spaced
(-> spell :duration string/lower-case)
|
81aad3db |
(when (spell :concentration) "(requires concentration)"))]
[description spell]
|
00d462f5 |
(let [at-higher-levels (some-> spell :higher_level join-lines)]
(when at-higher-levels
[:section.higher-level
[:span "At higher levels"]
|
f3f57785 |
[:span at-higher-levels]]))
|
81aad3db |
[license-notice spell]])
|
a9e41bf1 |
|
2fdc7f54 |
(defn equipment-panel [item]
[:article {:class (styles/equipment-panel)}
|
81aad3db |
[heading item]
|
3b4ee4ef |
[:span.kind
(spaced
(-> item :equipment_category :name)
(cond
(item :gear_category) (-> item :gear_category :name string/lower-case parenthesize)
(item :vehicle_category) (-> item :vehicle_category string/lower-case parenthesize)))]
|
81aad3db |
[property-list
|
3b4ee4ef |
:Speed
(speed item)
|
c9e74063 |
"Carrying Capacity"
(capacity item)
|
3b4ee4ef |
:Weight
|
edb01dde |
(weight item)
|
3b4ee4ef |
:Cost
(spaced
(cost item)
|
81aad3db |
(some->> item :quantity (str "for ") parenthesize))]
[description item]
[license-notice item]])
|
2fdc7f54 |
(defn weapon-panel [weapon]
(let [weapon-kind (-> weapon :weapon_range string/lower-case)
weapon-category (-> weapon :weapon_category string/lower-case)]
[:article {:class (styles/weapon-panel)}
|
81aad3db |
[heading weapon]
|
edb01dde |
[:span.kind (spaced weapon-category weapon-kind "weapon")]
|
81aad3db |
[property-list
|
2fdc7f54 |
; 'two-handed-damage' is defined for *single-handed* weapons with the "versatile"
; property. Actual two-handed weapons have 'damage' only.
:Damage
(let [damage-type (-> weapon :damage :damage_type :name string/lower-case)
damage (-> weapon :damage :damage_dice)
two-handed-damage (some-> weapon :two_handed_damage :damage_dice)]
(if two-handed-damage
|
3b4ee4ef |
(spaced damage (parenthesize two-handed-damage) damage-type)
(spaced damage damage-type)))
|
2fdc7f54 |
:Range
(when (= weapon-kind "ranged")
(gstr/format "%d' (%d')" (-> weapon :range :normal) (-> weapon :range :long)))
:Properties
|
edb01dde |
(some->> weapon :properties (map :name) (map string/lower-case) comma-separated)
|
2fdc7f54 |
:Weight
|
edb01dde |
(weight weapon)
|
2fdc7f54 |
:Cost
|
81aad3db |
(cost weapon)]
[license-notice weapon]]))
|
2fdc7f54 |
|
bd1831f6 |
(defn spell-list-panel [spells]
[:article {:class (styles/spell-list-panel)}
|
81aad3db |
[heading "Spells"]
|
bd1831f6 |
[:ul
|
13f1ec66 |
(for [[index spell] (enumerated (sort-by :name spells))]
^{:key index}
[:li
[:span (spell :name)]
|
0ab5b75f |
[:span (-> spell short-spell-description parenthesize)]])]
[listing-license-notice spells]])
|
0179abce |
|
80aaf0f7 |
(defn equipment-list-panel [items]
[:article {:class (styles/item-list-panel)}
|
81aad3db |
[heading "Items"]
[property-table items
|
80aaf0f7 |
:Name
{}
:name
:Cost
{:align :right}
cost
:Weight
{:align :right}
|
0ab5b75f |
#(weight % :unit "lb" :no-quantity true)]
[listing-license-notice items]])
|
0179abce |
|
a9e41bf1 |
(defn error-message
|
731380ac |
[{e :err}]
|
57bd4a3b |
[:div {:class (styles/error-message)} e])
|
a9e41bf1 |
|
c9e74063 |
(defn input [x]
[:div.input x])
|
a9e41bf1 |
|
99df709b |
(def is-license? :license-name)
|
731380ac |
(def is-spell? #(some-> % :url (string/starts-with? "/api/spells")))
|
406c6500 |
(def is-spell-list? #(some-> % first is-spell?))
|
731380ac |
(def is-equipment? #(some-> % :url (string/starts-with? "/api/equipment")))
|
80aaf0f7 |
(def is-equipment-list? #(some-> % first is-equipment?))
|
731380ac |
(def is-weapon? #(and (is-equipment? %) (-> % :equipment_category :index (= "weapon"))))
(def is-error? #(contains? % :err))
; TODO: refactor this to avoid repetition of 'x'
|
a9e41bf1 |
(defn output [x]
[:div.output
(cond
|
81aad3db |
(is-license? x) [license-panel x]
(is-equipment-list? x) [equipment-list-panel x]
(is-spell-list? x) [spell-list-panel x]
|
731380ac |
(is-spell? x) (spell-panel x)
|
34427c4a |
(is-equipment? x) (cond
|
81aad3db |
(is-weapon? x) [weapon-panel x]
:else [equipment-panel x])
(is-error? x) [error-message x])])
|
a9e41bf1 |
|
3f8355d8 |
(defn history []
(let [history (re-frame/subscribe [::subs/cmd-history])]
|
c9e74063 |
[:ul {:class (styles/history-style)}
|
c4d9230a |
(for [[index item] (enumerated @history)]
|
81aad3db |
^{:key index} [:li [input (item :input)] [output (item :output)]])]))
|
2771a4c3 |
|
13f07777 |
(defn- link-to [href & content]
[:a {:href href} content])
|
384aac16 |
(def agpl-link
|
13f07777 |
(link-to
"https://www.gnu.org/licenses/agpl-3.0.html"
"GNU Affero Public License"))
|
384aac16 |
|
0584fca2 |
(def uberspace-link
(link-to
"https://uberspace.de/"
"Uberspace"))
|
4d0e5d3f |
(defn footer []
[:footer {:class (styles/footer)}
|
384aac16 |
[:span.license agpl-link]
|
0584fca2 |
[:span.version (spaced "version:" config/version)]
[:span.host "Hosted by " uberspace-link]])
|
4d0e5d3f |
|
64347ccd |
(defn main-panel []
|
c0e61749 |
[:div {:class (styles/screen)}
|
00f7b042 |
[:div {:class (styles/main-panel)}
|
4d0e5d3f |
[title] [history] [prompt] [footer]]])
|