Compare commits

...

60 Commits

Author SHA1 Message Date
Andrés Moya
3435684c87 Merge branch 'staging' 2021-08-04 09:36:56 +02:00
Andrey Antukh
bed702d8de 🐛 Fix font uploading (related to storage internal changes). 2021-08-03 09:48:37 +02:00
Andrey Antukh
e4f755416d 🐛 Fix backward compatibility introduced in previous commit.
Related to stroage.
2021-07-29 16:44:25 +02:00
Andrey Antukh
4d5b0731be 📎 Prepare 1.7.2-alpha release. 2021-07-29 14:54:30 +02:00
Andrey Antukh
fde6ea1c83 Merge branch 'main' into staging 2021-07-29 14:44:37 +02:00
Andrey Antukh
7a94a2f087 🐛 Fix default storage config on docker images compose file. 2021-07-29 14:36:03 +02:00
Andrey Antukh
97b8f742dd 🐛 Fix exporter bug on docker images. 2021-07-29 13:05:39 +02:00
Andrey Antukh
06733ea7cd 🐛 Fix exporter bug on docker images. 2021-07-29 12:59:24 +02:00
Andrey Antukh
efa5120fac Fix inconsistencies on storage backend usage. 2021-07-29 12:59:24 +02:00
Andrés Moya
80ab6bbda2 🐛 Fix linter error 2021-07-28 16:23:15 +02:00
Andrés Moya
53620b9f1b 🐛 Fix tooltip errors:move nodes and draw nodes are swapped
From PR https://github.com/penpot/penpot/pull/1100 by @soultipsy
2021-07-28 16:15:56 +02:00
Andrés Moya
259b405526 Detach all assets when unlinking an external lib 2021-07-28 13:48:52 +02:00
Andrés Moya
c6fe19c321 🐛 Protect against broken component refs #1114 2021-07-28 13:48:52 +02:00
alonso.torres
9d545004cb 🐛 Fix problem with pasting text into text editor 2021-07-28 13:48:39 +02:00
Andrés Moya
7fe419ecb0 🐛 Fix error when editing texts 2021-07-27 17:05:44 +02:00
Andrey Antukh
55ddf9cc38 🎉 Add some missing js hints. 2021-07-27 14:10:56 +02:00
Andrey Antukh
38292bcda7 🐛 Properly handle group naming on group creation. 2021-07-27 14:10:56 +02:00
Andrey Antukh
08062e8ce8 📚 Add better docstring to group creation internal function. 2021-07-27 14:10:56 +02:00
Andrey Antukh
bff35de39f 🐛 Don't remove :workspace-layout on finalize-file. 2021-07-27 14:10:56 +02:00
Andrey Antukh
394e6b08ad 🎉 Add many improvements on nil handling and code structure on changes impl. 2021-07-27 14:10:56 +02:00
alonso.torres
d61a86cad1 🐛 Frame moving with title with button different than left 2021-07-26 19:28:06 +02:00
alonso.torres
43198eb263 🐛 Improved object deletion 2021-07-26 19:28:06 +02:00
alonso.torres
8493e51070 🐛 Fix problem with svg's viewbox 2021-07-26 19:28:06 +02:00
Andrey Antukh
07eeb76a5f Stream all transit responses.
Instead of buffering for etag. The etags are temporary disabled.
2021-07-26 13:43:39 +02:00
Andrey Antukh
6ee6a03e4a Revert "Update and rename frontend/src/app/main/ui/workspace/viewport/path_actions.cljs to 前端 /src /app /main /ui /工作区 /视口 /path_actions.cljs"
This reverts commit 9d372301ed.
2021-07-26 12:08:24 +02:00
Andrey Antukh
8e3eb98789 Revert "🔥 Remove file."
This reverts commit c5b23816e9.
2021-07-26 12:08:14 +02:00
Andrey Antukh
c5b23816e9 🔥 Remove file. 2021-07-26 11:33:05 +02:00
Andrey Antukh
0a3cd4f8e4 ⬆️ Update deps. 2021-07-26 11:32:46 +02:00
Andrey Antukh
7882dead81 Merge pull request #1100 from soultipsy/develop
Tooltip errors:move nodes and draw nodes are swapped
2021-07-26 11:03:37 +02:00
Andrey Antukh
44f96dd6a3 Merge pull request #1095 from penpot/text-editor-improvements
Text editor improvements
2021-07-26 11:02:29 +02:00
Andrey Antukh
a442afd8d2 Merge branch 'main' into develop 2021-07-26 09:49:37 +02:00
Andrey Antukh
bdbc57b926 📎 Update changelog and increase version. 2021-07-26 09:47:47 +02:00
Andrey Antukh
9ed53ba064 Merge remote-tracking branch 'origin/main' into develop 2021-07-26 09:42:59 +02:00
soultipsy
9d372301ed Update and rename frontend/src/app/main/ui/workspace/viewport/path_actions.cljs to 前端 /src /app /main /ui /工作区 /视口 /path_actions.cljs
Tooltip errors:move nodes and draw nodes are swapped.
2021-07-20 15:44:51 +08:00
Andrey Antukh
b483513fa8 Merge pull request #1099 from penpot/fix-vertical-resize
🐛 Fix vertical resize when nested shapes
2021-07-20 09:42:44 +02:00
Andrés Moya
578c561473 🐛 Fix linter issues 2021-07-20 09:35:22 +02:00
Andrés Moya
f6134a6bd3 🐛 Fix vertical resize when nested shapes 2021-07-20 09:19:24 +02:00
Andrey Antukh
2758b6ffd9 Merge pull request #1096 from penpot/fix-duplicate-names
🐛 Fix repeated names when duplicating object trees.
2021-07-16 16:26:56 +02:00
Andrés Moya
fa99dea8fe 📚 Add some comments about possible code enhancements 2021-07-16 16:21:56 +02:00
Andrés Moya
6ced56301c ♻️ Optimice a bit of performance 2021-07-16 16:21:56 +02:00
Andrés Moya
008134fde8 🐛 Fix repeated names when duplicating object trees. 2021-07-16 16:21:55 +02:00
Andrés Moya
3ed593e4b6 🐛 Fix scroll in teams dropdown at dashboard 2021-07-16 14:35:43 +02:00
alonso.torres
1fc5182979 🐛 Fix text focus issues 2021-07-16 14:14:36 +02:00
alonso.torres
9ebafddac2 Make last font used the default for next text box 2021-07-16 13:13:24 +02:00
alonso.torres
26467187c4 Fix text editor issues 2021-07-16 13:13:24 +02:00
alonso.torres
69e256ab86 Moves cursor to position when clicking in the text box 2021-07-16 13:13:24 +02:00
Andrey Antukh
b4b12e68bf Merge remote-tracking branch 'origin/main' into develop 2021-07-15 18:08:32 +02:00
Andrey Antukh
768216d9bc 🐛 Fix previous migration. 2021-07-15 17:39:56 +02:00
Andrey Antukh
f29d54ad0d 🐛 Add migration for fix unreferenced shapes on frames. 2021-07-15 17:23:51 +02:00
Andrey Antukh
946309a485 📎 Add migration for cleaning unused props on file data. 2021-07-15 16:50:56 +02:00
Andrey Antukh
7c98336148 📎 Improve error reporting. 2021-07-15 16:50:32 +02:00
Andrey Antukh
455b0efa71 🐛 Add migration for fix some inconsistencies on page data. 2021-07-15 16:40:00 +02:00
Andrey Antukh
9ddcb036cf Merge branch 'main' into develop 2021-07-15 15:17:36 +02:00
Andrés Moya
185e06ed79 Merge pull request #1093 from penpot/niwinz-hotfixes
Hotfixes
2021-07-15 14:13:42 +02:00
Andrey Antukh
17ae6bf89d 🐛 Fix problem when page deletion and undo.
Related to duplicated page reference in undo page deletion.
2021-07-15 14:03:11 +02:00
alonso.torres
7efc1a0366 🐛 Fix problem with undo operation and children order 2021-07-15 14:03:11 +02:00
Andrey Antukh
899dc5b680 🐛 Properly dissoc :metadata prop on image->path conversion. 2021-07-15 11:57:45 +02:00
Andrey Antukh
5126c85623 🐛 Properly handle path with fill-image on file media gc task. 2021-07-15 11:57:15 +02:00
Andrés Moya
9ec23ceed6 🐛 Hide popup messages when navigating out 2021-07-14 18:39:33 +02:00
Andrey Antukh
23e4915d60 ⬆️ Set next version number (1.8.0) 2021-07-14 11:10:03 +02:00
57 changed files with 1164 additions and 613 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View 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"}))

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View 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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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",

View File

@@ -38,6 +38,7 @@
z-index: 12;
max-height: 30rem;
min-width: 230px;
overflow-y: auto;
}
.options-dropdown {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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');
}

View File

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

View File

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

View File

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

View File

@@ -1 +1 @@
1.7.0-alpha
1.7.2-alpha