mirror of
https://github.com/penpot/penpot.git
synced 2025-03-13 08:11:30 -05:00
🎉 Allow to restore deleted components
This commit is contained in:
parent
ee1058950e
commit
251e7eada2
14 changed files with 209 additions and 34 deletions
|
@ -19,6 +19,7 @@
|
|||
[app.common.types.components-list :as ctkl]
|
||||
[app.common.types.container :as ctn]
|
||||
[app.common.types.colors-list :as ctcl]
|
||||
[app.common.types.file :as ctf]
|
||||
[app.common.types.page :as ctp]
|
||||
[app.common.types.pages-list :as ctpl]
|
||||
[app.common.types.shape :as cts]
|
||||
|
@ -302,9 +303,7 @@
|
|||
|
||||
(defmethod process-change :del-page
|
||||
[data {:keys [id]}]
|
||||
(-> data
|
||||
(update :pages (fn [pages] (filterv #(not= % id) pages)))
|
||||
(update :pages-index dissoc id)))
|
||||
(ctpl/delete-page data id))
|
||||
|
||||
(defmethod process-change :mov-page
|
||||
[data {:keys [id index]}]
|
||||
|
@ -320,7 +319,7 @@
|
|||
|
||||
(defmethod process-change :del-color
|
||||
[data {:keys [id]}]
|
||||
(update data :colors dissoc id))
|
||||
(ctcl/delete-color data id))
|
||||
|
||||
(defmethod process-change :add-recent-color
|
||||
[data {:keys [color]}]
|
||||
|
@ -372,7 +371,15 @@
|
|||
|
||||
(defmethod process-change :del-component
|
||||
[data {:keys [id]}]
|
||||
(d/dissoc-in data [:components id]))
|
||||
(ctf/delete-component data id))
|
||||
|
||||
(defmethod process-change :restore-component
|
||||
[data {:keys [id]}]
|
||||
(ctf/restore-component data id))
|
||||
|
||||
(defmethod process-change :purge-component
|
||||
[data {:keys [id]}]
|
||||
(ctf/purge-component data id))
|
||||
|
||||
;; -- Typography
|
||||
|
||||
|
@ -386,7 +393,7 @@
|
|||
|
||||
(defmethod process-change :del-typography
|
||||
[data {:keys [id]}]
|
||||
(update data :typographies dissoc id))
|
||||
(ctyl/delete-typography data id))
|
||||
|
||||
;; === Operations
|
||||
|
||||
|
|
|
@ -617,18 +617,34 @@
|
|||
changes)))
|
||||
|
||||
(defn delete-component
|
||||
[changes id]
|
||||
[changes id components-v2]
|
||||
(assert-library changes)
|
||||
(let [library-data (::library-data (meta changes))
|
||||
prev-component (get-in library-data [:components id])]
|
||||
(-> changes
|
||||
(update :redo-changes conj {:type :del-component
|
||||
:id id})
|
||||
(update :undo-changes 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))}))))
|
||||
(update :undo-changes
|
||||
(fn [undo-changes]
|
||||
(cond-> undo-changes
|
||||
components-v2
|
||||
(d/preconj {:type :purge-component
|
||||
:id id})
|
||||
|
||||
: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})))
|
||||
|
|
|
@ -162,6 +162,12 @@
|
|||
(defmethod change-spec :del-component [_]
|
||||
(s/keys :req-un [::id]))
|
||||
|
||||
(defmethod change-spec :restore-component [_]
|
||||
(s/keys :req-un [::id]))
|
||||
|
||||
(defmethod change-spec :purge-component [_]
|
||||
(s/keys :req-un [::id]))
|
||||
|
||||
(defmethod change-spec :add-typography [_]
|
||||
(s/keys :req-un [::ctt/typography]))
|
||||
|
||||
|
|
|
@ -22,3 +22,7 @@
|
|||
[file-data 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]
|
||||
(update-in file-data [:components component-id] f))
|
||||
|
||||
(defn delete-component
|
||||
[file-data component-id]
|
||||
(update file-data :components dissoc component-id))
|
||||
|
||||
|
|
|
@ -120,6 +120,54 @@
|
|||
|
||||
;; 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]
|
||||
(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
|
||||
components-v2
|
||||
(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?
|
||||
"Checks if a shape uses the given asset."
|
||||
(fn [asset-type _ _ _] asset-type))
|
||||
|
@ -185,7 +233,7 @@
|
|||
|
||||
(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."
|
||||
main instances for all components there. Mark the file with the :components-v2 option."
|
||||
[file-data]
|
||||
(let [components (ctkl/components-seq file-data)]
|
||||
(if (or (empty? components)
|
||||
|
|
|
@ -37,3 +37,9 @@
|
|||
[file-data 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)))
|
||||
|
||||
|
|
|
@ -22,3 +22,7 @@
|
|||
[file-data 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))
|
||||
|
||||
|
|
|
@ -18,6 +18,7 @@
|
|||
[app.common.types.color :as ctc]
|
||||
[app.common.types.container :as ctn]
|
||||
[app.common.types.file :as ctf]
|
||||
[app.common.types.pages-list :as ctpl]
|
||||
[app.common.types.shape-tree :as ctst]
|
||||
[app.common.types.typography :as ctt]
|
||||
[app.common.uuid :as uuid]
|
||||
|
@ -393,13 +394,50 @@
|
|||
(ptk/reify ::delete-component
|
||||
ptk/WatchEvent
|
||||
(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)
|
||||
(pcb/with-library-data data)
|
||||
(pcb/delete-component id))]
|
||||
(pcb/delete-component id components-v2))]
|
||||
|
||||
(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
|
||||
"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."
|
||||
|
|
|
@ -233,7 +233,16 @@
|
|||
(pcb/with-page page)
|
||||
(pcb/with-objects objects)
|
||||
(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 ids)
|
||||
(pcb/remove-objects empty-parents)
|
||||
|
@ -252,12 +261,7 @@
|
|||
(cond-> (seq starting-flows)
|
||||
(pcb/update-page-option :flows (fn [flows]
|
||||
(->> (map :id starting-flows)
|
||||
(reduce ctp/remove-flow flows))))))
|
||||
|
||||
changes (reduce (fn [changes component-id]
|
||||
(pcb/delete-component changes component-id))
|
||||
changes
|
||||
components-to-delete)]
|
||||
(reduce ctp/remove-flow flows))))))]
|
||||
|
||||
(rx/of (dc/detach-comment-thread ids)
|
||||
(dwsl/update-layout-positions all-parents)
|
||||
|
|
|
@ -10,6 +10,7 @@
|
|||
[app.common.data :as d]
|
||||
[app.common.data.macros :as dm]
|
||||
[app.common.pages.helpers :as cph]
|
||||
[app.common.types.components-list :as ctkl]
|
||||
[app.common.types.page :as ctp]
|
||||
[app.main.data.events :as ev]
|
||||
[app.main.data.modal :as modal]
|
||||
|
@ -18,6 +19,7 @@
|
|||
[app.main.data.workspace.libraries :as dwl]
|
||||
[app.main.data.workspace.selection :as dws]
|
||||
[app.main.data.workspace.shortcuts :as sc]
|
||||
[app.main.features :as features]
|
||||
[app.main.refs :as refs]
|
||||
[app.main.store :as st]
|
||||
[app.main.ui.components.dropdown :refer [dropdown]]
|
||||
|
@ -373,9 +375,19 @@
|
|||
component-file (-> shapes first :component-file)
|
||||
component-shapes (filter #(contains? % :component-id) shapes)
|
||||
|
||||
components-v2 (features/use-feature :components-v2)
|
||||
|
||||
current-file-id (mf/use-ctx ctx/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-detach-component #(st/emit! (dwl/detach-component shape-id))
|
||||
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-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-restore-component #(st/emit! (dwl/restore-component component-id))
|
||||
|
||||
do-update-remote-component
|
||||
#(st/emit! (modal/show
|
||||
|
@ -436,11 +449,14 @@
|
|||
|
||||
|
||||
(if local-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}]]
|
||||
(if (and (nil? main-component) components-v2)
|
||||
[:& menu-entry {:title (tr "workspace.shape.menu.restore-main")
|
||||
:on-click do-restore-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")
|
||||
|
|
|
@ -6,9 +6,11 @@
|
|||
|
||||
(ns app.main.ui.workspace.sidebar.options.menus.component
|
||||
(:require
|
||||
[app.common.types.components-list :as ctkl]
|
||||
[app.main.data.modal :as modal]
|
||||
[app.main.data.workspace :as dw]
|
||||
[app.main.data.workspace.libraries :as dwl]
|
||||
[app.main.refs :as refs]
|
||||
[app.main.store :as st]
|
||||
[app.main.ui.components.context-menu :refer [context-menu]]
|
||||
[app.main.ui.context :as ctx]
|
||||
|
@ -34,6 +36,15 @@
|
|||
(:main-instance? values)
|
||||
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
|
||||
(mf/use-callback
|
||||
(fn [event]
|
||||
|
@ -54,6 +65,9 @@
|
|||
do-update-component
|
||||
#(st/emit! (dwl/update-component-sync id library-id))
|
||||
|
||||
do-restore-component
|
||||
#(st/emit! (dwl/restore-component component-id))
|
||||
|
||||
do-update-remote-component
|
||||
#(st/emit! (modal/show
|
||||
{:type :confirm
|
||||
|
@ -85,11 +99,13 @@
|
|||
;; app/main/ui/workspace/context_menu.cljs
|
||||
[:& context-menu {:on-close on-menu-close
|
||||
:show (:menu-open @local)
|
||||
:options (if (= library-id current-file-id)
|
||||
[[(tr "workspace.shape.menu.detach-instance") do-detach-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]]
|
||||
:options (if local-component?
|
||||
(if (and (nil? main-component) components-v2)
|
||||
[[(tr "workspace.shape.menu.restore-main") do-restore-component]]
|
||||
[[(tr "workspace.shape.menu.detach-instance") do-detach-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.reset-overrides") do-reset-component]
|
||||
|
|
|
@ -4303,6 +4303,9 @@ msgstr "Update main components"
|
|||
msgid "workspace.shape.menu.update-main"
|
||||
msgstr "Update main component"
|
||||
|
||||
msgid "workspace.shape.menu.restore-main"
|
||||
msgstr "Restore main component"
|
||||
|
||||
#: src/app/main/ui/workspace/left_toolbar.cljs
|
||||
msgid "workspace.sidebar.history"
|
||||
msgstr "History (%s)"
|
||||
|
|
|
@ -4498,6 +4498,9 @@ msgstr "Actualizar componentes"
|
|||
msgid "workspace.shape.menu.update-main"
|
||||
msgstr "Actualizar componente principal"
|
||||
|
||||
msgid "workspace.shape.menu.restore-main"
|
||||
msgstr "Restaurar componente principal"
|
||||
|
||||
#: src/app/main/ui/workspace/left_toolbar.cljs
|
||||
msgid "workspace.sidebar.history"
|
||||
msgstr "Historial (%s)"
|
||||
|
|
Loading…
Add table
Reference in a new issue