mirror of
https://github.com/penpot/penpot.git
synced 2026-01-05 21:08:56 -05:00
Compare commits
60 Commits
1.7.0-alph
...
1.7.2-alph
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3435684c87 | ||
|
|
bed702d8de | ||
|
|
e4f755416d | ||
|
|
4d5b0731be | ||
|
|
fde6ea1c83 | ||
|
|
7a94a2f087 | ||
|
|
97b8f742dd | ||
|
|
06733ea7cd | ||
|
|
efa5120fac | ||
|
|
80ab6bbda2 | ||
|
|
53620b9f1b | ||
|
|
259b405526 | ||
|
|
c6fe19c321 | ||
|
|
9d545004cb | ||
|
|
7fe419ecb0 | ||
|
|
55ddf9cc38 | ||
|
|
38292bcda7 | ||
|
|
08062e8ce8 | ||
|
|
bff35de39f | ||
|
|
394e6b08ad | ||
|
|
d61a86cad1 | ||
|
|
43198eb263 | ||
|
|
8493e51070 | ||
|
|
07eeb76a5f | ||
|
|
6ee6a03e4a | ||
|
|
8e3eb98789 | ||
|
|
c5b23816e9 | ||
|
|
0a3cd4f8e4 | ||
|
|
7882dead81 | ||
|
|
44f96dd6a3 | ||
|
|
a442afd8d2 | ||
|
|
bdbc57b926 | ||
|
|
9ed53ba064 | ||
|
|
9d372301ed | ||
|
|
b483513fa8 | ||
|
|
578c561473 | ||
|
|
f6134a6bd3 | ||
|
|
2758b6ffd9 | ||
|
|
fa99dea8fe | ||
|
|
6ced56301c | ||
|
|
008134fde8 | ||
|
|
3ed593e4b6 | ||
|
|
1fc5182979 | ||
|
|
9ebafddac2 | ||
|
|
26467187c4 | ||
|
|
69e256ab86 | ||
|
|
b4b12e68bf | ||
|
|
768216d9bc | ||
|
|
f29d54ad0d | ||
|
|
946309a485 | ||
|
|
7c98336148 | ||
|
|
455b0efa71 | ||
|
|
9ddcb036cf | ||
|
|
185e06ed79 | ||
|
|
17ae6bf89d | ||
|
|
7efc1a0366 | ||
|
|
899dc5b680 | ||
|
|
5126c85623 | ||
|
|
9ec23ceed6 | ||
|
|
23e4915d60 |
40
CHANGES.md
40
CHANGES.md
@@ -1,15 +1,53 @@
|
||||
# CHANGELOG #
|
||||
|
||||
|
||||
|
||||
|
||||
## :rocket: Next
|
||||
|
||||
### :sparkles: New features
|
||||
|
||||
### :bug: Bugs fixed
|
||||
|
||||
### :arrow_up: Deps updates
|
||||
### :boom: Breaking changes
|
||||
### :heart: Community contributions by (Thank you!)
|
||||
|
||||
|
||||
## 1.7.2-alpha
|
||||
|
||||
### :sparkles: New features
|
||||
|
||||
- Add many improvements to text tool.
|
||||
|
||||
### :bug: Bugs fixed
|
||||
|
||||
- Add scroll bar to Teams menu [Taiga #1894](https://tree.taiga.io/project/penpot/issue/1894).
|
||||
- Fix repeated names when duplicating artboards or groups [Taiga #1892](https://tree.taiga.io/project/penpot/issue/1892).
|
||||
- Fix properly messages lifecycle on navigate.
|
||||
- Fix handling repeated names on duplicate object trees.
|
||||
- Fix group naming on group creation.
|
||||
- Fix some issues in svg transformation.
|
||||
|
||||
### :arrow_up: Deps updates
|
||||
|
||||
- Update frontend build tooling.
|
||||
|
||||
### :boom: Breaking changes
|
||||
### :heart: Community contributions by (Thank you!)
|
||||
|
||||
- soultipsy [#1100](https://github.com/penpot/penpot/pull/1100)
|
||||
|
||||
|
||||
## 1.7.1-alpha
|
||||
|
||||
### :bug: Bugs fixed
|
||||
|
||||
- Fix issue related to the GC and images in path shapes.
|
||||
- Fix issue on the shape order on some undo operations.
|
||||
- Fix issue on undo page deletion.
|
||||
- Fix some issues related to constraints.
|
||||
|
||||
|
||||
## 1.7.0-alpha
|
||||
|
||||
### :sparkles: New features
|
||||
|
||||
@@ -58,7 +58,7 @@
|
||||
:srepl-host "127.0.0.1"
|
||||
:srepl-port 6062
|
||||
|
||||
:assets-storage-backend :fs
|
||||
:assets-storage-backend :assets-fs
|
||||
:storage-assets-fs-directory "assets"
|
||||
|
||||
:feedback-destination "info@example.com"
|
||||
|
||||
@@ -136,7 +136,9 @@
|
||||
["/webhooks"
|
||||
["/sns" {:post (:sns-webhook cfg)}]]
|
||||
|
||||
["/api" {:middleware [[middleware/etag]
|
||||
["/api" {:middleware [
|
||||
;; Temporary disabled
|
||||
#_[middleware/etag]
|
||||
[middleware/format-response-body]
|
||||
[middleware/params]
|
||||
[middleware/multipart-params]
|
||||
|
||||
@@ -85,17 +85,22 @@
|
||||
(.close ^java.io.OutputStream output-stream))))))
|
||||
|
||||
(defn- impl-format-response-body
|
||||
[response request]
|
||||
[response _request]
|
||||
(let [body (:body response)
|
||||
opts {:type :json-verbose}]
|
||||
(cond
|
||||
(coll? body)
|
||||
(-> response
|
||||
(update :headers assoc "content-type" "application/transit+json")
|
||||
(assoc :body
|
||||
(if (= :post (:request-method request))
|
||||
(transit-streamable-body body opts)
|
||||
(t/encode body opts))))
|
||||
(assoc :body (transit-streamable-body body opts)))
|
||||
|
||||
;; ;; Temporary disabled
|
||||
;; (-> response
|
||||
;; (update :headers assoc "content-type" "application/transit+json")
|
||||
;; (assoc :body
|
||||
;; (if (= :post (:request-method request))
|
||||
;; (transit-streamable-body body opts)
|
||||
;; (t/encode body opts))))
|
||||
|
||||
(nil? body)
|
||||
(assoc response :status 204 :body "")
|
||||
|
||||
@@ -60,18 +60,15 @@
|
||||
(a/close! output)))
|
||||
|
||||
(defn- send-mattermost-notification!
|
||||
[cfg {:keys [host version id] :as cdata}]
|
||||
[cfg {:keys [host id] :as cdata}]
|
||||
(try
|
||||
(let [uri (:uri cfg)
|
||||
text (str "Unhandled exception:\n"
|
||||
"- detail: " (cfg/get :public-uri) "/dbg/error-by-id/" id "\n"
|
||||
"- profile-id: `" (:profile-id cdata) "`\n"
|
||||
"- host: `" host "`\n"
|
||||
"- version: `" version "`\n")
|
||||
rsp (http/send! {:uri uri
|
||||
:method :post
|
||||
:headers {"content-type" "application/json"}
|
||||
:body (json/encode-str {:text text})})]
|
||||
text (str "Unhandled exception (host: " host ", url: " (cfg/get :public-uri) "/dbg/error-by-id/" id "\n"
|
||||
"- profile-id: #" (:profile-id cdata) "\n")
|
||||
rsp (http/send! {:uri uri
|
||||
:method :post
|
||||
:headers {"content-type" "application/json"}
|
||||
:body (json/encode-str {:text text})})]
|
||||
(when (not= (:status rsp) 200)
|
||||
(l/error :hint "error on sending data to mattermost"
|
||||
:response (pr-str rsp))))
|
||||
|
||||
@@ -322,15 +322,17 @@
|
||||
:app.storage/storage
|
||||
{:pool (ig/ref :app.db/pool)
|
||||
:executor (ig/ref :app.worker/executor)
|
||||
:backend (cf/get :assets-storage-backend :assets-fs)
|
||||
:backends {:assets-s3 (ig/ref [::assets :app.storage.s3/backend])
|
||||
|
||||
:backends {
|
||||
:assets-s3 (ig/ref [::assets :app.storage.s3/backend])
|
||||
:assets-db (ig/ref [::assets :app.storage.db/backend])
|
||||
:assets-fs (ig/ref [::assets :app.storage.fs/backend])
|
||||
:s3 (ig/ref [::assets :app.storage.s3/backend])
|
||||
:db (ig/ref [::assets :app.storage.db/backend])
|
||||
:fs (ig/ref [::assets :app.storage.fs/backend])
|
||||
:tmp (ig/ref [::tmp :app.storage.fs/backend])
|
||||
:fdata-s3 (ig/ref [::fdata :app.storage.s3/backend])}}
|
||||
:fdata-s3 (ig/ref [::fdata :app.storage.s3/backend])
|
||||
|
||||
;; keep this for backward compatibility
|
||||
:s3 (ig/ref [::assets :app.storage.s3/backend])
|
||||
:fs (ig/ref [::assets :app.storage.fs/backend])}}
|
||||
|
||||
[::fdata :app.storage.s3/backend]
|
||||
{:region (cf/get :storage-fdata-s3-region)
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
[app.common.exceptions :as ex]
|
||||
[app.common.media :as cm]
|
||||
[app.common.spec :as us]
|
||||
[app.config :as cf]
|
||||
[app.rlimits :as rlm]
|
||||
[app.rpc.queries.svg :as svg]
|
||||
[buddy.core.bytes :as bb]
|
||||
@@ -28,10 +29,6 @@
|
||||
org.im4java.core.IMOperation
|
||||
org.im4java.core.Info))
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;; --- Utility functions
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
||||
(s/def ::image-content-type cm/valid-image-types)
|
||||
(s/def ::font-content-type cm/valid-font-types)
|
||||
|
||||
@@ -330,3 +327,17 @@
|
||||
(= stype :ttf)
|
||||
(-> (assoc "font/otf" (ttf->otf sfnt))
|
||||
(assoc "font/ttf" sfnt)))))))))
|
||||
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;; Utility functions
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
||||
(defn configure-assets-storage
|
||||
"Given storage map, returns a storage configured with the apropriate
|
||||
backend for assets."
|
||||
[storage conn]
|
||||
(-> storage
|
||||
(assoc :conn conn)
|
||||
(assoc :backend (cf/get :assets-storage-backend :assets-fs))))
|
||||
|
||||
|
||||
@@ -11,7 +11,6 @@
|
||||
[app.common.pages.migrations :as pmg]
|
||||
[app.common.spec :as us]
|
||||
[app.common.uuid :as uuid]
|
||||
[app.config :as cf]
|
||||
[app.db :as db]
|
||||
[app.rpc.permissions :as perms]
|
||||
[app.rpc.queries.files :as files]
|
||||
@@ -288,7 +287,7 @@
|
||||
|
||||
(defn- delete-from-storage
|
||||
[{:keys [storage] :as cfg} file]
|
||||
(when-let [backend (simpl/resolve-backend storage (cf/get :fdata-storage-backend))]
|
||||
(when-let [backend (simpl/resolve-backend storage (:data-backend file))]
|
||||
(simpl/del-object backend file)))
|
||||
|
||||
(defn- update-file
|
||||
|
||||
@@ -47,7 +47,8 @@
|
||||
(defn create-font-variant
|
||||
[{:keys [conn storage] :as cfg} {:keys [data] :as params}]
|
||||
(let [data (media/run cfg {:cmd :generate-fonts :input data :rlimit :font})
|
||||
storage (assoc storage :conn conn)
|
||||
storage (media/configure-assets-storage storage conn)
|
||||
|
||||
otf (when-let [fdata (get data "font/otf")]
|
||||
(sto/put-object storage {:content (sto/content fdata)
|
||||
:content-type "font/otf"}))
|
||||
|
||||
@@ -32,7 +32,6 @@
|
||||
(s/def ::file-id ::us/uuid)
|
||||
(s/def ::team-id ::us/uuid)
|
||||
|
||||
|
||||
;; --- Create File Media object (upload)
|
||||
|
||||
(declare create-file-media-object)
|
||||
@@ -94,10 +93,9 @@
|
||||
(defn create-file-media-object
|
||||
[{:keys [conn storage] :as cfg} {:keys [id file-id is-local name content] :as params}]
|
||||
(media/validate-media-type (:content-type content))
|
||||
(let [storage (assoc storage :conn conn)
|
||||
(let [storage (media/configure-assets-storage storage conn)
|
||||
source-path (fs/path (:tempfile content))
|
||||
source-mtype (:content-type content)
|
||||
|
||||
source-info (media/run cfg {:cmd :info :input {:path source-path :mtype source-mtype}})
|
||||
|
||||
thumb (when (and (not (svg-image? source-info))
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
[app.common.exceptions :as ex]
|
||||
[app.common.spec :as us]
|
||||
[app.common.uuid :as uuid]
|
||||
[app.config :as cfg]
|
||||
[app.config :as cf]
|
||||
[app.db :as db]
|
||||
[app.emails :as eml]
|
||||
[app.http.oauth :refer [extract-props]]
|
||||
@@ -99,11 +99,11 @@
|
||||
|
||||
(sv/defmethod ::prepare-register-profile {:auth false}
|
||||
[{:keys [pool tokens] :as cfg} params]
|
||||
(when-not (cfg/get :registration-enabled)
|
||||
(when-not (cf/get :registration-enabled)
|
||||
(ex/raise :type :restriction
|
||||
:code :registration-disabled))
|
||||
|
||||
(when-let [domains (cfg/get :registration-domain-whitelist)]
|
||||
(when-let [domains (cf/get :registration-domain-whitelist)]
|
||||
(when-not (email-domain-in-whitelist? domains (:email params))
|
||||
(ex/raise :type :validation
|
||||
:code :email-domain-is-not-allowed)))
|
||||
@@ -402,6 +402,7 @@
|
||||
{:password (derive-password password)}
|
||||
{:id id}))
|
||||
|
||||
|
||||
;; --- MUTATION: Update Photo
|
||||
|
||||
(declare update-profile-photo)
|
||||
@@ -416,11 +417,13 @@
|
||||
[{:keys [pool storage] :as cfg} {:keys [profile-id file] :as params}]
|
||||
(db/with-atomic [conn pool]
|
||||
(media/validate-media-type (:content-type file) #{"image/jpeg" "image/png" "image/webp"})
|
||||
(media/run cfg {:cmd :info :input {:path (:tempfile file)
|
||||
:mtype (:content-type file)}})
|
||||
|
||||
(let [profile (db/get-by-id conn :profile profile-id)
|
||||
_ (media/run cfg {:cmd :info :input {:path (:tempfile file)
|
||||
:mtype (:content-type file)}})
|
||||
photo (teams/upload-photo cfg params)
|
||||
storage (assoc storage :conn conn)]
|
||||
storage (media/configure-assets-storage storage conn)
|
||||
cfg (assoc cfg :storage storage)
|
||||
photo (teams/upload-photo cfg params)]
|
||||
|
||||
;; Schedule deletion of old photo
|
||||
(when-let [id (:photo-id profile)]
|
||||
@@ -453,7 +456,7 @@
|
||||
params (assoc params
|
||||
:profile profile
|
||||
:email (str/lower email))]
|
||||
(if (cfg/get :smtp-enabled)
|
||||
(if (cf/get :smtp-enabled)
|
||||
(request-email-change cfg params)
|
||||
(change-email-inmediatelly cfg params)))))
|
||||
|
||||
|
||||
@@ -251,10 +251,12 @@
|
||||
(db/with-atomic [conn pool]
|
||||
(teams/check-edition-permissions! conn profile-id team-id)
|
||||
(media/validate-media-type (:content-type file) #{"image/jpeg" "image/png" "image/webp"})
|
||||
(media/run cfg {:cmd :info :input {:path (:tempfile file)
|
||||
:mtype (:content-type file)}})
|
||||
|
||||
(let [team (teams/retrieve-team conn profile-id team-id)
|
||||
_ (media/run cfg {:cmd :info :input {:path (:tempfile file)
|
||||
:mtype (:content-type file)}})
|
||||
storage (media/configure-assets-storage storage conn)
|
||||
cfg (assoc cfg :storage storage)
|
||||
photo (upload-photo cfg params)]
|
||||
|
||||
;; Schedule deletion of old photo
|
||||
@@ -263,8 +265,8 @@
|
||||
|
||||
;; Save new photo
|
||||
(db/update! conn :team
|
||||
{:photo-id (:id photo)}
|
||||
{:id team-id})
|
||||
{:photo-id (:id photo)}
|
||||
{:id team-id})
|
||||
|
||||
(assoc team :photo-id (:id photo)))))
|
||||
|
||||
|
||||
@@ -9,7 +9,6 @@
|
||||
[app.common.pages.migrations :as pmg]
|
||||
[app.common.spec :as us]
|
||||
[app.common.uuid :as uuid]
|
||||
[app.config :as cf]
|
||||
[app.db :as db]
|
||||
[app.rpc.permissions :as perms]
|
||||
[app.rpc.queries.projects :as projects]
|
||||
@@ -175,7 +174,7 @@
|
||||
|
||||
(defn- retrieve-data*
|
||||
[{:keys [storage] :as cfg} file]
|
||||
(when-let [backend (simpl/resolve-backend storage (cf/get :fdata-storage-backend))]
|
||||
(when-let [backend (simpl/resolve-backend storage (:data-backend file))]
|
||||
(simpl/get-object-bytes backend file)))
|
||||
|
||||
(defn retrieve-data
|
||||
|
||||
@@ -28,8 +28,6 @@
|
||||
;; Storage Module State
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
||||
(s/def ::backend ::us/keyword)
|
||||
|
||||
(s/def ::s3 ::ss3/backend)
|
||||
(s/def ::fs ::sfs/backend)
|
||||
(s/def ::db ::sdb/backend)
|
||||
@@ -42,7 +40,7 @@
|
||||
:db ::sdb/backend))))
|
||||
|
||||
(defmethod ig/pre-init-spec ::storage [_]
|
||||
(s/keys :req-un [::backend ::wrk/executor ::db/pool ::backends]))
|
||||
(s/keys :req-un [::wrk/executor ::db/pool ::backends]))
|
||||
|
||||
(defmethod ig/prep-key ::storage
|
||||
[_ {:keys [backends] :as cfg}]
|
||||
@@ -55,7 +53,7 @@
|
||||
(assoc :backends (d/without-nils backends))))
|
||||
|
||||
(s/def ::storage
|
||||
(s/keys :req-un [::backends ::wrk/executor ::db/pool ::backend]))
|
||||
(s/keys :req-un [::backends ::wrk/executor ::db/pool]))
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;; Database Objects
|
||||
|
||||
@@ -64,16 +64,21 @@
|
||||
(comp
|
||||
(map :objects)
|
||||
(mapcat vals)
|
||||
(filter #(= :image (:type %)))
|
||||
(map :metadata)
|
||||
(map :id)))
|
||||
(map (fn [{:keys [type] :as obj}]
|
||||
(case type
|
||||
:path (get-in obj [:fill-image :id])
|
||||
:image (get-in obj [:metadata :id])
|
||||
nil)))
|
||||
(filter uuid?)))
|
||||
|
||||
(defn- collect-used-media
|
||||
[data]
|
||||
(let [pages (concat
|
||||
(vals (:pages-index data))
|
||||
(vals (:components data)))]
|
||||
(-> #{}
|
||||
(into collect-media-xf (vals (:pages-index data)))
|
||||
(into collect-media-xf (vals (:components data)))
|
||||
(into (keys (:media data)))))
|
||||
(into collect-media-xf pages)
|
||||
(into (keys (:media data))))))
|
||||
|
||||
(defn- process-file
|
||||
[{:keys [conn] :as cfg} {:keys [id data age] :as file}]
|
||||
|
||||
@@ -597,10 +597,8 @@
|
||||
(assoc :resize-origin (:resize-origin parent-modifiers)
|
||||
:resize-vector (gpt/point (:x (:resize-vector parent-modifiers)) 1))
|
||||
|
||||
(and (:resize-vector-2 parent-modifiers)
|
||||
(not (mth/close? (:x (:resize-vector-2 parent-modifiers)) 1)))
|
||||
(assoc :resize-origin-2 (:resize-origin-2 parent-modifiers)
|
||||
:resize-vector-2 (gpt/point (:x (:resize-vector-2 parent-modifiers)) 1))
|
||||
;; resize-vector-2 is always for vertical modifiers, so no need to
|
||||
;; check it here.
|
||||
|
||||
(:displacement parent-modifiers)
|
||||
(assoc :displacement
|
||||
@@ -654,10 +652,12 @@
|
||||
(assoc :resize-origin (:resize-origin parent-modifiers)
|
||||
:resize-vector (gpt/point 1 (:y (:resize-vector parent-modifiers))))
|
||||
|
||||
;; If there is a resize-vector-2, this means that we come from a recursive
|
||||
;; call, and the resize-vector has no vertical data, so we may override it.
|
||||
(and (:resize-vector-2 parent-modifiers)
|
||||
(not (mth/close? (:y (:resize-vector-2 parent-modifiers)) 1)))
|
||||
(assoc :resize-origin-2 (:resize-origin-2 parent-modifiers)
|
||||
:resize-vector-2 (gpt/point 1 (:y (:resize-vector-2 parent-modifiers))))
|
||||
(assoc :resize-origin (:resize-origin-2 parent-modifiers)
|
||||
:resize-vector (gpt/point 1 (:y (:resize-vector-2 parent-modifiers))))
|
||||
|
||||
(:displacement parent-modifiers)
|
||||
(assoc :displacement
|
||||
|
||||
@@ -15,6 +15,20 @@
|
||||
[app.common.pages.spec :as spec]
|
||||
[app.common.spec :as us]))
|
||||
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;; Specific helpers
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
||||
(defn- without-obj
|
||||
"Clear collection from specified obj and without nil values."
|
||||
[coll o]
|
||||
(into [] (filter #(not= % o)) coll))
|
||||
|
||||
(defn vec-without-nils
|
||||
[coll]
|
||||
(into [] (remove nil?) coll))
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;; Page Transformation Changes
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
@@ -54,45 +68,50 @@
|
||||
(assoc data :options (d/dissoc-in (:options data) path)))))))
|
||||
|
||||
(defmethod process-change :add-obj
|
||||
[data {:keys [id obj page-id component-id frame-id parent-id
|
||||
index ignore-touched]}]
|
||||
(letfn [(update-fn [data]
|
||||
(let [parent-id (or parent-id frame-id)
|
||||
objects (:objects data)
|
||||
obj (assoc obj
|
||||
:frame-id frame-id
|
||||
:parent-id parent-id
|
||||
:id id)]
|
||||
(if (and (or (nil? parent-id) (contains? objects parent-id))
|
||||
(or (nil? frame-id) (contains? objects frame-id)))
|
||||
(-> data
|
||||
(update :objects assoc id obj)
|
||||
(update-in [:objects parent-id :shapes]
|
||||
(fn [shapes]
|
||||
(let [shapes (or shapes [])]
|
||||
(cond
|
||||
(some #{id} shapes)
|
||||
shapes
|
||||
[data {:keys [id obj page-id component-id frame-id parent-id index ignore-touched]}]
|
||||
(letfn [(update-parent-shapes [shapes]
|
||||
;; Ensure that shapes is always a vector.
|
||||
(let [shapes (into [] shapes)]
|
||||
(cond
|
||||
(some #{id} shapes)
|
||||
shapes
|
||||
|
||||
(nil? index)
|
||||
(if (= :frame (:type obj))
|
||||
(d/concat [id] shapes)
|
||||
(conj shapes id))
|
||||
(nil? index)
|
||||
(if (= :frame (:type obj))
|
||||
(into [id] shapes)
|
||||
(conj shapes id))
|
||||
|
||||
:else
|
||||
(cph/insert-at-index shapes index [id])))))
|
||||
:else
|
||||
(cph/insert-at-index shapes index [id]))))
|
||||
|
||||
(update-parent [parent]
|
||||
(-> parent
|
||||
(update :shapes update-parent-shapes)
|
||||
(update :shapes vec-without-nils)
|
||||
(cond-> (and (:shape-ref parent)
|
||||
(not= (:id parent) frame-id)
|
||||
(not ignore-touched))
|
||||
(-> (update :touched cph/set-touched-group :shapes-group)
|
||||
(dissoc :remote-synced?)))))
|
||||
|
||||
(update-objects [objects parent-id]
|
||||
(if (and (or (nil? parent-id) (contains? objects parent-id))
|
||||
(or (nil? frame-id) (contains? objects frame-id)))
|
||||
(-> objects
|
||||
(assoc id (-> obj
|
||||
(assoc :frame-id frame-id)
|
||||
(assoc :parent-id parent-id)
|
||||
(assoc :id id)))
|
||||
(update parent-id update-parent))
|
||||
objects))
|
||||
|
||||
(update-container [data]
|
||||
(let [parent-id (or parent-id frame-id)]
|
||||
(update data :objects update-objects parent-id)))]
|
||||
|
||||
(cond-> (and (:shape-ref (get-in data [:objects parent-id]))
|
||||
(not= parent-id frame-id)
|
||||
(not ignore-touched))
|
||||
(->
|
||||
(update-in [:objects parent-id :touched]
|
||||
cph/set-touched-group :shapes-group)
|
||||
(d/dissoc-in [:objects parent-id :remote-synced?]))))
|
||||
data)))]
|
||||
(if page-id
|
||||
(d/update-in-when data [:pages-index page-id] update-fn)
|
||||
(d/update-in-when data [:components component-id] update-fn))))
|
||||
(d/update-in-when data [:pages-index page-id] update-container)
|
||||
(d/update-in-when data [:components component-id] update-container))))
|
||||
|
||||
(defmethod process-change :mod-obj
|
||||
[data {:keys [id page-id component-id operations]}]
|
||||
@@ -107,32 +126,27 @@
|
||||
|
||||
(defmethod process-change :del-obj
|
||||
[data {:keys [page-id component-id id ignore-touched]}]
|
||||
(letfn [(delete-object [objects id]
|
||||
(letfn [(delete-from-parent [parent]
|
||||
(let [parent (update parent :shapes without-obj id)]
|
||||
(cond-> parent
|
||||
(and (:shape-ref parent)
|
||||
(not ignore-touched))
|
||||
(-> (update :touched cph/set-touched-group :shapes-group)
|
||||
(dissoc :remote-synced?)))))
|
||||
|
||||
(delete-from-objects [objects]
|
||||
(if-let [target (get objects id)]
|
||||
(let [parent-id (cph/get-parent id objects)
|
||||
frame-id (:frame-id target)
|
||||
parent (get objects parent-id)
|
||||
objects (dissoc objects id)]
|
||||
(cond-> objects
|
||||
(and (not= parent-id frame-id)
|
||||
(#{:group :svg-raw} (:type parent)))
|
||||
(update-in [parent-id :shapes] (fn [s] (filterv #(not= % id) s)))
|
||||
|
||||
(and (:shape-ref parent) (not ignore-touched))
|
||||
(->
|
||||
(update-in [parent-id :touched] cph/set-touched-group :shapes-group)
|
||||
(d/dissoc-in [parent-id :remote-synced?]))
|
||||
|
||||
(contains? objects frame-id)
|
||||
(update-in [frame-id :shapes] (fn [s] (filterv #(not= % id) s)))
|
||||
|
||||
(seq (:shapes target)) ; Recursive delete all
|
||||
; dependend objects
|
||||
(as-> $ (reduce delete-object $ (:shapes target)))))
|
||||
(let [parent-id (or (:parent-id target)
|
||||
(:frame-id target))
|
||||
children (cph/get-children id objects)]
|
||||
(-> (reduce dissoc objects children)
|
||||
(dissoc id)
|
||||
(d/update-when parent-id delete-from-parent)))
|
||||
objects))]
|
||||
|
||||
(if page-id
|
||||
(d/update-in-when data [:pages-index page-id :objects] delete-object id)
|
||||
(d/update-in-when data [:components component-id :objects] delete-object id))))
|
||||
(d/update-in-when data [:pages-index page-id :objects] delete-from-objects)
|
||||
(d/update-in-when data [:components component-id :objects] delete-from-objects))))
|
||||
|
||||
;; reg-objects operation "regenerates" the geometry and selrect of the parent groups
|
||||
(defmethod process-change :reg-objects
|
||||
@@ -191,25 +205,24 @@
|
||||
(insert-items prev-shapes index shapes)
|
||||
;; For masked groups, the first shape is the mask
|
||||
;; and it cannot be moved.
|
||||
(let [mask-id (first prev-shapes)
|
||||
other-ids (rest prev-shapes)
|
||||
not-mask-shapes (strip-id shapes mask-id)
|
||||
new-index (if (nil? index) nil (max (dec index) 0))
|
||||
new-shapes (insert-items other-ids new-index not-mask-shapes)]
|
||||
(let [mask-id (first prev-shapes)
|
||||
other-ids (rest prev-shapes)
|
||||
not-mask-shapes (without-obj shapes mask-id)
|
||||
new-index (if (nil? index) nil (max (dec index) 0))
|
||||
new-shapes (insert-items other-ids new-index not-mask-shapes)]
|
||||
(d/concat [mask-id] new-shapes))))
|
||||
|
||||
(strip-id [coll id]
|
||||
(filterv #(not= % id) coll))
|
||||
|
||||
(add-to-parent [parent index shapes]
|
||||
(cond-> parent
|
||||
true
|
||||
(update :shapes check-insert-items parent index shapes)
|
||||
|
||||
(and (:shape-ref parent) (= (:type parent) :group) (not ignore-touched))
|
||||
(->
|
||||
(update :touched cph/set-touched-group :shapes-group)
|
||||
(dissoc :remote-synced?))))
|
||||
(let [parent (-> parent
|
||||
(update :shapes check-insert-items parent index shapes)
|
||||
;; We need to ensure that no `nil` in the
|
||||
;; shapes list after adding all the
|
||||
;; incoming shapes to the parent.
|
||||
(update :shapes vec-without-nils))]
|
||||
(cond-> parent
|
||||
(and (:shape-ref parent) (= (:type parent) :group) (not ignore-touched))
|
||||
(-> (update :touched cph/set-touched-group :shapes-group)
|
||||
(dissoc :remote-synced?)))))
|
||||
|
||||
(remove-from-old-parent [cpindex objects shape-id]
|
||||
(let [prev-parent-id (get cpindex shape-id)]
|
||||
@@ -217,22 +230,19 @@
|
||||
;; the new destination target parent id.
|
||||
(if (= prev-parent-id parent-id)
|
||||
objects
|
||||
(let [sid shape-id
|
||||
pid prev-parent-id
|
||||
obj (get objects pid)
|
||||
(let [sid shape-id
|
||||
pid prev-parent-id
|
||||
obj (get objects pid)
|
||||
component? (and (:shape-ref obj)
|
||||
(= (:type obj) :group)
|
||||
(not ignore-touched))]
|
||||
|
||||
(-> objects
|
||||
(d/update-in-when [pid :shapes] strip-id sid)
|
||||
|
||||
(cond-> component?
|
||||
(d/update-when
|
||||
pid
|
||||
#(-> %
|
||||
(update :touched cph/set-touched-group :shapes-group)
|
||||
(dissoc :remote-synced?)))))))))
|
||||
(d/update-in-when [pid :shapes] without-obj sid)
|
||||
(d/update-in-when [pid :shapes] vec-without-nils)
|
||||
(cond-> component? (d/update-when pid #(-> %
|
||||
(update :touched cph/set-touched-group :shapes-group)
|
||||
(dissoc :remote-synced?)))))))))
|
||||
|
||||
(update-parent-id [objects id]
|
||||
(-> objects
|
||||
@@ -240,8 +250,7 @@
|
||||
|
||||
;; Updates the frame-id references that might be outdated
|
||||
(assign-frame-id [frame-id objects id]
|
||||
(let [objects (-> objects
|
||||
(d/update-when id assoc :frame-id frame-id))
|
||||
(let [objects (d/update-when objects id assoc :frame-id frame-id)
|
||||
obj (get objects id)]
|
||||
(cond-> objects
|
||||
;; If we moving frame, the parent frame is the root
|
||||
@@ -293,23 +302,24 @@
|
||||
|
||||
(defmethod process-change :add-page
|
||||
[data {:keys [id name page]}]
|
||||
(cond
|
||||
(and (string? name) (uuid? id))
|
||||
(let [page (assoc init/empty-page-data
|
||||
:id id
|
||||
:name name)]
|
||||
(-> data
|
||||
(update :pages conj id)
|
||||
(update :pages-index assoc id page)))
|
||||
|
||||
(map? page)
|
||||
(-> data
|
||||
(update :pages conj (:id page))
|
||||
(update :pages-index assoc (:id page) page))
|
||||
|
||||
:else
|
||||
(when (and id name page)
|
||||
(ex/raise :type :conflict
|
||||
:hint "name or page should be provided, never both")))
|
||||
:hint "name or page should be provided, never both"))
|
||||
(letfn [(conj-if-not-exists [pages id]
|
||||
(cond-> pages
|
||||
(not (d/seek #(= % id) pages))
|
||||
(conj id)))]
|
||||
(if (and (string? name) (uuid? id))
|
||||
(let [page (assoc init/empty-page-data
|
||||
:id id
|
||||
:name name)]
|
||||
(-> data
|
||||
(update :pages conj-if-not-exists id)
|
||||
(update :pages-index assoc id page)))
|
||||
|
||||
(-> data
|
||||
(update :pages conj-if-not-exists (:id page))
|
||||
(update :pages-index assoc (:id page) page)))))
|
||||
|
||||
(defmethod process-change :mod-page
|
||||
[data {:keys [id name]}]
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
(:require
|
||||
[app.common.uuid :as uuid]))
|
||||
|
||||
(def file-version 8)
|
||||
(def file-version 11)
|
||||
(def default-color "#b1b2b5") ;; $color-gray-20
|
||||
(def root uuid/zero)
|
||||
|
||||
|
||||
@@ -99,19 +99,20 @@
|
||||
|
||||
;; Implemented with transient for performance
|
||||
(defn get-children
|
||||
"Retrieve all children ids recursively for a given object"
|
||||
"Retrieve all children ids recursively for a given object. The
|
||||
children's order will be breadth first."
|
||||
[id objects]
|
||||
|
||||
(loop [result (transient [])
|
||||
(loop [result (transient [])
|
||||
pending (transient [])
|
||||
next id]
|
||||
next id]
|
||||
(let [children (get-in objects [next :shapes] [])
|
||||
[result pending]
|
||||
;; Iterate through children and add them to the result
|
||||
;; also add them in pending to check for their children
|
||||
(loop [result result
|
||||
pending pending
|
||||
current (first children)
|
||||
current (first children)
|
||||
children (rest children)]
|
||||
(if current
|
||||
(recur (conj! result current)
|
||||
@@ -213,7 +214,7 @@
|
||||
(if (some #{id} acc)
|
||||
acc
|
||||
(conj acc id)))
|
||||
prev-ids
|
||||
(vec prev-ids)
|
||||
ids))
|
||||
|
||||
(defn select-toplevel-shapes
|
||||
|
||||
@@ -222,3 +222,49 @@
|
||||
(update :pages-index #(d/mapm clean-container %))
|
||||
(d/update-when :components #(d/mapm clean-container %)))))
|
||||
|
||||
(defmethod migrate 9
|
||||
[data]
|
||||
(letfn [(find-empty-groups [objects]
|
||||
(->> (vals objects)
|
||||
(filter (fn [shape]
|
||||
(and (= :group (:type shape))
|
||||
(or (empty? (:shapes shape))
|
||||
(every? (fn [child-id]
|
||||
(not (contains? objects child-id)))
|
||||
(:shapes shape))))))
|
||||
(map :id)))
|
||||
|
||||
(calculate-changes [[page-id page]]
|
||||
(let [objects (:objects page)
|
||||
eids (find-empty-groups objects)]
|
||||
|
||||
(map (fn [id]
|
||||
{:type :del-obj
|
||||
:page-id page-id
|
||||
:id id})
|
||||
eids)))]
|
||||
|
||||
(loop [data data]
|
||||
(let [changes (mapcat calculate-changes (:pages-index data))]
|
||||
(if (seq changes)
|
||||
(recur (cp/process-changes data changes))
|
||||
data)))))
|
||||
|
||||
(defmethod migrate 10
|
||||
[data]
|
||||
(letfn [(update-page [_ page]
|
||||
(d/update-in-when page [:objects uuid/zero] dissoc :points :selrect))]
|
||||
(update data :pages-index #(d/mapm update-page %))))
|
||||
|
||||
(defmethod migrate 11
|
||||
[data]
|
||||
(letfn [(update-object [objects _id shape]
|
||||
(if (= :frame (:type shape))
|
||||
(d/update-when shape :shapes (fn [shapes]
|
||||
(filterv (fn [id] (contains? objects id)) shapes)))
|
||||
shape))
|
||||
|
||||
(update-page [_ page]
|
||||
(update page :objects #(d/mapm (partial update-object %) %)))]
|
||||
|
||||
(update data :pages-index #(d/mapm update-page %))))
|
||||
|
||||
@@ -64,7 +64,8 @@
|
||||
|
||||
(defn ^boolean is-text-node?
|
||||
[node]
|
||||
(string? (:text node)))
|
||||
(and (string? (:text node))
|
||||
(not= (:text node) "")))
|
||||
|
||||
(defn ^boolean is-paragraph-node?
|
||||
[node]
|
||||
|
||||
@@ -3,10 +3,10 @@ LABEL maintainer="Andrey Antukh <niwi@niwi.nz>"
|
||||
|
||||
ARG DEBIAN_FRONTEND=noninteractive
|
||||
|
||||
ENV NODE_VERSION=v14.17.2 \
|
||||
CLOJURE_VERSION=1.10.3.882 \
|
||||
ENV NODE_VERSION=v14.17.3 \
|
||||
CLOJURE_VERSION=1.10.3.929 \
|
||||
CLJKONDO_VERSION=2021.06.18 \
|
||||
BABASHKA_VERSION=0.4.6 \
|
||||
BABASHKA_VERSION=0.5.0 \
|
||||
LANG=en_US.UTF-8 \
|
||||
LC_ALL=en_US.UTF-8
|
||||
|
||||
|
||||
@@ -13,8 +13,8 @@ PENPOT_REDIS_URI=redis://penpot-redis/0
|
||||
# can be configured to store in AWS S3 or completely in de the database.
|
||||
# Storing in the database makes the backups more easy but will make access to
|
||||
# media less performant.
|
||||
PENPOT_STORAGE_BACKEND=fs
|
||||
PENPOT_STORAGE_FS_DIRECTORY=/opt/data/assets
|
||||
ASSETS_STORAGE_BACKEND=assets-fs
|
||||
PENPOT_STORAGE_ASSETS_FS_DIRECTORY=/opt/data/assets
|
||||
|
||||
# Telemetry. When enabled, a periodical process will send anonymous data about
|
||||
# this instance. Telemetry data will enable us to learn on how the application
|
||||
|
||||
@@ -9,17 +9,6 @@ log() {
|
||||
## App Frontend config
|
||||
#########################################
|
||||
|
||||
|
||||
update_public_uri() {
|
||||
if [ -n "$PENPOT_PUBLIC_URI" ]; then
|
||||
log "Updating Public URI: $PENPOT_PUBLIC_URI"
|
||||
sed -i \
|
||||
-e "s|^//var penpotPublicURI = \".*\";|var penpotPublicURI = \"$PENPOT_PUBLIC_URI\";|g" \
|
||||
"$1"
|
||||
fi
|
||||
}
|
||||
|
||||
|
||||
update_demo_warning() {
|
||||
if [ -n "$PENPOT_DEMO_WARNING" ]; then
|
||||
log "Updating Demo Warning: $PENPOT_DEMO_WARNING"
|
||||
@@ -113,7 +102,6 @@ update_flags() {
|
||||
fi
|
||||
}
|
||||
|
||||
update_public_uri /var/www/app/js/config.js
|
||||
update_demo_warning /var/www/app/js/config.js
|
||||
update_allow_demo_users /var/www/app/js/config.js
|
||||
update_google_client_id /var/www/app/js/config.js
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
|
||||
:dev
|
||||
{:extra-deps
|
||||
{thheller/shadow-cljs {:mvn/version "2.15.1"}}}
|
||||
{thheller/shadow-cljs {:mvn/version "2.15.2"}}}
|
||||
|
||||
:shadow-cljs
|
||||
{:main-opts ["-m" "shadow.cljs.devtools.cli"]}
|
||||
|
||||
@@ -33,7 +33,7 @@
|
||||
"postcss-clean": "^1.2.2",
|
||||
"rimraf": "^3.0.0",
|
||||
"sass": "^1.35.1",
|
||||
"shadow-cljs": "2.15.1"
|
||||
"shadow-cljs": "2.15.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"date-fns": "^2.22.1",
|
||||
|
||||
@@ -38,6 +38,7 @@
|
||||
z-index: 12;
|
||||
max-height: 30rem;
|
||||
min-width: 230px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.options-dropdown {
|
||||
|
||||
@@ -52,11 +52,17 @@
|
||||
|
||||
ptk/WatchEvent
|
||||
(watch [_ _ stream]
|
||||
(when (:timeout data)
|
||||
(let [stoper (rx/filter (ptk/type? ::show) stream)]
|
||||
(->> (rx/of hide)
|
||||
(rx/delay (:timeout data))
|
||||
(rx/take-until stoper)))))))
|
||||
(rx/merge
|
||||
(let [stoper (rx/filter (ptk/type? ::hide) stream)]
|
||||
(->> stream
|
||||
(rx/filter (ptk/type? :app.util.router/navigate))
|
||||
(rx/map (constantly hide))
|
||||
(rx/take-until stoper)))
|
||||
(when (:timeout data)
|
||||
(let [stoper (rx/filter (ptk/type? ::show) stream)]
|
||||
(->> (rx/of hide)
|
||||
(rx/delay (:timeout data))
|
||||
(rx/take-until stoper))))))))
|
||||
|
||||
(def hide
|
||||
(ptk/reify ::hide
|
||||
|
||||
@@ -204,10 +204,19 @@
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(dissoc state
|
||||
:current-file-id
|
||||
:current-project-id
|
||||
:current-team-id
|
||||
:workspace-data
|
||||
:workspace-editor-state
|
||||
:workspace-file
|
||||
:workspace-project
|
||||
:workspace-libraries
|
||||
:workspace-media-objects
|
||||
:workspace-persistence))
|
||||
:workspace-persistence
|
||||
:workspace-presence
|
||||
:workspace-project
|
||||
:workspace-project
|
||||
:workspace-undo))
|
||||
|
||||
ptk/WatchEvent
|
||||
(watch [_ _ _]
|
||||
@@ -242,9 +251,10 @@
|
||||
(update [_ state]
|
||||
(let [page-id (or page-id (get-in state [:workspace-data :pages 0]))
|
||||
local (-> (:workspace-local state)
|
||||
(dissoc :edition)
|
||||
(dissoc :edit-path)
|
||||
(dissoc :selected))]
|
||||
(dissoc
|
||||
:edition
|
||||
:edit-path
|
||||
:selected))]
|
||||
(-> state
|
||||
(assoc-in [:workspace-cache page-id] local)
|
||||
(dissoc :current-page-id :workspace-local :trimmed-page :workspace-drawing))))))
|
||||
@@ -1474,8 +1484,8 @@
|
||||
(= :frame (get-in objects [(first selected) :type])))))
|
||||
|
||||
(defn- paste-shape
|
||||
[{:keys [selected objects images] :as data} in-viewport?]
|
||||
(letfn [;; Given a file-id and img (part generated by the
|
||||
[{:keys [selected objects images] :as data} in-viewport?] ;; TODO: perhaps rename 'objects' to 'shapes', because it contains only
|
||||
(letfn [;; Given a file-id and img (part generated by the ;; the shapes to paste, not the whole page tree of shapes
|
||||
;; copy-selected event), uploads the new media.
|
||||
(upload-media [file-id imgpart]
|
||||
(->> (http/send! {:uri (:file-data imgpart)
|
||||
@@ -1583,7 +1593,7 @@
|
||||
|
||||
page-id (:current-page-id state)
|
||||
unames (-> (wsh/lookup-page-objects state page-id)
|
||||
(dwc/retrieve-used-names))
|
||||
(dwc/retrieve-used-names)) ;; TODO: move this calculation inside prepare-duplcate-changes?
|
||||
|
||||
rchanges (->> (dws/prepare-duplicate-changes objects page-id unames selected delta)
|
||||
(mapv (partial process-rchange media-idx))
|
||||
|
||||
@@ -141,6 +141,11 @@
|
||||
(try
|
||||
(us/assert ::spec/changes redo-changes)
|
||||
(us/assert ::spec/changes undo-changes)
|
||||
|
||||
;; (prn "====== commit-changes ======" path)
|
||||
;; (cljs.pprint/pprint redo-changes)
|
||||
;; (cljs.pprint/pprint undo-changes)
|
||||
|
||||
(update-in state path cp/process-changes redo-changes false)
|
||||
|
||||
(catch :default e
|
||||
|
||||
@@ -350,6 +350,8 @@
|
||||
(let [page-id (:current-page-id state)
|
||||
objects (wsh/lookup-page-objects state page-id)
|
||||
|
||||
ids (cp/clean-loops objects ids)
|
||||
|
||||
groups-to-unmask
|
||||
(reduce (fn [group-ids id]
|
||||
;; When the shape to delete is the mask of a masked group,
|
||||
@@ -387,76 +389,85 @@
|
||||
ids)
|
||||
|
||||
all-children
|
||||
(reduce (fn [res id]
|
||||
(into res (cp/get-children id objects)))
|
||||
(d/ordered-set)
|
||||
ids)
|
||||
(->> ids
|
||||
(reduce (fn [res id]
|
||||
(into res (cp/get-children id objects)))
|
||||
[])
|
||||
(reverse)
|
||||
(into (d/ordered-set)))
|
||||
|
||||
empty-parents
|
||||
(into (d/ordered-set) empty-parents-xform all-parents)
|
||||
|
||||
mk-del-obj-xf
|
||||
(map (fn [id]
|
||||
{:type :del-obj
|
||||
:page-id page-id
|
||||
:id id}))
|
||||
(comp (filter (partial contains? objects))
|
||||
(map (fn [id]
|
||||
{:type :del-obj
|
||||
:page-id page-id
|
||||
:id id})))
|
||||
|
||||
mk-add-obj-xf
|
||||
(map (fn [id]
|
||||
(let [item (get objects id)]
|
||||
{:type :add-obj
|
||||
:id (:id item)
|
||||
:page-id page-id
|
||||
:index (cp/position-on-parent id objects)
|
||||
:frame-id (:frame-id item)
|
||||
:parent-id (:parent-id item)
|
||||
:obj item})))
|
||||
(comp (filter (partial contains? objects))
|
||||
(map (fn [id]
|
||||
(let [item (get objects id)]
|
||||
{:type :add-obj
|
||||
:id (:id item)
|
||||
:page-id page-id
|
||||
:index (cp/position-on-parent id objects)
|
||||
:frame-id (:frame-id item)
|
||||
:parent-id (:parent-id item)
|
||||
:obj item}))))
|
||||
|
||||
mk-mod-touched-xf
|
||||
(map (fn [id]
|
||||
(let [parent (get objects id)]
|
||||
{:type :mod-obj
|
||||
:page-id page-id
|
||||
:id (:id parent)
|
||||
:operations [{:type :set-touched
|
||||
:touched (:touched parent)}]})))
|
||||
(comp (filter (partial contains? objects))
|
||||
(map (fn [id]
|
||||
(let [parent (get objects id)]
|
||||
{:type :mod-obj
|
||||
:page-id page-id
|
||||
:id (:id parent)
|
||||
:operations [{:type :set-touched
|
||||
:touched (:touched parent)}]}))))
|
||||
|
||||
mk-mod-int-del-xf
|
||||
(map (fn [obj]
|
||||
{:type :mod-obj
|
||||
:page-id page-id
|
||||
:id (:id obj)
|
||||
:operations [{:type :set
|
||||
:attr :interactions
|
||||
:val (vec (remove (fn [interaction]
|
||||
(contains? ids (:destination interaction)))
|
||||
(:interactions obj)))}]}))
|
||||
(comp (filter some?)
|
||||
(map (fn [obj]
|
||||
{:type :mod-obj
|
||||
:page-id page-id
|
||||
:id (:id obj)
|
||||
:operations [{:type :set
|
||||
:attr :interactions
|
||||
:val (vec (remove (fn [interaction]
|
||||
(contains? ids (:destination interaction)))
|
||||
(:interactions obj)))}]})))
|
||||
mk-mod-int-add-xf
|
||||
(map (fn [obj]
|
||||
{:type :mod-obj
|
||||
:page-id page-id
|
||||
:id (:id obj)
|
||||
:operations [{:type :set
|
||||
:attr :interactions
|
||||
:val (:interactions obj)}]}))
|
||||
(comp (filter some?)
|
||||
(map (fn [obj]
|
||||
{:type :mod-obj
|
||||
:page-id page-id
|
||||
:id (:id obj)
|
||||
:operations [{:type :set
|
||||
:attr :interactions
|
||||
:val (:interactions obj)}]})))
|
||||
|
||||
mk-mod-unmask-xf
|
||||
(map (fn [id]
|
||||
{:type :mod-obj
|
||||
:page-id page-id
|
||||
:id id
|
||||
:operations [{:type :set
|
||||
:attr :masked-group?
|
||||
:val false}]}))
|
||||
(comp (filter (partial contains? objects))
|
||||
(map (fn [id]
|
||||
{:type :mod-obj
|
||||
:page-id page-id
|
||||
:id id
|
||||
:operations [{:type :set
|
||||
:attr :masked-group?
|
||||
:val false}]})))
|
||||
|
||||
mk-mod-mask-xf
|
||||
(map (fn [id]
|
||||
{:type :mod-obj
|
||||
:page-id page-id
|
||||
:id id
|
||||
:operations [{:type :set
|
||||
:attr :masked-group?
|
||||
:val true}]}))
|
||||
(comp (filter (partial contains? objects))
|
||||
(map (fn [id]
|
||||
{:type :mod-obj
|
||||
:page-id page-id
|
||||
:id id
|
||||
:operations [{:type :set
|
||||
:attr :masked-group?
|
||||
:val true}]})))
|
||||
|
||||
rchanges
|
||||
(-> []
|
||||
|
||||
@@ -23,21 +23,14 @@
|
||||
(map #(assoc % ::index (cp/position-on-parent (:id %) objects)))
|
||||
(sort-by ::index)))
|
||||
|
||||
(defn- make-group
|
||||
[shapes prefix keep-name]
|
||||
(let [selrect (gsh/selection-rect shapes)
|
||||
frame-id (-> shapes first :frame-id)
|
||||
group-name (if (and keep-name
|
||||
(= (count shapes) 1)
|
||||
(= (:type (first shapes)) :group))
|
||||
(:name (first shapes))
|
||||
(name (gensym prefix)))] ; TODO: we should something like in new shapes
|
||||
(-> (cp/make-minimal-group frame-id selrect group-name)
|
||||
(gsh/setup selrect)
|
||||
(assoc :shapes (mapv :id shapes)))))
|
||||
(defn- get-empty-groups-after-group-creation
|
||||
"An auxiliar function that finds and returns a set of ids that
|
||||
corresponds to groups that should be deleted after a group creation.
|
||||
|
||||
(defn get-empty-groups
|
||||
"Retrieve emtpy groups after group creation"
|
||||
The corner case happens when you selects two (or more) shapes that
|
||||
belongs each one to different groups, and after creating the new
|
||||
group, one (or many) groups can become empty because they have had a
|
||||
single shape which is moved to the created group."
|
||||
[objects parent-id shapes]
|
||||
(let [ids (cp/clean-loops objects (into #{} (map :id) shapes))
|
||||
parents (->> ids
|
||||
@@ -71,56 +64,67 @@
|
||||
result)))))))
|
||||
|
||||
(defn prepare-create-group
|
||||
[objects page-id shapes prefix keep-name]
|
||||
(let [group (make-group shapes prefix keep-name)
|
||||
frame-id (:frame-id (first shapes))
|
||||
[objects page-id shapes base-name keep-name?]
|
||||
(let [frame-id (:frame-id (first shapes))
|
||||
parent-id (:parent-id (first shapes))
|
||||
rchanges [{:type :add-obj
|
||||
:id (:id group)
|
||||
:page-id page-id
|
||||
:frame-id frame-id
|
||||
:parent-id parent-id
|
||||
:obj group
|
||||
:index (::index (first shapes))}
|
||||
gname (if (and keep-name?
|
||||
(= (count shapes) 1)
|
||||
(= (:type (first shapes)) :group))
|
||||
(:name (first shapes))
|
||||
(-> (dwc/retrieve-used-names objects)
|
||||
(dwc/generate-unique-name base-name)))
|
||||
|
||||
{:type :mov-objects
|
||||
:page-id page-id
|
||||
:parent-id (:id group)
|
||||
:shapes (mapv :id shapes)}]
|
||||
selrect (gsh/selection-rect shapes)
|
||||
group (-> (cp/make-minimal-group frame-id selrect gname)
|
||||
(gsh/setup selrect)
|
||||
(assoc :shapes (mapv :id shapes)))
|
||||
|
||||
uchanges (-> (mapv
|
||||
(fn [obj]
|
||||
{:type :mov-objects
|
||||
:page-id page-id
|
||||
:parent-id (:parent-id obj)
|
||||
:index (::index obj)
|
||||
:shapes [(:id obj)]}) shapes)
|
||||
(conj
|
||||
{:type :del-obj
|
||||
:id (:id group)
|
||||
:page-id page-id}))
|
||||
rchanges [{:type :add-obj
|
||||
:id (:id group)
|
||||
:page-id page-id
|
||||
:frame-id frame-id
|
||||
:parent-id parent-id
|
||||
:obj group
|
||||
:index (::index (first shapes))}
|
||||
|
||||
ids-to-delete (get-empty-groups objects parent-id shapes)
|
||||
{:type :mov-objects
|
||||
:page-id page-id
|
||||
:parent-id (:id group)
|
||||
:shapes (mapv :id shapes)}]
|
||||
|
||||
uchanges (-> (mapv (fn [obj]
|
||||
{:type :mov-objects
|
||||
:page-id page-id
|
||||
:parent-id (:parent-id obj)
|
||||
:index (::index obj)
|
||||
:shapes [(:id obj)]})
|
||||
shapes)
|
||||
(conj {:type :del-obj
|
||||
:id (:id group)
|
||||
:page-id page-id}))
|
||||
|
||||
;; Look at the `get-empty-groups-after-group-creation`
|
||||
;; doctring to understand the real purpuse of this code
|
||||
ids-to-delete (get-empty-groups-after-group-creation objects parent-id shapes)
|
||||
|
||||
delete-group
|
||||
(fn [changes id]
|
||||
(-> changes
|
||||
(conj {:type :del-obj
|
||||
:id id
|
||||
:page-id page-id})))
|
||||
(conj changes {:type :del-obj
|
||||
:id id
|
||||
:page-id page-id}))
|
||||
|
||||
add-deleted-group
|
||||
(fn [changes id]
|
||||
(let [obj (-> (get objects id)
|
||||
(d/without-keys [:shapes]))]
|
||||
|
||||
(d/concat [{:type :add-obj
|
||||
:id id
|
||||
:page-id page-id
|
||||
:frame-id (:frame-id obj)
|
||||
:parent-id (:parent-id obj)
|
||||
:obj obj
|
||||
:index (::index obj)}] changes)))
|
||||
(dissoc :shapes))]
|
||||
(into [{:type :add-obj
|
||||
:id id
|
||||
:page-id page-id
|
||||
:frame-id (:frame-id obj)
|
||||
:parent-id (:parent-id obj)
|
||||
:obj obj
|
||||
:index (::index obj)}]
|
||||
changes)))
|
||||
|
||||
rchanges (->> ids-to-delete
|
||||
(reduce delete-group rchanges))
|
||||
@@ -178,7 +182,7 @@
|
||||
shapes (shapes-for-grouping objects selected)]
|
||||
(when-not (empty? shapes)
|
||||
(let [[group rchanges uchanges]
|
||||
(prepare-create-group objects page-id shapes "Group-" false)]
|
||||
(prepare-create-group objects page-id shapes "Group" false)]
|
||||
(rx/of (dch/commit-changes {:redo-changes rchanges
|
||||
:undo-changes uchanges
|
||||
:origin it})
|
||||
@@ -217,7 +221,7 @@
|
||||
(if (and (= (count shapes) 1)
|
||||
(= (:type (first shapes)) :group))
|
||||
[(first shapes) [] []]
|
||||
(prepare-create-group objects page-id shapes "Group-" true))
|
||||
(prepare-create-group objects page-id shapes "Group" true))
|
||||
|
||||
rchanges (d/concat rchanges
|
||||
[{:type :mod-obj
|
||||
|
||||
@@ -382,7 +382,7 @@
|
||||
|
||||
page-id (:current-page-id state)
|
||||
objects (wsh/lookup-page-objects state page-id)
|
||||
unames (atom (dwc/retrieve-used-names objects))
|
||||
unames (volatile! (dwc/retrieve-used-names objects))
|
||||
|
||||
frame-id (cp/frame-id-by-position objects (gpt/add orig-pos delta))
|
||||
|
||||
@@ -391,7 +391,7 @@
|
||||
(let [new-name (dwc/generate-unique-name @unames (:name new-shape))]
|
||||
|
||||
(when (nil? (:parent-id original-shape))
|
||||
(swap! unames conj new-name))
|
||||
(vswap! unames conj new-name))
|
||||
|
||||
(cond-> new-shape
|
||||
true
|
||||
@@ -594,7 +594,7 @@
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(if (not= library-id (:current-file-id state))
|
||||
(assoc-in state [:workspace-libraries library-id :synced-at] (dt/now))
|
||||
(d/assoc-in-when state [:workspace-libraries library-id :synced-at] (dt/now))
|
||||
state))
|
||||
|
||||
ptk/WatchEvent
|
||||
|
||||
@@ -129,7 +129,7 @@
|
||||
(if (and (= (count shapes) 1)
|
||||
(= (:type (first shapes)) :group))
|
||||
[(first shapes) [] []]
|
||||
(dwg/prepare-create-group objects page-id shapes "Component-" true))
|
||||
(dwg/prepare-create-group objects page-id shapes "Component" true))
|
||||
|
||||
[new-shape new-shapes updated-shapes]
|
||||
(make-component-shape group objects file-id)
|
||||
@@ -204,6 +204,7 @@
|
||||
"Generate changes to remove the links between a shape and all its children
|
||||
with a component."
|
||||
[shape-id container]
|
||||
(log/debug :msg "Detach instance" :shape-id shape-id :container (:id container))
|
||||
(let [shapes (cp/get-object-with-children shape-id (:objects container))
|
||||
rchanges (mapv (fn [obj]
|
||||
(make-change
|
||||
@@ -646,89 +647,92 @@
|
||||
:shape (str (:name shape-inst))
|
||||
:component (:name component))
|
||||
|
||||
(let [omit-touched? (not reset?)
|
||||
clear-remote-synced? (and initial-root? reset?)
|
||||
set-remote-synced? (and (not initial-root?) reset?)
|
||||
(if (nil? shape-main)
|
||||
;; This should not occur, but protect against it in any case
|
||||
(generate-detach-instance (:id shape-inst) container)
|
||||
(let [omit-touched? (not reset?)
|
||||
clear-remote-synced? (and initial-root? reset?)
|
||||
set-remote-synced? (and (not initial-root?) reset?)
|
||||
|
||||
[rchanges uchanges]
|
||||
(concat-changes
|
||||
(update-attrs shape-inst
|
||||
shape-main
|
||||
root-inst
|
||||
root-main
|
||||
container
|
||||
omit-touched?)
|
||||
[rchanges uchanges]
|
||||
(concat-changes
|
||||
(if reset?
|
||||
(change-touched shape-inst
|
||||
shape-main
|
||||
container
|
||||
{:reset-touched? true})
|
||||
empty-changes)
|
||||
(update-attrs shape-inst
|
||||
shape-main
|
||||
root-inst
|
||||
root-main
|
||||
container
|
||||
omit-touched?)
|
||||
(concat-changes
|
||||
(if clear-remote-synced?
|
||||
(change-remote-synced shape-inst container nil)
|
||||
(if reset?
|
||||
(change-touched shape-inst
|
||||
shape-main
|
||||
container
|
||||
{:reset-touched? true})
|
||||
empty-changes)
|
||||
(if set-remote-synced?
|
||||
(change-remote-synced shape-inst container true)
|
||||
empty-changes))))
|
||||
(concat-changes
|
||||
(if clear-remote-synced?
|
||||
(change-remote-synced shape-inst container nil)
|
||||
empty-changes)
|
||||
(if set-remote-synced?
|
||||
(change-remote-synced shape-inst container true)
|
||||
empty-changes))))
|
||||
|
||||
children-inst (mapv #(cp/get-shape container %)
|
||||
(:shapes shape-inst))
|
||||
children-main (mapv #(cp/get-shape component %)
|
||||
(:shapes shape-main))
|
||||
children-inst (mapv #(cp/get-shape container %)
|
||||
(:shapes shape-inst))
|
||||
children-main (mapv #(cp/get-shape component %)
|
||||
(:shapes shape-main))
|
||||
|
||||
only-inst (fn [child-inst]
|
||||
(when-not (and omit-touched?
|
||||
(contains? (:touched shape-inst)
|
||||
:shapes-group))
|
||||
(remove-shape child-inst
|
||||
container
|
||||
omit-touched?)))
|
||||
only-inst (fn [child-inst]
|
||||
(when-not (and omit-touched?
|
||||
(contains? (:touched shape-inst)
|
||||
:shapes-group))
|
||||
(remove-shape child-inst
|
||||
container
|
||||
omit-touched?)))
|
||||
|
||||
only-main (fn [child-main]
|
||||
(when-not (and omit-touched?
|
||||
(contains? (:touched shape-inst)
|
||||
:shapes-group))
|
||||
(add-shape-to-instance child-main
|
||||
(d/index-of children-main
|
||||
child-main)
|
||||
component
|
||||
container
|
||||
root-inst
|
||||
root-main
|
||||
omit-touched?
|
||||
set-remote-synced?)))
|
||||
only-main (fn [child-main]
|
||||
(when-not (and omit-touched?
|
||||
(contains? (:touched shape-inst)
|
||||
:shapes-group))
|
||||
(add-shape-to-instance child-main
|
||||
(d/index-of children-main
|
||||
child-main)
|
||||
component
|
||||
container
|
||||
root-inst
|
||||
root-main
|
||||
omit-touched?
|
||||
set-remote-synced?)))
|
||||
|
||||
both (fn [child-inst child-main]
|
||||
(generate-sync-shape-direct-recursive container
|
||||
child-inst
|
||||
component
|
||||
child-main
|
||||
root-inst
|
||||
root-main
|
||||
reset?
|
||||
initial-root?))
|
||||
both (fn [child-inst child-main]
|
||||
(generate-sync-shape-direct-recursive container
|
||||
child-inst
|
||||
component
|
||||
child-main
|
||||
root-inst
|
||||
root-main
|
||||
reset?
|
||||
initial-root?))
|
||||
|
||||
moved (fn [child-inst child-main]
|
||||
(move-shape
|
||||
child-inst
|
||||
(d/index-of children-inst child-inst)
|
||||
(d/index-of children-main child-main)
|
||||
container
|
||||
omit-touched?))
|
||||
moved (fn [child-inst child-main]
|
||||
(move-shape
|
||||
child-inst
|
||||
(d/index-of children-inst child-inst)
|
||||
(d/index-of children-main child-main)
|
||||
container
|
||||
omit-touched?))
|
||||
|
||||
[child-rchanges child-uchanges]
|
||||
(compare-children children-inst
|
||||
children-main
|
||||
only-inst
|
||||
only-main
|
||||
both
|
||||
moved
|
||||
false)]
|
||||
[child-rchanges child-uchanges]
|
||||
(compare-children children-inst
|
||||
children-main
|
||||
only-inst
|
||||
only-main
|
||||
both
|
||||
moved
|
||||
false)]
|
||||
|
||||
[(d/concat rchanges child-rchanges)
|
||||
(d/concat uchanges child-uchanges)]))
|
||||
[(d/concat rchanges child-rchanges)
|
||||
(d/concat uchanges child-uchanges)])))
|
||||
|
||||
(defn generate-sync-shape-inverse
|
||||
"Generate changes to update the component a shape is linked to, from
|
||||
@@ -764,96 +768,99 @@
|
||||
:shape (str (:name shape-inst))
|
||||
:component (:name component))
|
||||
|
||||
(let [component-container (cp/make-container component :component)
|
||||
(if (nil? shape-main)
|
||||
;; This should not occur, but protect against it in any case
|
||||
empty-changes
|
||||
(let [component-container (cp/make-container component :component)
|
||||
|
||||
omit-touched? false
|
||||
set-remote-synced? (not initial-root?)
|
||||
clear-remote-synced? initial-root?
|
||||
omit-touched? false
|
||||
set-remote-synced? (not initial-root?)
|
||||
clear-remote-synced? initial-root?
|
||||
|
||||
[rchanges uchanges]
|
||||
(concat-changes
|
||||
(update-attrs shape-main
|
||||
shape-inst
|
||||
root-main
|
||||
root-inst
|
||||
component-container
|
||||
omit-touched?)
|
||||
[rchanges uchanges]
|
||||
(concat-changes
|
||||
(change-touched shape-inst
|
||||
shape-main
|
||||
container
|
||||
{:reset-touched? true})
|
||||
(update-attrs shape-main
|
||||
shape-inst
|
||||
root-main
|
||||
root-inst
|
||||
component-container
|
||||
omit-touched?)
|
||||
(concat-changes
|
||||
(change-touched shape-main
|
||||
shape-inst
|
||||
component-container
|
||||
{:copy-touched? true})
|
||||
(change-touched shape-inst
|
||||
shape-main
|
||||
container
|
||||
{:reset-touched? true})
|
||||
(concat-changes
|
||||
(if clear-remote-synced?
|
||||
(change-remote-synced shape-inst container nil)
|
||||
empty-changes)
|
||||
(if set-remote-synced?
|
||||
(change-remote-synced shape-inst container true)
|
||||
empty-changes)))))
|
||||
(change-touched shape-main
|
||||
shape-inst
|
||||
component-container
|
||||
{:copy-touched? true})
|
||||
(concat-changes
|
||||
(if clear-remote-synced?
|
||||
(change-remote-synced shape-inst container nil)
|
||||
empty-changes)
|
||||
(if set-remote-synced?
|
||||
(change-remote-synced shape-inst container true)
|
||||
empty-changes)))))
|
||||
|
||||
children-inst (mapv #(cp/get-shape container %)
|
||||
(:shapes shape-inst))
|
||||
children-main (mapv #(cp/get-shape component %)
|
||||
(:shapes shape-main))
|
||||
children-inst (mapv #(cp/get-shape container %)
|
||||
(:shapes shape-inst))
|
||||
children-main (mapv #(cp/get-shape component %)
|
||||
(:shapes shape-main))
|
||||
|
||||
only-inst (fn [child-inst]
|
||||
(add-shape-to-main child-inst
|
||||
(d/index-of children-inst
|
||||
child-inst)
|
||||
component
|
||||
container
|
||||
root-inst
|
||||
root-main))
|
||||
only-inst (fn [child-inst]
|
||||
(add-shape-to-main child-inst
|
||||
(d/index-of children-inst
|
||||
child-inst)
|
||||
component
|
||||
container
|
||||
root-inst
|
||||
root-main))
|
||||
|
||||
only-main (fn [child-main]
|
||||
(remove-shape child-main
|
||||
component-container
|
||||
false))
|
||||
only-main (fn [child-main]
|
||||
(remove-shape child-main
|
||||
component-container
|
||||
false))
|
||||
|
||||
both (fn [child-inst child-main]
|
||||
(generate-sync-shape-inverse-recursive container
|
||||
child-inst
|
||||
component
|
||||
child-main
|
||||
root-inst
|
||||
root-main
|
||||
initial-root?))
|
||||
both (fn [child-inst child-main]
|
||||
(generate-sync-shape-inverse-recursive container
|
||||
child-inst
|
||||
component
|
||||
child-main
|
||||
root-inst
|
||||
root-main
|
||||
initial-root?))
|
||||
|
||||
moved (fn [child-inst child-main]
|
||||
(move-shape
|
||||
child-main
|
||||
(d/index-of children-main child-main)
|
||||
(d/index-of children-inst child-inst)
|
||||
component-container
|
||||
false))
|
||||
moved (fn [child-inst child-main]
|
||||
(move-shape
|
||||
child-main
|
||||
(d/index-of children-main child-main)
|
||||
(d/index-of children-inst child-inst)
|
||||
component-container
|
||||
false))
|
||||
|
||||
[child-rchanges child-uchanges]
|
||||
(compare-children children-inst
|
||||
children-main
|
||||
only-inst
|
||||
only-main
|
||||
both
|
||||
moved
|
||||
true)
|
||||
[child-rchanges child-uchanges]
|
||||
(compare-children children-inst
|
||||
children-main
|
||||
only-inst
|
||||
only-main
|
||||
both
|
||||
moved
|
||||
true)
|
||||
|
||||
;; The inverse sync may be made on a component that is inside a
|
||||
;; remote library. We need to separate changes that are from
|
||||
;; local and remote files.
|
||||
check-local (fn [change]
|
||||
(cond-> change
|
||||
(= (:id change) (:id shape-inst))
|
||||
(assoc :local-change? true)))
|
||||
;; The inverse sync may be made on a component that is inside a
|
||||
;; remote library. We need to separate changes that are from
|
||||
;; local and remote files.
|
||||
check-local (fn [change]
|
||||
(cond-> change
|
||||
(= (:id change) (:id shape-inst))
|
||||
(assoc :local-change? true)))
|
||||
|
||||
rchanges (mapv check-local rchanges)
|
||||
uchanges (mapv check-local uchanges)]
|
||||
rchanges (mapv check-local rchanges)
|
||||
uchanges (mapv check-local uchanges)]
|
||||
|
||||
[(d/concat rchanges child-rchanges)
|
||||
(d/concat uchanges child-uchanges)]))
|
||||
[(d/concat rchanges child-rchanges)
|
||||
(d/concat uchanges child-uchanges)])))
|
||||
|
||||
|
||||
; ---- Operation generation helpers ----
|
||||
|
||||
@@ -326,13 +326,16 @@
|
||||
(defn unlink-file-from-library
|
||||
[file-id library-id]
|
||||
(ptk/reify ::unlink-file-from-library
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(d/dissoc-in state [:workspace-libraries library-id]))
|
||||
|
||||
ptk/WatchEvent
|
||||
(watch [_ _ _]
|
||||
(let [unlinked #(d/dissoc-in % [:workspace-libraries library-id])
|
||||
params {:file-id file-id
|
||||
:library-id library-id}]
|
||||
(let [params {:file-id file-id
|
||||
:library-id library-id}]
|
||||
(->> (rp/mutation :unlink-file-from-library params)
|
||||
(rx/map (constantly unlinked)))))))
|
||||
(rx/ignore))))))
|
||||
|
||||
|
||||
;; --- Upload File Media objects
|
||||
|
||||
@@ -257,8 +257,6 @@
|
||||
(declare prepare-duplicate-frame-change)
|
||||
(declare prepare-duplicate-shape-change)
|
||||
|
||||
(def ^:private change->name #(get-in % [:obj :name]))
|
||||
|
||||
(defn update-indices
|
||||
"Fixes the indices for a set of changes after a duplication. We need to
|
||||
fix the indices to take into the account the movement of indices.
|
||||
@@ -290,19 +288,19 @@
|
||||
"Prepare objects to paste: generate new id, give them unique names,
|
||||
move to the position of mouse pointer, and find in what frame they
|
||||
fit."
|
||||
[objects page-id names ids delta]
|
||||
(loop [names names
|
||||
ids (seq ids)
|
||||
chgs []]
|
||||
(if ids
|
||||
(let [id (first ids)
|
||||
result (prepare-duplicate-change objects page-id names id delta)
|
||||
result (if (vector? result) result [result])]
|
||||
(recur
|
||||
(into names (map change->name) result)
|
||||
(next ids)
|
||||
(into chgs result)))
|
||||
chgs)))
|
||||
[objects page-id unames ids delta]
|
||||
(let [unames (volatile! unames)
|
||||
update-unames! (fn [new-name] (vswap! unames conj new-name))]
|
||||
(loop [ids (seq ids)
|
||||
chgs []]
|
||||
(if ids
|
||||
(let [id (first ids)
|
||||
result (prepare-duplicate-change objects page-id unames update-unames! id delta)
|
||||
result (if (vector? result) result [result])]
|
||||
(recur
|
||||
(next ids)
|
||||
(into chgs result)))
|
||||
chgs))))
|
||||
|
||||
(defn duplicate-changes-update-indices
|
||||
"Parses the change set when duplicating to set-up the appropiate indices"
|
||||
@@ -317,32 +315,32 @@
|
||||
(-> changes (update-indices index-map))))
|
||||
|
||||
(defn- prepare-duplicate-change
|
||||
[objects page-id names id delta]
|
||||
[objects page-id unames update-unames! id delta]
|
||||
(let [obj (get objects id)]
|
||||
(if (= :frame (:type obj))
|
||||
(prepare-duplicate-frame-change objects page-id names obj delta)
|
||||
(prepare-duplicate-shape-change objects page-id names obj delta (:frame-id obj) (:parent-id obj)))))
|
||||
(prepare-duplicate-frame-change objects page-id unames update-unames! obj delta)
|
||||
(prepare-duplicate-shape-change objects page-id unames update-unames! obj delta (:frame-id obj) (:parent-id obj)))))
|
||||
|
||||
(defn- prepare-duplicate-shape-change
|
||||
[objects page-id names obj delta frame-id parent-id]
|
||||
[objects page-id unames update-unames! obj delta frame-id parent-id]
|
||||
(when (some? obj)
|
||||
(let [id (uuid/next)
|
||||
name (dwc/generate-unique-name names (:name obj))
|
||||
name (dwc/generate-unique-name @unames (:name obj))
|
||||
_ (update-unames! name)
|
||||
|
||||
renamed-obj (assoc obj :id id :name name)
|
||||
moved-obj (geom/move renamed-obj delta)
|
||||
parent-id (or parent-id frame-id)
|
||||
|
||||
children-changes
|
||||
(loop [names names
|
||||
result []
|
||||
(loop [result []
|
||||
cid (first (:shapes obj))
|
||||
cids (rest (:shapes obj))]
|
||||
(if (nil? cid)
|
||||
result
|
||||
(let [obj (get objects cid)
|
||||
changes (prepare-duplicate-shape-change objects page-id names obj delta frame-id id)]
|
||||
changes (prepare-duplicate-shape-change objects page-id unames update-unames! obj delta frame-id id)]
|
||||
(recur
|
||||
(into names (map change->name changes))
|
||||
(into result changes)
|
||||
(first cids)
|
||||
(rest cids)))))
|
||||
@@ -361,11 +359,13 @@
|
||||
children-changes))))
|
||||
|
||||
(defn- prepare-duplicate-frame-change
|
||||
[objects page-id names obj delta]
|
||||
[objects page-id unames update-unames! obj delta]
|
||||
(let [frame-id (uuid/next)
|
||||
frame-name (dwc/generate-unique-name names (:name obj))
|
||||
frame-name (dwc/generate-unique-name @unames (:name obj))
|
||||
_ (update-unames! frame-name)
|
||||
|
||||
sch (->> (map #(get objects %) (:shapes obj))
|
||||
(mapcat #(prepare-duplicate-shape-change objects page-id names % delta frame-id frame-id)))
|
||||
(mapcat #(prepare-duplicate-shape-change objects page-id unames update-unames! % delta frame-id frame-id)))
|
||||
|
||||
frame (-> obj
|
||||
(assoc :id frame-id)
|
||||
|
||||
@@ -7,10 +7,13 @@
|
||||
(ns app.main.data.workspace.svg-upload
|
||||
(:require
|
||||
[app.common.data :as d]
|
||||
[app.common.exceptions :as ex]
|
||||
[app.common.geom.matrix :as gmt]
|
||||
[app.common.geom.point :as gpt]
|
||||
[app.common.geom.shapes :as gsh]
|
||||
[app.common.math :as mth]
|
||||
[app.common.pages :as cp]
|
||||
[app.common.spec :refer [max-safe-int min-safe-int]]
|
||||
[app.common.uuid :as uuid]
|
||||
[app.main.data.workspace.changes :as dch]
|
||||
[app.main.data.workspace.common :as dwc]
|
||||
@@ -28,13 +31,36 @@
|
||||
(defonce default-circle {:r 0 :cx 0 :cy 0})
|
||||
(defonce default-image {:x 0 :y 0 :width 1 :height 1})
|
||||
|
||||
(defn- assert-valid-num [attr num]
|
||||
(when (or (nil? num)
|
||||
(mth/nan? num)
|
||||
(not (mth/finite? num))
|
||||
(>= num max-safe-int )
|
||||
(<= num min-safe-int))
|
||||
(ex/raise (str (d/name attr) " attribute invalid: " num)))
|
||||
|
||||
;; If the number is between 0-1 we round to 1 (same in negative form
|
||||
(cond
|
||||
(and (> num 0) (< num 1)) 1
|
||||
(and (< num 0) (> num -1)) -1
|
||||
:else num))
|
||||
|
||||
(defn- assert-valid-pos-num [attr num]
|
||||
(let [num (assert-valid-num attr num)]
|
||||
(when (< num 0)
|
||||
(ex/raise (str (d/name attr) " attribute invalid: " num)))
|
||||
num))
|
||||
|
||||
(defn- svg-dimensions [data]
|
||||
(let [width (get-in data [:attrs :width] 100)
|
||||
height (get-in data [:attrs :height] 100)
|
||||
viewbox (get-in data [:attrs :viewBox] (str "0 0 " width " " height))
|
||||
[x y width height] (->> (str/split viewbox " ")
|
||||
[x y width height] (->> (str/split viewbox #"\s+")
|
||||
(map d/parse-double))]
|
||||
[x y width height]))
|
||||
[(assert-valid-num :x x)
|
||||
(assert-valid-num :y y)
|
||||
(assert-valid-pos-num :width width)
|
||||
(assert-valid-pos-num :height height)]))
|
||||
|
||||
(defn tag->name
|
||||
"Given a tag returns its layer name"
|
||||
@@ -467,4 +493,6 @@
|
||||
(dwc/select-shapes (d/ordered-set root-id))))
|
||||
|
||||
(catch :default e
|
||||
(.error js/console "Error upload" e))))))
|
||||
(.error js/console "Error SVG" e)
|
||||
(rx/throw {:type :svg-parser
|
||||
:data e}))))))
|
||||
|
||||
@@ -67,19 +67,24 @@
|
||||
(rx/of
|
||||
(dch/update-shapes [id] #(assoc % :content content))
|
||||
(dwu/commit-undo-transaction)))))
|
||||
(rx/of (dws/deselect-shape id)
|
||||
(dwc/delete-shapes #{id})))))))
|
||||
|
||||
(when (some? id)
|
||||
(rx/of (dws/deselect-shape id)
|
||||
(dwc/delete-shapes #{id}))))))))
|
||||
|
||||
(defn initialize-editor-state
|
||||
[{:keys [id content] :as shape} decorator]
|
||||
(ptk/reify ::initialize-editor-state
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(update-in state [:workspace-editor-state id]
|
||||
(fn [_]
|
||||
(ted/create-editor-state
|
||||
(some->> content ted/import-content)
|
||||
decorator))))
|
||||
(let [text-state (some->> content ted/import-content)
|
||||
attrs (get-in state [:workspace-local :defaults :font])
|
||||
|
||||
editor (cond-> (ted/create-editor-state text-state decorator)
|
||||
(and (nil? content) (some? attrs))
|
||||
(ted/update-editor-current-block-data attrs))]
|
||||
(-> state
|
||||
(assoc-in [:workspace-editor-state id] editor))))
|
||||
|
||||
ptk/WatchEvent
|
||||
(watch [_ _ stream]
|
||||
@@ -101,6 +106,13 @@
|
||||
(update [_ state]
|
||||
(d/update-in-when state [:workspace-editor-state id] ted/editor-select-all))))
|
||||
|
||||
(defn cursor-to-end
|
||||
[{:keys [id] :as shape}]
|
||||
(ptk/reify ::cursor-to-end
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(d/update-in-when state [:workspace-editor-state id] ted/cursor-to-end))))
|
||||
|
||||
;; --- Helpers
|
||||
|
||||
(defn- shape-current-values
|
||||
@@ -193,8 +205,11 @@
|
||||
(when-not (some? (get-in state [:workspace-editor-state id]))
|
||||
(let [objects (wsh/lookup-page-objects state)
|
||||
shape (get objects id)
|
||||
update-node? (fn [node]
|
||||
(or (txt/is-text-node? node)
|
||||
(txt/is-paragraph-node? node)))
|
||||
|
||||
update-fn #(update-shape % txt/is-text-node? attrs/merge attrs)
|
||||
update-fn #(update-shape % update-node? attrs/merge attrs)
|
||||
shape-ids (cond (= (:type shape) :text) [id]
|
||||
(= (:type shape) :group) (cp/get-children id objects))]
|
||||
(rx/of (dch/update-shapes shape-ids update-fn)))))))
|
||||
@@ -309,3 +324,14 @@
|
||||
(rx/race resize-batch change-page)
|
||||
(rx/of #(dissoc % ::handling-texts))))
|
||||
(rx/empty))))))
|
||||
|
||||
(defn save-font
|
||||
[data]
|
||||
(ptk/reify ::save-font
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(let [multiple? (->> data vals (d/seek #(= % :multiple)))]
|
||||
(cond-> state
|
||||
(not multiple?)
|
||||
(assoc-in [:workspace-local :defaults :font] data))))))
|
||||
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
|
||||
(ns app.main.data.workspace.undo
|
||||
(:require
|
||||
[app.common.pages :as cp]
|
||||
[app.common.pages.spec :as spec]
|
||||
[app.common.spec :as us]
|
||||
[cljs.spec.alpha :as s]
|
||||
@@ -31,13 +30,13 @@
|
||||
(subvec undo (- cnt MAX-UNDO-SIZE))
|
||||
undo)))
|
||||
|
||||
;; TODO: Review the necessity of this method
|
||||
(defn materialize-undo
|
||||
[changes index]
|
||||
[_changes index]
|
||||
(ptk/reify ::materialize-undo
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(-> state
|
||||
(update :workspace-data cp/process-changes changes)
|
||||
(assoc-in [:workspace-undo :index] index)))))
|
||||
|
||||
(defn- add-undo-entry
|
||||
|
||||
@@ -14,19 +14,23 @@
|
||||
[app.util.timers :as timers]
|
||||
[rumext.alpha :as mf]))
|
||||
|
||||
(mf/defc editable-select [{:keys [value type options class on-change placeholder]}]
|
||||
(mf/defc editable-select [{:keys [value type options class on-change placeholder on-blur]}]
|
||||
(let [state (mf/use-state {:id (uuid/next)
|
||||
:is-open? false
|
||||
:current-value value
|
||||
:top nil
|
||||
:left nil
|
||||
:bottom nil})
|
||||
|
||||
emit-blur? (mf/use-ref nil)
|
||||
|
||||
open-dropdown #(swap! state assoc :is-open? true)
|
||||
close-dropdown #(swap! state assoc :is-open? false)
|
||||
select-item (fn [value]
|
||||
(fn [_]
|
||||
(swap! state assoc :current-value value)
|
||||
(when on-change (on-change value))))
|
||||
(when on-change (on-change value))
|
||||
(when on-blur (on-blur))))
|
||||
|
||||
as-key-value (fn [item] (if (map? item) [(:value item) (:label item)] [item item]))
|
||||
|
||||
@@ -55,21 +59,38 @@
|
||||
assoc
|
||||
:left left
|
||||
:top top
|
||||
:bottom bottom))))))]
|
||||
:bottom bottom))))))
|
||||
|
||||
handle-focus
|
||||
(mf/use-callback
|
||||
(fn []
|
||||
(mf/set-ref-val! emit-blur? false)))
|
||||
|
||||
handle-blur
|
||||
(mf/use-callback
|
||||
(fn []
|
||||
(mf/set-ref-val! emit-blur? true)
|
||||
(timers/schedule
|
||||
200
|
||||
(fn []
|
||||
(when (and on-blur (mf/ref-val emit-blur?)) (on-blur))))))]
|
||||
|
||||
(mf/use-effect
|
||||
(mf/deps value)
|
||||
#(reset! state {:current-value value}))
|
||||
(mf/deps value (:current-value @state))
|
||||
#(when (not= value (:current-value @state))
|
||||
(reset! state {:current-value value})))
|
||||
|
||||
(mf/use-effect
|
||||
(mf/deps options)
|
||||
#(reset! state {:is-open? false
|
||||
:current-value value}))
|
||||
(mf/deps (:is-open? @state))
|
||||
(fn []
|
||||
(mf/set-ref-val! emit-blur? (not (:is-open? @state)))))
|
||||
|
||||
[:div.editable-select {:class class
|
||||
:ref on-node-load}
|
||||
[:input.input-text {:value (or (-> @state :current-value value->label) "")
|
||||
:on-change handle-change-input
|
||||
:on-focus handle-focus
|
||||
:on-blur handle-blur
|
||||
:placeholder placeholder
|
||||
:type type}]
|
||||
[:span.dropdown-button {:on-click open-dropdown} i/arrow-down]
|
||||
|
||||
@@ -47,8 +47,8 @@
|
||||
text-align (:text-align data "start")
|
||||
grow-type (:grow-type shape)
|
||||
|
||||
base #js {:fontSize (str (:font-size txt/default-text-attrs) "px")
|
||||
:lineHeight (:line-height txt/default-text-attrs)
|
||||
base #js {:fontSize (str (:font-size data (:font-size txt/default-text-attrs)) "px")
|
||||
:lineHeight (:line-height data (:line-height txt/default-text-attrs))
|
||||
:margin "inherit"}]
|
||||
(cond-> base
|
||||
(some? line-height) (obj/set! "lineHeight" line-height)
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
(:require
|
||||
[app.main.data.messages :as dm]
|
||||
[app.main.data.workspace :as dw]
|
||||
[app.main.data.workspace.persistence :as dwp]
|
||||
[app.main.refs :as refs]
|
||||
[app.main.store :as st]
|
||||
[app.main.ui.context :as ctx]
|
||||
@@ -25,6 +26,7 @@
|
||||
[app.util.dom :as dom]
|
||||
[app.util.i18n :as i18n :refer [tr]]
|
||||
[app.util.object :as obj]
|
||||
[app.util.timers :as ts]
|
||||
[okulary.core :as l]
|
||||
[rumext.alpha :as mf]))
|
||||
|
||||
@@ -122,7 +124,10 @@
|
||||
(mf/deps project-id file-id)
|
||||
(fn []
|
||||
(st/emit! (dw/initialize-file project-id file-id))
|
||||
(st/emitf (dw/finalize-file project-id file-id))))
|
||||
(fn []
|
||||
;; Schedule to 100ms so we can do the update before the file is finalized
|
||||
(st/emit! ::dwp/force-persist)
|
||||
(ts/schedule 100 (st/emitf (dw/finalize-file project-id file-id))))))
|
||||
|
||||
(mf/use-effect
|
||||
(fn []
|
||||
|
||||
@@ -75,7 +75,11 @@
|
||||
(mf/use-callback (mf/deps file) #(st/emit! (dw/link-file-to-library (:id file) %)))
|
||||
|
||||
unlink-library
|
||||
(mf/use-callback (mf/deps file) #(st/emit! (dw/unlink-file-from-library (:id file) %)))]
|
||||
(mf/use-callback
|
||||
(mf/deps file)
|
||||
(fn [library-id]
|
||||
(st/emit! (dw/unlink-file-from-library (:id file) library-id)
|
||||
(dwl/sync-file (:id file) library-id))))]
|
||||
[:*
|
||||
[:div.section
|
||||
[:div.section-title (tr "workspace.libraries.in-this-file")]
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
(ns app.main.ui.workspace.shapes.text.editor
|
||||
(:require
|
||||
["draft-js" :as draft]
|
||||
[app.common.data :as d]
|
||||
[app.common.geom.shapes :as gsh]
|
||||
[app.common.text :as txt]
|
||||
[app.main.data.workspace :as dw]
|
||||
@@ -32,7 +33,7 @@
|
||||
(let [bprops (obj/get props "blockProps")
|
||||
data (obj/get bprops "data")
|
||||
style (sts/generate-paragraph-styles (obj/get bprops "shape")
|
||||
(obj/get bprops "data"))
|
||||
(obj/get bprops "data"))
|
||||
dir (:text-direction data "auto")]
|
||||
|
||||
|
||||
@@ -56,12 +57,36 @@
|
||||
:shape shape}}
|
||||
nil)))
|
||||
|
||||
(defn styles-fn [styles content]
|
||||
(if (= (.getText content) "")
|
||||
(-> (.getData content)
|
||||
(.toJS)
|
||||
(js->clj :keywordize-keys true)
|
||||
(sts/generate-text-styles))
|
||||
(-> (txt/styles-to-attrs styles)
|
||||
(sts/generate-text-styles))))
|
||||
|
||||
(def default-decorator
|
||||
(ted/create-decorator "PENPOT_SELECTION" selection-component))
|
||||
|
||||
(def empty-editor-state
|
||||
(ted/create-editor-state nil default-decorator))
|
||||
|
||||
(defn get-content-changes
|
||||
[old-state state]
|
||||
(let [old-blocks (js->clj (.toJS (.getBlockMap (.getCurrentContent ^js old-state)))
|
||||
:keywordize-keys false)
|
||||
new-blocks (js->clj (.toJS (.getBlockMap (.getCurrentContent ^js state)))
|
||||
|
||||
:keywordize-keys false)]
|
||||
(->> old-blocks
|
||||
(d/mapm
|
||||
(fn [bkey bstate]
|
||||
{:old (get bstate "text")
|
||||
:new (get-in new-blocks [bkey "text"])}))
|
||||
(filter #(contains? new-blocks (first %)))
|
||||
(into {}))))
|
||||
|
||||
(mf/defc text-shape-edit-html
|
||||
{::mf/wrap [mf/memo]
|
||||
::mf/wrap-props false
|
||||
@@ -106,10 +131,35 @@
|
||||
(fn [_]
|
||||
(reset! blured false)))
|
||||
|
||||
prev-value (mf/use-ref state)
|
||||
|
||||
;; Effect that keeps updated the `prev-value` reference
|
||||
_ (mf/use-effect
|
||||
(mf/deps state)
|
||||
#(mf/set-ref-val! prev-value state))
|
||||
|
||||
handle-change
|
||||
(mf/use-callback
|
||||
(fn [state]
|
||||
(let [old-state (mf/ref-val prev-value)]
|
||||
(if (and (some? state) (some? old-state))
|
||||
(let [block-states (get-content-changes old-state state)
|
||||
|
||||
block-to-add-styles
|
||||
(->> block-states
|
||||
(filter
|
||||
(fn [[_ v]]
|
||||
(and (not= (:old v) (:new v))
|
||||
(= (:old v) ""))))
|
||||
(mapv first))]
|
||||
(ted/apply-block-styles-to-content state block-to-add-styles))
|
||||
state))))
|
||||
|
||||
on-change
|
||||
(mf/use-callback
|
||||
(fn [val]
|
||||
(let [val (if (true? @blured)
|
||||
(let [val (handle-change val)
|
||||
val (if (true? @blured)
|
||||
(ted/add-editor-blur-selection val)
|
||||
(ted/remove-editor-blur-selection val))]
|
||||
(st/emit! (dwt/update-editor-state shape val)))))
|
||||
@@ -124,9 +174,27 @@
|
||||
handle-return
|
||||
(mf/use-callback
|
||||
(fn [_ state]
|
||||
(st/emit! (dwt/update-editor-state shape (ted/editor-split-block state)))
|
||||
(let [style (ted/get-editor-current-inline-styles state)
|
||||
state (-> (ted/insert-text state "\n" style)
|
||||
(handle-change))]
|
||||
(st/emit! (dwt/update-editor-state shape state)))
|
||||
"handled"))
|
||||
]
|
||||
|
||||
on-click
|
||||
(mf/use-callback
|
||||
(fn [event]
|
||||
(when (dom/class? (dom/get-target event) "DraftEditor-root")
|
||||
(st/emit! (dwt/cursor-to-end shape)))
|
||||
(st/emit! (dwt/focus-editor))))
|
||||
|
||||
handle-pasted-text
|
||||
(fn [text _ _]
|
||||
(let [style (ted/get-editor-current-inline-styles state)
|
||||
state (-> (ted/insert-text state text style)
|
||||
(handle-change))]
|
||||
(st/emit! (dwt/update-editor-state shape state)))
|
||||
|
||||
"handled")]
|
||||
|
||||
(mf/use-layout-effect on-mount)
|
||||
|
||||
@@ -135,7 +203,7 @@
|
||||
:style {:cursor cur/text
|
||||
:width (:width shape)
|
||||
:height (:height shape)}
|
||||
:on-click (st/emitf (dwt/focus-editor))
|
||||
:on-click on-click
|
||||
:class (dom/classnames
|
||||
:align-top (= (:vertical-align content "top") "top")
|
||||
:align-center (= (:vertical-align content) "center")
|
||||
@@ -146,9 +214,8 @@
|
||||
:on-focus on-focus
|
||||
:handle-return handle-return
|
||||
:strip-pasted-styles true
|
||||
:custom-style-fn (fn [styles _]
|
||||
(-> (txt/styles-to-attrs styles)
|
||||
(sts/generate-text-styles)))
|
||||
:handle-pasted-text handle-pasted-text
|
||||
:custom-style-fn styles-fn
|
||||
:block-renderer-fn #(render-block % shape)
|
||||
:ref on-editor
|
||||
:editor-state state}]]))
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
[app.main.ui.workspace.sidebar.options.menus.typography :refer [typography-entry typography-options]]
|
||||
[app.util.dom :as dom]
|
||||
[app.util.i18n :as i18n :refer [tr]]
|
||||
[app.util.timers :as tm]
|
||||
[cuerdas.core :as str]
|
||||
[rumext.alpha :as mf]))
|
||||
|
||||
@@ -80,11 +81,12 @@
|
||||
(def attrs (d/concat #{} shape-attrs root-attrs paragraph-attrs text-attrs))
|
||||
|
||||
(mf/defc text-align-options
|
||||
[{:keys [values on-change] :as props}]
|
||||
[{:keys [values on-change on-blur] :as props}]
|
||||
(let [{:keys [text-align]} values
|
||||
handle-change
|
||||
(fn [_ new-align]
|
||||
(on-change {:text-align new-align}))]
|
||||
(on-change {:text-align new-align})
|
||||
(when (some? on-blur) (on-blur)))]
|
||||
|
||||
;; --- Align
|
||||
[:div.align-icons
|
||||
@@ -110,10 +112,12 @@
|
||||
i/text-align-justify]]))
|
||||
|
||||
(mf/defc text-direction-options
|
||||
[{:keys [values on-change] :as props}]
|
||||
[{:keys [values on-change on-blur] :as props}]
|
||||
(let [direction (:text-direction values)
|
||||
handle-change (fn [_ val]
|
||||
(on-change {:text-direction val}))]
|
||||
handle-change
|
||||
(fn [_ val]
|
||||
(on-change {:text-direction val})
|
||||
(when (some? on-blur) (on-blur)))]
|
||||
;; --- Align
|
||||
[:div.align-icons
|
||||
[:span.tooltip.tooltip-bottom-left
|
||||
@@ -128,12 +132,13 @@
|
||||
i/text-direction-rtl]]))
|
||||
|
||||
(mf/defc vertical-align
|
||||
[{:keys [values on-change] :as props}]
|
||||
[{:keys [values on-change on-blur] :as props}]
|
||||
(let [{:keys [vertical-align]} values
|
||||
vertical-align (or vertical-align "top")
|
||||
handle-change
|
||||
(fn [_ new-align]
|
||||
(on-change {:vertical-align new-align}))]
|
||||
(on-change {:vertical-align new-align})
|
||||
(when (some? on-blur) (on-blur)))]
|
||||
|
||||
[:div.align-icons
|
||||
[:span.tooltip.tooltip-bottom-left
|
||||
@@ -153,11 +158,12 @@
|
||||
i/align-bottom]]))
|
||||
|
||||
(mf/defc grow-options
|
||||
[{:keys [ids values] :as props}]
|
||||
[{:keys [ids values on-blur] :as props}]
|
||||
(let [grow-type (:grow-type values)
|
||||
handle-change-grow
|
||||
(fn [_ grow-type]
|
||||
(st/emit! (dch/update-shapes ids #(assoc % :grow-type grow-type))))]
|
||||
(st/emit! (dch/update-shapes ids #(assoc % :grow-type grow-type)))
|
||||
(when (some? on-blur) (on-blur)))]
|
||||
|
||||
[:div.align-icons
|
||||
[:span.tooltip.tooltip-bottom
|
||||
@@ -177,11 +183,12 @@
|
||||
i/auto-height]]))
|
||||
|
||||
(mf/defc text-decoration-options
|
||||
[{:keys [values on-change] :as props}]
|
||||
[{:keys [values on-change on-blur] :as props}]
|
||||
(let [text-decoration (or (:text-decoration values) "none")
|
||||
handle-change
|
||||
(fn [_ type]
|
||||
(on-change {:text-decoration type}))]
|
||||
(on-change {:text-decoration type})
|
||||
(when (some? on-blur) (on-blur)))]
|
||||
[:div.align-icons
|
||||
[:span.tooltip.tooltip-bottom
|
||||
{:alt (tr "workspace.options.text-options.none")
|
||||
@@ -220,7 +227,11 @@
|
||||
|
||||
emit-update!
|
||||
(mf/use-callback
|
||||
(mf/deps values)
|
||||
(fn [id attrs]
|
||||
(st/emit! (dwt/save-font (-> (merge txt/default-text-attrs values attrs)
|
||||
(select-keys text-attrs))))
|
||||
|
||||
(let [attrs (select-keys attrs root-attrs)]
|
||||
(when-not (empty? attrs)
|
||||
(st/emit! (dwt/update-root-attrs {:id id :attrs attrs}))))
|
||||
@@ -235,7 +246,7 @@
|
||||
|
||||
on-change
|
||||
(mf/use-callback
|
||||
(mf/deps ids)
|
||||
(mf/deps ids emit-update!)
|
||||
(fn [attrs]
|
||||
(run! #(emit-update! % attrs) ids)))
|
||||
|
||||
@@ -287,7 +298,15 @@
|
||||
|
||||
opts #js {:ids ids
|
||||
:values values
|
||||
:on-change on-change}]
|
||||
:on-change on-change
|
||||
:on-blur
|
||||
(fn []
|
||||
(tm/schedule
|
||||
100
|
||||
(fn []
|
||||
(when (not= "INPUT" (-> (dom/get-active) (dom/get-tag-name)))
|
||||
(let [node (dom/get-element-by-class "public-DraftEditor-content")]
|
||||
(dom/focus! node))))))}]
|
||||
|
||||
[:div.element-set
|
||||
[:div.element-set-title
|
||||
|
||||
@@ -230,7 +230,7 @@
|
||||
:current? (= (:id font) (:id selected))}])))
|
||||
|
||||
(mf/defc font-options
|
||||
[{:keys [values on-change] :as props}]
|
||||
[{:keys [values on-change on-blur] :as props}]
|
||||
(let [{:keys [font-id font-size font-variant-id]} values
|
||||
|
||||
font-id (or font-id (:font-id txt/default-text-attrs))
|
||||
@@ -271,18 +271,26 @@
|
||||
:font-family (:family font)
|
||||
:font-variant-id new-variant-id
|
||||
:font-weight (:weight variant)
|
||||
:font-style (:style variant)}))))
|
||||
:font-style (:style variant)})
|
||||
(dom/blur! (dom/get-target event)))))
|
||||
|
||||
on-font-select
|
||||
(mf/use-callback
|
||||
(mf/deps change-font)
|
||||
(fn [font*]
|
||||
(when (not= font font*)
|
||||
(change-font (:id font*)))))
|
||||
(change-font (:id font*)))
|
||||
|
||||
(when (some? on-blur)
|
||||
(on-blur))))
|
||||
|
||||
on-font-selector-close
|
||||
(mf/use-callback
|
||||
#(reset! open-selector? false))]
|
||||
(fn []
|
||||
(reset! open-selector? false)
|
||||
(when (some? on-blur)
|
||||
(on-blur))
|
||||
))]
|
||||
|
||||
[:*
|
||||
(when @open-selector?
|
||||
@@ -314,12 +322,14 @@
|
||||
:options size-options
|
||||
:type "number"
|
||||
:placeholder "--"
|
||||
:on-change on-font-size-change}])
|
||||
:on-change on-font-size-change
|
||||
:on-blur on-blur}])
|
||||
|
||||
[:select.input-select.variant-option
|
||||
{:disabled (= font-id :multiple)
|
||||
:value (attr->string font-variant-id)
|
||||
:on-change on-font-variant-change}
|
||||
:on-change on-font-variant-change
|
||||
:on-blur on-blur}
|
||||
(when (or (= font-id :multiple) (= font-variant-id :multiple))
|
||||
[:option {:value ""} "--"])
|
||||
(for [variant (:variants font)]
|
||||
@@ -329,7 +339,7 @@
|
||||
|
||||
|
||||
(mf/defc spacing-options
|
||||
[{:keys [values on-change] :as props}]
|
||||
[{:keys [values on-change on-blur] :as props}]
|
||||
(let [{:keys [line-height
|
||||
letter-spacing]} values
|
||||
|
||||
@@ -353,7 +363,8 @@
|
||||
:max "200"
|
||||
:value (attr->string line-height)
|
||||
:placeholder (tr "settings.multiple")
|
||||
:on-change #(handle-change % :line-height)}]]
|
||||
:on-change #(handle-change % :line-height)
|
||||
:on-blur on-blur}]]
|
||||
|
||||
[:div.input-icon
|
||||
[:span.icon-before.tooltip.tooltip-bottom
|
||||
@@ -366,18 +377,21 @@
|
||||
:max "200"
|
||||
:value (attr->string letter-spacing)
|
||||
:placeholder (tr "settings.multiple")
|
||||
:on-change #(handle-change % :letter-spacing)}]]]))
|
||||
:on-change #(handle-change % :letter-spacing)
|
||||
:on-blur on-blur}]]]))
|
||||
|
||||
(mf/defc text-transform-options
|
||||
[{:keys [values on-change] :as props}]
|
||||
[{:keys [values on-change on-blur] :as props}]
|
||||
(let [text-transform (or (:text-transform values) "none")
|
||||
handle-change
|
||||
(fn [_ type]
|
||||
(on-change {:text-transform type}))]
|
||||
(on-change {:text-transform type})
|
||||
(when (some? on-blur) (on-blur)))]
|
||||
[:div.align-icons
|
||||
[:span.tooltip.tooltip-bottom
|
||||
{:alt (tr "workspace.options.text-options.none")
|
||||
:class (dom/classnames :current (= "none" text-transform))
|
||||
:on-focus #(dom/prevent-default %)
|
||||
:on-click #(handle-change % "none")}
|
||||
i/minus]
|
||||
[:span.tooltip.tooltip-bottom
|
||||
@@ -397,11 +411,12 @@
|
||||
i/titlecase]]))
|
||||
|
||||
(mf/defc typography-options
|
||||
[{:keys [ids editor values on-change]}]
|
||||
[{:keys [ids editor values on-change on-blur]}]
|
||||
(let [opts #js {:editor editor
|
||||
:ids ids
|
||||
:values values
|
||||
:on-change on-change}]
|
||||
:on-change on-change
|
||||
:on-blur on-blur}]
|
||||
[:div.element-set-content
|
||||
[:> font-options opts]
|
||||
[:div.row-flex
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
[app.main.refs :as refs]
|
||||
[app.main.ui.workspace.sidebar.options.menus.blur :refer [blur-menu]]
|
||||
[app.main.ui.workspace.sidebar.options.menus.constraints :refer [constraint-attrs constraints-menu]]
|
||||
[app.main.ui.workspace.sidebar.options.menus.fill :refer [fill-menu]]
|
||||
[app.main.ui.workspace.sidebar.options.menus.fill :refer [fill-menu fill-attrs]]
|
||||
[app.main.ui.workspace.sidebar.options.menus.layer :refer [layer-attrs layer-menu]]
|
||||
[app.main.ui.workspace.sidebar.options.menus.measures :refer [measure-attrs measures-menu]]
|
||||
[app.main.ui.workspace.sidebar.options.menus.shadow :refer [shadow-menu]]
|
||||
@@ -42,6 +42,7 @@
|
||||
|
||||
text-values (d/merge
|
||||
(select-keys shape [:grow-type])
|
||||
(select-keys shape fill-attrs)
|
||||
(dwt/current-root-values
|
||||
{:shape shape
|
||||
:attrs root-attrs})
|
||||
|
||||
@@ -108,14 +108,14 @@
|
||||
;; Draw Mode
|
||||
[:div.viewport-actions-entry.tooltip.tooltip-bottom
|
||||
{:class (when (= edit-mode :draw) "is-toggled")
|
||||
:alt (tr "workspace.path.actions.move-nodes" (sc/get-tooltip :move-nodes))
|
||||
:alt (tr "workspace.path.actions.draw-nodes" (sc/get-tooltip :draw-nodes))
|
||||
:on-click on-select-draw-mode}
|
||||
i/pen]
|
||||
|
||||
;; Edit mode
|
||||
[:div.viewport-actions-entry.tooltip.tooltip-bottom
|
||||
{:class (when (= edit-mode :move) "is-toggled")
|
||||
:alt (tr "workspace.path.actions.draw-nodes" (sc/get-tooltip :draw-nodes))
|
||||
:alt (tr "workspace.path.actions.move-nodes" (sc/get-tooltip :move-nodes))
|
||||
:on-click on-select-edit-mode}
|
||||
i/pointer-inner]]
|
||||
|
||||
|
||||
@@ -95,10 +95,12 @@
|
||||
on-mouse-down
|
||||
(mf/use-callback
|
||||
(mf/deps (:id frame) on-frame-select)
|
||||
(fn [event]
|
||||
(dom/prevent-default event)
|
||||
(dom/stop-propagation event)
|
||||
(on-frame-select event (:id frame))))
|
||||
(fn [bevent]
|
||||
(let [event (.-nativeEvent bevent)]
|
||||
(when (= 1 (.-which event))
|
||||
(dom/prevent-default event)
|
||||
(dom/stop-propagation event)
|
||||
(on-frame-select event (:id frame))))))
|
||||
|
||||
on-double-click
|
||||
(mf/use-callback
|
||||
|
||||
@@ -98,7 +98,7 @@
|
||||
(defn get-attribute
|
||||
"Extract the value of one attribute of a dom node."
|
||||
[node attr-name]
|
||||
(.getAttribute node attr-name))
|
||||
(.getAttribute ^js node attr-name))
|
||||
|
||||
(def get-target-val (comp get-value get-target))
|
||||
|
||||
@@ -223,11 +223,13 @@
|
||||
|
||||
(defn focus!
|
||||
[node]
|
||||
(.focus node))
|
||||
(when (some? node)
|
||||
(.focus node)))
|
||||
|
||||
(defn blur!
|
||||
[node]
|
||||
(.blur node))
|
||||
(when (some? node)
|
||||
(.blur node)))
|
||||
|
||||
(defn fullscreen?
|
||||
[]
|
||||
@@ -291,8 +293,11 @@
|
||||
(defn get-user-agent []
|
||||
(.-userAgent globals/navigator))
|
||||
|
||||
(defn get-active []
|
||||
(.-activeElement globals/document))
|
||||
|
||||
(defn active? [node]
|
||||
(= (.-activeElement globals/document) node))
|
||||
(= (get-active) node))
|
||||
|
||||
(defn get-data [^js node ^string attr]
|
||||
(.getAttribute node (str "data-" attr)))
|
||||
@@ -377,5 +382,5 @@
|
||||
(trigger-download-uri filename mtype uri)))
|
||||
|
||||
(defn left-mouse? [bevent]
|
||||
(let [event (.-nativeEvent bevent)]
|
||||
(let [event (.-nativeEvent ^js bevent)]
|
||||
(= 1 (.-which event))))
|
||||
|
||||
@@ -139,8 +139,8 @@
|
||||
(d/without-keys dissoc-attrs)
|
||||
(assoc :type :path)
|
||||
(assoc :content new-content)
|
||||
(cond-> (= :image type)
|
||||
(assoc :fill-image metadata))))
|
||||
(cond-> (= :image type) (-> (assoc :fill-image metadata)
|
||||
(dissoc :metadata)))))
|
||||
;; Do nothing if the shape is not of a correct type
|
||||
shape))
|
||||
|
||||
|
||||
@@ -70,8 +70,11 @@
|
||||
|
||||
(defn get-editor-current-inline-styles
|
||||
[state]
|
||||
(-> (.getCurrentInlineStyle ^js state)
|
||||
(txt/styles-to-attrs)))
|
||||
(if (impl/isCurrentEmpty state)
|
||||
(let [block (impl/getCurrentBlock state)]
|
||||
(get-editor-block-data block))
|
||||
(-> (.getCurrentInlineStyle ^js state)
|
||||
(txt/styles-to-attrs))))
|
||||
|
||||
(defn update-editor-current-block-data
|
||||
[state attrs]
|
||||
@@ -79,7 +82,18 @@
|
||||
|
||||
(defn update-editor-current-inline-styles
|
||||
[state attrs]
|
||||
(impl/applyInlineStyle state (txt/attrs-to-styles attrs)))
|
||||
(let [update-blocks
|
||||
(fn [state block-key]
|
||||
(if (empty? (impl/getBlockContent state block-key))
|
||||
(impl/updateBlockData state block-key (clj->js attrs))
|
||||
|
||||
(let [attrs (-> (impl/getInlineStyle state block-key 0)
|
||||
(txt/styles-to-attrs))]
|
||||
(impl/updateBlockData state block-key (clj->js attrs)))))
|
||||
|
||||
state (impl/applyInlineStyle state (txt/attrs-to-styles attrs))
|
||||
selected (impl/getSelectedBlocks state)]
|
||||
(reduce update-blocks state selected)))
|
||||
|
||||
(defn editor-split-block
|
||||
[state]
|
||||
@@ -92,3 +106,27 @@
|
||||
(defn remove-editor-blur-selection
|
||||
[state]
|
||||
(impl/removeBlurSelectionEntity state))
|
||||
|
||||
(defn cursor-to-end
|
||||
[state]
|
||||
(impl/cursorToEnd state))
|
||||
|
||||
(defn apply-block-styles-to-content
|
||||
[state blocks]
|
||||
(if (empty? blocks)
|
||||
state
|
||||
(let [selection (impl/getSelection state)
|
||||
redfn
|
||||
(fn [state bkey]
|
||||
(let [attrs (-> (impl/getBlockData state bkey)
|
||||
(js->clj :keywordize-keys true))]
|
||||
(-> state
|
||||
(impl/selectBlock bkey)
|
||||
(impl/applyInlineStyle (txt/attrs-to-styles attrs)))))]
|
||||
(as-> state $
|
||||
(reduce redfn $ blocks)
|
||||
(impl/setSelection $ selection)))))
|
||||
|
||||
(defn insert-text [state text attrs]
|
||||
(let [style (txt/attrs-to-styles attrs)]
|
||||
(impl/insertText state text (clj->js attrs) (clj->js style))))
|
||||
|
||||
@@ -9,19 +9,39 @@
|
||||
'use strict';
|
||||
|
||||
import {
|
||||
BlockMapBuilder,
|
||||
CharacterMetadata,
|
||||
EditorState,
|
||||
CompositeDecorator,
|
||||
EditorState,
|
||||
Modifier,
|
||||
RichTextEditorUtil,
|
||||
SelectionState,
|
||||
Modifier
|
||||
} from "draft-js";
|
||||
|
||||
import {Map} from "immutable";
|
||||
import DraftPasteProcessor from 'draft-js/lib/DraftPasteProcessor';
|
||||
import {Map, OrderedSet} from "immutable";
|
||||
|
||||
function isDefined(v) {
|
||||
return v !== undefined && v !== null;
|
||||
}
|
||||
|
||||
function mergeBlockData(block, newData) {
|
||||
let data = block.getData();
|
||||
|
||||
for (let key of Object.keys(newData)) {
|
||||
const oldVal = data.get(key);
|
||||
if (oldVal === newData[key]) {
|
||||
data = data.delete(key);
|
||||
} else {
|
||||
data = data.set(key, newData[key]);
|
||||
}
|
||||
}
|
||||
|
||||
return block.mergeDeep({
|
||||
data: data
|
||||
});
|
||||
}
|
||||
|
||||
export function createEditorState(content, decorator) {
|
||||
if (content === null) {
|
||||
return EditorState.createEmpty(decorator);
|
||||
@@ -56,6 +76,18 @@ function getSelectAllSelection(state) {
|
||||
});
|
||||
}
|
||||
|
||||
function getCursorInEndPosition(state) {
|
||||
const content = state.getCurrentContent();
|
||||
const lastBlock = content.getBlockMap().last();
|
||||
|
||||
return new SelectionState({
|
||||
"anchorKey": lastBlock.getKey(),
|
||||
"anchorOffset": lastBlock.getLength(),
|
||||
"focusKey": lastBlock.getKey(),
|
||||
"focusOffset": lastBlock.getLength()
|
||||
});
|
||||
}
|
||||
|
||||
export function selectAll(state) {
|
||||
return EditorState.forceSelection(state, getSelectAllSelection(state));
|
||||
}
|
||||
@@ -83,43 +115,38 @@ export function updateCurrentBlockData(state, attrs) {
|
||||
let content = state.getCurrentContent();
|
||||
|
||||
content = modifySelectedBlocks(content, selection, (block) => {
|
||||
let data = block.getData();
|
||||
for (let key of Object.keys(attrs)) {
|
||||
const oldVal = data.get(key);
|
||||
if (oldVal === attrs[key]) {
|
||||
data = data.delete(key);
|
||||
} else {
|
||||
data = data.set(key, attrs[key]);
|
||||
}
|
||||
}
|
||||
|
||||
return block.merge({
|
||||
data: data
|
||||
});
|
||||
return mergeBlockData(block, attrs);
|
||||
});
|
||||
|
||||
return EditorState.push(state, content, "change-block-data");
|
||||
}
|
||||
|
||||
export function applyInlineStyle(state, styles) {
|
||||
const selection = state.getSelection();
|
||||
const userSelection = state.getSelection();
|
||||
let selection = userSelection;
|
||||
|
||||
if (selection.isCollapsed()) {
|
||||
selection = getSelectAllSelection(state);
|
||||
}
|
||||
|
||||
let result = state;
|
||||
let content = null;
|
||||
|
||||
for (let style of styles) {
|
||||
const [p, k, v] = style.split("$$$");
|
||||
const prefix = [p, k, ""].join("$$$");
|
||||
|
||||
content = state.getCurrentContent();
|
||||
content = result.getCurrentContent();
|
||||
content = removeInlineStylePrefix(content, selection, prefix);
|
||||
|
||||
if (v !== "z:null") {
|
||||
content = Modifier.applyInlineStyle(content, selection, style);
|
||||
}
|
||||
|
||||
state = EditorState.push(state, content, "change-inline-style");
|
||||
result = EditorState.push(result, content, "change-inline-style");
|
||||
}
|
||||
|
||||
return state;
|
||||
return EditorState.acceptSelection(result, userSelection);
|
||||
}
|
||||
|
||||
export function splitBlockPreservingData(state) {
|
||||
@@ -209,3 +236,143 @@ export function removeInlineStylePrefix(contentState, selectionState, stylePrefi
|
||||
return block.set("characterList", chars);
|
||||
});
|
||||
}
|
||||
|
||||
export function cursorToEnd(state) {
|
||||
const newSelection = getCursorInEndPosition(state);
|
||||
const selection = state.getSelection();
|
||||
|
||||
let content = state.getCurrentContent();
|
||||
content = Modifier.applyEntity(content, newSelection, null);
|
||||
|
||||
state = EditorState.forceSelection(state, newSelection);
|
||||
state = EditorState.push(state, content, "apply-entity");
|
||||
|
||||
return state;
|
||||
}
|
||||
|
||||
export function isCurrentEmpty(state) {
|
||||
const selection = state.getSelection();
|
||||
|
||||
if (!selection.isCollapsed()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const blockKey = selection.getStartKey();
|
||||
const content = state.getCurrentContent();
|
||||
|
||||
const block = content.getBlockForKey(blockKey);
|
||||
|
||||
return block.getText() === "";
|
||||
}
|
||||
|
||||
/*
|
||||
Returns the block keys between a selection
|
||||
*/
|
||||
export function getSelectedBlocks(state) {
|
||||
const selection = state.getSelection();
|
||||
const startKey = selection.getStartKey();
|
||||
const endKey = selection.getEndKey();
|
||||
const content = state.getCurrentContent();
|
||||
const result = [ startKey ];
|
||||
|
||||
let currentKey = startKey;
|
||||
|
||||
while (currentKey !== endKey) {
|
||||
const currentBlock = content.getBlockAfter(currentKey);
|
||||
currentKey = currentBlock.getKey();
|
||||
result.push(currentKey);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
export function getBlockContent(state, blockKey) {
|
||||
const content = state.getCurrentContent();
|
||||
const block = content.getBlockForKey(blockKey);
|
||||
return block.getText();
|
||||
}
|
||||
|
||||
export function getBlockData(state, blockKey) {
|
||||
const content = state.getCurrentContent();
|
||||
const block = content.getBlockForKey(blockKey);
|
||||
return block && block.getData().toJS();
|
||||
}
|
||||
|
||||
export function updateBlockData(state, blockKey, data) {
|
||||
const userSelection = state.getSelection();
|
||||
const content = state.getCurrentContent();
|
||||
const block = content.getBlockForKey(blockKey);
|
||||
const newBlock = mergeBlockData(block, data);
|
||||
|
||||
const blockData = newBlock.getData();
|
||||
|
||||
const newContent = Modifier.setBlockData(
|
||||
state.getCurrentContent(),
|
||||
SelectionState.createEmpty(blockKey),
|
||||
blockData
|
||||
);
|
||||
|
||||
const result = EditorState.push(state, newContent, 'change-block-data');
|
||||
return EditorState.acceptSelection(result, userSelection);
|
||||
}
|
||||
|
||||
export function getSelection(state) {
|
||||
return state.getSelection();
|
||||
}
|
||||
|
||||
export function setSelection(state, selection) {
|
||||
return EditorState.acceptSelection(state, selection);
|
||||
}
|
||||
|
||||
export function selectBlock(state, blockKey) {
|
||||
const block = state.getCurrentContent().getBlockForKey(blockKey);
|
||||
const length = block.getText().length;
|
||||
const selection = SelectionState.createEmpty(blockKey).merge({
|
||||
focusOffset: length
|
||||
});
|
||||
return EditorState.acceptSelection(state, selection);
|
||||
}
|
||||
|
||||
export function getInlineStyle(state, blockKey, offset) {
|
||||
const content = state.getCurrentContent();
|
||||
const block = content.getBlockForKey(blockKey);
|
||||
return block.getInlineStyleAt(offset).toJS();
|
||||
}
|
||||
|
||||
const NEWLINE_REGEX = /\r\n?|\n/g;
|
||||
|
||||
function splitTextIntoTextBlocks(text) {
|
||||
return text.split(NEWLINE_REGEX);
|
||||
}
|
||||
|
||||
export function insertText(state, text, attrs, inlineStyles) {
|
||||
const blocks = splitTextIntoTextBlocks(text);
|
||||
|
||||
const character = CharacterMetadata.create({style: OrderedSet(inlineStyles)});
|
||||
|
||||
let blockArray = DraftPasteProcessor.processText(
|
||||
blocks,
|
||||
character,
|
||||
"unstyled",
|
||||
);
|
||||
|
||||
blockArray = blockArray.map((b) => {
|
||||
if (b.getText() === "") {
|
||||
return mergeBlockData(b, attrs)
|
||||
}
|
||||
return b;
|
||||
});
|
||||
|
||||
const fragment = BlockMapBuilder.createFromArray(blockArray);
|
||||
const content = state.getCurrentContent();
|
||||
const selection = state.getSelection();
|
||||
|
||||
const newContent = Modifier.replaceWithFragment(
|
||||
content,
|
||||
selection,
|
||||
fragment
|
||||
);
|
||||
|
||||
const resultSelection = SelectionState.createEmpty(selection.getStartKey());
|
||||
return EditorState.push(state, newContent, 'insert-fragment');
|
||||
}
|
||||
|
||||
@@ -89,11 +89,11 @@
|
||||
|
||||
;; NOTE: the group name depends on having executed
|
||||
;; the previous test.
|
||||
(t/is (= (:name group) "Component-2"))
|
||||
(t/is (= (:name group) "Component-1"))
|
||||
(t/is (= (:name shape1) "Rect 1"))
|
||||
(t/is (= (:name shape2) "Rect 2"))
|
||||
(t/is (= (:name component) "Component-2"))
|
||||
(t/is (= (:name c-group) "Component-2"))
|
||||
(t/is (= (:name component) "Component-1"))
|
||||
(t/is (= (:name c-group) "Component-1"))
|
||||
(t/is (= (:name c-shape1) "Rect 1"))
|
||||
(t/is (= (:name c-shape2) "Rect 2"))
|
||||
|
||||
@@ -139,11 +139,11 @@
|
||||
|
||||
(t/is (= (:name shape1) "Rect 1"))
|
||||
(t/is (= (:name shape2) "Rect 2"))
|
||||
(t/is (= (:name group) "Group-3"))
|
||||
(t/is (= (:name component) "Group-3"))
|
||||
(t/is (= (:name group) "Group-1"))
|
||||
(t/is (= (:name component) "Group-1"))
|
||||
(t/is (= (:name c-shape1) "Rect 1"))
|
||||
(t/is (= (:name c-shape2) "Rect 2"))
|
||||
(t/is (= (:name c-group) "Group-3"))
|
||||
(t/is (= (:name c-group) "Group-1"))
|
||||
|
||||
(thl/is-from-file group file))))
|
||||
|
||||
@@ -234,7 +234,7 @@
|
||||
new-component-id)]
|
||||
|
||||
(t/is (= (:name component2)
|
||||
"Component-6")))))
|
||||
"Component-2")))))
|
||||
|
||||
(rx/subs
|
||||
done
|
||||
@@ -322,9 +322,9 @@
|
||||
|
||||
(t/is (not= (:id instance1) (:id instance2)))
|
||||
(t/is (= (:id component) component-id))
|
||||
(t/is (= (:name instance2) "Component-8"))
|
||||
(t/is (= (:name instance2) "Component-2"))
|
||||
(t/is (= (:name shape2) "Rect 1"))
|
||||
(t/is (= (:name c-instance2) "Component-7"))
|
||||
(t/is (= (:name c-instance2) "Component-1"))
|
||||
(t/is (= (:name c-shape2) "Rect 1")))))
|
||||
|
||||
(rx/subs
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
[app.common.pages :as cp]
|
||||
[app.common.pages.helpers :as cph]
|
||||
[app.main.data.workspace :as dw]
|
||||
[app.main.data.workspace.state-helpers :as wsh]
|
||||
[app.main.data.workspace.groups :as dwg]
|
||||
[app.main.data.workspace.libraries-helpers :as dwlh]))
|
||||
|
||||
@@ -79,7 +80,7 @@
|
||||
:obj shape}]))))
|
||||
|
||||
(defn group-shapes
|
||||
([state label ids] (group-shapes state label ids "Group-"))
|
||||
([state label ids] (group-shapes state label ids "Group"))
|
||||
([state label ids prefix]
|
||||
(let [page (current-page state)
|
||||
shapes (dwg/shapes-for-grouping (:objects page) ids)]
|
||||
@@ -94,9 +95,9 @@
|
||||
|
||||
(defn make-component
|
||||
[state label ids]
|
||||
(let [page (current-page state)
|
||||
objects (wsh/lookup-page-objects state page-id)
|
||||
shapes (dwg/shapes-for-grouping objects ids)
|
||||
(let [page (current-page state)
|
||||
objects (wsh/lookup-page-objects state (:id page))
|
||||
shapes (dwg/shapes-for-grouping objects ids)
|
||||
|
||||
[group rchanges uchanges]
|
||||
(dwlh/generate-add-component shapes
|
||||
|
||||
@@ -4258,10 +4258,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.15.1:
|
||||
version "2.15.1"
|
||||
resolved "https://registry.yarnpkg.com/shadow-cljs/-/shadow-cljs-2.15.1.tgz#9f4b642efafeb84480396f46af2a8e59351d5986"
|
||||
integrity sha512-X0ueBJksdBg5FIuFOFguyZtAP9gzZZI6lmednxQ/eOsN9tGhpTXh5Y8/7lGzkfIFXxONe9THZx4f2m4JX5jBYA==
|
||||
shadow-cljs@2.15.2:
|
||||
version "2.15.2"
|
||||
resolved "https://registry.yarnpkg.com/shadow-cljs/-/shadow-cljs-2.15.2.tgz#195ad2cc45d3334920e629721f06c6d63802b1ac"
|
||||
integrity sha512-WPlSMkGgbU5b2nrt+Y1A1TsPs5Rip/JvCxGG2t2Pvzo+pLJ+RcpkZgAxjNQNNA7VYWEh5Pqwyvq5KzQ+0LMsxw==
|
||||
dependencies:
|
||||
node-libs-browser "^2.2.1"
|
||||
readline-sync "^1.4.7"
|
||||
|
||||
@@ -1 +1 @@
|
||||
1.7.0-alpha
|
||||
1.7.2-alpha
|
||||
|
||||
Reference in New Issue
Block a user