0
Fork 0
mirror of https://github.com/penpot/penpot.git synced 2025-03-14 16:51:18 -05:00

Merge remote-tracking branch 'origin/staging' into develop

This commit is contained in:
Andrey Antukh 2022-08-01 14:05:04 +02:00
commit f25222e441
28 changed files with 641 additions and 249 deletions

View file

@ -48,6 +48,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

@ -47,11 +47,11 @@
(px/with-dispatch executor
(let [profile-id (:profile-id data)
user-agent (:user-agent data)
token (tokens :generate {:iss "authentication"
:iat (dt/now)
:uid profile-id})
now (dt/now)
token (tokens :generate {:iss "authentication"
:iat now
:uid profile-id})
params {:user-agent user-agent
:profile-id profile-id
:created-at now

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

@ -42,7 +42,7 @@
{:extra-deps
{org.clojure/tools.namespace {:mvn/version "RELEASE"}
org.clojure/test.check {:mvn/version "RELEASE"}
thheller/shadow-cljs {:mvn/version "2.19.5"}
thheller/shadow-cljs {:mvn/version "2.19.8"}
com.bhauman/rebel-readline {:mvn/version "RELEASE"}
criterium/criterium {:mvn/version "RELEASE"}
mockery/mockery {:mvn/version "RELEASE"}}

View file

@ -13,7 +13,7 @@
"test": "yarn run compile-test && yarn run run-test"
},
"devDependencies": {
"shadow-cljs": "2.19.5",
"shadow-cljs": "2.19.8",
"source-map-support": "^0.5.19",
"ws": "^7.4.6"
}

View file

@ -107,11 +107,13 @@
(s/def ::number (s/conformer number-conformer str))
(s/def ::integer (s/conformer integer-conformer str))
(s/def ::not-empty-string (s/and string? #(not (str/empty? %))))
(s/def ::set-of-string (s/every string? :kind set?))
(s/def ::url string?)
(s/def ::fn fn?)
(s/def ::id ::uuid)
(s/def ::set-of-string (s/every string? :kind set?))
(s/def ::coll-of-uuid (s/every uuid?))
(defn bytes?
"Test if a first parameter is a byte
array or not."

View file

@ -533,10 +533,10 @@ shadow-cljs-jar@1.3.2:
resolved "https://registry.yarnpkg.com/shadow-cljs-jar/-/shadow-cljs-jar-1.3.2.tgz#97273afe1747b6a2311917c1c88d9e243c81957b"
integrity sha512-XmeffAZHv8z7451kzeq9oKh8fh278Ak+UIOGGrapyqrFBB773xN8vMQ3O7J7TYLnb9BUwcqadKkmgaq7q6fhZg==
shadow-cljs@2.19.3:
version "2.19.3"
resolved "https://registry.yarnpkg.com/shadow-cljs/-/shadow-cljs-2.19.3.tgz#115a33917f8bca1495e0f815dca7ec3957f669af"
integrity sha512-9TsTCRlmR8m1g2ekwblgomRUgJpbifQI99VlRrlH9NMqEzklev3zYAD1dvy4d5h8BoAhgdxOOEg7ld2d45CWTA==
shadow-cljs@2.19.8:
version "2.19.8"
resolved "https://registry.yarnpkg.com/shadow-cljs/-/shadow-cljs-2.19.8.tgz#1ce96cab3e4903bed8d401ffbe88b8939f5454d3"
integrity sha512-6qek3mcAP0hrnC5FxrTebBrgLGpOuhlnp06vdxp6g0M5Gl6w2Y0hzSwa1s2K8fMOkzE4/ciQor75b2y64INgaw==
dependencies:
node-libs-browser "^2.2.1"
readline-sync "^1.4.7"

View file

@ -15,7 +15,7 @@
:dev
{:extra-deps
{thheller/shadow-cljs {:mvn/version "2.19.3"}}}
{thheller/shadow-cljs {:mvn/version "2.19.8"}}}
:shadow-cljs
{:main-opts ["-m" "shadow.cljs.devtools.cli"]}

View file

@ -21,7 +21,7 @@
"xregexp": "^5.0.2"
},
"devDependencies": {
"shadow-cljs": "^2.19.3",
"shadow-cljs": "^2.19.8",
"source-map-support": "^0.5.21"
}
}

View file

@ -1098,10 +1098,10 @@ shadow-cljs-jar@1.3.2:
resolved "https://registry.yarnpkg.com/shadow-cljs-jar/-/shadow-cljs-jar-1.3.2.tgz#97273afe1747b6a2311917c1c88d9e243c81957b"
integrity sha512-XmeffAZHv8z7451kzeq9oKh8fh278Ak+UIOGGrapyqrFBB773xN8vMQ3O7J7TYLnb9BUwcqadKkmgaq7q6fhZg==
shadow-cljs@^2.19.3:
version "2.19.3"
resolved "https://registry.yarnpkg.com/shadow-cljs/-/shadow-cljs-2.19.3.tgz#115a33917f8bca1495e0f815dca7ec3957f669af"
integrity sha512-9TsTCRlmR8m1g2ekwblgomRUgJpbifQI99VlRrlH9NMqEzklev3zYAD1dvy4d5h8BoAhgdxOOEg7ld2d45CWTA==
shadow-cljs@^2.19.8:
version "2.19.8"
resolved "https://registry.yarnpkg.com/shadow-cljs/-/shadow-cljs-2.19.8.tgz#1ce96cab3e4903bed8d401ffbe88b8939f5454d3"
integrity sha512-6qek3mcAP0hrnC5FxrTebBrgLGpOuhlnp06vdxp6g0M5Gl6w2Y0hzSwa1s2K8fMOkzE4/ciQor75b2y64INgaw==
dependencies:
node-libs-browser "^2.2.1"
readline-sync "^1.4.7"

View file

@ -32,7 +32,7 @@
:dev
{:extra-paths ["dev"]
:extra-deps
{thheller/shadow-cljs {:mvn/version "2.19.5"}
{thheller/shadow-cljs {:mvn/version "2.19.8"}
org.clojure/tools.namespace {:mvn/version "RELEASE"}
cider/cider-nrepl {:mvn/version "0.28.4"}}}

View file

@ -48,7 +48,7 @@
"prettier": "^2.7.1",
"rimraf": "^3.0.0",
"sass": "^1.53.0",
"shadow-cljs": "2.19.5"
"shadow-cljs": "2.19.8"
},
"dependencies": {
"@sentry/browser": "^6.17.4",

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.types.shape-tree :as ctst]
[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 (ctst/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.changes-builder :as pcb]
[app.common.pages.helpers :as cph]
[app.common.spec :as us]
@ -52,6 +51,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]
@ -399,140 +399,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
@ -1769,3 +1635,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.types.shape-tree :as ctst]
[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)
(ctst/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))))))))
;; Move comment threads that are inside a frame when that frame is moved"
(defmethod ptk/resolve ::move-frame-comment-threads
[_ ids]
(us/assert! :spec (s/coll-of uuid?) :val 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

@ -63,11 +63,10 @@
guides (-> (select-keys guides ids) (vals))]
(rx/from (->> guides (mapv #(remove-guide %))))))))
(defn move-frame-guides
"Move guides that are inside a frame when that frame is moved"
[ids]
(us/verify (s/coll-of uuid?) ids)
(defmethod ptk/resolve ::move-frame-guides
[_ ids]
(us/assert! ::us/coll-of-uuid ids)
(ptk/reify ::move-frame-guides
ptk/WatchEvent
(watch [_ state _]
@ -88,11 +87,11 @@
(gpt/point (:x frame') (:y frame')))
guide (update guide :position + (get moved (:axis guide)))]
(update-guides guide)))]
(update-guides guide)))
(->> (wsh/lookup-page-options state)
:guides
(vals)
guides (-> state wsh/lookup-page-options :guides vals)]
(->> guides
(filter (comp frame-ids? :frame-id))
(map build-move-event)
(rx/from))))))

View file

@ -17,6 +17,7 @@
[app.common.types.shape-tree :as ctst]
[app.common.types.shape.interactions :as ctsi]
[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]
@ -261,8 +262,9 @@
changes
components-to-delete)]
(rx/of (dch/commit-changes changes)
(dwsl/update-layout-positions all-parents))))))
(rx/of (dc/detach-comment-thread ids)
(dwsl/update-layout-positions all-parents)
(dch/commit-changes changes))))))
(defn- viewport-center
[state]

View file

@ -19,7 +19,8 @@
[app.common.types.shape-tree :as ctt]
[app.main.data.workspace.changes :as dch]
[app.main.data.workspace.collapse :as dwc]
[app.main.data.workspace.guides :as dwg]
[app.main.data.workspace.comments :as-alias dwcm]
[app.main.data.workspace.guides :as-alias dwg]
[app.main.data.workspace.selection :as dws]
[app.main.data.workspace.state-helpers :as wsh]
[app.main.data.workspace.undo :as dwu]
@ -203,7 +204,8 @@
(if undo-transation?
(rx/of (dwu/start-undo-transaction))
(rx/empty))
(rx/of (dwg/move-frame-guides ids-with-children)
(rx/of (ptk/event ::dwg/move-frame-guides ids-with-children)
(ptk/event ::dwcm/move-frame-comment-threads ids-with-children)
(dch/update-shapes
ids
(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

@ -6,16 +6,24 @@
(ns app.main.store
(:require
[app.common.logging :as log]
[app.util.object :as obj]
[beicon.core :as rx]
[okulary.core :as l]
[potok.core :as ptk]))
(log/set-level! :info)
(enable-console-print!)
(defonce loader (l/atom false))
(defonce on-error (l/atom identity))
(defmethod ptk/resolve :default
[type data]
(log/warn :hint "no implementation found for event" :event type)
(ptk/data-event type data))
(defonce state
(ptk/store {:resolve ptk/resolve
:on-error (fn [e] (@on-error e))}))

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

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

View file

@ -4633,9 +4633,9 @@ safe-stable-stringify@^2.3.1:
integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==
sass@^1.53.0:
version "1.53.0"
resolved "https://registry.yarnpkg.com/sass/-/sass-1.53.0.tgz#eab73a7baac045cc57ddc1d1ff501ad2659952eb"
integrity sha512-zb/oMirbKhUgRQ0/GFz8TSAwRq2IlR29vOUJZOx0l8sV+CkHUfHa4u5nqrG+1VceZp7Jfj59SVW9ogdhTvJDcQ==
version "1.54.0"
resolved "https://registry.yarnpkg.com/sass/-/sass-1.54.0.tgz#24873673265e2a4fe3d3a997f714971db2fba1f4"
integrity sha512-C4zp79GCXZfK0yoHZg+GxF818/aclhp9F48XBu/+bm9vXEVAYov9iU3FBVRMq3Hx3OA4jfKL+p2K9180mEh0xQ==
dependencies:
chokidar ">=3.0.0 <4.0.0"
immutable "^4.0.0"
@ -4711,10 +4711,10 @@ shadow-cljs-jar@1.3.2:
resolved "https://registry.yarnpkg.com/shadow-cljs-jar/-/shadow-cljs-jar-1.3.2.tgz#97273afe1747b6a2311917c1c88d9e243c81957b"
integrity sha512-XmeffAZHv8z7451kzeq9oKh8fh278Ak+UIOGGrapyqrFBB773xN8vMQ3O7J7TYLnb9BUwcqadKkmgaq7q6fhZg==
shadow-cljs@2.19.5:
version "2.19.5"
resolved "https://registry.yarnpkg.com/shadow-cljs/-/shadow-cljs-2.19.5.tgz#e51c758d2f942db18e6e4015bcacf1857ad1e751"
integrity sha512-uZelOtmTYg4MOZP1ehJilhQcGDxkdybPKkGZ11qxp8awmfgAQMe+W/QEyZw4aVwFxVXyHIIerzCGkCqAgo/FuA==
shadow-cljs@2.19.8:
version "2.19.8"
resolved "https://registry.yarnpkg.com/shadow-cljs/-/shadow-cljs-2.19.8.tgz#1ce96cab3e4903bed8d401ffbe88b8939f5454d3"
integrity sha512-6qek3mcAP0hrnC5FxrTebBrgLGpOuhlnp06vdxp6g0M5Gl6w2Y0hzSwa1s2K8fMOkzE4/ciQor75b2y64INgaw==
dependencies:
node-libs-browser "^2.2.1"
readline-sync "^1.4.7"