🎉 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.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]}]
@ -372,7 +371,15 @@
(defmethod process-change :del-component (defmethod process-change :del-component
[data {:keys [id]}] [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 ;; -- 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

View file

@ -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
(fn [undo-changes]
(cond-> undo-changes
components-v2
(d/preconj {:type :purge-component
:id id})
:always
(d/preconj {:type :add-component
:id id :id id
:name (:name prev-component) :name (:name prev-component)
:path (:path prev-component) :path (:path prev-component)
:main-instance-id (:main-instance-id prev-component) :main-instance-id (:main-instance-id prev-component)
:main-instance-page (:main-instance-page prev-component) :main-instance-page (:main-instance-page prev-component)
:shapes (vals (:objects 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 [_] (defmethod change-spec :del-component [_]
(s/keys :req-un [::id])) (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 [_] (defmethod change-spec :add-typography [_]
(s/keys :req-un [::ctt/typography])) (s/keys :req-un [::ctt/typography]))

View file

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

View file

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

View file

@ -120,6 +120,54 @@
;; 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]
(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? (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 +233,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)

View file

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

View file

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

View file

@ -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]
@ -394,12 +395,49 @@
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."

View file

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

View file

@ -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.restore-main")
:on-click do-restore-component}]
[:* [:*
[:& menu-entry {:title (tr "workspace.shape.menu.update-main") [:& menu-entry {:title (tr "workspace.shape.menu.update-main")
:on-click do-update-component}] :on-click do-update-component}]
[:& menu-entry {:title (tr "workspace.shape.menu.show-main") [:& menu-entry {:title (tr "workspace.shape.menu.show-main")
:on-click do-show-component}]] :on-click do-show-component}]])
[:* [:*
[:& menu-entry {:title (tr "workspace.shape.menu.go-main") [:& menu-entry {:title (tr "workspace.shape.menu.go-main")

View file

@ -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?
(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.detach-instance") do-detach-component]
[(tr "workspace.shape.menu.reset-overrides") do-reset-component] [(tr "workspace.shape.menu.reset-overrides") do-reset-component]
[(tr "workspace.shape.menu.update-main") do-update-component] [(tr "workspace.shape.menu.update-main") do-update-component]
[(tr "workspace.shape.menu.show-main") do-show-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]

View file

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

View file

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