new structure continue, move utils, move fx macro to re-frame utils n… (#14373)

* new structure continue, move utils, move fx macro to re-frame utils namespace
This commit is contained in:
flexsurfer 2022-11-16 09:09:25 +01:00 committed by GitHub
parent 433f9185c8
commit 31b6e076be
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
25 changed files with 282 additions and 138 deletions

View File

@ -2,12 +2,13 @@
status-im.utils.views/letsubs clojure.core/let
reagent.core/with-let clojure.core/let
status-im.utils.fx/defn clj-kondo.lint-as/def-catch-all
utils.re-frame/defn clj-kondo.lint-as/def-catch-all
quo.react/with-deps-check clojure.core/fn
quo.previews.preview/list-comp clojure.core/for
status-im.utils.styles/def clojure.core/def
status-im.utils.styles/defn clojure.core/defn
taoensso.tufte/defnp clojure.core/defn}
:linters {:invalid-arity {:skip-args [status-im.utils.fx/defn]}
:linters {:invalid-arity {:skip-args [status-im.utils.fx/defn utils.re-frame/defn]}
;;TODO remove number when this is fixed
;;https://github.com/borkdude/clj-kondo/issues/867
:unresolved-symbol {:exclude [PersistentPriorityMap.EMPTY number]}}}

View File

@ -2,10 +2,10 @@
CHANGES=$(git diff --no-ext-diff --diff-filter=d --cached --unified=0 --no-prefix --minimal --exit-code src/quo src/quo2 | grep '^+' || echo '')
INVALID_CHANGES=$(echo "$CHANGES" | grep -E '(re-frame/dispatch|rf/dispatch|re-frame/subscribe|rf/subscribe|rf/sub|<sub|>evt)')
INVALID_CHANGES=$(echo "$CHANGES" | grep -E '(re-frame/dispatch|rf/dispatch|re-frame/subscribe|rf/subscribe|rf/sub|<sub|>evt|status-im)')
if test -n "$INVALID_CHANGES"; then
echo "re-frame dispatch/subscribe is not allowed in quo/quo2 components"
echo "re-frame dispatch/subscribe and status-im are not allowed in quo/quo2 components"
echo ''
echo "$INVALID_CHANGES"
exit 1

View File

@ -6,7 +6,7 @@
[quo2.components.avatars.user-avatar :as user-avatar]
[quo2.components.markdown.text :as text]
[quo2.foundations.colors :as colors]
[utils :as utils]))
[utils.string :as utils]))
(def themes-landed {:pinned colors/primary-50-opa-5
:added colors/primary-50-opa-5

View File

@ -3,7 +3,8 @@
[react-native.core :as rn]
[quo2.components.tabs.tab :as tab]
[reagent.core :as reagent]
[utils :as utils]
[utils.number :as utils.number]
[utils.collection :as utils.collection]
[quo2.foundations.colors :as colors]
[quo2.components.notifications.notification-dot :refer [notification-dot]]
[react-native.masked-view :as masked-view]
@ -50,7 +51,7 @@
;; Truncate to avoid unnecessary rendering.
(if (> fade-percentage 0.99)
0.99
(utils/naive-round fade-percentage 2))))
(utils.number/naive-round fade-percentage 2))))
(defn scrollable-tabs
"Just like the component `tabs`, displays horizontally scrollable tabs with
@ -120,7 +121,7 @@
:scroll-on-press?
:size)
(when scroll-on-press?
{:initial-scroll-index (utils/first-index #(= @active-tab-id (:id %)) data)})
{:initial-scroll-index (utils.collection/first-index #(= @active-tab-id (:id %)) data)})
{:ref #(reset! flat-list-ref %)
:extra-data (str @active-tab-id)
:horizontal true

View File

@ -3,7 +3,7 @@
[status-im.chat.models :as chat.models]
[status-im.chat.models.pin-message :as models.pin-message]
[status-im.i18n.i18n :as i18n]
[status-im.utils.re-frame :as rf]
[utils.re-frame :as rf]
[status-im.ui2.screens.common.core :as common]
[status-im.constants :as constants]
[quo2.components.drawers.action-drawers :as drawer]))

View File

@ -8,7 +8,7 @@
[quo.platform :as platform]
[quo2.components.markdown.text :as text]
[status-im.ui2.screens.chat.components.message-home-item.style :as style]
[status-im.utils.re-frame :as rf]
[utils.re-frame :as rf]
[status-im.ui2.screens.chat.actions :as actions]))
(defn open-chat [chat-id]

View File

@ -1,6 +1,6 @@
(ns status-im.ui2.screens.chat.components.message-home-item.view
(:require [clojure.string :as string]
[status-im.utils.re-frame :as rf]
[utils.re-frame :as rf]
[status-im.utils.datetime :as time]
[quo2.foundations.typography :as typography]
[quo2.components.icon :as icons]

View File

@ -249,7 +249,6 @@
contacts (prepare-contacts contacts)
notifications (<sub [:activity.center/notifications-grouped-by-date])
{requests :received-requests new-info :has-unread?} (find-contact-requests notifications)]
(println "wtfff" items contacts)
[rn/view {:style {:flex 1}}
[discover-card/discover-card {:title (i18n/label :t/invite-friends-to-status)
:description (i18n/label :t/share-invite-link)}]
@ -378,4 +377,3 @@
[quo2.text/text {:size :heading-1 :weight :semi-bold} (i18n/label :t/messages)]
[plus-button]]
[chats-list]])])

View File

@ -34,7 +34,7 @@
[status-im.ui.screens.communities.icon :as communities.icon]
[status-im.ui2.screens.chat.components.reply :as components.reply]
[status-im.utils.config :as config]
[status-im.utils.re-frame :as rf]
[utils.re-frame :as rf]
[status-im.utils.security :as security]
[quo2.components.icon :as icons]
[status-im.utils.datetime :as time]

View File

@ -2,7 +2,7 @@
(:require
[reagent.core :as reagent]
[status-im.i18n.i18n :as i18n]
[status-im.utils.re-frame :as rf]
[utils.re-frame :as rf]
[quo2.components.markdown.text :as quo2.text]
[react-native.core :as rn]
[quo2.core :as quo2]

View File

@ -8,7 +8,7 @@
[quo2.foundations.colors :as colors]
[quo2.components.community.discover-card :as discover-card]
[quo2.components.navigation.top-nav :as topnav]
[status-im.utils.re-frame :as rf]
[utils.re-frame :as rf]
[status-im.i18n.i18n :as i18n]
[status-im.ui.components.list.views :as list]
[status-im.ui.components.react :as rn]

View File

@ -4,34 +4,6 @@
[taoensso.timbre :as log]
[status-im.utils.debounce :as debounce]))
(defn- parse-json
;; NOTE(dmitryn) Expects JSON response like:
;; {"error": "msg"} or {"result": true}
[s]
(try
(let [res (-> s
js/JSON.parse
(js->clj :keywordize-keys true))]
;; NOTE(dmitryn): AddPeer() may return {"error": ""}
;; assuming empty error is a success response
;; by transforming {"error": ""} to {:result true}
(if (and (:error res)
(= (:error res) ""))
{:result true}
res))
(catch :default ^js e
{:error (.-message e)})))
(defn response-handler [success-fn error-fn]
(fn handle-response
([response]
(let [{:keys [error result]} (parse-json response)]
(handle-response error result)))
([error result]
(if error
(error-fn error)
(success-fn result)))))
(defn- pretty-print-event [ctx]
(let [[first _] (get-coeffect ctx :event)]
first))

View File

@ -1,7 +0,0 @@
(ns status-im.utils.re-frame
(:require [re-frame.core :as re-frame]))
(def sub (comp deref re-frame/subscribe))
(def dispatch re-frame/dispatch)

View File

@ -6,15 +6,14 @@
[react-native.platform :as platform]
[re-frame.core :as re-frame]
[taoensso.timbre :as log]
[quo2.foundations.colors :as colors]
[status-im2.navigation.roots :as roots]
[status-im2.navigation.state :as state]
[status-im2.navigation.view :as views]
[utils.re-frame :as rf]
;; TODO (14/11/22 flexsurfer) move to status-im2 namespace
[status-im.multiaccounts.login.core :as login-core]
[status-im2.navigation.view :as views]
[status-im.utils.fx :as fx]
[quo2.foundations.colors :as colors]))
[status-im.multiaccounts.login.core :as login-core]))
;; REGISTER COMPONENT (LAZY)
(defn reg-comp [key]
@ -171,7 +170,7 @@
(reset! state/root-id @state/root-comp-id)
(navigation/set-root (get (roots/roots) new-root-id))))
(fx/defn set-multiaccount-root
(rf/defn set-multiaccount-root
{:events [::set-multiaccount-root]}
[{:keys [db]}]
(log/debug :set-multiaccounts-root)

View File

@ -1,5 +1,5 @@
(ns status-im2.navigation.events
(:require [status-im.utils.fx :as fx]
(:require [utils.re-frame :as rf]
[status-im2.setup.hot-reload :as hot-reload]))
(defn- all-screens-params [db view screen-params]
@ -10,39 +10,39 @@
(seq screen-params)
(assoc-in [:navigation/screen-params view] screen-params)))
(fx/defn navigate-to-cofx
(rf/defn navigate-to-cofx
[{:keys [db]} go-to-view-id screen-params]
{:db
(-> (assoc db :view-id go-to-view-id)
(all-screens-params go-to-view-id screen-params))
:navigate-to-fx go-to-view-id})
(fx/defn navigate-to
(rf/defn navigate-to
{:events [:navigate-to]}
[cofx go-to-view-id screen-params]
(navigate-to-cofx cofx go-to-view-id screen-params))
(fx/defn navigate-back
(rf/defn navigate-back
{:events [:navigate-back]}
[_]
{:navigate-back-fx nil})
(fx/defn pop-to-root-tab
(rf/defn pop-to-root-tab
{:events [:pop-to-root-tab]}
[_ tab]
{:pop-to-root-tab-fx tab})
(fx/defn set-stack-root
(rf/defn set-stack-root
{:events [:set-stack-root]}
[_ stack root]
{:set-stack-root-fx [stack root]})
(fx/defn change-tab
(rf/defn change-tab
{:events [:navigate-change-tab]}
[_ tab]
{:change-tab-fx tab})
(fx/defn navigate-replace
(rf/defn navigate-replace
{:events [:navigate-replace]}
[{:keys [db]} go-to-view-id screen-params]
(let [db (cond-> (assoc db :view-id go-to-view-id)
@ -51,49 +51,49 @@
{:db db
:navigate-replace-fx go-to-view-id}))
(fx/defn open-modal
(rf/defn open-modal
{:events [:open-modal]}
[{:keys [db]} comp screen-params]
{:db (-> (assoc db :view-id comp)
(all-screens-params comp screen-params))
:open-modal-fx comp})
(fx/defn init-root
(rf/defn init-root
{:events [:init-root]}
[_ root-id]
{:init-root-fx root-id})
(fx/defn init-root-with-component
(rf/defn init-root-with-component
{:events [:init-root-with-component]}
[_ root-id comp-id]
{:init-root-with-component-fx [root-id comp-id]})
(fx/defn change-tab-count
(rf/defn change-tab-count
{:events [:change-tab-count]}
[_ tab cnt]
{:change-tab-count-fx [tab cnt]})
(fx/defn hide-signing-sheet
(rf/defn hide-signing-sheet
{:events [:hide-signing-sheet]}
[_]
{:hide-signing-sheet nil})
(fx/defn hide-select-acc-sheet
(rf/defn hide-select-acc-sheet
{:events [:hide-select-acc-sheet]}
[_]
{:hide-select-acc-sheet nil})
(fx/defn hide-wallet-connect-sheet
(rf/defn hide-wallet-connect-sheet
{:events [:hide-wallet-connect-sheet]}
[_]
{:hide-wallet-connect-sheet nil})
(fx/defn hide-wallet-connect-success-sheet
(rf/defn hide-wallet-connect-success-sheet
{:events [:hide-wallet-connect-success-sheet]}
[_]
{:hide-wallet-connect-success-sheet nil})
(fx/defn hide-wallet-connect-app-management-sheet
(rf/defn hide-wallet-connect-app-management-sheet
{:events [:hide-wallet-connect-app-management-sheet]}
[{:keys [db]}]
{:db (-> db
@ -102,7 +102,7 @@
:hide-wallet-connect-app-management-sheet nil})
;; NAVIGATION 2
(fx/defn reload-new-ui
(rf/defn reload-new-ui
{:events [:reload-new-ui]}
[_]
(hot-reload/reload)
@ -123,7 +123,7 @@
:id id
:clock now})}))
(fx/defn navigate-to-nav2
(rf/defn navigate-to-nav2
{:events [:navigate-to-nav2]}
[{:keys [db now]} go-to-view-id id _ from-switcher?]
(let [view-id (:view-id db)
@ -135,7 +135,7 @@
;; TODO(parvesh) - new stacks created from other screens should be stacked on current stack, instead of creating new entry
(navigate-from-shell-stack go-to-view-id id db now)))))
(fx/defn change-root-status-bar-style
(rf/defn change-root-status-bar-style
{:events [:change-root-status-bar-style]}
[_ style]
{:change-root-status-bar-style-fx style})

View File

@ -18,7 +18,6 @@
[status-im2.setup.i18n-resources :as i18n-resources]
[status-im2.setup.config :as config]
[status-im2.setup.log :as log]
status-im.events
;; TODO (14/11/22 flexsurfer move to status-im2 namespace

View File

@ -1,10 +1,23 @@
(ns status-im2.setup.dev
(:require [react-native.platform :as platform]
[utils.re-frame :as rf]
[status-im.ethereum.json-rpc :as json-rpc]
["react-native" :refer (DevSettings LogBox)]))
(.ignoreAllLogs LogBox)
(defn setup []
(rf/set-mergeable-keys #{:filters/load-filters
:pairing/set-installation-metadata
:dispatch-n
:status-im.ens.core/verify-names
:shh/send-direct-message
:shh/remove-filter
:transport/confirm-messages-processed
:group-chats/extract-membership-signature
:utils/dispatch-later
::json-rpc/call})
(when (and js/goog.DEBUG platform/ios? DevSettings)
;;on Android this method doesn't work
(when-let [nm (.-_nativeModule DevSettings)]

View File

@ -4,10 +4,11 @@
[status-im2.setup.db :as db]
[status-im2.common.theme.core :as theme]
[quo2.theme :as quo2.theme]
[utils.re-frame :as rf]
;; TODO (14/11/22 flexsurfer move to status-im2 namespace
[status-im.multiaccounts.login.core :as multiaccounts.login]
[status-im.native-module.core :as status]
[status-im.utils.fx :as fx]
[status-im.utils.keychain.core :as keychain]
[status-im2.navigation.events :as navigation]))
@ -22,7 +23,7 @@
(theme/add-mode-change-listener #(re-frame/dispatch [:system-theme-mode-changed %]))
(quo2.theme/set-theme (if (theme/dark-mode?) :dark :light))))
(fx/defn initialize-views
(rf/defn initialize-views
{:events [:setup/initialize-view]}
[cofx]
(let [{{:multiaccounts/keys [multiaccounts]} :db} cofx]
@ -30,14 +31,14 @@
;; We specifically pass a bunch of fields instead of the whole multiaccount
;; as we want store some fields in multiaccount that are not here
(let [multiaccount (first (sort-by :timestamp > (vals multiaccounts)))]
(fx/merge cofx
(rf/merge cofx
(multiaccounts.login/open-login (select-keys
multiaccount
[:key-uid :name :public-key :identicon :images]))
(keychain/get-auth-method (:key-uid multiaccount))))
(navigation/init-root cofx :intro))))
(fx/defn initialize-multiaccounts
(rf/defn initialize-multiaccounts
{:events [:setup/initialize-multiaccounts]}
[{:keys [db] :as cofx} all-multiaccounts {:keys [logout?]}]
(let [multiaccounts (reduce (fn [acc {:keys [key-uid keycard-pairing]
@ -48,7 +49,7 @@
keycard-pairing))))
{}
all-multiaccounts)]
(fx/merge cofx
(rf/merge cofx
{:db (-> db
(assoc :multiaccounts/multiaccounts multiaccounts)
(assoc :multiaccounts/logout? logout?)
@ -57,7 +58,7 @@
[:get-opted-in-to-new-terms-of-service]
[:load-information-box-states]]})))
(fx/defn initialize-app-db
(rf/defn initialize-app-db
"Initialize db to initial state"
[{{:keys [keycard supported-biometric-auth goto-key-storage?]
:network/keys [type] :keycard/keys [banner-hidden]} :db}]
@ -69,10 +70,10 @@
:goto-key-storage? goto-key-storage?
:multiaccounts/loading true)})
(fx/defn start-app
(rf/defn start-app
{:events [:setup/app-started]}
[cofx]
(fx/merge cofx
(rf/merge cofx
{:setup/init-theme nil
:get-supported-biometric-auth nil
:network/listen-to-network-info nil

View File

@ -3,8 +3,7 @@
[clojure.string :as string]
[re-frame.core :as re-frame]
[status-im2.setup.config :as config]
;; TODO (14/11/22 flexsurfer move to status-im2 namespace
[status-im.utils.fx :as fx]))
[utils.re-frame :as rf]))
(def logs-queue (atom #queue[]))
(def max-log-entries 1000)
@ -32,7 +31,7 @@
(fn [level]
(setup level)))
(fx/defn set-log-level
(rf/defn set-log-level
[{:keys [db]} log-level]
(let [log-level (or log-level config/log-level)]
{:db (assoc-in db [:multiaccount :log-level] log-level)

View File

@ -1,50 +0,0 @@
(ns utils
(:require [goog.string.format]))
(defn naive-round
"Quickly and naively round number `n` up to `decimal-places`.
Example usage: use it to avoid re-renders caused by floating-point number
changes in Reagent atoms. Such numbers can be rounded up to a certain number
of `decimal-places` in order to avoid re-rendering due to tiny fractional
changes.
Don't use this function for arbitrary-precision arithmetic."
[n decimal-places]
(let [scale (Math/pow 10 decimal-places)]
(/ (Math/round (* n scale))
scale)))
(defn truncate-str-memo
"Given string and max threshold, trims the string to threshold length with `...`
appended to end or in the middle if length of the string exceeds max threshold,
returns the same string if threshold is not exceeded"
[s threshold & [middle?]]
(if (and s (< threshold (count s)))
(if middle?
(let [str-len (count s)
max-len (- threshold 3)
start-len (Math/ceil (/ max-len 2))
end-len (Math/floor (/ max-len 2))
start (subs s 0 start-len)
end (subs s (- str-len end-len) str-len)]
(str start "..." end))
(str (subs s 0 (- threshold 3)) "..."))
s))
(def truncate-str (memoize truncate-str-memo))
(defn first-index
"Returns first index in coll where predicate on coll element is truthy"
[pred coll]
(->> coll
(keep-indexed (fn [idx e]
(when (pred e)
idx)))
first))
(defn index-by
"Given a collection and a unique key function, returns a map that indexes the collection.
Similar to group-by except that the map values are single objects (depends on key uniqueness)."
[key coll]
(into {} (map #(vector (key %) %) coll)))

16
src/utils/collection.cljs Normal file
View File

@ -0,0 +1,16 @@
(ns utils.collection)
(defn first-index
"Returns first index in coll where predicate on coll element is truthy"
[pred coll]
(->> coll
(keep-indexed (fn [idx e]
(when (pred e)
idx)))
first))
(defn index-by
"Given a collection and a unique key function, returns a map that indexes the collection.
Similar to group-by except that the map values are single objects (depends on key uniqueness)."
[key coll]
(into {} (map #(vector (key %) %) coll)))

15
src/utils/number.cljs Normal file
View File

@ -0,0 +1,15 @@
(ns utils.number)
(defn naive-round
"Quickly and naively round number `n` up to `decimal-places`.
Example usage: use it to avoid re-renders caused by floating-point number
changes in Reagent atoms. Such numbers can be rounded up to a certain number
of `decimal-places` in order to avoid re-rendering due to tiny fractional
changes.
Don't use this function for arbitrary-precision arithmetic."
[n decimal-places]
(let [scale (Math/pow 10 decimal-places)]
(/ (Math/round (* n scale))
scale)))

84
src/utils/re_frame.clj Normal file
View File

@ -0,0 +1,84 @@
(ns utils.re-frame
(:refer-clojure :exclude [defn]))
(defn- register-events
[events interceptors name argsyms]
(mapv (fn [event]
`(utils.re-frame/register-handler-fx
~event
~interceptors
(fn [cofx# [_# ~@argsyms]] (~name cofx# ~@argsyms))))
events))
(defn- fully-qualified-name [sym]
(str *ns* "/" (name sym)))
(defmacro defn
"Defines an fx producing function
Takes the same arguments as the defn macro
Produces a 2 arity function:
- first arity takes the declared parameters and returns a function that takes cofx as
single argument, for use in composition of effects
- second arity takes cofx as first arguments and declared parameters as next arguments,
for use in repl or direct call
Notes:
- destructuring of cofx is possible
- supports docstring
- supports attr-map with optional :events key which needs to be a vector of
event keywords under which the function will be registered
- TODO: add suport for `prepost-map?` (don't forget to add it to arglist)
- TODO: add validation of macro parameters"
{:arglists '([name doc-string? attr-map? [params*] body])}
[name & fdecl]
(let [m (if (string? (first fdecl))
{:doc (first fdecl)}
{})
fdecl (if (string? (first fdecl))
(next fdecl)
fdecl)
m (if (map? (first fdecl))
(conj m (first fdecl))
m)
events (get m :events [])
interceptors (get m :interceptors [])
fdecl (if (map? (first fdecl))
(next fdecl)
fdecl)
[cofx & args] (first fdecl)
fdecl (next fdecl)
argsyms (take (count args) (repeatedly #(gensym "arg")))]
(if (and (sequential? events)
(every? keyword? events))
`(do
(clojure.core/defn ~(with-meta name m)
([~@argsyms] (fn [cofx#] (~(with-meta name m) cofx# ~@argsyms)))
([cofx# ~@args]
(when js/goog.DEBUG
(when (taoensso.timbre/level>=
:trace
(:level taoensso.timbre/*config*))
(println
(clojure.string/join
(concat
(repeat
(deref utils.re-frame/handler-nesting-level)
"│ ")
["├─"]))
~(str (clojure.core/name name) " " *ns*))))
(if (and (map? cofx#)
(not (nil? (:db cofx#))))
(let [res# (let [~cofx cofx#] ~@fdecl)]
(when-not (nil? res#)
(aset res#
"cljs$core$ILookup$_lookup$arity$2"
(fn [foo# k#]
(clojure.core/this-as
m#
(when (and (map? k#)
(contains? k# :db))
(throw (js/Error. (str "fx/defn's result is used as fx producing function in " ~(fully-qualified-name name)))))
(get m# k# nil)))))
res#)
(throw (js/Error. (str "fx/defn expects a map of cofx as first argument got " cofx# " in function " ~(fully-qualified-name name)))))))
~@(register-events events interceptors (with-meta name m) argsyms))
(throw (Exception. (str "fx/defn expects a vector of keyword as value for :events key in attr-map in function " name))))))

83
src/utils/re_frame.cljs Normal file
View File

@ -0,0 +1,83 @@
(ns utils.re-frame
(:require-macros utils.re-frame)
(:require [taoensso.timbre :as log]
[re-frame.core :as re-frame]
[re-frame.interceptor :as interceptor])
(:refer-clojure :exclude [merge reduce]))
(def handler-nesting-level (atom 0))
(def debug-handlers-names
"Interceptor which logs debug information to js/console for each event."
(interceptor/->interceptor
:id :debug-handlers-names
:before (fn debug-handlers-names-before
[context]
(when js/goog.DEBUG
(reset! handler-nesting-level 0))
(log/debug "Handling re-frame event: " (first (interceptor/get-coeffect context :event)))
context)))
(defn register-handler-fx
([name handler]
(register-handler-fx name nil handler))
([name interceptors handler]
(re-frame/reg-event-fx
name
[debug-handlers-names (re-frame/inject-cofx :now) interceptors]
handler)))
(defn- update-db [cofx fx]
(if-let [db (:db fx)]
(assoc cofx :db db)
cofx))
(def ^:private mergeable-keys (atom nil))
(defn set-mergeable-keys [val]
(reset! mergeable-keys val))
(defn- safe-merge [fx new-fx]
(if (:merging-fx-with-common-keys fx)
fx
(clojure.core/reduce (fn [merged-fx [k v]]
(if (= :db k)
(assoc merged-fx :db v)
(if (get merged-fx k)
(if (get @mergeable-keys k)
(update merged-fx k into v)
(do (log/error "Merging fx with common-key: " k v (get merged-fx k))
(reduced {:merging-fx-with-common-keys k})))
(assoc merged-fx k v))))
fx
new-fx)))
(defn merge
"Takes a map of co-effects and forms as argument.
The first optional form can be map of effects
The next forms are functions applying effects and returning a map of effects.
The fn ensures that updates to db are passed from function to function within the cofx :db key and
that only a :merging-fx-with-common-keys effect is returned if some functions are trying
to produce the same effects (excepted :db, :data-source/tx effects).
:data-source/tx and effects are handled specially and their results
(list of transactions) are compacted to one transactions list (for each effect). "
[{:keys [db] :as cofx} & args]
(when js/goog.DEBUG
(swap! handler-nesting-level inc))
(let [[first-arg & rest-args] args
initial-fxs? (map? first-arg)
fx-fns (if initial-fxs? rest-args args)
res
(clojure.core/reduce (fn [fxs fx-fn]
(let [updated-cofx (update-db cofx fxs)]
(if fx-fn
(safe-merge fxs (fx-fn updated-cofx))
fxs)))
(if initial-fxs? first-arg {:db db})
fx-fns)]
(swap! handler-nesting-level dec)
res))
(def sub (comp deref re-frame/subscribe))
(def dispatch re-frame/dispatch)

20
src/utils/string.cljs Normal file
View File

@ -0,0 +1,20 @@
(ns utils.string)
(defn truncate-str-memo
"Given string and max threshold, trims the string to threshold length with `...`
appended to end or in the middle if length of the string exceeds max threshold,
returns the same string if threshold is not exceeded"
[s threshold & [middle?]]
(if (and s (< threshold (count s)))
(if middle?
(let [str-len (count s)
max-len (- threshold 3)
start-len (Math/ceil (/ max-len 2))
end-len (Math/floor (/ max-len 2))
start (subs s 0 start-len)
end (subs s (- str-len end-len) str-len)]
(str start "..." end))
(str (subs s 0 (- threshold 3)) "..."))
s))
(def truncate-str (memoize truncate-str-memo))