From 31b6e076be21f9ff690ec7e0eafe4072f02e1ff6 Mon Sep 17 00:00:00 2001 From: flexsurfer Date: Wed, 16 Nov 2022 09:09:25 +0100 Subject: [PATCH] =?UTF-8?q?new=20structure=20continue,=20move=20utils,=20m?= =?UTF-8?q?ove=20fx=20macro=20to=20re-frame=20utils=20n=E2=80=A6=20(#14373?= =?UTF-8?q?)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * new structure continue, move utils, move fx macro to re-frame utils namespace --- .clj-kondo/config.edn | 3 +- scripts/lint-re-frame-in-quo-components.sh | 4 +- .../components/messages/system_message.cljs | 2 +- src/quo2/components/tabs/tabs.cljs | 7 +- src/status_im/ui2/screens/chat/actions.cljs | 2 +- .../chat/components/contact_item/view.cljs | 2 +- .../components/message_home_item/view.cljs | 2 +- src/status_im/ui2/screens/chat/home.cljs | 2 - .../ui2/screens/chat/messages/message.cljs | 2 +- .../ui2/screens/common/alert/view.cljs | 2 +- .../screens/communities/communities_home.cljs | 2 +- src/status_im/utils/handlers.cljs | 28 ------- src/status_im/utils/re_frame.cljs | 7 -- src/status_im2/navigation/core.cljs | 11 ++- src/status_im2/navigation/events.cljs | 40 ++++----- src/status_im2/setup/core.cljs | 1 - src/status_im2/setup/dev.cljs | 13 +++ src/status_im2/setup/events.cljs | 17 ++-- src/status_im2/setup/log.cljs | 5 +- src/utils.cljs | 50 ----------- src/utils/collection.cljs | 16 ++++ src/utils/number.cljs | 15 ++++ src/utils/re_frame.clj | 84 +++++++++++++++++++ src/utils/re_frame.cljs | 83 ++++++++++++++++++ src/utils/string.cljs | 20 +++++ 25 files changed, 282 insertions(+), 138 deletions(-) delete mode 100644 src/status_im/utils/re_frame.cljs delete mode 100644 src/utils.cljs create mode 100644 src/utils/collection.cljs create mode 100644 src/utils/number.cljs create mode 100644 src/utils/re_frame.clj create mode 100644 src/utils/re_frame.cljs create mode 100644 src/utils/string.cljs diff --git a/.clj-kondo/config.edn b/.clj-kondo/config.edn index cd6e553366..b0d3dee87b 100644 --- a/.clj-kondo/config.edn +++ b/.clj-kondo/config.edn @@ -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]}}} diff --git a/scripts/lint-re-frame-in-quo-components.sh b/scripts/lint-re-frame-in-quo-components.sh index bfafb077bb..e5e7301cfc 100755 --- a/scripts/lint-re-frame-in-quo-components.sh +++ b/scripts/lint-re-frame-in-quo-components.sh @@ -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|evt)') +INVALID_CHANGES=$(echo "$CHANGES" | grep -E '(re-frame/dispatch|rf/dispatch|re-frame/subscribe|rf/subscribe|rf/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 diff --git a/src/quo2/components/messages/system_message.cljs b/src/quo2/components/messages/system_message.cljs index bf58be89d7..82113b6144 100644 --- a/src/quo2/components/messages/system_message.cljs +++ b/src/quo2/components/messages/system_message.cljs @@ -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 diff --git a/src/quo2/components/tabs/tabs.cljs b/src/quo2/components/tabs/tabs.cljs index 0a5e545e6a..2729cacc6c 100644 --- a/src/quo2/components/tabs/tabs.cljs +++ b/src/quo2/components/tabs/tabs.cljs @@ -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 diff --git a/src/status_im/ui2/screens/chat/actions.cljs b/src/status_im/ui2/screens/chat/actions.cljs index fee997983b..640a881aa7 100644 --- a/src/status_im/ui2/screens/chat/actions.cljs +++ b/src/status_im/ui2/screens/chat/actions.cljs @@ -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])) diff --git a/src/status_im/ui2/screens/chat/components/contact_item/view.cljs b/src/status_im/ui2/screens/chat/components/contact_item/view.cljs index 9dbc7595d4..ef4e7ff832 100644 --- a/src/status_im/ui2/screens/chat/components/contact_item/view.cljs +++ b/src/status_im/ui2/screens/chat/components/contact_item/view.cljs @@ -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] diff --git a/src/status_im/ui2/screens/chat/components/message_home_item/view.cljs b/src/status_im/ui2/screens/chat/components/message_home_item/view.cljs index 636a1665e9..77715e6428 100644 --- a/src/status_im/ui2/screens/chat/components/message_home_item/view.cljs +++ b/src/status_im/ui2/screens/chat/components/message_home_item/view.cljs @@ -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] diff --git a/src/status_im/ui2/screens/chat/home.cljs b/src/status_im/ui2/screens/chat/home.cljs index a21eccf06a..582537c4f0 100644 --- a/src/status_im/ui2/screens/chat/home.cljs +++ b/src/status_im/ui2/screens/chat/home.cljs @@ -249,7 +249,6 @@ contacts (prepare-contacts contacts) notifications ( 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)) diff --git a/src/status_im/utils/re_frame.cljs b/src/status_im/utils/re_frame.cljs deleted file mode 100644 index 117674af2b..0000000000 --- a/src/status_im/utils/re_frame.cljs +++ /dev/null @@ -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) - diff --git a/src/status_im2/navigation/core.cljs b/src/status_im2/navigation/core.cljs index a73c0af3b0..24d26dea3c 100644 --- a/src/status_im2/navigation/core.cljs +++ b/src/status_im2/navigation/core.cljs @@ -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) diff --git a/src/status_im2/navigation/events.cljs b/src/status_im2/navigation/events.cljs index 77892ff72f..bf2e9ce0b9 100644 --- a/src/status_im2/navigation/events.cljs +++ b/src/status_im2/navigation/events.cljs @@ -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}) diff --git a/src/status_im2/setup/core.cljs b/src/status_im2/setup/core.cljs index a13b08869c..593e7aea65 100644 --- a/src/status_im2/setup/core.cljs +++ b/src/status_im2/setup/core.cljs @@ -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 diff --git a/src/status_im2/setup/dev.cljs b/src/status_im2/setup/dev.cljs index 0cd34c34f5..89f75969fb 100644 --- a/src/status_im2/setup/dev.cljs +++ b/src/status_im2/setup/dev.cljs @@ -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)] diff --git a/src/status_im2/setup/events.cljs b/src/status_im2/setup/events.cljs index c22f2cb5fd..0730a891e2 100644 --- a/src/status_im2/setup/events.cljs +++ b/src/status_im2/setup/events.cljs @@ -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 diff --git a/src/status_im2/setup/log.cljs b/src/status_im2/setup/log.cljs index 251bddf20d..9635854440 100644 --- a/src/status_im2/setup/log.cljs +++ b/src/status_im2/setup/log.cljs @@ -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) diff --git a/src/utils.cljs b/src/utils.cljs deleted file mode 100644 index 2bd371a747..0000000000 --- a/src/utils.cljs +++ /dev/null @@ -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))) diff --git a/src/utils/collection.cljs b/src/utils/collection.cljs new file mode 100644 index 0000000000..fb2cfc7e5c --- /dev/null +++ b/src/utils/collection.cljs @@ -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))) diff --git a/src/utils/number.cljs b/src/utils/number.cljs new file mode 100644 index 0000000000..dcf1a3ec8c --- /dev/null +++ b/src/utils/number.cljs @@ -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))) diff --git a/src/utils/re_frame.clj b/src/utils/re_frame.clj new file mode 100644 index 0000000000..06fff3714a --- /dev/null +++ b/src/utils/re_frame.clj @@ -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)))))) diff --git a/src/utils/re_frame.cljs b/src/utils/re_frame.cljs new file mode 100644 index 0000000000..310f08fe39 --- /dev/null +++ b/src/utils/re_frame.cljs @@ -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) diff --git a/src/utils/string.cljs b/src/utils/string.cljs new file mode 100644 index 0000000000..98adceb590 --- /dev/null +++ b/src/utils/string.cljs @@ -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))