From c1d39795a284d946a7ca063a565a11eba4209567 Mon Sep 17 00:00:00 2001 From: Daniel Leong Date: Sat, 17 Nov 2018 11:04:19 -0500 Subject: [PATCH] Introduce reg-id-sub that supports passing the sheet-id of interest Refs #69 This is not 100% bullet-proof, but it's relatively painless to use in place of an existing subscription, and it allows those subscriptions to *optionally* be used with an arbitrary sheet-id, for use on character cards for a campaign page. --- src/cljs/wish/sheets/dnd5e/campaign.cljs | 9 ++- src/cljs/wish/sheets/dnd5e/subs.cljs | 70 ++++++++++---------- src/cljs/wish/subs.cljs | 52 ++++++++------- src/cljs/wish/subs_util.cljs | 81 +++++++++++++++++++++++- 4 files changed, 152 insertions(+), 60 deletions(-) diff --git a/src/cljs/wish/sheets/dnd5e/campaign.cljs b/src/cljs/wish/sheets/dnd5e/campaign.cljs index bc7aabc1..ede90454 100644 --- a/src/cljs/wish/sheets/dnd5e/campaign.cljs +++ b/src/cljs/wish/sheets/dnd5e/campaign.cljs @@ -1,11 +1,14 @@ (ns ^{:author "Daniel Leong" :doc "Campaign-viewer for D&D 5e"} wish.sheets.dnd5e.campaign - (:require [wish.views.campaign.chars-carousel :refer [chars-carousel]])) + (:require [wish.views.campaign.chars-carousel :refer [chars-carousel]] + [wish.sheets.dnd5e.subs :as subs] + [wish.util :refer [evt]])) -(defn char-card [c] +(defn char-card [{:keys [id] :as c}] [:div.character - (:name c)]) + (:name c) + (str (str]] [wish.sheets.dnd5e.builder.data :refer [point-buy-max score-point-cost]] + [wish.subs-util :refer [reg-id-sub query-vec->preferred-id]] [wish.util :refer [map]] [wish.util.string :as wstr])) @@ -91,7 +92,7 @@ "Convenience for creating a sub that just gets a specific field from the :sheet key of the sheet-meta" [id getter] - (reg-sub + (reg-id-sub id :<- [:meta/sheet] (fn [sheet _] @@ -143,7 +144,7 @@ ; ======= class and level ================================== -(reg-sub +(reg-id-sub ::class->level :<- [:classes] (fn [classes _] @@ -153,19 +154,19 @@ {} classes))) -(reg-sub +(reg-id-sub ::class-level :<- [::class->level] (fn [classes [_ class-id]] (get classes class-id))) -(reg-sub +(reg-id-sub ::abilities-raw :<- [:meta/sheet] (fn [sheet] (:abilities sheet))) -(reg-sub +(reg-id-sub ::abilities-improvements :<- [:classes] :<- [:races] @@ -175,7 +176,7 @@ (map (comp :buffs :attrs)) (apply merge-with +)))) -(reg-sub +(reg-id-sub ::abilities-racial :<- [:race] (fn [race] @@ -188,7 +189,7 @@ ; TODO when we do handle equippable item buffs here, we need ; to make sure ::available-classes doesn't use it (only ability ; score improvements and racial bonuses ...) -(reg-sub +(reg-id-sub ::abilities-base :<- [::abilities-raw] :<- [::abilities-racial] @@ -199,7 +200,7 @@ race improvements))) -(reg-sub +(reg-id-sub ::abilities :<- [::abilities-base] :<- [:meta/sheet] @@ -208,7 +209,7 @@ base (:ability-tmp sheet)))) -(reg-sub +(reg-id-sub ::ability-modifiers :<- [::abilities] (fn [abilities] @@ -218,7 +219,7 @@ {} abilities))) -(reg-sub +(reg-id-sub ::ability-saves :<- [::ability-modifiers] :<- [::proficiency-bonus] @@ -238,7 +239,7 @@ {} modifiers))) -(reg-sub +(reg-id-sub ::ability-info :<- [::abilities] :<- [::abilities-base] @@ -262,7 +263,7 @@ {} abilities))) -(reg-sub +(reg-id-sub ::skill-info :<- [::ability-modifiers] :<- [::skill-expertise] @@ -294,7 +295,7 @@ {} data/skill-id->ability))) -(reg-sub +(reg-id-sub ::limited-uses :<- [:limited-uses] :<- [:total-level] @@ -318,7 +319,7 @@ (sort-by :name)))) -(reg-sub +(reg-id-sub ::limited-use :<- [::limited-uses] :<- [:limited-used] @@ -355,13 +356,17 @@ :max-slots (:total input)} ))) -(reg-sub +(reg-id-sub ::rolled-hp :<- [:meta/sheet] (fn [sheet [_ ?path]] (get-in sheet (concat [:hp-rolled] - ?path)))) + + ; NOTE: as an id-sub, we can also be called + ; where the var at this position is the sheet id + (when (coll? ?path) + ?path))))) (reg-sheet-sub ::temp-hp @@ -372,7 +377,7 @@ :temp-max-hp) (def ^:private compile-hp-buff (memoize ->callable)) -(reg-sub +(reg-id-sub ::max-hp-buffs :<- [:race] :<- [:classes] @@ -391,7 +396,7 @@ entity))) (apply +)))) -(reg-sub +(reg-id-sub ::max-hp-mode :<- [:meta/sheet] (fn [sheet] @@ -404,7 +409,7 @@ ; default to :average for new users :average))) -(reg-sub +(reg-id-sub ::max-hp-rolled :<- [::rolled-hp] :<- [::class->level] @@ -431,7 +436,7 @@ (apply +)))) -(reg-sub +(reg-id-sub ::max-hp-average :<- [:classes] (fn [classes] @@ -456,24 +461,23 @@ 0 ; start at 0 classes))) -; NOTE: we use reg-sub-raw here since it feels wrong to -(reg-sub +(reg-id-sub ::max-hp - (fn [] + (fn [query-vec] [; NOTE: this preferred-id query-vec)]) + :manual [::max-hp-rolled] + :average [::max-hp-average]) + + [::temp-max-hp] + [::abilities] + [:total-level] + [::max-hp-buffs] ]) - (fn [[base-max temp-max abilities total-level buffs]] + (fn [[base-max temp-max abilities total-level buffs :as in]] (+ base-max temp-max @@ -485,7 +489,7 @@ buffs))) -(reg-sub +(reg-id-sub ::hp :<- [::temp-hp] :<- [::max-hp] diff --git a/src/cljs/wish/subs.cljs b/src/cljs/wish/subs.cljs index 68fcba44..156b002a 100644 --- a/src/cljs/wish/subs.cljs +++ b/src/cljs/wish/subs.cljs @@ -6,7 +6,7 @@ [wish.db :as db] [wish.inventory :as inv] [wish.providers :as providers] - [wish.subs-util :refer [active-sheet-id]] + [wish.subs-util :refer [active-sheet-id reg-id-sub]] [wish.sheets :as sheets] [wish.sources.compiler :refer [apply-directives inflate]] [wish.sources.compiler.lists :as lists] @@ -87,7 +87,7 @@ ; each part of the sheet-meta, to avoid a small edit to HP, ; for example, causing all of the spell lists and features ; (which rely on classes, etc.) to be re-calculated - (reg-sub + (reg-id-sub id :<- [:sheet-meta] (fn [sheet _] @@ -122,8 +122,14 @@ (reg-sub :active-sheet-id :<- [:page] - (fn [page-vec _] - (active-sheet-id nil page-vec))) + (fn [page-vec [_ ?requested-id]] + ; NOTE: subscriptions created with wish.sub-util/reg-id-sub + ; can accept an extra param in their query vector that will + ; get passed down to us as ?requested-id, if provided; if + ; not, we just do the normal thing and extract the + ; active-sheet-id from the page vector + (or ?requested-id + (active-sheet-id nil page-vec)))) (reg-sub :sharable-sheet-id @@ -205,7 +211,7 @@ ; if a specific sheet-id is not provided, loads ; for the active sheet id -(reg-sub +(reg-id-sub :sheet-source :<- [:sheet-sources] :<- [:active-sheet-id] @@ -228,14 +234,14 @@ ; ======= Accessors for the active sheet =================== -(reg-sub +(reg-id-sub :sheet-meta :<- [:sheets] :<- [:active-sheet-id] (fn [[sheets id]] (get sheets id))) -(reg-sub +(reg-id-sub :classes :<- [:meta/kind] :<- [:sheet-source] @@ -258,7 +264,7 @@ ; A single class instance, or nil if none at all; if any ; class is marked primary, that class is returned. If none ; are so marked, then nil is returned -(reg-sub +(reg-id-sub :primary-class :<- [:classes] (fn [classes] @@ -267,13 +273,13 @@ first))) ; sum of levels from all classes -(reg-sub +(reg-id-sub :total-level :<- [:classes] (fn [classes _] (apply + (map :level classes)))) -(reg-sub +(reg-id-sub :races :<- [:sheet-meta] :<- [:sheet-source] @@ -300,7 +306,7 @@ :race)))))))) ; combines :attrs from all classes and races into a single map -(reg-sub +(reg-id-sub :all-attrs :<- [:classes] :<- [:races] @@ -470,7 +476,7 @@ sheet data-source])) -(reg-sub +(reg-id-sub :class-features :<- [:classes] get-features) @@ -497,12 +503,12 @@ (subscribe [:sheet-source])]) only-feature-options) -(reg-sub +(reg-id-sub :race-features :<- [:races] get-features) -(reg-sub +(reg-id-sub :inflated-race-features :<- [:race-features] :<- [:meta/options] @@ -511,7 +517,7 @@ :<- [:sheet-source] inflate-feature-options) -(reg-sub +(reg-id-sub :race-features-with-options :<- [:race-features] :<- [:meta/options] @@ -521,7 +527,7 @@ only-feature-options) ; semantic convenience for single-race systems -(reg-sub +(reg-id-sub :race :<- [:races] (fn [races _] @@ -537,7 +543,7 @@ :wish/context-type kind :wish/context entity))))) -(reg-sub +(reg-id-sub :limited-uses :<- [:classes] :<- [:races] @@ -553,7 +559,7 @@ vals (map (partial uses-with-context :item))))))) -(reg-sub +(reg-id-sub :limited-uses-map :<- [:limited-uses] (fn [limited-uses] @@ -589,7 +595,7 @@ ; will always be the (surprise) item-id. ; In addition, every item in :equipped will have the :wish/equipped? ; set to true -(reg-sub +(reg-id-sub :inventory-map :<- [:meta/kind] :<- [:meta/inventory] @@ -622,7 +628,7 @@ raw-inventory))) ; sorted list of inflated inventory items -(reg-sub +(reg-id-sub :inventory-sorted :<- [:inventory-map] (fn [inventory-map] @@ -631,7 +637,7 @@ (sort-by :name)))) ; sorted list of inflated + equipped inventory items -(reg-sub +(reg-id-sub :equipped-sorted :<- [:inventory-sorted] (fn [inventory-sorted] @@ -662,7 +668,6 @@ (fn [source [_ entity-kind]] (src/list-entities source entity-kind))) - (reg-sub :options-> :<- [:meta/options] @@ -674,6 +679,7 @@ instanced-value v)))) + ; ======= Save state ======================================= (reg-sub @@ -717,7 +723,7 @@ :campaign-members :<- [:meta/players] :<- [:sheets] - (fn [[char-sheet-ids sheets]] + (fn [[char-sheet-ids sheets] qv] (->> char-sheet-ids (map (fn [id] (or (assoc (get sheets id) diff --git a/src/cljs/wish/subs_util.cljs b/src/cljs/wish/subs_util.cljs index 43a69cf0..396aec5d 100644 --- a/src/cljs/wish/subs_util.cljs +++ b/src/cljs/wish/subs_util.cljs @@ -1,6 +1,8 @@ (ns ^{:author "Daniel Leong" :doc "subs-util"} - wish.subs-util) + wish.subs-util + (:require-macros [wish.util.log :as log :refer [log]]) + (:require [re-frame.core :refer [reg-sub subscribe]])) (defn active-sheet-id [db & [page-vec]] @@ -15,3 +17,80 @@ ; else, no sheet nil)))) +(defonce ^:private id-subs (atom #{})) +(defn- id-sub? [query-vec] + (let [query-id (first query-vec)] + (or (= :active-sheet-id query-id) + (contains? @id-subs query-id)))) + +(defn query-vec->preferred-id [query-vec] + ; TODO handle normal arguments? We can't + ; just use (last), since if no args are passed + ; that will just return the query-id + (second query-vec)) + +(defn inject-preferred-id [vec preferred-sheet-id] + (if preferred-sheet-id + (conj vec preferred-sheet-id) + vec)) + +(defn reg-id-sub + "This is a drop-in replacement for a subscription which + ultimately depends on [:active-sheet-id], which optionally + takes a single parameter in the query vector indicating + the sheet-id that should be used instead of the result of + [:active-sheet-id]" + [query-id & args] + (let [computation-fn (last args) + input-args (butlast args) + err-header (str "(reg-id-sub " query-id ")") + inputs-fn (case (count input-args) + ; error case + 0 nil + + ; single function; just pass through + 1 (let [base-fn (first input-args)] + (fn inp-fn [query-vec] + (let [base-subs (base-fn query-vec) + actual-sheet-id (query-vec->preferred-id query-vec)] + ; verify sanity + (if-not (every? vector? base-subs) + (do (log/err err-header "should return query vectors, not subscriptions") + ; use them, I guess + base-subs) + + (->> base-subs + (map #(inject-preferred-id % actual-sheet-id)) + (map subscribe)))))) + + ; single sugar pair + 2 (let [[marker vec] input-args] + (when-not (= :<- marker) + (log/err err-header "expected :<-, got:" marker)) + (fn inp-fn [query-vec] + (let [actual-sheet-id (query-vec->preferred-id query-vec)] + (subscribe (inject-preferred-id vec actual-sheet-id))))) + + ; multiple sugar pairs + (let [pairs (partition 2 input-args) + markers (map first pairs) + vecs (map last pairs) + any-id-subs? (some id-sub? vecs)] + (when-not (and (every? #{:<-} markers) (every? vector? vecs)) + (log/err err-header "expected pairs of :<- and vectors, got:" pairs)) + + (fn inp-fn [query-vec] + (let [actual-sheet-id (query-vec->preferred-id query-vec)] + (->> vecs + (map #(inject-preferred-id % actual-sheet-id)) + (map subscribe))))) + )] + (if-not inputs-fn + (log/warn err-header "must have input args") + + (do + (swap! id-subs conj query-id) + (reg-sub + query-id + inputs-fn + computation-fn)))))