0
Fork 0
mirror of https://github.com/penpot/penpot.git synced 2025-02-13 02:28:18 -05:00

Merge pull request #2084 from penpot/eva-alex-move-comments

❇️ Comments positioning
This commit is contained in:
Pablo Alba 2022-08-01 10:03:03 +02:00 committed by GitHub
commit 01306841a9
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
15 changed files with 596 additions and 212 deletions

View file

@ -29,6 +29,7 @@
- Improved share link options. Now you can allow non-team members to comment and/or inspect [Taiga #3056] (https://tree.taiga.io/project/penpot/us/3056)
- Signin/Signup from shared link [Taiga #3472](https://tree.taiga.io/project/penpot/us/3472)
- Support for import/export binary format [Taiga #2991](https://tree.taiga.io/project/penpot/us/2991)
- Comments positioning [Taiga #https://2007](tree.taiga.io/project/penpot/us/2007)
### :bug: Bugs fixed

View file

@ -238,6 +238,9 @@
{:name "0076-mod-storage-object-table"
:fn (mg/resource "app/migrations/sql/0076-mod-storage-object-table.sql")}
{:name "0077-mod-comment-thread-table"
:fn (mg/resource "app/migrations/sql/0077-mod-comment-thread-table.sql")}
])

View file

@ -0,0 +1,3 @@
--- Add frame_id field.
ALTER TABLE comment_thread
ADD COLUMN frame_id uuid NULL DEFAULT '00000000-0000-0000-0000-000000000000';

View file

@ -31,9 +31,10 @@
(s/def ::profile-id ::us/uuid)
(s/def ::position ::gpt/point)
(s/def ::content ::us/string)
(s/def ::frame-id ::us/uuid)
(s/def ::create-comment-thread
(s/keys :req-un [::profile-id ::file-id ::position ::content ::page-id]
(s/keys :req-un [::profile-id ::file-id ::position ::content ::page-id ::frame-id]
:opt-un [::share-id]))
(sv/defmethod ::create-comment-thread
@ -53,7 +54,7 @@
(:next-seqn res)))
(defn- create-comment-thread
[conn {:keys [profile-id file-id page-id position content] :as params}]
[conn {:keys [profile-id file-id page-id position content frame-id] :as params}]
(let [seqn (retrieve-next-seqn conn file-id)
now (dt/now)
pname (retrieve-page-name conn params)
@ -66,7 +67,8 @@
:created-at now
:modified-at now
:seqn seqn
:position (db/pgpoint position)})]
:position (db/pgpoint position)
:frame-id frame-id})]
;; Create a comment entry
@ -281,3 +283,40 @@
:code :not-allowed))
(db/delete! conn :comment {:id id}))))
;; --- Mutation: Update comment thread position
(s/def ::update-comment-thread-position
(s/keys :req-un [::profile-id ::id ::position ::frame-id]))
(sv/defmethod ::update-comment-thread-position
[{:keys [pool] :as cfg} {:keys [profile-id id position frame-id] :as params}]
(db/with-atomic [conn pool]
(let [thread (db/get-by-id conn :comment-thread id {:for-update true})]
(when-not (= (:owner-id thread) profile-id)
(ex/raise :type :validation
:code :not-allowed))
(db/update! conn :comment-thread
{:modified-at (dt/now)
:position (db/pgpoint position)
:frame-id frame-id}
{:id (:id thread)})
nil)))
;; --- Mutation: Update comment frame
(s/def ::update-comment-thread-frame
(s/keys :req-un [::profile-id ::id ::frame-id]))
(sv/defmethod ::update-comment-thread-frame
[{:keys [pool] :as cfg} {:keys [profile-id id frame-id] :as params}]
(db/with-atomic [conn pool]
(let [thread (db/get-by-id conn :comment-thread id {:for-update true})]
(when-not (= (:owner-id thread) profile-id)
(ex/raise :type :validation
:code :not-allowed))
(db/update! conn :comment-thread
{:modified-at (dt/now)
:frame-id frame-id}
{:id (:id thread)})
nil)))

View file

@ -29,6 +29,9 @@
&.unread {
background-color: $color-primary;
}
span {
user-select: none;
}
}
.thread-content {
@ -77,7 +80,7 @@
resize: none;
width: 100%;
border-radius: 2px;
border: 1px solid $color-gray-10;
border: 1px solid $color-gray-20;
max-height: 4rem;
}
@ -188,6 +191,7 @@
margin: 0 $size-2 0 26px;
white-space: pre-wrap;
display: inline-block;
word-break: break-all;
}
}
}

View file

@ -8,7 +8,10 @@
(:require
[app.common.data :as d]
[app.common.geom.point :as gpt]
[app.common.pages.helpers :as cph]
[app.common.spec :as us]
[app.common.uuid :as uuid]
[app.main.data.workspace.state-helpers :as wsh]
[app.main.repo :as rp]
[beicon.core :as rx]
[cljs.spec.alpha :as s]
@ -59,29 +62,70 @@
(declare retrieve-comment-threads)
(declare refresh-comment-thread)
(s/def ::create-thread-params
(s/def ::create-thread-on-workspace-params
(s/keys :req-un [::page-id ::file-id ::position ::content]))
(defn create-thread
[params]
(us/assert ::create-thread-params params)
(letfn [(created [{:keys [id comment] :as thread} state]
(-> state
(update :comment-threads assoc id (dissoc thread :comment))
(update :comments-local assoc :open id)
(update :comments-local dissoc :draft)
(update :workspace-drawing dissoc :comment)
(update-in [:comments id] assoc (:id comment) comment)))]
(s/def ::create-thread-on-viewer-params
(s/keys :req-un [::page-id ::file-id ::position ::content ::frame-id]))
(ptk/reify ::create-comment-thread
ptk/WatchEvent
(watch [_ state _]
(let [share-id (-> state :viewer-local :share-id)
params (assoc params :share-id share-id)]
(->> (rp/mutation :create-comment-thread params)
(rx/mapcat #(rp/query :comment-thread {:file-id (:file-id %) :id (:id %) :share-id share-id}))
(rx/map #(partial created %))
(rx/catch #(rx/throw {:type :comment-error}))))))))
(defn created-thread-on-workspace
[{:keys [id comment page-id] :as thread}]
(ptk/reify ::created-thread-on-workspace
ptk/UpdateEvent
(update [_ state]
(-> state
(update :comment-threads assoc id (dissoc thread :comment))
(update-in [:workspace-data :pages-index page-id :options :comment-threads-position] assoc id (select-keys thread [:position :frame-id]))
(update :comments-local assoc :open id)
(update :comments-local dissoc :draft)
(update :workspace-drawing dissoc :comment)
(update-in [:comments id] assoc (:id comment) comment)))))
(defn create-thread-on-workspace
[params]
(us/assert ::create-thread-on-workspace-params params)
(ptk/reify ::create-thread-on-workspace
ptk/WatchEvent
(watch [_ state _]
(let [page-id (:current-page-id state)
objects (wsh/lookup-page-objects state page-id)
frame-id (cph/frame-id-by-position objects (:position params))
params (assoc params :frame-id frame-id)]
(->> (rp/mutation :create-comment-thread params)
(rx/mapcat #(rp/query :comment-thread {:file-id (:file-id %) :id (:id %)}))
(rx/map created-thread-on-workspace)
(rx/catch #(rx/throw {:type :comment-error})))))))
(defn created-thread-on-viewer
[{:keys [id comment page-id] :as thread}]
(ptk/reify ::created-thread-on-workspace
ptk/UpdateEvent
(update [_ state]
(-> state
(update :comment-threads assoc id (dissoc thread :comment))
(update-in [:viewer :pages page-id :options :comment-threads-position] assoc id (select-keys thread [:position :frame-id]))
(update :comments-local assoc :open id)
(update :comments-local dissoc :draft)
(update :workspace-drawing dissoc :comment)
(update-in [:comments id] assoc (:id comment) comment)))))
(defn create-thread-on-viewer
[params]
(us/assert ::create-thread-on-viewer-params params)
(ptk/reify ::create-thread-on-viewer
ptk/WatchEvent
(watch [_ state _]
(let [share-id (-> state :viewer-local :share-id)
frame-id (:frame-id params)
params (assoc params :share-id share-id :frame-id frame-id)]
(->> (rp/mutation :create-comment-thread params)
(rx/mapcat #(rp/query :comment-thread {:file-id (:file-id %) :id (:id %) :share-id share-id}))
(rx/map created-thread-on-viewer)
(rx/catch #(rx/throw {:type :comment-error})))))))
(defn update-comment-thread-status
[{:keys [id] :as thread}]
@ -95,7 +139,6 @@
(rx/map (constantly done))
(rx/catch #(rx/throw {:type :comment-error})))))))
(defn update-comment-thread
[{:keys [id is-resolved] :as thread}]
(us/assert ::comment-thread thread)
@ -114,7 +157,6 @@
(rx/catch #(rx/throw {:type :comment-error}))
(rx/ignore))))))
(defn add-comment
[thread content]
(us/assert ::comment-thread thread)
@ -146,15 +188,35 @@
(rx/catch #(rx/throw {:type :comment-error}))
(rx/ignore))))))
(defn delete-comment-thread
(defn delete-comment-thread-on-workspace
[{:keys [id] :as thread}]
(us/assert ::comment-thread thread)
(ptk/reify ::delete-comment-thread
(ptk/reify ::delete-comment-thread-on-workspace
ptk/UpdateEvent
(update [_ state]
(-> state
(update :comments dissoc id)
(update :comment-threads dissoc id)))
(let [page-id (:current-page-id state)]
(-> state
(update-in [:workspace-data :pages-index page-id :options :comment-threads-position] dissoc id)
(update :comments dissoc id)
(update :comment-threads dissoc id))))
ptk/WatchEvent
(watch [_ _ _]
(->> (rp/mutation :delete-comment-thread {:id id})
(rx/catch #(rx/throw {:type :comment-error}))
(rx/ignore)))))
(defn delete-comment-thread-on-viewer
[{:keys [id] :as thread}]
(us/assert ::comment-thread thread)
(ptk/reify ::delete-comment-thread-on-viewer
ptk/UpdateEvent
(update [_ state]
(let [page-id (:current-page-id state)]
(-> state
(update-in [:viewer :pages page-id :options :comment-threads-position] dissoc id)
(update :comments dissoc id)
(update :comment-threads dissoc id))))
ptk/WatchEvent
(watch [_ state _]
@ -194,8 +256,18 @@
(defn retrieve-comment-threads
[file-id]
(us/assert ::us/uuid file-id)
(letfn [(fetched [data state]
(assoc state :comment-threads (d/index-by :id data)))]
(letfn [(set-comment-threds [state comment-thread]
(let [path [:workspace-data :pages-index (:page-id comment-thread) :options :comment-threads-position (:id comment-thread)]
thread-position (get-in state path)]
(cond-> state
(nil? thread-position)
(->
(assoc-in (conj path :position) (:position comment-thread))
(assoc-in (conj path :frame-id) (:frame-id comment-thread))))))
(fetched [data state]
(let [state (assoc state :comment-threads (d/index-by :id data))]
(reduce set-comment-threds state data)))]
(ptk/reify ::retrieve-comment-threads
ptk/WatchEvent
(watch [_ state _]
@ -338,3 +410,41 @@
(= :yours mode)
(filter #(contains? (:participants %) (:id profile))))))
(defn update-comment-thread-frame
([thread ]
(update-comment-thread-frame thread uuid/zero))
([thread frame-id]
(us/assert ::comment-thread thread)
(ptk/reify ::update-comment-thread-frame
ptk/UpdateEvent
(update [_ state]
(let [thread-id (:id thread)]
(assoc-in state [:comment-threads thread-id :frame-id] frame-id)))
ptk/WatchEvent
(watch [_ _ _]
(let [thread-id (:id thread)]
(->> (rp/mutation :update-comment-thread-frame {:id thread-id :frame-id frame-id})
(rx/catch #(rx/throw {:type :comment-error :code :update-comment-thread-frame}))
(rx/ignore)))))))
(defn detach-comment-thread
"Detach comment threads that are inside a frame when that frame is deleted"
[ids]
(us/verify (s/coll-of uuid?) ids)
(ptk/reify ::detach-comment-thread
ptk/WatchEvent
(watch [_ state _]
(let [objects (wsh/lookup-page-objects state)
is-frame? (fn [id] (= :frame (get-in objects [id :type])))
frame-ids? (into #{} (filter is-frame?) ids)]
(->> state
:comment-threads
(vals)
(filter (fn [comment] (some #(= % (:frame-id comment)) frame-ids?)))
(map update-comment-thread-frame)
(rx/from))))))

View file

@ -13,7 +13,6 @@
[app.common.geom.point :as gpt]
[app.common.geom.proportions :as gpr]
[app.common.geom.shapes :as gsh]
[app.common.math :as mth]
[app.common.pages :as cp]
[app.common.pages.changes-builder :as pcb]
[app.common.pages.helpers :as cph]
@ -50,6 +49,7 @@
[app.main.data.workspace.thumbnails :as dwth]
[app.main.data.workspace.transforms :as dwt]
[app.main.data.workspace.undo :as dwu]
[app.main.data.workspace.viewport :as dwv]
[app.main.data.workspace.zoom :as dwz]
[app.main.repo :as rp]
[app.main.streams :as ms]
@ -397,140 +397,6 @@
(assoc-in state [:workspace-global :tooltip] content)
(assoc-in state [:workspace-global :tooltip] nil)))))
;; --- Viewport Sizing
(defn initialize-viewport
[{:keys [width height] :as size}]
(letfn [(update* [{:keys [vport] :as local}]
(let [wprop (/ (:width vport) width)
hprop (/ (:height vport) height)]
(-> local
(assoc :vport size)
(update :vbox (fn [vbox]
(-> vbox
(update :width #(/ % wprop))
(update :height #(/ % hprop))))))))
(initialize [state local]
(let [page-id (:current-page-id state)
objects (wsh/lookup-page-objects state page-id)
shapes (cph/get-immediate-children objects)
srect (gsh/selection-rect shapes)
local (assoc local :vport size :zoom 1)]
(cond
(or (not (d/num? (:width srect)))
(not (d/num? (:height srect))))
(assoc local :vbox (assoc size :x 0 :y 0))
(or (> (:width srect) width)
(> (:height srect) height))
(let [srect (gal/adjust-to-viewport size srect {:padding 40})
zoom (/ (:width size) (:width srect))]
(-> local
(assoc :zoom zoom)
(update :vbox merge srect)))
:else
(assoc local :vbox (assoc size
:x (+ (:x srect) (/ (- (:width srect) width) 2))
:y (+ (:y srect) (/ (- (:height srect) height) 2)))))))
(setup [state local]
(if (and (:vbox local) (:vport local))
(update* local)
(initialize state local)))]
(ptk/reify ::initialize-viewport
ptk/UpdateEvent
(update [_ state]
(update state :workspace-local
(fn [local]
(setup state local)))))))
(defn update-viewport-position
[{:keys [x y] :or {x identity y identity}}]
(us/assert fn? x)
(us/assert fn? y)
(ptk/reify ::update-viewport-position
ptk/UpdateEvent
(update [_ state]
(update-in state [:workspace-local :vbox]
(fn [vbox]
(-> vbox
(update :x x)
(update :y y)))))))
(defn update-viewport-size
[resize-type {:keys [width height] :as size}]
(ptk/reify ::update-viewport-size
ptk/UpdateEvent
(update [_ state]
(update state :workspace-local
(fn [{:keys [vport] :as local}]
(if (or (nil? vport)
(mth/almost-zero? width)
(mth/almost-zero? height))
;; If we have a resize to zero just keep the old value
local
(let [wprop (/ (:width vport) width)
hprop (/ (:height vport) height)
vbox (:vbox local)
vbox-x (:x vbox)
vbox-y (:y vbox)
vbox-width (:width vbox)
vbox-height (:height vbox)
vbox-width' (/ vbox-width wprop)
vbox-height' (/ vbox-height hprop)
vbox-x'
(case resize-type
:left (+ vbox-x (- vbox-width vbox-width'))
:right vbox-x
(+ vbox-x (/ (- vbox-width vbox-width') 2)))
vbox-y'
(case resize-type
:top (+ vbox-y (- vbox-height vbox-height'))
:bottom vbox-y
(+ vbox-y (/ (- vbox-height vbox-height') 2)))]
(-> local
(assoc :vport size)
(assoc-in [:vbox :x] vbox-x')
(assoc-in [:vbox :y] vbox-y')
(assoc-in [:vbox :width] vbox-width')
(assoc-in [:vbox :height] vbox-height')))))))))
(defn start-panning []
(ptk/reify ::start-panning
ptk/WatchEvent
(watch [_ state stream]
(let [stopper (->> stream (rx/filter (ptk/type? ::finish-panning)))
zoom (-> (get-in state [:workspace-local :zoom]) gpt/point)]
(when-not (get-in state [:workspace-local :panning])
(rx/concat
(rx/of #(-> % (assoc-in [:workspace-local :panning] true)))
(->> stream
(rx/filter ms/pointer-event?)
(rx/filter #(= :delta (:source %)))
(rx/map :pt)
(rx/take-until stopper)
(rx/map (fn [delta]
(let [delta (gpt/divide delta zoom)]
(update-viewport-position {:x #(- % (:x delta))
:y #(- % (:y delta))})))))))))))
(defn finish-panning []
(ptk/reify ::finish-panning
ptk/UpdateEvent
(update [_ state]
(-> state
(update :workspace-local dissoc :panning)))))
;; --- Update Shape Attrs
(defn update-shape
@ -1762,3 +1628,10 @@
;; Thumbnails
(dm/export dwth/update-thumbnail)
;; Viewport
(dm/export dwv/initialize-viewport)
(dm/export dwv/update-viewport-position)
(dm/export dwv/update-viewport-size)
(dm/export dwv/start-panning)
(dm/export dwv/finish-panning)

View file

@ -6,13 +6,22 @@
(ns app.main.data.workspace.comments
(:require
[app.common.geom.point :as gpt]
[app.common.geom.shapes :as gsh]
[app.common.pages.changes-builder :as pcb]
[app.common.pages.helpers :as cph]
[app.common.spec :as us]
[app.main.data.comments :as dcm]
[app.main.data.workspace :as dw]
[app.main.data.workspace.common :as dwc]
[app.main.data.workspace.changes :as dwc]
[app.main.data.workspace.common :as dwco]
[app.main.data.workspace.drawing :as dwd]
[app.main.data.workspace.state-helpers :as wsh]
[app.main.data.workspace.viewport :as dwv]
[app.main.repo :as rp]
[app.main.streams :as ms]
[app.util.router :as rt]
[beicon.core :as rx]
[cljs.spec.alpha :as s]
[potok.core :as ptk]))
(declare handle-interrupt)
@ -33,7 +42,7 @@
(rx/map handle-comment-layer-click)
(rx/take-until stoper))
(->> stream
(rx/filter dwc/interrupt?)
(rx/filter dwco/interrupt?)
(rx/map handle-interrupt)
(rx/take-until stoper)))))))
@ -95,8 +104,76 @@
(rx/merge
(rx/of (rt/nav :workspace pparams qparams))
(->> stream
(rx/filter (ptk/type? ::dw/initialize-viewport))
(rx/filter (ptk/type? ::dwv/initialize-viewport))
(rx/take 1)
(rx/mapcat #(rx/of (center-to-comment-thread thread)
(dw/select-for-drawing :comments)
(dwd/select-for-drawing :comments)
(dcm/open-thread thread)))))))))
(defn update-comment-thread-position
([thread [new-x new-y]]
(update-comment-thread-position thread [new-x new-y] nil))
([thread [new-x new-y] frame-id]
(us/assert ::dcm/comment-thread thread)
(ptk/reify ::update-comment-thread-position
ptk/WatchEvent
(watch [it state _]
(let [thread-id (:id thread)
page (wsh/lookup-page state)
page-id (:id page)
objects (wsh/lookup-page-objects state page-id)
new-frame-id (if (nil? frame-id)
(cph/frame-id-by-position objects {:x new-x :y new-y})
(:frame-id thread))
thread (assoc thread
:position {:x new-x :y new-y}
:frame-id new-frame-id)
changes
(-> (pcb/empty-changes it)
(pcb/with-page page)
(pcb/update-page-option :comment-threads-position assoc thread-id (select-keys thread [:position :frame-id])))]
(rx/merge
(rx/of (dwc/commit-changes changes))
(->> (rp/mutation :update-comment-thread-position thread)
(rx/catch #(rx/throw {:type :update-comment-thread-position}))
(rx/ignore))))))))
(defn move-frame-comment-threads
"Move comment threads that are inside a frame when that frame is moved"
[ids]
(us/verify (s/coll-of uuid?) ids)
(ptk/reify ::move-frame-comment-threads
ptk/WatchEvent
(watch [_ state _]
(let [objects (wsh/lookup-page-objects state)
is-frame? (fn [id] (= :frame (get-in objects [id :type])))
frame-ids? (into #{} (filter is-frame?) ids)
object-modifiers (:workspace-modifiers state)
threads-position-map (:comment-threads-position (wsh/lookup-page-options state))
build-move-event
(fn [comment-thread]
(let [frame (get objects (:frame-id comment-thread))
frame' (-> (merge frame (get object-modifiers (:frame-id comment-thread)))
(gsh/transform-shape))
moved (gpt/to-vec (gpt/point (:x frame) (:y frame))
(gpt/point (:x frame') (:y frame')))
position (get-in threads-position-map [(:id comment-thread) :position])
new-x (+ (:x position) (:x moved))
new-y (+ (:y position) (:y moved))]
(update-comment-thread-position comment-thread [new-x new-y] (:id frame))))]
(->> (:comment-threads state)
(vals)
(map #(assoc % :position (get-in threads-position-map [(:id %) :position])))
(map #(assoc % :frame-id (get-in threads-position-map [(:id %) :frame-id])))
(filter (comp frame-ids? :frame-id))
(map build-move-event)
(rx/from))))))

View file

@ -17,6 +17,7 @@
[app.common.types.shape :as spec.shape]
[app.common.types.shape.interactions :as csi]
[app.common.uuid :as uuid]
[app.main.data.comments :as dc]
[app.main.data.workspace.changes :as dch]
[app.main.data.workspace.edition :as dwe]
[app.main.data.workspace.selection :as dws]
@ -237,7 +238,9 @@
(->> (map :id starting-flows)
(reduce csp/remove-flow flows))))))]
(rx/of (dch/commit-changes changes))))))
(rx/of
(dc/detach-comment-thread ids)
(dch/commit-changes changes))))))
(defn- viewport-center
[state]

View file

@ -18,6 +18,7 @@
[app.common.spec :as us]
[app.main.data.workspace.changes :as dch]
[app.main.data.workspace.collapse :as dwc]
[app.main.data.workspace.comments :as dwcm]
[app.main.data.workspace.guides :as dwg]
[app.main.data.workspace.selection :as dws]
[app.main.data.workspace.state-helpers :as wsh]
@ -201,6 +202,7 @@
(rx/of (dwu/start-undo-transaction))
(rx/empty))
(rx/of (dwg/move-frame-guides ids-with-children)
(dwcm/move-frame-comment-threads ids-with-children)
(dch/update-shapes
ids-with-children
(fn [shape]

View file

@ -0,0 +1,148 @@
;; 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/.
;;
;; Copyright (c) UXBOX Labs SL
(ns app.main.data.workspace.viewport
(:require
[app.common.data :as d]
[app.common.geom.align :as gal]
[app.common.geom.point :as gpt]
[app.common.geom.shapes :as gsh]
[app.common.math :as mth]
[app.common.pages.helpers :as cph]
[app.common.spec :as us]
[app.main.data.workspace.state-helpers :as wsh]
[app.main.streams :as ms]
[beicon.core :as rx]
[potok.core :as ptk]))
(defn initialize-viewport
[{:keys [width height] :as size}]
(letfn [(update* [{:keys [vport] :as local}]
(let [wprop (/ (:width vport) width)
hprop (/ (:height vport) height)]
(-> local
(assoc :vport size)
(update :vbox (fn [vbox]
(-> vbox
(update :width #(/ % wprop))
(update :height #(/ % hprop))))))))
(initialize [state local]
(let [page-id (:current-page-id state)
objects (wsh/lookup-page-objects state page-id)
shapes (cph/get-immediate-children objects)
srect (gsh/selection-rect shapes)
local (assoc local :vport size :zoom 1)]
(cond
(or (not (d/num? (:width srect)))
(not (d/num? (:height srect))))
(assoc local :vbox (assoc size :x 0 :y 0))
(or (> (:width srect) width)
(> (:height srect) height))
(let [srect (gal/adjust-to-viewport size srect {:padding 40})
zoom (/ (:width size) (:width srect))]
(-> local
(assoc :zoom zoom)
(update :vbox merge srect)))
:else
(assoc local :vbox (assoc size
:x (+ (:x srect) (/ (- (:width srect) width) 2))
:y (+ (:y srect) (/ (- (:height srect) height) 2)))))))
(setup [state local]
(if (and (:vbox local) (:vport local))
(update* local)
(initialize state local)))]
(ptk/reify ::initialize-viewport
ptk/UpdateEvent
(update [_ state]
(update state :workspace-local
(fn [local]
(setup state local)))))))
(defn update-viewport-position
[{:keys [x y] :or {x identity y identity}}]
(us/assert fn? x)
(us/assert fn? y)
(ptk/reify ::update-viewport-position
ptk/UpdateEvent
(update [_ state]
(update-in state [:workspace-local :vbox]
(fn [vbox]
(-> vbox
(update :x x)
(update :y y)))))))
(defn update-viewport-size
[resize-type {:keys [width height] :as size}]
(ptk/reify ::update-viewport-size
ptk/UpdateEvent
(update [_ state]
(update state :workspace-local
(fn [{:keys [vport] :as local}]
(if (or (nil? vport)
(mth/almost-zero? width)
(mth/almost-zero? height))
;; If we have a resize to zero just keep the old value
local
(let [wprop (/ (:width vport) width)
hprop (/ (:height vport) height)
vbox (:vbox local)
vbox-x (:x vbox)
vbox-y (:y vbox)
vbox-width (:width vbox)
vbox-height (:height vbox)
vbox-width' (/ vbox-width wprop)
vbox-height' (/ vbox-height hprop)
vbox-x'
(case resize-type
:left (+ vbox-x (- vbox-width vbox-width'))
:right vbox-x
(+ vbox-x (/ (- vbox-width vbox-width') 2)))
vbox-y'
(case resize-type
:top (+ vbox-y (- vbox-height vbox-height'))
:bottom vbox-y
(+ vbox-y (/ (- vbox-height vbox-height') 2)))]
(-> local
(assoc :vport size)
(assoc-in [:vbox :x] vbox-x')
(assoc-in [:vbox :y] vbox-y')
(assoc-in [:vbox :width] vbox-width')
(assoc-in [:vbox :height] vbox-height')))))))))
(defn start-panning []
(ptk/reify ::start-panning
ptk/WatchEvent
(watch [_ state stream]
(let [stopper (->> stream (rx/filter (ptk/type? ::finish-panning)))
zoom (-> (get-in state [:workspace-local :zoom]) gpt/point)]
(when-not (get-in state [:workspace-local :panning])
(rx/concat
(rx/of #(-> % (assoc-in [:workspace-local :panning] true)))
(->> stream
(rx/filter ms/pointer-event?)
(rx/filter #(= :delta (:source %)))
(rx/map :pt)
(rx/take-until stopper)
(rx/map (fn [delta]
(let [delta (gpt/divide delta zoom)]
(update-viewport-position {:x #(- % (:x delta))
:y #(- % (:y delta))})))))))))))
(defn finish-panning []
(ptk/reify ::finish-panning
ptk/UpdateEvent
(update [_ state]
(-> state
(update :workspace-local dissoc :panning)))))

View file

@ -9,6 +9,7 @@
[app.config :as cfg]
[app.main.data.comments :as dcm]
[app.main.data.modal :as modal]
[app.main.data.workspace.comments :as dwcm]
[app.main.refs :as refs]
[app.main.store :as st]
[app.main.ui.components.dropdown :refer [dropdown]]
@ -183,7 +184,7 @@
[:input.btn-secondary {:type "button" :value "Cancel" :on-click on-cancel}]]]))
(mf/defc comment-item
[{:keys [comment thread users] :as props}]
[{:keys [comment thread users origin] :as props}]
(let [owner (get users (:owner-id comment))
profile (mf/deref refs/profile)
options (mf/use-state false)
@ -210,7 +211,9 @@
(mf/use-callback
(mf/deps thread)
#(st/emit! (dcm/close-thread)
(dcm/delete-comment-thread thread)))
(if (= origin :viewer)
(dcm/delete-comment-thread-on-viewer thread)
(dcm/delete-comment-thread-on-workspace thread))))
on-delete-thread
@ -278,9 +281,10 @@
(l/derived (l/in [:comments id]) st/state))
(mf/defc thread-comments
[{:keys [thread zoom users]}]
[{:keys [thread zoom users origin]}]
(let [ref (mf/use-ref)
pos (:position thread)
pos-x (+ (* (:x pos) zoom) 14)
pos-y (- (* (:y pos) zoom) 14)
@ -313,33 +317,134 @@
[:div.comments
[:& comment-item {:comment comment
:users users
:thread thread}]
:thread thread
:origin origin}]
(for [item (rest comments)]
[:*
[:hr]
[:& comment-item {:comment item :users users}]])
[:& comment-item {:comment item
:users users
:origin origin}]])
[:div {:ref ref}]]
[:& reply-form {:thread thread}]])))
(defn use-buble
[zoom {:keys [position frame-id]}]
(let [dragging-ref (mf/use-ref false)
start-ref (mf/use-ref nil)
state (mf/use-state {:hover false
:new-position-x nil
:new-position-y nil
:new-frame-id frame-id})
on-pointer-down
(mf/use-callback
(fn [event]
(dom/capture-pointer event)
(mf/set-ref-val! dragging-ref true)
(mf/set-ref-val! start-ref (dom/get-client-position event))))
on-pointer-up
(mf/use-callback
(mf/deps (select-keys @state [:new-position-x :new-position-y :new-frame-id]))
(fn [_ thread]
(when (and
(some? (:new-position-x @state))
(some? (:new-position-y @state)))
(st/emit! (dwcm/update-comment-thread-position thread [(:new-position-x @state) (:new-position-y @state)])))))
on-lost-pointer-capture
(mf/use-callback
(fn [event]
(dom/release-pointer event)
(mf/set-ref-val! dragging-ref false)
(mf/set-ref-val! start-ref nil)
(swap! state assoc :new-position-x nil)
(swap! state assoc :new-position-y nil)))
on-mouse-move
(mf/use-callback
(mf/deps position zoom)
(fn [event]
(when-let [_ (mf/ref-val dragging-ref)]
(let [start-pt (mf/ref-val start-ref)
current-pt (dom/get-client-position event)
delta-x (/ (- (:x current-pt) (:x start-pt)) zoom)
delta-y (/ (- (:y current-pt) (:y start-pt)) zoom)]
(swap! state assoc
:new-position-x (+ (:x position) delta-x)
:new-position-y (+ (:y position) delta-y))))))]
{:on-pointer-down on-pointer-down
:on-pointer-up on-pointer-up
:on-mouse-move on-mouse-move
:on-lost-pointer-capture on-lost-pointer-capture
:state state}))
(mf/defc thread-bubble
{::mf/wrap [mf/memo]}
[{:keys [thread zoom on-click] :as params}]
[{:keys [thread zoom open? on-click origin]}]
(let [pos (:position thread)
pos-x (* (:x pos) zoom)
pos-y (* (:y pos) zoom)
on-click* (fn [event]
(dom/stop-propagation event)
(on-click thread))]
drag? (mf/use-ref nil)
was-open? (mf/use-ref nil)
{:keys [on-pointer-down
on-pointer-up
on-mouse-move
state
on-lost-pointer-capture]} (use-buble zoom thread)
pos-x (* (or (:new-position-x @state) (:x pos)) zoom)
pos-y (* (or (:new-position-y @state) (:y pos)) zoom)
on-pointer-down* (mf/use-callback
(mf/deps origin was-open? open? drag? on-pointer-down)
(fn [event]
(when (not= origin :viewer)
(mf/set-ref-val! was-open? open?)
(when open? (st/emit! (dcm/close-thread)))
(mf/set-ref-val! drag? false)
(dom/stop-propagation event)
(on-pointer-down event))))
on-pointer-up* (mf/use-callback
(mf/deps origin thread was-open? drag? on-pointer-up)
(fn [event]
(when (not= origin :viewer)
(dom/stop-propagation event)
(on-pointer-up event thread)
(when (or (and (mf/ref-val was-open?) (mf/ref-val drag?))
(and (not (mf/ref-val was-open?)) (not (mf/ref-val drag?))))
(st/emit! (dcm/open-thread thread))))))
on-mouse-move* (mf/use-callback
(mf/deps origin drag? on-mouse-move)
(fn [event]
(when (not= origin :viewer)
(mf/set-ref-val! drag? true)
(dom/stop-propagation event)
(on-mouse-move event))))
on-click* (mf/use-callback
(mf/deps origin thread on-click)
(fn [event]
(dom/stop-propagation event)
(when (= origin :viewer)
(on-click thread))))]
[:div.thread-bubble
{:style {:top (str pos-y "px")
:left (str pos-x "px")}
:on-mouse-down (fn [event]
(dom/prevent-default event))
:on-pointer-down on-pointer-down*
:on-pointer-up on-pointer-up*
:on-mouse-move on-mouse-move*
:on-click on-click*
:on-lost-pointer-capture on-lost-pointer-capture
:class (dom/classnames
:resolved (:is-resolved thread)
:unread (pos? (:count-unread-comments thread)))
:on-click on-click*}
:unread (pos? (:count-unread-comments thread)))}
[:span (:seqn thread)]]))
(mf/defc comment-thread

View file

@ -85,6 +85,8 @@
(mf/defc comments-layer
[{:keys [zoom file users frame page] :as props}]
(let [profile (mf/deref refs/profile)
threads-position-ref (l/derived (l/in [:viewer :pages (:id page) :options :comment-threads-position]) st/state)
threads-position-map (mf/deref threads-position-ref)
threads-map (mf/deref threads-ref)
frame-corner (-> frame :points gsh/points->selrect gpt/point)
@ -96,7 +98,16 @@
cstate (mf/deref refs/comments-local)
update-thread-position (fn update-thread-position [thread]
(if (contains? threads-position-map (:id thread))
(-> thread
(assoc :position (get-in threads-position-map [(:id thread) :position]))
(assoc :frame-id (get-in threads-position-map [(:id thread) :frame-id])))
thread))
threads (->> (vals threads-map)
(map update-thread-position)
(filter #(= (:frame-id %) (:id frame)))
(dcm/apply-filters cstate profile)
(filter (fn [{:keys [position]}]
(gsh/has-point? frame position))))
@ -135,8 +146,10 @@
(mf/use-callback
(mf/deps frame)
(fn [draft]
(let [params (update draft :position gpt/transform modifier2)]
(st/emit! (dcm/create-thread params)
(let [params (-> draft
(update :position gpt/transform modifier2)
(assoc :frame-id (:id frame)))]
(st/emit! (dcm/create-thread-on-viewer params)
(dcm/close-thread)))))]
[:div.comments-section {:on-click on-click}
@ -148,7 +161,8 @@
:zoom zoom
:on-click on-bubble-click
:open? (= (:id item) (:open cstate))
:key (:seqn item)}]))
:key (:seqn item)
:origin :viewer}]))
(when-let [id (:open cstate)]
(when-let [thread (as-> (get threads-map id) $

View file

@ -144,7 +144,7 @@
on-pointer-move (actions/on-pointer-move viewport-ref zoom move-stream)
on-pointer-up (actions/on-pointer-up)
on-move-selected (actions/on-move-selected hover hover-ids selected space?)
on-menu-selected (actions/on-menu-selected hover hover-ids selected)
on-menu-selected (actions/on-menu-selected hover hover-ids selected)
on-frame-enter (actions/on-frame-enter frame-hover)
on-frame-leave (actions/on-frame-leave frame-hover)

View file

@ -12,27 +12,32 @@
[app.main.store :as st]
[app.main.ui.comments :as cmt]
[cuerdas.core :as str]
[okulary.core :as l]
[rumext.alpha :as mf]))
(mf/defc comments-layer
[{:keys [vbox vport zoom file-id page-id drawing] :as props}]
(let [pos-x (* (- (:x vbox)) zoom)
pos-y (* (- (:y vbox)) zoom)
(let [pos-x (* (- (:x vbox)) zoom)
pos-y (* (- (:y vbox)) zoom)
profile (mf/deref refs/profile)
users (mf/deref refs/current-file-comments-users)
local (mf/deref refs/comments-local)
threads-map (mf/deref refs/threads-ref)
profile (mf/deref refs/profile)
users (mf/deref refs/current-file-comments-users)
local (mf/deref refs/comments-local)
threads-position-ref (l/derived (l/in [:workspace-data :pages-index page-id :options :comment-threads-position]) st/state)
threads-position-map (mf/deref threads-position-ref)
threads-map (mf/deref refs/threads-ref)
threads (->> (vals threads-map)
(filter #(= (:page-id %) page-id))
(dcm/apply-filters local profile))
update-thread-position (fn update-thread-position [thread]
(if (contains? threads-position-map (:id thread))
(-> thread
(assoc :position (get-in threads-position-map [(:id thread) :position]))
(assoc :frame-id (get-in threads-position-map [(:id thread) :frame-id])))
thread))
on-bubble-click
(fn [{:keys [id] :as thread}]
(if (= (:open local) id)
(st/emit! (dcm/close-thread))
(st/emit! (dcm/open-thread thread))))
threads (->> (vals threads-map)
(filter #(= (:page-id %) page-id))
(mapv update-thread-position)
(dcm/apply-filters local profile))
on-draft-cancel
(mf/use-callback
@ -41,7 +46,7 @@
on-draft-submit
(mf/use-callback
(fn [draft]
(st/emit! (dcm/create-thread draft))))]
(st/emit! (dcm/create-thread-on-workspace draft))))]
(mf/use-effect
(mf/deps file-id)
@ -58,13 +63,12 @@
(for [item threads]
[:& cmt/thread-bubble {:thread item
:zoom zoom
:on-click on-bubble-click
:open? (= (:id item) (:open local))
:key (:seqn item)}])
(when-let [id (:open local)]
(when-let [thread (get threads-map id)]
[:& cmt/thread-comments {:thread thread
[:& cmt/thread-comments {:thread (update-thread-position thread)
:users users
:zoom zoom}]))
@ -73,5 +77,3 @@
:on-cancel on-draft-cancel
:on-submit on-draft-submit
:zoom zoom}])]]]))