mirror of
https://github.com/penpot/penpot.git
synced 2025-08-06 05:38:28 +02:00
Merge pull request #2201 from penpot/hiru-undelete-components
🎉 Allow to restore deleted components
This commit is contained in:
commit
11018581ed
26 changed files with 469 additions and 133 deletions
|
@ -750,6 +750,10 @@
|
||||||
(uuid? (:typography-ref-file form))
|
(uuid? (:typography-ref-file form))
|
||||||
(update :typography-ref-file lookup-index)
|
(update :typography-ref-file lookup-index)
|
||||||
|
|
||||||
|
;; This covers the component instance links
|
||||||
|
(uuid? (:component-file form))
|
||||||
|
(update :component-file lookup-index)
|
||||||
|
|
||||||
;; This covers the shadows and grids (they have directly
|
;; This covers the shadows and grids (they have directly
|
||||||
;; the :file-id prop)
|
;; the :file-id prop)
|
||||||
(uuid? (:file-id form))
|
(uuid? (:file-id form))
|
||||||
|
|
|
@ -9,12 +9,16 @@
|
||||||
(:require
|
(:require
|
||||||
[app.common.data :as d]
|
[app.common.data :as d]
|
||||||
[app.common.geom.matrix :as gmt]
|
[app.common.geom.matrix :as gmt]
|
||||||
|
[app.common.geom.point :as gpt]
|
||||||
[app.common.geom.shapes :as gsh]
|
[app.common.geom.shapes :as gsh]
|
||||||
[app.common.pages.changes :as ch]
|
[app.common.pages.changes :as ch]
|
||||||
[app.common.pages.changes-spec :as pcs]
|
[app.common.pages.changes-spec :as pcs]
|
||||||
[app.common.spec :as us]
|
[app.common.spec :as us]
|
||||||
|
[app.common.types.components-list :as ctkl]
|
||||||
|
[app.common.types.container :as ctn]
|
||||||
[app.common.types.file :as ctf]
|
[app.common.types.file :as ctf]
|
||||||
[app.common.types.page :as ctp]
|
[app.common.types.page :as ctp]
|
||||||
|
[app.common.types.pages-list :as ctpl]
|
||||||
[app.common.types.shape :as cts]
|
[app.common.types.shape :as cts]
|
||||||
[app.common.uuid :as uuid]
|
[app.common.uuid :as uuid]
|
||||||
[cuerdas.core :as str]))
|
[cuerdas.core :as str]))
|
||||||
|
@ -516,10 +520,17 @@
|
||||||
[file data]
|
[file data]
|
||||||
|
|
||||||
(let [selrect cts/empty-selrect
|
(let [selrect cts/empty-selrect
|
||||||
name (:name data)
|
name (:name data)
|
||||||
path (:path data)
|
path (:path data)
|
||||||
|
main-instance-id (:main-instance-id data)
|
||||||
|
main-instance-page (:main-instance-page data)
|
||||||
obj (-> (cts/make-minimal-group nil selrect name)
|
obj (-> (cts/make-minimal-group nil selrect name)
|
||||||
(merge data)
|
(merge data)
|
||||||
|
(dissoc :path
|
||||||
|
:main-instance-id
|
||||||
|
:main-instance-page
|
||||||
|
:main-instance-x
|
||||||
|
:main-instance-y)
|
||||||
(check-name file :group)
|
(check-name file :group)
|
||||||
(d/without-nils))]
|
(d/without-nils))]
|
||||||
(-> file
|
(-> file
|
||||||
|
@ -528,6 +539,8 @@
|
||||||
:id (:id obj)
|
:id (:id obj)
|
||||||
:name name
|
:name name
|
||||||
:path path
|
:path path
|
||||||
|
:main-instance-id main-instance-id
|
||||||
|
:main-instance-page main-instance-page
|
||||||
:shapes [obj]})
|
:shapes [obj]})
|
||||||
|
|
||||||
(assoc :last-id (:id obj))
|
(assoc :last-id (:id obj))
|
||||||
|
@ -546,7 +559,8 @@
|
||||||
(commit-change
|
(commit-change
|
||||||
file
|
file
|
||||||
{:type :del-component
|
{:type :del-component
|
||||||
:id component-id})
|
:id component-id
|
||||||
|
:skip-undelete? true})
|
||||||
|
|
||||||
(:masked-group? component)
|
(:masked-group? component)
|
||||||
(let [mask (first children)]
|
(let [mask (first children)]
|
||||||
|
@ -586,6 +600,42 @@
|
||||||
(dissoc :current-component-id)
|
(dissoc :current-component-id)
|
||||||
(update :parent-stack pop))))
|
(update :parent-stack pop))))
|
||||||
|
|
||||||
|
(defn finish-deleted-component
|
||||||
|
[component-id page-id main-instance-x main-instance-y file]
|
||||||
|
(let [file (assoc file :current-component-id component-id)
|
||||||
|
page (ctpl/get-page (:data file) page-id)
|
||||||
|
component (ctkl/get-component (:data file) component-id)
|
||||||
|
main-instance-id (:main-instance-id component)
|
||||||
|
|
||||||
|
; To obtain a deleted component, we first create the component
|
||||||
|
; and the main instance in the workspace, and then delete them.
|
||||||
|
[_ shapes]
|
||||||
|
(ctn/make-component-instance page
|
||||||
|
component
|
||||||
|
(:id file)
|
||||||
|
(gpt/point main-instance-x
|
||||||
|
main-instance-y)
|
||||||
|
{:main-instance? true
|
||||||
|
:force-id main-instance-id})]
|
||||||
|
(as-> file $
|
||||||
|
(reduce #(commit-change %1
|
||||||
|
{:type :add-obj
|
||||||
|
:id (:id %2)
|
||||||
|
:page-id (:id page)
|
||||||
|
:parent-id (:parent-id %2)
|
||||||
|
:frame-id (:frame-id %2)
|
||||||
|
:obj %2})
|
||||||
|
$
|
||||||
|
shapes)
|
||||||
|
(commit-change $ {:type :del-component
|
||||||
|
:id component-id})
|
||||||
|
(reduce #(commit-change %1 {:type :del-obj
|
||||||
|
:page-id page-id
|
||||||
|
:id (:id %2)})
|
||||||
|
$
|
||||||
|
shapes)
|
||||||
|
(dissoc $ :current-component-id))))
|
||||||
|
|
||||||
(defn delete-object
|
(defn delete-object
|
||||||
[file id]
|
[file id]
|
||||||
(let [page-id (:current-page-id file)]
|
(let [page-id (:current-page-id file)]
|
||||||
|
|
|
@ -19,6 +19,7 @@
|
||||||
[app.common.types.components-list :as ctkl]
|
[app.common.types.components-list :as ctkl]
|
||||||
[app.common.types.container :as ctn]
|
[app.common.types.container :as ctn]
|
||||||
[app.common.types.colors-list :as ctcl]
|
[app.common.types.colors-list :as ctcl]
|
||||||
|
[app.common.types.file :as ctf]
|
||||||
[app.common.types.page :as ctp]
|
[app.common.types.page :as ctp]
|
||||||
[app.common.types.pages-list :as ctpl]
|
[app.common.types.pages-list :as ctpl]
|
||||||
[app.common.types.shape :as cts]
|
[app.common.types.shape :as cts]
|
||||||
|
@ -302,9 +303,7 @@
|
||||||
|
|
||||||
(defmethod process-change :del-page
|
(defmethod process-change :del-page
|
||||||
[data {:keys [id]}]
|
[data {:keys [id]}]
|
||||||
(-> data
|
(ctpl/delete-page data id))
|
||||||
(update :pages (fn [pages] (filterv #(not= % id) pages)))
|
|
||||||
(update :pages-index dissoc id)))
|
|
||||||
|
|
||||||
(defmethod process-change :mov-page
|
(defmethod process-change :mov-page
|
||||||
[data {:keys [id index]}]
|
[data {:keys [id index]}]
|
||||||
|
@ -320,7 +319,7 @@
|
||||||
|
|
||||||
(defmethod process-change :del-color
|
(defmethod process-change :del-color
|
||||||
[data {:keys [id]}]
|
[data {:keys [id]}]
|
||||||
(update data :colors dissoc id))
|
(ctcl/delete-color data id))
|
||||||
|
|
||||||
(defmethod process-change :add-recent-color
|
(defmethod process-change :add-recent-color
|
||||||
[data {:keys [color]}]
|
[data {:keys [color]}]
|
||||||
|
@ -371,8 +370,16 @@
|
||||||
(assoc :objects objects))))
|
(assoc :objects objects))))
|
||||||
|
|
||||||
(defmethod process-change :del-component
|
(defmethod process-change :del-component
|
||||||
|
[data {:keys [id skip-undelete?]}]
|
||||||
|
(ctf/delete-component data id skip-undelete?))
|
||||||
|
|
||||||
|
(defmethod process-change :restore-component
|
||||||
[data {:keys [id]}]
|
[data {:keys [id]}]
|
||||||
(d/dissoc-in data [:components id]))
|
(ctf/restore-component data id))
|
||||||
|
|
||||||
|
(defmethod process-change :purge-component
|
||||||
|
[data {:keys [id]}]
|
||||||
|
(ctf/purge-component data id))
|
||||||
|
|
||||||
;; -- Typography
|
;; -- Typography
|
||||||
|
|
||||||
|
@ -386,7 +393,7 @@
|
||||||
|
|
||||||
(defmethod process-change :del-typography
|
(defmethod process-change :del-typography
|
||||||
[data {:keys [id]}]
|
[data {:keys [id]}]
|
||||||
(update data :typographies dissoc id))
|
(ctyl/delete-typography data id))
|
||||||
|
|
||||||
;; === Operations
|
;; === Operations
|
||||||
|
|
||||||
|
|
|
@ -617,18 +617,34 @@
|
||||||
changes)))
|
changes)))
|
||||||
|
|
||||||
(defn delete-component
|
(defn delete-component
|
||||||
[changes id]
|
[changes id components-v2]
|
||||||
(assert-library changes)
|
(assert-library changes)
|
||||||
(let [library-data (::library-data (meta changes))
|
(let [library-data (::library-data (meta changes))
|
||||||
prev-component (get-in library-data [:components id])]
|
prev-component (get-in library-data [:components id])]
|
||||||
(-> changes
|
(-> changes
|
||||||
(update :redo-changes conj {:type :del-component
|
(update :redo-changes conj {:type :del-component
|
||||||
:id id})
|
:id id})
|
||||||
(update :undo-changes d/preconj {:type :add-component
|
(update :undo-changes
|
||||||
:id id
|
(fn [undo-changes]
|
||||||
:name (:name prev-component)
|
(cond-> undo-changes
|
||||||
:path (:path prev-component)
|
components-v2
|
||||||
:main-instance-id (:main-instance-id prev-component)
|
(d/preconj {:type :purge-component
|
||||||
:main-instance-page (:main-instance-page prev-component)
|
:id id})
|
||||||
:shapes (vals (:objects prev-component))}))))
|
|
||||||
|
|
||||||
|
:always
|
||||||
|
(d/preconj {:type :add-component
|
||||||
|
: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))})))))))
|
||||||
|
|
||||||
|
(defn restore-component
|
||||||
|
[changes id]
|
||||||
|
(assert-library changes)
|
||||||
|
(-> changes
|
||||||
|
(update :redo-changes conj {:type :restore-component
|
||||||
|
:id id})
|
||||||
|
(update :undo-changes d/preconj {:type :del-component
|
||||||
|
:id id})))
|
||||||
|
|
|
@ -159,7 +159,16 @@
|
||||||
(s/keys :req-un [::id]
|
(s/keys :req-un [::id]
|
||||||
:opt-un [::name :internal.changes.add-component/shapes]))
|
:opt-un [::name :internal.changes.add-component/shapes]))
|
||||||
|
|
||||||
|
(s/def :internal.changes.del-component/skip-undelete? boolean?)
|
||||||
|
|
||||||
(defmethod change-spec :del-component [_]
|
(defmethod change-spec :del-component [_]
|
||||||
|
(s/keys :req-un [::id]
|
||||||
|
:opt-un [:internal.changes.del-component/skip-undelete?]))
|
||||||
|
|
||||||
|
(defmethod change-spec :restore-component [_]
|
||||||
|
(s/keys :req-un [::id]))
|
||||||
|
|
||||||
|
(defmethod change-spec :purge-component [_]
|
||||||
(s/keys :req-un [::id]))
|
(s/keys :req-un [::id]))
|
||||||
|
|
||||||
(defmethod change-spec :add-typography [_]
|
(defmethod change-spec :add-typography [_]
|
||||||
|
|
|
@ -22,3 +22,7 @@
|
||||||
[file-data color-id f]
|
[file-data color-id f]
|
||||||
(update-in file-data [:colors color-id] f))
|
(update-in file-data [:colors color-id] f))
|
||||||
|
|
||||||
|
(defn delete-color
|
||||||
|
[file-data color-id]
|
||||||
|
(update file-data :colors dissoc color-id))
|
||||||
|
|
||||||
|
|
|
@ -35,3 +35,7 @@
|
||||||
[file-data component-id f]
|
[file-data component-id f]
|
||||||
(update-in file-data [:components component-id] f))
|
(update-in file-data [:components component-id] f))
|
||||||
|
|
||||||
|
(defn delete-component
|
||||||
|
[file-data component-id]
|
||||||
|
(update file-data :components dissoc component-id))
|
||||||
|
|
||||||
|
|
|
@ -108,53 +108,59 @@
|
||||||
"Clone the shapes of the component, generating new names and ids, and linking
|
"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
|
each new shape to the corresponding one of the component. Place the new instance
|
||||||
coordinates in the given position."
|
coordinates in the given position."
|
||||||
[container component component-file-id position main-instance?]
|
([container component component-file-id position]
|
||||||
(let [component-shape (get-shape component (:id component))
|
(make-component-instance container component component-file-id position {}))
|
||||||
|
|
||||||
orig-pos (gpt/point (:x component-shape) (:y component-shape))
|
([container component component-file-id position
|
||||||
delta (gpt/subtract position orig-pos)
|
{:keys [main-instance? force-id] :or {main-instance? false force-id nil}}]
|
||||||
|
(let [component-shape (get-shape component (:id component))
|
||||||
|
|
||||||
objects (:objects container)
|
orig-pos (gpt/point (:x component-shape) (:y component-shape))
|
||||||
unames (volatile! (ctst/retrieve-used-names objects))
|
delta (gpt/subtract position orig-pos)
|
||||||
|
|
||||||
frame-id (ctst/frame-id-by-position objects (gpt/add orig-pos delta))
|
objects (:objects container)
|
||||||
|
unames (volatile! (ctst/retrieve-used-names objects))
|
||||||
|
|
||||||
update-new-shape
|
frame-id (ctst/frame-id-by-position objects (gpt/add orig-pos delta))
|
||||||
(fn [new-shape original-shape]
|
|
||||||
(let [new-name (ctst/generate-unique-name @unames (:name new-shape))]
|
|
||||||
|
|
||||||
(when (nil? (:parent-id original-shape))
|
update-new-shape
|
||||||
(vswap! unames conj new-name))
|
(fn [new-shape original-shape]
|
||||||
|
(let [new-name (ctst/generate-unique-name @unames (:name new-shape))]
|
||||||
|
|
||||||
(cond-> new-shape
|
(when (nil? (:parent-id original-shape))
|
||||||
true
|
(vswap! unames conj new-name))
|
||||||
(as-> $
|
|
||||||
(gsh/move $ delta)
|
|
||||||
(assoc $ :frame-id frame-id)
|
|
||||||
(assoc $ :parent-id
|
|
||||||
(or (:parent-id $) (:frame-id $)))
|
|
||||||
(dissoc $ :touched))
|
|
||||||
|
|
||||||
(nil? (:shape-ref original-shape))
|
(cond-> new-shape
|
||||||
(assoc :shape-ref (:id original-shape))
|
true
|
||||||
|
(as-> $
|
||||||
|
(gsh/move $ delta)
|
||||||
|
(assoc $ :frame-id frame-id)
|
||||||
|
(assoc $ :parent-id
|
||||||
|
(or (:parent-id $) (:frame-id $)))
|
||||||
|
(dissoc $ :touched))
|
||||||
|
|
||||||
(nil? (:parent-id original-shape))
|
(nil? (:shape-ref original-shape))
|
||||||
(assoc :component-id (:id original-shape)
|
(assoc :shape-ref (:id original-shape))
|
||||||
:component-file component-file-id
|
|
||||||
:component-root? true
|
|
||||||
:name new-name)
|
|
||||||
|
|
||||||
(and (nil? (:parent-id original-shape)) main-instance?)
|
(nil? (:parent-id original-shape))
|
||||||
(assoc :main-instance? true)
|
(assoc :component-id (:id original-shape)
|
||||||
|
:component-file component-file-id
|
||||||
|
:component-root? true
|
||||||
|
:name new-name)
|
||||||
|
|
||||||
(some? (:parent-id original-shape))
|
(and (nil? (:parent-id original-shape)) main-instance?)
|
||||||
(dissoc :component-root?))))
|
(assoc :main-instance? true)
|
||||||
|
|
||||||
[new-shape new-shapes _]
|
(some? (:parent-id original-shape))
|
||||||
(ctst/clone-object component-shape
|
(dissoc :component-root?))))
|
||||||
nil
|
|
||||||
(get component :objects)
|
[new-shape new-shapes _]
|
||||||
update-new-shape)]
|
(ctst/clone-object component-shape
|
||||||
|
nil
|
||||||
|
(get component :objects)
|
||||||
|
update-new-shape
|
||||||
|
(fn [object _] object)
|
||||||
|
force-id)]
|
||||||
|
|
||||||
|
[new-shape new-shapes])))
|
||||||
|
|
||||||
[new-shape new-shapes]))
|
|
||||||
|
|
||||||
|
|
|
@ -120,6 +120,57 @@
|
||||||
|
|
||||||
;; Asset helpers
|
;; Asset helpers
|
||||||
|
|
||||||
|
(defn delete-component
|
||||||
|
"Delete a component and store it to be able to be recovered later.
|
||||||
|
|
||||||
|
Remember also the position of the main instance."
|
||||||
|
([file-data component-id]
|
||||||
|
(delete-component file-data component-id false))
|
||||||
|
|
||||||
|
([file-data component-id skip-undelete?]
|
||||||
|
(let [components-v2 (get-in file-data [:options :components-v2])
|
||||||
|
|
||||||
|
add-to-deleted-components
|
||||||
|
(fn [file-data]
|
||||||
|
(let [component (ctkl/get-component file-data component-id)]
|
||||||
|
(if (some? component)
|
||||||
|
(let [page (ctpl/get-page file-data (:main-instance-page component))
|
||||||
|
main-instance (ctn/get-shape page (:main-instance-id component))
|
||||||
|
component (assoc component
|
||||||
|
:main-instance-x (:x main-instance) ; An instance root is always a group,
|
||||||
|
:main-instance-y (:y main-instance))] ; so it will have :x and :y
|
||||||
|
(when (nil? main-instance)
|
||||||
|
(throw (ex-info "Cannot delete the main instance before the component" {:component-id component-id})))
|
||||||
|
(assoc-in file-data [:deleted-components component-id] component))
|
||||||
|
file-data)))]
|
||||||
|
|
||||||
|
(cond-> file-data
|
||||||
|
(and components-v2 (not skip-undelete?))
|
||||||
|
(add-to-deleted-components)
|
||||||
|
|
||||||
|
:always
|
||||||
|
(ctkl/delete-component component-id)))))
|
||||||
|
|
||||||
|
(defn get-deleted-component
|
||||||
|
"Retrieve a component that has been deleted but still is in the safe store."
|
||||||
|
[file-data component-id]
|
||||||
|
(get-in file-data [:deleted-components component-id]))
|
||||||
|
|
||||||
|
(defn restore-component
|
||||||
|
"Recover a deleted component and put it again in place."
|
||||||
|
[file-data component-id]
|
||||||
|
(let [component (-> (get-in file-data [:deleted-components component-id])
|
||||||
|
(dissoc :main-instance-x :main-instance-y))]
|
||||||
|
(cond-> file-data
|
||||||
|
(some? component)
|
||||||
|
(-> (assoc-in [:components component-id] component)
|
||||||
|
(d/dissoc-in [:deleted-components component-id])))))
|
||||||
|
|
||||||
|
(defn purge-component
|
||||||
|
"Remove permanently a component."
|
||||||
|
[file-data component-id]
|
||||||
|
(d/dissoc-in file-data [:deleted-components component-id]))
|
||||||
|
|
||||||
(defmulti uses-asset?
|
(defmulti uses-asset?
|
||||||
"Checks if a shape uses the given asset."
|
"Checks if a shape uses the given asset."
|
||||||
(fn [asset-type _ _ _] asset-type))
|
(fn [asset-type _ _ _] asset-type))
|
||||||
|
@ -185,7 +236,7 @@
|
||||||
|
|
||||||
(defn migrate-to-components-v2
|
(defn migrate-to-components-v2
|
||||||
"If there is any component in the file library, add a new 'Library backup' and generate
|
"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."
|
main instances for all components there. Mark the file with the :components-v2 option."
|
||||||
[file-data]
|
[file-data]
|
||||||
(let [components (ctkl/components-seq file-data)]
|
(let [components (ctkl/components-seq file-data)]
|
||||||
(if (or (empty? components)
|
(if (or (empty? components)
|
||||||
|
@ -205,7 +256,7 @@
|
||||||
component
|
component
|
||||||
(:id file-data)
|
(:id file-data)
|
||||||
position
|
position
|
||||||
true)
|
{:main-instance? true})
|
||||||
|
|
||||||
add-shapes
|
add-shapes
|
||||||
(fn [page]
|
(fn [page]
|
||||||
|
@ -269,7 +320,7 @@
|
||||||
component
|
component
|
||||||
(:id file-data)
|
(:id file-data)
|
||||||
position
|
position
|
||||||
true)
|
{:main-instance? true})
|
||||||
|
|
||||||
; Add all shapes of the main instance to the library page
|
; Add all shapes of the main instance to the library page
|
||||||
add-main-instance-shapes
|
add-main-instance-shapes
|
||||||
|
|
|
@ -37,3 +37,9 @@
|
||||||
[file-data page-id f]
|
[file-data page-id f]
|
||||||
(update-in file-data [:pages-index page-id] f))
|
(update-in file-data [:pages-index page-id] f))
|
||||||
|
|
||||||
|
(defn delete-page
|
||||||
|
[file-data page-id]
|
||||||
|
(-> file-data
|
||||||
|
(update :pages (fn [pages] (filterv #(not= % page-id) pages)))
|
||||||
|
(update :pages-index dissoc page-id)))
|
||||||
|
|
||||||
|
|
|
@ -284,10 +284,13 @@
|
||||||
the order of the children of each parent."
|
the order of the children of each parent."
|
||||||
|
|
||||||
([object parent-id objects update-new-object]
|
([object parent-id objects update-new-object]
|
||||||
(clone-object object parent-id objects update-new-object (fn [object _] object)))
|
(clone-object object parent-id objects update-new-object (fn [object _] object) nil))
|
||||||
|
|
||||||
([object parent-id objects update-new-object update-original-object]
|
([object parent-id objects update-new-object update-original-object]
|
||||||
(let [new-id (uuid/next)]
|
(clone-object object parent-id objects update-new-object update-original-object nil))
|
||||||
|
|
||||||
|
([object parent-id objects update-new-object update-original-object force-id]
|
||||||
|
(let [new-id (or force-id (uuid/next))]
|
||||||
(loop [child-ids (seq (:shapes object))
|
(loop [child-ids (seq (:shapes object))
|
||||||
new-direct-children []
|
new-direct-children []
|
||||||
new-children []
|
new-children []
|
||||||
|
|
|
@ -22,3 +22,7 @@
|
||||||
[file-data typography-id f]
|
[file-data typography-id f]
|
||||||
(update-in file-data [:typographies typography-id] f))
|
(update-in file-data [:typographies typography-id] f))
|
||||||
|
|
||||||
|
(defn delete-typography
|
||||||
|
[file-data typography-id]
|
||||||
|
(update file-data :typographies dissoc typography-id))
|
||||||
|
|
||||||
|
|
|
@ -97,8 +97,7 @@
|
||||||
(ctn/make-component-instance (ctpl/get-page file-data page-id)
|
(ctn/make-component-instance (ctpl/get-page file-data page-id)
|
||||||
(ctkl/get-component (:data library) component-id)
|
(ctkl/get-component (:data library) component-id)
|
||||||
(:id library)
|
(:id library)
|
||||||
(gpt/point 0 0)
|
(gpt/point 0 0))]
|
||||||
false)]
|
|
||||||
|
|
||||||
(swap! idmap assoc label (:id instance-shape))
|
(swap! idmap assoc label (:id instance-shape))
|
||||||
(-> file-data
|
(-> file-data
|
||||||
|
|
|
@ -18,6 +18,7 @@
|
||||||
[app.common.types.color :as ctc]
|
[app.common.types.color :as ctc]
|
||||||
[app.common.types.container :as ctn]
|
[app.common.types.container :as ctn]
|
||||||
[app.common.types.file :as ctf]
|
[app.common.types.file :as ctf]
|
||||||
|
[app.common.types.pages-list :as ctpl]
|
||||||
[app.common.types.shape-tree :as ctst]
|
[app.common.types.shape-tree :as ctst]
|
||||||
[app.common.types.typography :as ctt]
|
[app.common.types.typography :as ctt]
|
||||||
[app.common.uuid :as uuid]
|
[app.common.uuid :as uuid]
|
||||||
|
@ -393,13 +394,50 @@
|
||||||
(ptk/reify ::delete-component
|
(ptk/reify ::delete-component
|
||||||
ptk/WatchEvent
|
ptk/WatchEvent
|
||||||
(watch [it state _]
|
(watch [it state _]
|
||||||
(let [data (get state :workspace-data)
|
(let [data (get state :workspace-data)
|
||||||
|
components-v2 (features/active-feature? state :components-v2)
|
||||||
changes (-> (pcb/empty-changes it)
|
changes (-> (pcb/empty-changes it)
|
||||||
(pcb/with-library-data data)
|
(pcb/with-library-data data)
|
||||||
(pcb/delete-component id))]
|
(pcb/delete-component id components-v2))]
|
||||||
|
|
||||||
(rx/of (dch/commit-changes changes))))))
|
(rx/of (dch/commit-changes changes))))))
|
||||||
|
|
||||||
|
(defn restore-component
|
||||||
|
"Restore a deleted component, with the given id, on the current file library."
|
||||||
|
[id]
|
||||||
|
(us/assert ::us/uuid id)
|
||||||
|
(ptk/reify ::restore-component
|
||||||
|
ptk/WatchEvent
|
||||||
|
(watch [it state _]
|
||||||
|
(let [data (get state :workspace-data)
|
||||||
|
component (ctf/get-deleted-component data id)
|
||||||
|
page (ctpl/get-page data (:main-instance-page component))
|
||||||
|
|
||||||
|
; Make a new main instance, with the same id of the original
|
||||||
|
[_main-instance shapes]
|
||||||
|
(ctn/make-component-instance page
|
||||||
|
component
|
||||||
|
(:id data)
|
||||||
|
(gpt/point (:main-instance-x component)
|
||||||
|
(:main-instance-y component))
|
||||||
|
{:main-instance? true
|
||||||
|
:force-id (:main-instance-id component)})
|
||||||
|
|
||||||
|
changes (-> (pcb/empty-changes it)
|
||||||
|
(pcb/with-library-data data)
|
||||||
|
(pcb/with-page page))
|
||||||
|
|
||||||
|
changes (reduce #(pcb/add-object %1 %2 {:ignore-touched true})
|
||||||
|
changes
|
||||||
|
shapes)
|
||||||
|
|
||||||
|
; restore-component change needs to be done after add main instance
|
||||||
|
; because when undo changes, the orden is inverse
|
||||||
|
changes (pcb/restore-component changes id)]
|
||||||
|
|
||||||
|
(rx/of (dch/commit-changes changes))))))
|
||||||
|
|
||||||
|
|
||||||
(defn instantiate-component
|
(defn instantiate-component
|
||||||
"Create a new shape in the current page, from the component with the given id
|
"Create a new shape in the current page, from the component with the given id
|
||||||
in the given file library. Then selects the newly created instance."
|
in the given file library. Then selects the newly created instance."
|
||||||
|
|
|
@ -118,8 +118,7 @@
|
||||||
:name (:name new-component-shape)
|
:name (:name new-component-shape)
|
||||||
:objects (d/index-by :id new-component-shapes)}
|
:objects (d/index-by :id new-component-shapes)}
|
||||||
(:component-file main-instance-shape)
|
(:component-file main-instance-shape)
|
||||||
position
|
position))]
|
||||||
false))]
|
|
||||||
|
|
||||||
[new-component-shape new-component-shapes
|
[new-component-shape new-component-shapes
|
||||||
new-instance-shape new-instance-shapes]))
|
new-instance-shape new-instance-shapes]))
|
||||||
|
@ -130,7 +129,7 @@
|
||||||
(let [component (cph/get-component libraries file-id component-id)
|
(let [component (cph/get-component libraries file-id component-id)
|
||||||
|
|
||||||
[new-shape new-shapes]
|
[new-shape new-shapes]
|
||||||
(ctn/make-component-instance page component file-id position false)
|
(ctn/make-component-instance page component file-id position)
|
||||||
|
|
||||||
changes (reduce #(pcb/add-object %1 %2 {:ignore-touched true})
|
changes (reduce #(pcb/add-object %1 %2 {:ignore-touched true})
|
||||||
(pcb/empty-changes it (:id page))
|
(pcb/empty-changes it (:id page))
|
||||||
|
|
|
@ -233,7 +233,16 @@
|
||||||
(pcb/with-page page)
|
(pcb/with-page page)
|
||||||
(pcb/with-objects objects)
|
(pcb/with-objects objects)
|
||||||
(pcb/with-library-data file)
|
(pcb/with-library-data file)
|
||||||
(pcb/set-page-option :guides guides)
|
(pcb/set-page-option :guides guides))
|
||||||
|
|
||||||
|
changes (reduce (fn [changes component-id]
|
||||||
|
;; It's important to delete the component before the main instance, because we
|
||||||
|
;; need to store the instance position if we want to restore it later.
|
||||||
|
(pcb/delete-component changes component-id components-v2))
|
||||||
|
changes
|
||||||
|
components-to-delete)
|
||||||
|
|
||||||
|
changes (-> changes
|
||||||
(pcb/remove-objects all-children)
|
(pcb/remove-objects all-children)
|
||||||
(pcb/remove-objects ids)
|
(pcb/remove-objects ids)
|
||||||
(pcb/remove-objects empty-parents)
|
(pcb/remove-objects empty-parents)
|
||||||
|
@ -252,12 +261,7 @@
|
||||||
(cond-> (seq starting-flows)
|
(cond-> (seq starting-flows)
|
||||||
(pcb/update-page-option :flows (fn [flows]
|
(pcb/update-page-option :flows (fn [flows]
|
||||||
(->> (map :id starting-flows)
|
(->> (map :id starting-flows)
|
||||||
(reduce ctp/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 (dc/detach-comment-thread ids)
|
(rx/of (dc/detach-comment-thread ids)
|
||||||
(dwsl/update-layout-positions all-parents)
|
(dwsl/update-layout-positions all-parents)
|
||||||
|
|
|
@ -220,7 +220,7 @@
|
||||||
:fill "none"}
|
:fill "none"}
|
||||||
|
|
||||||
(when include-metadata?
|
(when include-metadata?
|
||||||
[:& export/export-page {:options (:options data)}])
|
[:& export/export-page {:id (:id data) :options (:options data)}])
|
||||||
|
|
||||||
(let [shapes (->> shapes
|
(let [shapes (->> shapes
|
||||||
(remove cph/frame-shape?)
|
(remove cph/frame-shape?)
|
||||||
|
@ -393,6 +393,11 @@
|
||||||
object (get objects id)
|
object (get objects id)
|
||||||
selrect (:selrect object)
|
selrect (:selrect object)
|
||||||
|
|
||||||
|
main-instance-id (:main-instance-id data)
|
||||||
|
main-instance-page (:main-instance-page data)
|
||||||
|
main-instance-x (:main-instance-x data)
|
||||||
|
main-instance-y (:main-instance-y data)
|
||||||
|
|
||||||
vbox
|
vbox
|
||||||
(format-viewbox
|
(format-viewbox
|
||||||
{:width (:width selrect)
|
{:width (:width selrect)
|
||||||
|
@ -403,7 +408,13 @@
|
||||||
(mf/deps objects)
|
(mf/deps objects)
|
||||||
(fn [] (group-wrapper-factory objects)))]
|
(fn [] (group-wrapper-factory objects)))]
|
||||||
|
|
||||||
[:> "symbol" #js {:id (str id) :viewBox vbox "penpot:path" path}
|
[:> "symbol" #js {:id (str id)
|
||||||
|
:viewBox vbox
|
||||||
|
"penpot:path" path
|
||||||
|
"penpot:main-instance-id" main-instance-id
|
||||||
|
"penpot:main-instance-page" main-instance-page
|
||||||
|
"penpot:main-instance-x" main-instance-x
|
||||||
|
"penpot:main-instance-y" main-instance-y}
|
||||||
[:title name]
|
[:title name]
|
||||||
[:> shape-container {:shape object}
|
[:> shape-container {:shape object}
|
||||||
[:& group-wrapper {:shape object :view-box vbox}]]]))
|
[:& group-wrapper {:shape object :view-box vbox}]]]))
|
||||||
|
@ -414,7 +425,8 @@
|
||||||
(let [data (obj/get props "data")
|
(let [data (obj/get props "data")
|
||||||
children (obj/get props "children")
|
children (obj/get props "children")
|
||||||
render-embed? (obj/get props "render-embed?")
|
render-embed? (obj/get props "render-embed?")
|
||||||
include-metadata? (obj/get props "include-metadata?")]
|
include-metadata? (obj/get props "include-metadata?")
|
||||||
|
source (keyword (obj/get props "source" "components"))]
|
||||||
[:& (mf/provider embed/context) {:value render-embed?}
|
[:& (mf/provider embed/context) {:value render-embed?}
|
||||||
[:& (mf/provider export/include-metadata-ctx) {:value include-metadata?}
|
[:& (mf/provider export/include-metadata-ctx) {:value include-metadata?}
|
||||||
[:svg {:version "1.1"
|
[:svg {:version "1.1"
|
||||||
|
@ -424,7 +436,7 @@
|
||||||
:style {:display (when-not (some? children) "none")}
|
:style {:display (when-not (some? children) "none")}
|
||||||
:fill "none"}
|
:fill "none"}
|
||||||
[:defs
|
[:defs
|
||||||
(for [[id data] (:components data)]
|
(for [[id data] (source data)]
|
||||||
[:& component-symbol {:id id :key (dm/str id) :data data}])]
|
[:& component-symbol {:id id :key (dm/str id) :data data}])]
|
||||||
|
|
||||||
children]]]))
|
children]]]))
|
||||||
|
@ -482,9 +494,9 @@
|
||||||
(rds/renderToStaticMarkup elem)))))))
|
(rds/renderToStaticMarkup elem)))))))
|
||||||
|
|
||||||
(defn render-components
|
(defn render-components
|
||||||
[data]
|
[data source]
|
||||||
(let [;; Join all components objects into a single map
|
(let [;; Join all components objects into a single map
|
||||||
objects (->> (:components data)
|
objects (->> (source data)
|
||||||
(vals)
|
(vals)
|
||||||
(map :objects)
|
(map :objects)
|
||||||
(reduce conj))]
|
(reduce conj))]
|
||||||
|
@ -498,5 +510,6 @@
|
||||||
(rx/map
|
(rx/map
|
||||||
(fn [data]
|
(fn [data]
|
||||||
(let [elem (mf/element components-sprite-svg
|
(let [elem (mf/element components-sprite-svg
|
||||||
#js {:data data :render-embed? true :include-metadata? true})]
|
#js {:data data :render-embed? true :include-metadata? true
|
||||||
|
:source (name source)})]
|
||||||
(rds/renderToStaticMarkup elem))))))))
|
(rds/renderToStaticMarkup elem))))))))
|
||||||
|
|
|
@ -13,6 +13,7 @@
|
||||||
[app.main.data.events :as ev]
|
[app.main.data.events :as ev]
|
||||||
[app.main.data.messages :as msg]
|
[app.main.data.messages :as msg]
|
||||||
[app.main.data.modal :as modal]
|
[app.main.data.modal :as modal]
|
||||||
|
[app.main.features :as features]
|
||||||
[app.main.store :as st]
|
[app.main.store :as st]
|
||||||
[app.main.ui.components.file-uploader :refer [file-uploader]]
|
[app.main.ui.components.file-uploader :refer [file-uploader]]
|
||||||
[app.main.ui.icons :as i]
|
[app.main.ui.icons :as i]
|
||||||
|
@ -247,6 +248,8 @@
|
||||||
:files (->> files
|
:files (->> files
|
||||||
(mapv #(assoc % :status :analyzing)))})
|
(mapv #(assoc % :status :analyzing)))})
|
||||||
|
|
||||||
|
components-v2 (features/use-feature :components-v2)
|
||||||
|
|
||||||
analyze-import
|
analyze-import
|
||||||
(mf/use-callback
|
(mf/use-callback
|
||||||
(fn [files]
|
(fn [files]
|
||||||
|
@ -268,6 +271,7 @@
|
||||||
:num-files (count files)}))
|
:num-files (count files)}))
|
||||||
(->> (uw/ask-many!
|
(->> (uw/ask-many!
|
||||||
{:cmd :import-files
|
{:cmd :import-files
|
||||||
|
:components-v2 components-v2
|
||||||
:project-id project-id
|
:project-id project-id
|
||||||
:files files})
|
:files files})
|
||||||
(rx/subs
|
(rx/subs
|
||||||
|
|
|
@ -54,7 +54,8 @@
|
||||||
([props attr trfn]
|
([props attr trfn]
|
||||||
(let [val (get shape attr)
|
(let [val (get shape attr)
|
||||||
val (if (keyword? val) (d/name val) val)
|
val (if (keyword? val) (d/name val) val)
|
||||||
ns-attr (str "penpot:" (-> attr d/name))]
|
ns-attr (-> (str "penpot:" (-> attr d/name))
|
||||||
|
(str/strip-suffix "?"))]
|
||||||
(cond-> props
|
(cond-> props
|
||||||
(some? val)
|
(some? val)
|
||||||
(obj/set! ns-attr (trfn val)))))))
|
(obj/set! ns-attr (trfn val)))))))
|
||||||
|
@ -136,7 +137,8 @@
|
||||||
(add! :typography-ref-file)
|
(add! :typography-ref-file)
|
||||||
(add! :component-file)
|
(add! :component-file)
|
||||||
(add! :component-id)
|
(add! :component-id)
|
||||||
(add! :component-root)
|
(add! :component-root?)
|
||||||
|
(add! :main-instance?)
|
||||||
(add! :shape-ref))))
|
(add! :shape-ref))))
|
||||||
|
|
||||||
(defn prefix-keys [m]
|
(defn prefix-keys [m]
|
||||||
|
@ -177,11 +179,11 @@
|
||||||
:axis (d/name axis)}])])
|
:axis (d/name axis)}])])
|
||||||
|
|
||||||
(mf/defc export-page
|
(mf/defc export-page
|
||||||
[{:keys [options]}]
|
[{:keys [id options]}]
|
||||||
(let [saved-grids (get options :saved-grids)
|
(let [saved-grids (get options :saved-grids)
|
||||||
flows (get options :flows)
|
flows (get options :flows)
|
||||||
guides (get options :guides)]
|
guides (get options :guides)]
|
||||||
[:> "penpot:page" #js {}
|
[:> "penpot:page" #js {:id id}
|
||||||
(when (d/not-empty? saved-grids)
|
(when (d/not-empty? saved-grids)
|
||||||
(let [parse-grid (fn [[type params]] {:type type :params params})
|
(let [parse-grid (fn [[type params]] {:type type :params params})
|
||||||
grids (->> saved-grids (mapv parse-grid))]
|
grids (->> saved-grids (mapv parse-grid))]
|
||||||
|
|
|
@ -10,6 +10,7 @@
|
||||||
[app.common.data :as d]
|
[app.common.data :as d]
|
||||||
[app.common.data.macros :as dm]
|
[app.common.data.macros :as dm]
|
||||||
[app.common.pages.helpers :as cph]
|
[app.common.pages.helpers :as cph]
|
||||||
|
[app.common.types.components-list :as ctkl]
|
||||||
[app.common.types.page :as ctp]
|
[app.common.types.page :as ctp]
|
||||||
[app.main.data.events :as ev]
|
[app.main.data.events :as ev]
|
||||||
[app.main.data.modal :as modal]
|
[app.main.data.modal :as modal]
|
||||||
|
@ -18,6 +19,7 @@
|
||||||
[app.main.data.workspace.libraries :as dwl]
|
[app.main.data.workspace.libraries :as dwl]
|
||||||
[app.main.data.workspace.selection :as dws]
|
[app.main.data.workspace.selection :as dws]
|
||||||
[app.main.data.workspace.shortcuts :as sc]
|
[app.main.data.workspace.shortcuts :as sc]
|
||||||
|
[app.main.features :as features]
|
||||||
[app.main.refs :as refs]
|
[app.main.refs :as refs]
|
||||||
[app.main.store :as st]
|
[app.main.store :as st]
|
||||||
[app.main.ui.components.dropdown :refer [dropdown]]
|
[app.main.ui.components.dropdown :refer [dropdown]]
|
||||||
|
@ -373,9 +375,19 @@
|
||||||
component-file (-> shapes first :component-file)
|
component-file (-> shapes first :component-file)
|
||||||
component-shapes (filter #(contains? % :component-id) shapes)
|
component-shapes (filter #(contains? % :component-id) shapes)
|
||||||
|
|
||||||
|
components-v2 (features/use-feature :components-v2)
|
||||||
|
|
||||||
current-file-id (mf/use-ctx ctx/current-file-id)
|
current-file-id (mf/use-ctx ctx/current-file-id)
|
||||||
local-component? (= component-file current-file-id)
|
local-component? (= component-file current-file-id)
|
||||||
|
|
||||||
|
local-library (when local-component?
|
||||||
|
;; Not needed to subscribe to changes because it's not expected
|
||||||
|
;; to change while context menu is open
|
||||||
|
(deref refs/workspace-local-library))
|
||||||
|
|
||||||
|
main-component (when local-component?
|
||||||
|
(ctkl/get-component local-library (:component-id (first shapes))))
|
||||||
|
|
||||||
do-add-component #(st/emit! (dwl/add-component))
|
do-add-component #(st/emit! (dwl/add-component))
|
||||||
do-detach-component #(st/emit! (dwl/detach-component shape-id))
|
do-detach-component #(st/emit! (dwl/detach-component shape-id))
|
||||||
do-detach-component-in-bulk #(st/emit! dwl/detach-selected-components)
|
do-detach-component-in-bulk #(st/emit! dwl/detach-selected-components)
|
||||||
|
@ -384,6 +396,7 @@
|
||||||
do-navigate-component-file #(st/emit! (dwl/nav-to-component-file component-file))
|
do-navigate-component-file #(st/emit! (dwl/nav-to-component-file component-file))
|
||||||
do-update-component #(st/emit! (dwl/update-component-sync shape-id component-file))
|
do-update-component #(st/emit! (dwl/update-component-sync shape-id component-file))
|
||||||
do-update-component-in-bulk #(st/emit! (dwl/update-component-in-bulk component-shapes component-file))
|
do-update-component-in-bulk #(st/emit! (dwl/update-component-in-bulk component-shapes component-file))
|
||||||
|
do-restore-component #(st/emit! (dwl/restore-component component-id))
|
||||||
|
|
||||||
do-update-remote-component
|
do-update-remote-component
|
||||||
#(st/emit! (modal/show
|
#(st/emit! (modal/show
|
||||||
|
@ -436,11 +449,14 @@
|
||||||
|
|
||||||
|
|
||||||
(if local-component?
|
(if local-component?
|
||||||
[:*
|
(if (and (nil? main-component) components-v2)
|
||||||
[:& menu-entry {:title (tr "workspace.shape.menu.update-main")
|
[:& menu-entry {:title (tr "workspace.shape.menu.restore-main")
|
||||||
:on-click do-update-component}]
|
:on-click do-restore-component}]
|
||||||
[:& menu-entry {:title (tr "workspace.shape.menu.show-main")
|
[:*
|
||||||
:on-click do-show-component}]]
|
[:& menu-entry {:title (tr "workspace.shape.menu.update-main")
|
||||||
|
:on-click do-update-component}]
|
||||||
|
[:& menu-entry {:title (tr "workspace.shape.menu.show-main")
|
||||||
|
:on-click do-show-component}]])
|
||||||
|
|
||||||
[:*
|
[:*
|
||||||
[:& menu-entry {:title (tr "workspace.shape.menu.go-main")
|
[:& menu-entry {:title (tr "workspace.shape.menu.go-main")
|
||||||
|
|
|
@ -6,9 +6,11 @@
|
||||||
|
|
||||||
(ns app.main.ui.workspace.sidebar.options.menus.component
|
(ns app.main.ui.workspace.sidebar.options.menus.component
|
||||||
(:require
|
(:require
|
||||||
|
[app.common.types.components-list :as ctkl]
|
||||||
[app.main.data.modal :as modal]
|
[app.main.data.modal :as modal]
|
||||||
[app.main.data.workspace :as dw]
|
[app.main.data.workspace :as dw]
|
||||||
[app.main.data.workspace.libraries :as dwl]
|
[app.main.data.workspace.libraries :as dwl]
|
||||||
|
[app.main.refs :as refs]
|
||||||
[app.main.store :as st]
|
[app.main.store :as st]
|
||||||
[app.main.ui.components.context-menu :refer [context-menu]]
|
[app.main.ui.components.context-menu :refer [context-menu]]
|
||||||
[app.main.ui.context :as ctx]
|
[app.main.ui.context :as ctx]
|
||||||
|
@ -34,6 +36,15 @@
|
||||||
(:main-instance? values)
|
(:main-instance? values)
|
||||||
true)
|
true)
|
||||||
|
|
||||||
|
local-component? (= library-id current-file-id)
|
||||||
|
local-library (when local-component?
|
||||||
|
;; Not needed to subscribe to changes because it's not expected
|
||||||
|
;; to change while context menu is open
|
||||||
|
(deref refs/workspace-local-library))
|
||||||
|
|
||||||
|
main-component (when local-component?
|
||||||
|
(ctkl/get-component local-library component-id))
|
||||||
|
|
||||||
on-menu-click
|
on-menu-click
|
||||||
(mf/use-callback
|
(mf/use-callback
|
||||||
(fn [event]
|
(fn [event]
|
||||||
|
@ -54,6 +65,9 @@
|
||||||
do-update-component
|
do-update-component
|
||||||
#(st/emit! (dwl/update-component-sync id library-id))
|
#(st/emit! (dwl/update-component-sync id library-id))
|
||||||
|
|
||||||
|
do-restore-component
|
||||||
|
#(st/emit! (dwl/restore-component component-id))
|
||||||
|
|
||||||
do-update-remote-component
|
do-update-remote-component
|
||||||
#(st/emit! (modal/show
|
#(st/emit! (modal/show
|
||||||
{:type :confirm
|
{:type :confirm
|
||||||
|
@ -85,11 +99,13 @@
|
||||||
;; app/main/ui/workspace/context_menu.cljs
|
;; app/main/ui/workspace/context_menu.cljs
|
||||||
[:& context-menu {:on-close on-menu-close
|
[:& context-menu {:on-close on-menu-close
|
||||||
:show (:menu-open @local)
|
:show (:menu-open @local)
|
||||||
:options (if (= library-id current-file-id)
|
:options (if local-component?
|
||||||
[[(tr "workspace.shape.menu.detach-instance") do-detach-component]
|
(if (and (nil? main-component) components-v2)
|
||||||
[(tr "workspace.shape.menu.reset-overrides") do-reset-component]
|
[[(tr "workspace.shape.menu.restore-main") do-restore-component]]
|
||||||
[(tr "workspace.shape.menu.update-main") do-update-component]
|
[[(tr "workspace.shape.menu.detach-instance") do-detach-component]
|
||||||
[(tr "workspace.shape.menu.show-main") do-show-component]]
|
[(tr "workspace.shape.menu.reset-overrides") do-reset-component]
|
||||||
|
[(tr "workspace.shape.menu.update-main") do-update-component]
|
||||||
|
[(tr "workspace.shape.menu.show-main") do-show-component]])
|
||||||
|
|
||||||
[[(tr "workspace.shape.menu.detach-instance") do-detach-component]
|
[[(tr "workspace.shape.menu.detach-instance") do-detach-component]
|
||||||
[(tr "workspace.shape.menu.reset-overrides") do-reset-component]
|
[(tr "workspace.shape.menu.reset-overrides") do-reset-component]
|
||||||
|
|
|
@ -400,7 +400,8 @@
|
||||||
component-id (get-meta node :component-id uuid/uuid)
|
component-id (get-meta node :component-id uuid/uuid)
|
||||||
component-file (get-meta node :component-file uuid/uuid)
|
component-file (get-meta node :component-file uuid/uuid)
|
||||||
shape-ref (get-meta node :shape-ref uuid/uuid)
|
shape-ref (get-meta node :shape-ref uuid/uuid)
|
||||||
component-root? (get-meta node :component-root str->bool)]
|
component-root? (get-meta node :component-root str->bool)
|
||||||
|
main-instance? (get-meta node :main-instance str->bool)]
|
||||||
|
|
||||||
(cond-> props
|
(cond-> props
|
||||||
(some? stroke-color-ref-id)
|
(some? stroke-color-ref-id)
|
||||||
|
@ -414,6 +415,9 @@
|
||||||
component-root?
|
component-root?
|
||||||
(assoc :component-root? component-root?)
|
(assoc :component-root? component-root?)
|
||||||
|
|
||||||
|
main-instance?
|
||||||
|
(assoc :main-instance? main-instance?)
|
||||||
|
|
||||||
(some? shape-ref)
|
(some? shape-ref)
|
||||||
(assoc :shape-ref shape-ref))))
|
(assoc :shape-ref shape-ref))))
|
||||||
|
|
||||||
|
|
|
@ -40,17 +40,18 @@
|
||||||
(reduce format-page {}))]
|
(reduce format-page {}))]
|
||||||
(-> manifest
|
(-> manifest
|
||||||
(assoc (str (:id file))
|
(assoc (str (:id file))
|
||||||
{:name name
|
{:name name
|
||||||
:shared is-shared
|
:shared is-shared
|
||||||
:pages pages
|
:pages pages
|
||||||
:pagesIndex index
|
:pagesIndex index
|
||||||
:version current-version
|
:version current-version
|
||||||
:libraries (->> (:libraries file) (into #{}) (mapv str))
|
:libraries (->> (:libraries file) (into #{}) (mapv str))
|
||||||
:exportType (d/name export-type)
|
:exportType (d/name export-type)
|
||||||
:hasComponents (d/not-empty? (get-in file [:data :components]))
|
:hasComponents (d/not-empty? (get-in file [:data :components]))
|
||||||
:hasMedia (d/not-empty? (get-in file [:data :media]))
|
:hasDeletedComponents (d/not-empty? (get-in file [:data :deleted-components]))
|
||||||
:hasColors (d/not-empty? (get-in file [:data :colors]))
|
:hasMedia (d/not-empty? (get-in file [:data :media]))
|
||||||
:hasTypographies (d/not-empty? (get-in file [:data :typographies]))}))))]
|
:hasColors (d/not-empty? (get-in file [:data :colors]))
|
||||||
|
:hasTypographies (d/not-empty? (get-in file [:data :typographies]))}))))]
|
||||||
(let [manifest {:teamId (str team-id)
|
(let [manifest {:teamId (str team-id)
|
||||||
:fileId (str file-id)
|
:fileId (str file-id)
|
||||||
:files (->> (vals files) (reduce format-file {}))}]
|
:files (->> (vals files) (reduce format-file {}))}]
|
||||||
|
@ -146,9 +147,14 @@
|
||||||
|
|
||||||
(defn parse-library-components
|
(defn parse-library-components
|
||||||
[file]
|
[file]
|
||||||
(->> (r/render-components (:data file))
|
(->> (r/render-components (:data file) :components)
|
||||||
(rx/map #(vector (str (:id file) "/components.svg") %))))
|
(rx/map #(vector (str (:id file) "/components.svg") %))))
|
||||||
|
|
||||||
|
(defn parse-deleted-components
|
||||||
|
[file]
|
||||||
|
(->> (r/render-components (:data file) :deleted-components)
|
||||||
|
(rx/map #(vector (str (:id file) "/deleted-components.svg") %))))
|
||||||
|
|
||||||
(defn fetch-file-with-libraries [file-id components-v2]
|
(defn fetch-file-with-libraries [file-id components-v2]
|
||||||
(->> (rx/zip (rp/query :file {:id file-id :components-v2 components-v2})
|
(->> (rx/zip (rp/query :file {:id file-id :components-v2 components-v2})
|
||||||
(rp/query :file-libraries {:file-id file-id}))
|
(rp/query :file-libraries {:file-id file-id}))
|
||||||
|
@ -426,6 +432,12 @@
|
||||||
(rx/filter #(d/not-empty? (get-in % [:data :components])))
|
(rx/filter #(d/not-empty? (get-in % [:data :components])))
|
||||||
(rx/flat-map parse-library-components))
|
(rx/flat-map parse-library-components))
|
||||||
|
|
||||||
|
deleted-components-stream
|
||||||
|
(->> files-stream
|
||||||
|
(rx/flat-map vals)
|
||||||
|
(rx/filter #(d/not-empty? (get-in % [:data :deleted-components])))
|
||||||
|
(rx/flat-map parse-deleted-components))
|
||||||
|
|
||||||
pages-stream
|
pages-stream
|
||||||
(->> render-stream
|
(->> render-stream
|
||||||
(rx/map collect-page))]
|
(rx/map collect-page))]
|
||||||
|
@ -441,6 +453,7 @@
|
||||||
manifest-stream
|
manifest-stream
|
||||||
pages-stream
|
pages-stream
|
||||||
components-stream
|
components-stream
|
||||||
|
deleted-components-stream
|
||||||
media-stream
|
media-stream
|
||||||
colors-stream
|
colors-stream
|
||||||
typographies-stream)
|
typographies-stream)
|
||||||
|
|
|
@ -46,14 +46,15 @@
|
||||||
([context type id media]
|
([context type id media]
|
||||||
(let [file-id (:file-id context)
|
(let [file-id (:file-id context)
|
||||||
path (case type
|
path (case type
|
||||||
:manifest (str "manifest.json")
|
:manifest (str "manifest.json")
|
||||||
:page (str file-id "/" id ".svg")
|
:page (str file-id "/" id ".svg")
|
||||||
:colors (str file-id "/colors.json")
|
:colors (str file-id "/colors.json")
|
||||||
:typographies (str file-id "/typographies.json")
|
:typographies (str file-id "/typographies.json")
|
||||||
:media-list (str file-id "/media.json")
|
:media-list (str file-id "/media.json")
|
||||||
:media (let [ext (cm/mtype->extension (:mtype media))]
|
:media (let [ext (cm/mtype->extension (:mtype media))]
|
||||||
(str/concat file-id "/media/" id ext))
|
(str/concat file-id "/media/" id ext))
|
||||||
:components (str file-id "/components.svg"))
|
:components (str file-id "/components.svg")
|
||||||
|
:deleted-components (str file-id "/deleted-components.svg"))
|
||||||
|
|
||||||
parse-svg? (and (not= type :media) (str/ends-with? path "svg"))
|
parse-svg? (and (not= type :media) (str/ends-with? path "svg"))
|
||||||
parse-json? (and (not= type :media) (str/ends-with? path "json"))
|
parse-json? (and (not= type :media) (str/ends-with? path "json"))
|
||||||
|
@ -125,7 +126,7 @@
|
||||||
|
|
||||||
(defn create-file
|
(defn create-file
|
||||||
"Create a new file on the back-end"
|
"Create a new file on the back-end"
|
||||||
[context]
|
[context components-v2]
|
||||||
(let [resolve (:resolve context)
|
(let [resolve (:resolve context)
|
||||||
file-id (resolve (:file-id context))]
|
file-id (resolve (:file-id context))]
|
||||||
(rp/mutation :create-temp-file
|
(rp/mutation :create-temp-file
|
||||||
|
@ -133,7 +134,9 @@
|
||||||
:name (:name context)
|
:name (:name context)
|
||||||
:is-shared (:shared context)
|
:is-shared (:shared context)
|
||||||
:project-id (:project-id context)
|
:project-id (:project-id context)
|
||||||
:data (-> ctf/empty-file-data (assoc :id file-id))})))
|
:data (-> ctf/empty-file-data
|
||||||
|
(assoc :id file-id)
|
||||||
|
(assoc-in [:options :components-v2] components-v2))})))
|
||||||
|
|
||||||
(defn link-file-libraries
|
(defn link-file-libraries
|
||||||
"Create a new file on the back-end"
|
"Create a new file on the back-end"
|
||||||
|
@ -380,18 +383,22 @@
|
||||||
(rx/map (comp fb/close-page setup-interactions))))))))
|
(rx/map (comp fb/close-page setup-interactions))))))))
|
||||||
|
|
||||||
(defn import-component [context file node]
|
(defn import-component [context file node]
|
||||||
(let [resolve (:resolve context)
|
(let [resolve (:resolve context)
|
||||||
content (cip/find-node node :g)
|
content (cip/find-node node :g)
|
||||||
file-id (:id file)
|
file-id (:id file)
|
||||||
old-id (cip/get-id node)
|
old-id (cip/get-id node)
|
||||||
id (resolve old-id)
|
id (resolve old-id)
|
||||||
path (get-in node [:attrs :penpot:path] "")
|
path (get-in node [:attrs :penpot:path] "")
|
||||||
data (-> (cip/parse-data :group content)
|
main-instance-id (resolve (uuid (get-in node [:attrs :penpot:main-instance-id] "")))
|
||||||
(assoc :path path)
|
main-instance-page (resolve (uuid (get-in node [:attrs :penpot:main-instance-page] "")))
|
||||||
(assoc :id id))
|
data (-> (cip/parse-data :group content)
|
||||||
|
(assoc :path path)
|
||||||
|
(assoc :id id)
|
||||||
|
(assoc :main-instance-id main-instance-id)
|
||||||
|
(assoc :main-instance-page main-instance-page))
|
||||||
|
|
||||||
file (-> file (fb/start-component data))
|
file (-> file (fb/start-component data))
|
||||||
children (cip/node-seq node)]
|
children (cip/node-seq node)]
|
||||||
|
|
||||||
(->> (rx/from children)
|
(->> (rx/from children)
|
||||||
(rx/filter cip/shape?)
|
(rx/filter cip/shape?)
|
||||||
|
@ -401,6 +408,43 @@
|
||||||
(rx/reduce (partial process-import-node context) file)
|
(rx/reduce (partial process-import-node context) file)
|
||||||
(rx/map fb/finish-component))))
|
(rx/map fb/finish-component))))
|
||||||
|
|
||||||
|
(defn import-deleted-component [context file node]
|
||||||
|
(let [resolve (:resolve context)
|
||||||
|
content (cip/find-node node :g)
|
||||||
|
file-id (:id file)
|
||||||
|
old-id (cip/get-id node)
|
||||||
|
id (resolve old-id)
|
||||||
|
path (get-in node [:attrs :penpot:path] "")
|
||||||
|
main-instance-id (resolve (uuid (get-in node [:attrs :penpot:main-instance-id] "")))
|
||||||
|
main-instance-page (resolve (uuid (get-in node [:attrs :penpot:main-instance-page] "")))
|
||||||
|
main-instance-x (get-in node [:attrs :penpot:main-instance-x] "")
|
||||||
|
main-instance-y (get-in node [:attrs :penpot:main-instance-y] "")
|
||||||
|
|
||||||
|
data (-> (cip/parse-data :group content)
|
||||||
|
(assoc :path path)
|
||||||
|
(assoc :id id)
|
||||||
|
(assoc :main-instance-id main-instance-id)
|
||||||
|
(assoc :main-instance-page main-instance-page)
|
||||||
|
(assoc :main-instance-x main-instance-x)
|
||||||
|
(assoc :main-instance-y main-instance-y))
|
||||||
|
|
||||||
|
file (-> file (fb/start-component data))
|
||||||
|
component-id (:current-component-id file)
|
||||||
|
children (cip/node-seq node)]
|
||||||
|
|
||||||
|
(->> (rx/from children)
|
||||||
|
(rx/filter cip/shape?)
|
||||||
|
(rx/skip 1)
|
||||||
|
(rx/skip-last 1)
|
||||||
|
(rx/mapcat (partial resolve-media context file-id))
|
||||||
|
(rx/reduce (partial process-import-node context) file)
|
||||||
|
(rx/map fb/finish-component)
|
||||||
|
(rx/map (partial fb/finish-deleted-component
|
||||||
|
component-id
|
||||||
|
main-instance-page
|
||||||
|
main-instance-x
|
||||||
|
main-instance-y)))))
|
||||||
|
|
||||||
(defn process-pages
|
(defn process-pages
|
||||||
[context file]
|
[context file]
|
||||||
(let [index (:pages-index context)
|
(let [index (:pages-index context)
|
||||||
|
@ -486,6 +530,18 @@
|
||||||
(rx/concat-reduce (partial import-component context) file)))
|
(rx/concat-reduce (partial import-component context) file)))
|
||||||
(rx/of file)))
|
(rx/of file)))
|
||||||
|
|
||||||
|
(defn process-deleted-components
|
||||||
|
[context file]
|
||||||
|
(if (:has-deleted-components context)
|
||||||
|
(let [split-components
|
||||||
|
(fn [content] (->> (cip/node-seq content)
|
||||||
|
(filter #(= :symbol (:tag %)))))]
|
||||||
|
|
||||||
|
(->> (get-file context :deleted-components)
|
||||||
|
(rx/flat-map split-components)
|
||||||
|
(rx/concat-reduce (partial import-deleted-component context) file)))
|
||||||
|
(rx/of file)))
|
||||||
|
|
||||||
(defn process-file
|
(defn process-file
|
||||||
[context file]
|
[context file]
|
||||||
|
|
||||||
|
@ -502,18 +558,20 @@
|
||||||
(rx/flat-map (partial process-library-media context))
|
(rx/flat-map (partial process-library-media context))
|
||||||
(rx/tap #(progress! context :process-components))
|
(rx/tap #(progress! context :process-components))
|
||||||
(rx/flat-map (partial process-library-components context))
|
(rx/flat-map (partial process-library-components context))
|
||||||
|
(rx/tap #(progress! context :process-deleted-components))
|
||||||
|
(rx/flat-map (partial process-deleted-components context))
|
||||||
(rx/flat-map (partial send-changes context))
|
(rx/flat-map (partial send-changes context))
|
||||||
(rx/tap #(rx/end! progress-str)))]))
|
(rx/tap #(rx/end! progress-str)))]))
|
||||||
|
|
||||||
(defn create-files
|
(defn create-files
|
||||||
[context files]
|
[context files components-v2]
|
||||||
|
|
||||||
(let [data (group-by :file-id files)]
|
(let [data (group-by :file-id files)]
|
||||||
(rx/concat
|
(rx/concat
|
||||||
(->> (rx/from files)
|
(->> (rx/from files)
|
||||||
(rx/map #(merge context %))
|
(rx/map #(merge context %))
|
||||||
(rx/flat-map (fn [context]
|
(rx/flat-map (fn [context]
|
||||||
(->> (create-file context)
|
(->> (create-file context components-v2)
|
||||||
(rx/map #(vector % (first (get data (:file-id context)))))))))
|
(rx/map #(vector % (first (get data (:file-id context)))))))))
|
||||||
|
|
||||||
(->> (rx/from files)
|
(->> (rx/from files)
|
||||||
|
@ -564,7 +622,7 @@
|
||||||
(rx/catch #(rx/of {:uri (:uri file) :error (.-message %)}))))))))
|
(rx/catch #(rx/of {:uri (:uri file) :error (.-message %)}))))))))
|
||||||
|
|
||||||
(defmethod impl/handler :import-files
|
(defmethod impl/handler :import-files
|
||||||
[{:keys [project-id files]}]
|
[{:keys [project-id files components-v2]}]
|
||||||
|
|
||||||
(let [context {:project-id project-id
|
(let [context {:project-id project-id
|
||||||
:resolve (resolve-factory)}
|
:resolve (resolve-factory)}
|
||||||
|
@ -572,7 +630,7 @@
|
||||||
binary-files (filter #(= "application/octet-stream" (:type %)) files)]
|
binary-files (filter #(= "application/octet-stream" (:type %)) files)]
|
||||||
|
|
||||||
(->> (rx/merge
|
(->> (rx/merge
|
||||||
(->> (create-files context zip-files)
|
(->> (create-files context zip-files components-v2)
|
||||||
(rx/flat-map
|
(rx/flat-map
|
||||||
(fn [[file data]]
|
(fn [[file data]]
|
||||||
(->> (uz/load-from-url (:uri data))
|
(->> (uz/load-from-url (:uri data))
|
||||||
|
|
|
@ -4303,6 +4303,9 @@ msgstr "Update main components"
|
||||||
msgid "workspace.shape.menu.update-main"
|
msgid "workspace.shape.menu.update-main"
|
||||||
msgstr "Update main component"
|
msgstr "Update main component"
|
||||||
|
|
||||||
|
msgid "workspace.shape.menu.restore-main"
|
||||||
|
msgstr "Restore main component"
|
||||||
|
|
||||||
#: src/app/main/ui/workspace/left_toolbar.cljs
|
#: src/app/main/ui/workspace/left_toolbar.cljs
|
||||||
msgid "workspace.sidebar.history"
|
msgid "workspace.sidebar.history"
|
||||||
msgstr "History (%s)"
|
msgstr "History (%s)"
|
||||||
|
|
|
@ -4498,6 +4498,9 @@ msgstr "Actualizar componentes"
|
||||||
msgid "workspace.shape.menu.update-main"
|
msgid "workspace.shape.menu.update-main"
|
||||||
msgstr "Actualizar componente principal"
|
msgstr "Actualizar componente principal"
|
||||||
|
|
||||||
|
msgid "workspace.shape.menu.restore-main"
|
||||||
|
msgstr "Restaurar componente principal"
|
||||||
|
|
||||||
#: src/app/main/ui/workspace/left_toolbar.cljs
|
#: src/app/main/ui/workspace/left_toolbar.cljs
|
||||||
msgid "workspace.sidebar.history"
|
msgid "workspace.sidebar.history"
|
||||||
msgstr "Historial (%s)"
|
msgstr "Historial (%s)"
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue