mirror of
https://github.com/penpot/penpot.git
synced 2025-07-21 16:27:20 +02:00
🎉 Add comments to dashboard.
This commit is contained in:
parent
420294aef4
commit
742af4e066
28 changed files with 968 additions and 438 deletions
|
@ -40,11 +40,14 @@
|
|||
(s/def ::count-unread-comments ::us/integer)
|
||||
(s/def ::created-at ::us/inst)
|
||||
(s/def ::file-id ::us/uuid)
|
||||
(s/def ::file-name ::us/string)
|
||||
(s/def ::modified-at ::us/inst)
|
||||
(s/def ::owner-id ::us/uuid)
|
||||
(s/def ::page-id ::us/uuid)
|
||||
(s/def ::page-name ::us/string)
|
||||
(s/def ::participants (s/every ::us/uuid :kind set?))
|
||||
(s/def ::position ::us/point)
|
||||
(s/def ::project-id ::us/uuid)
|
||||
(s/def ::seqn ::us/integer)
|
||||
(s/def ::thread-id ::us/uuid)
|
||||
|
||||
|
@ -52,15 +55,18 @@
|
|||
(s/keys :req-un [::us/id
|
||||
::page-id
|
||||
::file-id
|
||||
::project-id
|
||||
::page-name
|
||||
::file-name
|
||||
::seqn
|
||||
::content
|
||||
::participants
|
||||
::count-unread-comments
|
||||
::count-comments
|
||||
::created-at
|
||||
::modified-at
|
||||
::owner-id
|
||||
::position]))
|
||||
::position]
|
||||
:opt-un [::count-unread-comments
|
||||
::count-comments]))
|
||||
|
||||
(s/def ::comment
|
||||
(s/keys :req-un [::us/id
|
||||
|
@ -92,20 +98,18 @@
|
|||
ptk/WatchEvent
|
||||
(watch [_ state stream]
|
||||
(->> (rp/mutation :create-comment-thread params)
|
||||
(rx/mapcat #(rp/query :comment-thread {:file-id (:file-id %) :id (:id %)}))
|
||||
(rx/map #(partial created %)))))))
|
||||
|
||||
(defn update-comment-thread-status
|
||||
[{:keys [id] :as thread}]
|
||||
(us/assert ::comment-thread thread)
|
||||
(ptk/reify ::update-comment-thread-status
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(d/update-in-when state [:comment-threads id] assoc :count-unread-comments 0))
|
||||
|
||||
ptk/WatchEvent
|
||||
(watch [_ state stream]
|
||||
(->> (rp/mutation :update-comment-thread-status {:id id})
|
||||
(rx/ignore)))))
|
||||
(let [done #(d/update-in-when % [:comment-threads id] assoc :count-unread-comments 0)]
|
||||
(->> (rp/mutation :update-comment-thread-status {:id id})
|
||||
(rx/map (constantly done)))))))
|
||||
|
||||
|
||||
(defn update-comment-thread
|
||||
|
@ -211,6 +215,18 @@
|
|||
(->> (rp/query :comments {:thread-id thread-id})
|
||||
(rx/map #(partial fetched %)))))))
|
||||
|
||||
(defn retrieve-unread-comment-threads
|
||||
"A event used mainly in dashboard for retrieve all unread threads of a team."
|
||||
[team-id]
|
||||
(us/assert ::us/uuid team-id)
|
||||
(ptk/reify ::retrieve-unread-comment-threads
|
||||
ptk/WatchEvent
|
||||
(watch [_ state stream]
|
||||
(let [fetched #(assoc %2 :comment-threads (d/index-by :id %1))]
|
||||
(->> (rp/query :unread-comment-threads {:team-id team-id})
|
||||
(rx/map #(partial fetched %)))))))
|
||||
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;; Local State
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
@ -221,6 +237,7 @@
|
|||
(ptk/reify ::open-thread
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(prn "open-thread" id)
|
||||
(-> state
|
||||
(update :comments-local assoc :open id)
|
||||
(update :workspace-drawing dissoc :comment)))))
|
||||
|
@ -230,6 +247,7 @@
|
|||
(ptk/reify ::close-thread
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(prn "close-thread")
|
||||
(-> state
|
||||
(update :comments-local dissoc :open :draft)
|
||||
(update :workspace-drawing dissoc :comment)))))
|
||||
|
@ -282,11 +300,31 @@
|
|||
(if (= (:page-id current) (:page-id thread))
|
||||
(cons (update current :items conj thread)
|
||||
(rest result))
|
||||
(cons {:page-id (:page-id thread) :items [thread]}
|
||||
(cons {:page-id (:page-id thread)
|
||||
:page-name (:page-name thread)
|
||||
:items [thread]}
|
||||
result))))]
|
||||
(reverse
|
||||
(reduce group-by-page nil threads))))
|
||||
|
||||
|
||||
(defn group-threads-by-file-and-page
|
||||
[threads]
|
||||
(letfn [(group-by-file-and-page [result thread]
|
||||
(let [current (first result)]
|
||||
(if (and (= (:page-id current) (:page-id thread))
|
||||
(= (:file-id current) (:file-id thread)))
|
||||
(cons (update current :items conj thread)
|
||||
(rest result))
|
||||
(cons {:page-id (:page-id thread)
|
||||
:page-name (:page-name thread)
|
||||
:file-id (:file-id thread)
|
||||
:file-name (:file-name thread)
|
||||
:items [thread]}
|
||||
result))))]
|
||||
(reverse
|
||||
(reduce group-by-file-and-page nil threads))))
|
||||
|
||||
(defn apply-filters
|
||||
[cstate profile threads]
|
||||
(let [{:keys [show mode open]} cstate]
|
||||
|
|
|
@ -64,13 +64,6 @@
|
|||
|
||||
;; --- Fetch Team
|
||||
|
||||
(defn assoc-team-avatar
|
||||
[{:keys [photo name] :as team}]
|
||||
(us/assert ::team team)
|
||||
(cond-> team
|
||||
(or (nil? photo) (empty? photo))
|
||||
(assoc :photo (avatars/generate {:name name}))))
|
||||
|
||||
(defn fetch-team
|
||||
[{:keys [id] :as params}]
|
||||
(letfn [(fetched [team state]
|
||||
|
@ -80,20 +73,36 @@
|
|||
(watch [_ state stream]
|
||||
(let [profile (:profile state)]
|
||||
(->> (rp/query :team params)
|
||||
(rx/map assoc-team-avatar)
|
||||
(rx/map #(avatars/assoc-avatar % :name))
|
||||
(rx/map #(partial fetched %))))))))
|
||||
|
||||
(defn fetch-team-members
|
||||
[{:keys [id] :as params}]
|
||||
(us/assert ::us/uuid id)
|
||||
(letfn [(fetched [members state]
|
||||
(assoc-in state [:team-members id] (d/index-by :id members)))]
|
||||
(->> (map #(avatars/assoc-avatar % :name) members)
|
||||
(d/index-by :id)
|
||||
(assoc-in state [:team-members id])))]
|
||||
(ptk/reify ::fetch-team-members
|
||||
ptk/WatchEvent
|
||||
(watch [_ state stream]
|
||||
(->> (rp/query :team-members {:team-id id})
|
||||
(rx/map #(partial fetched %)))))))
|
||||
|
||||
|
||||
(defn fetch-team-users
|
||||
[{:keys [id] :as params}]
|
||||
(us/assert ::us/uuid id)
|
||||
(letfn [(fetched [users state]
|
||||
(->> (map #(avatars/assoc-avatar % :fullname) users)
|
||||
(d/index-by :id)
|
||||
(assoc-in state [:team-users id])))]
|
||||
(ptk/reify ::fetch-team-users
|
||||
ptk/WatchEvent
|
||||
(watch [_ state stream]
|
||||
(->> (rp/query :team-users {:team-id id})
|
||||
(rx/map #(partial fetched %)))))))
|
||||
|
||||
;; --- Fetch Projects
|
||||
|
||||
(defn fetch-projects
|
||||
|
@ -115,7 +124,8 @@
|
|||
(watch [_ state stream]
|
||||
(let [profile (:profile state)]
|
||||
(->> (rx/merge (ptk/watch (fetch-team params) state stream)
|
||||
(ptk/watch (fetch-projects {:team-id id}) state stream))
|
||||
(ptk/watch (fetch-projects {:team-id id}) state stream)
|
||||
(ptk/watch (fetch-team-users params) state stream))
|
||||
(rx/catch (fn [{:keys [type code] :as error}]
|
||||
(cond
|
||||
(and (= :not-found type)
|
||||
|
|
|
@ -55,8 +55,8 @@
|
|||
(update [_ state]
|
||||
(assoc state :profile
|
||||
(cond-> data
|
||||
(nil? (:photo-uri data))
|
||||
(assoc :photo-uri (avatars/generate {:name fullname}))
|
||||
(empty? (:photo data))
|
||||
(assoc :photo (avatars/generate {:name fullname}))
|
||||
|
||||
(nil? (:lang data))
|
||||
(assoc :lang cfg/default-language)
|
||||
|
|
|
@ -14,10 +14,12 @@
|
|||
[app.common.math :as mth]
|
||||
[app.common.spec :as us]
|
||||
[app.main.constants :as c]
|
||||
[app.main.data.workspace :as dw]
|
||||
[app.main.data.workspace.common :as dwc]
|
||||
[app.main.data.comments :as dcm]
|
||||
[app.main.store :as st]
|
||||
[app.main.streams :as ms]
|
||||
[app.util.router :as rt]
|
||||
[beicon.core :as rx]
|
||||
[cljs.spec.alpha :as s]
|
||||
[potok.core :as ptk]))
|
||||
|
@ -82,12 +84,31 @@
|
|||
(update [_ state]
|
||||
(update state :workspace-local
|
||||
(fn [{:keys [vbox vport zoom] :as local}]
|
||||
(prn "center-to-comment-thread" vbox)
|
||||
(let [pw (/ 50 zoom)
|
||||
ph (/ 200 zoom)
|
||||
nw (mth/round (- (/ (:width vbox) 2) pw))
|
||||
nh (mth/round (- (/ (:height vbox) 2) ph))
|
||||
nx (- (:x position) nw)
|
||||
ny (- (:y position) nh)]
|
||||
(update local :vbox assoc :x nx :y ny)))))))
|
||||
|
||||
|
||||
(update local :vbox assoc :x nx :y ny)))))))
|
||||
|
||||
(defn navigate
|
||||
[{:keys [project-id file-id page-id] :as thread}]
|
||||
(us/assert ::dcm/comment-thread thread)
|
||||
(ptk/reify ::navigate
|
||||
ptk/WatchEvent
|
||||
(watch [_ state stream]
|
||||
(let [pparams {:project-id (:project-id thread)
|
||||
:file-id (:file-id thread)}
|
||||
qparams {:page-id (:page-id thread)}]
|
||||
(rx/merge
|
||||
(rx/of (rt/nav :workspace pparams qparams)
|
||||
(dw/select-for-drawing :comments))
|
||||
(->> stream
|
||||
(rx/filter (ptk/type? ::dw/initialize-viewport))
|
||||
(rx/take 1)
|
||||
(rx/mapcat #(rx/of (center-to-comment-thread thread)
|
||||
(dcm/open-thread thread)))))))))
|
||||
|
|
|
@ -208,6 +208,9 @@
|
|||
(def viewer-local
|
||||
(l/derived :viewer-local st/state))
|
||||
|
||||
(def comment-threads
|
||||
(l/derived :comment-threads st/state))
|
||||
|
||||
(def comments-local
|
||||
(l/derived :comments-local st/state))
|
||||
|
||||
|
|
|
@ -148,7 +148,7 @@
|
|||
:left (str (+ pos-x 14) "px")}
|
||||
:on-click dom/stop-propagation}
|
||||
[:div.reply-form
|
||||
[:& resizing-textarea {:placeholder "Write new comment"
|
||||
[:& resizing-textarea {:placeholder (tr "labels.write-new-comment")
|
||||
:value (or content "")
|
||||
:on-esc on-esc
|
||||
:on-change on-change}]
|
||||
|
@ -267,10 +267,10 @@
|
|||
[:& dropdown {:show @options
|
||||
:on-close on-hide-options}
|
||||
[:ul.dropdown.comment-options-dropdown
|
||||
[:li {:on-click on-edit-clicked} "Edit"]
|
||||
[:li {:on-click on-edit-clicked} (tr "labels.edit")]
|
||||
(if thread
|
||||
[:li {:on-click on-delete-thread} "Delete thread"]
|
||||
[:li {:on-click on-delete-comment} "Delete comment"])]]]))
|
||||
[:li {:on-click on-delete-thread} (tr "labels.delete-comment-thread")]
|
||||
[:li {:on-click on-delete-comment} (tr "labels.delete-comment")])]]]))
|
||||
|
||||
(defn comments-ref
|
||||
[{:keys [id] :as thread}]
|
||||
|
@ -289,12 +289,13 @@
|
|||
(sort-by :created-at))
|
||||
comment (first comments)]
|
||||
|
||||
(mf/use-effect
|
||||
(st/emitf (dcm/update-comment-thread-status thread)))
|
||||
(mf/use-layout-effect
|
||||
(mf/deps thread)
|
||||
(st/emitf (dcm/retrieve-comments (:id thread))))
|
||||
|
||||
(mf/use-effect
|
||||
(mf/deps thread)
|
||||
(st/emitf (dcm/retrieve-comments (:id thread))))
|
||||
(st/emitf (dcm/update-comment-thread-status thread)))
|
||||
|
||||
(mf/use-layout-effect
|
||||
(mf/deps thread comments-map)
|
||||
|
@ -338,3 +339,62 @@
|
|||
:unread (pos? (:count-unread-comments thread)))
|
||||
:on-click on-click*}
|
||||
[:span (:seqn thread)]]))
|
||||
|
||||
(mf/defc comment-thread
|
||||
[{:keys [item users on-click] :as props}]
|
||||
(let [profile (get users (:owner-id item))
|
||||
|
||||
on-click*
|
||||
(mf/use-callback
|
||||
(mf/deps item)
|
||||
(fn [event]
|
||||
(dom/stop-propagation event)
|
||||
(dom/prevent-default event)
|
||||
(when (fn? on-click)
|
||||
(on-click item))))]
|
||||
|
||||
[:div.comment {:on-click on-click*}
|
||||
[:div.author
|
||||
[:div.thread-bubble
|
||||
{:class (dom/classnames
|
||||
:resolved (:is-resolved item)
|
||||
:unread (pos? (:count-unread-comments item)))}
|
||||
(:seqn item)]
|
||||
[:div.avatar
|
||||
[:img {:src (cfg/resolve-media-path (:photo profile))}]]
|
||||
[:div.name
|
||||
[:div.fullname (:fullname profile) ", "]
|
||||
[:div.timeago (dt/timeago (:modified-at item))]]]
|
||||
[:div.content
|
||||
[:span.text (:content item)]]
|
||||
[:div.content.replies
|
||||
(let [unread (:count-unread-comments item ::none)
|
||||
total (:count-comments item 1)]
|
||||
[:*
|
||||
(when (> total 1)
|
||||
(if (= total 2)
|
||||
[:span.total-replies "1 reply"]
|
||||
[:span.total-replies (str (dec total) " replies")]))
|
||||
|
||||
(when (and (> total 1) (> unread 0))
|
||||
(if (= unread 1)
|
||||
[:span.new-replies "1 new reply"]
|
||||
[:span.new-replies (str unread " new replies")]))])]]))
|
||||
|
||||
(mf/defc comment-thread-group
|
||||
[{:keys [group users on-thread-click]}]
|
||||
[:div.thread-group
|
||||
(if (:file-name group)
|
||||
[:div.section-title
|
||||
[:span.label.filename (:file-name group) ", "]
|
||||
[:span.label (:page-name group)]]
|
||||
[:div.section-title
|
||||
[:span.icon i/file-html]
|
||||
[:span.label (:page-name group)]])
|
||||
[:div.threads
|
||||
(for [item (:items group)]
|
||||
[:& comment-thread
|
||||
{:item item
|
||||
:on-click on-thread-click
|
||||
:users users
|
||||
:key (:id item)}])]])
|
||||
|
|
104
frontend/src/app/main/ui/dashboard/comments.cljs
Normal file
104
frontend/src/app/main/ui/dashboard/comments.cljs
Normal file
|
@ -0,0 +1,104 @@
|
|||
;; This Source Code Form is subject to the terms of the Mozilla Public
|
||||
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
;;
|
||||
;; 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
|
||||
|
||||
(ns app.main.ui.dashboard.comments
|
||||
(:require
|
||||
[okulary.core :as l]
|
||||
[app.common.data :as d]
|
||||
[app.common.spec :as us]
|
||||
[app.config :as cfg]
|
||||
[app.main.data.auth :as da]
|
||||
[app.main.data.dashboard :as dd]
|
||||
[app.main.data.workspace :as dw]
|
||||
[app.main.data.workspace.comments :as dwcm]
|
||||
[app.main.data.comments :as dcm]
|
||||
[app.main.refs :as refs]
|
||||
[app.main.repo :as rp]
|
||||
[app.main.store :as st]
|
||||
[app.main.ui.components.dropdown :refer [dropdown]]
|
||||
[app.main.ui.comments :as cmt]
|
||||
[app.main.ui.icons :as i]
|
||||
[app.util.dom :as dom]
|
||||
[app.util.i18n :as i18n :refer [t tr]]
|
||||
[app.util.object :as obj]
|
||||
[app.util.router :as rt]
|
||||
[app.util.time :as dt]
|
||||
[app.util.timers :as tm]
|
||||
[beicon.core :as rx]
|
||||
[cljs.spec.alpha :as s]
|
||||
[cuerdas.core :as str]
|
||||
[rumext.alpha :as mf]))
|
||||
|
||||
|
||||
(defn team-members-ref
|
||||
[{:keys [id] :as team}]
|
||||
(l/derived (l/in [:team-users id]) st/state))
|
||||
|
||||
(mf/defc comments-section
|
||||
[{:keys [profile team]}]
|
||||
|
||||
(mf/use-effect
|
||||
(mf/deps team)
|
||||
(st/emitf (dcm/retrieve-unread-comment-threads (:id team))))
|
||||
|
||||
(let [show-dropdown? (mf/use-state false)
|
||||
show-dropdown (mf/use-fn #(reset! show-dropdown? true))
|
||||
hide-dropdown (mf/use-fn #(reset! show-dropdown? false))
|
||||
threads-map (mf/deref refs/comment-threads)
|
||||
|
||||
users-ref (mf/use-memo (mf/deps team) #(team-members-ref team))
|
||||
users (mf/deref users-ref)
|
||||
|
||||
tgroups (->> (vals threads-map)
|
||||
(sort-by :modified-at)
|
||||
(reverse)
|
||||
(dcm/apply-filters {} profile)
|
||||
(dcm/group-threads-by-file-and-page))
|
||||
|
||||
|
||||
on-navigate
|
||||
(mf/use-callback
|
||||
(fn [thread]
|
||||
(st/emit! (dwcm/navigate thread))))]
|
||||
|
||||
[:div.dashboard-comments-section
|
||||
[:div.button
|
||||
{:on-click show-dropdown
|
||||
:class (dom/classnames :open @show-dropdown?
|
||||
:unread (boolean (seq tgroups)))}
|
||||
i/chat]
|
||||
|
||||
[:& dropdown {:show @show-dropdown? :on-close hide-dropdown}
|
||||
[:div.dropdown.comments-section.comment-threads-section.
|
||||
[:div.header
|
||||
[:h3 (tr "labels.comments")]
|
||||
[:span.close {:on-click hide-dropdown} i/close]]
|
||||
|
||||
[:hr]
|
||||
|
||||
(if (seq tgroups)
|
||||
[:div.thread-groups
|
||||
[:& cmt/comment-thread-group
|
||||
{:group (first tgroups)
|
||||
:on-thread-click on-navigate
|
||||
:show-file-name true
|
||||
:users users}]
|
||||
(for [tgroup (rest tgroups)]
|
||||
[:*
|
||||
[:hr]
|
||||
|
||||
[:& cmt/comment-thread-group
|
||||
{:group tgroup
|
||||
:on-thread-click on-navigate
|
||||
:show-file-name true
|
||||
:users users
|
||||
:key (:page-id tgroup)}]])]
|
||||
|
||||
[:div.thread-groups-placeholder
|
||||
(tr "labels.no-comments-available")])]]]))
|
|
@ -15,21 +15,24 @@
|
|||
[app.main.data.auth :as da]
|
||||
[app.main.data.dashboard :as dd]
|
||||
[app.main.data.messages :as dm]
|
||||
[app.main.data.modal :as modal]
|
||||
[app.main.data.comments :as dcm]
|
||||
[app.main.refs :as refs]
|
||||
[app.main.repo :as rp]
|
||||
[app.main.store :as st]
|
||||
[app.main.ui.components.dropdown :refer [dropdown]]
|
||||
[app.main.ui.dashboard.inline-edition :refer [inline-edition]]
|
||||
[app.main.ui.components.forms :as fm]
|
||||
[app.main.ui.dashboard.comments :refer [comments-section]]
|
||||
[app.main.ui.dashboard.inline-edition :refer [inline-edition]]
|
||||
[app.main.ui.dashboard.team-form]
|
||||
[app.main.ui.icons :as i]
|
||||
[app.main.ui.keyboard :as kbd]
|
||||
[app.main.data.modal :as modal]
|
||||
[app.util.dom :as dom]
|
||||
[app.util.i18n :as i18n :refer [t tr]]
|
||||
[app.util.object :as obj]
|
||||
[app.util.router :as rt]
|
||||
[app.util.time :as dt]
|
||||
[app.util.avatars :as avatars]
|
||||
[beicon.core :as rx]
|
||||
[cljs.spec.alpha :as s]
|
||||
[cuerdas.core :as str]
|
||||
|
@ -133,7 +136,8 @@
|
|||
(mf/deps (:id team))
|
||||
(fn []
|
||||
(->> (rp/query! :teams)
|
||||
(rx/map #(mapv dd/assoc-team-avatar %))
|
||||
(rx/map (fn [teams]
|
||||
(mapv #(avatars/assoc-avatar % :name) teams)))
|
||||
(rx/subs #(reset! teams %)))))
|
||||
|
||||
[:ul.dropdown.teams-dropdown
|
||||
|
@ -421,12 +425,9 @@
|
|||
|
||||
|
||||
(mf/defc profile-section
|
||||
[{:keys [profile locale] :as props}]
|
||||
[{:keys [profile locale team] :as props}]
|
||||
(let [show (mf/use-state false)
|
||||
photo (:photo-uri profile "")
|
||||
photo (if (str/empty? photo)
|
||||
"/images/avatar.jpg"
|
||||
photo)
|
||||
photo (cfg/resolve-media-path (:photo profile))
|
||||
|
||||
on-click
|
||||
(mf/use-callback
|
||||
|
@ -436,10 +437,10 @@
|
|||
(st/emit! (rt/nav section))
|
||||
(st/emit! section))))]
|
||||
|
||||
[:div.profile-section {:on-click #(reset! show true)}
|
||||
[:img {:src photo}]
|
||||
[:span (:fullname profile)]
|
||||
i/arrow-down
|
||||
[:div.profile-section
|
||||
[:div.profile {:on-click #(reset! show true)}
|
||||
[:img {:src photo}]
|
||||
[:span (:fullname profile)]
|
||||
|
||||
[:& dropdown {:on-close #(reset! show false)
|
||||
:show @show}
|
||||
|
@ -452,17 +453,25 @@
|
|||
[:span.text (t locale "labels.password")]]
|
||||
[:li {:on-click (partial on-click (da/logout))}
|
||||
[:span.icon i/exit]
|
||||
[:span.text (t locale "labels.logout")]]]]]))
|
||||
[:span.text (t locale "labels.logout")]]]]]
|
||||
|
||||
(when (and team profile)
|
||||
[:& comments-section {:profile profile
|
||||
:team team}])]))
|
||||
|
||||
(mf/defc sidebar
|
||||
{::mf/wrap-props false
|
||||
::mf/wrap [mf/memo]}
|
||||
[props]
|
||||
(let [locale (mf/deref i18n/locale)
|
||||
team (obj/get props "team")
|
||||
profile (obj/get props "profile")
|
||||
props (-> (obj/clone props)
|
||||
(obj/set! "locale" locale))]
|
||||
[:div.dashboard-sidebar
|
||||
[:div.sidebar-inside
|
||||
[:> sidebar-content props]
|
||||
[:& profile-section {:profile profile :locale locale}]]]))
|
||||
[:& profile-section
|
||||
{:profile profile
|
||||
:team team
|
||||
:locale locale}]]]))
|
||||
|
|
|
@ -106,8 +106,8 @@
|
|||
(dcm/close-thread)))))
|
||||
]
|
||||
|
||||
[:div.viewer-comments {:on-click on-click}
|
||||
[:div.comments-layer
|
||||
[:div.comments-section {:on-click on-click}
|
||||
[:div.viewer-comments-container
|
||||
[:div.threads
|
||||
(for [item threads]
|
||||
[:& cmt/thread-bubble {:thread item
|
||||
|
|
|
@ -60,7 +60,6 @@
|
|||
#_(dcm/close-thread))))
|
||||
]
|
||||
|
||||
|
||||
(mf/use-effect
|
||||
(mf/deps file-id)
|
||||
(fn []
|
||||
|
@ -68,8 +67,8 @@
|
|||
(fn []
|
||||
(st/emit! ::dwcm/finalize))))
|
||||
|
||||
[:div.workspace-comments
|
||||
[:div.comments-layer
|
||||
[:div.comments-section
|
||||
[:div.workspace-comments-container
|
||||
{:style {:width (str (:width vport) "px")
|
||||
:height (str (:height vport) "px")}}
|
||||
[:div.threads {:style {:transform (str/format "translate(%spx, %spx)" pos-x pos-y)}}
|
||||
|
@ -96,66 +95,6 @@
|
|||
;; Sidebar
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
||||
(mf/defc sidebar-group-item
|
||||
[{:keys [item] :as props}]
|
||||
(let [profile (get @refs/workspace-users (:owner-id item))
|
||||
page-id (mf/use-ctx ctx/current-page-id)
|
||||
file-id (mf/use-ctx ctx/current-file-id)
|
||||
|
||||
on-click
|
||||
(mf/use-callback
|
||||
(mf/deps item page-id)
|
||||
(fn []
|
||||
(when (not= page-id (:page-id item))
|
||||
(st/emit! (dw/go-to-page (:page-id item))))
|
||||
(tm/schedule
|
||||
(st/emitf (dwcm/center-to-comment-thread item)
|
||||
(dcm/open-thread item)))))]
|
||||
|
||||
[:div.comment {:on-click on-click}
|
||||
[:div.author
|
||||
[:div.thread-bubble
|
||||
{:class (dom/classnames
|
||||
:resolved (:is-resolved item)
|
||||
:unread (pos? (:count-unread-comments item)))}
|
||||
(:seqn item)]
|
||||
[:div.avatar
|
||||
[:img {:src (cfg/resolve-media-path (:photo profile))}]]
|
||||
[:div.name
|
||||
[:div.fullname (:fullname profile) ", "]
|
||||
[:div.timeago (dt/timeago (:modified-at item))]]]
|
||||
[:div.content
|
||||
[:span.text (:content item)]]
|
||||
[:div.content.replies
|
||||
(let [unread (:count-unread-comments item ::none)
|
||||
total (:count-comments item 1)]
|
||||
[:*
|
||||
(when (> total 1)
|
||||
(if (= total 2)
|
||||
[:span.total-replies "1 reply"]
|
||||
[:span.total-replies (str (dec total) " replies")]))
|
||||
|
||||
(when (and (> total 1) (> unread 0))
|
||||
(if (= unread 1)
|
||||
[:span.new-replies "1 new reply"]
|
||||
[:span.new-replies (str unread " new replies")]))])]]))
|
||||
|
||||
(defn page-name-ref
|
||||
[id]
|
||||
(l/derived (l/in [:workspace-data :pages-index id :name]) st/state))
|
||||
|
||||
(mf/defc sidebar-item
|
||||
[{:keys [group]}]
|
||||
(let [page-name-ref (mf/use-memo (mf/deps (:page-id group)) #(page-name-ref (:page-id group)))
|
||||
page-name (mf/deref page-name-ref)]
|
||||
[:div.page-section
|
||||
[:div.section-title
|
||||
[:span.icon i/file-html]
|
||||
[:span.label page-name]]
|
||||
[:div.comments-container
|
||||
(for [item (:items group)]
|
||||
[:& sidebar-group-item {:item item :key (:id item)}])]]))
|
||||
|
||||
(mf/defc sidebar-options
|
||||
[{:keys [local] :as props}]
|
||||
(let [{cmode :mode cshow :show} (mf/deref refs/comments-local)
|
||||
|
@ -171,7 +110,7 @@
|
|||
(fn [mode]
|
||||
(st/emit! (dcm/update-filters {:show mode}))))]
|
||||
|
||||
[:ul.dropdown.with-check.sidebar-options-dropdown
|
||||
[:ul.dropdown.with-check
|
||||
[:li {:class (dom/classnames :selected (or (= :all cmode) (nil? cmode)))
|
||||
:on-click #(update-mode :all)}
|
||||
[:span.icon i/tick]
|
||||
|
@ -193,6 +132,7 @@
|
|||
[]
|
||||
(let [threads-map (mf/deref threads-ref)
|
||||
profile (mf/deref refs/profile)
|
||||
users (mf/deref refs/workspace-users)
|
||||
local (mf/deref refs/comments-local)
|
||||
options? (mf/use-state false)
|
||||
|
||||
|
@ -200,28 +140,45 @@
|
|||
(sort-by :modified-at)
|
||||
(reverse)
|
||||
(dcm/apply-filters local profile)
|
||||
(dcm/group-threads-by-page))]
|
||||
(dcm/group-threads-by-page))
|
||||
|
||||
[:div.workspace-comments.workspace-comments-sidebar
|
||||
[:div.sidebar-title
|
||||
page-id (mf/use-ctx ctx/current-page-id)
|
||||
|
||||
on-thread-click
|
||||
(mf/use-callback
|
||||
(fn [thread]
|
||||
(when (not= page-id (:page-id thread))
|
||||
(st/emit! (dw/go-to-page (:page-id thread))))
|
||||
(tm/schedule
|
||||
(st/emitf (dwcm/center-to-comment-thread thread)
|
||||
(dcm/open-thread thread)))))]
|
||||
|
||||
[:div.comments-section.comment-threads-section
|
||||
[:div.workspace-comment-threads-sidebar-header
|
||||
[:div.label "Comments"]
|
||||
[:div.options {:on-click #(reset! options? true)}
|
||||
[:div.label (case (:filter local)
|
||||
[:div.label (case (:mode local)
|
||||
(nil :all) "All"
|
||||
:yours "Only yours")]
|
||||
[:div.icon i/arrow-down]]]
|
||||
[:div.icon i/arrow-down]]
|
||||
|
||||
[:& dropdown {:show @options?
|
||||
:on-close #(reset! options? false)}
|
||||
[:& sidebar-options {:local local}]]
|
||||
[:& dropdown {:show @options?
|
||||
:on-close #(reset! options? false)}
|
||||
[:& sidebar-options {:local local}]]]
|
||||
|
||||
(when (seq tgroups)
|
||||
[:div.threads
|
||||
[:& sidebar-item {:group (first tgroups)}]
|
||||
[:div.thread-groups
|
||||
[:& cmt/comment-thread-group
|
||||
{:group (first tgroups)
|
||||
:on-thread-click on-thread-click
|
||||
:users users}]
|
||||
(for [tgroup (rest tgroups)]
|
||||
[:*
|
||||
[:hr]
|
||||
[:& sidebar-item {:group tgroup
|
||||
:key (:page-id tgroup)}]])])]))
|
||||
[:& cmt/comment-thread-group
|
||||
{:group tgroup
|
||||
:on-thread-click on-thread-click
|
||||
:users users
|
||||
:key (:page-id tgroup)}]])])]))
|
||||
|
||||
|
||||
|
|
|
@ -36,8 +36,13 @@
|
|||
|
||||
(.toDataURL canvas)))
|
||||
|
||||
(defn assoc-profile-avatar
|
||||
[{:keys [photo fullname] :as profile}]
|
||||
(cond-> profile
|
||||
(defn assoc-avatar
|
||||
[{:keys [photo] :as object} key]
|
||||
(cond-> object
|
||||
(or (nil? photo) (empty? photo))
|
||||
(assoc :photo (generate {:name fullname}))))
|
||||
(assoc :photo (generate {:name (get object key)}))))
|
||||
|
||||
(defn assoc-profile-avatar
|
||||
[object]
|
||||
(assoc-avatar object :fullname))
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue