(ns dmr.views (:require [re-frame.core :as re-frame] [clojure.string :as string] [goog.string :as gstr] [dmr.styles :as styles] [dmr.subs :as subs] [dmr.events :as events])) (defn prompt [] (let [cmdline (re-frame/subscribe [::subs/cmdline]) update-cmdline #(re-frame/dispatch [::events/update-cmdline (-> % .-target .-value)]) submit-cmd #((.preventDefault %) (re-frame/dispatch [::events/submit-cmd]))] [:form {:class (styles/prompt-style) :on-submit submit-cmd} [:input {:value @cmdline :on-change update-cmdline :type "text"}]])) (defn title [] (let [name (re-frame/subscribe [::subs/name])] [:h1 "Welcome to " @name])) ; 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))) (defn- join-lines [coll] (string/join \newline coll)) (defn- comma-separated [coll] (string/join ", " coll)) (defn- parenthesize [s] (when s (str "(" s ")"))) (defn- property-list [& props] (into [:ul {:class (styles/property-list)}] (->> props (partition 2) (filter second) (map #(vector :li [:span (-> % first)] [:span (-> % second)]))))) (defn spell-panel [spell] [:article {:class (styles/spell-panel)} [:h1 (:name spell)] [:div [:span.level (-> spell :level ordinal) " level"] [:span.school "school of " (-> spell (get-in [:school :name]))]] [:ul {:class (styles/property-list)} ; Refactor these so that we get the "name" from the keyword by replacing ; '-' and '_' and capitalizing [:li [:span "Casting Time"] [:span (-> spell :casting_time)]] [:li [:span "Range"] [:span (-> spell :range)]] [:li [:span "Components"] [:span (comma-separated (-> spell :components))] [:span (parenthesize (-> spell :material))]] [:li [:span "Duration"] [:span (-> spell :duration)]]] [:section.description (-> spell :desc join-lines)] [:section.higher-level [:span "At higher levels"] [:span (-> spell :higher_level join-lines)]]]) (defn equipment-panel [item] [:article {:class (styles/equipment-panel)} [:h1 (item :name)]]) (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)} [:h1 (weapon :name)] [:span.kind (string/join " " [weapon-category weapon-kind "weapon"])] (property-list ; '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 (str damage " " (parenthesize two-handed-damage) ", " damage-type) (str damage ", " damage-type))) :Range (when (= weapon-kind "ranged") (gstr/format "%d' (%d')" (-> weapon :range :normal) (-> weapon :range :long))) :Properties (comma-separated (->> weapon :properties (map :name) (map string/lower-case))) :Weight (spaced (weapon :weight) "lbs") :Cost (spaced (-> weapon :cost :quantity) (-> weapon :cost :unit)))])) (defn error-message [e] [:div {:class (styles/error-message)} e]) (defn input [x] [:div.input x]) ; TODO: refactor this using the 'match' macro (defn output [x] [:div.output (cond (contains? x :spell) (spell-panel (x :spell)) (contains? x :equipment) (weapon-panel (x :equipment)) (contains? x :err) (error-message (x :err)))]) (defn history [] (let [history (re-frame/subscribe [::subs/cmd-history])] (into [:ul {:class (styles/history-style)}] (map #(vector :li (-> % :input input) (-> % :output output)) @history)))) (defn main-panel [] [:div {:class (styles/screen)} [:div {:class (styles/main-panel)} (title) (history) (prompt)]])