0
Fork 0
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:
Andrés Moya 2022-08-29 09:23:51 +02:00
parent ee1058950e
commit 251e7eada2
14 changed files with 209 additions and 34 deletions

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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