0
Fork 0
mirror of https://github.com/penpot/penpot.git synced 2025-03-13 16:21:57 -05:00

🎉 Update component in a shared library

This commit is contained in:
Andrés Moya 2021-01-21 14:40:04 +01:00 committed by Andrey Antukh
parent 7f3ef7bb82
commit 870eff5826
10 changed files with 293 additions and 123 deletions

View file

@ -2113,6 +2113,38 @@
"es" : "Añadir “%s” como Biblioteca Compartida"
}
},
"modals.update-remote-component.message" : {
"translations" : {
"en" : "Update a component in a shared library",
"fr" : "",
"ru" : "",
"es" : "Actualizar un componente en librería"
}
},
"modals.update-remote-component.hint" : {
"translations" : {
"en" : "You are about to update a component in a shared library. This may affect other files that use it.",
"fr" : "",
"ru" : "",
"es" : "Vas a actualizar un componente en una librería compartida. Esto puede afectar a otros archivos que la usen."
}
},
"modals.update-remote-component.accept" : {
"translations" : {
"en" : "Update component",
"fr" : "",
"ru" : "",
"es" : "Actualizar componente"
}
},
"modals.update-remote-component.cancel" : {
"translations" : {
"en" : "Cancel",
"fr" : "",
"ru" : "",
"es" : "Cancelar"
}
},
"notifications.profile-deletion-not-allowed" : {
"used-in" : [ "src/app/main/ui/settings/delete_account.cljs:28" ],
"translations" : {

View file

@ -66,7 +66,8 @@
([changes undo-changes]
(commit-changes changes undo-changes {}))
([changes undo-changes {:keys [save-undo?
commit-local?]
commit-local?
file-id]
:or {save-undo? true
commit-local? false}
:as opts}]
@ -79,17 +80,25 @@
(let [error (volatile! nil)]
(ptk/reify ::commit-changes
cljs.core/IDeref
(-deref [_] changes)
(-deref [_] {:file-id file-id :changes changes})
ptk/UpdateEvent
(update [_ state]
(try
(let [state (update-in state [:workspace-file :data] cp/process-changes changes)]
(cond-> state
commit-local? (update :workspace-data cp/process-changes changes)))
(catch :default e
(vreset! error e)
state)))
(let [current-file-id (get state :current-file-id)
file-id (or file-id current-file-id)
path1 (if (= file-id current-file-id)
[:workspace-file :data]
[:workspace-libraries file-id :data])
path2 (if (= file-id current-file-id)
[:workspace-data]
[:workspace-libraries file-id :data])]
(try
(let [state (update-in state path1 cp/process-changes changes)]
(cond-> state
commit-local? (update-in path2 cp/process-changes changes)))
(catch :default e
(vreset! error e)
state))))
ptk/WatchEvent
(watch [_ state stream]

View file

@ -36,21 +36,21 @@
(log/set-level! :warn)
(defn- log-changes
[changes local-library]
[changes file]
(let [extract-change
(fn [change]
(let [shape (when (:id change)
(cond
(:page-id change)
(get-in local-library [:pages-index
(:page-id change)
:objects
(:id change)])
(get-in file [:pages-index
(:page-id change)
:objects
(:id change)])
(:component-id change)
(get-in local-library [:components
(:component-id change)
:objects
(:id change)])
(get-in file [:components
(:component-id change)
:objects
(:id change)])
:default nil))
prefix (if (:component-id change) "[C] " "[P] ")
@ -118,7 +118,7 @@
uchg {:type :mod-color
:color prev}]
(rx/of (dwc/commit-changes [rchg] [uchg] {:commit-local? true})
(sync-file file-id))))))
(sync-file (:current-file-id state) file-id))))))
(defn delete-color
[{:keys [id] :as params}]
@ -208,7 +208,7 @@
uchg {:type :mod-typography
:typography prev}]
(rx/of (dwc/commit-changes [rchg] [uchg] {:commit-local? true})
(sync-file file-id))))))
(sync-file (:current-file-id state) file-id))))))
(defn delete-typography
[id]
@ -337,7 +337,7 @@
(watch [_ state stream]
(let [component (cp/get-component id
(:current-file-id state)
(dwlh/get-local-library state)
(dwlh/get-local-file state)
nil)
all-components (vals (get-in state [:workspace-data :components]))
unames (set (map :name all-components))
@ -385,7 +385,7 @@
(ptk/reify ::instantiate-component
ptk/WatchEvent
(watch [_ state stream]
(let [local-library (dwlh/get-local-library state)
(let [local-library (dwlh/get-local-file state)
libraries (get state :workspace-libraries)
component (cp/get-component component-id file-id local-library libraries)
component-shape (cp/get-shape component component-id)
@ -529,14 +529,15 @@
(st/emit! (rt/nav-new-window :workspace pparams qparams))))))
(defn ext-library-changed
[file-id modified-at changes]
[file-id modified-at revn changes]
(us/assert ::us/uuid file-id)
(us/assert ::cp/changes changes)
(ptk/reify ::ext-library-changed
ptk/UpdateEvent
(update [_ state]
(-> state
(assoc-in [:workspace-libraries file-id :modified-at] modified-at)
(update-in [:workspace-libraries file-id]
#(assoc % :modified-at modified-at :revn revn))
(d/update-in-when [:workspace-libraries file-id :data]
cp/process-changes changes)))))
@ -550,7 +551,7 @@
ptk/WatchEvent
(watch [_ state stream]
(log/info :msg "RESET-COMPONENT of shape" :id (str id))
(let [local-library (dwlh/get-local-library state)
(let [local-library (dwlh/get-local-file state)
libraries (dwlh/get-libraries state)
container (cp/get-container (get state :current-page-id)
:page
@ -577,46 +578,84 @@
ptk/WatchEvent
(watch [_ state stream]
(log/info :msg "UPDATE-COMPONENT of shape" :id (str id))
(let [local-library (dwlh/get-local-library state)
(let [page-id (get state :current-page-id)
local-library (dwlh/get-local-file state)
libraries (dwlh/get-libraries state)
[rchanges uchanges]
(dwlh/generate-sync-shape-inverse (get state :current-page-id)
(dwlh/generate-sync-shape-inverse page-id
id
local-library
libraries)]
libraries)
(log/debug :msg "UPDATE-COMPONENT finished" :js/rchanges (log-changes
rchanges
local-library))
container (cp/get-container page-id :page local-library)
shape (cp/get-shape container id)
file-id (:component-file shape)
file (dwlh/get-file state file-id)
(rx/of (dwc/commit-changes rchanges uchanges {:commit-local? true}))))))
local-rchanges (->> rchanges
(filter :local-change?)
(map #(dissoc % :local-change?))
vec)
local-uchanges (->> uchanges
(filter :local-change?)
(map #(dissoc % :local-change?))
vec)
rchanges (->> rchanges
(remove :local-change?)
(map #(dissoc % :local-change?))
vec)
uchanges (->> uchanges
(remove :local-change?)
(map #(dissoc % :local-change?))
vec)]
(log/debug :msg "UPDATE-COMPONENT finished"
:js/local-rchanges (log-changes
local-rchanges
local-library)
:js/rchanges (log-changes
rchanges
file))
(rx/of (when (seq local-rchanges)
(dwc/commit-changes local-rchanges local-uchanges
{:commit-local? true
:file-id (:id local-library)}))
(when (seq rchanges)
(dwc/commit-changes rchanges uchanges
{:commit-local? true
:file-id file-id})))))))
(declare sync-file-2nd-stage)
(defn sync-file
"Syhchronize the library file with the given id, with the current file.
Walk through all shapes in all pages that use some color, typography or
component of the library file, and copy the new values to the shapes.
Do it also for shapes inside components of the local file library."
[file-id]
"Syhchronize the given file from ghe given library. Walk through all shapes
in all pages in the file that use some color, typography or component of the
library, and copy the new values to the shapes. Do it also for shapes inside
components of the local file library."
[file-id library-id]
(us/assert ::us/uuid file-id)
(us/assert ::us/uuid library-id)
(ptk/reify ::sync-file
ptk/UpdateEvent
(update [_ state]
(if (not= file-id (:current-file-id state))
(assoc-in state [:workspace-libraries file-id :synced-at] (dt/now))
(if (not= library-id (:current-file-id state))
(assoc-in state [:workspace-libraries library-id :synced-at] (dt/now))
state))
ptk/WatchEvent
(watch [_ state stream]
(log/info :msg "SYNC-FILE" :file (if (= file-id (:current-file-id state)) "local" (str file-id)))
(let [local-library (dwlh/get-local-library state)
library-changes [(dwlh/generate-sync-library :components file-id state)
(dwlh/generate-sync-library :colors file-id state)
(dwlh/generate-sync-library :typographies file-id state)]
file-changes [(dwlh/generate-sync-file :components file-id state)
(dwlh/generate-sync-file :colors file-id state)
(dwlh/generate-sync-file :typographies file-id state)]
(log/info :msg "SYNC-FILE"
:file (dwlh/pretty-file file-id state)
:library (dwlh/pretty-file library-id state))
(let [file (dwlh/get-file state file-id)
library-changes [(dwlh/generate-sync-library file-id :components library-id state)
(dwlh/generate-sync-library file-id :colors library-id state)
(dwlh/generate-sync-library file-id :typographies library-id state)]
file-changes [(dwlh/generate-sync-file file-id :components library-id state)
(dwlh/generate-sync-file file-id :colors library-id state)
(dwlh/generate-sync-file file-id :typographies library-id state)]
rchanges (d/concat []
(->> library-changes (remove nil?) (map first) (flatten))
(->> file-changes (remove nil?) (map first) (flatten)))
@ -625,17 +664,22 @@
(->> file-changes (remove nil?) (map second) (flatten)))]
(log/debug :msg "SYNC-FILE finished" :js/rchanges (log-changes
rchanges
local-library))
file))
(rx/concat
(rx/of (dm/hide-tag :sync-dialog))
(when rchanges
(rx/of (dwc/commit-changes rchanges uchanges {:commit-local? true})))
(when (not= file-id (:current-file-id state))
(rp/mutation :update-sync
{:file-id (get-in state [:workspace-file :id])
:library-id file-id}))
(rx/of (dwc/commit-changes rchanges uchanges {:commit-local? true
:file-id file-id})))
(when (not= file-id library-id)
;; When we have just updated the library file, give some time for the
;; update to finish, before marking this file as synced.
;; TODO: look for a more precise way of syncing this.
(rx/concat (rx/timer 3000)
(rp/mutation :update-sync
{:file-id file-id
:library-id library-id})))
(when (some? library-changes)
(rx/of (sync-file-2nd-stage file-id))))))))
(rx/of (sync-file-2nd-stage file-id library-id))))))))
(defn sync-file-2nd-stage
"If some components have been modified, we need to launch another synchronization
@ -646,22 +690,26 @@
;; recursively. But for this not to cause an infinite loop, we need to
;; implement updated-at at component level, to detect what components have
;; not changed, and then not to apply sync and terminate the loop.
[file-id]
[file-id library-id]
(us/assert ::us/uuid file-id)
(us/assert ::us/uuid library-id)
(ptk/reify ::sync-file-2nd-stage
ptk/WatchEvent
(watch [_ state stream]
(log/info :msg "SYNC-FILE (2nd stage)" :file (if (= file-id (:current-file-id state)) "local" (str file-id)))
(let [local-library (dwlh/get-local-library state)
[rchanges1 uchanges1] (dwlh/generate-sync-file :components file-id state)
[rchanges2 uchanges2] (dwlh/generate-sync-library :components file-id state)
(log/info :msg "SYNC-FILE (2nd stage)"
:file (dwlh/pretty-file file-id state)
:library (dwlh/pretty-file library-id state))
(let [file (dwlh/get-file state file-id)
[rchanges1 uchanges1] (dwlh/generate-sync-file file-id :components library-id state)
[rchanges2 uchanges2] (dwlh/generate-sync-library file-id :components library-id state)
rchanges (d/concat rchanges1 rchanges2)
uchanges (d/concat uchanges1 uchanges2)]
(when rchanges
(log/debug :msg "SYNC-FILE (2nd stage) finished" :js/rchanges (log-changes
rchanges
local-library))
(rx/of (dwc/commit-changes rchanges uchanges {:commit-local? true})))))))
file))
(rx/of (dwc/commit-changes rchanges uchanges {:commit-local? true
:file-id file-id})))))))
(def ignore-sync
(ptk/reify ::ignore-sync
@ -684,7 +732,8 @@
(let [libraries-need-sync (filter #(> (:modified-at %) (:synced-at %))
(vals (get state :workspace-libraries)))
do-update #(do (apply st/emit! (map (fn [library]
(sync-file (:id library)))
(sync-file (:current-file-id state)
(:id library)))
libraries-need-sync))
(st/emit! dm/hide))
do-dismiss #(do (st/emit! ignore-sync)

View file

@ -59,14 +59,26 @@
[(d/concat rchanges1 rchanges2)
(d/concat uchanges1 uchanges2)])
(defn get-local-library
(defn get-local-file
[state]
(get state :workspace-data))
(defn get-file
[state file-id]
(if (= file-id (:current-file-id state))
(get state :workspace-data)
(get-in state [:workspace-libraries file-id :data])))
(defn get-libraries
[state]
(get state :workspace-libraries))
(defn pretty-file
[file-id state]
(if (= file-id (:current-file-id state))
"<local>"
(str "<" (get-in state [:workspace-libraries file-id :name]) ">")))
;; ---- Create a new component ----
@ -124,25 +136,26 @@
;; ---- General library synchronization functions ----
(defn generate-sync-file
"Generate changes to synchronize all shapes in all pages of the current file,
"Generate changes to synchronize all shapes in all pages of the given file,
that use assets of the given type in the given library."
[asset-type library-id state]
[file-id asset-type library-id state]
(s/assert #{:colors :components :typographies} asset-type)
(s/assert ::us/uuid file-id)
(s/assert ::us/uuid library-id)
(log/info :msg "Sync local file with library"
(log/info :msg "Sync file with library"
:asset-type asset-type
:library (str (or library-id "local")))
:file (pretty-file file-id state)
:library (pretty-file library-id state))
(let [library-items
(if (= library-id (:current-file-id state))
(get-in state [:workspace-data asset-type])
(get-in state [:workspace-libraries library-id :data asset-type]))]
(let [file (get-file state file-id)
library (get-file state library-id)
library-items (get library asset-type)]
(if (empty? library-items)
empty-changes
(loop [pages (vals (get-in state [:workspace-data :pages-index]))
(loop [pages (vals (get file :pages-index))
rchanges []
uchanges []]
(if-let [page (first pages)]
@ -157,22 +170,24 @@
[rchanges uchanges])))))
(defn generate-sync-library
"Generate changes to synchronize all shapes in all components of the current
file library, that use assets of the given type in the given library."
[asset-type library-id state]
"Generate changes to synchronize all shapes in all components of the
local library of the given file, that use assets of the given type in
the given library."
[file-id asset-type library-id state]
(log/info :msg "Sync local components with library"
:asset-type asset-type
:library (str library-id))
:file (pretty-file file-id state)
:library (pretty-file library-id state))
(let [file (get-file state file-id)
library (get-file state library-id)
library-items (get library asset-type)]
(let [library-items
(if (= library-id (:current-file-id state))
(get-in state [:workspace-data asset-type])
(get-in state [:workspace-libraries library-id :data asset-type]))]
(if (empty? library-items)
empty-changes
(loop [local-components (seq (vals (get-in state [:workspace-data :components])))
(loop [local-components (vals (get file :components))
rchanges []
uchanges []]
(if-let [local-component (first local-components)]
@ -261,7 +276,7 @@
[_ library-id state container shape]
(generate-sync-shape-direct container
(:id shape)
(get-local-library state)
(get-local-file state)
(get-libraries state)
false))
@ -689,7 +704,18 @@
only-master
both
moved
true)]
true)
;; The inverse sync may be made on a component that is inside a
;; remote library. We need to separate changes that are from
;; local and remote files.
check-local (fn [change]
(cond-> change
(= (:id change) (:id shape-inst))
(assoc :local-change? true)))
rchanges (vec (map check-local rchanges))
uchanges (vec (map check-local uchanges))]
[(d/concat rchanges child-rchanges)
(d/concat uchanges child-uchanges)]))

View file

@ -218,12 +218,12 @@
::changes]))
(defn handle-library-change
[{:keys [file-id modified-at changes] :as msg}]
[{:keys [file-id modified-at changes revn] :as msg}]
(us/assert ::library-change-event msg)
(ptk/reify ::handle-library-change
ptk/WatchEvent
(watch [_ state stream]
(when (contains? (:workspace-libraries state) file-id)
(rx/of (dwl/ext-library-changed file-id modified-at changes)
(rx/of (dwl/ext-library-changed file-id modified-at revn changes)
(dwl/notify-sync-file file-id))))))

View file

@ -55,6 +55,13 @@
(rx/debounce 2000)
(rx/merge stoper forcer))
local-file? #(let [event-file-id (:file-id %)]
(or (nil? event-file-id)
(= event-file-id file-id)))
library-file? #(let [event-file-id (:file-id %)]
(and (some? event-file-id)
(not= event-file-id file-id)))
on-dirty
(fn []
;; Enable reload stoper
@ -70,27 +77,36 @@
;; Disable reload stoper
(obj/set! js/window "onbeforeunload" nil)
(st/emit! (update-persistence-status {:status :saved})))]
(->> (rx/merge
(->> stream
(rx/filter (ptk/type? ::dwc/commit-changes))
(rx/map deref)
(rx/tap on-dirty)
(rx/buffer-until notifier)
(rx/map vec)
(rx/filter (complement empty?))
(rx/map #(persist-changes file-id %))
(rx/tap on-saving)
(rx/take-until (rx/delay 100 stoper)))
(->> stream
(rx/filter (ptk/type? ::changes-persisted))
(rx/tap on-saved)
(rx/ignore)
(rx/take-until stoper)))
(->> stream
(rx/filter (ptk/type? ::dwc/commit-changes))
(rx/map deref)
(rx/filter local-file?)
(rx/tap on-dirty)
(rx/buffer-until notifier)
(rx/filter (complement empty?))
(rx/map (fn [buf] {:file-id file-id
:changes (into [] (mapcat :changes) buf)}))
(rx/map persist-changes)
(rx/tap on-saving)
(rx/take-until (rx/delay 100 stoper)))
(->> stream
(rx/filter (ptk/type? ::dwc/commit-changes))
(rx/map deref)
(rx/filter library-file?)
(rx/filter (complement #(empty? (:changes %))))
(rx/map persist-changes)
(rx/take-until (rx/delay 100 stoper)))
(->> stream
(rx/filter (ptk/type? ::changes-persisted))
(rx/tap on-saved)
(rx/ignore)
(rx/take-until stoper)))
(rx/subs #(st/emit! %)))))))
(defn persist-changes
[file-id changes]
[{:keys [file-id changes]}]
(us/verify ::us/uuid file-id)
(ptk/reify ::persist-changes
ptk/UpdateEvent
(update [_ state]
@ -102,10 +118,11 @@
ptk/WatchEvent
(watch [_ state stream]
(let [sid (:session-id state)
file (:workspace-file state)
file (if (= file-id (:current-file-id state))
(get state :workspace-file)
(get-in state [:workspace-libraries file-id]))
queue (get-in state [:workspace-persistence :queue] [])
xf-cat (comp (mapcat :changes)
(mapcat identity))
xf-cat (comp (mapcat :changes))
changes (into [] xf-cat queue)
params {:id (:id file)
:revn (:revn file)
@ -143,10 +160,9 @@
(rx/delay 200)
(rx/mapcat #(rx/throw error))))))]
(when (= file-id (:id file))
(->> (rp/mutation :update-file params)
(rx/mapcat handle-response)
(rx/catch on-error)))))))
(->> (rp/mutation :update-file params)
(rx/mapcat handle-response)
(rx/catch on-error))))))
(defn update-persistence-status
@ -171,14 +187,16 @@
(ptk/reify ::changes-persisted
ptk/UpdateEvent
(update [_ state]
(let [sid (:session-id state)
file (:workspace-file state)]
(if (= file-id (:id file))
(let [state (update-in state [:workspace-file :revn] #(max % revn))]
(-> state
(update :workspace-data cp/process-changes changes)
(update-in [:workspace-file :data] cp/process-changes changes)))
state)))))
(if (= file-id (:current-file-id state))
(-> state
(update-in [:workspace-file :revn] #(max % revn))
(update :workspace-data cp/process-changes changes)
(update-in [:workspace-file :data] cp/process-changes changes))
(-> state
(update-in state [:workspace-libraries file-id :revn]
#(max % revn))
(update-in [:workspace-libraries file-id :data]
cp/process-changes changes))))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

View file

@ -10,6 +10,7 @@
(ns app.main.ui.workspace.context-menu
"A workspace specific context menu (mouse right click)."
(:require
[app.main.data.modal :as modal]
[app.main.data.workspace :as dw]
[app.main.data.workspace.common :as dwc]
[app.main.data.workspace.libraries :as dwl]
@ -76,8 +77,24 @@
do-update-component #(do
(st/emit! (dwc/start-undo-transaction))
(st/emit! (dwl/update-component id))
(st/emit! (dwl/sync-file current-file-id))
(st/emit! (dwl/sync-file current-file-id
(:component-file shape)))
(st/emit! (dwc/commit-undo-transaction)))
confirm-update-remote-component #(do
(st/emit! (dwl/update-component id))
(st/emit! (dwl/sync-file current-file-id
(:component-file shape)))
(st/emit! (dwl/sync-file (:component-file shape)
(:component-file shape))))
do-update-remote-component (st/emitf (modal/show
{:type :confirm
:message ""
:title (t locale "modals.update-remote-component.message")
:hint (t locale "modals.update-remote-component.hint")
:cancel-label (t locale "modals.update-remote-component.cancel")
:accept-label (t locale "modals.update-remote-component.accept")
:accept-style :primary
:on-accept confirm-update-remote-component}))
do-show-component #(st/emit! (dw/go-to-layout :assets))
do-navigate-component-file #(st/emit! (dwl/nav-to-component-file
(:component-file shape)))]
@ -175,7 +192,9 @@
[:& menu-entry {:title (t locale "workspace.shape.menu.reset-overrides")
:on-click do-reset-component}]
[:& menu-entry {:title (t locale "workspace.shape.menu.go-master")
:on-click do-navigate-component-file}]]))
:on-click do-navigate-component-file}]
[:& menu-entry {:title (t locale "workspace.shape.menu.update-master")
:on-click do-update-remote-component}]]))
[:& menu-separator]
[:& menu-entry {:title (t locale "workspace.shape.menu.delete")

View file

@ -126,7 +126,7 @@
[{:keys [file libraries] :as props}]
(let [libraries-need-sync (filter #(> (:modified-at %) (:synced-at %))
(vals libraries))
update-library #(st/emit! (dwl/sync-file %))]
update-library #(st/emit! (dwl/sync-file (:id file) %))]
[:div.section
(if (empty? libraries-need-sync)
[:div.section-list-empty

View file

@ -62,7 +62,7 @@
(mf/deps state)
(fn []
(st/emit! (dwl/delete-component {:id (:component-id @state)}))
(st/emit! (dwl/sync-file file-id))))
(st/emit! (dwl/sync-file file-id file-id))))
on-rename
(mf/use-callback

View file

@ -11,6 +11,7 @@
(:require
[rumext.alpha :as mf]
[app.common.pages :as cp]
[app.main.data.modal :as modal]
[app.main.refs :as refs]
[app.main.store :as st]
[app.main.ui.context :as ctx]
@ -54,8 +55,23 @@
do-update-component #(do
(st/emit! (dwc/start-undo-transaction))
(st/emit! (dwl/update-component id))
(st/emit! (dwl/sync-file current-file-id))
(st/emit! (dwl/sync-file current-file-id current-file-id))
(st/emit! (dwc/commit-undo-transaction)))
confirm-update-remote-component #(do
(st/emit! (dwl/update-component id))
(st/emit! (dwl/sync-file current-file-id
(:component-file values)))
(st/emit! (dwl/sync-file (:component-file values)
(:component-file values))))
do-update-remote-component (st/emitf (modal/show
{:type :confirm
:message ""
:title (t locale "modals.update-remote-component.message")
:hint (t locale "modals.update-remote-component.hint")
:cancel-label (t locale "modals.update-remote-component.cancel")
:accept-label (t locale "modals.update-remote-component.accept")
:accept-style :primary
:on-accept confirm-update-remote-component}))
do-show-component #(st/emit! (dw/go-to-layout :assets))
do-navigate-component-file #(st/emit! (dwl/nav-to-component-file
(:component-file values)))]
@ -83,5 +99,6 @@
[[(t locale "workspace.shape.menu.detach-instance") do-detach-component]
[(t locale "workspace.shape.menu.reset-overrides") do-reset-component]
[(t locale "workspace.shape.menu.go-master") do-navigate-component-file]])}]]]]])))
[(t locale "workspace.shape.menu.go-master") do-navigate-component-file]
[(t locale "workspace.shape.menu.update-master") do-update-remote-component]])}]]]]])))