diff --git a/frontend/src/app/main/data/workspace.cljs b/frontend/src/app/main/data/workspace.cljs index 31c2f20be..3b464cb82 100644 --- a/frontend/src/app/main/data/workspace.cljs +++ b/frontend/src/app/main/data/workspace.cljs @@ -593,20 +593,21 @@ (defn rename-file [id name] {:pre [(uuid? id) (string? name)]} - (ptk/reify ::rename-file - IDeref - (-deref [_] - {::ev/origin "workspace" :id id :name name}) + (let [name (str/prune name 200)] + (ptk/reify ::rename-file + IDeref + (-deref [_] + {::ev/origin "workspace" :id id :name name}) - ptk/UpdateEvent - (update [_ state] - (assoc-in state [:workspace-file :name] name)) + ptk/UpdateEvent + (update [_ state] + (assoc-in state [:workspace-file :name] name)) - ptk/WatchEvent - (watch [_ _ _] - (let [params {:id id :name name}] - (->> (rp/cmd! :rename-file params) - (rx/ignore)))))) + ptk/WatchEvent + (watch [_ _ _] + (let [params {:id id :name name}] + (->> (rp/cmd! :rename-file params) + (rx/ignore))))))) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; Workspace State Manipulation diff --git a/frontend/src/app/main/data/workspace/interactions.cljs b/frontend/src/app/main/data/workspace/interactions.cljs index 2bdc9f2e1..9c85ffcd3 100644 --- a/frontend/src/app/main/data/workspace/interactions.cljs +++ b/frontend/src/app/main/data/workspace/interactions.cljs @@ -27,6 +27,11 @@ (defn add-flow [starting-frame] + + (dm/assert! + "expect uuid" + (uuid? starting-frame)) + (ptk/reify ::add-flow ptk/WatchEvent (watch [it state _] diff --git a/frontend/src/app/main/data/workspace/notifications.cljs b/frontend/src/app/main/data/workspace/notifications.cljs index 54397b2f3..8df110920 100644 --- a/frontend/src/app/main/data/workspace/notifications.cljs +++ b/frontend/src/app/main/data/workspace/notifications.cljs @@ -144,7 +144,8 @@ (remove nil?)) used (into #{} xfm presence) avail (set/difference presence-palette used)] - (or (first avail) "var(--app-black)"))) + ;; If all colores are used we select the default one + (or (first avail) "#dee563"))) (update-color [color presence] (if (some? color) @@ -158,7 +159,7 @@ (assoc :updated-at (dt/now)) (assoc :version version) (update :color update-color presence) - (assoc :text-color "#000"))) + (assoc :text-color "#000000"))) (update-presence [presence] (-> presence diff --git a/frontend/src/app/main/ui/workspace/colorpicker.cljs b/frontend/src/app/main/ui/workspace/colorpicker.cljs index dbd0da26a..b80536e2c 100644 --- a/frontend/src/app/main/ui/workspace/colorpicker.cljs +++ b/frontend/src/app/main/ui/workspace/colorpicker.cljs @@ -9,6 +9,7 @@ (:require [app.common.colors :as cc] [app.common.data :as d] + [app.common.data.macros :as dm] [app.config :as cfg] [app.main.data.modal :as modal] [app.main.data.workspace.colors :as dc] @@ -50,6 +51,7 @@ ;; --- Color Picker Modal (mf/defc colorpicker + {::mf/props :obj} [{:keys [data disable-gradient disable-opacity disable-image on-change on-accept]}] (let [state (mf/deref refs/colorpicker) node-ref (mf/use-ref) @@ -90,7 +92,7 @@ (not @drag?))))) on-fill-image-click - (mf/use-callback #(dom/click (mf/ref-val fill-image-ref))) + (mf/use-fn #(dom/click (mf/ref-val fill-image-ref))) on-fill-image-selected (mf/use-fn @@ -107,7 +109,7 @@ (assoc :keep-aspect-ratio keep-aspect-ratio?))} true))))) - set-tab! + on-change-tab (mf/use-fn (fn [event] (let [tab (-> (dom/get-current-target event) @@ -226,7 +228,7 @@ (dom/set-css-property! node "--saturation-grad-from" (format-hsl hsl-from)) (dom/set-css-property! node "--saturation-grad-to" (format-hsl hsl-to)))) - ;; Updates color when used el pixel picker + ;; Updates color when pixel picker is used (mf/with-effect [picking-color? picked-color picked-color-select] (when (and picking-color? picked-color picked-color-select) (let [[r g b alpha] picked-color @@ -294,7 +296,7 @@ [:* [:div {:class (stl/css :colorpicker-tabs)} [:& tab-container - {:on-change-tab set-tab! + {:on-change-tab on-change-tab :selected @active-color-tab :collapsable false} @@ -349,7 +351,7 @@ :on-select-color on-select-library-color :on-add-library-color on-add-library-color}]]) - (when on-accept + (when (fn? on-accept) [:div {:class (stl/css :actions)} [:button {:class (stl/css-case :accept-color true @@ -372,59 +374,69 @@ x-pos 400] (cond - (or (nil? x) (nil? y)) #js {:left "auto" :right "16rem" :top "4rem"} + (or (nil? x) (nil? y)) + #js {:left "auto" :right "16rem" :top "4rem"} + (= position :left) (if (> y max-y) - #js {:left (str (- x x-pos) "px") + #js {:left (dm/str (- x x-pos) "px") :bottom "1rem"} - #js {:left (str (- x x-pos) "px") - :top (str (- y 70) "px")}) + #js {:left (dm/str (- x x-pos) "px") + :top (dm/str (- y 70) "px")}) + (= position :right) (if (> y max-y) - #js {:left (str (+ x 80) "px") + #js {:left (dm/str (+ x 80) "px") :bottom "1rem"} - #js {:left (str (+ x 80) "px") - :top (str (- y 70) "px")}) - :else (if (> y max-y) - #js {:left (str (+ x left-offset) "px") - :bottom "1rem"} - #js {:left (str (+ x left-offset) "px") - :top (str (- y 70) "px")})))) + #js {:left (dm/str (+ x 80) "px") + :top (dm/str (- y 70) "px")}) + + :else + (if (> y max-y) + #js {:left (dm/str (+ x left-offset) "px") + :bottom "1rem"} + #js {:left (dm/str (+ x left-offset) "px") + :top (dm/str (- y 70) "px")})))) (mf/defc colorpicker-modal {::mf/register modal/components - ::mf/register-as :colorpicker} + ::mf/register-as :colorpicker + ::mf/props :obj} [{:keys [x y data position disable-gradient disable-opacity disable-image on-change on-close - on-accept] :as props}] - (let [vport (mf/deref viewport) - dirty? (mf/use-var false) + on-accept]}] + (let [vport (mf/deref viewport) + dirty? (mf/use-var false) last-change (mf/use-var nil) - position (or position :left) - style (calculate-position vport position x y) + position (d/nilv position :left) + style (calculate-position vport position x y) - handle-change - (fn [new-data] - (reset! dirty? (not= data new-data)) - (reset! last-change new-data) - (when on-change - (on-change new-data)))] + on-change' + (mf/use-fn + (mf/deps on-change) + (fn [new-data] + (reset! dirty? (not= data new-data)) + (reset! last-change new-data) - (mf/use-effect - (fn [] - #(when (and @dirty? @last-change on-close) - (on-close @last-change)))) + (if (fn? on-change) + (on-change new-data) + (st/emit! (dc/update-colorpicker new-data)))))] + + (mf/with-effect [] + #(when (and @dirty? @last-change on-close) + (on-close @last-change))) [:div {:class (stl/css :colorpicker-tooltip) :style style} + [:& colorpicker {:data data :disable-gradient disable-gradient :disable-opacity disable-opacity :disable-image disable-image - :on-change handle-change + :on-change on-change' :on-accept on-accept}]])) diff --git a/frontend/src/app/main/ui/workspace/presence.cljs b/frontend/src/app/main/ui/workspace/presence.cljs index f1afac29d..cdf6c6e23 100644 --- a/frontend/src/app/main/ui/workspace/presence.cljs +++ b/frontend/src/app/main/ui/workspace/presence.cljs @@ -15,61 +15,64 @@ [app.util.timers :as tm] [rumext.v2 :as mf])) -;; --- SESSION WIDGET - (mf/defc session-widget - [{:keys [session profile index] :as props}] - (let [profile (assoc profile :color (:color session))] + {::mf/props :obj + ::mf/memo true} + [{:keys [color profile index]}] + (let [profile (assoc profile :color color) + full-name (:fullname profile)] [:li {:class (stl/css :session-icon) - :style {:z-index (str (or (+ 1 (* -1 index)) 0)) - :background-color (:color session)} - :title (:fullname profile)} - [:img {:alt (:fullname profile) - :style {:background-color (:color session)} + :style {:z-index (dm/str (+ 1 (* -1 index))) + :background-color color} + :title full-name} + [:img {:alt full-name + :style {:background-color color} :src (cfg/resolve-profile-photo-url profile)}]])) (mf/defc active-sessions - {::mf/wrap [mf/memo]} + {::mf/memo true} [] (let [users (mf/deref refs/users) presence (mf/deref refs/workspace-presence) - user-ids (vals presence) - num-users (count user-ids) - first-users (take 2 user-ids) + + sessions (vals presence) + num-sessions (count sessions) + open* (mf/use-state false) - open? (deref open*) - open-users-widget + open? (and ^boolean (deref open*) (> num-sessions 2)) + on-open (mf/use-fn (fn [] (reset! open* true) (tm/schedule-on-idle #(dom/focus! (dom/get-element "users-close"))))) - close-users-widget (mf/use-fn #(reset! open* false))] + + on-close + (mf/use-fn #(reset! open* false))] [:* - (when (and (> num-users 2) open?) + (when ^boolean open? [:button {:id "users-close" :class (stl/css :active-users-opened) - :on-click close-users-widget - :on-blur close-users-widget} + :on-click on-close + :on-blur on-close} [:ul {:class (stl/css :active-users-list)} - (for [session user-ids] + (for [session sessions] [:& session-widget - {:session session + {:color (:color session) :index 0 :profile (get users (:profile-id session)) - :key (:id session)}])]]) + :key (dm/str (:id session))}])]]) [:button {:class (stl/css-case :active-users true) - :on-click open-users-widget} - + :on-click on-open} [:ul {:class (stl/css :active-users-list)} - (when (> num-users 2) [:span {:class (stl/css :users-num)} (dm/str "+" (- num-users 2))]) - (for [[index session] (d/enumerate first-users)] + (when (> num-sessions 2) + [:span {:class (stl/css :users-num)} (dm/str "+" (- num-sessions 2))]) + + (for [[index session] (d/enumerate (take 2 sessions))] [:& session-widget - {:session session + {:color (:color session) :index index :profile (get users (:profile-id session)) - :key (:id session)}])]]])) - - + :key (dm/str (:id session))}])]]])) diff --git a/frontend/src/app/main/ui/workspace/sidebar/assets/colors.cljs b/frontend/src/app/main/ui/workspace/sidebar/assets/colors.cljs index 50e2ed940..333e4e2fa 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/assets/colors.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/assets/colors.cljs @@ -377,6 +377,7 @@ (grp/group-assets colors reverse-sort?)) read-only? (mf/use-ctx ctx/workspace-read-only?) + add-color (mf/use-fn (fn [value _] @@ -386,23 +387,22 @@ (mf/use-fn (mf/deps file-id) (fn [event] - (let [bounding-rect (-> event - (dom/get-current-target) - (dom/get-bounding-rect)) - x-position (:right bounding-rect) - y-position (:top bounding-rect)] + (let [bounds (-> event + (dom/get-current-target) + (dom/get-bounding-rect)) + x-position (:right bounds) + y-position (:top bounds)] (st/emit! (dw/set-assets-section-open file-id :colors true) (ptk/event ::ev/event {::ev/name "add-asset-to-library" - :asset-type "color"})) - ;; FIXME: replace interop with dom helpers - (modal/show! :colorpicker - {:x x-position - :y y-position - :on-accept add-color - :data {:color "#406280" - :opacity 1} - :position :right})))) + :asset-type "color"}) + (modal/show :colorpicker + {:x x-position + :y y-position + :on-accept add-color + :data {:color "#406280" + :opacity 1} + :position :right}))))) create-group (mf/use-fn diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/menus/layout_container.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/menus/layout_container.cljs index fd5e42324..2bd071164 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/menus/layout_container.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/menus/layout_container.cljs @@ -61,7 +61,7 @@ :layout-grid-rows]) (defn get-layout-flex-icon - [type val is-col?] + [type val ^boolean is-col?] (case type :align-items (if is-col? @@ -115,7 +115,6 @@ :space-between i/align-content-row-between-refactor :stretch nil)) - :align-self (if is-col? (case val @@ -134,7 +133,7 @@ :baseline i/align-self-column-baseline)))) (defn get-layout-grid-icon-refactor - [type val is-col?] + [type val ^boolean is-col?] (case type :align-items (if is-col? @@ -171,7 +170,8 @@ :stretch i/align-content-row-stretch-refactor)))) (mf/defc direction-row-flex - [{:keys [saved-dir on-change] :as props}] + {::mf/props :obj} + [{:keys [saved-dir on-change]}] [:& radio-buttons {:selected (d/name saved-dir) :on-change on-change :name "flex-direction"} @@ -193,7 +193,8 @@ :icon (dir-icons-refactor :column-reverse)}]]) (mf/defc wrap-row - [{:keys [wrap-type on-click] :as props}] + {::mf/props :obj} + [{:keys [wrap-type on-click]}] [:button {:class (stl/css-case :wrap-button true :selected (= wrap-type :wrap)) :title (if (= :wrap wrap-type) @@ -203,7 +204,8 @@ i/wrap-refactor]) (mf/defc align-row - [{:keys [is-col? align-items on-change] :as props}] + {::mf/props :obj} + [{:keys [is-col? align-items on-change]}] [:& radio-buttons {:selected (d/name align-items) :on-change on-change :name "flex-align-items"} @@ -221,7 +223,8 @@ :id "align-items-end"}]]) (mf/defc align-content-row - [{:keys [is-col? align-content on-change] :as props}] + {::mf/props :obj} + [{:keys [is-col? align-content on-change]}] [:& radio-buttons {:selected (d/name align-content) :on-change on-change :name "flex-align-content"} @@ -251,7 +254,8 @@ :id "align-content-space-evenly"}]]) (mf/defc justify-content-row - [{:keys [is-col? justify-content on-change] :as props}] + {::mf/props :obj} + [{:keys [is-col? justify-content on-change]}] [:& radio-buttons {:selected (d/name justify-content) :on-change on-change :name "flex-justify"} @@ -281,8 +285,8 @@ :id "justify-content-space-evenly"}]]) (mf/defc padding-section - [{:keys [values on-change-style on-change] :as props}] - + {::mf/props :obj} + [{:keys [values on-change-style on-change]}] (let [padding-type (:layout-padding-type values) toggle-padding-mode @@ -418,6 +422,7 @@ i/padding-extended-refactor]])) (mf/defc gap-section + {::mf/props :obj} [{:keys [is-col? wrap-type gap-selected? on-change gap-value]}] (let [select-gap (fn [gap] @@ -474,7 +479,7 @@ ;; GRID COMPONENTS (defn get-layout-grid-icon - [type val is-col?] + [type val ^boolean is-col?] (case type :justify-items (if is-col? @@ -497,6 +502,7 @@ :space-evenly i/grid-justify-content-row-between)))) (mf/defc direction-row-grid + {::mf/props :obj} [{:keys [saved-dir on-change] :as props}] [:& radio-buttons {:selected (d/name saved-dir) :on-change on-change @@ -511,7 +517,8 @@ :icon (dir-icons-refactor :column)}]]) (mf/defc grid-edit-mode - [{:keys [id] :as props}] + {::mf/props :obj} + [{:keys [id]}] (let [edition (mf/deref refs/selected-edition) active? (= id edition) @@ -529,7 +536,8 @@ (tr "workspace.layout_grid.editor.options.edit-grid")])) (mf/defc align-grid-row - [{:keys [is-col? align-items set-align] :as props}] + {::mf/props :obj} + [{:keys [is-col? align-items set-align]}] (let [type (if is-col? :column :row)] [:& radio-buttons {:selected (d/name align-items) :on-change #(set-align % type) @@ -548,7 +556,8 @@ :id (dm/str "align-items-end-" (d/name type))}]])) (mf/defc justify-grid-row - [{:keys [is-col? justify-items set-justify] :as props}] + {::mf/props :obj} + [{:keys [is-col? justify-items set-justify]}] (let [type (if is-col? :column :row)] [:& radio-buttons {:selected (d/name justify-items) @@ -561,7 +570,8 @@ :title (dm/str "Justify items " (d/name justify)) :id (dm/str "justify-items-" (d/name justify) "-" (d/name type))}])])) -(defn manage-values [{:keys [value type]}] +(defn- manage-values + [{:keys [type value]}] (case type :auto "auto" :percent (fmt/format-percent value) @@ -570,6 +580,7 @@ value)) (mf/defc grid-track-info + {::mf/props :obj} [{:keys [is-col? type index @@ -650,12 +661,13 @@ i/remove-refactor]])) (mf/defc grid-columns-row + {::mf/props :obj} [{:keys [is-col? expanded? column-values toggle add-new-element set-column-value set-column-type - remove-element reorder-track hover-track on-select-track] :as props}] + remove-element reorder-track hover-track on-select-track]}] (let [column-num (count column-values) direction (if (> column-num 1) - (if is-col? "Columns " "Rows ") - (if is-col? "Column " "Row ")) + (if ^boolean is-col? "Columns " "Rows ") + (if ^boolean is-col? "Column " "Row ")) track-name (dm/str direction (if (= column-num 0) " - empty" column-num)) track-detail (str/join ", " (map manage-values column-values)) @@ -694,8 +706,9 @@ ;; LAYOUT COMPONENT (mf/defc layout-container-menu - {::mf/wrap [#(mf/memo' % (mf/check-props ["ids" "values" "multiple"]))]} - [{:keys [ids values multiple] :as props}] + {::mf/memo #{:ids :values :multiple} + ::mf/props :obj} + [{:keys [ids values multiple]}] (let [;; Display layout-type (:layout values) has-layout? (some? layout-type) @@ -711,7 +724,7 @@ toggle-content (mf/use-fn #(swap! state* not)) on-add-layout - (mf/use-callback + (mf/use-fn (fn [type] (st/emit! (dwsl/create-layout type)) (reset! state* true))) @@ -730,13 +743,13 @@ (reset! state* false)) set-flex - (mf/use-callback + (mf/use-fn (mf/deps on-add-layout) (fn [] (on-add-layout :flex))) set-grid - (mf/use-callback + (mf/use-fn (mf/deps on-add-layout) (fn [] (on-add-layout :grid))) @@ -875,22 +888,22 @@ (st/emit! (dwsl/update-layout ids {:layout-align-content value})))))) handle-show-layout-dropdown - (mf/use-callback + (mf/use-fn (fn [] (swap! show-layout-dropdown* not))) handle-close-layout-options - (mf/use-callback + (mf/use-fn (fn [] (reset! show-layout-dropdown* false))) handle-open-flex-help - (mf/use-callback + (mf/use-fn (fn [] (st/emit! (dom/open-new-window cf/flex-help-uri)))) handle-open-grid-help - (mf/use-callback + (mf/use-fn (fn [] (st/emit! (dom/open-new-window cf/grid-help-uri))))] @@ -1011,8 +1024,9 @@ nil)))])) (mf/defc grid-layout-edition - {::mf/wrap [#(mf/memo' % (mf/check-props ["ids" "values"]))]} - [{:keys [ids values] :as props}] + {::mf/memo #{:ids :values} + ::mf/props :obj} + [{:keys [ids values]}] (let [;; Gap gap-selected? (mf/use-state :none) saved-grid-dir (:layout-grid-dir values) @@ -1135,12 +1149,12 @@ (st/emit! (dwsl/change-layout-track ids type index {:value value :type track-type}))))) handle-open-grid-help - (mf/use-callback + (mf/use-fn (fn [] (st/emit! (dom/open-new-window cf/grid-help-uri)))) handle-locate-grid - (mf/use-callback + (mf/use-fn (fn [] (st/emit! (dwge/locate-board (first ids)))))] diff --git a/frontend/src/app/main/ui/workspace/viewport/presence.cljs b/frontend/src/app/main/ui/workspace/viewport/presence.cljs index ffd4a59bf..e5d019464 100644 --- a/frontend/src/app/main/ui/workspace/viewport/presence.cljs +++ b/frontend/src/app/main/ui/workspace/viewport/presence.cljs @@ -6,6 +6,7 @@ (ns app.main.ui.workspace.viewport.presence (:require + [app.common.data.macros :as dm] [app.main.refs :as refs] [app.util.time :as dt] [app.util.timers :as ts] @@ -13,56 +14,66 @@ [cuerdas.core :as str] [rumext.v2 :as mf])) -(def pointer-icon-path - (str "M11.58,-0.47L11.47,-0.35L0.34,10.77L0.30,10.96L-0.46," - "15.52L4.29,14.72L15.53,3.47L11.58,-0.47ZL11.58," - "-0.47ZL11.58,-0.47ZM11.58,1.3C12.31,2.05,13.02," - "2.742,13.76,3.47L4.0053,13.23C3.27,12.50,2.55," - "11.78,1.82,11.05L11.58,1.30ZL11.58,1.30ZM1.37,12.15L2.90," - "13.68L1.67,13.89L1.165,13.39L1.37,12.15ZL1.37,12.15Z")) +(def pointer-path + (dm/str "M11.58,-0.47L11.47,-0.35L0.34,10.77L0.30,10.96L-0.46," + "15.52L4.29,14.72L15.53,3.47L11.58,-0.47ZL11.58," + "-0.47ZL11.58,-0.47ZM11.58,1.3C12.31,2.05,13.02," + "2.742,13.76,3.47L4.0053,13.23C3.27,12.50,2.55," + "11.78,1.82,11.05L11.58,1.30ZL11.58,1.30ZM1.37,12.15L2.90," + "13.68L1.67,13.89L1.165,13.39L1.37,12.15ZL1.37,12.15Z")) (mf/defc session-cursor - [{:keys [session profile] :as props}] - (let [zoom (mf/deref refs/selected-zoom) - point (:point session) - background-color (:color session "var(--app-black)") - text-color (:text-color session "var(--app-white)") - transform (str/fmt "translate(%s, %s) scale(%s)" (:x point) (:y point) (/ 1 zoom)) - shown-name (if (> (count (:fullname profile)) 16) - (str (str/slice (:fullname profile) 0 12) "...") - (:fullname profile))] + {::mf/props :obj + ::mf/memo true} + [{:keys [session profile zoom]}] + (let [point (:point session) + bg-color (:color session) + fg-color "var(--app-white)" + transform (str/ffmt "translate(%, %) scale(%)" + (dm/get-prop point :x) + (dm/get-prop point :y) + (/ 1 zoom)) + + + fullname (:fullname profile) + fullname (if (> (count fullname) 16) + (dm/str (str/slice fullname 0 12) "...") + fullname)] + [:g.multiuser-cursor {:transform transform} - [:path {:fill background-color - :d pointer-icon-path}] + [:path {:fill bg-color :d pointer-path}] [:g {:transform "translate(17 -10)"} [:foreignObject {:x -0.3 :y -12.5 :width 300 :height 120} - [:div.profile-name {:style {:background-color background-color - :color text-color}} - shown-name]]]])) + [:div.profile-name {:style {:background-color bg-color + :color fg-color}} + fullname]]]])) (mf/defc active-cursors - {::mf/wrap [mf/memo]} - [{:keys [page-id] :as props}] + {::mf/props :obj} + [{:keys [page-id]}] (let [counter (mf/use-state 0) users (mf/deref refs/users) sessions (mf/deref refs/workspace-presence) + zoom (mf/deref refs/selected-zoom) + sessions (->> (vals sessions) + (filter :point) (filter #(= page-id (:page-id %))) - (filter #(>= 5000 (- (inst-ms (dt/now)) (inst-ms (:updated-at %))))))] - (mf/use-effect - nil - (fn [] - (let [sem (ts/schedule 1000 #(swap! counter inc))] - (fn [] (rx/dispose! sem))))) + (filter #(>= 5000 (- (inst-ms (dt/now)) + (inst-ms (:updated-at %))))))] + (mf/with-effect nil + (let [sem (ts/schedule 1000 #(swap! counter inc))] + (fn [] (rx/dispose! sem)))) (for [session sessions] - (when (:point session) - [:& session-cursor {:session session - :profile (get users (:profile-id session)) - :key (:id session)}])))) + [:& session-cursor + {:session session + :zoom zoom + :profile (get users (:profile-id session)) + :key (dm/str (:id session))}])))