mirror of
https://github.com/penpot/penpot.git
synced 2025-03-19 03:01:27 -05:00
Merge pull request #2108 from penpot/hiru-main-instance
Components v2 (first PR)
This commit is contained in:
commit
18970cb233
101 changed files with 3212 additions and 1262 deletions
|
@ -11,6 +11,7 @@
|
|||
[app.common.pages :as cp]
|
||||
[app.common.pages.migrations :as pmg]
|
||||
[app.common.spec :as us]
|
||||
[app.common.types.file :as ctf]
|
||||
[app.common.uuid :as uuid]
|
||||
[app.config :as cf]
|
||||
[app.db :as db]
|
||||
|
@ -45,7 +46,7 @@
|
|||
(s/def ::is-shared ::us/boolean)
|
||||
(s/def ::create-file
|
||||
(s/keys :req-un [::profile-id ::name ::project-id]
|
||||
:opt-un [::id ::is-shared]))
|
||||
:opt-un [::id ::is-shared ::components-v2]))
|
||||
|
||||
(sv/defmethod ::create-file
|
||||
[{:keys [pool] :as cfg} {:keys [profile-id project-id] :as params}]
|
||||
|
@ -65,11 +66,12 @@
|
|||
|
||||
(defn create-file
|
||||
[conn {:keys [id name project-id is-shared data revn
|
||||
modified-at deleted-at ignore-sync-until]
|
||||
modified-at deleted-at ignore-sync-until
|
||||
components-v2]
|
||||
:or {is-shared false revn 0}
|
||||
:as params}]
|
||||
(let [id (or id (:id data) (uuid/next))
|
||||
data (or data (cp/make-file-data id))
|
||||
data (or data (ctf/make-file-data id components-v2))
|
||||
file (db/insert! conn :file
|
||||
(d/without-nils
|
||||
{:id id
|
||||
|
@ -110,16 +112,25 @@
|
|||
;; --- Mutation: Set File shared
|
||||
|
||||
(declare set-file-shared)
|
||||
(declare unlink-files)
|
||||
(declare absorb-library)
|
||||
|
||||
(s/def ::set-file-shared
|
||||
(s/keys :req-un [::profile-id ::id ::is-shared]))
|
||||
|
||||
(sv/defmethod ::set-file-shared
|
||||
[{:keys [pool] :as cfg} {:keys [id profile-id] :as params}]
|
||||
[{:keys [pool] :as cfg} {:keys [id profile-id is-shared] :as params}]
|
||||
(db/with-atomic [conn pool]
|
||||
(files/check-edition-permissions! conn profile-id id)
|
||||
(when-not is-shared
|
||||
(absorb-library conn params)
|
||||
(unlink-files conn params))
|
||||
(set-file-shared conn params)))
|
||||
|
||||
(defn- unlink-files
|
||||
[conn {:keys [id] :as params}]
|
||||
(db/delete! conn :file-library-rel {:library-file-id id}))
|
||||
|
||||
(defn- set-file-shared
|
||||
[conn {:keys [id is-shared] :as params}]
|
||||
(db/update! conn :file
|
||||
|
@ -137,6 +148,7 @@
|
|||
[{:keys [pool] :as cfg} {:keys [id profile-id] :as params}]
|
||||
(db/with-atomic [conn pool]
|
||||
(files/check-edition-permissions! conn profile-id id)
|
||||
(absorb-library conn params)
|
||||
(mark-file-deleted conn params)))
|
||||
|
||||
(defn mark-file-deleted
|
||||
|
@ -146,6 +158,29 @@
|
|||
{:id id})
|
||||
nil)
|
||||
|
||||
(defn absorb-library
|
||||
"Find all files using a shared library, and absorb all library assets
|
||||
into the file local libraries"
|
||||
[conn {:keys [id] :as params}]
|
||||
(let [library (->> (db/get-by-id conn :file id)
|
||||
(files/decode-row)
|
||||
(pmg/migrate-file))]
|
||||
(when (:is-shared library)
|
||||
(let [process-file
|
||||
(fn [row]
|
||||
(let [ts (dt/now)
|
||||
file (->> (db/get-by-id conn :file (:file-id row))
|
||||
(files/decode-row)
|
||||
(pmg/migrate-file))
|
||||
updated-data (ctf/absorb-assets (:data file) (:data library))]
|
||||
|
||||
(db/update! conn :file
|
||||
{:revn (inc (:revn file))
|
||||
:data (blob/encode updated-data)
|
||||
:modified-at ts}
|
||||
{:id (:id file)})))]
|
||||
|
||||
(run! process-file (db/query conn :file-library-rel {:library-file-id id}))))))
|
||||
|
||||
;; --- Mutation: Link file to library
|
||||
|
||||
|
@ -273,10 +308,11 @@
|
|||
|
||||
(s/def ::session-id ::us/uuid)
|
||||
(s/def ::revn ::us/integer)
|
||||
(s/def ::components-v2 ::us/boolean)
|
||||
(s/def ::update-file
|
||||
(s/and
|
||||
(s/keys :req-un [::id ::session-id ::profile-id ::revn]
|
||||
:opt-un [::changes ::changes-with-metadata])
|
||||
:opt-un [::changes ::changes-with-metadata ::components-v2])
|
||||
(fn [o]
|
||||
(or (contains? o :changes)
|
||||
(contains? o :changes-with-metadata)))))
|
||||
|
@ -313,7 +349,8 @@
|
|||
(simpl/del-object backend file))))
|
||||
|
||||
(defn- update-file
|
||||
[{:keys [conn metrics] :as cfg} {:keys [file changes changes-with-metadata session-id profile-id] :as params}]
|
||||
[{:keys [conn metrics] :as cfg}
|
||||
{:keys [file changes changes-with-metadata session-id profile-id components-v2] :as params}]
|
||||
(when (> (:revn params)
|
||||
(:revn file))
|
||||
|
||||
|
@ -338,12 +375,18 @@
|
|||
(update :data (fn [data]
|
||||
;; Trace the length of bytes of processed data
|
||||
(mtx/run! metrics {:id :update-file-bytes-processed :inc (alength data)})
|
||||
(-> data
|
||||
(blob/decode)
|
||||
(assoc :id (:id file))
|
||||
(pmg/migrate-data)
|
||||
(cp/process-changes changes)
|
||||
(blob/encode)))))]
|
||||
(cond-> data
|
||||
:always
|
||||
(-> (blob/decode)
|
||||
(assoc :id (:id file))
|
||||
(pmg/migrate-data))
|
||||
|
||||
components-v2
|
||||
(ctf/migrate-to-components-v2)
|
||||
|
||||
:always
|
||||
(-> (cp/process-changes changes)
|
||||
(blob/encode))))))]
|
||||
;; Insert change to the xlog
|
||||
(db/insert! conn :file-change
|
||||
{:id (uuid/next)
|
||||
|
|
|
@ -13,6 +13,8 @@
|
|||
[app.common.pages.helpers :as cph]
|
||||
[app.common.pages.migrations :as pmg]
|
||||
[app.common.spec :as us]
|
||||
[app.common.types.file :as ctf]
|
||||
[app.common.types.shape-tree :as ctt]
|
||||
[app.db :as db]
|
||||
[app.db.sql :as sql]
|
||||
[app.rpc.helpers :as rpch]
|
||||
|
@ -26,7 +28,6 @@
|
|||
[cuerdas.core :as str]))
|
||||
|
||||
(declare decode-row)
|
||||
(declare decode-row-xf)
|
||||
|
||||
;; --- Helpers & Specs
|
||||
|
||||
|
@ -38,6 +39,7 @@
|
|||
(s/def ::profile-id ::us/uuid)
|
||||
(s/def ::team-id ::us/uuid)
|
||||
(s/def ::search-term ::us/string)
|
||||
(s/def ::components-v2 ::us/boolean)
|
||||
|
||||
;; --- Query: File Permissions
|
||||
|
||||
|
@ -122,8 +124,7 @@
|
|||
(defn check-comment-permissions!
|
||||
[conn profile-id file-id share-id]
|
||||
(let [can-read (has-read-permissions? conn profile-id file-id)
|
||||
can-comment (has-comment-permissions? conn profile-id file-id share-id)
|
||||
]
|
||||
can-comment (has-comment-permissions? conn profile-id file-id share-id)]
|
||||
(when-not (or can-read can-comment)
|
||||
(ex/raise :type :not-found
|
||||
:code :object-not-found
|
||||
|
@ -226,20 +227,29 @@
|
|||
(d/index-by :object-id :data))))))
|
||||
|
||||
(defn retrieve-file
|
||||
[{:keys [pool] :as cfg} id]
|
||||
(->> (db/get-by-id pool :file id)
|
||||
(decode-row)
|
||||
(pmg/migrate-file)))
|
||||
[{:keys [pool] :as cfg} id components-v2]
|
||||
(let [file (->> (db/get-by-id pool :file id)
|
||||
(decode-row)
|
||||
(pmg/migrate-file))]
|
||||
|
||||
(if components-v2
|
||||
(update file :data ctf/migrate-to-components-v2)
|
||||
(if (get-in file [:data :options :components-v2])
|
||||
(ex/raise :type :restriction
|
||||
:code :feature-disabled
|
||||
:hint "tried to open a components-v2 file with feature disabled")
|
||||
file))))
|
||||
|
||||
(s/def ::file
|
||||
(s/keys :req-un [::profile-id ::id]))
|
||||
(s/keys :req-un [::profile-id ::id]
|
||||
:opt-un [::components-v2]))
|
||||
|
||||
(sv/defmethod ::file
|
||||
"Retrieve a file by its ID. Only authenticated users."
|
||||
[{:keys [pool] :as cfg} {:keys [profile-id id] :as params}]
|
||||
[{:keys [pool] :as cfg} {:keys [profile-id id components-v2] :as params}]
|
||||
(let [perms (get-permissions pool profile-id id)]
|
||||
(check-read-permissions! perms)
|
||||
(let [file (retrieve-file cfg id)
|
||||
(let [file (retrieve-file cfg id components-v2)
|
||||
thumbs (retrieve-object-thumbnails cfg id)]
|
||||
(-> file
|
||||
(assoc :thumbnails thumbs)
|
||||
|
@ -268,7 +278,7 @@
|
|||
(s/def ::page
|
||||
(s/and
|
||||
(s/keys :req-un [::profile-id ::file-id]
|
||||
:opt-un [::page-id ::object-id])
|
||||
:opt-un [::page-id ::object-id ::components-v2])
|
||||
(fn [obj]
|
||||
(if (contains? obj :object-id)
|
||||
(contains? obj :page-id)
|
||||
|
@ -284,9 +294,9 @@
|
|||
mandatory.
|
||||
|
||||
Mainly used for rendering purposes."
|
||||
[{:keys [pool] :as cfg} {:keys [profile-id file-id page-id object-id] :as props}]
|
||||
[{:keys [pool] :as cfg} {:keys [profile-id file-id page-id object-id components-v2] :as props}]
|
||||
(check-read-permissions! pool profile-id file-id)
|
||||
(let [file (retrieve-file cfg file-id)
|
||||
(let [file (retrieve-file cfg file-id components-v2)
|
||||
page-id (or page-id (-> file :data :pages first))
|
||||
page (get-in file [:data :pages-index page-id])]
|
||||
|
||||
|
@ -304,7 +314,7 @@
|
|||
(get-thumbnail-frame [data]
|
||||
(d/seek :use-for-thumbnail?
|
||||
(for [page (-> data :pages-index vals)
|
||||
frame (-> page :objects cph/get-frames)]
|
||||
frame (-> page :objects ctt/get-frames)]
|
||||
(assoc frame :page-id (:id page)))))
|
||||
|
||||
;; function responsible to filter objects data structure of
|
||||
|
@ -355,7 +365,7 @@
|
|||
(-> data :pages first))
|
||||
|
||||
page (dm/get-in data [:pages-index page-id])
|
||||
frame-ids (if (some? frame) (list frame-id) (map :id (cph/get-frames (:objects page))))
|
||||
frame-ids (if (some? frame) (list frame-id) (map :id (ctt/get-frames (:objects page))))
|
||||
|
||||
obj-ids (map #(str page-id %) frame-ids)
|
||||
thumbs (retrieve-object-thumbnails cfg id obj-ids)]
|
||||
|
@ -373,14 +383,15 @@
|
|||
(update :objects assoc-thumbnails page-id thumbs)))))
|
||||
|
||||
(s/def ::file-data-for-thumbnail
|
||||
(s/keys :req-un [::profile-id ::file-id]))
|
||||
(s/keys :req-un [::profile-id ::file-id]
|
||||
:opt-un [::components-v2]))
|
||||
|
||||
(sv/defmethod ::file-data-for-thumbnail
|
||||
"Retrieves the data for generate the thumbnail of the file. Used
|
||||
mainly for render thumbnails on dashboard."
|
||||
[{:keys [pool] :as cfg} {:keys [profile-id file-id] :as props}]
|
||||
[{:keys [pool] :as cfg} {:keys [profile-id file-id components-v2] :as props}]
|
||||
(check-read-permissions! pool profile-id file-id)
|
||||
(let [file (retrieve-file cfg file-id)]
|
||||
(let [file (retrieve-file cfg file-id components-v2)]
|
||||
{:file-id file-id
|
||||
:revn (:revn file)
|
||||
:page (get-file-thumbnail-data cfg file)}))
|
||||
|
@ -453,6 +464,24 @@
|
|||
(check-read-permissions! pool profile-id file-id)
|
||||
(retrieve-file-libraries cfg false file-id))
|
||||
|
||||
|
||||
;; --- Query: Files that use this File library
|
||||
|
||||
(def ^:private sql:library-using-files
|
||||
"SELECT f.id,
|
||||
f.name
|
||||
FROM file_library_rel AS flr
|
||||
INNER JOIN file AS f ON f.id = flr.file_id
|
||||
WHERE flr.library_file_id = ?;")
|
||||
|
||||
(s/def ::library-using-files
|
||||
(s/keys :req-un [::profile-id ::file-id]))
|
||||
|
||||
(sv/defmethod ::library-using-files
|
||||
[{:keys [pool] :as cfg} {:keys [profile-id file-id] :as params}]
|
||||
(check-read-permissions! pool profile-id file-id)
|
||||
(db/exec! pool [sql:library-using-files file-id]))
|
||||
|
||||
;; --- QUERY: team-recent-files
|
||||
|
||||
(def sql:team-recent-files
|
||||
|
@ -522,7 +551,3 @@
|
|||
(cond-> row
|
||||
changes (assoc :changes (blob/decode changes))
|
||||
data (assoc :data (blob/decode data)))))
|
||||
|
||||
(def decode-row-xf
|
||||
(comp (map decode-row)
|
||||
(map pmg/migrate-file)))
|
||||
|
|
|
@ -23,8 +23,8 @@
|
|||
(db/get-by-id pool :project id {:columns [:id :name :team-id]}))
|
||||
|
||||
(defn- retrieve-bundle
|
||||
[{:keys [pool] :as cfg} file-id profile-id]
|
||||
(p/let [file (files/retrieve-file cfg file-id)
|
||||
[{:keys [pool] :as cfg} file-id profile-id components-v2]
|
||||
(p/let [file (files/retrieve-file cfg file-id components-v2)
|
||||
project (retrieve-project pool (:project-id file))
|
||||
libs (files/retrieve-file-libraries cfg false file-id)
|
||||
users (comments/retrieve-file-comments-users pool file-id profile-id)
|
||||
|
@ -47,14 +47,14 @@
|
|||
(s/def ::share-id ::us/uuid)
|
||||
|
||||
(s/def ::view-only-bundle
|
||||
(s/keys :req-un [::file-id] :opt-un [::profile-id ::share-id]))
|
||||
(s/keys :req-un [::file-id] :opt-un [::profile-id ::share-id ::components-v2]))
|
||||
|
||||
(sv/defmethod ::view-only-bundle {:auth false}
|
||||
[{:keys [pool] :as cfg} {:keys [profile-id file-id share-id] :as params}]
|
||||
[{:keys [pool] :as cfg} {:keys [profile-id file-id share-id components-v2] :as params}]
|
||||
(p/let [slink (slnk/retrieve-share-link pool file-id share-id)
|
||||
perms (files/get-permissions pool profile-id file-id share-id)
|
||||
thumbs (files/retrieve-object-thumbnails cfg file-id)
|
||||
bundle (p/-> (retrieve-bundle cfg file-id profile-id)
|
||||
bundle (p/-> (retrieve-bundle cfg file-id profile-id components-v2)
|
||||
(assoc :permissions perms)
|
||||
(assoc-in [:file :thumbnails] thumbs))]
|
||||
|
||||
|
|
|
@ -12,8 +12,8 @@
|
|||
(:require
|
||||
[app.common.data :as d]
|
||||
[app.common.logging :as l]
|
||||
[app.common.pages.helpers :as cph]
|
||||
[app.common.pages.migrations :as pmg]
|
||||
[app.common.types.shape-tree :as ctt]
|
||||
[app.db :as db]
|
||||
[app.util.blob :as blob]
|
||||
[app.util.time :as dt]
|
||||
|
@ -128,7 +128,7 @@
|
|||
|
||||
get-objects-ids
|
||||
(fn [{:keys [id objects]}]
|
||||
(->> (cph/get-frames objects)
|
||||
(->> (ctt/get-frames objects)
|
||||
(map #(str id (:id %)))))
|
||||
|
||||
using (into #{}
|
||||
|
|
|
@ -32,7 +32,8 @@
|
|||
:project-id proj-id
|
||||
:id file-id
|
||||
:name "foobar"
|
||||
:is-shared false}
|
||||
:is-shared false
|
||||
:components-v2 true}
|
||||
out (th/mutation! data)]
|
||||
|
||||
;; (th/print-result! out)
|
||||
|
@ -71,7 +72,8 @@
|
|||
(t/testing "query single file without users"
|
||||
(let [data {::th/type :file
|
||||
:profile-id (:id prof)
|
||||
:id file-id}
|
||||
:id file-id
|
||||
:components-v2 true}
|
||||
out (th/query! data)]
|
||||
|
||||
;; (th/print-result! out)
|
||||
|
@ -95,7 +97,8 @@
|
|||
(t/testing "query single file after delete"
|
||||
(let [data {::th/type :file
|
||||
:profile-id (:id prof)
|
||||
:id file-id}
|
||||
:id file-id
|
||||
:components-v2 true}
|
||||
out (th/query! data)]
|
||||
|
||||
;; (th/print-result! out)
|
||||
|
@ -143,6 +146,7 @@
|
|||
:session-id (uuid/random)
|
||||
:profile-id profile-id
|
||||
:revn revn
|
||||
:components-v2 true
|
||||
:changes changes}
|
||||
out (th/mutation! params)]
|
||||
(t/is (nil? (:error out)))
|
||||
|
@ -171,6 +175,7 @@
|
|||
:id shid
|
||||
:parent-id uuid/zero
|
||||
:frame-id uuid/zero
|
||||
:components-v2 true
|
||||
:obj {:id shid
|
||||
:name "image"
|
||||
:frame-id uuid/zero
|
||||
|
@ -246,7 +251,8 @@
|
|||
:profile-id (:id profile2)
|
||||
:project-id (:default-project-id profile1)
|
||||
:name "foobar"
|
||||
:is-shared false}
|
||||
:is-shared false
|
||||
:components-v2 true}
|
||||
out (th/mutation! data)
|
||||
error (:error out)]
|
||||
|
||||
|
@ -462,6 +468,7 @@
|
|||
(th/update-file* {:file-id (:id file)
|
||||
:profile-id (:id prof)
|
||||
:revn 0
|
||||
:components-v2 true
|
||||
:changes changes})
|
||||
|
||||
(t/testing "RPC page query (rendering purposes)"
|
||||
|
@ -469,7 +476,8 @@
|
|||
;; Query :page RPC method without passing page-id
|
||||
(let [data {::th/type :page
|
||||
:profile-id (:id prof)
|
||||
:file-id (:id file)}
|
||||
:file-id (:id file)
|
||||
:components-v2 true}
|
||||
{:keys [error result] :as out} (th/query! data)]
|
||||
|
||||
;; (th/print-result! out)
|
||||
|
@ -485,7 +493,8 @@
|
|||
(let [data {::th/type :page
|
||||
:profile-id (:id prof)
|
||||
:file-id (:id file)
|
||||
:page-id page-id}
|
||||
:page-id page-id
|
||||
:components-v2 true}
|
||||
{:keys [error result] :as out} (th/query! data)]
|
||||
;; (th/print-result! out)
|
||||
(t/is (map? result))
|
||||
|
@ -501,7 +510,8 @@
|
|||
:profile-id (:id prof)
|
||||
:file-id (:id file)
|
||||
:page-id page-id
|
||||
:object-id frame1-id}
|
||||
:object-id frame1-id
|
||||
:components-v2 true}
|
||||
{:keys [error result] :as out} (th/query! data)]
|
||||
;; (th/print-result! out)
|
||||
(t/is (map? result))
|
||||
|
@ -516,7 +526,8 @@
|
|||
(let [data {::th/type :page
|
||||
:profile-id (:id prof)
|
||||
:file-id (:id file)
|
||||
:object-id frame1-id}
|
||||
:object-id frame1-id
|
||||
:components-v2 true}
|
||||
{:keys [error result] :as out} (th/query! data)]
|
||||
;; (th/print-result! out)
|
||||
(t/is (= :validation (th/ex-type error)))
|
||||
|
@ -537,7 +548,8 @@
|
|||
;; Check the result
|
||||
(let [data {::th/type :file-data-for-thumbnail
|
||||
:profile-id (:id prof)
|
||||
:file-id (:id file)}
|
||||
:file-id (:id file)
|
||||
:components-v2 true}
|
||||
{:keys [error result] :as out} (th/query! data)]
|
||||
;; (th/print-result! out)
|
||||
(t/is (map? result))
|
||||
|
@ -562,7 +574,8 @@
|
|||
;; Check the result
|
||||
(let [data {::th/type :file-data-for-thumbnail
|
||||
:profile-id (:id prof)
|
||||
:file-id (:id file)}
|
||||
:file-id (:id file)
|
||||
:components-v2 true}
|
||||
{:keys [error result] :as out} (th/query! data)]
|
||||
;; (th/print-result! out)
|
||||
(t/is (map? result))
|
||||
|
|
|
@ -30,7 +30,8 @@
|
|||
(let [data {::th/type :view-only-bundle
|
||||
:profile-id (:id prof)
|
||||
:file-id (:id file)
|
||||
:page-id (get-in file [:data :pages 0])}
|
||||
:page-id (get-in file [:data :pages 0])
|
||||
:components-v2 true}
|
||||
|
||||
out (th/query! data)]
|
||||
|
||||
|
@ -63,7 +64,8 @@
|
|||
(let [data {::th/type :view-only-bundle
|
||||
:profile-id (:id prof2)
|
||||
:file-id (:id file)
|
||||
:page-id (get-in file [:data :pages 0])}
|
||||
:page-id (get-in file [:data :pages 0])
|
||||
:components-v2 true}
|
||||
out (th/query! data)]
|
||||
|
||||
;; (th/print-result! out)
|
||||
|
@ -78,7 +80,8 @@
|
|||
:profile-id (:id prof2)
|
||||
:share-id @share-id
|
||||
:file-id (:id file)
|
||||
:page-id (get-in file [:data :pages 0])}
|
||||
:page-id (get-in file [:data :pages 0])
|
||||
:components-v2 true}
|
||||
out (th/query! data)]
|
||||
|
||||
;; (th/print-result! out)
|
||||
|
@ -93,7 +96,8 @@
|
|||
(let [data {::th/type :view-only-bundle
|
||||
:share-id @share-id
|
||||
:file-id (:id file)
|
||||
:page-id (get-in file [:data :pages 0])}
|
||||
:page-id (get-in file [:data :pages 0])
|
||||
:components-v2 true}
|
||||
out (th/query! data)]
|
||||
|
||||
;; (th/print-result! out)
|
||||
|
|
|
@ -164,7 +164,8 @@
|
|||
(us/assert uuid? project-id)
|
||||
(#'files/create-file conn
|
||||
(merge {:id (mk-uuid "file" i)
|
||||
:name (str "file" i)}
|
||||
:name (str "file" i)
|
||||
:components-v2 true}
|
||||
params))))
|
||||
|
||||
(defn mark-file-deleted*
|
||||
|
@ -249,6 +250,7 @@
|
|||
:metrics metrics}
|
||||
{:file file
|
||||
:revn revn
|
||||
:components-v2 true
|
||||
:changes changes
|
||||
:session-id session-id
|
||||
:profile-id profile-id}))))
|
||||
|
|
|
@ -173,6 +173,10 @@
|
|||
[data]
|
||||
(into {} (remove (comp nil? second)) data))
|
||||
|
||||
(defn vec-without-nils
|
||||
[coll]
|
||||
(into [] (remove nil?) coll))
|
||||
|
||||
(defn without-qualified
|
||||
[data]
|
||||
(into {} (remove (comp qualified-keyword? first)) data))
|
||||
|
|
|
@ -12,8 +12,10 @@
|
|||
[app.common.geom.shapes :as gsh]
|
||||
[app.common.pages.changes :as ch]
|
||||
[app.common.pages.changes-spec :as pcs]
|
||||
[app.common.pages.init :as init]
|
||||
[app.common.spec :as us]
|
||||
[app.common.types.file :as ctf]
|
||||
[app.common.types.page :as ctp]
|
||||
[app.common.types.shape :as cts]
|
||||
[app.common.uuid :as uuid]
|
||||
[cuerdas.core :as str]))
|
||||
|
||||
|
@ -167,7 +169,7 @@
|
|||
([id name]
|
||||
{:id id
|
||||
:name name
|
||||
:data (-> init/empty-file-data
|
||||
:data (-> ctf/empty-file-data
|
||||
(assoc :id id))
|
||||
|
||||
;; We keep the changes so we can send them to the backend
|
||||
|
@ -178,8 +180,7 @@
|
|||
|
||||
(assert (nil? (:current-component-id file)))
|
||||
(let [page-id (or (:id data) (uuid/next))
|
||||
page (-> init/empty-page-data
|
||||
(assoc :id page-id)
|
||||
page (-> (ctp/make-empty-page page-id "Page-1")
|
||||
(d/deep-merge data))]
|
||||
(-> file
|
||||
(commit-change
|
||||
|
@ -208,7 +209,7 @@
|
|||
|
||||
(defn add-artboard [file data]
|
||||
(assert (nil? (:current-component-id file)))
|
||||
(let [obj (-> (init/make-minimal-shape :frame)
|
||||
(let [obj (-> (cts/make-minimal-shape :frame)
|
||||
(merge data)
|
||||
(check-name file :frame)
|
||||
(setup-selrect)
|
||||
|
@ -232,9 +233,9 @@
|
|||
|
||||
(defn add-group [file data]
|
||||
(let [frame-id (:current-frame-id file)
|
||||
selrect init/empty-selrect
|
||||
selrect cts/empty-selrect
|
||||
name (:name data)
|
||||
obj (-> (init/make-minimal-group frame-id selrect name)
|
||||
obj (-> (cts/make-minimal-group frame-id selrect name)
|
||||
(merge data)
|
||||
(check-name file :group)
|
||||
(d/without-nils))]
|
||||
|
@ -346,7 +347,7 @@
|
|||
(update :parent-stack pop))))
|
||||
|
||||
(defn create-shape [file type data]
|
||||
(let [obj (-> (init/make-minimal-shape type)
|
||||
(let [obj (-> (cts/make-minimal-shape type)
|
||||
(merge data)
|
||||
(check-name file :type)
|
||||
(setup-selrect)
|
||||
|
@ -514,10 +515,10 @@
|
|||
(defn start-component
|
||||
[file data]
|
||||
|
||||
(let [selrect init/empty-selrect
|
||||
(let [selrect cts/empty-selrect
|
||||
name (:name data)
|
||||
path (:path data)
|
||||
obj (-> (init/make-minimal-group nil selrect name)
|
||||
obj (-> (cts/make-minimal-group nil selrect name)
|
||||
(merge data)
|
||||
(check-name file :group)
|
||||
(d/without-nils))]
|
||||
|
|
|
@ -41,13 +41,23 @@
|
|||
|
||||
;; --- Helpers
|
||||
|
||||
(defn left-bound
|
||||
(defn bounding-box
|
||||
"Returns a rect that wraps the shape after all transformations applied."
|
||||
[shape]
|
||||
(get shape :x (:x (:selrect shape)))) ; Paths don't have :x attribute
|
||||
; TODO: perhaps we need to store this calculation in a shape attribute
|
||||
(gpr/points->rect (:points shape)))
|
||||
|
||||
(defn left-bound
|
||||
"Returns the lowest x coord of the shape BEFORE applying transformations."
|
||||
; TODO: perhaps some day we want after transformations, but for the
|
||||
; moment it's enough as is now.
|
||||
[shape]
|
||||
(or (:x shape) (:x (:selrect shape)))) ; Paths don't have :x attribute
|
||||
|
||||
(defn top-bound
|
||||
"Returns the lowest y coord of the shape BEFORE applying transformations."
|
||||
[shape]
|
||||
(get shape :y (:y (:selrect shape)))) ; Paths don't have :y attribute
|
||||
(or (:y shape) (:y (:selrect shape)))) ; Paths don't have :y attribute
|
||||
|
||||
(defn fully-contained?
|
||||
"Checks if one rect is fully inside the other"
|
||||
|
|
|
@ -12,7 +12,7 @@
|
|||
[app.common.pages.common :as common]
|
||||
[app.common.pages.focus :as focus]
|
||||
[app.common.pages.indices :as indices]
|
||||
[app.common.pages.init :as init]))
|
||||
[app.common.types.file :as ctf]))
|
||||
|
||||
;; Common
|
||||
(dm/export common/root)
|
||||
|
@ -36,11 +36,5 @@
|
|||
(dm/export changes/process-changes)
|
||||
|
||||
;; Initialization
|
||||
(dm/export init/default-frame-attrs)
|
||||
(dm/export init/default-shape-attrs)
|
||||
(dm/export init/make-file-data)
|
||||
(dm/export init/make-minimal-shape)
|
||||
(dm/export init/make-minimal-group)
|
||||
(dm/export init/empty-file-data)
|
||||
(dm/export init/setup-shape)
|
||||
(dm/export init/setup-rect-selrect)
|
||||
(dm/export ctf/make-file-data)
|
||||
(dm/export ctf/empty-file-data)
|
||||
|
|
|
@ -14,10 +14,16 @@
|
|||
[app.common.math :as mth]
|
||||
[app.common.pages.common :refer [component-sync-attrs]]
|
||||
[app.common.pages.helpers :as cph]
|
||||
[app.common.pages.init :as init]
|
||||
[app.common.spec :as us]
|
||||
[app.common.pages.changes-spec :as pcs]
|
||||
[app.common.types.shape :as cts]))
|
||||
[app.common.types.components-list :as ctkl]
|
||||
[app.common.types.container :as ctn]
|
||||
[app.common.types.colors-list :as ctcl]
|
||||
[app.common.types.page :as ctp]
|
||||
[app.common.types.pages-list :as ctpl]
|
||||
[app.common.types.shape :as cts]
|
||||
[app.common.types.shape-tree :as ctst]
|
||||
[app.common.types.typographies-list :as ctyl]))
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;; Specific helpers
|
||||
|
@ -28,15 +34,11 @@
|
|||
[coll o]
|
||||
(into [] (filter #(not= % o)) coll))
|
||||
|
||||
(defn vec-without-nils
|
||||
[coll]
|
||||
(into [] (remove nil?) coll))
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;; Page Transformation Changes
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
||||
;; --- Changes Processing Impl
|
||||
;; === Changes Processing Impl
|
||||
|
||||
(defmulti process-change (fn [_ change] (:type change)))
|
||||
(defmulti process-operation (fn [_ op] (:type op)))
|
||||
|
@ -74,44 +76,9 @@
|
|||
|
||||
(defmethod process-change :add-obj
|
||||
[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)
|
||||
(conj shapes 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?)))))
|
||||
|
||||
;; TODO: this looks wrong, why we allow nil values?
|
||||
(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)))]
|
||||
(let [update-container
|
||||
(fn [container]
|
||||
(ctst/add-shape id obj container frame-id parent-id index ignore-touched))]
|
||||
|
||||
(if page-id
|
||||
(d/update-in-when data [:pages-index page-id] update-container)
|
||||
|
@ -237,7 +204,7 @@
|
|||
;; 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))]
|
||||
(update :shapes d/vec-without-nils))]
|
||||
(cond-> parent
|
||||
(and (:shape-ref parent) (= (:type parent) :group) (not ignore-touched))
|
||||
(-> (update :touched cph/set-touched-group :shapes-group)
|
||||
|
@ -258,7 +225,7 @@
|
|||
|
||||
(-> objects
|
||||
(d/update-in-when [pid :shapes] without-obj sid)
|
||||
(d/update-in-when [pid :shapes] vec-without-nils)
|
||||
(d/update-in-when [pid :shapes] d/vec-without-nils)
|
||||
(cond-> component? (d/update-when pid #(-> %
|
||||
(update :touched cph/set-touched-group :shapes-group)
|
||||
(dissoc :remote-synced?)))))))))
|
||||
|
@ -323,22 +290,11 @@
|
|||
[data {:keys [id name page]}]
|
||||
(when (and id name page)
|
||||
(ex/raise :type :conflict
|
||||
: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)))))
|
||||
:hint "id+name or page should be provided, never both"))
|
||||
(let [page (if (and (string? name) (uuid? id))
|
||||
(ctp/make-empty-page id name)
|
||||
page)]
|
||||
(ctpl/add-page data page)))
|
||||
|
||||
(defmethod process-change :mod-page
|
||||
[data {:keys [id name]}]
|
||||
|
@ -356,7 +312,7 @@
|
|||
|
||||
(defmethod process-change :add-color
|
||||
[data {:keys [color]}]
|
||||
(update data :colors assoc (:id color) color))
|
||||
(ctcl/add-color data color))
|
||||
|
||||
(defmethod process-change :mod-color
|
||||
[data {:keys [color]}]
|
||||
|
@ -392,12 +348,14 @@
|
|||
;; -- Components
|
||||
|
||||
(defmethod process-change :add-component
|
||||
[data {:keys [id name path shapes]}]
|
||||
(assoc-in data [:components id]
|
||||
{:id id
|
||||
:name name
|
||||
:path path
|
||||
:objects (d/index-by :id shapes)}))
|
||||
[data {:keys [id name path main-instance-id main-instance-page shapes]}]
|
||||
(ctkl/add-component data
|
||||
id
|
||||
name
|
||||
path
|
||||
main-instance-id
|
||||
main-instance-page
|
||||
shapes))
|
||||
|
||||
(defmethod process-change :mod-component
|
||||
[data {:keys [id name path objects]}]
|
||||
|
@ -420,7 +378,7 @@
|
|||
|
||||
(defmethod process-change :add-typography
|
||||
[data {:keys [typography]}]
|
||||
(update data :typographies assoc (:id typography) typography))
|
||||
(ctyl/add-typography data typography))
|
||||
|
||||
(defmethod process-change :mod-typography
|
||||
[data {:keys [typography]}]
|
||||
|
@ -430,7 +388,7 @@
|
|||
[data {:keys [id]}]
|
||||
(update data :typographies dissoc id))
|
||||
|
||||
;; -- Operations
|
||||
;; === Operations
|
||||
|
||||
(defmethod process-operation :set
|
||||
[shape op]
|
||||
|
@ -494,3 +452,36 @@
|
|||
(ex/raise :type :not-implemented
|
||||
:code :operation-not-implemented
|
||||
:context {:type (:type op)}))
|
||||
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;; Component changes detection
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
||||
;; Analyze one change and checks if if modifies the main instance of
|
||||
;; any component, so that it needs to be synced immediately to the
|
||||
;; main component. Return the ids of the components that need sync.
|
||||
|
||||
(defmulti components-changed (fn [_ change] (:type change)))
|
||||
|
||||
(defmethod components-changed :mod-obj
|
||||
[file-data {:keys [id page-id _component-id operations]}]
|
||||
(when page-id
|
||||
(let [page (ctpl/get-page file-data page-id)
|
||||
shape-and-parents (map #(ctn/get-shape page %)
|
||||
(into [id] (cph/get-parent-ids (:objects page) id)))
|
||||
need-sync? (fn [operation]
|
||||
; We need to trigger a sync if the shape has changed any
|
||||
; attribute that participates in components syncronization.
|
||||
(and (= (:type operation) :set)
|
||||
(component-sync-attrs (:attr operation))))
|
||||
any-sync? (some need-sync? operations)]
|
||||
(when any-sync?
|
||||
(let [xform (comp (filter :main-instance?) ; Select shapes that are main component instances
|
||||
(map :id))]
|
||||
(into #{} xform shape-and-parents))))))
|
||||
|
||||
(defmethod components-changed :default
|
||||
[_ _]
|
||||
nil)
|
||||
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
[app.common.math :as mth]
|
||||
[app.common.pages :as cp]
|
||||
[app.common.pages.helpers :as cph]
|
||||
[app.common.types.file :as ctf]
|
||||
[app.common.uuid :as uuid]))
|
||||
|
||||
;; Auxiliary functions to help create a set of changes (undo + redo)
|
||||
|
@ -49,7 +50,7 @@
|
|||
|
||||
(defn with-objects
|
||||
[changes objects]
|
||||
(let [file-data (-> (cp/make-file-data (uuid/next) uuid/zero)
|
||||
(let [file-data (-> (ctf/make-file-data (uuid/next) uuid/zero true)
|
||||
(assoc-in [:pages-index uuid/zero :objects] objects))]
|
||||
(vary-meta changes assoc ::file-data file-data
|
||||
::applied-changes-count 0)))
|
||||
|
@ -111,7 +112,9 @@
|
|||
redo-changes (:redo-changes changes)
|
||||
new-changes (if (< index (count redo-changes))
|
||||
(->> (subvec (:redo-changes changes) index)
|
||||
(map #(assoc % :page-id uuid/zero)))
|
||||
(map #(-> %
|
||||
(assoc :page-id uuid/zero)
|
||||
(dissoc :component-id))))
|
||||
[])
|
||||
new-file-data (cp/process-changes file-data new-changes)]
|
||||
(vary-meta changes assoc ::file-data new-file-data
|
||||
|
@ -223,6 +226,15 @@
|
|||
(update :undo-changes d/preconj del-change)
|
||||
(apply-changes-local)))))
|
||||
|
||||
(defn add-objects
|
||||
([changes objects]
|
||||
(add-objects changes objects nil))
|
||||
|
||||
([changes objects params]
|
||||
(reduce #(add-object %1 %2 params)
|
||||
changes
|
||||
objects)))
|
||||
|
||||
(defn change-parent
|
||||
([changes parent-id shapes]
|
||||
(change-parent changes parent-id shapes nil))
|
||||
|
@ -532,7 +544,7 @@
|
|||
(apply-changes-local))))
|
||||
|
||||
(defn add-component
|
||||
[changes id path name new-shapes updated-shapes]
|
||||
[changes id path name new-shapes updated-shapes main-instance-id main-instance-page]
|
||||
(assert-page-id changes)
|
||||
(assert-objects changes)
|
||||
(let [page-id (::page-id (meta changes))
|
||||
|
@ -552,6 +564,9 @@
|
|||
{:type :set
|
||||
:attr :component-root?
|
||||
:val (:component-root? shape)}
|
||||
{:type :set
|
||||
:attr :main-instance?
|
||||
:val (:main-instance? shape)}
|
||||
{:type :set
|
||||
:attr :shape-ref
|
||||
:val (:shape-ref shape)}
|
||||
|
@ -566,6 +581,8 @@
|
|||
:id id
|
||||
:path path
|
||||
:name name
|
||||
:main-instance-id main-instance-id
|
||||
:main-instance-page main-instance-page
|
||||
:shapes new-shapes})
|
||||
(into (map mk-change) updated-shapes))))
|
||||
(update :undo-changes
|
||||
|
@ -611,5 +628,7 @@
|
|||
:id id
|
||||
:name (:name prev-component)
|
||||
:path (:path prev-component)
|
||||
:main-instance-id (:main-instance-id prev-component)
|
||||
:main-instance-page (:main-instance-page prev-component)
|
||||
:shapes (vals (:objects prev-component))}))))
|
||||
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
(:require
|
||||
[app.common.data :as d]
|
||||
[app.common.pages.helpers :as cph]
|
||||
[app.common.types.shape-tree :as ctt]
|
||||
[app.common.uuid :as uuid]))
|
||||
|
||||
(defn focus-objects
|
||||
|
@ -21,7 +22,7 @@
|
|||
(cond-> objects
|
||||
(some? ids-with-children)
|
||||
(-> (select-keys ids-with-children)
|
||||
(assoc-in [uuid/zero :shapes] (cph/sort-z-index objects focus))))))
|
||||
(assoc-in [uuid/zero :shapes] (ctt/sort-z-index objects focus))))))
|
||||
|
||||
(defn filter-not-focus
|
||||
[objects focus ids]
|
||||
|
|
|
@ -8,10 +8,7 @@
|
|||
(:require
|
||||
[app.common.data :as d]
|
||||
[app.common.data.macros :as dm]
|
||||
[app.common.geom.shapes :as gsh]
|
||||
[app.common.math :as mth]
|
||||
[app.common.spec :as us]
|
||||
[app.common.types.page :as ctp]
|
||||
[app.common.uuid :as uuid]
|
||||
[cuerdas.core :as str]))
|
||||
|
||||
|
@ -62,14 +59,6 @@
|
|||
(and (not (frame-shape? shape))
|
||||
(= (:frame-id shape) uuid/zero)))
|
||||
|
||||
(defn get-shape
|
||||
[container shape-id]
|
||||
(us/assert ::ctp/container container)
|
||||
(us/assert ::us/uuid shape-id)
|
||||
(-> container
|
||||
(get :objects)
|
||||
(get shape-id)))
|
||||
|
||||
(defn get-children-ids
|
||||
[objects id]
|
||||
(if-let [shapes (-> (get objects id) :shapes (some-> vec))]
|
||||
|
@ -158,146 +147,6 @@
|
|||
(:shapes)
|
||||
(keep lookup)))))
|
||||
|
||||
(defn get-frames
|
||||
"Retrieves all frame objects as vector"
|
||||
[objects]
|
||||
(or (-> objects meta ::index-frames)
|
||||
(let [lookup (d/getf objects)
|
||||
xform (comp (remove #(= uuid/zero %))
|
||||
(keep lookup)
|
||||
(filter frame-shape?))]
|
||||
(->> (keys objects)
|
||||
(into [] xform)))))
|
||||
|
||||
(defn get-frames-ids
|
||||
"Retrieves all frame ids as vector"
|
||||
[objects]
|
||||
(->> (get-frames objects)
|
||||
(mapv :id)))
|
||||
|
||||
(defn get-nested-frames
|
||||
[objects frame-id]
|
||||
(into #{}
|
||||
(comp (filter frame-shape?)
|
||||
(map :id))
|
||||
(get-children objects frame-id)))
|
||||
|
||||
(defn get-root-frames-ids
|
||||
"Retrieves all frame objects as vector. It is not implemented in
|
||||
function of `get-immediate-children` for performance reasons. This
|
||||
function is executed in the render hot path."
|
||||
[objects]
|
||||
(let [add-frame
|
||||
(fn [result shape]
|
||||
(cond-> result
|
||||
(frame-shape? shape)
|
||||
(conj (:id shape))))]
|
||||
(reduce-objects objects (complement frame-shape?) add-frame [])))
|
||||
|
||||
(defn get-root-objects
|
||||
"Get all the objects under the root object"
|
||||
[objects]
|
||||
(let [add-shape
|
||||
(fn [result shape]
|
||||
(conj result shape))]
|
||||
(reduce-objects objects (complement frame-shape?) add-shape [])))
|
||||
|
||||
(defn get-root-shapes
|
||||
"Get all shapes that are not frames"
|
||||
[objects]
|
||||
(let [add-shape
|
||||
(fn [result shape]
|
||||
(cond-> result
|
||||
(not (frame-shape? shape))
|
||||
(conj shape)))]
|
||||
(reduce-objects objects (complement frame-shape?) add-shape [])))
|
||||
|
||||
(defn get-root-shapes-ids
|
||||
[objects]
|
||||
(->> (get-root-shapes objects)
|
||||
(mapv :id)))
|
||||
|
||||
(defn get-base
|
||||
[objects id-a id-b]
|
||||
|
||||
(let [parents-a (reverse (get-parents-seq objects id-a))
|
||||
parents-b (reverse (get-parents-seq objects id-b))
|
||||
|
||||
[base base-child-a base-child-b]
|
||||
(loop [parents-a (rest parents-a)
|
||||
parents-b (rest parents-b)
|
||||
base uuid/zero]
|
||||
(cond
|
||||
(not= (first parents-a) (first parents-b))
|
||||
[base (first parents-a) (first parents-b)]
|
||||
|
||||
(or (empty? parents-a) (empty? parents-b))
|
||||
[uuid/zero (first parents-a) (first parents-b)]
|
||||
|
||||
:else
|
||||
(recur (rest parents-a) (rest parents-b) (first parents-a))))
|
||||
|
||||
index-base-a (when base-child-a (get-position-on-parent objects base-child-a))
|
||||
index-base-b (when base-child-b (get-position-on-parent objects base-child-b))]
|
||||
|
||||
[base index-base-a index-base-b]))
|
||||
|
||||
(defn is-shape-over-shape?
|
||||
[objects base-shape-id over-shape-id {:keys [top-frames?]}]
|
||||
|
||||
(let [[base index-a index-b] (get-base objects base-shape-id over-shape-id)]
|
||||
(cond
|
||||
(= base base-shape-id)
|
||||
(and (not top-frames?)
|
||||
(frame-shape? objects base-shape-id)
|
||||
(root-frame? objects base-shape-id))
|
||||
|
||||
(= base over-shape-id)
|
||||
(or top-frames?
|
||||
(not (frame-shape? objects over-shape-id))
|
||||
(not (root-frame? objects over-shape-id)))
|
||||
|
||||
:else
|
||||
(< index-a index-b))))
|
||||
|
||||
(defn sort-z-index
|
||||
([objects ids]
|
||||
(sort-z-index objects ids nil))
|
||||
|
||||
([objects ids {:keys [bottom-frames?] :as options}]
|
||||
(letfn [(comp [id-a id-b]
|
||||
(let [type-a (dm/get-in objects [id-a :type])
|
||||
type-b (dm/get-in objects [id-b :type])]
|
||||
(cond
|
||||
(and bottom-frames? (= :frame type-a) (not= :frame type-b))
|
||||
1
|
||||
|
||||
(and bottom-frames? (not= :frame type-a) (= :frame type-b))
|
||||
-1
|
||||
|
||||
(= id-a id-b)
|
||||
0
|
||||
|
||||
(is-shape-over-shape? objects id-a id-b options)
|
||||
1
|
||||
|
||||
:else
|
||||
-1)))]
|
||||
(sort comp ids))))
|
||||
|
||||
(defn frame-id-by-position
|
||||
[objects position]
|
||||
(let [top-frame
|
||||
(->> (get-frames-ids objects)
|
||||
(sort-z-index objects)
|
||||
(d/seek #(and position (gsh/has-point? (get objects %) position))))]
|
||||
(or top-frame uuid/zero)))
|
||||
|
||||
(defn frame-by-position
|
||||
[objects position]
|
||||
(let [frame-id (frame-id-by-position objects position)]
|
||||
(get objects frame-id)))
|
||||
|
||||
(declare indexed-shapes)
|
||||
|
||||
(defn get-base-shape
|
||||
|
@ -356,16 +205,6 @@
|
|||
([libraries library-id component-id]
|
||||
(get-in libraries [library-id :data :components component-id])))
|
||||
|
||||
(defn is-main-of?
|
||||
[shape-main shape-inst]
|
||||
(and (:shape-ref shape-inst)
|
||||
(or (= (:shape-ref shape-inst) (:id shape-main))
|
||||
(= (:shape-ref shape-inst) (:shape-ref shape-main)))))
|
||||
|
||||
(defn get-component-root
|
||||
[component]
|
||||
(get-in component [:objects (:id component)]))
|
||||
|
||||
(defn get-component-shape
|
||||
"Get the parent shape linked to a component for this shape, if any"
|
||||
[objects shape]
|
||||
|
@ -462,57 +301,6 @@
|
|||
|
||||
(reduce add-element (d/ordered-set) ids)))
|
||||
|
||||
(defn clone-object
|
||||
"Gets a copy of the object and all its children, with new ids
|
||||
and with the parent-children links correctly set. Admits functions
|
||||
to make more transformations to the cloned objects and the
|
||||
original ones.
|
||||
|
||||
Returns the cloned object, the list of all new objects (including
|
||||
the cloned one), and possibly a list of original objects modified."
|
||||
|
||||
([object parent-id objects update-new-object]
|
||||
(clone-object object parent-id objects update-new-object identity))
|
||||
|
||||
([object parent-id objects update-new-object update-original-object]
|
||||
(let [new-id (uuid/next)]
|
||||
(loop [child-ids (seq (:shapes object))
|
||||
new-direct-children []
|
||||
new-children []
|
||||
updated-children []]
|
||||
|
||||
(if (empty? child-ids)
|
||||
(let [new-object (cond-> object
|
||||
true
|
||||
(assoc :id new-id
|
||||
:parent-id parent-id)
|
||||
|
||||
(some? (:shapes object))
|
||||
(assoc :shapes (mapv :id new-direct-children)))
|
||||
|
||||
new-object (update-new-object new-object object)
|
||||
new-objects (into [new-object] new-children)
|
||||
|
||||
updated-object (update-original-object object new-object)
|
||||
updated-objects (if (identical? object updated-object)
|
||||
updated-children
|
||||
(into [updated-object] updated-children))]
|
||||
|
||||
[new-object new-objects updated-objects])
|
||||
|
||||
(let [child-id (first child-ids)
|
||||
child (get objects child-id)
|
||||
_ (us/assert some? child)
|
||||
|
||||
[new-child new-child-objects updated-child-objects]
|
||||
(clone-object child new-id objects update-new-object update-original-object)]
|
||||
|
||||
(recur
|
||||
(next child-ids)
|
||||
(into new-direct-children [new-child])
|
||||
(into new-children new-child-objects)
|
||||
(into updated-children updated-child-objects))))))))
|
||||
|
||||
(defn indexed-shapes
|
||||
"Retrieves a list with the indexes for each element in the layer tree.
|
||||
This will be used for shift+selection."
|
||||
|
@ -695,36 +483,3 @@
|
|||
|
||||
:id))
|
||||
|
||||
(defn get-viewer-frames
|
||||
([objects]
|
||||
(get-viewer-frames objects nil))
|
||||
|
||||
([objects {:keys [all-frames?]}]
|
||||
(into []
|
||||
(comp (map (d/getf objects))
|
||||
(if all-frames?
|
||||
(map identity)
|
||||
(remove :hide-in-viewer)))
|
||||
(sort-z-index objects (get-frames-ids objects) {:top-frames? true}))))
|
||||
|
||||
(defn start-page-index
|
||||
[objects]
|
||||
(with-meta objects {::index-frames (get-frames (with-meta objects nil))}))
|
||||
|
||||
(defn update-page-index
|
||||
[objects]
|
||||
(with-meta objects {::index-frames (get-frames (with-meta objects nil))}))
|
||||
|
||||
(defn start-object-indices
|
||||
[file]
|
||||
(letfn [(process-index [page-index page-id]
|
||||
(update-in page-index [page-id :objects] start-page-index))]
|
||||
(update file :pages-index #(reduce process-index % (keys %)))))
|
||||
|
||||
(defn update-object-indices
|
||||
[file page-id]
|
||||
(update-in file [:pages-index page-id :objects] update-page-index))
|
||||
|
||||
(defn rotated-frame?
|
||||
[frame]
|
||||
(not (mth/almost-zero? (:rotation frame 0))))
|
||||
|
|
|
@ -1,186 +0,0 @@
|
|||
;; This Source Code Form is subject to the terms of the Mozilla Public
|
||||
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
;;
|
||||
;; Copyright (c) UXBOX Labs SL
|
||||
|
||||
(ns app.common.pages.init
|
||||
(:require
|
||||
[app.common.colors :as clr]
|
||||
[app.common.data :as d]
|
||||
[app.common.exceptions :as ex]
|
||||
[app.common.geom.shapes :as gsh]
|
||||
[app.common.pages.common :refer [file-version default-color]]
|
||||
[app.common.uuid :as uuid]))
|
||||
|
||||
(def root uuid/zero)
|
||||
|
||||
(def empty-page-data
|
||||
{:options {}
|
||||
:name "Page-1"
|
||||
:objects
|
||||
{root
|
||||
{:id root
|
||||
:type :frame
|
||||
:name "Root Frame"}}})
|
||||
|
||||
(def empty-file-data
|
||||
{:version file-version
|
||||
:pages []
|
||||
:pages-index {}})
|
||||
|
||||
(def default-shape-attrs
|
||||
{})
|
||||
|
||||
(def default-frame-attrs
|
||||
{:frame-id uuid/zero
|
||||
:fills [{:fill-color clr/white
|
||||
:fill-opacity 1}]
|
||||
:strokes []
|
||||
:shapes []
|
||||
:hide-fill-on-export false})
|
||||
|
||||
(def ^:private minimal-shapes
|
||||
[{:type :rect
|
||||
:name "Rect-1"
|
||||
:fills [{:fill-color default-color
|
||||
:fill-opacity 1}]
|
||||
:strokes []
|
||||
:rx 0
|
||||
:ry 0}
|
||||
|
||||
{:type :image
|
||||
:rx 0
|
||||
:ry 0
|
||||
:fills []
|
||||
:strokes []}
|
||||
|
||||
{:type :circle
|
||||
:name "Circle-1"
|
||||
:fills [{:fill-color default-color
|
||||
:fill-opacity 1}]
|
||||
:strokes []}
|
||||
|
||||
{:type :path
|
||||
:name "Path-1"
|
||||
:fills []
|
||||
:strokes [{:stroke-style :solid
|
||||
:stroke-alignment :center
|
||||
:stroke-width 2
|
||||
:stroke-color clr/black
|
||||
:stroke-opacity 1}]}
|
||||
|
||||
{:type :frame
|
||||
:name "Board-1"
|
||||
:fills [{:fill-color clr/white
|
||||
:fill-opacity 1}]
|
||||
:strokes []
|
||||
:stroke-style :none
|
||||
:stroke-alignment :center
|
||||
:stroke-width 0
|
||||
:stroke-color clr/black
|
||||
:stroke-opacity 0
|
||||
:rx 0
|
||||
:ry 0}
|
||||
|
||||
{:type :text
|
||||
:name "Text-1"
|
||||
:content nil}
|
||||
|
||||
{:type :svg-raw}])
|
||||
|
||||
(def empty-selrect
|
||||
{:x 0 :y 0
|
||||
:x1 0 :y1 0
|
||||
:x2 0.01 :y2 0.01
|
||||
:width 0.01 :height 0.01})
|
||||
|
||||
(defn make-minimal-shape
|
||||
[type]
|
||||
(let [type (cond (= type :curve) :path
|
||||
:else type)
|
||||
shape (d/seek #(= type (:type %)) minimal-shapes)]
|
||||
(when-not shape
|
||||
(ex/raise :type :assertion
|
||||
:code :shape-type-not-implemented
|
||||
:context {:type type}))
|
||||
|
||||
(cond-> shape
|
||||
:always
|
||||
(assoc :id (uuid/next))
|
||||
|
||||
(not= :path (:type shape))
|
||||
(assoc :x 0
|
||||
:y 0
|
||||
:width 0.01
|
||||
:height 0.01
|
||||
:selrect {:x 0
|
||||
:y 0
|
||||
:x1 0
|
||||
:y1 0
|
||||
:x2 0.01
|
||||
:y2 0.01
|
||||
:width 0.01
|
||||
:height 0.01}))))
|
||||
|
||||
(defn make-minimal-group
|
||||
[frame-id selection-rect group-name]
|
||||
{:id (uuid/next)
|
||||
:type :group
|
||||
:name group-name
|
||||
:shapes []
|
||||
:frame-id frame-id
|
||||
:x (:x selection-rect)
|
||||
:y (:y selection-rect)
|
||||
:width (:width selection-rect)
|
||||
:height (:height selection-rect)})
|
||||
|
||||
(defn make-file-data
|
||||
([file-id]
|
||||
(make-file-data file-id (uuid/next)))
|
||||
|
||||
([file-id page-id]
|
||||
(let [pd (assoc empty-page-data
|
||||
:id page-id
|
||||
:name "Page-1")]
|
||||
(-> empty-file-data
|
||||
(assoc :id file-id)
|
||||
(update :pages conj page-id)
|
||||
(update :pages-index assoc page-id pd)))))
|
||||
|
||||
(defn setup-rect-selrect
|
||||
"Initializes the selrect and points for a shape"
|
||||
[shape]
|
||||
(let [selrect (gsh/rect->selrect shape)
|
||||
points (gsh/rect->points shape)]
|
||||
(-> shape
|
||||
(assoc :selrect selrect
|
||||
:points points))))
|
||||
|
||||
(defn- setup-rect
|
||||
"A specialized function for setup rect-like shapes."
|
||||
[shape {:keys [x y width height]}]
|
||||
(-> shape
|
||||
(assoc :x x :y y :width width :height height)
|
||||
(setup-rect-selrect)))
|
||||
|
||||
(defn- setup-image
|
||||
[{:keys [metadata] :as shape} props]
|
||||
(-> (setup-rect shape props)
|
||||
(assoc
|
||||
:proportion (/ (:width metadata)
|
||||
(:height metadata))
|
||||
:proportion-lock true)))
|
||||
|
||||
(defn setup-shape
|
||||
"A function that initializes the first coordinates for
|
||||
the shape. Used mainly for draw operations."
|
||||
([props]
|
||||
(setup-shape {:type :rect} props))
|
||||
|
||||
([shape props]
|
||||
(case (:type shape)
|
||||
:image (setup-image shape props)
|
||||
(setup-rect shape props))))
|
||||
|
||||
|
|
@ -15,6 +15,7 @@
|
|||
[app.common.math :as mth]
|
||||
[app.common.pages :as cp]
|
||||
[app.common.pages.helpers :as cph]
|
||||
[app.common.types.shape :as cts]
|
||||
[app.common.uuid :as uuid]
|
||||
[cuerdas.core :as str]))
|
||||
|
||||
|
@ -84,7 +85,7 @@
|
|||
|
||||
(fix-empty-points [shape]
|
||||
(let [shape (cond-> shape
|
||||
(empty? (:selrect shape)) (cp/setup-rect-selrect))]
|
||||
(empty? (:selrect shape)) (cts/setup-rect-selrect))]
|
||||
(cond-> shape
|
||||
(empty? (:points shape))
|
||||
(assoc :points (gsh/rect->points (:selrect shape))))))
|
||||
|
|
|
@ -107,6 +107,7 @@
|
|||
(s/def ::number (s/conformer number-conformer str))
|
||||
(s/def ::integer (s/conformer integer-conformer str))
|
||||
(s/def ::not-empty-string (s/and string? #(not (str/empty? %))))
|
||||
(s/def ::set-of-string (s/every string? :kind set?))
|
||||
(s/def ::url string?)
|
||||
(s/def ::fn fn?)
|
||||
(s/def ::id ::uuid)
|
||||
|
|
|
@ -102,6 +102,12 @@
|
|||
:fill-opacity opacity
|
||||
:fill-color-gradient gradient)))))
|
||||
|
||||
(defn attach-fill-color
|
||||
[shape position ref-id ref-file]
|
||||
(-> shape
|
||||
(assoc-in [:fills position :fill-color-ref-id] ref-id)
|
||||
(assoc-in [:fills position :fill-color-ref-file] ref-file)))
|
||||
|
||||
(defn detach-fill-color
|
||||
[shape position]
|
||||
(-> shape
|
||||
|
@ -127,6 +133,12 @@
|
|||
:stroke-opacity opacity
|
||||
:stroke-color-gradient gradient)))))
|
||||
|
||||
(defn attach-stroke-color
|
||||
[shape position ref-id ref-file]
|
||||
(-> shape
|
||||
(assoc-in [:strokes position :stroke-color-ref-id] ref-id)
|
||||
(assoc-in [:strokes position :stroke-color-ref-file] ref-file)))
|
||||
|
||||
(defn detach-stroke-color
|
||||
[shape position]
|
||||
(-> shape
|
||||
|
@ -152,6 +164,12 @@
|
|||
:opacity opacity
|
||||
:gradient gradient)))))
|
||||
|
||||
(defn attach-shadow-color
|
||||
[shape position ref-id ref-file]
|
||||
(-> shape
|
||||
(assoc-in [:shadow position :color :id] ref-id)
|
||||
(assoc-in [:shadow position :color :file-id] ref-file)))
|
||||
|
||||
(defn detach-shadow-color
|
||||
[shape position]
|
||||
(-> shape
|
||||
|
@ -176,6 +194,11 @@
|
|||
:color color
|
||||
:opacity opacity
|
||||
:gradient gradient)))))
|
||||
(defn attach-grid-color
|
||||
[shape position ref-id ref-file]
|
||||
(-> shape
|
||||
(assoc-in [:grids position :params :color :id] ref-id)
|
||||
(assoc-in [:grids position :params :color :file-id] ref-file)))
|
||||
|
||||
(defn detach-grid-color
|
||||
[shape position]
|
||||
|
@ -213,65 +236,97 @@
|
|||
(= (:ref-file %) library-id))
|
||||
all-colors)))
|
||||
|
||||
(defn uses-library-color?
|
||||
"Check if the shape uses the given library color."
|
||||
[shape library-id color-id]
|
||||
(let [all-colors (get-all-colors shape)]
|
||||
(some #(and (= (:ref-id %) color-id)
|
||||
(= (:ref-file %) library-id))
|
||||
all-colors)))
|
||||
|
||||
(defn- process-shape-colors
|
||||
"Execute an update function on all colors of a shape."
|
||||
[shape process-fn]
|
||||
(let [process-fill (fn [shape [position fill]]
|
||||
(process-fn shape
|
||||
position
|
||||
(fill->shape-color fill)
|
||||
set-fill-color
|
||||
attach-fill-color
|
||||
detach-fill-color))
|
||||
|
||||
process-stroke (fn [shape [position stroke]]
|
||||
(process-fn shape
|
||||
position
|
||||
(stroke->shape-color stroke)
|
||||
set-stroke-color
|
||||
attach-stroke-color
|
||||
detach-stroke-color))
|
||||
|
||||
process-shadow (fn [shape [position shadow]]
|
||||
(process-fn shape
|
||||
position
|
||||
(shadow->shape-color shadow)
|
||||
set-shadow-color
|
||||
attach-shadow-color
|
||||
detach-shadow-color))
|
||||
|
||||
process-grid (fn [shape [position grid]]
|
||||
(process-fn shape
|
||||
position
|
||||
(grid->shape-color grid)
|
||||
set-grid-color
|
||||
attach-grid-color
|
||||
detach-grid-color))
|
||||
|
||||
process-text-node (fn [node]
|
||||
(as-> node $
|
||||
(reduce process-fill $ (d/enumerate (:fills $)))
|
||||
(reduce process-stroke $ (d/enumerate (:strokes $)))))
|
||||
|
||||
process-text (fn [shape]
|
||||
(let [content (:content shape)
|
||||
new-content (txt/transform-nodes process-text-node content)]
|
||||
(if (not= content new-content)
|
||||
(assoc shape :content new-content)
|
||||
shape)))]
|
||||
|
||||
(as-> shape $
|
||||
(reduce process-fill $ (d/enumerate (:fills $)))
|
||||
(reduce process-stroke $ (d/enumerate (:strokes $)))
|
||||
(reduce process-shadow $ (d/enumerate (:shadow $)))
|
||||
(reduce process-grid $ (d/enumerate (:grids $)))
|
||||
(process-text $))))
|
||||
|
||||
(defn remap-colors
|
||||
"Change the shape so that any use of the given color now points to
|
||||
the given library."
|
||||
[shape library-id color]
|
||||
(letfn [(remap-color [shape position shape-color _ attach-fn _]
|
||||
(if (= (:ref-id shape-color) (:id color))
|
||||
(attach-fn shape
|
||||
position
|
||||
(:id color)
|
||||
library-id)
|
||||
shape))]
|
||||
|
||||
(process-shape-colors shape remap-color)))
|
||||
|
||||
(defn sync-shape-colors
|
||||
"Look for usage of any color of the given library inside the shape,
|
||||
and, in this case, copy the library color into the shape."
|
||||
[shape library-id library-colors]
|
||||
(let [sync-color (fn [shape position shape-color set-fn detach-fn]
|
||||
(if (= (:ref-file shape-color) library-id)
|
||||
(let [library-color (get library-colors (:ref-id shape-color))]
|
||||
(if (some? library-color)
|
||||
(set-fn shape
|
||||
position
|
||||
(:color library-color)
|
||||
(:opacity library-color)
|
||||
(:gradient library-color))
|
||||
(detach-fn shape position)))
|
||||
shape))
|
||||
(letfn [(sync-color [shape position shape-color set-fn _ detach-fn]
|
||||
(if (= (:ref-file shape-color) library-id)
|
||||
(let [library-color (get library-colors (:ref-id shape-color))]
|
||||
(if (some? library-color)
|
||||
(set-fn shape
|
||||
position
|
||||
(:color library-color)
|
||||
(:opacity library-color)
|
||||
(:gradient library-color))
|
||||
(detach-fn shape position)))
|
||||
shape))]
|
||||
|
||||
sync-fill (fn [shape [position fill]]
|
||||
(sync-color shape
|
||||
position
|
||||
(fill->shape-color fill)
|
||||
set-fill-color
|
||||
detach-fill-color))
|
||||
(process-shape-colors shape sync-color)))
|
||||
|
||||
sync-stroke (fn [shape [position stroke]]
|
||||
(sync-color shape
|
||||
position
|
||||
(stroke->shape-color stroke)
|
||||
set-stroke-color
|
||||
detach-stroke-color))
|
||||
|
||||
sync-shadow (fn [shape [position shadow]]
|
||||
(sync-color shape
|
||||
position
|
||||
(shadow->shape-color shadow)
|
||||
set-shadow-color
|
||||
detach-shadow-color))
|
||||
|
||||
sync-grid (fn [shape [position grid]]
|
||||
(sync-color shape
|
||||
position
|
||||
(grid->shape-color grid)
|
||||
set-grid-color
|
||||
detach-grid-color))
|
||||
|
||||
sync-text-node (fn [node]
|
||||
(as-> node $
|
||||
(reduce sync-fill $ (d/enumerate (:fills $)))
|
||||
(reduce sync-stroke $ (d/enumerate (:strokes $)))))
|
||||
|
||||
sync-text (fn [shape]
|
||||
(let [content (:content shape)
|
||||
new-content (txt/transform-nodes sync-text-node content)]
|
||||
(if (not= content new-content)
|
||||
(assoc shape :content new-content)
|
||||
shape)))]
|
||||
|
||||
(as-> shape $
|
||||
(reduce sync-fill $ (d/enumerate (:fills $)))
|
||||
(reduce sync-stroke $ (d/enumerate (:strokes $)))
|
||||
(reduce sync-shadow $ (d/enumerate (:shadow $)))
|
||||
(reduce sync-grid $ (d/enumerate (:grids $)))
|
||||
(sync-text $))))
|
||||
|
|
24
common/src/app/common/types/colors_list.cljc
Normal file
24
common/src/app/common/types/colors_list.cljc
Normal file
|
@ -0,0 +1,24 @@
|
|||
;; This Source Code Form is subject to the terms of the Mozilla Public
|
||||
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
;;
|
||||
;; Copyright (c) UXBOX Labs SL
|
||||
|
||||
(ns app.common.types.colors-list)
|
||||
|
||||
(defn colors-seq
|
||||
[file-data]
|
||||
(vals (:colors file-data)))
|
||||
|
||||
(defn add-color
|
||||
[file-data color]
|
||||
(update file-data :colors assoc (:id color) color))
|
||||
|
||||
(defn get-color
|
||||
[file-data color-id]
|
||||
(get-in file-data [:colors color-id]))
|
||||
|
||||
(defn update-color
|
||||
[file-data color-id f]
|
||||
(update-in file-data [:colors color-id] f))
|
||||
|
36
common/src/app/common/types/component.cljc
Normal file
36
common/src/app/common/types/component.cljc
Normal file
|
@ -0,0 +1,36 @@
|
|||
;; This Source Code Form is subject to the terms of the Mozilla Public
|
||||
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
;;
|
||||
;; Copyright (c) UXBOX Labs SL
|
||||
|
||||
(ns app.common.types.component)
|
||||
|
||||
(defn instance-of?
|
||||
[shape file-id component-id]
|
||||
(and (some? (:component-id shape))
|
||||
(some? (:component-file shape))
|
||||
(= (:component-id shape) component-id)
|
||||
(= (:component-file shape) file-id)))
|
||||
|
||||
(defn is-main-of?
|
||||
[shape-main shape-inst]
|
||||
(and (:shape-ref shape-inst)
|
||||
(or (= (:shape-ref shape-inst) (:id shape-main))
|
||||
(= (:shape-ref shape-inst) (:shape-ref shape-main)))))
|
||||
|
||||
(defn is-main-instance?
|
||||
[shape-id page-id component]
|
||||
(and (= shape-id (:main-instance-id component))
|
||||
(= page-id (:main-instance-page component))))
|
||||
|
||||
(defn get-component-root
|
||||
[component]
|
||||
(get-in component [:objects (:id component)]))
|
||||
|
||||
(defn uses-library-components?
|
||||
"Check if the shape uses any component in the given library."
|
||||
[shape library-id]
|
||||
(and (some? (:component-id shape))
|
||||
(= (:component-file shape) library-id)))
|
||||
|
37
common/src/app/common/types/components_list.cljc
Normal file
37
common/src/app/common/types/components_list.cljc
Normal file
|
@ -0,0 +1,37 @@
|
|||
;; This Source Code Form is subject to the terms of the Mozilla Public
|
||||
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
;;
|
||||
;; Copyright (c) UXBOX Labs SL
|
||||
|
||||
(ns app.common.types.components-list
|
||||
(:require
|
||||
[app.common.data :as d]))
|
||||
|
||||
(defn components-seq
|
||||
[file-data]
|
||||
(vals (:components file-data)))
|
||||
|
||||
(defn add-component
|
||||
[file-data id name path main-instance-id main-instance-page shapes]
|
||||
(let [components-v2 (get-in file-data [:options :components-v2])]
|
||||
(cond-> file-data
|
||||
:always
|
||||
(assoc-in [:components id]
|
||||
{:id id
|
||||
:name name
|
||||
:path path
|
||||
:objects (d/index-by :id shapes)})
|
||||
|
||||
components-v2
|
||||
(update-in [:components id] assoc :main-instance-id main-instance-id
|
||||
:main-instance-page main-instance-page))))
|
||||
|
||||
(defn get-component
|
||||
[file-data component-id]
|
||||
(get-in file-data [:components component-id]))
|
||||
|
||||
(defn update-component
|
||||
[file-data component-id f]
|
||||
(update-in file-data [:components component-id] f))
|
||||
|
160
common/src/app/common/types/container.cljc
Normal file
160
common/src/app/common/types/container.cljc
Normal file
|
@ -0,0 +1,160 @@
|
|||
;; This Source Code Form is subject to the terms of the Mozilla Public
|
||||
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
;;
|
||||
;; Copyright (c) UXBOX Labs SL
|
||||
|
||||
(ns app.common.types.container
|
||||
(:require
|
||||
[app.common.geom.point :as gpt]
|
||||
[app.common.geom.shapes :as gsh]
|
||||
[app.common.spec :as us]
|
||||
[app.common.types.shape-tree :as ctst]
|
||||
[clojure.spec.alpha :as s]))
|
||||
|
||||
(s/def ::type #{:page :component})
|
||||
(s/def ::id uuid?)
|
||||
(s/def ::name string?)
|
||||
(s/def ::path (s/nilable string?))
|
||||
|
||||
(s/def ::container
|
||||
;; (s/keys :req-un [::id ::name ::ctst/objects]
|
||||
(s/keys :req-un [::id ::name]
|
||||
:opt-un [::type ::path]))
|
||||
|
||||
(defn make-container
|
||||
[page-or-component type]
|
||||
(assoc page-or-component :type type))
|
||||
|
||||
(defn page?
|
||||
[container]
|
||||
(= (:type container) :page))
|
||||
|
||||
(defn component?
|
||||
[container]
|
||||
(= (:type container) :component))
|
||||
|
||||
(defn get-container
|
||||
[file type id]
|
||||
(us/assert map? file)
|
||||
(us/assert ::type type)
|
||||
(us/assert uuid? id)
|
||||
|
||||
(-> (if (= type :page)
|
||||
(get-in file [:pages-index id])
|
||||
(get-in file [:components id]))
|
||||
(assoc :type type)))
|
||||
|
||||
(defn get-shape
|
||||
[container shape-id]
|
||||
(us/assert ::container container)
|
||||
(us/assert ::us/uuid shape-id)
|
||||
(-> container
|
||||
(get :objects)
|
||||
(get shape-id)))
|
||||
|
||||
(defn shapes-seq
|
||||
[container]
|
||||
(vals (:objects container)))
|
||||
|
||||
(defn update-shape
|
||||
[container shape-id f]
|
||||
(update-in container [:objects shape-id] f))
|
||||
|
||||
(defn make-component-shape
|
||||
"Clone the shape and all children. Generate new ids and detach
|
||||
from parent and frame. Update the original shapes to have links
|
||||
to the new ones."
|
||||
[shape objects file-id components-v2]
|
||||
(assert (nil? (:component-id shape)))
|
||||
(assert (nil? (:component-file shape)))
|
||||
(assert (nil? (:shape-ref shape)))
|
||||
(let [;; Ensure that the component root is not an instance and
|
||||
;; it's no longer tied to a frame.
|
||||
update-new-shape (fn [new-shape _original-shape]
|
||||
(cond-> new-shape
|
||||
true
|
||||
(-> (assoc :frame-id nil)
|
||||
(dissoc :component-root?))
|
||||
|
||||
(nil? (:parent-id new-shape))
|
||||
(dissoc :component-id
|
||||
:component-file
|
||||
:shape-ref)))
|
||||
|
||||
;; Make the original shape an instance of the new component.
|
||||
;; If one of the original shape children already was a component
|
||||
;; instance, maintain this instanceness untouched.
|
||||
update-original-shape (fn [original-shape new-shape]
|
||||
(cond-> original-shape
|
||||
(nil? (:shape-ref original-shape))
|
||||
(-> (assoc :shape-ref (:id new-shape))
|
||||
(dissoc :touched))
|
||||
|
||||
(nil? (:parent-id new-shape))
|
||||
(assoc :component-id (:id new-shape)
|
||||
:component-file file-id
|
||||
:component-root? true)
|
||||
|
||||
(and (nil? (:parent-id new-shape)) components-v2)
|
||||
(assoc :main-instance? true)
|
||||
|
||||
(some? (:parent-id new-shape))
|
||||
(dissoc :component-root?)))]
|
||||
|
||||
(ctst/clone-object shape nil objects update-new-shape update-original-shape)))
|
||||
|
||||
(defn make-component-instance
|
||||
"Clone the shapes of the component, generating new names and ids, and linking
|
||||
each new shape to the corresponding one of the component. Place the new instance
|
||||
coordinates in the given position."
|
||||
[container component component-file-id position main-instance?]
|
||||
(let [component-shape (get-shape component (:id component))
|
||||
|
||||
orig-pos (gpt/point (:x component-shape) (:y component-shape))
|
||||
delta (gpt/subtract position orig-pos)
|
||||
|
||||
objects (:objects container)
|
||||
unames (volatile! (ctst/retrieve-used-names objects))
|
||||
|
||||
frame-id (ctst/frame-id-by-position objects (gpt/add orig-pos delta))
|
||||
|
||||
update-new-shape
|
||||
(fn [new-shape original-shape]
|
||||
(let [new-name (ctst/generate-unique-name @unames (:name new-shape))]
|
||||
|
||||
(when (nil? (:parent-id original-shape))
|
||||
(vswap! unames conj new-name))
|
||||
|
||||
(cond-> new-shape
|
||||
true
|
||||
(as-> $
|
||||
(gsh/move $ delta)
|
||||
(assoc $ :frame-id frame-id)
|
||||
(assoc $ :parent-id
|
||||
(or (:parent-id $) (:frame-id $)))
|
||||
(dissoc $ :touched))
|
||||
|
||||
(nil? (:shape-ref original-shape))
|
||||
(assoc :shape-ref (:id original-shape))
|
||||
|
||||
(nil? (:parent-id original-shape))
|
||||
(assoc :component-id (:id original-shape)
|
||||
:component-file component-file-id
|
||||
:component-root? true
|
||||
:name new-name)
|
||||
|
||||
(and (nil? (:parent-id original-shape)) main-instance?)
|
||||
(assoc :main-instance? true)
|
||||
|
||||
(some? (:parent-id original-shape))
|
||||
(dissoc :component-root?))))
|
||||
|
||||
[new-shape new-shapes _]
|
||||
(ctst/clone-object component-shape
|
||||
nil
|
||||
(get component :objects)
|
||||
update-new-shape)]
|
||||
|
||||
[new-shape new-shapes]))
|
||||
|
|
@ -6,10 +6,27 @@
|
|||
|
||||
(ns app.common.types.file
|
||||
(:require
|
||||
[app.common.spec :as us]
|
||||
[app.common.types.color :as ctc]
|
||||
[app.common.types.page :as ctp]
|
||||
[clojure.spec.alpha :as s]))
|
||||
[app.common.data :as d]
|
||||
[app.common.geom.point :as gpt]
|
||||
[app.common.geom.shapes :as gsh]
|
||||
[app.common.pages.common :refer [file-version]]
|
||||
[app.common.pages.helpers :as cph]
|
||||
[app.common.spec :as us]
|
||||
[app.common.types.color :as ctc]
|
||||
[app.common.types.colors-list :as ctcl]
|
||||
[app.common.types.component :as ctk]
|
||||
[app.common.types.components-list :as ctkl]
|
||||
[app.common.types.container :as ctn]
|
||||
[app.common.types.page :as ctp]
|
||||
[app.common.types.pages-list :as ctpl]
|
||||
[app.common.types.shape-tree :as ctst]
|
||||
[app.common.types.typographies-list :as ctyl]
|
||||
[app.common.types.typography :as cty]
|
||||
[app.common.uuid :as uuid]
|
||||
[clojure.spec.alpha :as s]
|
||||
[cuerdas.core :as str]))
|
||||
|
||||
;; Specs
|
||||
|
||||
(s/def :internal.media-object/name string?)
|
||||
(s/def :internal.media-object/width ::us/safe-integer)
|
||||
|
@ -57,3 +74,413 @@
|
|||
::recent-colors
|
||||
::typographies
|
||||
::media]))
|
||||
|
||||
;; Initialization
|
||||
|
||||
(def empty-file-data
|
||||
{:version file-version
|
||||
:pages []
|
||||
:pages-index {}})
|
||||
|
||||
(defn make-file-data
|
||||
([file-id components-v2]
|
||||
(make-file-data file-id (uuid/next) components-v2))
|
||||
|
||||
([file-id page-id components-v2]
|
||||
(let [page (ctp/make-empty-page page-id "Page-1")]
|
||||
(cond-> (-> empty-file-data
|
||||
(assoc :id file-id)
|
||||
(ctpl/add-page page))
|
||||
|
||||
components-v2
|
||||
(assoc-in [:options :components-v2] true)))))
|
||||
|
||||
;; Helpers
|
||||
|
||||
(defn file-data
|
||||
[file]
|
||||
(:data file))
|
||||
|
||||
(defn update-file-data
|
||||
[file f]
|
||||
(update file :data f))
|
||||
|
||||
(defn containers-seq
|
||||
"Generate a sequence of all pages and all components, wrapped as containers"
|
||||
[file-data]
|
||||
(concat (map #(ctn/make-container % :page) (ctpl/pages-seq file-data))
|
||||
(map #(ctn/make-container % :component) (ctkl/components-seq file-data))))
|
||||
|
||||
(defn update-container
|
||||
"Update a container inside the file, it can be a page or a component"
|
||||
[file-data container f]
|
||||
(if (ctn/page? container)
|
||||
(ctpl/update-page file-data (:id container) f)
|
||||
(ctkl/update-component file-data (:id container) f)))
|
||||
|
||||
;; Asset helpers
|
||||
|
||||
(defmulti uses-asset?
|
||||
"Checks if a shape uses the given asset."
|
||||
(fn [asset-type _ _ _] asset-type))
|
||||
|
||||
(defmethod uses-asset? :component
|
||||
[_ shape library-id component]
|
||||
(ctk/instance-of? shape library-id (:id component)))
|
||||
|
||||
(defmethod uses-asset? :color
|
||||
[_ shape library-id color]
|
||||
(ctc/uses-library-color? shape library-id (:id color)))
|
||||
|
||||
(defmethod uses-asset? :typography
|
||||
[_ shape library-id typography]
|
||||
(cty/uses-library-typography? shape library-id (:id typography)))
|
||||
|
||||
(defn find-asset-type-usages
|
||||
"Find all usages of an asset in a file (may be in pages or in the components
|
||||
of the local library).
|
||||
|
||||
Returns a list ((asset ((container shapes) (container shapes)...))...)"
|
||||
[file-data library-data asset-type]
|
||||
(let [assets-seq (case asset-type
|
||||
:component (ctkl/components-seq library-data)
|
||||
:color (ctcl/colors-seq library-data)
|
||||
:typography (ctyl/typographies-seq library-data))
|
||||
|
||||
find-usages-in-container
|
||||
(fn [container asset]
|
||||
(let [instances (filter #(uses-asset? asset-type % (:id library-data) asset)
|
||||
(ctn/shapes-seq container))]
|
||||
(when (d/not-empty? instances)
|
||||
[[container instances]])))
|
||||
|
||||
find-asset-usages
|
||||
(fn [file-data asset]
|
||||
(mapcat #(find-usages-in-container % asset) (containers-seq file-data)))]
|
||||
|
||||
(mapcat (fn [asset]
|
||||
(let [instances (find-asset-usages file-data asset)]
|
||||
(when (d/not-empty? instances)
|
||||
[[asset instances]])))
|
||||
assets-seq)))
|
||||
|
||||
(defn get-or-add-library-page
|
||||
"If exists a page named 'Library backup', get the id and calculate the position to start
|
||||
adding new components. If not, create it and start at (0, 0)."
|
||||
[file-data grid-gap]
|
||||
(let [library-page (d/seek #(= (:name %) "Library backup") (ctpl/pages-seq file-data))]
|
||||
(if (some? library-page)
|
||||
(let [compare-pos (fn [pos shape]
|
||||
(let [bounds (gsh/bounding-box shape)]
|
||||
(gpt/point (min (:x pos) (get bounds :x 0))
|
||||
(max (:y pos) (+ (get bounds :y 0)
|
||||
(get bounds :height 0)
|
||||
grid-gap)))))
|
||||
position (reduce compare-pos
|
||||
(gpt/point 0 0)
|
||||
(ctn/shapes-seq library-page))]
|
||||
[file-data (:id library-page) position])
|
||||
(let [library-page (ctp/make-empty-page (uuid/next) "Library backup")]
|
||||
[(ctpl/add-page file-data library-page) (:id library-page) (gpt/point 0 0)]))))
|
||||
|
||||
(defn migrate-to-components-v2
|
||||
"If there is any component in the file library, add a new 'Library backup' and generate
|
||||
main instances for all components there. Mark the file with the :comonents-v2 option."
|
||||
[file-data]
|
||||
(let [components (ctkl/components-seq file-data)]
|
||||
(if (or (empty? components)
|
||||
(get-in file-data [:options :components-v2]))
|
||||
(assoc-in file-data [:options :components-v2] true)
|
||||
(let [grid-gap 50
|
||||
|
||||
[file-data page-id start-pos]
|
||||
(get-or-add-library-page file-data grid-gap)
|
||||
|
||||
add-main-instance
|
||||
(fn [file-data component position]
|
||||
(let [page (ctpl/get-page file-data page-id)
|
||||
|
||||
[new-shape new-shapes]
|
||||
(ctn/make-component-instance page
|
||||
component
|
||||
(:id file-data)
|
||||
position
|
||||
true)
|
||||
|
||||
add-shapes
|
||||
(fn [page]
|
||||
(reduce (fn [page shape]
|
||||
(ctst/add-shape (:id shape)
|
||||
shape
|
||||
page
|
||||
(:frame-id shape)
|
||||
(:parent-id shape)
|
||||
nil ; <- As shapes are ordered, we can safely add each
|
||||
true)) ; one at the end of the parent's children list.
|
||||
page
|
||||
new-shapes))
|
||||
|
||||
update-component
|
||||
(fn [component]
|
||||
(assoc component
|
||||
:main-instance-id (:id new-shape)
|
||||
:main-instance-page page-id))]
|
||||
|
||||
(-> file-data
|
||||
(ctpl/update-page page-id add-shapes)
|
||||
(ctkl/update-component (:id component) update-component))))
|
||||
|
||||
add-instance-grid
|
||||
(fn [file-data components]
|
||||
(let [position-seq (ctst/generate-shape-grid
|
||||
(map ctk/get-component-root components)
|
||||
start-pos
|
||||
grid-gap)]
|
||||
(loop [file-data file-data
|
||||
components-seq (seq components)
|
||||
position-seq position-seq]
|
||||
(let [component (first components-seq)
|
||||
position (first position-seq)]
|
||||
(if (nil? component)
|
||||
file-data
|
||||
(recur (add-main-instance file-data component position)
|
||||
(rest components-seq)
|
||||
(rest position-seq)))))))]
|
||||
|
||||
(-> file-data
|
||||
(add-instance-grid (sort-by :name components))
|
||||
(assoc-in [:options :components-v2] true))))))
|
||||
|
||||
(defn- absorb-components
|
||||
[file-data used-components]
|
||||
(let [grid-gap 50
|
||||
|
||||
; Search for the library page. If not exists, create it.
|
||||
[file-data page-id start-pos]
|
||||
(get-or-add-library-page file-data grid-gap)
|
||||
|
||||
absorb-component
|
||||
(fn [file-data [component instances] position]
|
||||
(let [page (ctpl/get-page file-data page-id)
|
||||
|
||||
; Make a new main instance for the component
|
||||
[main-instance-shape main-instance-shapes]
|
||||
(ctn/make-component-instance page
|
||||
component
|
||||
(:id file-data)
|
||||
position
|
||||
true)
|
||||
|
||||
; Add all shapes of the main instance to the library page
|
||||
add-main-instance-shapes
|
||||
(fn [page]
|
||||
(reduce (fn [page shape]
|
||||
(ctst/add-shape (:id shape)
|
||||
shape
|
||||
page
|
||||
(:frame-id shape)
|
||||
(:parent-id shape)
|
||||
nil ; <- As shapes are ordered, we can safely add each
|
||||
true)) ; one at the end of the parent's children list.
|
||||
page
|
||||
main-instance-shapes))
|
||||
|
||||
; Copy the component in the file local library
|
||||
copy-component
|
||||
(fn [file-data]
|
||||
(ctkl/add-component file-data
|
||||
(:id component)
|
||||
(:name component)
|
||||
(:path component)
|
||||
(:id main-instance-shape)
|
||||
page-id
|
||||
(vals (:objects component))))
|
||||
|
||||
; Change all existing instances to point to the local file
|
||||
remap-instances
|
||||
(fn [file-data [container shapes]]
|
||||
(let [remap-instance #(assoc % :component-file (:id file-data))]
|
||||
(update-container file-data
|
||||
container
|
||||
#(reduce (fn [container shape]
|
||||
(ctn/update-shape container
|
||||
(:id shape)
|
||||
remap-instance))
|
||||
%
|
||||
shapes))))]
|
||||
|
||||
(as-> file-data $
|
||||
(ctpl/update-page $ page-id add-main-instance-shapes)
|
||||
(copy-component $)
|
||||
(reduce remap-instances $ instances))))
|
||||
|
||||
; Absorb all used components into the local library. Position
|
||||
; the main instances in a grid in the library page.
|
||||
add-component-grid
|
||||
(fn [data used-components]
|
||||
(let [position-seq (ctst/generate-shape-grid
|
||||
(map #(ctk/get-component-root (first %)) used-components)
|
||||
start-pos
|
||||
grid-gap)]
|
||||
(loop [data data
|
||||
components-seq (seq used-components)
|
||||
position-seq position-seq]
|
||||
(let [used-component (first components-seq)
|
||||
position (first position-seq)]
|
||||
(if (nil? used-component)
|
||||
data
|
||||
(recur (absorb-component data used-component position)
|
||||
(rest components-seq)
|
||||
(rest position-seq)))))))]
|
||||
|
||||
(add-component-grid file-data (sort-by #(:name (first %)) used-components))))
|
||||
|
||||
(defn- absorb-colors
|
||||
[file-data used-colors]
|
||||
(let [absorb-color
|
||||
(fn [file-data [color usages]]
|
||||
(let [remap-shape #(ctc/remap-colors % (:id file-data) color)
|
||||
|
||||
remap-shapes
|
||||
(fn [file-data [container shapes]]
|
||||
(update-container file-data
|
||||
container
|
||||
#(reduce (fn [container shape]
|
||||
(ctn/update-shape container
|
||||
(:id shape)
|
||||
remap-shape))
|
||||
%
|
||||
shapes)))]
|
||||
(as-> file-data $
|
||||
(ctcl/add-color $ color)
|
||||
(reduce remap-shapes $ usages))))]
|
||||
|
||||
(reduce absorb-color
|
||||
file-data
|
||||
used-colors)))
|
||||
|
||||
(defn- absorb-typographies
|
||||
[file-data used-typographies]
|
||||
(let [absorb-typography
|
||||
(fn [file-data [typography usages]]
|
||||
(let [remap-shape #(cty/remap-typographies % (:id file-data) typography)
|
||||
|
||||
remap-shapes
|
||||
(fn [file-data [container shapes]]
|
||||
(update-container file-data
|
||||
container
|
||||
#(reduce (fn [container shape]
|
||||
(ctn/update-shape container
|
||||
(:id shape)
|
||||
remap-shape))
|
||||
%
|
||||
shapes)))]
|
||||
(as-> file-data $
|
||||
(ctyl/add-typography $ typography)
|
||||
(reduce remap-shapes $ usages))))]
|
||||
|
||||
(reduce absorb-typography
|
||||
file-data
|
||||
used-typographies)))
|
||||
|
||||
(defn absorb-assets
|
||||
"Find all assets of a library that are used in the file, and
|
||||
move them to the file local library."
|
||||
[file-data library-data]
|
||||
(let [used-components (find-asset-type-usages file-data library-data :component)
|
||||
used-colors (find-asset-type-usages file-data library-data :color)
|
||||
used-typographies (find-asset-type-usages file-data library-data :typography)]
|
||||
|
||||
(cond-> file-data
|
||||
(d/not-empty? used-components)
|
||||
(absorb-components used-components)
|
||||
|
||||
(d/not-empty? used-colors)
|
||||
(absorb-colors used-colors)
|
||||
|
||||
(d/not-empty? used-typographies)
|
||||
(absorb-typographies used-typographies))))
|
||||
|
||||
|
||||
;; Debug helpers
|
||||
|
||||
(defn dump-tree
|
||||
([file-data page-id libraries]
|
||||
(dump-tree file-data page-id libraries false false))
|
||||
|
||||
([file-data page-id libraries show-ids]
|
||||
(dump-tree file-data page-id libraries show-ids false))
|
||||
|
||||
([file-data page-id libraries show-ids show-touched]
|
||||
(let [page (ctpl/get-page file-data page-id)
|
||||
objects (:objects page)
|
||||
components (:components file-data)
|
||||
root (d/seek #(nil? (:parent-id %)) (vals objects))]
|
||||
|
||||
(letfn [(show-shape [shape-id level objects]
|
||||
(let [shape (get objects shape-id)]
|
||||
(println (str/pad (str (str/repeat " " level)
|
||||
(:name shape)
|
||||
(when (seq (:touched shape)) "*")
|
||||
(when show-ids (str/format " <%s>" (:id shape))))
|
||||
{:length 20
|
||||
:type :right})
|
||||
(show-component shape objects))
|
||||
(when show-touched
|
||||
(when (seq (:touched shape))
|
||||
(println (str (str/repeat " " level)
|
||||
" "
|
||||
(str (:touched shape)))))
|
||||
(when (:remote-synced? shape)
|
||||
(println (str (str/repeat " " level)
|
||||
" (remote-synced)"))))
|
||||
(when (:shapes shape)
|
||||
(dorun (for [shape-id (:shapes shape)]
|
||||
(show-shape shape-id (inc level) objects))))))
|
||||
|
||||
(show-component [shape objects]
|
||||
(if (nil? (:shape-ref shape))
|
||||
""
|
||||
(let [root-shape (cph/get-component-shape objects shape)
|
||||
component-id (when root-shape (:component-id root-shape))
|
||||
component-file-id (when root-shape (:component-file root-shape))
|
||||
component-file (when component-file-id (get libraries component-file-id nil))
|
||||
component (when component-id
|
||||
(if component-file
|
||||
(get-in component-file [:data :components component-id])
|
||||
(get components component-id)))
|
||||
component-shape (when (and component (:shape-ref shape))
|
||||
(get-in component [:objects (:shape-ref shape)]))]
|
||||
(str/format " %s--> %s%s%s"
|
||||
(cond (:component-root? shape) "#"
|
||||
(:component-id shape) "@"
|
||||
:else "-")
|
||||
(when component-file (str/format "<%s> " (:name component-file)))
|
||||
(or (:name component-shape) "?")
|
||||
(if (or (:component-root? shape)
|
||||
(nil? (:component-id shape))
|
||||
true)
|
||||
""
|
||||
(let [component-id (:component-id shape)
|
||||
component-file-id (:component-file shape)
|
||||
component-file (when component-file-id (get libraries component-file-id nil))
|
||||
component (if component-file
|
||||
(get-in component-file [:data :components component-id])
|
||||
(get components component-id))]
|
||||
(str/format " (%s%s)"
|
||||
(when component-file (str/format "<%s> " (:name component-file)))
|
||||
(:name component))))))))]
|
||||
|
||||
(println "[Page]")
|
||||
(show-shape (:id root) 0 objects)
|
||||
|
||||
(dorun (for [component (vals components)]
|
||||
(do
|
||||
(println)
|
||||
(println (str/format "[%s]" (:name component))
|
||||
(when show-ids
|
||||
(str/format " (main: %s/%s)"
|
||||
(:main-instance-page component)
|
||||
(:main-instance-id component))))
|
||||
(show-shape (:id component) 0 (:objects component)))))))))
|
||||
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
[app.common.data :as d]
|
||||
[app.common.spec :as us]
|
||||
[app.common.types.shape :as cts]
|
||||
[app.common.uuid :as uuid]
|
||||
[clojure.spec.alpha :as s]))
|
||||
|
||||
;; --- Grid options
|
||||
|
@ -95,11 +96,22 @@
|
|||
(s/def ::page
|
||||
(s/keys :req-un [::id ::name ::objects ::options]))
|
||||
|
||||
(s/def ::type #{:page :component})
|
||||
(s/def ::path (s/nilable string?))
|
||||
(s/def ::container
|
||||
(s/keys :req-un [::id ::name ::objects]
|
||||
:opt-un [::type ::path]))
|
||||
;; --- Initialization
|
||||
|
||||
(def root uuid/zero)
|
||||
|
||||
(def empty-page-data
|
||||
{:options {}
|
||||
:objects {root
|
||||
{:id root
|
||||
:type :frame
|
||||
:name "Root Frame"}}})
|
||||
|
||||
(defn make-empty-page
|
||||
[id name]
|
||||
(assoc empty-page-data
|
||||
:id id
|
||||
:name name))
|
||||
|
||||
;; --- Helpers for flow
|
||||
|
||||
|
|
34
common/src/app/common/types/pages_list.cljc
Normal file
34
common/src/app/common/types/pages_list.cljc
Normal file
|
@ -0,0 +1,34 @@
|
|||
;; This Source Code Form is subject to the terms of the Mozilla Public
|
||||
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
;;
|
||||
;; Copyright (c) UXBOX Labs SL
|
||||
|
||||
(ns app.common.types.pages-list
|
||||
(:require
|
||||
[app.common.data :as d]))
|
||||
|
||||
(defn get-page
|
||||
[file-data id]
|
||||
(get-in file-data [:pages-index id]))
|
||||
|
||||
(defn add-page
|
||||
[file-data page]
|
||||
(let [; It's legitimate to add a page that is already there,
|
||||
; for example in an idempotent changes operation.
|
||||
conj-if-not-exists (fn [pages id]
|
||||
(cond-> pages
|
||||
(not (d/seek #(= % id) pages))
|
||||
(conj id)))]
|
||||
(-> file-data
|
||||
(update :pages conj-if-not-exists (:id page))
|
||||
(update :pages-index assoc (:id page) page))))
|
||||
|
||||
(defn pages-seq
|
||||
[file-data]
|
||||
(vals (:pages-index file-data)))
|
||||
|
||||
(defn update-page
|
||||
[file-data page-id f]
|
||||
(update-in file-data [:pages-index page-id] f))
|
||||
|
|
@ -6,8 +6,13 @@
|
|||
|
||||
(ns app.common.types.shape
|
||||
(:require
|
||||
[app.common.colors :as clr]
|
||||
[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.pages.common :refer [default-color]]
|
||||
[app.common.spec :as us]
|
||||
[app.common.types.color :as ctc]
|
||||
[app.common.types.shape.blur :as ctsb]
|
||||
|
@ -16,6 +21,7 @@
|
|||
[app.common.types.shape.layout :as ctsl]
|
||||
[app.common.types.shape.radius :as ctsr]
|
||||
[app.common.types.shape.shadow :as ctss]
|
||||
[app.common.uuid :as uuid]
|
||||
[clojure.set :as set]
|
||||
[clojure.spec.alpha :as s]))
|
||||
|
||||
|
@ -316,3 +322,155 @@
|
|||
(s/and (s/multi-spec shape-spec :type)
|
||||
#(contains? % :type)
|
||||
#(contains? % :name)))
|
||||
|
||||
|
||||
;; --- Initialization
|
||||
|
||||
(def default-shape-attrs
|
||||
{})
|
||||
|
||||
(def default-frame-attrs
|
||||
{:frame-id uuid/zero
|
||||
:fills [{:fill-color clr/white
|
||||
:fill-opacity 1}]
|
||||
:strokes []
|
||||
:shapes []
|
||||
:hide-fill-on-export false})
|
||||
|
||||
(def ^:private minimal-shapes
|
||||
[{:type :rect
|
||||
:name "Rect-1"
|
||||
:fills [{:fill-color default-color
|
||||
:fill-opacity 1}]
|
||||
:strokes []
|
||||
:rx 0
|
||||
:ry 0}
|
||||
|
||||
{:type :image
|
||||
:rx 0
|
||||
:ry 0
|
||||
:fills []
|
||||
:strokes []}
|
||||
|
||||
{:type :circle
|
||||
:name "Circle-1"
|
||||
:fills [{:fill-color default-color
|
||||
:fill-opacity 1}]
|
||||
:strokes []}
|
||||
|
||||
{:type :path
|
||||
:name "Path-1"
|
||||
:fills []
|
||||
:strokes [{:stroke-style :solid
|
||||
:stroke-alignment :center
|
||||
:stroke-width 2
|
||||
:stroke-color clr/black
|
||||
:stroke-opacity 1}]}
|
||||
|
||||
{:type :frame
|
||||
:name "Board-1"
|
||||
:fills [{:fill-color clr/white
|
||||
:fill-opacity 1}]
|
||||
:strokes []
|
||||
:stroke-style :none
|
||||
:stroke-alignment :center
|
||||
:stroke-width 0
|
||||
:stroke-color clr/black
|
||||
:stroke-opacity 0
|
||||
:rx 0
|
||||
:ry 0}
|
||||
|
||||
{:type :text
|
||||
:name "Text-1"
|
||||
:content nil}
|
||||
|
||||
{:type :svg-raw}])
|
||||
|
||||
(def empty-selrect
|
||||
{:x 0 :y 0
|
||||
:x1 0 :y1 0
|
||||
:x2 0.01 :y2 0.01
|
||||
:width 0.01 :height 0.01})
|
||||
|
||||
(defn make-minimal-shape
|
||||
[type]
|
||||
(let [type (cond (= type :curve) :path
|
||||
:else type)
|
||||
shape (d/seek #(= type (:type %)) minimal-shapes)]
|
||||
(when-not shape
|
||||
(ex/raise :type :assertion
|
||||
:code :shape-type-not-implemented
|
||||
:context {:type type}))
|
||||
|
||||
(cond-> shape
|
||||
:always
|
||||
(assoc :id (uuid/next))
|
||||
|
||||
(not= :path (:type shape))
|
||||
(assoc :x 0
|
||||
:y 0
|
||||
:width 0.01
|
||||
:height 0.01
|
||||
:selrect {:x 0
|
||||
:y 0
|
||||
:x1 0
|
||||
:y1 0
|
||||
:x2 0.01
|
||||
:y2 0.01
|
||||
:width 0.01
|
||||
:height 0.01}))))
|
||||
|
||||
(defn make-minimal-group
|
||||
[frame-id rect group-name]
|
||||
{:id (uuid/next)
|
||||
:type :group
|
||||
:name group-name
|
||||
:shapes []
|
||||
:frame-id frame-id
|
||||
:x (:x rect)
|
||||
:y (:y rect)
|
||||
:width (:width rect)
|
||||
:height (:height rect)})
|
||||
|
||||
(defn setup-rect-selrect
|
||||
"Initializes the selrect and points for a shape."
|
||||
[shape]
|
||||
(let [selrect (gsh/rect->selrect shape)
|
||||
points (gsh/rect->points shape)]
|
||||
(-> shape
|
||||
(assoc :selrect selrect
|
||||
:points points))))
|
||||
|
||||
(defn- setup-rect
|
||||
"A specialized function for setup rect-like shapes."
|
||||
[shape {:keys [x y width height]}]
|
||||
(-> shape
|
||||
(assoc :x x :y y :width width :height height)
|
||||
(setup-rect-selrect)))
|
||||
|
||||
(defn- setup-image
|
||||
[{:keys [metadata] :as shape} props]
|
||||
(-> (setup-rect shape props)
|
||||
(assoc
|
||||
:proportion (/ (:width metadata)
|
||||
(:height metadata))
|
||||
:proportion-lock true)))
|
||||
|
||||
(defn setup-shape
|
||||
"A function that initializes the geometric data of
|
||||
the shape. The props must have :x :y :width :height."
|
||||
([props]
|
||||
(setup-shape {:type :rect} props))
|
||||
|
||||
([shape props]
|
||||
(case (:type shape)
|
||||
:image (setup-image shape props)
|
||||
(setup-rect shape props))))
|
||||
|
||||
(defn make-shape
|
||||
"Make a non group shape, ready to use."
|
||||
[type geom-props attrs]
|
||||
(-> (make-minimal-shape type)
|
||||
(setup-shape geom-props)
|
||||
(merge attrs)))
|
||||
|
||||
|
|
350
common/src/app/common/types/shape_tree.cljc
Normal file
350
common/src/app/common/types/shape_tree.cljc
Normal file
|
@ -0,0 +1,350 @@
|
|||
;; This Source Code Form is subject to the terms of the Mozilla Public
|
||||
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
;;
|
||||
;; Copyright (c) UXBOX Labs SL
|
||||
|
||||
(ns app.common.types.shape-tree
|
||||
(:require
|
||||
[app.common.data :as d]
|
||||
[app.common.data.macros :as dm]
|
||||
[app.common.geom.point :as gpt]
|
||||
[app.common.geom.shapes :as gsh]
|
||||
[app.common.math :as mth]
|
||||
[app.common.pages.helpers :as cph]
|
||||
[app.common.spec :as us]
|
||||
[app.common.types.shape :as cts]
|
||||
[app.common.uuid :as uuid]
|
||||
[clojure.spec.alpha :as s]))
|
||||
|
||||
(s/def ::objects (s/map-of uuid? ::cts/shape))
|
||||
|
||||
(defn add-shape
|
||||
"Insert a shape in the tree, at the given index below the given parent or frame.
|
||||
Update the parent as needed."
|
||||
[id shape container frame-id parent-id index ignore-touched]
|
||||
(let [update-parent-shapes
|
||||
(fn [shapes]
|
||||
;; Ensure that shapes is always a vector.
|
||||
(let [shapes (into [] shapes)]
|
||||
(cond
|
||||
(some #{id} shapes)
|
||||
shapes
|
||||
|
||||
(nil? index)
|
||||
(conj shapes id)
|
||||
|
||||
:else
|
||||
(cph/insert-at-index shapes index [id]))))
|
||||
|
||||
update-parent
|
||||
(fn [parent]
|
||||
(-> parent
|
||||
(update :shapes update-parent-shapes)
|
||||
(update :shapes d/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?)))))
|
||||
|
||||
;; TODO: this looks wrong, why we allow nil values?
|
||||
update-objects
|
||||
(fn [objects parent-id]
|
||||
(if (and (or (nil? parent-id) (contains? objects parent-id))
|
||||
(or (nil? frame-id) (contains? objects frame-id)))
|
||||
(-> objects
|
||||
(assoc id (-> shape
|
||||
(assoc :frame-id frame-id)
|
||||
(assoc :parent-id parent-id)
|
||||
(assoc :id id)))
|
||||
(update parent-id update-parent))
|
||||
objects))
|
||||
|
||||
parent-id (or parent-id frame-id)]
|
||||
|
||||
(update container :objects update-objects parent-id)))
|
||||
|
||||
(defn set-shape
|
||||
"Replace a shape in the tree with a new one"
|
||||
[container shape]
|
||||
(assoc-in container [:objects (:id shape)] shape))
|
||||
|
||||
(defn get-frames
|
||||
"Retrieves all frame objects as vector"
|
||||
[objects]
|
||||
(or (-> objects meta ::index-frames)
|
||||
(let [lookup (d/getf objects)
|
||||
xform (comp (remove #(= uuid/zero %))
|
||||
(keep lookup)
|
||||
(filter cph/frame-shape?))]
|
||||
(->> (keys objects)
|
||||
(into [] xform)))))
|
||||
|
||||
(defn get-frames-ids
|
||||
"Retrieves all frame ids as vector"
|
||||
[objects]
|
||||
(->> (get-frames objects)
|
||||
(mapv :id)))
|
||||
|
||||
(defn get-nested-frames
|
||||
[objects frame-id]
|
||||
(into #{}
|
||||
(comp (filter cph/frame-shape?)
|
||||
(map :id))
|
||||
(cph/get-children objects frame-id)))
|
||||
|
||||
(defn get-root-frames-ids
|
||||
"Retrieves all frame objects as vector. It is not implemented in
|
||||
function of `get-immediate-children` for performance reasons. This
|
||||
function is executed in the render hot path."
|
||||
[objects]
|
||||
(let [add-frame
|
||||
(fn [result shape]
|
||||
(cond-> result
|
||||
(cph/frame-shape? shape)
|
||||
(conj (:id shape))))]
|
||||
(cph/reduce-objects objects (complement cph/frame-shape?) add-frame [])))
|
||||
|
||||
(defn get-root-objects
|
||||
"Get all the objects under the root object"
|
||||
[objects]
|
||||
(let [add-shape
|
||||
(fn [result shape]
|
||||
(conj result shape))]
|
||||
(cph/reduce-objects objects (complement cph/frame-shape?) add-shape [])))
|
||||
|
||||
(defn get-root-shapes
|
||||
"Get all shapes that are not frames"
|
||||
[objects]
|
||||
(let [add-shape
|
||||
(fn [result shape]
|
||||
(cond-> result
|
||||
(not (cph/frame-shape? shape))
|
||||
(conj shape)))]
|
||||
(cph/reduce-objects objects (complement cph/frame-shape?) add-shape [])))
|
||||
|
||||
(defn get-root-shapes-ids
|
||||
[objects]
|
||||
(->> (get-root-shapes objects)
|
||||
(mapv :id)))
|
||||
|
||||
(defn get-base
|
||||
[objects id-a id-b]
|
||||
|
||||
(let [parents-a (reverse (cph/get-parents-seq objects id-a))
|
||||
parents-b (reverse (cph/get-parents-seq objects id-b))
|
||||
|
||||
[base base-child-a base-child-b]
|
||||
(loop [parents-a (rest parents-a)
|
||||
parents-b (rest parents-b)
|
||||
base uuid/zero]
|
||||
(cond
|
||||
(not= (first parents-a) (first parents-b))
|
||||
[base (first parents-a) (first parents-b)]
|
||||
|
||||
(or (empty? parents-a) (empty? parents-b))
|
||||
[uuid/zero (first parents-a) (first parents-b)]
|
||||
|
||||
:else
|
||||
(recur (rest parents-a) (rest parents-b) (first parents-a))))
|
||||
|
||||
index-base-a (when base-child-a (cph/get-position-on-parent objects base-child-a))
|
||||
index-base-b (when base-child-b (cph/get-position-on-parent objects base-child-b))]
|
||||
|
||||
[base index-base-a index-base-b]))
|
||||
|
||||
(defn is-shape-over-shape?
|
||||
[objects base-shape-id over-shape-id {:keys [top-frames?]}]
|
||||
|
||||
(let [[base index-a index-b] (get-base objects base-shape-id over-shape-id)]
|
||||
(cond
|
||||
(= base base-shape-id)
|
||||
(and (not top-frames?)
|
||||
(cph/frame-shape? objects base-shape-id)
|
||||
(cph/root-frame? objects base-shape-id))
|
||||
|
||||
(= base over-shape-id)
|
||||
(or top-frames?
|
||||
(not (cph/frame-shape? objects over-shape-id))
|
||||
(not (cph/root-frame? objects over-shape-id)))
|
||||
|
||||
:else
|
||||
(< index-a index-b))))
|
||||
|
||||
(defn sort-z-index
|
||||
([objects ids]
|
||||
(sort-z-index objects ids nil))
|
||||
|
||||
([objects ids {:keys [bottom-frames?] :as options}]
|
||||
(letfn [(comp [id-a id-b]
|
||||
(let [type-a (dm/get-in objects [id-a :type])
|
||||
type-b (dm/get-in objects [id-b :type])]
|
||||
(cond
|
||||
(and bottom-frames? (= :frame type-a) (not= :frame type-b))
|
||||
1
|
||||
|
||||
(and bottom-frames? (not= :frame type-a) (= :frame type-b))
|
||||
-1
|
||||
|
||||
(= id-a id-b)
|
||||
0
|
||||
|
||||
(is-shape-over-shape? objects id-a id-b options)
|
||||
1
|
||||
|
||||
:else
|
||||
-1)))]
|
||||
(sort comp ids))))
|
||||
|
||||
(defn frame-id-by-position
|
||||
[objects position]
|
||||
(assert (gpt/point? position))
|
||||
(let [top-frame
|
||||
(->> (get-frames-ids objects)
|
||||
(sort-z-index objects)
|
||||
(d/seek #(and position (gsh/has-point? (get objects %) position))))]
|
||||
(or top-frame uuid/zero)))
|
||||
|
||||
(defn frame-by-position
|
||||
[objects position]
|
||||
(let [frame-id (frame-id-by-position objects position)]
|
||||
(get objects frame-id)))
|
||||
|
||||
(defn get-viewer-frames
|
||||
([objects]
|
||||
(get-viewer-frames objects nil))
|
||||
|
||||
([objects {:keys [all-frames?]}]
|
||||
(into []
|
||||
(comp (map (d/getf objects))
|
||||
(if all-frames?
|
||||
identity
|
||||
(remove :hide-in-viewer)))
|
||||
(sort-z-index objects (get-frames-ids objects) {:top-frames? true}))))
|
||||
|
||||
(defn start-page-index
|
||||
[objects]
|
||||
(with-meta objects {::index-frames (get-frames (with-meta objects nil))}))
|
||||
|
||||
(defn update-page-index
|
||||
[objects]
|
||||
(with-meta objects {::index-frames (get-frames (with-meta objects nil))}))
|
||||
|
||||
(defn start-object-indices
|
||||
[file]
|
||||
(letfn [(process-index [page-index page-id]
|
||||
(update-in page-index [page-id :objects] start-page-index))]
|
||||
(update file :pages-index #(reduce process-index % (keys %)))))
|
||||
|
||||
(defn update-object-indices
|
||||
[file page-id]
|
||||
(update-in file [:pages-index page-id :objects] update-page-index))
|
||||
|
||||
(defn rotated-frame?
|
||||
[frame]
|
||||
(not (mth/almost-zero? (:rotation frame 0))))
|
||||
|
||||
(defn retrieve-used-names
|
||||
[objects]
|
||||
(into #{} (comp (map :name) (remove nil?)) (vals objects)))
|
||||
|
||||
(defn- extract-numeric-suffix
|
||||
[basename]
|
||||
(if-let [[_ p1 p2] (re-find #"(.*)-([0-9]+)$" basename)]
|
||||
[p1 (+ 1 (d/parse-integer p2))]
|
||||
[basename 1]))
|
||||
|
||||
(defn generate-unique-name
|
||||
"A unique name generator"
|
||||
[used basename]
|
||||
(s/assert ::us/set-of-string used)
|
||||
(s/assert ::us/string basename)
|
||||
(if-not (contains? used basename)
|
||||
basename
|
||||
(let [[prefix initial] (extract-numeric-suffix basename)]
|
||||
(loop [counter initial]
|
||||
(let [candidate (str prefix "-" counter)]
|
||||
(if (contains? used candidate)
|
||||
(recur (inc counter))
|
||||
candidate))))))
|
||||
|
||||
(defn clone-object
|
||||
"Gets a copy of the object and all its children, with new ids
|
||||
and with the parent-children links correctly set. Admits functions
|
||||
to make more transformations to the cloned objects and the
|
||||
original ones.
|
||||
|
||||
Returns the cloned object, the list of all new objects (including
|
||||
the cloned one), and possibly a list of original objects modified.
|
||||
|
||||
The list of objects are returned in tree traversal order, respecting
|
||||
the order of the children of each parent."
|
||||
|
||||
([object parent-id objects update-new-object]
|
||||
(clone-object object parent-id objects update-new-object (fn [object _] object)))
|
||||
|
||||
([object parent-id objects update-new-object update-original-object]
|
||||
(let [new-id (uuid/next)]
|
||||
(loop [child-ids (seq (:shapes object))
|
||||
new-direct-children []
|
||||
new-children []
|
||||
updated-children []]
|
||||
|
||||
(if (empty? child-ids)
|
||||
(let [new-object (cond-> object
|
||||
true
|
||||
(assoc :id new-id
|
||||
:parent-id parent-id)
|
||||
|
||||
(some? (:shapes object))
|
||||
(assoc :shapes (mapv :id new-direct-children)))
|
||||
|
||||
new-object (update-new-object new-object object)
|
||||
new-objects (into [new-object] new-children)
|
||||
|
||||
updated-object (update-original-object object new-object)
|
||||
updated-objects (if (identical? object updated-object)
|
||||
updated-children
|
||||
(into [updated-object] updated-children))]
|
||||
|
||||
[new-object new-objects updated-objects])
|
||||
|
||||
(let [child-id (first child-ids)
|
||||
child (get objects child-id)
|
||||
_ (us/assert some? child)
|
||||
|
||||
[new-child new-child-objects updated-child-objects]
|
||||
(clone-object child new-id objects update-new-object update-original-object)]
|
||||
|
||||
(recur
|
||||
(next child-ids)
|
||||
(into new-direct-children [new-child])
|
||||
(into new-children new-child-objects)
|
||||
(into updated-children updated-child-objects))))))))
|
||||
|
||||
(defn generate-shape-grid
|
||||
"Generate a sequence of positions that lays out the list of
|
||||
shapes in a grid of equal-sized rows and columns."
|
||||
[shapes start-pos gap]
|
||||
(let [shapes-bounds (map gsh/bounding-box shapes)
|
||||
|
||||
grid-size (mth/ceil (mth/sqrt (count shapes)))
|
||||
row-size (+ (apply max (map :height shapes-bounds))
|
||||
gap)
|
||||
column-size (+ (apply max (map :width shapes-bounds))
|
||||
gap)
|
||||
|
||||
next-pos (fn [position]
|
||||
(let [counter (inc (:counter (meta position)))
|
||||
row (quot counter grid-size)
|
||||
column (mod counter grid-size)
|
||||
new-pos (gpt/add start-pos
|
||||
(gpt/point (* column column-size)
|
||||
(* row row-size)))]
|
||||
(with-meta new-pos
|
||||
{:counter counter})))]
|
||||
(iterate next-pos
|
||||
(with-meta start-pos
|
||||
{:counter 0}))))
|
||||
|
24
common/src/app/common/types/typographies_list.cljc
Normal file
24
common/src/app/common/types/typographies_list.cljc
Normal file
|
@ -0,0 +1,24 @@
|
|||
;; This Source Code Form is subject to the terms of the Mozilla Public
|
||||
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
;;
|
||||
;; Copyright (c) UXBOX Labs SL
|
||||
|
||||
(ns app.common.types.typographies-list)
|
||||
|
||||
(defn typographies-seq
|
||||
[file-data]
|
||||
(vals (:typographies file-data)))
|
||||
|
||||
(defn add-typography
|
||||
[file-data typography]
|
||||
(update file-data :typographies assoc (:id typography) typography))
|
||||
|
||||
(defn get-typography
|
||||
[file-data typography-id]
|
||||
(get-in file-data [:typographies typography-id]))
|
||||
|
||||
(defn update-typography
|
||||
[file-data typography-id f]
|
||||
(update-in file-data [:typographies typography-id] f))
|
||||
|
|
@ -6,7 +6,8 @@
|
|||
|
||||
(ns app.common.types.typography
|
||||
(:require
|
||||
[clojure.spec.alpha :as s]))
|
||||
[app.common.text :as txt]
|
||||
[clojure.spec.alpha :as s]))
|
||||
|
||||
(s/def ::id uuid?)
|
||||
(s/def ::name string?)
|
||||
|
@ -35,4 +36,37 @@
|
|||
::text-transform]
|
||||
:opt-un [::path]))
|
||||
|
||||
(defn uses-library-typographies?
|
||||
"Check if the shape uses any typography in the given library."
|
||||
[shape library-id]
|
||||
(and (= (:type shape) :text)
|
||||
(->> shape
|
||||
:content
|
||||
;; Check if any node in the content has a reference for the library
|
||||
(txt/node-seq
|
||||
#(and (some? (:typography-ref-id %))
|
||||
(= (:typography-ref-file %) library-id))))))
|
||||
|
||||
(defn uses-library-typography?
|
||||
"Check if the shape uses the given library typography."
|
||||
[shape library-id typography-id]
|
||||
(and (= (:type shape) :text)
|
||||
(->> shape
|
||||
:content
|
||||
;; Check if any node in the content has a reference for the library
|
||||
(txt/node-seq
|
||||
#(and (= (:typography-ref-id %) typography-id)
|
||||
(= (:typography-ref-file %) library-id))))))
|
||||
|
||||
(defn remap-typographies
|
||||
"Change the shape so that any use of the given typography now points to
|
||||
the given library."
|
||||
[shape library-id typography]
|
||||
(let [remap-typography #(assoc % :typography-ref-file library-id)]
|
||||
|
||||
(update shape :content
|
||||
(fn [content]
|
||||
(txt/transform-nodes #(= (:typography-ref-id %) (:id typography))
|
||||
remap-typography
|
||||
content)))))
|
||||
|
||||
|
|
|
@ -10,7 +10,7 @@
|
|||
[app.common.geom.point :as gpt]
|
||||
[app.common.geom.shapes :as gsh]
|
||||
[app.common.math :as mth :refer [close?]]
|
||||
[app.common.pages :refer [make-minimal-shape]]
|
||||
[app.common.types.shape :as cts]
|
||||
[clojure.test :as t]))
|
||||
|
||||
(def default-path
|
||||
|
@ -41,7 +41,7 @@
|
|||
(defn create-test-shape
|
||||
([type] (create-test-shape type {}))
|
||||
([type params]
|
||||
(-> (make-minimal-shape type)
|
||||
(-> (cts/make-minimal-shape type)
|
||||
(merge params)
|
||||
(cond->
|
||||
(= type :path) (add-path-data)
|
||||
|
|
|
@ -9,12 +9,13 @@
|
|||
[clojure.test :as t]
|
||||
[clojure.pprint :refer [pprint]]
|
||||
[app.common.pages :as cp]
|
||||
[app.common.types.file :as ctf]
|
||||
[app.common.uuid :as uuid]))
|
||||
|
||||
(t/deftest process-change-set-option
|
||||
(let [file-id (uuid/custom 2 2)
|
||||
page-id (uuid/custom 1 1)
|
||||
data (cp/make-file-data file-id page-id)]
|
||||
data (ctf/make-file-data file-id page-id true)]
|
||||
(t/testing "Sets option single"
|
||||
(let [chg {:type :set-option
|
||||
:page-id page-id
|
||||
|
@ -80,7 +81,7 @@
|
|||
(t/deftest process-change-add-obj
|
||||
(let [file-id (uuid/custom 2 2)
|
||||
page-id (uuid/custom 1 1)
|
||||
data (cp/make-file-data file-id page-id)
|
||||
data (ctf/make-file-data file-id page-id true)
|
||||
id-a (uuid/custom 2 1)
|
||||
id-b (uuid/custom 2 2)
|
||||
id-c (uuid/custom 2 3)]
|
||||
|
@ -134,7 +135,7 @@
|
|||
(t/deftest process-change-mod-obj
|
||||
(let [file-id (uuid/custom 2 2)
|
||||
page-id (uuid/custom 1 1)
|
||||
data (cp/make-file-data file-id page-id)]
|
||||
data (ctf/make-file-data file-id page-id true)]
|
||||
(t/testing "simple mod-obj"
|
||||
(let [chg {:type :mod-obj
|
||||
:page-id page-id
|
||||
|
@ -161,7 +162,7 @@
|
|||
(let [file-id (uuid/custom 2 2)
|
||||
page-id (uuid/custom 1 1)
|
||||
id (uuid/custom 2 1)
|
||||
data (cp/make-file-data file-id page-id)
|
||||
data (ctf/make-file-data file-id page-id true)
|
||||
data (-> data
|
||||
(assoc-in [:pages-index page-id :objects uuid/zero :shapes] [id])
|
||||
(assoc-in [:pages-index page-id :objects id]
|
||||
|
@ -205,7 +206,7 @@
|
|||
|
||||
file-id (uuid/custom 2 2)
|
||||
page-id (uuid/custom 1 1)
|
||||
data (cp/make-file-data file-id page-id)
|
||||
data (ctf/make-file-data file-id page-id true)
|
||||
|
||||
data (update-in data [:pages-index page-id :objects]
|
||||
#(-> %
|
||||
|
@ -449,7 +450,7 @@
|
|||
:obj {:type :rect
|
||||
:name "Shape 3"}}
|
||||
]
|
||||
data (cp/make-file-data file-id page-id)
|
||||
data (ctf/make-file-data file-id page-id true)
|
||||
data (cp/process-changes data changes)]
|
||||
|
||||
(t/testing "preserve order on multiple shape mov 1"
|
||||
|
@ -556,7 +557,7 @@
|
|||
:parent-id group-1-id
|
||||
:shapes [shape-1-id shape-2-id]}]
|
||||
|
||||
data (cp/make-file-data file-id page-id)
|
||||
data (ctf/make-file-data file-id page-id true)
|
||||
data (cp/process-changes data changes)]
|
||||
|
||||
(t/testing "case 1"
|
||||
|
|
149
common/test/app/common/test_helpers/components.cljc
Normal file
149
common/test/app/common/test_helpers/components.cljc
Normal file
|
@ -0,0 +1,149 @@
|
|||
(ns app.common.test-helpers.components
|
||||
(:require
|
||||
[clojure.test :as t]
|
||||
[app.common.pages.helpers :as cph]
|
||||
[app.common.types.component :as ctk]
|
||||
[app.common.types.container :as ctn]))
|
||||
|
||||
;; ---- Helpers to manage libraries and synchronization
|
||||
|
||||
(defn check-instance-root
|
||||
[shape]
|
||||
(t/is (some? (:shape-ref shape)))
|
||||
(t/is (some? (:component-id shape)))
|
||||
(t/is (= (:component-root? shape) true)))
|
||||
|
||||
(defn check-instance-subroot
|
||||
[shape]
|
||||
(t/is (some? (:shape-ref shape)))
|
||||
(t/is (some? (:component-id shape)))
|
||||
(t/is (nil? (:component-root? shape))))
|
||||
|
||||
(defn check-instance-child
|
||||
[shape]
|
||||
(t/is (some? (:shape-ref shape)))
|
||||
(t/is (nil? (:component-id shape)))
|
||||
(t/is (nil? (:component-file shape)))
|
||||
(t/is (nil? (:component-root? shape))))
|
||||
|
||||
(defn check-instance-inner
|
||||
[shape]
|
||||
(if (some? (:component-id shape))
|
||||
(check-instance-subroot shape)
|
||||
(check-instance-child shape)))
|
||||
|
||||
(defn check-noninstance
|
||||
[shape]
|
||||
(t/is (nil? (:shape-ref shape)))
|
||||
(t/is (nil? (:component-id shape)))
|
||||
(t/is (nil? (:component-file shape)))
|
||||
(t/is (nil? (:component-root? shape)))
|
||||
(t/is (nil? (:remote-synced? shape)))
|
||||
(t/is (nil? (:touched shape))))
|
||||
|
||||
(defn check-from-file
|
||||
[shape file]
|
||||
(t/is (= (:component-file shape)
|
||||
(:id file))))
|
||||
|
||||
(defn resolve-instance
|
||||
"Get the shape with the given id and all its children, and
|
||||
verify that they are a well constructed instance tree."
|
||||
[page root-inst-id]
|
||||
(let [root-inst (ctn/get-shape page root-inst-id)
|
||||
shapes-inst (cph/get-children-with-self (:objects page)
|
||||
root-inst-id)]
|
||||
(check-instance-root (first shapes-inst))
|
||||
(run! check-instance-inner (rest shapes-inst))
|
||||
|
||||
shapes-inst))
|
||||
|
||||
(defn resolve-noninstance
|
||||
"Get the shape with the given id and all its children, and
|
||||
verify that they are not a component instance."
|
||||
[page root-inst-id]
|
||||
(let [root-inst (ctn/get-shape page root-inst-id)
|
||||
shapes-inst (cph/get-children-with-self (:objects page)
|
||||
root-inst-id)]
|
||||
(run! check-noninstance shapes-inst)
|
||||
|
||||
shapes-inst))
|
||||
|
||||
(defn resolve-instance-and-main
|
||||
"Get the shape with the given id and all its children, and also
|
||||
the main component and all its shapes."
|
||||
[page root-inst-id libraries]
|
||||
(let [root-inst (ctn/get-shape page root-inst-id)
|
||||
|
||||
component (cph/get-component libraries (:component-id root-inst))
|
||||
|
||||
shapes-inst (cph/get-children-with-self (:objects page) root-inst-id)
|
||||
shapes-main (cph/get-children-with-self (:objects component) (:shape-ref root-inst))
|
||||
|
||||
unique-refs (into #{} (map :shape-ref) shapes-inst)
|
||||
|
||||
main-exists? (fn [shape]
|
||||
(let [component-shape
|
||||
(cph/get-component-shape (:objects page) shape)
|
||||
|
||||
component
|
||||
(cph/get-component libraries (:component-id component-shape))
|
||||
|
||||
main-shape
|
||||
(ctn/get-shape component (:shape-ref shape))]
|
||||
|
||||
(t/is (some? main-shape))))]
|
||||
|
||||
;; Validate that the instance tree is well constructed
|
||||
(check-instance-root (first shapes-inst))
|
||||
(run! check-instance-inner (rest shapes-inst))
|
||||
(t/is (= (count shapes-inst)
|
||||
(count shapes-main)
|
||||
(count unique-refs)))
|
||||
(run! main-exists? shapes-inst)
|
||||
|
||||
[shapes-inst shapes-main component]))
|
||||
|
||||
(defn resolve-instance-and-main-allow-dangling
|
||||
"Get the shape with the given id and all its children, and also
|
||||
the main component and all its shapes. Allows shapes with the
|
||||
corresponding component shape missing."
|
||||
[page root-inst-id libraries]
|
||||
(let [root-inst (ctn/get-shape page root-inst-id)
|
||||
|
||||
component (cph/get-component libraries (:component-id root-inst))
|
||||
|
||||
shapes-inst (cph/get-children-with-self (:objects page) root-inst-id)
|
||||
shapes-main (cph/get-children-with-self (:objects component) (:shape-ref root-inst))
|
||||
|
||||
unique-refs (into #{} (map :shape-ref) shapes-inst)
|
||||
|
||||
main-exists? (fn [shape]
|
||||
(let [component-shape
|
||||
(cph/get-component-shape (:objects page) shape)
|
||||
|
||||
component
|
||||
(cph/get-component libraries (:component-id component-shape))
|
||||
|
||||
main-shape
|
||||
(ctn/get-shape component (:shape-ref shape))]
|
||||
|
||||
(t/is (some? main-shape))))]
|
||||
|
||||
;; Validate that the instance tree is well constructed
|
||||
(check-instance-root (first shapes-inst))
|
||||
|
||||
[shapes-inst shapes-main component]))
|
||||
|
||||
(defn resolve-component
|
||||
"Get the component with the given id and all its shapes."
|
||||
[page component-id libraries]
|
||||
(let [component (cph/get-component libraries component-id)
|
||||
root-main (ctk/get-component-root component)
|
||||
shapes-main (cph/get-children-with-self (:objects component) (:id root-main))]
|
||||
|
||||
;; Validate that the component tree is well constructed
|
||||
(run! check-noninstance shapes-main)
|
||||
|
||||
[shapes-main component]))
|
||||
|
149
common/test/app/common/test_helpers/files.cljc
Normal file
149
common/test/app/common/test_helpers/files.cljc
Normal file
|
@ -0,0 +1,149 @@
|
|||
;; This Source Code Form is subject to the terms of the Mozilla Public
|
||||
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
;;
|
||||
;; Copyright (c) UXBOX Labs SL
|
||||
|
||||
(ns app.common.test-helpers.files
|
||||
(:require
|
||||
[app.common.geom.point :as gpt]
|
||||
[app.common.types.components-list :as ctkl]
|
||||
[app.common.types.colors-list :as ctcl]
|
||||
[app.common.types.container :as ctn]
|
||||
[app.common.types.file :as ctf]
|
||||
[app.common.types.pages-list :as ctpl]
|
||||
[app.common.types.shape :as cts]
|
||||
[app.common.types.shape-tree :as ctst]
|
||||
[app.common.types.typographies-list :as ctyl]
|
||||
[app.common.uuid :as uuid]))
|
||||
|
||||
(def ^:private idmap (atom {}))
|
||||
|
||||
(defn reset-idmap! []
|
||||
(reset! idmap {}))
|
||||
|
||||
(defn id
|
||||
[label]
|
||||
(get @idmap label))
|
||||
|
||||
(defn sample-file
|
||||
([file-id page-id] (sample-file file-id page-id nil))
|
||||
([file-id page-id props]
|
||||
(merge {:id file-id
|
||||
:name (get props :name "File1")
|
||||
:data (ctf/make-file-data file-id page-id true)}
|
||||
props)))
|
||||
|
||||
(defn sample-shape
|
||||
[file label type page-id props]
|
||||
(ctf/update-file-data
|
||||
file
|
||||
(fn [file-data]
|
||||
(let [frame-id (get props :frame-id uuid/zero)
|
||||
parent-id (get props :parent-id uuid/zero)
|
||||
shape (if (= type :group)
|
||||
(cts/make-minimal-group frame-id
|
||||
{:x 0 :y 0 :width 1 :height 1}
|
||||
(get props :name "Group1"))
|
||||
(cts/make-shape type
|
||||
{:x 0 :y 0 :width 1 :height 1}
|
||||
props))]
|
||||
|
||||
(swap! idmap assoc label (:id shape))
|
||||
(ctpl/update-page file-data
|
||||
page-id
|
||||
#(ctst/add-shape (:id shape)
|
||||
shape
|
||||
%
|
||||
frame-id
|
||||
parent-id
|
||||
0
|
||||
true))))))
|
||||
|
||||
(defn sample-component
|
||||
[file label page-id shape-id]
|
||||
(ctf/update-file-data
|
||||
file
|
||||
(fn [file-data]
|
||||
(let [page (ctpl/get-page file-data page-id)
|
||||
|
||||
[component-shape component-shapes updated-shapes]
|
||||
(ctn/make-component-shape (ctn/get-shape page shape-id true)
|
||||
(:objects page)
|
||||
(:id file)
|
||||
true)]
|
||||
|
||||
(swap! idmap assoc label (:id component-shape))
|
||||
(-> file-data
|
||||
(ctpl/update-page page-id
|
||||
#(reduce (fn [page shape] (ctst/set-shape page shape))
|
||||
%
|
||||
updated-shapes))
|
||||
(ctkl/add-component (:id component-shape)
|
||||
(:name component-shape)
|
||||
""
|
||||
shape-id
|
||||
page-id
|
||||
component-shapes))))))
|
||||
|
||||
(defn sample-instance
|
||||
[file label page-id library component-id]
|
||||
(ctf/update-file-data
|
||||
file
|
||||
(fn [file-data]
|
||||
(let [[instance-shape instance-shapes]
|
||||
(ctn/make-component-instance (ctpl/get-page file-data page-id)
|
||||
(ctkl/get-component (:data library) component-id)
|
||||
(:id library)
|
||||
(gpt/point 0 0)
|
||||
false)]
|
||||
|
||||
(swap! idmap assoc label (:id instance-shape))
|
||||
(-> file-data
|
||||
(ctpl/update-page page-id
|
||||
#(reduce (fn [page shape]
|
||||
(ctst/add-shape (:id shape)
|
||||
shape
|
||||
page
|
||||
uuid/zero
|
||||
(:parent-id shape)
|
||||
0
|
||||
true))
|
||||
%
|
||||
instance-shapes)))))))
|
||||
|
||||
(defn sample-color
|
||||
[file label props]
|
||||
(ctf/update-file-data
|
||||
file
|
||||
(fn [file-data]
|
||||
(let [id (uuid/next)
|
||||
props (merge {:id id
|
||||
:name "Color-1"
|
||||
:color "#000000"
|
||||
:opacity 1}
|
||||
props)]
|
||||
(swap! idmap assoc label id)
|
||||
(ctcl/add-color file-data props)))))
|
||||
|
||||
(defn sample-typography
|
||||
[file label props]
|
||||
(ctf/update-file-data
|
||||
file
|
||||
(fn [file-data]
|
||||
(let [id (uuid/next)
|
||||
props (merge {:id id
|
||||
:name "Typography-1"
|
||||
:font-id "sourcesanspro"
|
||||
:font-family "sourcesanspro"
|
||||
:font-size "14"
|
||||
:font-style "normal"
|
||||
:font-variant-id "regular"
|
||||
:font-weight "400"
|
||||
:line-height "1.2"
|
||||
:letter-spacing "0"
|
||||
:text-transform "none"}
|
||||
props)]
|
||||
(swap! idmap assoc label id)
|
||||
(ctyl/add-typography file-data props)))))
|
||||
|
209
common/test/app/common/types/file_test.cljc
Normal file
209
common/test/app/common/types/file_test.cljc
Normal file
|
@ -0,0 +1,209 @@
|
|||
;; This Source Code Form is subject to the terms of the Mozilla Public
|
||||
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
;;
|
||||
;; Copyright (c) UXBOX Labs SL
|
||||
|
||||
(ns app.common.types.file-test
|
||||
(:require
|
||||
;; Uncomment to debug
|
||||
;; [clojure.pprint :refer [pprint]]
|
||||
;; [cuerdas.core :as str]
|
||||
[clojure.test :as t]
|
||||
[app.common.data :as d]
|
||||
[app.common.geom.point :as gpt]
|
||||
[app.common.text :as txt]
|
||||
[app.common.types.colors-list :as ctcl]
|
||||
[app.common.types.component :as ctk]
|
||||
[app.common.types.components-list :as ctkl]
|
||||
[app.common.types.container :as ctn]
|
||||
[app.common.types.file :as ctf]
|
||||
[app.common.types.pages-list :as ctpl]
|
||||
[app.common.types.shape :as cts]
|
||||
[app.common.types.shape-tree :as ctst]
|
||||
[app.common.types.typographies-list :as ctyl]
|
||||
[app.common.uuid :as uuid]
|
||||
[app.common.test-helpers.files :as thf]
|
||||
[app.common.test-helpers.components :as thk]))
|
||||
|
||||
(t/use-fixtures :each
|
||||
{:before thf/reset-idmap!})
|
||||
|
||||
(t/deftest test-absorb-components
|
||||
(let [library-id (uuid/custom 1 1)
|
||||
library-page-id (uuid/custom 2 2)
|
||||
file-id (uuid/custom 3 3)
|
||||
file-page-id (uuid/custom 4 4)
|
||||
|
||||
library (-> (thf/sample-file library-id library-page-id {:is-shared true})
|
||||
(thf/sample-shape :group1
|
||||
:group
|
||||
library-page-id
|
||||
{:name "Group1"})
|
||||
(thf/sample-shape :shape1
|
||||
:rect
|
||||
library-page-id
|
||||
{:name "Rect1"
|
||||
:parent-id (thf/id :group1)})
|
||||
(thf/sample-component :component1
|
||||
library-page-id
|
||||
(thf/id :group1)))
|
||||
|
||||
file (-> (thf/sample-file file-id file-page-id)
|
||||
(thf/sample-instance :instance1
|
||||
file-page-id
|
||||
library
|
||||
(thf/id :component1)))
|
||||
|
||||
absorbed-file (ctf/update-file-data
|
||||
file
|
||||
#(ctf/absorb-assets % (:data library)))
|
||||
|
||||
pages (ctpl/pages-seq (ctf/file-data absorbed-file))
|
||||
components (ctkl/components-seq (ctf/file-data absorbed-file))
|
||||
shapes-1 (ctn/shapes-seq (first pages))
|
||||
shapes-2 (ctn/shapes-seq (second pages))
|
||||
|
||||
[[p-group p-shape] [c-group1 c-shape1] component1]
|
||||
(thk/resolve-instance-and-main
|
||||
(first pages)
|
||||
(:id (second shapes-1))
|
||||
{file-id absorbed-file})
|
||||
|
||||
[[lp-group lp-shape] [c-group2 c-shape2] component2]
|
||||
(thk/resolve-instance-and-main
|
||||
(second pages)
|
||||
(:id (second shapes-2))
|
||||
{file-id absorbed-file})]
|
||||
|
||||
;; Uncomment to debug
|
||||
|
||||
;; (println "\n===== library")
|
||||
;; (ctf/dump-tree (:data library)
|
||||
;; library-page-id
|
||||
;; {}
|
||||
;; true)
|
||||
|
||||
;; (println "\n===== file")
|
||||
;; (ctf/dump-tree (:data file)
|
||||
;; file-page-id
|
||||
;; {library-id library}
|
||||
;; true)
|
||||
|
||||
;; (println "\n===== absorbed file")
|
||||
;; (println (str "\n<" (:name (first pages)) ">"))
|
||||
;; (ctf/dump-tree (:data absorbed-file)
|
||||
;; (:id (first pages))
|
||||
;; {file-id absorbed-file}
|
||||
;; false)
|
||||
;; (println (str "\n<" (:name (second pages)) ">"))
|
||||
;; (ctf/dump-tree (:data absorbed-file)
|
||||
;; (:id (second pages))
|
||||
;; {file-id absorbed-file}
|
||||
;; false)
|
||||
|
||||
(t/is (= (count pages) 2))
|
||||
(t/is (= (:name (first pages)) "Page-1"))
|
||||
(t/is (= (:name (second pages)) "Library backup"))
|
||||
|
||||
(t/is (= (count components) 1))
|
||||
|
||||
(t/is (= (:name p-group) "Group1"))
|
||||
(t/is (ctk/instance-of? p-group file-id (:id component1)))
|
||||
(t/is (not (:main-instance? p-group)))
|
||||
(t/is (not (ctk/is-main-instance? (:id p-group) file-page-id component1)))
|
||||
(t/is (ctk/is-main-of? c-group1 p-group))
|
||||
|
||||
(t/is (= (:name p-shape) "Rect1"))
|
||||
(t/is (ctk/is-main-of? c-shape1 p-shape))))
|
||||
|
||||
|
||||
(t/deftest test-absorb-colors
|
||||
(let [library-id (uuid/custom 1 1)
|
||||
library-page-id (uuid/custom 2 2)
|
||||
file-id (uuid/custom 3 3)
|
||||
file-page-id (uuid/custom 4 4)
|
||||
|
||||
library (-> (thf/sample-file library-id library-page-id {:is-shared true})
|
||||
(thf/sample-color :color1 {:name "Test color"
|
||||
:color "#abcdef"}))
|
||||
|
||||
file (-> (thf/sample-file file-id file-page-id)
|
||||
(thf/sample-shape :shape1
|
||||
:rect
|
||||
file-page-id
|
||||
{:name "Rect1"
|
||||
:fills [{:fill-color "#abcdef"
|
||||
:fill-opacity 1
|
||||
:fill-color-ref-id (thf/id :color1)
|
||||
:fill-color-ref-file library-id}]}))
|
||||
|
||||
absorbed-file (ctf/update-file-data
|
||||
file
|
||||
#(ctf/absorb-assets % (:data library)))
|
||||
|
||||
colors (ctcl/colors-seq (ctf/file-data absorbed-file))
|
||||
page (ctpl/get-page (ctf/file-data absorbed-file) file-page-id)
|
||||
shape1 (ctn/get-shape page (thf/id :shape1))
|
||||
fill (first (:fills shape1))]
|
||||
|
||||
(t/is (= (count colors) 1))
|
||||
(t/is (= (:id (first colors)) (thf/id :color1)))
|
||||
(t/is (= (:name (first colors)) "Test color"))
|
||||
(t/is (= (:color (first colors)) "#abcdef"))
|
||||
|
||||
(t/is (= (:fill-color fill) "#abcdef"))
|
||||
(t/is (= (:fill-color-ref-id fill) (thf/id :color1)))
|
||||
(t/is (= (:fill-color-ref-file fill) file-id))))
|
||||
|
||||
(t/deftest test-absorb-typographies
|
||||
(let [library-id (uuid/custom 1 1)
|
||||
library-page-id (uuid/custom 2 2)
|
||||
file-id (uuid/custom 3 3)
|
||||
file-page-id (uuid/custom 4 4)
|
||||
|
||||
library (-> (thf/sample-file library-id library-page-id {:is-shared true})
|
||||
(thf/sample-typography :typography1 {:name "Test typography"}))
|
||||
|
||||
file (-> (thf/sample-file file-id file-page-id)
|
||||
(thf/sample-shape :shape1
|
||||
:text
|
||||
file-page-id
|
||||
{:name "Text1"
|
||||
:content {:type "root"
|
||||
:children [{:type "paragraph-set"
|
||||
:children [{:type "paragraph"
|
||||
:key "67uep"
|
||||
:children [{:text "Example text"
|
||||
:typography-ref-id (thf/id :typography1)
|
||||
:typography-ref-file library-id
|
||||
:line-height "1.2"
|
||||
:font-style "normal"
|
||||
:text-transform "none"
|
||||
:text-align "left"
|
||||
:font-id "sourcesanspro"
|
||||
:font-family "sourcesanspro"
|
||||
:font-size "14"
|
||||
:font-weight "400"
|
||||
:font-variant-id "regular"
|
||||
:text-decoration "none"
|
||||
:letter-spacing "0"
|
||||
:fills [{:fill-color "#000000"
|
||||
:fill-opacity 1}]}]
|
||||
}]}]}}))
|
||||
absorbed-file (ctf/update-file-data
|
||||
file
|
||||
#(ctf/absorb-assets % (:data library)))
|
||||
|
||||
typographies (ctyl/typographies-seq (ctf/file-data absorbed-file))
|
||||
page (ctpl/get-page (ctf/file-data absorbed-file) file-page-id)
|
||||
shape1 (ctn/get-shape page (thf/id :shape1))
|
||||
text-node (d/seek #(some? (:text %)) (txt/node-seq (:content shape1)))]
|
||||
|
||||
(t/is (= (count typographies) 1))
|
||||
(t/is (= (:id (first typographies)) (thf/id :typography1)))
|
||||
(t/is (= (:name (first typographies)) "Test typography"))
|
||||
|
||||
(t/is (= (:typography-ref-id text-node) (thf/id :typography1)))
|
||||
(t/is (= (:typography-ref-file text-node) file-id))))
|
||||
|
|
@ -4,20 +4,20 @@
|
|||
;;
|
||||
;; Copyright (c) UXBOX Labs SL
|
||||
|
||||
(ns app.common.spec-interactions-test
|
||||
(ns app.common.types.shape.spec-interactions-test
|
||||
(:require
|
||||
[clojure.test :as t]
|
||||
[clojure.pprint :refer [pprint]]
|
||||
[app.common.exceptions :as ex]
|
||||
[app.common.pages.init :as cpi]
|
||||
[app.common.types.shape :as cts]
|
||||
[app.common.types.shape.interactions :as ctsi]
|
||||
[app.common.uuid :as uuid]
|
||||
[app.common.geom.point :as gpt]))
|
||||
|
||||
(t/deftest set-event-type
|
||||
(let [interaction ctsi/default-interaction
|
||||
shape (cpi/make-minimal-shape :rect)
|
||||
frame (cpi/make-minimal-shape :frame)]
|
||||
shape (cts/make-minimal-shape :rect)
|
||||
frame (cts/make-minimal-shape :frame)]
|
||||
|
||||
(t/testing "Set event type unchanged"
|
||||
(let [new-interaction
|
||||
|
@ -148,7 +148,7 @@
|
|||
|
||||
|
||||
(t/deftest option-delay
|
||||
(let [frame (cpi/make-minimal-shape :frame)
|
||||
(let [frame (cts/make-minimal-shape :frame)
|
||||
i1 ctsi/default-interaction
|
||||
i2 (ctsi/set-event-type i1 :after-delay frame)]
|
||||
|
||||
|
@ -211,10 +211,10 @@
|
|||
|
||||
|
||||
(t/deftest option-overlay-opts
|
||||
(let [base-frame (-> (cpi/make-minimal-shape :frame)
|
||||
(let [base-frame (-> (cts/make-minimal-shape :frame)
|
||||
(assoc-in [:selrect :width] 100)
|
||||
(assoc-in [:selrect :height] 100))
|
||||
overlay-frame (-> (cpi/make-minimal-shape :frame)
|
||||
overlay-frame (-> (cts/make-minimal-shape :frame)
|
||||
(assoc-in [:selrect :width] 30)
|
||||
(assoc-in [:selrect :height] 20))
|
||||
objects {(:id base-frame) base-frame
|
||||
|
@ -542,12 +542,12 @@
|
|||
|
||||
|
||||
(t/deftest remap-interactions
|
||||
(let [frame1 (cpi/make-minimal-shape :frame)
|
||||
frame2 (cpi/make-minimal-shape :frame)
|
||||
frame3 (cpi/make-minimal-shape :frame)
|
||||
frame4 (cpi/make-minimal-shape :frame)
|
||||
frame5 (cpi/make-minimal-shape :frame)
|
||||
frame6 (cpi/make-minimal-shape :frame)
|
||||
(let [frame1 (cts/make-minimal-shape :frame)
|
||||
frame2 (cts/make-minimal-shape :frame)
|
||||
frame3 (cts/make-minimal-shape :frame)
|
||||
frame4 (cts/make-minimal-shape :frame)
|
||||
frame5 (cts/make-minimal-shape :frame)
|
||||
frame6 (cts/make-minimal-shape :frame)
|
||||
|
||||
objects {(:id frame3) frame3
|
||||
(:id frame4) frame4
|
3
frontend/resources/images/icons/component-copy.svg
Normal file
3
frontend/resources/images/icons/component-copy.svg
Normal file
|
@ -0,0 +1,3 @@
|
|||
<svg width="500" height="500" viewBox="2115 2827.9 500 500" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="m2615 3077.69-140.71 250.21h-218.22L2115 3077.69l141.07-249.79h218.22zm-337.86 207.73h176.07l118.93-207.3-118.93-207.74h-176.07l-118.93 208.16z"/>
|
||||
</svg>
|
After Width: | Height: | Size: 261 B |
|
@ -134,6 +134,12 @@
|
|||
font-size: $fs16;
|
||||
font-weight: 400;
|
||||
}
|
||||
&.delete-shared {
|
||||
padding: 15px 32px;
|
||||
.modal-item-element {
|
||||
font-size: $fs16;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.modal-footer {
|
||||
|
@ -179,7 +185,8 @@
|
|||
}
|
||||
}
|
||||
|
||||
.confirm-dialog {
|
||||
.confirm-dialog,
|
||||
.alert-dialog {
|
||||
background-color: $color-white;
|
||||
|
||||
p {
|
||||
|
|
|
@ -63,6 +63,11 @@
|
|||
flags (sequence (map keyword) (str/words flags))]
|
||||
(flags/parse flags/default default-flags flags)))
|
||||
|
||||
(defn- parse-features
|
||||
[global]
|
||||
(when-let [features-str (obj/get global "penpotFeatures")]
|
||||
(map keyword (str/words features-str))))
|
||||
|
||||
(defn- parse-version
|
||||
[global]
|
||||
(-> (obj/get global "penpotVersion")
|
||||
|
@ -88,6 +93,7 @@
|
|||
|
||||
(def build-date (parse-build-date global))
|
||||
(def flags (atom (parse-flags global)))
|
||||
(def features (atom (parse-features global)))
|
||||
(def version (atom (parse-version global)))
|
||||
(def target (atom (parse-target global)))
|
||||
(def browser (atom (parse-browser)))
|
||||
|
|
|
@ -16,7 +16,9 @@
|
|||
[app.main.sentry :as sentry]
|
||||
[app.main.store :as st]
|
||||
[app.main.ui :as ui]
|
||||
[app.main.ui.alert]
|
||||
[app.main.ui.confirm]
|
||||
[app.main.ui.delete-shared]
|
||||
[app.main.ui.modal :refer [modal]]
|
||||
[app.main.ui.routes :as rt]
|
||||
[app.main.worker :as worker]
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
[app.main.data.fonts :as df]
|
||||
[app.main.data.media :as di]
|
||||
[app.main.data.users :as du]
|
||||
[app.main.features :as features]
|
||||
[app.main.repo :as rp]
|
||||
[app.util.i18n :as i18n :refer [tr]]
|
||||
[app.util.router :as rt]
|
||||
|
@ -246,6 +247,32 @@
|
|||
(->> (rp/query :team-shared-files {:team-id team-id})
|
||||
(rx/map shared-files-fetched))))))
|
||||
|
||||
;; --- EVENT: Get files that use this shared-file
|
||||
|
||||
(defn clean-temp-shared
|
||||
[]
|
||||
(ptk/reify ::clean-temp-shared
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(assoc-in state [:dashboard-local :files-with-shared] nil))))
|
||||
|
||||
(defn library-using-files-fetched
|
||||
[files]
|
||||
(ptk/reify ::library-using-files-fetched
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(let [files (d/index-by :id files)]
|
||||
(assoc-in state [:dashboard-local :files-with-shared] files)))))
|
||||
|
||||
(defn fetch-library-using-files
|
||||
[file]
|
||||
(ptk/reify ::fetch-library-using-files
|
||||
ptk/WatchEvent
|
||||
(watch [_ _ _]
|
||||
(let [file-id (:id file)]
|
||||
(->> (rp/query :library-using-files {:file-id file-id})
|
||||
(rx/map library-using-files-fetched))))))
|
||||
|
||||
;; --- EVENT: recent-files
|
||||
|
||||
(defn recent-files-fetched
|
||||
|
@ -718,12 +745,13 @@
|
|||
(-deref [_] {:project-id project-id})
|
||||
|
||||
ptk/WatchEvent
|
||||
(watch [it _ _]
|
||||
(watch [it state _]
|
||||
(let [{:keys [on-success on-error]
|
||||
:or {on-success identity
|
||||
on-error rx/throw}} (meta params)
|
||||
name (name (gensym (str (tr "dashboard.new-file-prefix") " ")))
|
||||
params (assoc params :name name)]
|
||||
components-v2 (features/active-feature? state :components-v2)
|
||||
params (assoc params :name name :components-v2 components-v2)]
|
||||
|
||||
(->> (rp/mutation! :create-file params)
|
||||
(rx/tap on-success)
|
||||
|
|
|
@ -10,9 +10,11 @@
|
|||
[app.common.geom.point :as gpt]
|
||||
[app.common.pages.helpers :as cph]
|
||||
[app.common.spec :as us]
|
||||
[app.common.types.shape-tree :as ctt]
|
||||
[app.common.types.shape.interactions :as ctsi]
|
||||
[app.main.data.comments :as dcm]
|
||||
[app.main.data.fonts :as df]
|
||||
[app.main.features :as features]
|
||||
[app.main.repo :as rp]
|
||||
[app.util.globals :as ug]
|
||||
[app.util.router :as rt]
|
||||
|
@ -99,9 +101,15 @@
|
|||
(us/assert ::fetch-bundle-params params)
|
||||
(ptk/reify ::fetch-file
|
||||
ptk/WatchEvent
|
||||
(watch [_ _ _]
|
||||
(let [params' (cond-> {:file-id file-id}
|
||||
(uuid? share-id) (assoc :share-id share-id))]
|
||||
(watch [_ state _]
|
||||
(let [components-v2 (features/active-feature? state :components-v2)
|
||||
params' (cond-> {:file-id file-id}
|
||||
(uuid? share-id)
|
||||
(assoc :share-id share-id)
|
||||
|
||||
:always
|
||||
(assoc :components-v2 components-v2))]
|
||||
|
||||
(->> (rp/query :view-only-bundle params')
|
||||
(rx/mapcat
|
||||
(fn [{:keys [fonts] :as bundle}]
|
||||
|
@ -116,8 +124,8 @@
|
|||
(map (fn [page-id]
|
||||
(let [data (get-in file [:data :pages-index page-id])]
|
||||
[page-id (assoc data
|
||||
:frames (cph/get-viewer-frames (:objects data))
|
||||
:all-frames (cph/get-viewer-frames (:objects data) {:all-frames? true}))])))
|
||||
:frames (ctt/get-viewer-frames (:objects data))
|
||||
:all-frames (ctt/get-viewer-frames (:objects data) {:all-frames? true}))])))
|
||||
(into {}))]
|
||||
|
||||
(ptk/reify ::bundle-fetched
|
||||
|
|
|
@ -14,13 +14,13 @@
|
|||
[app.common.geom.proportions :as gpr]
|
||||
[app.common.geom.shapes :as gsh]
|
||||
[app.common.math :as mth]
|
||||
[app.common.pages :as cp]
|
||||
[app.common.pages.changes-builder :as pcb]
|
||||
[app.common.pages.helpers :as cph]
|
||||
[app.common.spec :as us]
|
||||
[app.common.text :as txt]
|
||||
[app.common.transit :as t]
|
||||
[app.common.types.shape :as cts]
|
||||
[app.common.types.shape-tree :as ctst]
|
||||
[app.common.uuid :as uuid]
|
||||
[app.config :as cfg]
|
||||
[app.main.data.events :as ev]
|
||||
|
@ -59,7 +59,6 @@
|
|||
[app.util.globals :as ug]
|
||||
[app.util.http :as http]
|
||||
[app.util.i18n :as i18n]
|
||||
[app.util.names :as un]
|
||||
[app.util.router :as rt]
|
||||
[app.util.timers :as tm]
|
||||
[app.util.webapi :as wapi]
|
||||
|
@ -157,7 +156,7 @@
|
|||
:workspace-project project
|
||||
:workspace-file (assoc file :initialized true)
|
||||
:workspace-data (-> (:data file)
|
||||
(cph/start-object-indices)
|
||||
(ctst/start-object-indices)
|
||||
;; DEBUG: Uncomment this to try out migrations in local without changing
|
||||
;; the version number
|
||||
#_(assoc :version 17)
|
||||
|
@ -215,7 +214,8 @@
|
|||
(watch [_ state _]
|
||||
(if (contains? (get-in state [:workspace-data :pages-index]) page-id)
|
||||
(rx/of (dwp/preload-data-uris)
|
||||
(dwth/watch-state-changes))
|
||||
(dwth/watch-state-changes)
|
||||
(dwl/watch-component-changes))
|
||||
(let [default-page-id (get-in state [:workspace-data :pages 0])]
|
||||
(rx/of (go-to-page default-page-id)))))
|
||||
|
||||
|
@ -270,8 +270,8 @@
|
|||
ptk/WatchEvent
|
||||
(watch [it state _]
|
||||
(let [pages (get-in state [:workspace-data :pages-index])
|
||||
unames (un/retrieve-used-names pages)
|
||||
name (un/generate-unique-name unames "Page-1")
|
||||
unames (ctst/retrieve-used-names pages)
|
||||
name (ctst/generate-unique-name unames "Page-1")
|
||||
|
||||
changes (-> (pcb/empty-changes it)
|
||||
(pcb/add-empty-page id name))]
|
||||
|
@ -285,9 +285,9 @@
|
|||
(watch [it state _]
|
||||
(let [id (uuid/next)
|
||||
pages (get-in state [:workspace-data :pages-index])
|
||||
unames (un/retrieve-used-names pages)
|
||||
unames (ctst/retrieve-used-names pages)
|
||||
page (get-in state [:workspace-data :pages-index page-id])
|
||||
name (un/generate-unique-name unames (:name page))
|
||||
name (ctst/generate-unique-name unames (:name page))
|
||||
|
||||
no_thumbnails_objects (->> (:objects page)
|
||||
(d/mapm (fn [_ val] (dissoc val :use-for-thumbnail?))))
|
||||
|
@ -991,7 +991,7 @@
|
|||
(let [selected (wsh/lookup-selected state)
|
||||
pages (-> state :workspace-data :pages-index vals)
|
||||
get-frames (fn [{:keys [objects id] :as page}]
|
||||
(->> (cph/get-frames objects)
|
||||
(->> (ctst/get-frames objects)
|
||||
(sequence
|
||||
(comp (filter :use-for-thumbnail?)
|
||||
(map :id)
|
||||
|
@ -1223,7 +1223,7 @@
|
|||
;; selected and its parents
|
||||
objects (cph/selected-subtree objects selected)
|
||||
|
||||
selected (->> (cph/sort-z-index objects selected)
|
||||
selected (->> (ctst/sort-z-index objects selected)
|
||||
(into (d/ordered-set)))]
|
||||
|
||||
(assoc data :selected selected)))
|
||||
|
@ -1478,7 +1478,7 @@
|
|||
[frame-id frame-id delta])
|
||||
|
||||
(empty? page-selected)
|
||||
(let [frame-id (cph/frame-id-by-position page-objects mouse-pos)
|
||||
(let [frame-id (ctst/frame-id-by-position page-objects mouse-pos)
|
||||
delta (gpt/subtract mouse-pos orig-pos)]
|
||||
[frame-id frame-id delta])
|
||||
|
||||
|
@ -1590,8 +1590,8 @@
|
|||
height 16
|
||||
page-id (:current-page-id state)
|
||||
frame-id (-> (wsh/lookup-page-objects state page-id)
|
||||
(cph/frame-id-by-position @ms/mouse-position))
|
||||
shape (cp/setup-rect-selrect
|
||||
(ctst/frame-id-by-position @ms/mouse-position))
|
||||
shape (cts/setup-rect-selrect
|
||||
{:id id
|
||||
:type :text
|
||||
:name "Text"
|
||||
|
@ -1681,12 +1681,12 @@
|
|||
(let [srect (gsh/selection-rect selected-objs)
|
||||
frame-id (get-in objects [(first selected) :frame-id])
|
||||
parent-id (get-in objects [(first selected) :parent-id])
|
||||
shape (-> (cp/make-minimal-shape :frame)
|
||||
shape (-> (cts/make-minimal-shape :frame)
|
||||
(merge {:x (:x srect) :y (:y srect) :width (:width srect) :height (:height srect)})
|
||||
(assoc :frame-id frame-id :parent-id parent-id)
|
||||
(cond-> (not= frame-id uuid/zero)
|
||||
(assoc :fills [] :hide-in-viewer true))
|
||||
(cp/setup-rect-selrect))]
|
||||
(cts/setup-rect-selrect))]
|
||||
(rx/of
|
||||
(dwu/start-undo-transaction)
|
||||
(dwsh/add-shape shape)
|
||||
|
|
|
@ -11,11 +11,11 @@
|
|||
[app.common.pages.changes-builder :as pcb]
|
||||
[app.common.pages.helpers :as cph]
|
||||
[app.common.path.shapes-to-path :as stp]
|
||||
[app.common.types.shape-tree :as ctt]
|
||||
[app.common.uuid :as uuid]
|
||||
[app.main.data.workspace.changes :as dch]
|
||||
[app.main.data.workspace.selection :as dws]
|
||||
[app.main.data.workspace.state-helpers :as wsh]
|
||||
[app.util.names :as un]
|
||||
[beicon.core :as rx]
|
||||
[cuerdas.core :as str]
|
||||
[potok.core :as ptk]))
|
||||
|
@ -90,8 +90,8 @@
|
|||
(let [page-id (:current-page-id state)
|
||||
objects (wsh/lookup-page-objects state)
|
||||
base-name (-> bool-type d/name str/capital (str "-1"))
|
||||
name (-> (un/retrieve-used-names objects)
|
||||
(un/generate-unique-name base-name))
|
||||
name (-> (ctt/retrieve-used-names objects)
|
||||
(ctt/generate-unique-name base-name))
|
||||
shapes (selected-shapes state)]
|
||||
|
||||
(when-not (empty? shapes)
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
[app.common.pages.changes-spec :as pcs]
|
||||
[app.common.pages.helpers :as cph]
|
||||
[app.common.spec :as us]
|
||||
[app.common.types.shape-tree :as ctst]
|
||||
[app.common.uuid :as uuid]
|
||||
[app.main.data.workspace.state-helpers :as wsh]
|
||||
[app.main.data.workspace.undo :as dwu]
|
||||
|
@ -165,7 +166,7 @@
|
|||
(update-in state path (fn [file]
|
||||
(-> file
|
||||
(cp/process-changes redo-changes false)
|
||||
(cph/update-object-indices page-id))))
|
||||
(ctst/update-object-indices page-id))))
|
||||
|
||||
(catch :default err
|
||||
(log/error :js/error err)
|
||||
|
@ -191,6 +192,7 @@
|
|||
process-page-changes
|
||||
(fn [[page-id _changes]]
|
||||
(update-indices page-id redo-changes))]
|
||||
|
||||
(rx/concat
|
||||
(rx/from (map process-page-changes changes-by-pages))
|
||||
|
||||
|
|
|
@ -108,4 +108,3 @@
|
|||
:undo-changes []
|
||||
:origin it
|
||||
:save-undo? false})))))))))))
|
||||
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
(ns app.main.data.workspace.drawing
|
||||
"Drawing interactions."
|
||||
(:require
|
||||
[app.common.pages :as cp]
|
||||
[app.common.types.shape :as cts]
|
||||
[app.common.uuid :as uuid]
|
||||
[app.main.data.workspace.common :as dwc]
|
||||
[app.main.data.workspace.drawing.box :as box]
|
||||
|
@ -91,7 +91,7 @@
|
|||
(ptk/reify ::handle-drawing
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(let [data (cp/make-minimal-shape type)]
|
||||
(let [data (cts/make-minimal-shape type)]
|
||||
(update-in state [:workspace-drawing :object] merge data)))
|
||||
|
||||
ptk/WatchEvent
|
||||
|
|
|
@ -9,8 +9,9 @@
|
|||
[app.common.geom.point :as gpt]
|
||||
[app.common.geom.shapes :as gsh]
|
||||
[app.common.math :as mth]
|
||||
[app.common.pages :as cp]
|
||||
[app.common.pages.helpers :as cph]
|
||||
[app.common.types.shape :as cts]
|
||||
[app.common.types.shape-tree :as ctt]
|
||||
[app.common.uuid :as uuid]
|
||||
[app.main.data.workspace.drawing.common :as common]
|
||||
[app.main.data.workspace.state-helpers :as wsh]
|
||||
|
@ -65,11 +66,11 @@
|
|||
focus (:workspace-focus-selected state)
|
||||
zoom (get-in state [:workspace-local :zoom] 1)
|
||||
|
||||
fid (cph/frame-id-by-position objects initial)
|
||||
fid (ctt/frame-id-by-position objects initial)
|
||||
|
||||
shape (get-in state [:workspace-drawing :object])
|
||||
shape (-> shape
|
||||
(cp/setup-shape {:x (:x initial)
|
||||
(cts/setup-shape {:x (:x initial)
|
||||
:y (:y initial)
|
||||
:width 0.01
|
||||
:height 0.01})
|
||||
|
|
|
@ -9,8 +9,8 @@
|
|||
[app.common.geom.matrix :as gmt]
|
||||
[app.common.geom.shapes :as gsh]
|
||||
[app.common.math :as mth]
|
||||
[app.common.pages :as cp]
|
||||
[app.common.pages.helpers :as cph]
|
||||
[app.common.types.shape :as cts]
|
||||
[app.main.data.workspace.shapes :as dwsh]
|
||||
[app.main.data.workspace.state-helpers :as wsh]
|
||||
[app.main.data.workspace.undo :as dwu]
|
||||
|
@ -55,7 +55,7 @@
|
|||
(assoc :height 17 :width 4 :grow-type :auto-width)
|
||||
|
||||
click-draw?
|
||||
(cp/setup-rect-selrect)
|
||||
(cts/setup-rect-selrect)
|
||||
|
||||
:always
|
||||
(-> (gsh/transform-shape)
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
(:require
|
||||
[app.common.geom.shapes :as gsh]
|
||||
[app.common.geom.shapes.path :as gsp]
|
||||
[app.common.pages.helpers :as cph]
|
||||
[app.common.types.shape-tree :as ctt]
|
||||
[app.main.data.workspace.drawing.common :as common]
|
||||
[app.main.data.workspace.state-helpers :as wsh]
|
||||
[app.main.streams :as ms]
|
||||
|
@ -47,7 +47,7 @@
|
|||
(let [objects (wsh/lookup-page-objects state)
|
||||
content (get-in state [:workspace-drawing :object :content] [])
|
||||
position (get-in content [0 :params] nil)
|
||||
frame-id (cph/frame-id-by-position objects position)]
|
||||
frame-id (ctt/frame-id-by-position objects position)]
|
||||
(-> state
|
||||
(assoc-in [:workspace-drawing :object :frame-id] frame-id))))))
|
||||
|
||||
|
|
|
@ -8,13 +8,13 @@
|
|||
(:require
|
||||
[app.common.data :as d]
|
||||
[app.common.geom.shapes :as gsh]
|
||||
[app.common.pages :as cp]
|
||||
[app.common.pages.changes-builder :as pcb]
|
||||
[app.common.pages.helpers :as cph]
|
||||
[app.common.types.shape :as cts]
|
||||
[app.common.types.shape-tree :as ctt]
|
||||
[app.main.data.workspace.changes :as dch]
|
||||
[app.main.data.workspace.selection :as dws]
|
||||
[app.main.data.workspace.state-helpers :as wsh]
|
||||
[app.util.names :as un]
|
||||
[beicon.core :as rx]
|
||||
[potok.core :as ptk]))
|
||||
|
||||
|
@ -71,12 +71,12 @@
|
|||
(= (count shapes) 1)
|
||||
(= (:type (first shapes)) :group))
|
||||
(:name (first shapes))
|
||||
(-> (un/retrieve-used-names objects)
|
||||
(un/generate-unique-name base-name)))
|
||||
(-> (ctt/retrieve-used-names objects)
|
||||
(ctt/generate-unique-name base-name)))
|
||||
|
||||
selrect (gsh/selection-rect shapes)
|
||||
group (-> (cp/make-minimal-group frame-id selrect gname)
|
||||
(cp/setup-shape selrect)
|
||||
group (-> (cts/make-minimal-group frame-id selrect gname)
|
||||
(cts/setup-shape selrect)
|
||||
(assoc :shapes (mapv :id shapes)
|
||||
:parent-id parent-id
|
||||
:frame-id frame-id
|
||||
|
|
|
@ -12,12 +12,12 @@
|
|||
[app.common.pages.helpers :as cph]
|
||||
[app.common.spec :as us]
|
||||
[app.common.types.page :as ctp]
|
||||
[app.common.types.shape-tree :as ctst]
|
||||
[app.common.types.shape.interactions :as ctsi]
|
||||
[app.common.uuid :as uuid]
|
||||
[app.main.data.workspace.changes :as dch]
|
||||
[app.main.data.workspace.state-helpers :as wsh]
|
||||
[app.main.streams :as ms]
|
||||
[app.util.names :as un]
|
||||
[beicon.core :as rx]
|
||||
[potok.core :as ptk]))
|
||||
|
||||
|
@ -32,7 +32,7 @@
|
|||
|
||||
flows (get-in page [:options :flows] [])
|
||||
unames (into #{} (map :name flows))
|
||||
name (un/generate-unique-name unames "Flow-1")
|
||||
name (ctst/generate-unique-name unames "Flow-1")
|
||||
|
||||
new-flow {:id (uuid/next)
|
||||
:name name
|
||||
|
@ -182,7 +182,7 @@
|
|||
from-frame-id (if (cph/frame-shape? from-shape)
|
||||
from-id (:frame-id from-shape))
|
||||
|
||||
target-frame (cph/frame-by-position objects position)]
|
||||
target-frame (ctst/frame-by-position objects position)]
|
||||
|
||||
(when (and (not= (:id target-frame) uuid/zero)
|
||||
(not= (:id target-frame) from-frame-id)
|
||||
|
|
|
@ -10,12 +10,15 @@
|
|||
[app.common.geom.point :as gpt]
|
||||
[app.common.logging :as log]
|
||||
[app.common.pages :as cp]
|
||||
[app.common.pages.changes :as ch]
|
||||
[app.common.pages.changes-builder :as pcb]
|
||||
[app.common.pages.changes-spec :as pcs]
|
||||
[app.common.pages.helpers :as cph]
|
||||
[app.common.spec :as us]
|
||||
[app.common.types.color :as ctc]
|
||||
[app.common.types.container :as ctn]
|
||||
[app.common.types.file :as ctf]
|
||||
[app.common.types.shape-tree :as ctst]
|
||||
[app.common.types.typography :as ctt]
|
||||
[app.common.uuid :as uuid]
|
||||
[app.main.data.dashboard :as dd]
|
||||
|
@ -27,10 +30,11 @@
|
|||
[app.main.data.workspace.selection :as dws]
|
||||
[app.main.data.workspace.state-helpers :as wsh]
|
||||
[app.main.data.workspace.undo :as dwu]
|
||||
[app.main.features :as features]
|
||||
[app.main.refs :as refs]
|
||||
[app.main.repo :as rp]
|
||||
[app.main.store :as st]
|
||||
[app.util.i18n :refer [tr]]
|
||||
[app.util.names :as un]
|
||||
[app.util.router :as rt]
|
||||
[app.util.time :as dt]
|
||||
[beicon.core :as rx]
|
||||
|
@ -137,7 +141,7 @@
|
|||
(pcb/update-color color))]
|
||||
(rx/of (dwu/start-undo-transaction)
|
||||
(dch/commit-changes changes)
|
||||
(sync-file (:current-file-id state) file-id)
|
||||
(sync-file (:current-file-id state) file-id :colors (:id color))
|
||||
(dwu/commit-undo-transaction))))
|
||||
|
||||
(defn update-color
|
||||
|
@ -240,7 +244,7 @@
|
|||
(pcb/update-typography typography))]
|
||||
(rx/of (dwu/start-undo-transaction)
|
||||
(dch/commit-changes changes)
|
||||
(sync-file (:current-file-id state) file-id)
|
||||
(sync-file (:current-file-id state) file-id :typographies (:id typography))
|
||||
(dwu/commit-undo-transaction))))
|
||||
|
||||
(defn update-typography
|
||||
|
@ -280,7 +284,7 @@
|
|||
|
||||
(defn- add-component2
|
||||
"This is the second step of the component creation."
|
||||
[selected]
|
||||
[selected components-v2]
|
||||
(ptk/reify ::add-component2
|
||||
IDeref
|
||||
(-deref [_] {:num-shapes (count selected)})
|
||||
|
@ -293,7 +297,7 @@
|
|||
shapes (dwg/shapes-for-grouping objects selected)]
|
||||
(when-not (empty? shapes)
|
||||
(let [[group _ changes]
|
||||
(dwlh/generate-add-component it shapes objects page-id file-id)]
|
||||
(dwlh/generate-add-component it shapes objects page-id file-id components-v2)]
|
||||
(when-not (empty? (:redo-changes changes))
|
||||
(rx/of (dch/commit-changes changes)
|
||||
(dws/select-shapes (d/ordered-set (:id group)))))))))))
|
||||
|
@ -307,10 +311,11 @@
|
|||
(ptk/reify ::add-component
|
||||
ptk/WatchEvent
|
||||
(watch [_ state _]
|
||||
(let [objects (wsh/lookup-page-objects state)
|
||||
selected (->> (wsh/lookup-selected state)
|
||||
(cph/clean-loops objects))]
|
||||
(rx/of (add-component2 selected))))))
|
||||
(let [objects (wsh/lookup-page-objects state)
|
||||
selected (->> (wsh/lookup-selected state)
|
||||
(cph/clean-loops objects))
|
||||
components-v2 (features/active-feature? state :components-v2)]
|
||||
(rx/of (add-component2 selected components-v2))))))
|
||||
|
||||
(defn rename-component
|
||||
"Rename the component with the given id, in the current file library."
|
||||
|
@ -352,18 +357,30 @@
|
|||
component (cph/get-component libraries id)
|
||||
all-components (-> state :workspace-data :components vals)
|
||||
unames (into #{} (map :name) all-components)
|
||||
new-name (un/generate-unique-name unames (:name component))
|
||||
new-name (ctst/generate-unique-name unames (:name component))
|
||||
|
||||
[new-shape new-shapes _updated-shapes]
|
||||
(dwlh/duplicate-component component)
|
||||
components-v2 (features/active-feature? state :components-v2)
|
||||
|
||||
changes (-> (pcb/empty-changes it nil) ;; no objects are changed
|
||||
(pcb/with-objects nil) ;; in the current page
|
||||
(pcb/add-component (:id new-shape)
|
||||
main-instance-page (when components-v2
|
||||
(wsh/lookup-page state (:main-instance-page component)))
|
||||
main-instance-shape (when components-v2
|
||||
(ctn/get-shape main-instance-page (:main-instance-id component)))
|
||||
|
||||
[new-component-shape new-component-shapes
|
||||
new-main-instance-shape new-main-instance-shapes]
|
||||
(dwlh/duplicate-component component main-instance-page main-instance-shape)
|
||||
|
||||
changes (-> (pcb/empty-changes it nil)
|
||||
(pcb/with-page main-instance-page)
|
||||
(pcb/with-objects (:objects main-instance-page))
|
||||
(pcb/add-objects new-main-instance-shapes {:ignore-touched true})
|
||||
(pcb/add-component (:id new-component-shape)
|
||||
(:path component)
|
||||
new-name
|
||||
new-shapes
|
||||
[]))]
|
||||
new-component-shapes
|
||||
[]
|
||||
(:id new-main-instance-shape)
|
||||
(:id main-instance-page)))]
|
||||
|
||||
(rx/of (dch/commit-changes changes))))))
|
||||
|
||||
|
@ -521,7 +538,7 @@
|
|||
libraries (wsh/get-libraries state)
|
||||
|
||||
container (cph/get-container local-file :page page-id)
|
||||
shape (cph/get-shape container id)
|
||||
shape (ctn/get-shape container id)
|
||||
|
||||
changes
|
||||
(-> (pcb/empty-changes it)
|
||||
|
@ -568,13 +585,15 @@
|
|||
(ptk/reify ::update-component-sync
|
||||
ptk/WatchEvent
|
||||
(watch [_ state _]
|
||||
(let [current-file-id (:current-file-id state)]
|
||||
(let [current-file-id (:current-file-id state)
|
||||
page (wsh/lookup-page state)
|
||||
shape (ctn/get-shape page shape-id)]
|
||||
(rx/of
|
||||
(dwu/start-undo-transaction)
|
||||
(update-component shape-id)
|
||||
(sync-file current-file-id file-id)
|
||||
(sync-file current-file-id file-id :components (:component-id shape))
|
||||
(when (not= current-file-id file-id)
|
||||
(sync-file file-id file-id))
|
||||
(sync-file file-id file-id :components (:component-id shape)))
|
||||
(dwu/commit-undo-transaction))))))
|
||||
|
||||
(defn update-component-in-bulk
|
||||
|
@ -593,63 +612,83 @@
|
|||
"Synchronize the given file from the given library. Walk through all
|
||||
shapes in all pages in the file that use some color, typography or
|
||||
component of the library, and copy the new values to the shapes. Do
|
||||
it also for shapes inside components of the local file library."
|
||||
[file-id library-id]
|
||||
(us/assert ::us/uuid file-id)
|
||||
(us/assert ::us/uuid library-id)
|
||||
(ptk/reify ::sync-file
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(if (not= library-id (:current-file-id state))
|
||||
(d/assoc-in-when state [:workspace-libraries library-id :synced-at] (dt/now))
|
||||
state))
|
||||
it also for shapes inside components of the local file library.
|
||||
|
||||
ptk/WatchEvent
|
||||
(watch [it state _]
|
||||
(when (and (some? file-id) (some? library-id)) ; Prevent race conditions while navigating out of the file
|
||||
(log/info :msg "SYNC-FILE"
|
||||
:file (dwlh/pretty-file file-id state)
|
||||
:library (dwlh/pretty-file library-id state))
|
||||
(let [file (wsh/get-file state file-id)
|
||||
If it's known that only one asset has changed, you can give its
|
||||
type and id, and only shapes that use it will be synced, thus avoiding
|
||||
a lot of unneeded checks."
|
||||
([file-id library-id]
|
||||
(sync-file file-id library-id nil nil))
|
||||
([file-id library-id asset-type asset-id]
|
||||
(us/assert ::us/uuid file-id)
|
||||
(us/assert ::us/uuid library-id)
|
||||
(us/assert (s/nilable #{:colors :components :typographies}) asset-type)
|
||||
(us/assert (s/nilable ::us/uuid) asset-id)
|
||||
(ptk/reify ::sync-file
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(if (and (not= library-id (:current-file-id state))
|
||||
(nil? asset-id))
|
||||
(d/assoc-in-when state [:workspace-libraries library-id :synced-at] (dt/now))
|
||||
state))
|
||||
|
||||
library-changes (reduce
|
||||
pcb/concat-changes
|
||||
(pcb/empty-changes it)
|
||||
[(dwlh/generate-sync-library it file-id :components library-id state)
|
||||
(dwlh/generate-sync-library it file-id :colors library-id state)
|
||||
(dwlh/generate-sync-library it file-id :typographies library-id state)])
|
||||
file-changes (reduce
|
||||
pcb/concat-changes
|
||||
(pcb/empty-changes it)
|
||||
[(dwlh/generate-sync-file it file-id :components library-id state)
|
||||
(dwlh/generate-sync-file it file-id :colors library-id state)
|
||||
(dwlh/generate-sync-file it file-id :typographies library-id state)])
|
||||
ptk/WatchEvent
|
||||
(watch [it state _]
|
||||
(when (and (some? file-id) (some? library-id)) ; Prevent race conditions while navigating out of the file
|
||||
(log/info :msg "SYNC-FILE"
|
||||
:file (dwlh/pretty-file file-id state)
|
||||
:library (dwlh/pretty-file library-id state))
|
||||
(let [file (wsh/get-file state file-id)
|
||||
|
||||
changes (pcb/concat-changes library-changes file-changes)]
|
||||
sync-components? (or (nil? asset-type) (= asset-type :components))
|
||||
sync-colors? (or (nil? asset-type) (= asset-type :colors))
|
||||
sync-typographies? (or (nil? asset-type) (= asset-type :typographies))
|
||||
|
||||
(log/debug :msg "SYNC-FILE finished" :js/rchanges (log-changes
|
||||
(:redo-changes changes)
|
||||
file))
|
||||
(rx/concat
|
||||
(rx/of (dm/hide-tag :sync-dialog))
|
||||
(when (seq (:redo-changes changes))
|
||||
(rx/of (dch/commit-changes (assoc changes ;; TODO a ver qué pasa con esto
|
||||
:file-id file-id))))
|
||||
(when (not= file-id library-id)
|
||||
;; When we have just updated the library file, give some time for the
|
||||
;; update to finish, before marking this file as synced.
|
||||
;; TODO: look for a more precise way of syncing this.
|
||||
;; Maybe by using the stream (second argument passed to watch)
|
||||
;; to wait for the corresponding changes-committed and then proceed
|
||||
;; with the :update-sync mutation.
|
||||
(rx/concat (rx/timer 3000)
|
||||
(rp/mutation :update-sync
|
||||
{:file-id file-id
|
||||
:library-id library-id})))
|
||||
(when (seq (:redo-changes library-changes))
|
||||
(rx/of (sync-file-2nd-stage file-id library-id)))))))))
|
||||
library-changes (reduce
|
||||
pcb/concat-changes
|
||||
(pcb/empty-changes it)
|
||||
[(when sync-components?
|
||||
(dwlh/generate-sync-library it file-id :components asset-id library-id state))
|
||||
(when sync-colors?
|
||||
(dwlh/generate-sync-library it file-id :colors asset-id library-id state))
|
||||
(when sync-typographies?
|
||||
(dwlh/generate-sync-library it file-id :typographies asset-id library-id state))])
|
||||
file-changes (reduce
|
||||
pcb/concat-changes
|
||||
(pcb/empty-changes it)
|
||||
[(when sync-components?
|
||||
(dwlh/generate-sync-file it file-id :components asset-id library-id state))
|
||||
(when sync-colors?
|
||||
(dwlh/generate-sync-file it file-id :colors asset-id library-id state))
|
||||
(when sync-typographies?
|
||||
(dwlh/generate-sync-file it file-id :typographies asset-id library-id state))])
|
||||
|
||||
(defn sync-file-2nd-stage
|
||||
changes (pcb/concat-changes library-changes file-changes)]
|
||||
|
||||
(log/debug :msg "SYNC-FILE finished" :js/rchanges (log-changes
|
||||
(:redo-changes changes)
|
||||
file))
|
||||
(rx/concat
|
||||
(rx/of (dm/hide-tag :sync-dialog))
|
||||
(when (seq (:redo-changes changes))
|
||||
(rx/of (dch/commit-changes (assoc changes ;; TODO a ver qué pasa con esto
|
||||
:file-id file-id))))
|
||||
(when (not= file-id library-id)
|
||||
;; When we have just updated the library file, give some time for the
|
||||
;; update to finish, before marking this file as synced.
|
||||
;; TODO: look for a more precise way of syncing this.
|
||||
;; Maybe by using the stream (second argument passed to watch)
|
||||
;; to wait for the corresponding changes-committed and then proceed
|
||||
;; with the :update-sync mutation.
|
||||
(rx/concat (rx/timer 3000)
|
||||
(rp/mutation :update-sync
|
||||
{:file-id file-id
|
||||
:library-id library-id})))
|
||||
(when (and (seq (:redo-changes library-changes))
|
||||
sync-components?)
|
||||
(rx/of (sync-file-2nd-stage file-id library-id asset-id))))))))))
|
||||
|
||||
(defn- sync-file-2nd-stage
|
||||
"If some components have been modified, we need to launch another synchronization
|
||||
to update the instances of the changed components."
|
||||
;; TODO: this does not work if there are multiple nested components. Only the
|
||||
|
@ -658,9 +697,10 @@
|
|||
;; recursively. But for this not to cause an infinite loop, we need to
|
||||
;; implement updated-at at component level, to detect what components have
|
||||
;; not changed, and then not to apply sync and terminate the loop.
|
||||
[file-id library-id]
|
||||
[file-id library-id asset-id]
|
||||
(us/assert ::us/uuid file-id)
|
||||
(us/assert ::us/uuid library-id)
|
||||
(us/assert (s/nilable ::us/uuid) asset-id)
|
||||
(ptk/reify ::sync-file-2nd-stage
|
||||
ptk/WatchEvent
|
||||
(watch [it state _]
|
||||
|
@ -671,8 +711,8 @@
|
|||
changes (reduce
|
||||
pcb/concat-changes
|
||||
(pcb/empty-changes it)
|
||||
[(dwlh/generate-sync-file it file-id :components library-id state)
|
||||
(dwlh/generate-sync-library it file-id :components library-id state)])]
|
||||
[(dwlh/generate-sync-file it file-id :components asset-id library-id state)
|
||||
(dwlh/generate-sync-library it file-id :components asset-id library-id state)])]
|
||||
(when (seq (:redo-changes changes))
|
||||
(log/debug :msg "SYNC-FILE (2nd stage) finished" :js/rchanges (log-changes
|
||||
(:redo-changes changes)
|
||||
|
@ -716,6 +756,48 @@
|
|||
:callback do-dismiss}]
|
||||
:sync-dialog))))))
|
||||
|
||||
(defn watch-component-changes
|
||||
"Watch the state for changes that affect to any main instance. If a change is detected will throw
|
||||
an update-component-sync, so changes are immediately propagated to the component and copies."
|
||||
[]
|
||||
(ptk/reify ::watch-component-changes
|
||||
ptk/WatchEvent
|
||||
(watch [_ state stream]
|
||||
(let [components-v2 (features/active-feature? state :components-v2)
|
||||
|
||||
stopper
|
||||
(->> stream
|
||||
(rx/filter #(or (= :app.main.data.workspace/finalize-page (ptk/type %))
|
||||
(= ::watch-component-changes (ptk/type %)))))
|
||||
|
||||
workspace-data-s
|
||||
(->> (rx/concat
|
||||
(rx/of nil)
|
||||
(rx/from-atom refs/workspace-data {:emit-current-value? true})))
|
||||
|
||||
change-s
|
||||
(->> stream
|
||||
(rx/filter #(or (dch/commit-changes? %)
|
||||
(= (ptk/type %) :app.main.data.workspace.notifications/handle-file-change)))
|
||||
(rx/observe-on :async))
|
||||
|
||||
check-changes
|
||||
(fn [[event data]]
|
||||
(let [changes (-> event deref :changes)
|
||||
components-changed (reduce #(into %1 (ch/components-changed data %2))
|
||||
#{}
|
||||
changes)]
|
||||
(when (d/not-empty? components-changed)
|
||||
(run! st/emit!
|
||||
(map #(update-component-sync % (:id data))
|
||||
components-changed)))))]
|
||||
|
||||
(when components-v2
|
||||
(->> change-s
|
||||
(rx/with-latest-from workspace-data-s)
|
||||
(rx/map check-changes)
|
||||
(rx/take-until stopper)))))))
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;; Backend interactions
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
@ -762,12 +844,13 @@
|
|||
[file-id library-id]
|
||||
(ptk/reify ::attach-library
|
||||
ptk/WatchEvent
|
||||
(watch [_ _ _]
|
||||
(let [fetched #(assoc-in %2 [:workspace-libraries (:id %1)] %1)
|
||||
params {:file-id file-id
|
||||
:library-id library-id}]
|
||||
(watch [_ state _]
|
||||
(let [components-v2 (features/active-feature? state :components-v2)
|
||||
fetched #(assoc-in %2 [:workspace-libraries (:id %1)] %1)
|
||||
params {:file-id file-id
|
||||
:library-id library-id}]
|
||||
(->> (rp/mutation :link-file-to-library params)
|
||||
(rx/mapcat #(rp/query :file {:id library-id}))
|
||||
(rx/mapcat #(rp/query :file {:id library-id :components-v2 components-v2}))
|
||||
(rx/map #(partial fetched %)))))))
|
||||
|
||||
(defn unlink-file-from-library
|
||||
|
|
|
@ -16,9 +16,12 @@
|
|||
[app.common.spec :as us]
|
||||
[app.common.text :as txt]
|
||||
[app.common.types.color :as ctc]
|
||||
[app.common.types.component :as ctk]
|
||||
[app.common.types.container :as ctn]
|
||||
[app.common.types.shape-tree :as ctst]
|
||||
[app.common.types.typography :as cty]
|
||||
[app.main.data.workspace.groups :as dwg]
|
||||
[app.main.data.workspace.state-helpers :as wsh]
|
||||
[app.util.names :as un]
|
||||
[cljs.spec.alpha :as s]
|
||||
[clojure.set :as set]))
|
||||
|
||||
|
@ -55,51 +58,11 @@
|
|||
|
||||
;; ---- Components and instances creation ----
|
||||
|
||||
(defn make-component-shape
|
||||
"Clone the shape and all children. Generate new ids and detach
|
||||
from parent and frame. Update the original shapes to have links
|
||||
to the new ones."
|
||||
[shape objects file-id]
|
||||
(assert (nil? (:component-id shape)))
|
||||
(assert (nil? (:component-file shape)))
|
||||
(assert (nil? (:shape-ref shape)))
|
||||
(let [;; Ensure that the component root is not an instance and
|
||||
;; it's no longer tied to a frame.
|
||||
update-new-shape (fn [new-shape _original-shape]
|
||||
(cond-> new-shape
|
||||
true
|
||||
(-> (assoc :frame-id nil)
|
||||
(dissoc :component-root?))
|
||||
|
||||
(nil? (:parent-id new-shape))
|
||||
(dissoc :component-id
|
||||
:component-file
|
||||
:shape-ref)))
|
||||
|
||||
;; Make the original shape an instance of the new component.
|
||||
;; If one of the original shape children already was a component
|
||||
;; instance, maintain this instanceness untouched.
|
||||
update-original-shape (fn [original-shape new-shape]
|
||||
(cond-> original-shape
|
||||
(nil? (:shape-ref original-shape))
|
||||
(-> (assoc :shape-ref (:id new-shape))
|
||||
(dissoc :touched))
|
||||
|
||||
(nil? (:parent-id new-shape))
|
||||
(assoc :component-id (:id new-shape)
|
||||
:component-file file-id
|
||||
:component-root? true)
|
||||
|
||||
(some? (:parent-id new-shape))
|
||||
(dissoc :component-root?)))]
|
||||
|
||||
(cph/clone-object shape nil objects update-new-shape update-original-shape)))
|
||||
|
||||
(defn generate-add-component
|
||||
"If there is exactly one id, and it's a group, use it as root. Otherwise,
|
||||
create a group that contains all ids. Then, make a component with it,
|
||||
and link all shapes to their corresponding one in the component."
|
||||
[it shapes objects page-id file-id]
|
||||
[it shapes objects page-id file-id components-v2]
|
||||
(if (and (= (count shapes) 1)
|
||||
(:component-id (first shapes)))
|
||||
[(first shapes) (pcb/empty-changes it)]
|
||||
|
@ -114,73 +77,54 @@
|
|||
(dwg/prepare-create-group it objects page-id shapes name true))
|
||||
|
||||
[new-shape new-shapes updated-shapes]
|
||||
(make-component-shape group objects file-id)
|
||||
(ctn/make-component-shape group objects file-id components-v2)
|
||||
|
||||
changes (-> changes
|
||||
(pcb/add-component (:id new-shape)
|
||||
path
|
||||
name
|
||||
new-shapes
|
||||
updated-shapes))]
|
||||
updated-shapes
|
||||
(:id group)
|
||||
page-id))]
|
||||
[group new-shape changes])))
|
||||
|
||||
(defn duplicate-component
|
||||
"Clone the root shape of the component and all children. Generate new
|
||||
ids from all of them."
|
||||
[component]
|
||||
(let [component-root (cph/get-component-root component)]
|
||||
(cph/clone-object component-root
|
||||
nil
|
||||
(get component :objects)
|
||||
identity)))
|
||||
[component main-instance-page main-instance-shape]
|
||||
(let [position (gpt/add (gpt/point (:x main-instance-shape) (:y main-instance-shape))
|
||||
(gpt/point (+ (:width main-instance-shape) 50) 0))
|
||||
|
||||
component-root (ctk/get-component-root component)
|
||||
|
||||
[new-component-shape new-component-shapes _]
|
||||
(ctst/clone-object component-root
|
||||
nil
|
||||
(get component :objects)
|
||||
identity)
|
||||
|
||||
|
||||
[new-instance-shape new-instance-shapes]
|
||||
(when (and (some? main-instance-page) (some? main-instance-shape))
|
||||
(ctn/make-component-instance main-instance-page
|
||||
{:id (:id new-component-shape)
|
||||
:name (:name new-component-shape)
|
||||
:objects (d/index-by :id new-component-shapes)}
|
||||
(:component-file main-instance-shape)
|
||||
position
|
||||
false))]
|
||||
|
||||
[new-component-shape new-component-shapes
|
||||
new-instance-shape new-instance-shapes]))
|
||||
|
||||
(defn generate-instantiate-component
|
||||
"Generate changes to create a new instance from a component."
|
||||
[it file-id component-id position page libraries]
|
||||
(let [component (cph/get-component libraries file-id component-id)
|
||||
component-shape (cph/get-shape component component-id)
|
||||
|
||||
orig-pos (gpt/point (:x component-shape) (:y component-shape))
|
||||
delta (gpt/subtract position orig-pos)
|
||||
|
||||
objects (:objects page)
|
||||
unames (volatile! (un/retrieve-used-names objects))
|
||||
|
||||
frame-id (cph/frame-id-by-position objects (gpt/add orig-pos delta))
|
||||
|
||||
update-new-shape
|
||||
(fn [new-shape original-shape]
|
||||
(let [new-name (un/generate-unique-name @unames (:name new-shape))]
|
||||
|
||||
(when (nil? (:parent-id original-shape))
|
||||
(vswap! unames conj new-name))
|
||||
|
||||
(cond-> new-shape
|
||||
true
|
||||
(as-> $
|
||||
(gsh/move $ delta)
|
||||
(assoc $ :frame-id frame-id)
|
||||
(assoc $ :parent-id
|
||||
(or (:parent-id $) (:frame-id $)))
|
||||
(dissoc $ :touched))
|
||||
|
||||
(nil? (:shape-ref original-shape))
|
||||
(assoc :shape-ref (:id original-shape))
|
||||
|
||||
(nil? (:parent-id original-shape))
|
||||
(assoc :component-id (:id original-shape)
|
||||
:component-file file-id
|
||||
:component-root? true
|
||||
:name new-name)
|
||||
|
||||
(some? (:parent-id original-shape))
|
||||
(dissoc :component-root?))))
|
||||
|
||||
[new-shape new-shapes _]
|
||||
(cph/clone-object component-shape
|
||||
nil
|
||||
(get component :objects)
|
||||
update-new-shape)
|
||||
[new-shape new-shapes]
|
||||
(ctn/make-component-instance page component file-id position false)
|
||||
|
||||
changes (reduce #(pcb/add-object %1 %2 {:ignore-touched true})
|
||||
(pcb/empty-changes it (:id page))
|
||||
|
@ -212,14 +156,19 @@
|
|||
|
||||
(defn generate-sync-file
|
||||
"Generate changes to synchronize all shapes in all pages of the given file,
|
||||
that use assets of the given type in the given library."
|
||||
[it file-id asset-type library-id state]
|
||||
that use assets of the given type in the given library.
|
||||
|
||||
If an asset id is given, only shapes linked to this particular asset will
|
||||
be syncrhonized."
|
||||
[it file-id asset-type asset-id library-id state]
|
||||
(s/assert #{:colors :components :typographies} asset-type)
|
||||
(s/assert (s/nilable ::us/uuid) asset-id)
|
||||
(s/assert ::us/uuid file-id)
|
||||
(s/assert ::us/uuid library-id)
|
||||
|
||||
(log/info :msg "Sync file with library"
|
||||
:asset-type asset-type
|
||||
:asset-id asset-id
|
||||
:file (pretty-file file-id state)
|
||||
:library (pretty-file library-id state))
|
||||
|
||||
|
@ -232,6 +181,7 @@
|
|||
changes
|
||||
(generate-sync-container it
|
||||
asset-type
|
||||
asset-id
|
||||
library-id
|
||||
state
|
||||
(cph/make-container page :page))))
|
||||
|
@ -240,11 +190,19 @@
|
|||
(defn generate-sync-library
|
||||
"Generate changes to synchronize all shapes in all components of the
|
||||
local library of the given file, that use assets of the given type in
|
||||
the given library."
|
||||
[it file-id asset-type library-id state]
|
||||
the given library.
|
||||
|
||||
If an asset id is given, only shapes linked to this particular asset will
|
||||
be syncrhonized."
|
||||
[it file-id asset-type asset-id library-id state]
|
||||
(s/assert #{:colors :components :typographies} asset-type)
|
||||
(s/assert (s/nilable ::us/uuid) asset-id)
|
||||
(s/assert ::us/uuid file-id)
|
||||
(s/assert ::us/uuid library-id)
|
||||
|
||||
(log/info :msg "Sync local components with library"
|
||||
:asset-type asset-type
|
||||
:asset-id asset-id
|
||||
:file (pretty-file file-id state)
|
||||
:library (pretty-file library-id state))
|
||||
|
||||
|
@ -257,6 +215,7 @@
|
|||
changes
|
||||
(generate-sync-container it
|
||||
asset-type
|
||||
asset-id
|
||||
library-id
|
||||
state
|
||||
(cph/make-container local-component :component))))
|
||||
|
@ -265,14 +224,14 @@
|
|||
(defn- generate-sync-container
|
||||
"Generate changes to synchronize all shapes in a particular container (a page
|
||||
or a component) that use assets of the given type in the given library."
|
||||
[it asset-type library-id state container]
|
||||
[it asset-type asset-id library-id state container]
|
||||
|
||||
(if (cph/page? container)
|
||||
(log/debug :msg "Sync page in local file" :page-id (:id container))
|
||||
(log/debug :msg "Sync component in local library" :component-id (:id container)))
|
||||
|
||||
(let [linked-shapes (->> (vals (:objects container))
|
||||
(filter #(uses-assets? asset-type % library-id (cph/page? container))))]
|
||||
(let [linked-shapes (->> (vals (:objects container))
|
||||
(filter #(uses-assets? asset-type asset-id % library-id (cph/page? container))))]
|
||||
(loop [shapes (seq linked-shapes)
|
||||
changes (-> (pcb/empty-changes it)
|
||||
(pcb/with-container container)
|
||||
|
@ -289,27 +248,26 @@
|
|||
|
||||
(defmulti uses-assets?
|
||||
"Checks if a shape uses some asset of the given type in the given library."
|
||||
(fn [asset-type _ _ _] asset-type))
|
||||
(fn [asset-type _ _ _ _] asset-type))
|
||||
|
||||
(defmethod uses-assets? :components
|
||||
[_ shape library-id page?]
|
||||
(and (some? (:component-id shape))
|
||||
(= (:component-file shape) library-id)
|
||||
[_ component-id shape library-id page?]
|
||||
(and (if (nil? component-id)
|
||||
(ctk/uses-library-components? shape library-id)
|
||||
(ctk/instance-of? shape library-id component-id))
|
||||
(or (:component-root? shape) (not page?)))) ; avoid nested components inside pages
|
||||
|
||||
(defmethod uses-assets? :colors
|
||||
[_ shape library-id _]
|
||||
(ctc/uses-library-colors? shape library-id))
|
||||
[_ color-id shape library-id _]
|
||||
(if (nil? color-id)
|
||||
(ctc/uses-library-colors? shape library-id)
|
||||
(ctc/uses-library-color? shape library-id color-id)))
|
||||
|
||||
(defmethod uses-assets? :typographies
|
||||
[_ shape library-id _]
|
||||
(and (= (:type shape) :text)
|
||||
(->> shape
|
||||
:content
|
||||
;; Check if any node in the content has a reference for the library
|
||||
(txt/node-seq
|
||||
#(and (some? (:typography-ref-id %))
|
||||
(= (:typography-ref-file %) library-id))))))
|
||||
[_ typography-id shape library-id _]
|
||||
(if (nil? typography-id)
|
||||
(cty/uses-library-typographies? shape library-id)
|
||||
(cty/uses-library-typography? shape library-id typography-id)))
|
||||
|
||||
(defmulti generate-sync-shape
|
||||
"Generate changes to synchronize one shape from all assets of the given type
|
||||
|
@ -482,18 +440,18 @@
|
|||
instance, and all its children, from the given component."
|
||||
[changes libraries container shape-id reset?]
|
||||
(log/debug :msg "Sync shape direct" :shape (str shape-id) :reset? reset?)
|
||||
(let [shape-inst (cph/get-shape container shape-id)
|
||||
(let [shape-inst (ctn/get-shape container shape-id)
|
||||
component (cph/get-component libraries
|
||||
(:component-file shape-inst)
|
||||
(:component-id shape-inst))
|
||||
shape-main (when component
|
||||
(cph/get-shape component (:shape-ref shape-inst)))
|
||||
(ctn/get-shape component (:shape-ref shape-inst)))
|
||||
|
||||
initial-root? (:component-root? shape-inst)
|
||||
|
||||
root-inst shape-inst
|
||||
root-main (when component
|
||||
(cph/get-component-root component))]
|
||||
(ctk/get-component-root component))]
|
||||
|
||||
(if component
|
||||
(generate-sync-shape-direct-recursive changes
|
||||
|
@ -543,9 +501,9 @@
|
|||
set-remote-synced?
|
||||
(change-remote-synced shape-inst container true))
|
||||
|
||||
children-inst (mapv #(cph/get-shape container %)
|
||||
children-inst (mapv #(ctn/get-shape container %)
|
||||
(:shapes shape-inst))
|
||||
children-main (mapv #(cph/get-shape component %)
|
||||
children-main (mapv #(ctn/get-shape component %)
|
||||
(:shapes shape-main))
|
||||
|
||||
only-inst (fn [changes child-inst]
|
||||
|
@ -608,16 +566,16 @@
|
|||
the values in the shape and all its children."
|
||||
[changes libraries container shape-id]
|
||||
(log/debug :msg "Sync shape inverse" :shape (str shape-id))
|
||||
(let [shape-inst (cph/get-shape container shape-id)
|
||||
(let [shape-inst (ctn/get-shape container shape-id)
|
||||
component (cph/get-component libraries
|
||||
(:component-file shape-inst)
|
||||
(:component-id shape-inst))
|
||||
shape-main (cph/get-shape component (:shape-ref shape-inst))
|
||||
shape-main (ctn/get-shape component (:shape-ref shape-inst))
|
||||
|
||||
initial-root? (:component-root? shape-inst)
|
||||
|
||||
root-inst shape-inst
|
||||
root-main (cph/get-component-root component)]
|
||||
root-main (ctk/get-component-root component)]
|
||||
|
||||
(if component
|
||||
(generate-sync-shape-inverse-recursive changes
|
||||
|
@ -668,9 +626,9 @@
|
|||
set-remote-synced?
|
||||
(change-remote-synced shape-inst container true))
|
||||
|
||||
children-inst (mapv #(cph/get-shape container %)
|
||||
children-inst (mapv #(ctn/get-shape container %)
|
||||
(:shapes shape-inst))
|
||||
children-main (mapv #(cph/get-shape component %)
|
||||
children-main (mapv #(ctn/get-shape component %)
|
||||
(:shapes shape-main))
|
||||
|
||||
only-inst (fn [changes child-inst]
|
||||
|
@ -751,13 +709,13 @@
|
|||
(reduce only-inst-cb changes children-inst)
|
||||
|
||||
:else
|
||||
(if (cph/is-main-of? child-main child-inst)
|
||||
(if (ctk/is-main-of? child-main child-inst)
|
||||
(recur (next children-inst)
|
||||
(next children-main)
|
||||
(both-cb changes child-inst child-main))
|
||||
|
||||
(let [child-inst' (d/seek #(cph/is-main-of? child-main %) children-inst)
|
||||
child-main' (d/seek #(cph/is-main-of? % child-inst) children-main)]
|
||||
(let [child-inst' (d/seek #(ctk/is-main-of? child-main %) children-inst)
|
||||
child-main' (d/seek #(ctk/is-main-of? % child-inst) children-main)]
|
||||
(cond
|
||||
(nil? child-inst')
|
||||
(recur children-inst
|
||||
|
@ -785,8 +743,8 @@
|
|||
(defn- add-shape-to-instance
|
||||
[changes component-shape index component container root-instance root-main omit-touched? set-remote-synced?]
|
||||
(log/info :msg (str "ADD [P] " (:name component-shape)))
|
||||
(let [component-parent-shape (cph/get-shape component (:parent-id component-shape))
|
||||
parent-shape (d/seek #(cph/is-main-of? component-parent-shape %)
|
||||
(let [component-parent-shape (ctn/get-shape component (:parent-id component-shape))
|
||||
parent-shape (d/seek #(ctk/is-main-of? component-parent-shape %)
|
||||
(cph/get-children-with-self (:objects container)
|
||||
(:id root-instance)))
|
||||
all-parents (into [(:id parent-shape)]
|
||||
|
@ -811,7 +769,7 @@
|
|||
original-shape)
|
||||
|
||||
[_ new-shapes _]
|
||||
(cph/clone-object component-shape
|
||||
(ctst/clone-object component-shape
|
||||
(:id parent-shape)
|
||||
(get component :objects)
|
||||
update-new-shape
|
||||
|
@ -853,8 +811,8 @@
|
|||
(defn- add-shape-to-main
|
||||
[changes shape index component page root-instance root-main]
|
||||
(log/info :msg (str "ADD [C] " (:name shape)))
|
||||
(let [parent-shape (cph/get-shape page (:parent-id shape))
|
||||
component-parent-shape (d/seek #(cph/is-main-of? % parent-shape)
|
||||
(let [parent-shape (ctn/get-shape page (:parent-id shape))
|
||||
component-parent-shape (d/seek #(ctk/is-main-of? % parent-shape)
|
||||
(cph/get-children-with-self (:objects component)
|
||||
(:id root-main)))
|
||||
all-parents (into [(:id component-parent-shape)]
|
||||
|
@ -873,7 +831,7 @@
|
|||
original-shape))
|
||||
|
||||
[_new-shape new-shapes updated-shapes]
|
||||
(cph/clone-object shape
|
||||
(ctst/clone-object shape
|
||||
(:id component-parent-shape)
|
||||
(get page :objects)
|
||||
update-new-shape
|
||||
|
@ -980,7 +938,7 @@
|
|||
index-before
|
||||
" -> "
|
||||
index-after))
|
||||
(let [parent (cph/get-shape container (:parent-id shape))
|
||||
(let [parent (ctn/get-shape container (:parent-id shape))
|
||||
|
||||
changes' (-> changes
|
||||
(update :redo-changes conj (make-change
|
||||
|
|
|
@ -8,10 +8,10 @@
|
|||
(:require
|
||||
[app.common.geom.point :as gpt]
|
||||
[app.common.geom.shapes.path :as upg]
|
||||
[app.common.pages.helpers :as cph]
|
||||
[app.common.path.commands :as upc]
|
||||
[app.common.path.shapes-to-path :as upsp]
|
||||
[app.common.spec :as us]
|
||||
[app.common.types.shape-tree :as ctt]
|
||||
[app.main.data.workspace.changes :as dch]
|
||||
[app.main.data.workspace.drawing.common :as dwdc]
|
||||
[app.main.data.workspace.edition :as dwe]
|
||||
|
@ -258,7 +258,7 @@
|
|||
(let [objects (wsh/lookup-page-objects state)
|
||||
content (get-in state [:workspace-drawing :object :content] [])
|
||||
position (get-in content [0 :params] nil)
|
||||
frame-id (cph/frame-id-by-position objects position)]
|
||||
frame-id (ctt/frame-id-by-position objects position)]
|
||||
(-> state
|
||||
(assoc-in [:workspace-drawing :object :frame-id] frame-id))))))
|
||||
|
||||
|
|
|
@ -16,12 +16,15 @@
|
|||
[app.config :as cf]
|
||||
[app.main.data.dashboard :as dd]
|
||||
[app.main.data.fonts :as df]
|
||||
[app.main.data.modal :as modal]
|
||||
[app.main.data.workspace.changes :as dch]
|
||||
[app.main.data.workspace.state-helpers :as wsh]
|
||||
[app.main.data.workspace.thumbnails :as dwt]
|
||||
[app.main.features :as features]
|
||||
[app.main.repo :as rp]
|
||||
[app.main.store :as st]
|
||||
[app.util.http :as http]
|
||||
[app.util.i18n :as i18n :refer [tr]]
|
||||
[app.util.router :as rt]
|
||||
[app.util.time :as dt]
|
||||
[beicon.core :as rx]
|
||||
|
@ -124,8 +127,7 @@
|
|||
(rx/map persist-synchronous-changes)
|
||||
(rx/take-until (rx/delay 100 stoper))
|
||||
(rx/finalize (fn []
|
||||
(log/debug :hint "finalize persistence: synchronous save loop"))))
|
||||
)))))
|
||||
(log/debug :hint "finalize persistence: synchronous save loop")))))))))
|
||||
|
||||
(defn persist-changes
|
||||
[file-id changes]
|
||||
|
@ -134,12 +136,14 @@
|
|||
(ptk/reify ::persist-changes
|
||||
ptk/WatchEvent
|
||||
(watch [_ state _]
|
||||
(let [sid (:session-id state)
|
||||
file (get state :workspace-file)
|
||||
params {:id (:id file)
|
||||
:revn (:revn file)
|
||||
:session-id sid
|
||||
:changes-with-metadata (into [] changes)}]
|
||||
(let [components-v2 (features/active-feature? state :components-v2)
|
||||
sid (:session-id state)
|
||||
file (get state :workspace-file)
|
||||
params {:id (:id file)
|
||||
:revn (:revn file)
|
||||
:session-id sid
|
||||
:changes-with-metadata (into [] changes)
|
||||
:components-v2 components-v2}]
|
||||
|
||||
(when (= file-id (:id params))
|
||||
(->> (rp/mutation :update-file params)
|
||||
|
@ -175,13 +179,15 @@
|
|||
(ptk/reify ::persist-synchronous-changes
|
||||
ptk/WatchEvent
|
||||
(watch [_ state _]
|
||||
(let [sid (:session-id state)
|
||||
(let [components-v2 (features/active-feature? state :components-v2)
|
||||
sid (:session-id state)
|
||||
file (get-in state [:workspace-libraries file-id])
|
||||
|
||||
params {:id (:id file)
|
||||
:revn (:revn file)
|
||||
:session-id sid
|
||||
:changes changes}]
|
||||
:changes changes
|
||||
:components-v2 components-v2}]
|
||||
|
||||
(when (:id params)
|
||||
(->> (rp/mutation :update-file params)
|
||||
|
@ -261,8 +267,9 @@
|
|||
(ptk/reify ::fetch-bundle
|
||||
ptk/WatchEvent
|
||||
(watch [_ state _]
|
||||
(let [share-id (-> state :viewer-local :share-id)]
|
||||
(->> (rx/zip (rp/query :file-raw {:id file-id})
|
||||
(let [share-id (-> state :viewer-local :share-id)
|
||||
components-v2 (features/active-feature? state :components-v2)]
|
||||
(->> (rx/zip (rp/query :file-raw {:id file-id :components-v2 components-v2})
|
||||
(rp/query :team-users {:file-id file-id})
|
||||
(rp/query :project {:id project-id})
|
||||
(rp/query :file-libraries {:file-id file-id})
|
||||
|
@ -276,8 +283,16 @@
|
|||
:file-comments-users file-comments-users}))
|
||||
(rx/mapcat (fn [{:keys [project] :as bundle}]
|
||||
(rx/of (ptk/data-event ::bundle-fetched bundle)
|
||||
(df/load-team-fonts (:team-id project))))))))))
|
||||
|
||||
(df/load-team-fonts (:team-id project)))))
|
||||
(rx/catch (fn [err]
|
||||
(if (and (= (:type err) :restriction)
|
||||
(= (:code err) :feature-disabled))
|
||||
(let [team-id (:current-team-id state)]
|
||||
(rx/of (modal/show
|
||||
{:type :alert
|
||||
:message (tr "errors.components-v2")
|
||||
:on-accept #(st/emit! (rt/nav :dashboard-projects {:team-id team-id}))})))
|
||||
(rx/throw err)))))))))
|
||||
|
||||
;; --- Helpers
|
||||
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
[app.common.pages.helpers :as cph]
|
||||
[app.common.spec :as us]
|
||||
[app.common.types.page :as ctp]
|
||||
[app.common.types.shape-tree :as ctt]
|
||||
[app.common.types.shape.interactions :as ctsi]
|
||||
[app.common.uuid :as uuid]
|
||||
[app.main.data.modal :as md]
|
||||
|
@ -26,7 +27,6 @@
|
|||
[app.main.refs :as refs]
|
||||
[app.main.streams :as ms]
|
||||
[app.main.worker :as uw]
|
||||
[app.util.names :as un]
|
||||
[beicon.core :as rx]
|
||||
[cljs.spec.alpha :as s]
|
||||
[clojure.set :as set]
|
||||
|
@ -284,7 +284,7 @@
|
|||
move to the desired position, and recalculate parents and frames as needed."
|
||||
[all-objects page ids delta it]
|
||||
(let [shapes (map (d/getf all-objects) ids)
|
||||
unames (volatile! (un/retrieve-used-names (:objects page)))
|
||||
unames (volatile! (ctt/retrieve-used-names (:objects page)))
|
||||
update-unames! (fn [new-name] (vswap! unames conj new-name))
|
||||
all-ids (reduce #(into %1 (cons %2 (cph/get-children-ids all-objects %2))) (d/ordered-set) ids)
|
||||
ids-map (into {} (map #(vector % (uuid/next))) all-ids)
|
||||
|
@ -319,7 +319,7 @@
|
|||
(defn- prepare-duplicate-frame-change
|
||||
[changes objects page unames update-unames! ids-map obj delta]
|
||||
(let [new-id (ids-map (:id obj))
|
||||
frame-name (un/generate-unique-name @unames (:name obj))
|
||||
frame-name (ctt/generate-unique-name @unames (:name obj))
|
||||
_ (update-unames! frame-name)
|
||||
|
||||
new-frame (-> obj
|
||||
|
@ -354,7 +354,7 @@
|
|||
(if (some? obj)
|
||||
(let [new-id (ids-map (:id obj))
|
||||
parent-id (or parent-id frame-id)
|
||||
name (un/generate-unique-name @unames (:name obj))
|
||||
name (ctt/generate-unique-name @unames (:name obj))
|
||||
_ (update-unames! name)
|
||||
|
||||
new-obj (-> obj
|
||||
|
@ -395,7 +395,7 @@
|
|||
(let [update-flows (fn [flows]
|
||||
(reduce
|
||||
(fn [flows frame]
|
||||
(let [name (un/generate-unique-name @unames "Flow-1")
|
||||
(let [name (ctt/generate-unique-name @unames "Flow-1")
|
||||
_ (vswap! unames conj name)
|
||||
new-flow {:id (uuid/next)
|
||||
:name name
|
||||
|
|
|
@ -9,33 +9,33 @@
|
|||
[app.common.data :as d]
|
||||
[app.common.data.macros :as dm]
|
||||
[app.common.geom.proportions :as gpr]
|
||||
[app.common.pages :as cp]
|
||||
[app.common.pages.changes-builder :as pcb]
|
||||
[app.common.pages.helpers :as cph]
|
||||
[app.common.spec :as us]
|
||||
[app.common.types.page :as csp]
|
||||
[app.common.types.shape :as spec.shape]
|
||||
[app.common.types.shape.interactions :as csi]
|
||||
[app.common.types.page :as ctp]
|
||||
[app.common.types.shape :as cts]
|
||||
[app.common.types.shape-tree :as ctst]
|
||||
[app.common.types.shape.interactions :as ctsi]
|
||||
[app.common.uuid :as uuid]
|
||||
[app.main.data.workspace.changes :as dch]
|
||||
[app.main.data.workspace.edition :as dwe]
|
||||
[app.main.data.workspace.selection :as dws]
|
||||
[app.main.data.workspace.shape-layout :as dwsl]
|
||||
[app.main.data.workspace.state-helpers :as wsh]
|
||||
[app.main.features :as features]
|
||||
[app.main.streams :as ms]
|
||||
[app.util.names :as un]
|
||||
[beicon.core :as rx]
|
||||
[cljs.spec.alpha :as s]
|
||||
[potok.core :as ptk]))
|
||||
|
||||
(s/def ::shape-attrs ::spec.shape/shape-attrs)
|
||||
(s/def ::shape-attrs ::cts/shape-attrs)
|
||||
|
||||
(defn get-shape-layer-position
|
||||
[objects selected attrs]
|
||||
|
||||
;; Calculate the frame over which we're drawing
|
||||
(let [position @ms/mouse-position
|
||||
frame-id (:frame-id attrs (cph/frame-id-by-position objects position))
|
||||
frame-id (:frame-id attrs (ctst/frame-id-by-position objects position))
|
||||
shape (when-not (empty? selected)
|
||||
(cph/get-base-shape objects selected))]
|
||||
|
||||
|
@ -52,8 +52,8 @@
|
|||
(defn make-new-shape
|
||||
[attrs objects selected]
|
||||
(let [default-attrs (if (= :frame (:type attrs))
|
||||
cp/default-frame-attrs
|
||||
cp/default-shape-attrs)
|
||||
cts/default-frame-attrs
|
||||
cts/default-shape-attrs)
|
||||
|
||||
selected-non-frames
|
||||
(into #{} (comp (map (d/getf objects))
|
||||
|
@ -84,8 +84,8 @@
|
|||
|
||||
id (or (:id attrs) (uuid/next))
|
||||
name (-> objects
|
||||
(un/retrieve-used-names)
|
||||
(un/generate-unique-name (:name attrs)))
|
||||
(ctst/retrieve-used-names)
|
||||
(ctst/generate-unique-name (:name attrs)))
|
||||
|
||||
shape (make-new-shape
|
||||
(assoc attrs :id id :name name)
|
||||
|
@ -117,7 +117,7 @@
|
|||
to-move-shapes
|
||||
(into []
|
||||
(map (d/getf objects))
|
||||
(reverse (cph/sort-z-index objects shapes)))
|
||||
(reverse (ctst/sort-z-index objects shapes)))
|
||||
|
||||
changes
|
||||
(when (d/not-empty? to-move-shapes)
|
||||
|
@ -138,13 +138,17 @@
|
|||
(ptk/reify ::delete-shapes
|
||||
ptk/WatchEvent
|
||||
(watch [it state _]
|
||||
(let [page-id (:current-page-id state)
|
||||
objects (wsh/lookup-page-objects state page-id)
|
||||
page (wsh/lookup-page state page-id)
|
||||
(let [file-id (:current-file-id state)
|
||||
page-id (:current-page-id state)
|
||||
file (wsh/get-file state file-id)
|
||||
page (wsh/lookup-page state page-id)
|
||||
objects (wsh/lookup-page-objects state page-id)
|
||||
|
||||
ids (cph/clean-loops objects ids)
|
||||
lookup (d/getf objects)
|
||||
|
||||
components-v2 (features/active-feature? state :components-v2)
|
||||
|
||||
groups-to-unmask
|
||||
(reduce (fn [group-ids id]
|
||||
;; When the shape to delete is the mask of a masked group,
|
||||
|
@ -164,7 +168,7 @@
|
|||
;; If any of the deleted shapes is the destination of
|
||||
;; some interaction, this must be deleted, too.
|
||||
(let [interactions (:interactions shape)]
|
||||
(some #(and (csi/has-destination %)
|
||||
(some #(and (ctsi/has-destination %)
|
||||
(contains? ids (:destination %)))
|
||||
interactions)))
|
||||
(vals objects))
|
||||
|
@ -215,9 +219,22 @@
|
|||
;; Any parent whose children are all deleted, must be deleted too.
|
||||
(into (d/ordered-set) (find-all-empty-parents #{}))
|
||||
|
||||
components-to-delete
|
||||
(if components-v2
|
||||
(reduce (fn [components id]
|
||||
(let [shape (get objects id)]
|
||||
(if (and (= (:component-file shape) file-id) ;; Main instances should exist only in local file
|
||||
(:main-instance? shape)) ;; but check anyway
|
||||
(conj components (:component-id shape))
|
||||
components)))
|
||||
[]
|
||||
(into ids all-children))
|
||||
[])
|
||||
|
||||
changes (-> (pcb/empty-changes it page-id)
|
||||
(pcb/with-page page)
|
||||
(pcb/with-objects objects)
|
||||
(pcb/with-library-data file)
|
||||
(pcb/set-page-option :guides guides)
|
||||
(pcb/remove-objects all-children)
|
||||
(pcb/remove-objects ids)
|
||||
|
@ -231,13 +248,18 @@
|
|||
(d/update-when shape :interactions
|
||||
(fn [interactions]
|
||||
(into []
|
||||
(remove #(and (csi/has-destination %)
|
||||
(remove #(and (ctsi/has-destination %)
|
||||
(contains? ids (:destination %))))
|
||||
interactions)))))
|
||||
(cond-> (seq starting-flows)
|
||||
(pcb/update-page-option :flows (fn [flows]
|
||||
(->> (map :id starting-flows)
|
||||
(reduce csp/remove-flow flows))))))]
|
||||
(reduce ctp/remove-flow flows))))))
|
||||
|
||||
changes (reduce (fn [changes component-id]
|
||||
(pcb/delete-component changes component-id))
|
||||
changes
|
||||
components-to-delete)]
|
||||
|
||||
(rx/of (dch/commit-changes changes)
|
||||
(dwsl/update-layout-positions all-parents))))))
|
||||
|
@ -260,10 +282,10 @@
|
|||
y (:y data (- vbc-y (/ height 2)))
|
||||
page-id (:current-page-id state)
|
||||
frame-id (-> (wsh/lookup-page-objects state page-id)
|
||||
(cph/frame-id-by-position {:x frame-x :y frame-y}))
|
||||
shape (-> (cp/make-minimal-shape type)
|
||||
(ctst/frame-id-by-position {:x frame-x :y frame-y}))
|
||||
shape (-> (cts/make-minimal-shape type)
|
||||
(merge data)
|
||||
(merge {:x x :y y})
|
||||
(assoc :frame-id frame-id)
|
||||
(cp/setup-rect-selrect))]
|
||||
(cts/setup-rect-selrect))]
|
||||
(rx/of (add-shape shape))))))
|
||||
|
|
|
@ -11,17 +11,16 @@
|
|||
[app.common.geom.matrix :as gmt]
|
||||
[app.common.geom.point :as gpt]
|
||||
[app.common.geom.shapes :as gsh]
|
||||
[app.common.pages :as cp]
|
||||
[app.common.pages.changes-builder :as pcb]
|
||||
[app.common.pages.helpers :as cph]
|
||||
[app.common.spec :refer [max-safe-int min-safe-int]]
|
||||
[app.common.types.shape :as cts]
|
||||
[app.common.types.shape-tree :as ctt]
|
||||
[app.common.uuid :as uuid]
|
||||
[app.main.data.workspace.changes :as dch]
|
||||
[app.main.data.workspace.selection :as dws]
|
||||
[app.main.data.workspace.shapes :as dwsh]
|
||||
[app.main.data.workspace.state-helpers :as wsh]
|
||||
[app.util.color :as uc]
|
||||
[app.util.names :as un]
|
||||
[app.util.path.parser :as upp]
|
||||
[app.util.svg :as usvg]
|
||||
[beicon.core :as rx]
|
||||
|
@ -183,7 +182,7 @@
|
|||
(assoc :svg-attrs attrs)
|
||||
(assoc :svg-viewbox (-> (select-keys svg-data [:width :height])
|
||||
(assoc :x offset-x :y offset-y)))
|
||||
(cp/setup-rect-selrect))))
|
||||
(cts/setup-rect-selrect))))
|
||||
|
||||
(defn create-svg-root [frame-id svg-data]
|
||||
(let [{:keys [name x y width height offset-x offset-y]} svg-data]
|
||||
|
@ -195,7 +194,7 @@
|
|||
:height height
|
||||
:x (+ x offset-x)
|
||||
:y (+ y offset-y)}
|
||||
(cp/setup-rect-selrect)
|
||||
(cts/setup-rect-selrect)
|
||||
(assoc :svg-attrs (-> (:attrs svg-data)
|
||||
(dissoc :viewBox :xmlns)
|
||||
(d/without-keys usvg/inheritable-props))))))
|
||||
|
@ -215,7 +214,7 @@
|
|||
(assoc :svg-attrs (d/without-keys attrs usvg/inheritable-props))
|
||||
(assoc :svg-viewbox (-> (select-keys svg-data [:width :height])
|
||||
(assoc :x offset-x :y offset-y)))
|
||||
(cp/setup-rect-selrect))))
|
||||
(cts/setup-rect-selrect))))
|
||||
|
||||
(defn create-path-shape [name frame-id svg-data {:keys [attrs] :as data}]
|
||||
(when (and (contains? attrs :d) (seq (:d attrs)))
|
||||
|
@ -360,7 +359,7 @@
|
|||
(let [{:keys [tag attrs hidden]} element-data
|
||||
attrs (usvg/format-styles attrs)
|
||||
element-data (cond-> element-data (map? element-data) (assoc :attrs attrs))
|
||||
name (un/generate-unique-name unames (or (:id attrs) (tag->name tag)))
|
||||
name (ctt/generate-unique-name unames (or (:id attrs) (tag->name tag)))
|
||||
att-refs (usvg/find-attr-references attrs)
|
||||
references (usvg/find-def-references (:defs svg-data) att-refs)
|
||||
|
||||
|
@ -437,17 +436,17 @@
|
|||
(try
|
||||
(let [page-id (:current-page-id state)
|
||||
objects (wsh/lookup-page-objects state page-id)
|
||||
frame-id (cph/frame-id-by-position objects position)
|
||||
frame-id (ctt/frame-id-by-position objects position)
|
||||
selected (wsh/lookup-selected state)
|
||||
|
||||
[vb-x vb-y vb-width vb-height] (svg-dimensions svg-data)
|
||||
x (- x vb-x (/ vb-width 2))
|
||||
y (- y vb-y (/ vb-height 2))
|
||||
|
||||
unames (un/retrieve-used-names objects)
|
||||
unames (ctt/retrieve-used-names objects)
|
||||
|
||||
svg-name (->> (str/replace (:name svg-data) ".svg" "")
|
||||
(un/generate-unique-name unames))
|
||||
(ctt/generate-unique-name unames))
|
||||
|
||||
svg-data (-> svg-data
|
||||
(assoc :x x
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
[app.common.pages.common :as cpc]
|
||||
[app.common.pages.helpers :as cph]
|
||||
[app.common.spec :as us]
|
||||
[app.common.types.shape-tree :as ctt]
|
||||
[app.main.data.workspace.changes :as dch]
|
||||
[app.main.data.workspace.collapse :as dwc]
|
||||
[app.main.data.workspace.guides :as dwg]
|
||||
|
@ -752,7 +753,7 @@
|
|||
(let [position @ms/mouse-position
|
||||
page-id (:current-page-id state)
|
||||
objects (wsh/lookup-page-objects state page-id)
|
||||
frame-id (cph/frame-id-by-position objects position)
|
||||
frame-id (ctt/frame-id-by-position objects position)
|
||||
|
||||
moving-shapes
|
||||
(->> ids
|
||||
|
|
|
@ -4,10 +4,11 @@
|
|||
;;
|
||||
;; Copyright (c) UXBOX Labs SL
|
||||
|
||||
(ns app.main.ui.features
|
||||
(ns app.main.features
|
||||
(:require
|
||||
[app.common.data :as d]
|
||||
[app.common.logging :as log]
|
||||
[app.config :as cfg]
|
||||
[app.main.store :as st]
|
||||
[okulary.core :as l]
|
||||
[potok.core :as ptk]
|
||||
|
@ -15,9 +16,9 @@
|
|||
|
||||
(log/set-level! :debug)
|
||||
|
||||
(def features-list #{:auto-layout})
|
||||
(def features-list #{:auto-layout :components-v2})
|
||||
|
||||
(defn toggle-feature
|
||||
(defn- toggle-feature
|
||||
[feature]
|
||||
(ptk/reify ::toggle-feature
|
||||
ptk/UpdateEvent
|
||||
|
@ -27,7 +28,6 @@
|
|||
:result (if (not (contains? (:features state) feature))
|
||||
"enabled"
|
||||
"disabled"))
|
||||
|
||||
(-> state
|
||||
(update :features
|
||||
(fn [features]
|
||||
|
@ -41,6 +41,13 @@
|
|||
(assert (contains? features-list feature) "Not supported feature")
|
||||
(st/emit! (toggle-feature feature)))
|
||||
|
||||
(defn active-feature?
|
||||
([feature]
|
||||
(active-feature? @st/state feature))
|
||||
([state feature]
|
||||
(assert (contains? features-list feature) "Not supported feature")
|
||||
(contains? (get state :features) feature)))
|
||||
|
||||
(def features
|
||||
(l/derived :features st/state))
|
||||
|
||||
|
@ -55,8 +62,14 @@
|
|||
active-feature? (mf/deref active-feature-ref)]
|
||||
active-feature?))
|
||||
|
||||
;; By default the features are active in local environments
|
||||
(when *assert*
|
||||
;; Activate all features in local environment
|
||||
(doseq [f features-list]
|
||||
(toggle-feature! f)))
|
||||
;; Read initial enabled features from config, if set
|
||||
(if-let [enabled-features @cfg/features]
|
||||
(doseq [f enabled-features]
|
||||
(toggle-feature! f))
|
||||
(when *assert*
|
||||
;; By default, all features disabled, except in development
|
||||
;; environment, that are enabled except components-v2
|
||||
(doseq [f features-list]
|
||||
(when (not= f :components-v2)
|
||||
(toggle-feature! f)))))
|
||||
|
|
@ -10,6 +10,7 @@
|
|||
[app.common.data :as d]
|
||||
[app.common.data.macros :as dm]
|
||||
[app.common.pages.helpers :as cph]
|
||||
[app.common.types.shape-tree :as ctt]
|
||||
[app.main.data.workspace.state-helpers :as wsh]
|
||||
[app.main.store :as st]
|
||||
[okulary.core :as l]))
|
||||
|
@ -284,7 +285,7 @@
|
|||
(l/derived :options workspace-page))
|
||||
|
||||
(def workspace-frames
|
||||
(l/derived cph/get-frames workspace-page-objects =))
|
||||
(l/derived ctt/get-frames workspace-page-objects =))
|
||||
|
||||
(def workspace-editor
|
||||
(l/derived :workspace-editor st/state))
|
||||
|
|
|
@ -21,6 +21,7 @@
|
|||
[app.common.geom.shapes.bounds :as gsb]
|
||||
[app.common.math :as mth]
|
||||
[app.common.pages.helpers :as cph]
|
||||
[app.common.types.shape-tree :as ctst]
|
||||
[app.config :as cfg]
|
||||
[app.main.fonts :as fonts]
|
||||
[app.main.ui.context :as muc]
|
||||
|
@ -61,7 +62,7 @@
|
|||
(defn- calculate-dimensions
|
||||
[objects]
|
||||
(let [bounds
|
||||
(->> (cph/get-root-objects objects)
|
||||
(->> (ctst/get-root-objects objects)
|
||||
(map (partial gsb/get-object-bounds objects))
|
||||
(gsh/join-rects))]
|
||||
(-> bounds
|
||||
|
|
78
frontend/src/app/main/ui/alert.cljs
Normal file
78
frontend/src/app/main/ui/alert.cljs
Normal file
|
@ -0,0 +1,78 @@
|
|||
;; This Source Code Form is subject to the terms of the Mozilla Public
|
||||
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
;;
|
||||
;; Copyright (c) UXBOX Labs SL
|
||||
|
||||
(ns app.main.ui.alert
|
||||
(:require
|
||||
[app.main.data.modal :as modal]
|
||||
[app.main.store :as st]
|
||||
[app.main.ui.icons :as i]
|
||||
[app.util.dom :as dom]
|
||||
[app.util.i18n :as i18n :refer [tr t]]
|
||||
[app.util.keyboard :as k]
|
||||
[goog.events :as events]
|
||||
[rumext.alpha :as mf])
|
||||
(:import goog.events.EventType))
|
||||
|
||||
(mf/defc alert-dialog
|
||||
{::mf/register modal/components
|
||||
::mf/register-as :alert}
|
||||
[{:keys [message
|
||||
scd-message
|
||||
title
|
||||
on-accept
|
||||
hint
|
||||
accept-label
|
||||
accept-style] :as props}]
|
||||
(let [locale (mf/deref i18n/locale)
|
||||
|
||||
on-accept (or on-accept identity)
|
||||
message (or message (t locale "ds.alert-title"))
|
||||
accept-label (or accept-label (tr "ds.alert-ok"))
|
||||
accept-style (or accept-style :danger)
|
||||
title (or title (t locale "ds.alert-title"))
|
||||
|
||||
accept-fn
|
||||
(mf/use-callback
|
||||
(fn [event]
|
||||
(dom/prevent-default event)
|
||||
(st/emit! (modal/hide))
|
||||
(on-accept props)))]
|
||||
|
||||
(mf/with-effect
|
||||
(letfn [(on-keydown [event]
|
||||
(when (k/enter? event)
|
||||
(dom/prevent-default event)
|
||||
(dom/stop-propagation event)
|
||||
(st/emit! (modal/hide))
|
||||
(on-accept props)))]
|
||||
(->> (events/listen js/document EventType.KEYDOWN on-keydown)
|
||||
(partial events/unlistenByKey))))
|
||||
|
||||
[:div.modal-overlay
|
||||
[:div.modal-container.alert-dialog
|
||||
[:div.modal-header
|
||||
[:div.modal-header-title
|
||||
[:h2 title]]
|
||||
[:div.modal-close-button
|
||||
{:on-click accept-fn} i/close]]
|
||||
|
||||
[:div.modal-content
|
||||
(when (and (string? message) (not= message ""))
|
||||
[:h3 message])
|
||||
(when (and (string? scd-message) (not= scd-message ""))
|
||||
[:h3 scd-message])
|
||||
(when (string? hint)
|
||||
[:p hint])]
|
||||
|
||||
[:div.modal-footer
|
||||
[:div.action-buttons
|
||||
[:input.accept-button
|
||||
{:class (dom/classnames
|
||||
:danger (= accept-style :danger)
|
||||
:primary (= accept-style :primary))
|
||||
:type "button"
|
||||
:value accept-label
|
||||
:on-click accept-fn}]]]]]))
|
|
@ -11,7 +11,7 @@
|
|||
|
||||
|
||||
(mf/defc element-icon
|
||||
[{:keys [shape] :as props}]
|
||||
[{:keys [shape main-instance?] :as props}]
|
||||
(case (:type shape)
|
||||
:frame i/artboard
|
||||
:image i/image
|
||||
|
@ -21,7 +21,9 @@
|
|||
:rect i/box
|
||||
:text i/text
|
||||
:group (if (some? (:component-id shape))
|
||||
i/component
|
||||
(if main-instance?
|
||||
i/component
|
||||
i/component-copy)
|
||||
(if (:masked-group? shape)
|
||||
i/mask
|
||||
i/folder))
|
||||
|
|
|
@ -21,6 +21,8 @@
|
|||
(def current-project-id (mf/create-context nil))
|
||||
(def current-page-id (mf/create-context nil))
|
||||
(def current-file-id (mf/create-context nil))
|
||||
(def libraries (mf/create-context nil))
|
||||
(def scroll-ctx (mf/create-context nil))
|
||||
(def active-frames-ctx (mf/create-context nil))
|
||||
(def render-thumbnails (mf/create-context nil))
|
||||
(def render-thumbnails (mf/create-context nil))
|
||||
(def components-v2 (mf/create-context nil))
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
(:require
|
||||
[app.common.data :as d]
|
||||
[app.main.data.modal :as modal]
|
||||
[app.main.features :as features]
|
||||
[app.main.store :as st]
|
||||
[app.main.ui.icons :as i]
|
||||
[app.main.worker :as uw]
|
||||
|
@ -56,6 +57,8 @@
|
|||
:files (->> files (mapv #(assoc % :loading? true)))})
|
||||
selected-option (mf/use-state :all)
|
||||
|
||||
components-v2 (features/use-feature :components-v2)
|
||||
|
||||
start-export
|
||||
(fn []
|
||||
(swap! state assoc :status :exporting)
|
||||
|
@ -64,7 +67,7 @@
|
|||
:team-id team-id
|
||||
:export-type @selected-option
|
||||
:files files
|
||||
})
|
||||
:components-v2 components-v2})
|
||||
(rx/delay-emit 1000)
|
||||
(rx/subs
|
||||
(fn [msg]
|
||||
|
|
|
@ -92,19 +92,26 @@
|
|||
on-delete
|
||||
(fn [event]
|
||||
(dom/stop-propagation event)
|
||||
(if multi?
|
||||
(st/emit! (modal/show
|
||||
{:type :confirm
|
||||
:title (tr "modals.delete-file-multi-confirm.title" file-count)
|
||||
:message (tr "modals.delete-file-multi-confirm.message" file-count)
|
||||
:accept-label (tr "modals.delete-file-multi-confirm.accept" file-count)
|
||||
:on-accept delete-fn}))
|
||||
(st/emit! (modal/show
|
||||
{:type :confirm
|
||||
:title (tr "modals.delete-file-confirm.title")
|
||||
:message (tr "modals.delete-file-confirm.message")
|
||||
:accept-label (tr "modals.delete-file-confirm.accept")
|
||||
:on-accept delete-fn}))))
|
||||
(if (:is-shared file)
|
||||
(do (st/emit! (dd/fetch-library-using-files file))
|
||||
(st/emit! (modal/show
|
||||
{:type :delete-shared
|
||||
:origin :delete
|
||||
:on-accept delete-fn})))
|
||||
|
||||
(if multi?
|
||||
(st/emit! (modal/show
|
||||
{:type :confirm
|
||||
:title (tr "modals.delete-file-multi-confirm.title" file-count)
|
||||
:message (tr "modals.delete-file-multi-confirm.message" file-count)
|
||||
:accept-label (tr "modals.delete-file-multi-confirm.accept" file-count)
|
||||
:on-accept delete-fn}))
|
||||
(st/emit! (modal/show
|
||||
{:type :confirm
|
||||
:title (tr "modals.delete-file-confirm.title")
|
||||
:message (tr "modals.delete-file-confirm.message")
|
||||
:accept-label (tr "modals.delete-file-confirm.accept")
|
||||
:on-accept delete-fn})))))
|
||||
|
||||
on-move-success
|
||||
(fn [team-id project-id]
|
||||
|
@ -148,13 +155,10 @@
|
|||
(fn [event]
|
||||
(dom/prevent-default event)
|
||||
(dom/stop-propagation event)
|
||||
(st/emit! (dd/fetch-library-using-files file))
|
||||
(st/emit! (modal/show
|
||||
{:type :confirm
|
||||
:message ""
|
||||
:title (tr "modals.remove-shared-confirm.message" (:name file))
|
||||
:hint (tr "modals.remove-shared-confirm.hint")
|
||||
:cancel-label :omit
|
||||
:accept-label (tr "modals.remove-shared-confirm.accept")
|
||||
{:type :delete-shared
|
||||
:origin :unpublish
|
||||
:on-accept del-shared})))
|
||||
|
||||
on-export-files
|
||||
|
@ -233,7 +237,7 @@
|
|||
(when (or (seq current-projects) (seq other-teams))
|
||||
[(tr "dashboard.move-to") nil sub-options "file-move-to"])
|
||||
(if (:is-shared file)
|
||||
[(tr "dashboard.remove-shared") on-del-shared nil "file-del-shared"]
|
||||
[(tr "dashboard.unpublish-shared") on-del-shared nil "file-del-shared"]
|
||||
[(tr "dashboard.add-shared") on-add-shared nil "file-add-shared"])
|
||||
[:separator]
|
||||
[(tr "dashboard.download-binary-file") on-export-binary-files nil "download-binary-file"]
|
||||
|
|
|
@ -127,5 +127,6 @@
|
|||
[:section.dashboard-container
|
||||
[:& grid {:project project
|
||||
:files files
|
||||
:on-create-clicked on-create-clicked}]]]))
|
||||
:on-create-clicked on-create-clicked
|
||||
:origin :files}]]]))
|
||||
|
||||
|
|
|
@ -10,6 +10,7 @@
|
|||
[app.common.math :as mth]
|
||||
[app.main.data.dashboard :as dd]
|
||||
[app.main.data.messages :as dm]
|
||||
[app.main.features :as features]
|
||||
[app.main.fonts :as fonts]
|
||||
[app.main.refs :as refs]
|
||||
[app.main.store :as st]
|
||||
|
@ -36,9 +37,11 @@
|
|||
(defn ask-for-thumbnail
|
||||
"Creates some hooks to handle the files thumbnails cache"
|
||||
[file]
|
||||
(wrk/ask! {:cmd :thumbnails/generate
|
||||
:revn (:revn file)
|
||||
:file-id (:id file)}))
|
||||
(let [components-v2 (features/active-feature? :components-v2)]
|
||||
(wrk/ask! {:cmd :thumbnails/generate
|
||||
:revn (:revn file)
|
||||
:file-id (:id file)
|
||||
:components-v2 components-v2})))
|
||||
|
||||
(mf/defc grid-item-thumbnail
|
||||
{::mf/wrap [mf/memo]}
|
||||
|
@ -72,12 +75,13 @@
|
|||
|
||||
(mf/defc grid-item
|
||||
{:wrap [mf/memo]}
|
||||
[{:keys [file navigate?] :as props}]
|
||||
[{:keys [file navigate? origin] :as props}]
|
||||
(let [file-id (:id file)
|
||||
local (mf/use-state {:menu-open false
|
||||
:menu-pos nil
|
||||
:edition false})
|
||||
selected-files (mf/deref refs/dashboard-selected-files)
|
||||
dashboard-local (mf/deref refs/dashboard-local)
|
||||
item-ref (mf/use-ref)
|
||||
menu-ref (mf/use-ref)
|
||||
selected? (contains? selected-files file-id)
|
||||
|
@ -202,10 +206,12 @@
|
|||
:top (:y (:menu-pos @local))
|
||||
:navigate? navigate?
|
||||
:on-edit on-edit
|
||||
:on-menu-close on-menu-close}])]]]))
|
||||
:on-menu-close on-menu-close
|
||||
:origin origin
|
||||
:dashboard-local dashboard-local}])]]]))
|
||||
|
||||
(mf/defc grid
|
||||
[{:keys [files project on-create-clicked] :as props}]
|
||||
[{:keys [files project on-create-clicked origin] :as props}]
|
||||
(let [dragging? (mf/use-state false)
|
||||
project-id (:id project)
|
||||
|
||||
|
@ -265,7 +271,8 @@
|
|||
[:& grid-item
|
||||
{:file item
|
||||
:key (:id item)
|
||||
:navigate? true}])]
|
||||
:navigate? true
|
||||
:origin origin}])]
|
||||
|
||||
:else
|
||||
[:& empty-placeholder {:default? (:is-default project)
|
||||
|
@ -273,7 +280,7 @@
|
|||
:project project}])]))
|
||||
|
||||
(mf/defc line-grid-row
|
||||
[{:keys [files selected-files on-load-more dragging?] :as props}]
|
||||
[{:keys [files selected-files on-load-more dragging? origin] :as props}]
|
||||
(let [rowref (mf/use-ref)
|
||||
|
||||
width (mf/use-state nil)
|
||||
|
@ -319,7 +326,8 @@
|
|||
:file item
|
||||
:selected-files selected-files
|
||||
:key (:id item)
|
||||
:navigate? false}])
|
||||
:navigate? false
|
||||
:origin origin}])
|
||||
(when (and (> limit 0)
|
||||
(> (count files) limit))
|
||||
[:div.grid-item.placeholder {:on-click on-load-more}
|
||||
|
@ -328,7 +336,7 @@
|
|||
(tr "dashboard.show-all-files")]])]))
|
||||
|
||||
(mf/defc line-grid
|
||||
[{:keys [project team files on-load-more on-create-clicked] :as props}]
|
||||
[{:keys [project team files on-load-more on-create-clicked origin] :as props}]
|
||||
(let [dragging? (mf/use-state false)
|
||||
project-id (:id project)
|
||||
team-id (:id team)
|
||||
|
@ -412,7 +420,8 @@
|
|||
:team-id team-id
|
||||
:selected-files selected-files
|
||||
:on-load-more on-load-more
|
||||
:dragging? @dragging?}]
|
||||
:dragging? @dragging?
|
||||
:origin origin}]
|
||||
|
||||
:else
|
||||
[:& empty-placeholder {:dragging? @dragging?
|
||||
|
|
|
@ -42,5 +42,6 @@
|
|||
[:h1 (tr "dashboard.libraries-title")]]]
|
||||
[:section.dashboard-container
|
||||
[:& grid {:files files
|
||||
:project default-project}]]]))
|
||||
:project default-project
|
||||
:origin :libraries}]]]))
|
||||
|
||||
|
|
|
@ -153,7 +153,8 @@
|
|||
:team team
|
||||
:on-load-more on-nav
|
||||
:files files
|
||||
:on-create-clicked (partial create-file "dashboard:empty-folder-placeholder")}]]))
|
||||
:on-create-clicked (partial create-file "dashboard:empty-folder-placeholder")
|
||||
:origin :project}]]))
|
||||
|
||||
(def recent-files-ref
|
||||
(l/derived :dashboard-recent-files st/state))
|
||||
|
|
117
frontend/src/app/main/ui/delete_shared.cljs
Normal file
117
frontend/src/app/main/ui/delete_shared.cljs
Normal file
|
@ -0,0 +1,117 @@
|
|||
;; This Source Code Form is subject to the terms of the Mozilla Public
|
||||
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
;;
|
||||
;; Copyright (c) UXBOX Labs SL
|
||||
|
||||
(ns app.main.ui.delete-shared
|
||||
(:require
|
||||
[app.main.data.dashboard :as dd]
|
||||
[app.main.data.modal :as modal]
|
||||
[app.main.refs :as refs]
|
||||
[app.main.store :as st]
|
||||
[app.main.ui.icons :as i]
|
||||
[app.util.dom :as dom]
|
||||
[app.util.i18n :as i18n :refer [tr]]
|
||||
[app.util.keyboard :as k]
|
||||
[goog.events :as events]
|
||||
[rumext.alpha :as mf])
|
||||
(:import goog.events.EventType))
|
||||
|
||||
(mf/defc delete-shared-dialog
|
||||
{::mf/register modal/components
|
||||
::mf/register-as :delete-shared}
|
||||
[{:keys [on-accept
|
||||
on-cancel
|
||||
accept-style
|
||||
origin] :as props}]
|
||||
(let [on-accept (or on-accept identity)
|
||||
on-cancel (or on-cancel identity)
|
||||
cancel-label (tr "labels.cancel")
|
||||
accept-style (or accept-style :danger)
|
||||
is-delete? (= origin :delete)
|
||||
dashboard-local (mf/deref refs/dashboard-local)
|
||||
files->shared (:files-with-shared dashboard-local)
|
||||
count-files (count (keys files->shared))
|
||||
title (if is-delete?
|
||||
(tr "modals.delete-shared-confirm.title")
|
||||
(tr "modals.unpublish-shared-confirm.title"))
|
||||
message (if is-delete?
|
||||
(tr "modals.delete-shared-confirm.message")
|
||||
(tr "modals.unpublish-shared-confirm.message"))
|
||||
|
||||
accept-label (if is-delete?
|
||||
(tr "modals.delete-shared-confirm.accept")
|
||||
(tr "modals.unpublish-shared-confirm.accept"))
|
||||
scd-message (if is-delete?
|
||||
(tr "modals.delete-shared-confirm.scd-message" (i18n/c count-files))
|
||||
(tr "modals.unpublish-shared-confirm.scd-message" (i18n/c count-files)))
|
||||
hint (if is-delete?
|
||||
""
|
||||
(tr "modals.unpublish-shared-confirm.hint" (i18n/c count-files)))
|
||||
|
||||
accept-fn
|
||||
(mf/use-callback
|
||||
(fn [event]
|
||||
(dom/prevent-default event)
|
||||
(st/emit! (modal/hide))
|
||||
(on-accept props)))
|
||||
|
||||
cancel-fn
|
||||
(mf/use-callback
|
||||
(fn [event]
|
||||
(dom/prevent-default event)
|
||||
(st/emit! (modal/hide))
|
||||
(on-cancel props)))]
|
||||
|
||||
(mf/with-effect
|
||||
(letfn [(on-keydown [event]
|
||||
(when (k/enter? event)
|
||||
(dom/prevent-default event)
|
||||
(dom/stop-propagation event)
|
||||
(st/emit! (modal/hide))
|
||||
(on-accept props)))]
|
||||
(->> (events/listen js/document EventType.KEYDOWN on-keydown)
|
||||
(partial events/unlistenByKey)))
|
||||
#(st/emit! (dd/clean-temp-shared)))
|
||||
|
||||
[:div.modal-overlay
|
||||
[:div.modal-container.confirm-dialog
|
||||
[:div.modal-header
|
||||
[:div.modal-header-title
|
||||
[:h2 title]]
|
||||
[:div.modal-close-button
|
||||
{:on-click cancel-fn} i/close]]
|
||||
|
||||
[:div.modal-content.delete-shared
|
||||
(when (and (string? message) (not= message ""))
|
||||
[:h3 message])
|
||||
|
||||
(when (> (count files->shared) 0)
|
||||
[:*
|
||||
[:div
|
||||
(when (and (string? scd-message) (not= scd-message ""))
|
||||
[:h3 scd-message])
|
||||
[:ul.file-list
|
||||
(for [[id file] files->shared]
|
||||
[:li.modal-item-element
|
||||
{:key id}
|
||||
[:span "- " (:name file)]])]]
|
||||
(when (and (string? hint) (not= hint ""))
|
||||
[:h3 hint])])]
|
||||
|
||||
[:div.modal-footer
|
||||
[:div.action-buttons
|
||||
(when-not (= cancel-label :omit)
|
||||
[:input.cancel-button
|
||||
{:type "button"
|
||||
:value cancel-label
|
||||
:on-click cancel-fn}])
|
||||
|
||||
[:input.accept-button
|
||||
{:class (dom/classnames
|
||||
:danger (= accept-style :danger)
|
||||
:primary (= accept-style :primary))
|
||||
:type "button"
|
||||
:value accept-label
|
||||
:on-click accept-fn}]]]]]))
|
|
@ -53,6 +53,7 @@
|
|||
(def close (icon-xref :close))
|
||||
(def code (icon-xref :code))
|
||||
(def component (icon-xref :component))
|
||||
(def component-copy (icon-xref :component-copy))
|
||||
(def copy (icon-xref :copy))
|
||||
(def curve (icon-xref :curve))
|
||||
(def cross (icon-xref :cross))
|
||||
|
|
|
@ -11,6 +11,7 @@
|
|||
[app.main.data.messages :as msg]
|
||||
[app.main.data.workspace :as dw]
|
||||
[app.main.data.workspace.persistence :as dwp]
|
||||
[app.main.features :as features]
|
||||
[app.main.refs :as refs]
|
||||
[app.main.store :as st]
|
||||
[app.main.ui.context :as ctx]
|
||||
|
@ -119,6 +120,8 @@
|
|||
layout (mf/deref refs/workspace-layout)
|
||||
wglobal (mf/deref refs/workspace-global)
|
||||
|
||||
components-v2 (features/use-feature :components-v2)
|
||||
|
||||
background-color (:background-color wglobal)]
|
||||
|
||||
;; Setting the layout preset by its name
|
||||
|
@ -145,23 +148,22 @@
|
|||
[:& (mf/provider ctx/current-team-id) {:value (:team-id project)}
|
||||
[:& (mf/provider ctx/current-project-id) {:value (:id project)}
|
||||
[:& (mf/provider ctx/current-page-id) {:value page-id}
|
||||
[:section#workspace {:style {:background-color background-color}}
|
||||
(when (not (:hide-ui layout))
|
||||
[:& header {:file file
|
||||
:page-id page-id
|
||||
:project project
|
||||
:layout layout}])
|
||||
|
||||
[:& context-menu]
|
||||
|
||||
(if (and (and file project)
|
||||
(:initialized file))
|
||||
[:& workspace-page {:key (dm/str "page-" page-id)
|
||||
:page-id page-id
|
||||
:file file
|
||||
:wglobal wglobal
|
||||
:layout layout}]
|
||||
[:& workspace-loader])]]]]]))
|
||||
[:& (mf/provider ctx/components-v2) {:value components-v2}
|
||||
[:section#workspace {:style {:background-color background-color}}
|
||||
(when (not (:hide-ui layout))
|
||||
[:& header {:file file
|
||||
:page-id page-id
|
||||
:project project
|
||||
:layout layout}])
|
||||
|
||||
[:& context-menu]
|
||||
|
||||
(if (and (and file project)
|
||||
(:initialized file))
|
||||
[:& workspace-page {:key (dm/str "page-" page-id)
|
||||
:page-id page-id
|
||||
:file file
|
||||
:wglobal wglobal
|
||||
:layout layout}]
|
||||
[:& workspace-loader])]]]]]]))
|
||||
|
||||
|
|
|
@ -188,7 +188,6 @@
|
|||
(add-group % group-name)))))
|
||||
(st/emit! (dwu/commit-undo-transaction)))
|
||||
|
||||
|
||||
(defn- on-drop-asset
|
||||
[event asset dragging? selected-assets selected-assets-full selected-assets-paths rename]
|
||||
(let [create-typed-assets-group (partial create-assets-group rename)]
|
||||
|
@ -568,7 +567,7 @@
|
|||
(on-assets-delete)
|
||||
(st/emit! (dwu/start-undo-transaction)
|
||||
(dwl/delete-component {:id (:component-id @state)})
|
||||
(dwl/sync-file file-id file-id)
|
||||
(dwl/sync-file file-id file-id :components (:component-id @state))
|
||||
(dwu/commit-undo-transaction)))))
|
||||
|
||||
on-rename
|
||||
|
@ -1120,7 +1119,7 @@
|
|||
(on-assets-delete)
|
||||
(st/emit! (dwu/start-undo-transaction)
|
||||
(dwl/delete-color color)
|
||||
(dwl/sync-file file-id file-id)
|
||||
(dwl/sync-file file-id file-id :color (:id color))
|
||||
(dwu/commit-undo-transaction)))))
|
||||
|
||||
rename-color-clicked
|
||||
|
@ -1762,7 +1761,7 @@
|
|||
(on-assets-delete)
|
||||
(st/emit! (dwu/start-undo-transaction)
|
||||
(dwl/delete-typography (:id @state))
|
||||
(dwl/sync-file file-id file-id)
|
||||
(dwl/sync-file file-id file-id :typographies (:id @state))
|
||||
(dwu/commit-undo-transaction)))))
|
||||
|
||||
editing-id (or (:rename-typography local-data)
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
[app.main.refs :as refs]
|
||||
[app.main.store :as st]
|
||||
[app.main.ui.components.shape-icon :as si]
|
||||
[app.main.ui.context :as ctx]
|
||||
[app.main.ui.hooks :as hooks]
|
||||
[app.main.ui.icons :as i]
|
||||
[app.util.dom :as dom]
|
||||
|
@ -101,6 +102,11 @@
|
|||
container? (or (cph/frame-shape? item)
|
||||
(cph/group-shape? item))
|
||||
|
||||
components-v2 (mf/use-ctx ctx/components-v2)
|
||||
main-instance? (if components-v2
|
||||
(:main-instance? item)
|
||||
true)
|
||||
|
||||
toggle-collapse
|
||||
(mf/use-fn
|
||||
(mf/deps expanded?)
|
||||
|
@ -244,7 +250,8 @@
|
|||
[:div {:on-double-click #(do (dom/stop-propagation %)
|
||||
(dom/prevent-default %)
|
||||
(st/emit! dw/zoom-to-selected-shape))}
|
||||
[:& si/element-icon {:shape item}]]
|
||||
[:& si/element-icon {:shape item
|
||||
:main-instance? main-instance?}]]
|
||||
[:& layer-name {:shape item
|
||||
:name-ref ref
|
||||
:on-start-edit #(reset! disable-drag true)
|
||||
|
@ -444,7 +451,6 @@
|
|||
(take (:num-items @filter-state))
|
||||
filtered-objects-total))))
|
||||
|
||||
|
||||
handle-show-more
|
||||
(fn []
|
||||
(when (<= (:num-items @filter-state) (count filtered-objects-total))
|
||||
|
@ -542,7 +548,6 @@
|
|||
(when last-hidden-frame
|
||||
(dom/add-class! last-hidden-frame "sticky"))))]
|
||||
|
||||
|
||||
[:div#layers.tool-window
|
||||
(if (d/not-empty? focus)
|
||||
[:div.tool-window-bar
|
||||
|
|
|
@ -6,22 +6,23 @@
|
|||
|
||||
(ns app.main.ui.workspace.sidebar.options.menus.component
|
||||
(:require
|
||||
[app.main.data.modal :as modal]
|
||||
[app.main.data.workspace :as dw]
|
||||
[app.main.data.workspace.libraries :as dwl]
|
||||
[app.main.store :as st]
|
||||
[app.main.ui.components.context-menu :refer [context-menu]]
|
||||
[app.main.ui.context :as ctx]
|
||||
[app.main.ui.icons :as i]
|
||||
[app.util.dom :as dom]
|
||||
[app.util.i18n :as i18n :refer [tr]]
|
||||
[rumext.alpha :as mf]))
|
||||
[app.main.data.modal :as modal]
|
||||
[app.main.data.workspace :as dw]
|
||||
[app.main.data.workspace.libraries :as dwl]
|
||||
[app.main.store :as st]
|
||||
[app.main.ui.components.context-menu :refer [context-menu]]
|
||||
[app.main.ui.context :as ctx]
|
||||
[app.main.ui.icons :as i]
|
||||
[app.util.dom :as dom]
|
||||
[app.util.i18n :as i18n :refer [tr]]
|
||||
[rumext.alpha :as mf]))
|
||||
|
||||
(def component-attrs [:component-id :component-file :shape-ref])
|
||||
(def component-attrs [:component-id :component-file :shape-ref :main-instance?])
|
||||
|
||||
(mf/defc component-menu
|
||||
[{:keys [ids values shape-name] :as props}]
|
||||
(let [current-file-id (mf/use-ctx ctx/current-file-id)
|
||||
components-v2 (mf/use-ctx ctx/components-v2)
|
||||
|
||||
id (first ids)
|
||||
local (mf/use-state {:menu-open false})
|
||||
|
@ -29,6 +30,9 @@
|
|||
component-id (:component-id values)
|
||||
library-id (:component-file values)
|
||||
show? (some? component-id)
|
||||
main-instance? (if components-v2
|
||||
(:main-instance? values)
|
||||
true)
|
||||
|
||||
on-menu-click
|
||||
(mf/use-callback
|
||||
|
@ -69,7 +73,9 @@
|
|||
[:span (tr "workspace.options.component")]]
|
||||
[:div.element-set-content
|
||||
[:div.row-flex.component-row
|
||||
i/component
|
||||
(if main-instance?
|
||||
i/component
|
||||
i/component-copy)
|
||||
shape-name
|
||||
[:div.row-actions
|
||||
{:on-click on-menu-click}
|
||||
|
|
|
@ -10,6 +10,7 @@
|
|||
[app.common.data.macros :as dm]
|
||||
[app.common.pages.helpers :as cph]
|
||||
[app.common.types.page :as ctp]
|
||||
[app.common.types.shape-tree :as ctt]
|
||||
[app.common.types.shape.interactions :as ctsi]
|
||||
[app.common.uuid :as uuid]
|
||||
[app.main.data.workspace :as dw]
|
||||
|
@ -182,7 +183,7 @@
|
|||
(let [objects (deref refs/workspace-page-objects)
|
||||
destination (get objects (:destination interaction))
|
||||
|
||||
frames (mf/with-memo [objects] (cph/get-viewer-frames objects {:all-frames? (not= :navigate (:action-type interaction))}))
|
||||
frames (mf/with-memo [objects] (ctt/get-viewer-frames objects {:all-frames? (not= :navigate (:action-type interaction))}))
|
||||
|
||||
overlay-pos-type (:overlay-pos-type interaction)
|
||||
close-click-outside? (:close-click-outside interaction false)
|
||||
|
|
|
@ -6,8 +6,8 @@
|
|||
|
||||
(ns app.main.ui.workspace.sidebar.options.shapes.frame
|
||||
(:require
|
||||
[app.main.features :as features]
|
||||
[app.main.refs :as refs]
|
||||
[app.main.ui.features :as features]
|
||||
[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-attrs-shape fill-menu]]
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
[app.common.data :as d]
|
||||
[app.common.geom.shapes :as gsh]
|
||||
[app.common.math :as mth]
|
||||
[app.common.pages.helpers :as cph]
|
||||
[app.common.types.shape-tree :as ctst]
|
||||
[app.common.uuid :as uuid]
|
||||
[app.main.refs :as refs]
|
||||
[app.util.geom.grid :as gg]
|
||||
|
@ -134,7 +134,7 @@
|
|||
[:g.grid-display {:style {:pointer-events "none"}}
|
||||
(for [frame frames]
|
||||
(when (and (not (is-transform? frame))
|
||||
(not (cph/rotated-frame? frame))
|
||||
(not (ctst/rotated-frame? frame))
|
||||
(or (empty? focus) (contains? focus (:id frame))))
|
||||
[:& grid-display-frame {:key (str "grid-" (:id frame))
|
||||
:zoom zoom
|
||||
|
|
|
@ -11,6 +11,7 @@
|
|||
[app.common.geom.shapes :as gsh]
|
||||
[app.common.math :as mth]
|
||||
[app.common.pages.helpers :as cph]
|
||||
[app.common.types.shape-tree :as ctst]
|
||||
[app.common.uuid :as uuid]
|
||||
[app.main.data.workspace :as dw]
|
||||
[app.main.refs :as refs]
|
||||
|
@ -292,7 +293,7 @@
|
|||
|
||||
(when (or (nil? frame)
|
||||
(and (cph/root-frame? frame)
|
||||
(not (cph/rotated-frame? frame))))
|
||||
(not (ctst/rotated-frame? frame))))
|
||||
[:g.guide-area {:opacity (when frame-guide-outside? 0)}
|
||||
(when-not disabled-guides?
|
||||
(let [{:keys [x y width height]} (guide-area-axis pos vbox zoom frame axis)]
|
||||
|
|
|
@ -10,6 +10,7 @@
|
|||
[app.common.geom.shapes :as gsh]
|
||||
[app.common.pages :as cp]
|
||||
[app.common.pages.helpers :as cph]
|
||||
[app.common.types.shape-tree :as ctt]
|
||||
[app.main.data.shortcuts :as dsc]
|
||||
[app.main.data.workspace :as dw]
|
||||
[app.main.data.workspace.path.shortcuts :as psc]
|
||||
|
@ -183,7 +184,7 @@
|
|||
|
||||
ids (into
|
||||
(d/ordered-set)
|
||||
(cph/sort-z-index objects ids {:bottom-frames? mod?}))
|
||||
(ctt/sort-z-index objects ids {:bottom-frames? mod?}))
|
||||
|
||||
grouped? (fn [id] (contains? #{:group :bool} (get-in objects [id :type])))
|
||||
|
||||
|
@ -218,7 +219,7 @@
|
|||
(let [root-frame-ids
|
||||
(mf/use-memo
|
||||
(mf/deps objects)
|
||||
#(cph/get-root-shapes-ids objects))
|
||||
#(ctt/get-root-shapes-ids objects))
|
||||
modifiers (select-keys modifiers root-frame-ids)]
|
||||
(sfd/use-dynamic-modifiers objects globals/document modifiers)))
|
||||
|
||||
|
@ -229,7 +230,7 @@
|
|||
(defn setup-active-frames
|
||||
[objects hover-ids selected active-frames zoom transform vbox]
|
||||
|
||||
(let [all-frames (mf/use-memo (mf/deps objects) #(cph/get-root-frames-ids objects))
|
||||
(let [all-frames (mf/use-memo (mf/deps objects) #(ctt/get-root-frames-ids objects))
|
||||
selected-frames (mf/use-memo (mf/deps selected) #(->> all-frames (filter selected)))
|
||||
|
||||
xf-selected-frame (comp (remove cph/root-frame?)
|
||||
|
|
|
@ -11,7 +11,7 @@
|
|||
[app.common.geom.matrix :as gmt]
|
||||
[app.common.geom.point :as gpt]
|
||||
[app.common.geom.shapes :as gsh]
|
||||
[app.common.pages :as cp]
|
||||
[app.common.types.shape :as cts]
|
||||
[app.main.data.workspace :as dw]
|
||||
[app.main.refs :as refs]
|
||||
[app.main.store :as st]
|
||||
|
@ -343,7 +343,7 @@
|
|||
#(->> shapes
|
||||
(map gsh/transform-shape)
|
||||
(gsh/selection-rect)
|
||||
(cp/setup-shape)))
|
||||
(cts/setup-shape)))
|
||||
on-resize
|
||||
(fn [current-position _initial-position event]
|
||||
(when (dom/left-mouse? event)
|
||||
|
@ -371,7 +371,7 @@
|
|||
#(->> shapes
|
||||
(map gsh/transform-shape)
|
||||
(gsh/selection-rect)
|
||||
(cp/setup-shape)))]
|
||||
(cts/setup-shape)))]
|
||||
|
||||
[:& controls-selection
|
||||
{:shape shape
|
||||
|
|
|
@ -10,7 +10,7 @@
|
|||
[app.common.data.macros :as dm]
|
||||
[app.common.geom.point :as gpt]
|
||||
[app.common.geom.shapes :as gsh]
|
||||
[app.common.pages.helpers :as cph]
|
||||
[app.common.types.shape-tree :as ctt]
|
||||
[app.common.uuid :as uuid]
|
||||
[app.main.data.workspace :as dw]
|
||||
[app.main.data.workspace.interactions :as dwi]
|
||||
|
@ -178,7 +178,7 @@
|
|||
on-frame-enter (unchecked-get props "on-frame-enter")
|
||||
on-frame-leave (unchecked-get props "on-frame-leave")
|
||||
on-frame-select (unchecked-get props "on-frame-select")
|
||||
frames (cph/get-frames objects)]
|
||||
frames (ctt/get-frames objects)]
|
||||
|
||||
[:g.frame-titles
|
||||
(for [frame frames]
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
[app.common.uri :as u]
|
||||
[app.config :as cf]
|
||||
[app.main.data.fonts :as df]
|
||||
[app.main.features :as features]
|
||||
[app.main.render :as render]
|
||||
[app.main.repo :as repo]
|
||||
[app.main.store :as st]
|
||||
|
@ -99,22 +100,24 @@
|
|||
|
||||
(mf/defc object-svg
|
||||
[{:keys [page-id file-id object-id render-embed?]}]
|
||||
(let [fetch-state (mf/use-fn
|
||||
(mf/deps file-id page-id object-id)
|
||||
(fn []
|
||||
(->> (rx/zip
|
||||
(repo/query! :font-variants {:file-id file-id})
|
||||
(repo/query! :page {:file-id file-id
|
||||
:page-id page-id
|
||||
:object-id object-id}))
|
||||
(rx/tap (fn [[fonts]]
|
||||
(when (seq fonts)
|
||||
(st/emit! (df/fonts-fetched fonts)))))
|
||||
(rx/map (comp :objects second))
|
||||
(rx/map (fn [objects]
|
||||
(let [objects (render/adapt-objects-for-shape objects object-id)]
|
||||
{:objects objects
|
||||
:object (get objects object-id)}))))))
|
||||
(let [components-v2 (features/use-feature :components-v2)
|
||||
fetch-state (mf/use-fn
|
||||
(mf/deps file-id page-id object-id)
|
||||
(fn []
|
||||
(->> (rx/zip
|
||||
(repo/query! :font-variants {:file-id file-id})
|
||||
(repo/query! :page {:file-id file-id
|
||||
:page-id page-id
|
||||
:object-id object-id
|
||||
:components-v2 components-v2}))
|
||||
(rx/tap (fn [[fonts]]
|
||||
(when (seq fonts)
|
||||
(st/emit! (df/fonts-fetched fonts)))))
|
||||
(rx/map (comp :objects second))
|
||||
(rx/map (fn [objects]
|
||||
(let [objects (render/adapt-objects-for-shape objects object-id)]
|
||||
{:objects objects
|
||||
:object (get objects object-id)}))))))
|
||||
|
||||
{:keys [objects object]} (use-resource fetch-state)]
|
||||
|
||||
|
@ -124,8 +127,8 @@
|
|||
(when object
|
||||
(dom/set-page-style!
|
||||
{:size (str/concat
|
||||
(mth/ceil (:width object)) "px "
|
||||
(mth/ceil (:height object)) "px")})))
|
||||
(mth/ceil (:width object)) "px "
|
||||
(mth/ceil (:height object)) "px")})))
|
||||
|
||||
(when objects
|
||||
[:& render/object-svg
|
||||
|
@ -135,17 +138,19 @@
|
|||
|
||||
(mf/defc objects-svg
|
||||
[{:keys [page-id file-id object-ids render-embed?]}]
|
||||
(let [fetch-state (mf/use-fn
|
||||
(mf/deps file-id page-id)
|
||||
(fn []
|
||||
(->> (rx/zip
|
||||
(repo/query! :font-variants {:file-id file-id})
|
||||
(repo/query! :page {:file-id file-id
|
||||
:page-id page-id}))
|
||||
(rx/tap (fn [[fonts]]
|
||||
(when (seq fonts)
|
||||
(st/emit! (df/fonts-fetched fonts)))))
|
||||
(rx/map (comp :objects second)))))
|
||||
(let [components-v2 (features/use-feature :components-v2)
|
||||
fetch-state (mf/use-fn
|
||||
(mf/deps file-id page-id)
|
||||
(fn []
|
||||
(->> (rx/zip
|
||||
(repo/query! :font-variants {:file-id file-id})
|
||||
(repo/query! :page {:file-id file-id
|
||||
:page-id page-id
|
||||
:components-v2 components-v2}))
|
||||
(rx/tap (fn [[fonts]]
|
||||
(when (seq fonts)
|
||||
(st/emit! (df/fonts-fetched fonts)))))
|
||||
(rx/map (comp :objects second)))))
|
||||
|
||||
objects (use-resource fetch-state)]
|
||||
|
||||
|
|
|
@ -8,7 +8,8 @@
|
|||
(:require
|
||||
[app.common.geom.point :as gpt]
|
||||
[app.common.geom.shapes :as gsh]
|
||||
[app.common.pages.helpers :as cph]))
|
||||
[app.common.pages.helpers :as cph]
|
||||
[app.common.types.shape-tree :as ctst]))
|
||||
|
||||
(defn selrect-snap-points [{:keys [x y width height] :as selrect}]
|
||||
#{(gpt/point x y)
|
||||
|
@ -38,7 +39,7 @@
|
|||
|
||||
(cond
|
||||
(and (some? frame)
|
||||
(not (cph/rotated-frame? frame))
|
||||
(not (ctst/rotated-frame? frame))
|
||||
(not (cph/root-frame? frame)))
|
||||
#{}
|
||||
|
||||
|
|
|
@ -1,38 +0,0 @@
|
|||
;; This Source Code Form is subject to the terms of the Mozilla Public
|
||||
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
;;
|
||||
;; Copyright (c) UXBOX Labs SL
|
||||
|
||||
(ns app.util.names
|
||||
(:require
|
||||
[app.common.data :as d]
|
||||
[app.common.spec :as us]
|
||||
[cljs.spec.alpha :as s]))
|
||||
|
||||
(s/def ::set-of-string (s/every string? :kind set?))
|
||||
|
||||
(defn- extract-numeric-suffix
|
||||
[basename]
|
||||
(if-let [[_ p1 p2] (re-find #"(.*)-([0-9]+)$" basename)]
|
||||
[p1 (+ 1 (d/parse-integer p2))]
|
||||
[basename 1]))
|
||||
|
||||
(defn retrieve-used-names
|
||||
[objects]
|
||||
(into #{} (comp (map :name) (remove nil?)) (vals objects)))
|
||||
|
||||
(defn generate-unique-name
|
||||
"A unique name generator"
|
||||
[used basename]
|
||||
(s/assert ::set-of-string used)
|
||||
(s/assert ::us/string basename)
|
||||
(if-not (contains? used basename)
|
||||
basename
|
||||
(let [[prefix initial] (extract-numeric-suffix basename)]
|
||||
(loop [counter initial]
|
||||
(let [candidate (str prefix "-" counter)]
|
||||
(if (contains? used candidate)
|
||||
(recur (inc counter))
|
||||
candidate))))))
|
||||
|
|
@ -12,6 +12,7 @@
|
|||
[app.common.data :as d]
|
||||
[app.common.pages.diff :as diff]
|
||||
[app.common.pages.helpers :as cph]
|
||||
[app.common.types.shape-tree :as ctst]
|
||||
[app.common.uuid :as uuid]
|
||||
[app.util.geom.grid :as gg]
|
||||
[app.util.geom.snap-points :as snap]
|
||||
|
@ -55,7 +56,7 @@
|
|||
|
||||
(defn get-grids-snap-points
|
||||
[frame coord]
|
||||
(if (not (cph/rotated-frame? frame))
|
||||
(if (not (ctst/rotated-frame? frame))
|
||||
[]
|
||||
(let [grid->snap (fn [[grid-type position]]
|
||||
{:type :layout
|
||||
|
@ -196,7 +197,7 @@
|
|||
(defn add-page
|
||||
"Adds page information"
|
||||
[snap-data {:keys [objects options] :as page}]
|
||||
(let [frames (cph/get-frames objects)
|
||||
(let [frames (ctst/get-frames objects)
|
||||
shapes (->> (vals (:objects page))
|
||||
(remove cph/frame-shape?))
|
||||
guides (vals (:guides options))
|
||||
|
|
|
@ -149,8 +149,8 @@
|
|||
(->> (r/render-components (:data file))
|
||||
(rx/map #(vector (str (:id file) "/components.svg") %))))
|
||||
|
||||
(defn fetch-file-with-libraries [file-id]
|
||||
(->> (rx/zip (rp/query :file {:id file-id})
|
||||
(defn fetch-file-with-libraries [file-id components-v2]
|
||||
(->> (rx/zip (rp/query :file {:id file-id :components-v2 components-v2})
|
||||
(rp/query :file-libraries {:file-id file-id}))
|
||||
(rx/map
|
||||
(fn [[file file-libraries]]
|
||||
|
@ -351,7 +351,7 @@
|
|||
(update file-id dissoc :libraries))))
|
||||
|
||||
(defn collect-files
|
||||
[file-id export-type]
|
||||
[file-id export-type components-v2]
|
||||
|
||||
(letfn [(fetch-dependencies [[files pending]]
|
||||
(if (empty? pending)
|
||||
|
@ -365,7 +365,7 @@
|
|||
;; The file is already in the result
|
||||
(rx/of [files pending])
|
||||
|
||||
(->> (fetch-file-with-libraries next)
|
||||
(->> (fetch-file-with-libraries next components-v2)
|
||||
(rx/map
|
||||
(fn [file]
|
||||
[(-> files
|
||||
|
@ -381,9 +381,9 @@
|
|||
(rx/map #(process-export file-id export-type %))))))
|
||||
|
||||
(defn export-file
|
||||
[team-id file-id export-type]
|
||||
[team-id file-id export-type components-v2]
|
||||
|
||||
(let [files-stream (->> (collect-files file-id export-type)
|
||||
(let [files-stream (->> (collect-files file-id export-type components-v2)
|
||||
(rx/share))
|
||||
|
||||
manifest-stream
|
||||
|
@ -471,12 +471,12 @@
|
|||
:file-id (:id file)}))))))))
|
||||
|
||||
(defmethod impl/handler :export-standard-file
|
||||
[{:keys [team-id files export-type] :as message}]
|
||||
[{:keys [team-id files export-type components-v2] :as message}]
|
||||
|
||||
(->> (rx/from files)
|
||||
(rx/mapcat
|
||||
(fn [file]
|
||||
(->> (export-file team-id (:id file) export-type)
|
||||
(->> (export-file team-id (:id file) export-type components-v2)
|
||||
(rx/map
|
||||
(fn [value]
|
||||
(if (contains? value :type)
|
||||
|
|
|
@ -14,8 +14,8 @@
|
|||
[app.common.geom.shapes.path :as gpa]
|
||||
[app.common.logging :as log]
|
||||
[app.common.media :as cm]
|
||||
[app.common.pages :as cp]
|
||||
[app.common.text :as ct]
|
||||
[app.common.types.file :as ctf]
|
||||
[app.common.uuid :as uuid]
|
||||
[app.main.repo :as rp]
|
||||
[app.util.http :as http]
|
||||
|
@ -133,7 +133,7 @@
|
|||
:name (:name context)
|
||||
:is-shared (:shared context)
|
||||
:project-id (:project-id context)
|
||||
:data (-> cp/empty-file-data (assoc :id file-id))})))
|
||||
:data (-> ctf/empty-file-data (assoc :id file-id))})))
|
||||
|
||||
(defn link-file-libraries
|
||||
"Create a new file on the back-end"
|
||||
|
|
|
@ -48,11 +48,12 @@
|
|||
(= :request-body-too-large code)))
|
||||
|
||||
(defn- request-data-for-thumbnail
|
||||
[file-id revn]
|
||||
[file-id revn components-v2]
|
||||
(let [path "api/rpc/query/file-data-for-thumbnail"
|
||||
params {:file-id file-id
|
||||
:revn revn
|
||||
:strip-frames-with-thumbnails true}
|
||||
:strip-frames-with-thumbnails true
|
||||
:components-v2 components-v2}
|
||||
request {:method :get
|
||||
:uri (u/join (cfg/get-public-uri) path)
|
||||
:credentials "include"
|
||||
|
@ -107,18 +108,18 @@
|
|||
(rx/map (constantly params)))))
|
||||
|
||||
(defmethod impl/handler :thumbnails/generate
|
||||
[{:keys [file-id revn] :as message}]
|
||||
[{:keys [file-id revn components-v2] :as message}]
|
||||
(letfn [(on-result [{:keys [data props]}]
|
||||
{:data data
|
||||
:fonts (:fonts props)})
|
||||
|
||||
(on-cache-miss [_]
|
||||
(->> (request-data-for-thumbnail file-id revn)
|
||||
(->> (request-data-for-thumbnail file-id revn components-v2)
|
||||
(rx/map render-thumbnail)
|
||||
(rx/mapcat persist-thumbnail)))]
|
||||
|
||||
(if (debug? :disable-thumbnail-cache)
|
||||
(->> (request-data-for-thumbnail file-id revn)
|
||||
(->> (request-data-for-thumbnail file-id revn components-v2)
|
||||
(rx/map render-thumbnail))
|
||||
(->> (request-thumbnail file-id revn)
|
||||
(rx/catch not-found? on-cache-miss)
|
||||
|
|
|
@ -8,8 +8,8 @@
|
|||
(:require
|
||||
[app.common.data :as d]
|
||||
[app.common.logging :as l]
|
||||
[app.common.pages.helpers :as cph]
|
||||
[app.common.transit :as t]
|
||||
[app.common.types.file :as ctf]
|
||||
[app.common.uuid :as uuid]
|
||||
[app.main.data.dashboard.shortcuts]
|
||||
[app.main.data.viewer.shortcuts]
|
||||
|
@ -211,73 +211,9 @@
|
|||
([state show-ids] (dump-tree' state show-ids false))
|
||||
([state show-ids show-touched]
|
||||
(let [page-id (get state :current-page-id)
|
||||
objects (get-in state [:workspace-data :pages-index page-id :objects])
|
||||
components (get-in state [:workspace-data :components])
|
||||
libraries (get state :workspace-libraries)
|
||||
root (d/seek #(nil? (:parent-id %)) (vals objects))]
|
||||
|
||||
(letfn [(show-shape [shape-id level objects]
|
||||
(let [shape (get objects shape-id)]
|
||||
(println (str/pad (str (str/repeat " " level)
|
||||
(:name shape)
|
||||
(when (seq (:touched shape)) "*")
|
||||
(when show-ids (str/format " <%s>" (:id shape))))
|
||||
{:length 20
|
||||
:type :right})
|
||||
(show-component shape objects))
|
||||
(when show-touched
|
||||
(when (seq (:touched shape))
|
||||
(println (str (str/repeat " " level)
|
||||
" "
|
||||
(str (:touched shape)))))
|
||||
(when (:remote-synced? shape)
|
||||
(println (str (str/repeat " " level)
|
||||
" (remote-synced)"))))
|
||||
(when (:shapes shape)
|
||||
(dorun (for [shape-id (:shapes shape)]
|
||||
(show-shape shape-id (inc level) objects))))))
|
||||
|
||||
(show-component [shape objects]
|
||||
(if (nil? (:shape-ref shape))
|
||||
""
|
||||
(let [root-shape (cph/get-component-shape objects shape)
|
||||
component-id (when root-shape (:component-id root-shape))
|
||||
component-file-id (when root-shape (:component-file root-shape))
|
||||
component-file (when component-file-id (get libraries component-file-id nil))
|
||||
component (when component-id
|
||||
(if component-file
|
||||
(get-in component-file [:data :components component-id])
|
||||
(get components component-id)))
|
||||
component-shape (when (and component (:shape-ref shape))
|
||||
(get-in component [:objects (:shape-ref shape)]))]
|
||||
(str/format " %s--> %s%s%s"
|
||||
(cond (:component-root? shape) "#"
|
||||
(:component-id shape) "@"
|
||||
:else "-")
|
||||
(when component-file (str/format "<%s> " (:name component-file)))
|
||||
(or (:name component-shape) "?")
|
||||
(if (or (:component-root? shape)
|
||||
(nil? (:component-id shape))
|
||||
true)
|
||||
""
|
||||
(let [component-id (:component-id shape)
|
||||
component-file-id (:component-file shape)
|
||||
component-file (when component-file-id (get libraries component-file-id nil))
|
||||
component (if component-file
|
||||
(get-in component-file [:data :components component-id])
|
||||
(get components component-id))]
|
||||
(str/format " (%s%s)"
|
||||
(when component-file (str/format "<%s> " (:name component-file)))
|
||||
(:name component))))))))]
|
||||
|
||||
(println "[Page]")
|
||||
(show-shape (:id root) 0 objects)
|
||||
|
||||
(dorun (for [component (vals components)]
|
||||
(do
|
||||
(println)
|
||||
(println (str/format "[%s]" (:name component)))
|
||||
(show-shape (:id component) 0 (:objects component)))))))))
|
||||
file-data (get state :workspace-data)
|
||||
libraries (get state :workspace-libraries)]
|
||||
(ctf/dump-tree file-data page-id libraries show-ids show-touched))))
|
||||
|
||||
(defn ^:export dump-tree
|
||||
([] (dump-tree' @st/state))
|
||||
|
|
|
@ -7,8 +7,11 @@
|
|||
;; This namespace is only to export the functions for toggle features
|
||||
(ns features
|
||||
(:require
|
||||
[app.main.ui.features :as features]))
|
||||
[app.main.features :as features]))
|
||||
|
||||
(defn ^:export autolayout []
|
||||
(features/toggle-feature! :auto-layout))
|
||||
|
||||
(defn ^:export components-v2 []
|
||||
(features/toggle-feature! :components-v2))
|
||||
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
[app.common.data :as d]
|
||||
[app.common.geom.point :as gpt]
|
||||
[app.common.pages.helpers :as cph]
|
||||
[app.common.types.container :as ctn]
|
||||
[app.main.data.workspace :as dw]
|
||||
[app.main.data.workspace.groups :as dwg]
|
||||
[app.main.data.workspace.libraries :as dwl]
|
||||
|
@ -340,7 +341,7 @@
|
|||
(ptk/emit!
|
||||
store
|
||||
(dwl/delete-component {:id component-id})
|
||||
(dwl/sync-file (:id file) (:id file))
|
||||
(dwl/sync-file (:id file) (:id file) :components component-id)
|
||||
:the/end))))
|
||||
|
||||
(t/deftest test-instantiate-component
|
||||
|
@ -520,7 +521,7 @@
|
|||
;
|
||||
(let [page (thp/current-page new-state)
|
||||
shape1 (thp/get-shape new-state :shape1)
|
||||
parent1 (cph/get-shape page (:parent-id shape1))
|
||||
parent1 (ctn/get-shape page (:parent-id shape1))
|
||||
|
||||
[[group shape1 shape2]
|
||||
[c-group c-shape1 c-shape2]
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
[app.common.data :as d]
|
||||
[app.common.geom.point :as gpt]
|
||||
[app.common.pages.helpers :as cph]
|
||||
[app.common.types.container :as ctn]
|
||||
[app.main.data.workspace :as dw]
|
||||
[app.main.data.workspace.changes :as dch]
|
||||
[app.main.data.workspace.shapes :as dwsh]
|
||||
|
@ -1352,7 +1353,7 @@
|
|||
instance1 (thp/get-shape state :instance1)
|
||||
instance2 (thp/get-shape state :instance2)
|
||||
|
||||
shape2 (cph/get-shape (wsh/lookup-page state)
|
||||
shape2 (ctn/get-shape (wsh/lookup-page state)
|
||||
(first (:shapes instance2)))
|
||||
|
||||
update-fn1 (fn [shape]
|
||||
|
|
|
@ -2,14 +2,9 @@
|
|||
(:require
|
||||
[cljs.test :as t :include-macros true]
|
||||
[cljs.pprint :refer [pprint]]
|
||||
[beicon.core :as rx]
|
||||
[potok.core :as ptk]
|
||||
[app.common.uuid :as uuid]
|
||||
[app.common.geom.point :as gpt]
|
||||
[app.common.geom.shapes :as gsh]
|
||||
[app.common.pages.helpers :as cph]
|
||||
[app.main.data.workspace :as dw]
|
||||
[app.main.data.workspace.libraries-helpers :as dwlh]
|
||||
[app.common.types.component :as ctk]
|
||||
[app.common.types.container :as ctn]
|
||||
[app.main.data.workspace.state-helpers :as wsh]
|
||||
[app.test-helpers.pages :as thp]))
|
||||
|
||||
|
@ -59,7 +54,7 @@
|
|||
verify that they are a well constructed instance tree."
|
||||
[state root-inst-id]
|
||||
(let [page (thp/current-page state)
|
||||
root-inst (cph/get-shape page root-inst-id)
|
||||
root-inst (ctn/get-shape page root-inst-id)
|
||||
shapes-inst (cph/get-children-with-self (:objects page)
|
||||
root-inst-id)]
|
||||
(is-instance-root (first shapes-inst))
|
||||
|
@ -72,7 +67,7 @@
|
|||
verify that they are not a component instance."
|
||||
[state root-inst-id]
|
||||
(let [page (thp/current-page state)
|
||||
root-inst (cph/get-shape page root-inst-id)
|
||||
root-inst (ctn/get-shape page root-inst-id)
|
||||
shapes-inst (cph/get-children-with-self (:objects page)
|
||||
root-inst-id)]
|
||||
(run! is-noninstance shapes-inst)
|
||||
|
@ -84,7 +79,7 @@
|
|||
the main component and all its shapes."
|
||||
[state root-inst-id]
|
||||
(let [page (thp/current-page state)
|
||||
root-inst (cph/get-shape page root-inst-id)
|
||||
root-inst (ctn/get-shape page root-inst-id)
|
||||
|
||||
libs (wsh/get-libraries state)
|
||||
component (cph/get-component libs (:component-id root-inst))
|
||||
|
@ -102,7 +97,7 @@
|
|||
(cph/get-component libs (:component-id component-shape))
|
||||
|
||||
main-shape
|
||||
(cph/get-shape component (:shape-ref shape))]
|
||||
(ctn/get-shape component (:shape-ref shape))]
|
||||
|
||||
(t/is (some? main-shape))))]
|
||||
|
||||
|
@ -122,7 +117,7 @@
|
|||
corresponding component shape missing."
|
||||
[state root-inst-id]
|
||||
(let [page (thp/current-page state)
|
||||
root-inst (cph/get-shape page root-inst-id)
|
||||
root-inst (ctn/get-shape page root-inst-id)
|
||||
|
||||
libs (wsh/get-libraries state)
|
||||
component (cph/get-component libs (:component-id root-inst))
|
||||
|
@ -140,7 +135,7 @@
|
|||
(cph/get-component libs (:component-id component-shape))
|
||||
|
||||
main-shape
|
||||
(cph/get-shape component (:shape-ref shape))]
|
||||
(ctn/get-shape component (:shape-ref shape))]
|
||||
|
||||
(t/is (some? main-shape))))]
|
||||
|
||||
|
@ -155,7 +150,7 @@
|
|||
(let [page (thp/current-page state)
|
||||
libs (wsh/get-libraries state)
|
||||
component (cph/get-component libs component-id)
|
||||
root-main (cph/get-component-root component)
|
||||
root-main (ctk/get-component-root component)
|
||||
shapes-main (cph/get-children-with-self (:objects component) (:id root-main))]
|
||||
|
||||
;; Validate that the component tree is well constructed
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
[app.common.geom.shapes :as gsh]
|
||||
[app.common.pages :as cp]
|
||||
[app.common.pages.helpers :as cph]
|
||||
[app.common.types.shape :as cts]
|
||||
[app.main.data.workspace :as dw]
|
||||
[app.main.data.workspace.groups :as dwg]
|
||||
[app.main.data.workspace.layout :as layout]
|
||||
|
@ -69,9 +70,7 @@
|
|||
([state label type props]
|
||||
(let [page (current-page state)
|
||||
frame (cph/get-frame (:objects page))
|
||||
shape (-> (cp/make-minimal-shape type)
|
||||
(cp/setup-shape {:x 0 :y 0 :width 1 :height 1})
|
||||
(merge props))]
|
||||
shape (cts/make-shape type {:x 0 :y 0 :width 1 :height 1} props)]
|
||||
(swap! idmap assoc label (:id shape))
|
||||
(update state :workspace-data
|
||||
cp/process-changes
|
||||
|
@ -106,7 +105,8 @@
|
|||
shapes
|
||||
(:objects page)
|
||||
(:id page)
|
||||
current-file-id)]
|
||||
current-file-id
|
||||
true)]
|
||||
|
||||
(swap! idmap assoc instance-label (:id group)
|
||||
component-label (:id component-root))
|
||||
|
|
|
@ -9,7 +9,6 @@
|
|||
[app.common.uuid :as uuid]
|
||||
[cljs.test :as t :include-macros true]
|
||||
[cljs.pprint :refer [pprint]]
|
||||
[app.common.pages.init :as init]
|
||||
[app.common.file-builder :as fb]
|
||||
[app.util.snap-data :as sd]))
|
||||
|
||||
|
|
|
@ -539,6 +539,10 @@ msgstr "Want to remove your account?"
|
|||
msgid "dashboard.remove-shared"
|
||||
msgstr "Remove as Shared Library"
|
||||
|
||||
#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs
|
||||
msgid "dashboard.unpublish-shared"
|
||||
msgstr "Unpublish Library"
|
||||
|
||||
#: src/app/main/ui/settings/profile.cljs
|
||||
msgid "dashboard.save-settings"
|
||||
msgstr "Save settings"
|
||||
|
@ -639,6 +643,14 @@ msgstr "Your name"
|
|||
msgid "dashboard.your-penpot"
|
||||
msgstr "Your Penpot"
|
||||
|
||||
#: src/app/main/ui/alert.cljs
|
||||
msgid "ds.alert-ok"
|
||||
msgstr "Ok"
|
||||
|
||||
#: src/app/main/ui/alert.cljs
|
||||
msgid "ds.alert-title"
|
||||
msgstr "Attention"
|
||||
|
||||
#: src/app/main/ui/confirm.cljs
|
||||
msgid "ds.component-subtitle"
|
||||
msgstr "Components to update:"
|
||||
|
@ -718,6 +730,10 @@ msgstr "LDAP authentication is disabled."
|
|||
msgid "errors.media-format-unsupported"
|
||||
msgstr "The image format is not supported (must be svg, jpg or png)."
|
||||
|
||||
#: src/app/main/data/workspace/persistence.cljs
|
||||
msgid "errors.components-v2"
|
||||
msgstr "This file has already used with Components V2 enabled."
|
||||
|
||||
#: src/app/main/data/workspace/persistence.cljs
|
||||
msgid "errors.media-too-large"
|
||||
msgstr "The image is too large to be inserted (must be under 5mb)."
|
||||
|
@ -1763,6 +1779,49 @@ msgstr ""
|
|||
msgid "modals.remove-shared-confirm.message"
|
||||
msgstr "Remove “%s” as Shared Library"
|
||||
|
||||
#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs
|
||||
msgid "modals.unpublish-shared-confirm.title"
|
||||
msgstr "Unpublish library"
|
||||
|
||||
#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs
|
||||
msgid "modals.unpublish-shared-confirm.message"
|
||||
msgstr "Are you sure you want to unpublish this library?"
|
||||
|
||||
#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs
|
||||
msgid "modals.unpublish-shared-confirm.scd-message"
|
||||
msgid_plural "modals.unpublish-shared-confirm.scd-message"
|
||||
msgstr[0] "It's in use in this file:"
|
||||
msgstr[1] "It's in use in these files:"
|
||||
|
||||
#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs
|
||||
msgid "modals.unpublish-shared-confirm.hint"
|
||||
msgid_plural "modals.unpublish-shared-confirm.hint"
|
||||
msgstr[0] "If you unpublish it, the assets in it became a library of this file."
|
||||
msgstr[1] "If you unpublish it, the assets in it became a library of these files."
|
||||
|
||||
#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs
|
||||
msgid "modals.unpublish-shared-confirm.accept"
|
||||
msgstr "Unpublish"
|
||||
|
||||
#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs
|
||||
msgid "modals.delete-shared-confirm.title"
|
||||
msgstr "Deleting file"
|
||||
|
||||
#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs
|
||||
msgid "modals.delete-shared-confirm.message"
|
||||
msgstr "Are you sure you want to delete this file?"
|
||||
|
||||
#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs
|
||||
msgid "modals.delete-shared-confirm.scd-message"
|
||||
msgid_plural "modals.delete-shared-confirm.scd-message"
|
||||
msgstr[0] "This file has libraries that are being used in this file:"
|
||||
msgstr[1] "This file has libraries that are being used in these files:"
|
||||
|
||||
#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs
|
||||
msgid "modals.delete-shared-confirm.accept"
|
||||
msgstr "Delete file"
|
||||
|
||||
|
||||
#: src/app/main/ui/workspace/nudge.cljs
|
||||
msgid "modals.small-nudge"
|
||||
msgstr "Small nudge"
|
||||
|
@ -1795,6 +1854,10 @@ msgstr ""
|
|||
msgid "modals.update-remote-component.message"
|
||||
msgstr "Update a component in a shared library"
|
||||
|
||||
#: src/app/main/ui/delete_shared.cljs
|
||||
msgid "modals.delete-shared.title"
|
||||
msgstr "Deleting file"
|
||||
|
||||
#: src/app/main/ui/dashboard/team.cljs
|
||||
msgid "notifications.invitation-email-sent"
|
||||
msgstr "Invitation sent successfully"
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue