0
Fork 0
mirror of https://github.com/penpot/penpot.git synced 2025-01-09 00:10:11 -05:00

♻️ Reimplement workspace presence state.

Remove the use of the database for presence state.
This commit is contained in:
Andrey Antukh 2021-03-18 09:11:53 +01:00 committed by Andrés Moya
parent a16f4393b9
commit e15d93e8a4
4 changed files with 91 additions and 116 deletions

View file

@ -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]

View file

@ -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]))))

View file

@ -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}]

View file

@ -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)}])]))