mirror of
https://github.com/penpot/penpot.git
synced 2025-01-09 16:30:37 -05:00
♻️ Reimplement workspace presence state.
Remove the use of the database for presence state.
This commit is contained in:
parent
a16f4393b9
commit
e15d93e8a4
4 changed files with 91 additions and 116 deletions
|
@ -194,6 +194,7 @@
|
|||
|
||||
;; --- CONNECTION INIT
|
||||
|
||||
(declare send-presence)
|
||||
(declare handle-message)
|
||||
(declare start-loop!)
|
||||
|
||||
|
@ -211,7 +212,7 @@
|
|||
(.disconnect session)))))))
|
||||
|
||||
(defn- start-loop!
|
||||
[{:keys [rcv-ch out-ch sub-ch session-id] :as cfg}]
|
||||
[{:keys [rcv-ch out-ch sub-ch session-id profile-id] :as cfg}]
|
||||
(aa/go-try
|
||||
(loop []
|
||||
(let [timeout (a/timeout 30000)
|
||||
|
@ -224,11 +225,17 @@
|
|||
(aa/<? (handle-message cfg val))
|
||||
(recur))
|
||||
|
||||
;; If message comes from subscription channel; we just need
|
||||
;; to foreward it to the output channel.
|
||||
|
||||
;; Process message coming from pubsub.
|
||||
(and (= port sub-ch) (some? val))
|
||||
(do
|
||||
(when-not (= (:session-id val) session-id)
|
||||
;; If we receive a connect message of other user, we need
|
||||
;; to send an update presence to all participants.
|
||||
(when (= :connect (:type val))
|
||||
(a/<! (send-presence cfg)))
|
||||
|
||||
;; Then, just forward the message
|
||||
(a/>! out-ch val))
|
||||
(recur))
|
||||
|
||||
|
@ -243,51 +250,14 @@
|
|||
:else
|
||||
nil)))))
|
||||
|
||||
;; --- PRESENCE HANDLING API
|
||||
|
||||
(def ^:private
|
||||
sql:retrieve-presence
|
||||
"select * from presence
|
||||
where file_id=?
|
||||
and (clock_timestamp() - updated_at) < '5 min'::interval")
|
||||
|
||||
(def ^:private
|
||||
sql:update-presence
|
||||
"insert into presence (file_id, session_id, profile_id, updated_at)
|
||||
values (?, ?, ?, clock_timestamp())
|
||||
on conflict (file_id, session_id, profile_id)
|
||||
do update set updated_at=clock_timestamp()")
|
||||
|
||||
(defn- retrieve-presence
|
||||
[{:keys [pool file-id] :as cfg}]
|
||||
(let [rows (db/exec! pool [sql:retrieve-presence file-id])]
|
||||
(mapv (juxt :session-id :profile-id) rows)))
|
||||
|
||||
(defn- retrieve-presence*
|
||||
[{:keys [executor] :as cfg}]
|
||||
(aa/with-thread executor
|
||||
(retrieve-presence cfg)))
|
||||
|
||||
(defn- update-presence
|
||||
[{:keys [pool file-id session-id profile-id] :as cfg}]
|
||||
(let [sql [sql:update-presence file-id session-id profile-id]]
|
||||
(db/exec-one! pool sql)))
|
||||
|
||||
(defn- update-presence*
|
||||
[{:keys [executor] :as cfg}]
|
||||
(aa/with-thread executor
|
||||
(update-presence cfg)))
|
||||
|
||||
(defn- delete-presence
|
||||
[{:keys [pool file-id session-id profile-id] :as cfg}]
|
||||
(db/delete! pool :presence {:file-id file-id
|
||||
:profile-id profile-id
|
||||
:session-id session-id}))
|
||||
|
||||
(defn- delete-presence*
|
||||
[{:keys [executor] :as cfg}]
|
||||
(aa/with-thread executor
|
||||
(delete-presence cfg)))
|
||||
(defn send-presence
|
||||
([cfg] (send-presence cfg :presence))
|
||||
([{:keys [msgbus session-id profile-id file-id]} type]
|
||||
(a/go
|
||||
(a/<! (msgbus :pub {:topic file-id
|
||||
:message {:type type
|
||||
:session-id session-id
|
||||
:profile-id profile-id}})))))
|
||||
|
||||
;; --- INCOMING MSG PROCESSING
|
||||
|
||||
|
@ -297,24 +267,16 @@
|
|||
(defmethod handle-message :connect
|
||||
[{:keys [file-id msgbus] :as cfg} _message]
|
||||
;; (log/debugf "profile '%s' is connected to file '%s'" profile-id file-id)
|
||||
(aa/go-try
|
||||
(aa/<? (update-presence* cfg))
|
||||
(let [members (aa/<? (retrieve-presence* cfg))
|
||||
val {:topic file-id :message {:type :presence :sessions members}}]
|
||||
(a/<! (msgbus :pub val)))))
|
||||
(send-presence cfg :connect))
|
||||
|
||||
(defmethod handle-message :disconnect
|
||||
[{:keys [file-id msgbus] :as cfg} _message]
|
||||
;; (log/debugf "profile '%s' is disconnected from '%s'" profile-id file-id)
|
||||
(aa/go-try
|
||||
(aa/<? (delete-presence* cfg))
|
||||
(let [members (aa/<? (retrieve-presence* cfg))
|
||||
val {:topic file-id :message {:type :presence :sessions members}}]
|
||||
(a/<! (msgbus :pub val)))))
|
||||
(send-presence cfg :disconnect))
|
||||
|
||||
(defmethod handle-message :keepalive
|
||||
[cfg _message]
|
||||
(update-presence* cfg))
|
||||
(a/go (do :nothing)))
|
||||
|
||||
(defmethod handle-message :pointer-update
|
||||
[{:keys [profile-id file-id session-id msgbus] :as cfg} message]
|
||||
|
|
|
@ -50,12 +50,13 @@
|
|||
;; --- Profile Fetched
|
||||
|
||||
(defn profile-fetched
|
||||
[{:keys [fullname] :as data}]
|
||||
[{:keys [fullname id] :as data}]
|
||||
(us/verify ::profile data)
|
||||
(ptk/reify ::profile-fetched
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(-> state
|
||||
(assoc :profile-id id)
|
||||
(assoc :profile data)
|
||||
;; Safeguard if the profile is loaded after teams
|
||||
(assoc-in [:profile :teams] (get-in state [:profile :teams]))))
|
||||
|
|
|
@ -70,6 +70,10 @@
|
|||
(rx/filter #(s/valid? ::message %))
|
||||
(rx/map process-message))
|
||||
|
||||
(rx/of (handle-presence {:type :connect
|
||||
:session-id (:session-id state)
|
||||
:profile-id (:profile-id state)}))
|
||||
|
||||
;; Send back to backend all pointer messages.
|
||||
(->> stream
|
||||
(rx/filter ms/pointer-event?)
|
||||
|
@ -80,9 +84,11 @@
|
|||
(defn- process-message
|
||||
[{:keys [type] :as msg}]
|
||||
(case type
|
||||
:presence (handle-presence msg)
|
||||
:connect (handle-presence msg)
|
||||
:presence (handle-presence msg)
|
||||
:disconnect (handle-presence msg)
|
||||
:pointer-update (handle-pointer-update msg)
|
||||
:file-change (handle-file-change msg)
|
||||
:file-change (handle-file-change msg)
|
||||
:library-change (handle-library-change msg)
|
||||
::unknown))
|
||||
|
||||
|
@ -136,41 +142,41 @@
|
|||
})
|
||||
|
||||
(defn handle-presence
|
||||
[{:keys [sessions] :as message}]
|
||||
(letfn [(assign-color [sessions session]
|
||||
(if (string? (:color session))
|
||||
session
|
||||
(let [used (into #{}
|
||||
(comp (map second)
|
||||
(map :color)
|
||||
(remove nil?))
|
||||
sessions)
|
||||
avail (set/difference presence-palette used)
|
||||
color (or (first avail) "#000000")]
|
||||
(assoc session :color color))))
|
||||
[{:keys [type session-id profile-id] :as message}]
|
||||
(letfn [(get-next-color [presence]
|
||||
(let [xfm (comp (map second)
|
||||
(map :color)
|
||||
(remove nil?))
|
||||
used (into #{} xfm presence)
|
||||
avail (set/difference presence-palette used)]
|
||||
(or (first avail) "#000000")))
|
||||
|
||||
(assign-session [sessions {:keys [id profile]}]
|
||||
(let [session {:id id
|
||||
:fullname (:fullname profile)
|
||||
:updated-at (dt/now)
|
||||
:photo-uri (cfg/resolve-profile-photo-url profile)}
|
||||
session (assign-color sessions session)]
|
||||
(assoc sessions id session)))
|
||||
(update-color [color presence]
|
||||
(if (some? color)
|
||||
color
|
||||
(get-next-color presence)))
|
||||
|
||||
(update-sessions [previous profiles]
|
||||
(let [previous (select-keys previous (map first sessions)) ; Initial clearing
|
||||
pending (->> sessions
|
||||
(filter #(not (contains? previous (first %))))
|
||||
(map (fn [[session-id profile-id]]
|
||||
{:id session-id
|
||||
:profile (get profiles profile-id)})))]
|
||||
(reduce assign-session previous pending)))]
|
||||
(update-sesion [session presence]
|
||||
(-> session
|
||||
(assoc :id session-id)
|
||||
(assoc :profile-id profile-id)
|
||||
(assoc :updated-at (dt/now))
|
||||
(update :color update-color presence)))
|
||||
|
||||
(update-presence [presence]
|
||||
(-> presence
|
||||
(update session-id update-sesion presence)
|
||||
(d/without-nils)))
|
||||
|
||||
]
|
||||
|
||||
(ptk/reify ::handle-presence
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(let [profiles (:users state)]
|
||||
(update state :workspace-presence update-sessions profiles))))))
|
||||
;; (let [profiles (:users state)]
|
||||
(if (= :disconnect type)
|
||||
(update state :workspace-presence dissoc session-id)
|
||||
(update state :workspace-presence update-presence))))))
|
||||
|
||||
(defn handle-pointer-update
|
||||
[{:keys [page-id profile-id session-id x y] :as msg}]
|
||||
|
|
|
@ -5,18 +5,19 @@
|
|||
;; This Source Code Form is "Incompatible With Secondary Licenses", as
|
||||
;; defined by the Mozilla Public License, v. 2.0.
|
||||
;;
|
||||
;; Copyright (c) 2020 UXBOX Labs SL
|
||||
;; Copyright (c) UXBOX Labs SL
|
||||
|
||||
(ns app.main.ui.workspace.presence
|
||||
(:require
|
||||
[rumext.alpha :as mf]
|
||||
[cuerdas.core :as str]
|
||||
[beicon.core :as rx]
|
||||
[app.config :as cfg]
|
||||
[app.main.refs :as refs]
|
||||
[app.main.store :as st]
|
||||
[app.util.router :as rt]
|
||||
[app.util.time :as dt]
|
||||
[app.util.timers :as tm]
|
||||
[app.util.router :as rt]))
|
||||
[app.util.timers :as ts]
|
||||
[beicon.core :as rx]
|
||||
[cuerdas.core :as str]
|
||||
[rumext.alpha :as mf]))
|
||||
|
||||
(def pointer-icon-path
|
||||
(str "M5.292 4.027L1.524.26l-.05-.01L0 0l.258 1.524 3.769 3.768zm-.45 "
|
||||
|
@ -24,7 +25,7 @@
|
|||
"3.39 3.39zM1.192.526l-.668.667L.431.646.64.43l.552.094z"))
|
||||
|
||||
(mf/defc session-cursor
|
||||
[{:keys [session] :as props}]
|
||||
[{:keys [session profile] :as props}]
|
||||
(let [zoom (mf/deref refs/selected-zoom)
|
||||
point (:point session)
|
||||
color (:color session "#000000")
|
||||
|
@ -32,7 +33,7 @@
|
|||
[:g.multiuser-cursor {:transform transform}
|
||||
[:path {:fill color
|
||||
:d pointer-icon-path
|
||||
:font-family "sans-serif"}]
|
||||
}]
|
||||
[:g {:transform "translate(0 -291.708)"}
|
||||
[:rect {:width 25
|
||||
:height 5
|
||||
|
@ -50,53 +51,58 @@
|
|||
:overflow "hidden"
|
||||
:fill "#fff"
|
||||
:stroke-width 1
|
||||
:font-family "Open Sans"
|
||||
:font-family "Works Sans"
|
||||
:font-size 3
|
||||
:font-weight 400
|
||||
:letter-spacing 0
|
||||
:style { :line-height 1.25 }
|
||||
:word-spacing 0}
|
||||
(str (str/slice (:fullname session) 0 14)
|
||||
(when (> (count (:fullname session)) 14) "..."))]]]))
|
||||
(str (str/slice (:fullname profile) 0 14)
|
||||
(when (> (count (:fullname profile)) 14) "..."))]]]))
|
||||
|
||||
(mf/defc active-cursors
|
||||
{::mf/wrap [mf/memo]}
|
||||
[{:keys [page-id] :as props}]
|
||||
(let [counter (mf/use-state 0)
|
||||
users (mf/deref refs/users)
|
||||
sessions (mf/deref refs/workspace-presence)
|
||||
sessions (->> (vals sessions)
|
||||
(filter #(= page-id (:page-id %)))
|
||||
(filter #(>= 3000 (- (inst-ms (dt/now)) (inst-ms (:updated-at %))))))]
|
||||
(filter #(>= 5000 (- (inst-ms (dt/now)) (inst-ms (:updated-at %))))))]
|
||||
(mf/use-effect
|
||||
nil
|
||||
(fn []
|
||||
(let [sem (tm/schedule 1000 #(swap! counter inc))]
|
||||
(let [sem (ts/schedule 1000 #(swap! counter inc))]
|
||||
(fn [] (rx/dispose! sem)))))
|
||||
|
||||
(for [session sessions]
|
||||
(when (:point session)
|
||||
[:& session-cursor {:session session :key (:id session)}]))))
|
||||
[:& session-cursor {:session session
|
||||
:profile (get users (:profile-id session))
|
||||
:key (:id session)}]))))
|
||||
|
||||
;; --- SESSION WIDGET
|
||||
|
||||
(mf/defc session-widget
|
||||
[{:keys [session self?] :as props}]
|
||||
(let [photo (:photo-uri session "/images/avatar.jpg")]
|
||||
[:li.tooltip.tooltip-bottom
|
||||
{:alt (:fullname session)
|
||||
:on-click (when self?
|
||||
#(st/emit! (rt/navigate :settings/profile)))}
|
||||
[:img {:style {:border-color (:color session)}
|
||||
:src photo}]]))
|
||||
[{:keys [session self? profile] :as props}]
|
||||
[:li.tooltip.tooltip-bottom
|
||||
{:alt (:fullname profile)
|
||||
:on-click (when self? (st/emitf (rt/navigate :settings/profile)))}
|
||||
[:img {:style {:border-color (:color session)}
|
||||
:src (cfg/resolve-profile-photo-url profile)}]])
|
||||
|
||||
(mf/defc active-sessions
|
||||
{::mf/wrap [mf/memo]}
|
||||
[]
|
||||
(let [profile (mf/deref refs/profile)
|
||||
sessions (mf/deref refs/workspace-presence)]
|
||||
users (mf/deref refs/users)
|
||||
presence (mf/deref refs/workspace-presence)]
|
||||
[:ul.active-users
|
||||
(for [session (vals sessions)]
|
||||
(for [session (vals presence)]
|
||||
[:& session-widget
|
||||
{:session session
|
||||
:self? (= (:id session) (:id profile))
|
||||
:profile (get users (:profile-id session))
|
||||
:self? (= (:profile-id session) (:id profile))
|
||||
:key (:id session)}])]))
|
||||
|
||||
|
||||
|
|
Loading…
Reference in a new issue