diff --git a/CHANGES.md b/CHANGES.md
index 0604c3213..25e385304 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -12,6 +12,18 @@
### :heart: Community contributions by (Thank you!)
+## 1.19.3
+
+### :sparkles: New features
+
+- Remember last color mode in colorpicker [Taiga #5508](https://tree.taiga.io/project/penpot/issue/5508)
+- Improve layers multiselection behaviour [Github #5741](https://github.com/penpot/penpot/issues/5741)
+
+### :bug: Bugs fixed
+
+- List view is discarded on tab change on Workspace Assets Sidebar tab [Github #3547](https://github.com/penpot/penpot/issues/3547)
+- Fix message popup remains open when exiting workspace with browser back button [Taiga #5747](https://tree.taiga.io/project/penpot/issue/5747)
+- When editing text if font is changed, the proportions of the rendered shape are wrong [Taiga #5786](https://tree.taiga.io/project/penpot/issue/5786)
## 1.19.2
diff --git a/backend/resources/log4j2-devenv.xml b/backend/resources/log4j2-devenv.xml
index 8c1142887..947d06e26 100644
--- a/backend/resources/log4j2-devenv.xml
+++ b/backend/resources/log4j2-devenv.xml
@@ -23,7 +23,7 @@
-
+
diff --git a/backend/src/app/rpc/commands/binfile.clj b/backend/src/app/rpc/commands/binfile.clj
index 315170137..c4a7efc5f 100644
--- a/backend/src/app/rpc/commands/binfile.clj
+++ b/backend/src/app/rpc/commands/binfile.clj
@@ -773,7 +773,7 @@
(defn- lookup-index
[id]
(let [val (get-in @*state* [:index id])]
- (l/debug :fn "lookup-index" :id id :val val ::l/sync? true)
+ (l/trc :fn "lookup-index" :id id :val val ::l/sync? true)
(when (and (not (::ignore-index-errors? *options*)) (not val))
(ex/raise :type :validation
:code :incomplete-index
diff --git a/backend/src/app/srepl/helpers.clj b/backend/src/app/srepl/helpers.clj
index 113dae09c..94fe56371 100644
--- a/backend/src/app/srepl/helpers.clj
+++ b/backend/src/app/srepl/helpers.clj
@@ -38,7 +38,6 @@
[promesa.exec.csp :as sp]))
(def ^:dynamic *conn* nil)
-(def ^:dynamic *pool* nil)
(defn println!
[& params]
diff --git a/backend/src/app/storage.clj b/backend/src/app/storage.clj
index 20cc8efe6..be0159e0f 100644
--- a/backend/src/app/storage.clj
+++ b/backend/src/app/storage.clj
@@ -251,53 +251,59 @@
(defmethod ig/init-key ::gc-deleted-task
[_ {:keys [::db/pool ::storage ::min-age]}]
- (letfn [(retrieve-deleted-objects-chunk [conn min-age cursor]
- (let [min-age (db/interval min-age)
- rows (db/exec! conn [sql:retrieve-deleted-objects-chunk min-age cursor])]
- [(some-> rows peek :created-at)
+ (letfn [(get-to-delete-chunk [cursor]
+ (let [sql (str "select s.* "
+ " from storage_object as s "
+ " where s.deleted_at is not null "
+ " and s.deleted_at < ? "
+ " order by s.deleted_at desc "
+ " limit 25")
+ rows (db/exec! pool [sql cursor])]
+ [(some-> rows peek :deleted-at)
(some->> (seq rows) (d/group-by #(-> % :backend keyword) :id #{}) seq)]))
- (retrieve-deleted-objects [conn min-age]
- (d/iteration (partial retrieve-deleted-objects-chunk conn min-age)
- :initk (dt/now)
+ (get-to-delete-chunks [min-age]
+ (d/iteration get-to-delete-chunk
+ :initk (dt/minus (dt/now) min-age)
:vf second
:kf first))
- (delete-in-bulk [backend-id ids]
- (let [backend (impl/resolve-backend storage backend-id)]
+ (delete-in-bulk! [backend-id ids]
+ (try
+ (db/with-atomic [conn pool]
+ (let [sql "delete from storage_object where id = ANY(?)"
+ ids' (db/create-array conn "uuid" ids)
- (doseq [id ids]
- (l/debug :hint "gc-deleted: permanently delete storage object" :backend backend-id :id id))
+ total (-> (db/exec-one! conn [sql ids'])
+ (db/get-update-count))]
- (impl/del-objects-in-bulk backend ids)))]
+ (-> (impl/resolve-backend storage backend-id)
+ (impl/del-objects-in-bulk ids))
+
+ (doseq [id ids]
+ (l/dbg :hint "gc-deleted: permanently delete storage object" :backend backend-id :id id))
+
+ total))
+
+ (catch Throwable cause
+ (l/err :hint "gc-deleted: unexpected error on bulk deletion"
+ :ids (vec ids)
+ :cause cause)
+ 0)))]
(fn [params]
- (let [min-age (or (:min-age params) min-age)]
- (db/with-atomic [conn pool]
- (loop [total 0
- groups (retrieve-deleted-objects conn min-age)]
- (if-let [[backend-id ids] (first groups)]
- (do
- (delete-in-bulk backend-id ids)
- (recur (+ total (count ids))
- (rest groups)))
- (do
- (l/info :hint "gc-deleted: task finished" :min-age (dt/format-duration min-age) :total total)
- {:deleted total}))))))))
-
-(def sql:retrieve-deleted-objects-chunk
- "with items_part as (
- select s.id
- from storage_object as s
- where s.deleted_at is not null
- and s.deleted_at < (now() - ?::interval)
- and s.created_at < ?
- order by s.created_at desc
- limit 25
- )
- delete from storage_object
- where id in (select id from items_part)
- returning *;")
+ (let [min-age (or (some-> params :min-age dt/duration) min-age)]
+ (loop [total 0
+ chunks (get-to-delete-chunks min-age)]
+ (if-let [[backend-id ids] (first chunks)]
+ (let [deleted (delete-in-bulk! backend-id ids)]
+ (recur (+ total deleted)
+ (rest chunks)))
+ (do
+ (l/inf :hint "gc-deleted: task finished"
+ :min-age (dt/format-duration min-age)
+ :total total)
+ {:deleted total})))))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Garbage Collection: Analyze touched objects
diff --git a/backend/src/app/worker.clj b/backend/src/app/worker.clj
index 1c7bcfd1b..8bafe3a8a 100644
--- a/backend/src/app/worker.clj
+++ b/backend/src/app/worker.clj
@@ -207,10 +207,10 @@
(db/create-array conn "uuid" ids)]]
(db/exec-one! conn sql)
- (l/debug :hist "dispatcher: queue tasks"
- :queue queue
- :tasks (count ids)
- :queued res)))
+ (l/dbg :hist "dispatcher: queue tasks"
+ :queue queue
+ :tasks (count ids)
+ :queued res)))
(run-batch! [rconn]
(try
@@ -433,12 +433,12 @@
:else
(try
- (l/debug :hint "worker: executing task"
- :name (:name task)
- :id (:id task)
- :queue queue
- :worker-id worker-id
- :retry (:retry-num task))
+ (l/dbg :hint "worker: executing task"
+ :name (:name task)
+ :id (str (:id task))
+ :queue queue
+ :worker-id worker-id
+ :retry (:retry-num task))
(handle-task task)
(catch InterruptedException cause
(throw cause))
@@ -678,13 +678,13 @@
(-> (db/exec-one! conn [sql:remove-not-started-tasks task queue label])
:next.jdbc/update-count))]
- (l/debug :hint "submit task"
- :name task
- :queue queue
- :label label
- :dedupe (boolean dedupe)
- :deleted (or deleted 0)
- :in (dt/format-duration duration))
+ (l/trc :hint "submit task"
+ :name task
+ :queue queue
+ :label label
+ :dedupe (boolean dedupe)
+ :deleted (or deleted 0)
+ :in (dt/format-duration duration))
(db/exec-one! conn [sql:insert-new-task id task props queue
label priority max-retries interval])
diff --git a/backend/test/backend_tests/storage_test.clj b/backend/test/backend_tests/storage_test.clj
index b0a60c718..ee6045d30 100644
--- a/backend/test/backend_tests/storage_test.clj
+++ b/backend/test/backend_tests/storage_test.clj
@@ -100,6 +100,7 @@
(configure-storage-backend))
content1 (sto/content "content1")
content2 (sto/content "content2")
+ content3 (sto/content "content3")
object1 (sto/put-object! storage {::sto/content content1
::sto/expired-at (dt/now)
:content-type "text/plain"
@@ -107,16 +108,20 @@
object2 (sto/put-object! storage {::sto/content content2
::sto/expired-at (dt/in-past {:hours 2})
:content-type "text/plain"
+ })
+ object3 (sto/put-object! storage {::sto/content content3
+ ::sto/expired-at (dt/in-past {:hours 1})
+ :content-type "text/plain"
})]
+
(th/sleep 200)
- (let [task (:app.storage/gc-deleted-task th/*system*)
- res (task {})]
+ (let [res (th/run-task! :storage-gc-deleted {})]
(t/is (= 1 (:deleted res))))
(let [res (db/exec-one! th/*pool* ["select count(*) from storage_object;"])]
- (t/is (= 1 (:count res))))))
+ (t/is (= 2 (:count res))))))
(t/deftest test-touched-gc-task-1
(let [storage (-> (:app.storage/storage th/*system*)
diff --git a/frontend/src/app/main/data/comments.cljs b/frontend/src/app/main/data/comments.cljs
index b73f4e9c8..6c119803e 100644
--- a/frontend/src/app/main/data/comments.cljs
+++ b/frontend/src/app/main/data/comments.cljs
@@ -279,8 +279,9 @@
(assoc-in (conj path :position) (:position comment-thread))
(assoc-in (conj path :frame-id) (:frame-id comment-thread))))))
(fetched [[users comments] state]
- (let [pages (get-in state [:workspace-data :pages-index])
- comments (filter #(some? (get pages (:page-id %))) comments)
+ (let [pages (-> (get-in state [:workspace-data :pages])
+ set)
+ comments (filter #(contains? pages (:page-id %)) comments)
state (-> state
(assoc :comment-threads (d/index-by :id comments))
(update :current-file-comments-users merge (d/index-by :id users)))]
diff --git a/frontend/src/app/main/data/workspace/assets.cljs b/frontend/src/app/main/data/workspace/assets.cljs
new file mode 100644
index 000000000..7db9cc1ce
--- /dev/null
+++ b/frontend/src/app/main/data/workspace/assets.cljs
@@ -0,0 +1,28 @@
+;; This Source Code Form is subject to the terms of the Mozilla Public
+;; License, v. 2.0. If a copy of the MPL was not distributed with this
+;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
+;;
+;; Copyright (c) KALEIDOS INC
+
+(ns app.main.data.workspace.assets
+ "Workspace assets management events and helpers."
+ (:require
+ [app.util.storage :refer [storage]]))
+
+(defn get-current-assets-ordering
+ []
+ (let [ordering (::ordering @storage)]
+ (or ordering :asc)))
+
+(defn set-current-assets-ordering!
+ [ordering]
+ (swap! storage assoc ::ordering ordering))
+
+(defn get-current-assets-list-style
+ []
+ (let [list-style (::list-style @storage)]
+ (or list-style :thumbs)))
+
+(defn set-current-assets-list-style!
+ [list-style]
+ (swap! storage assoc ::list-style list-style))
diff --git a/frontend/src/app/main/data/workspace/colors.cljs b/frontend/src/app/main/data/workspace/colors.cljs
index 32de9cf96..3fdb48ac9 100644
--- a/frontend/src/app/main/data/workspace/colors.cljs
+++ b/frontend/src/app/main/data/workspace/colors.cljs
@@ -22,6 +22,7 @@
[app.main.data.workspace.texts :as dwt]
[app.main.data.workspace.undo :as dwu]
[app.util.color :as uc]
+ [app.util.storage :refer [storage]]
[beicon.core :as rx]
[potok.core :as ptk]))
@@ -647,3 +648,12 @@
:position :right})
(ptk/event ::ev/event {::ev/name "add-asset-to-library"
:asset-type "color"}))))))
+
+(defn get-active-color-tab
+ []
+ (let [tab (::tab @storage)]
+ (or tab :ramp)))
+
+(defn set-active-color-tab!
+ [tab]
+ (swap! storage assoc ::tab tab))
diff --git a/frontend/src/app/main/data/workspace/selection.cljs b/frontend/src/app/main/data/workspace/selection.cljs
index b13ed7bc8..35d7eb2b8 100644
--- a/frontend/src/app/main/data/workspace/selection.cljs
+++ b/frontend/src/app/main/data/workspace/selection.cljs
@@ -121,7 +121,9 @@
(ptk/reify ::select-shape
ptk/UpdateEvent
(update [_ state]
- (update-in state [:workspace-local :selected] d/toggle-selection id toggle?))
+ (-> state
+ (update-in [:workspace-local :selected] d/toggle-selection id toggle?)
+ (assoc-in [:workspace-local :last-selected] id)))
ptk/WatchEvent
(watch [_ state _]
@@ -184,7 +186,9 @@
(ptk/reify ::deselect-shape
ptk/UpdateEvent
(update [_ state]
- (update-in state [:workspace-local :selected] disj id))))
+ (-> state
+ (update-in [:workspace-local :selected] disj id)
+ (update :workspace-local dissoc :last-selected)))))
(defn shift-select-shapes
([id]
@@ -194,13 +198,15 @@
(ptk/reify ::shift-select-shapes
ptk/UpdateEvent
(update [_ state]
- (let [objects (or objects (wsh/lookup-page-objects state))
+ (let [objects (or objects (wsh/lookup-page-objects state))
+ append-to-selection (cph/expand-region-selection objects (into #{} [(get-in state [:workspace-local :last-selected]) id]))
selection (-> state
wsh/lookup-selected
(conj id))]
(-> state
(assoc-in [:workspace-local :selected]
- (cph/expand-region-selection objects selection))))))))
+ (set/union selection append-to-selection))
+ (update :workspace-local assoc :last-selected id)))))))
(defn select-shapes
[ids]
diff --git a/frontend/src/app/main/ui/workspace.cljs b/frontend/src/app/main/ui/workspace.cljs
index 5176ff396..730062873 100644
--- a/frontend/src/app/main/ui/workspace.cljs
+++ b/frontend/src/app/main/ui/workspace.cljs
@@ -8,8 +8,10 @@
(:require-macros [app.main.style :refer [css]])
(:require
[app.common.data.macros :as dm]
+ [app.main.data.messages :as msg]
[app.main.data.modal :as modal]
[app.main.data.workspace :as dw]
+ [app.main.data.workspace.colors :as dc]
[app.main.data.workspace.persistence :as dwp]
[app.main.features :as features]
[app.main.refs :as refs]
@@ -192,6 +194,9 @@
(st/emit! (dw/initialize-file project-id file-id))
(fn []
(st/emit! ::dwp/force-persist
+ (dc/stop-picker)
+ (modal/hide)
+ msg/hide
(dw/finalize-file project-id file-id))))
[:& (mf/provider ctx/current-file-id) {:value file-id}
diff --git a/frontend/src/app/main/ui/workspace/colorpicker.cljs b/frontend/src/app/main/ui/workspace/colorpicker.cljs
index 5b15d247d..9b912afd5 100644
--- a/frontend/src/app/main/ui/workspace/colorpicker.cljs
+++ b/frontend/src/app/main/ui/workspace/colorpicker.cljs
@@ -56,12 +56,17 @@
current-color (:current-color state)
- active-tab (mf/use-state :ramp #_:harmony #_:hsva)
- set-ramp-tab! (mf/use-fn #(reset! active-tab :ramp))
- set-harmony-tab! (mf/use-fn #(reset! active-tab :harmony))
- set-hsva-tab! (mf/use-fn #(reset! active-tab :hsva))
-
+ active-tab (mf/use-state (dc/get-active-color-tab))
drag? (mf/use-state false)
+
+ set-tab!
+ (mf/use-fn
+ (fn [event]
+ (let [tab (-> (dom/get-current-target event)
+ (dom/get-data "tab")
+ (keyword))]
+ (reset! active-tab tab)
+ (dc/set-active-color-tab! tab))))
handle-change-color
(mf/use-fn
@@ -81,9 +86,9 @@
(fn []
(if picking-color?
(do (modal/disallow-click-outside!)
- (st/emit! (dc/stop-picker)))
+ (st/emit! (dc/stop-picker)))
(do (modal/allow-click-outside!)
- (st/emit! (dc/start-picker))))))
+ (st/emit! (dc/start-picker))))))
handle-change-stop
(mf/use-fn
@@ -225,15 +230,18 @@
[:div.colorpicker-tab.tooltip.tooltip-bottom.tooltip-expand
{:class (when (= @active-tab :ramp) "active")
:alt (tr "workspace.libraries.colors.rgba")
- :on-click set-ramp-tab!} i/picker-ramp]
+ :on-click set-tab!
+ :data-tab "ramp"} i/picker-ramp]
[:div.colorpicker-tab.tooltip.tooltip-bottom.tooltip-expand
{:class (when (= @active-tab :harmony) "active")
:alt (tr "workspace.libraries.colors.rgb-complementary")
- :on-click set-harmony-tab!} i/picker-harmony]
+ :on-click set-tab!
+ :data-tab "harmony"} i/picker-harmony]
[:div.colorpicker-tab.tooltip.tooltip-bottom.tooltip-expand
{:class (when (= @active-tab :hsva) "active")
:alt (tr "workspace.libraries.colors.hsv")
- :on-click set-hsva-tab!} i/picker-hsv]]
+ :on-click set-tab!
+ :data-tab "hsva"} i/picker-hsv]]
(if picking-color?
[:div.picker-detail-wrapper
diff --git a/frontend/src/app/main/ui/workspace/header.cljs b/frontend/src/app/main/ui/workspace/header.cljs
index 205895abc..0a80a988f 100644
--- a/frontend/src/app/main/ui/workspace/header.cljs
+++ b/frontend/src/app/main/ui/workspace/header.cljs
@@ -13,7 +13,6 @@
[app.main.data.exports :as de]
[app.main.data.modal :as modal]
[app.main.data.workspace :as dw]
- [app.main.data.workspace.colors :as dc]
[app.main.data.workspace.common :as dwc]
[app.main.data.workspace.libraries :as dwl]
[app.main.data.workspace.shortcuts :as sc]
@@ -168,9 +167,7 @@
(st/emit! (ptk/event ::ev/event {::ev/name "show-release-notes" :version version}))
(if (and (kbd/alt? event) (kbd/mod? event))
(st/emit! (modal/show {:type :onboarding}))
- (st/emit! (modal/show {:type :release-notes :version version}))))))
-
- ]
+ (st/emit! (modal/show {:type :release-notes :version version}))))))]
[:& dropdown {:show true :on-close on-close}
[:ul.sub-menu.help-info
@@ -603,16 +600,10 @@
(dom/prevent-default event)
(reset! editing* true)))
- close-modals
- (mf/use-fn
- #(st/emit! (dc/stop-picker)
- (modal/hide)))
-
go-back
(mf/use-fn
(mf/deps project)
(fn []
- (close-modals)
(st/emit! (dw/go-to-dashboard project))))
nav-to-viewer
diff --git a/frontend/src/app/main/ui/workspace/shapes/text/viewport_texts_html.cljs b/frontend/src/app/main/ui/workspace/shapes/text/viewport_texts_html.cljs
index a6633ccd7..65804663b 100644
--- a/frontend/src/app/main/ui/workspace/shapes/text/viewport_texts_html.cljs
+++ b/frontend/src/app/main/ui/workspace/shapes/text/viewport_texts_html.cljs
@@ -26,6 +26,7 @@
[app.util.object :as obj]
[app.util.text-editor :as ted]
[app.util.text-svg-position :as tsp]
+ [app.util.timers :as ts]
[promesa.core :as p]
[rumext.v2 :as mf]))
@@ -79,25 +80,29 @@
(defn- update-text-modifier
[{:keys [grow-type id] :as shape} node]
+ (->> (tsp/calc-position-data id)
+ (p/fmap (fn [position-data]
+ (let [props {:position-data position-data}]
+ (if (contains? #{:auto-height :auto-width} grow-type)
+ (let [{:keys [width height]} (-> (dom/query node ".paragraph-set") (dom/get-client-size))
+ width (mth/ceil width)
+ height (mth/ceil height)]
+ (if (and (not (mth/almost-zero? width)) (not (mth/almost-zero? height)))
+ (cond-> props
+ (= grow-type :auto-width)
+ (assoc :width width)
- (p/let [position-data (tsp/calc-position-data id)
- props {:position-data position-data}
-
- props
- (if (contains? #{:auto-height :auto-width} grow-type)
- (let [{:keys [width height]} (-> (dom/query node ".paragraph-set") (dom/get-client-size))
- width (mth/ceil width)
- height (mth/ceil height)]
- (if (and (not (mth/almost-zero? width)) (not (mth/almost-zero? height)))
- (cond-> props
- (= grow-type :auto-width)
- (assoc :width width)
-
- (or (= grow-type :auto-height) (= grow-type :auto-width))
- (assoc :height height))
- props))
- props)]
- (st/emit! (dwt/update-text-modifier id props))))
+ (or (= grow-type :auto-height) (= grow-type :auto-width))
+ (assoc :height height))
+ props))
+ props))))
+ (p/fmap (fn [props]
+ ;; We need to wait for the text modifier to be updated before
+ ;; we can update the position data. Otherwise the position data
+ ;; will be wrong.
+ ;; TODO: This is a hack. We need to find a better way to do this.
+ (st/emit! (dwt/update-text-modifier id props))
+ (ts/schedule 30 #(update-text-shape shape node))))))
(mf/defc text-container
{::mf/wrap-props false
diff --git a/frontend/src/app/main/ui/workspace/sidebar/assets.cljs b/frontend/src/app/main/ui/workspace/sidebar/assets.cljs
index 2a0970000..242475b51 100644
--- a/frontend/src/app/main/ui/workspace/sidebar/assets.cljs
+++ b/frontend/src/app/main/ui/workspace/sidebar/assets.cljs
@@ -9,6 +9,7 @@
(:require
[app.common.data.macros :as dm]
[app.main.data.modal :as modal]
+ [app.main.data.workspace.assets :as dwa]
[app.main.refs :as refs]
[app.main.ui.components.context-menu-a11y :refer [context-menu-a11y]]
[app.main.ui.components.search-bar :refer [search-bar]]
@@ -68,27 +69,39 @@
{::mf/wrap [mf/memo]
::mf/wrap-props false}
[]
- (let [components-v2 (mf/use-ctx ctx/components-v2)
- read-only? (mf/use-ctx ctx/workspace-read-only?)
- new-css-system (mf/use-ctx ctx/new-css-system)
- filters* (mf/use-state
+ (let [components-v2 (mf/use-ctx ctx/components-v2)
+ read-only? (mf/use-ctx ctx/workspace-read-only?)
+ new-css-system (mf/use-ctx ctx/new-css-system)
+ filters* (mf/use-state
{:term ""
:section "all"
- :ordering :asc
- :list-style :thumbs
+ :ordering (dwa/get-current-assets-ordering)
+ :list-style (dwa/get-current-assets-list-style)
:open-menu false})
- filters (deref filters*)
- term (:term filters)
- menu-open? (:open-menu filters)
- section (:section filters)
- ordering (:ordering filters)
- reverse-sort? (= :desc ordering)
+ filters (deref filters*)
+ term (:term filters)
+ ordering (:ordering filters)
+ list-style (:list-style filters)
+ menu-open? (:open-menu filters)
+ section (:section filters)
+ ordering (:ordering filters)
+ reverse-sort? (= :desc ordering)
toggle-ordering
- (mf/use-fn #(swap! filters* update :ordering toggle-values [:asc :desc]))
+ (mf/use-fn
+ (mf/deps ordering)
+ (fn []
+ (let [new-value (toggle-values ordering [:asc :desc])]
+ (swap! filters* assoc :ordering new-value)
+ (dwa/set-current-assets-ordering! new-value))))
toggle-list-style
- (mf/use-fn #(swap! filters* update :list-style toggle-values [:thumbs :list]))
+ (mf/use-fn
+ (mf/deps list-style)
+ (fn []
+ (let [new-value (toggle-values list-style [:thumbs :list])]
+ (swap! filters* assoc :list-style new-value)
+ (dwa/set-current-assets-list-style! new-value))))
on-search-term-change
(mf/use-fn
diff --git a/frontend/src/app/main/ui/workspace/sidebar/layer_item.cljs b/frontend/src/app/main/ui/workspace/sidebar/layer_item.cljs
index 34470d0d2..964de3776 100644
--- a/frontend/src/app/main/ui/workspace/sidebar/layer_item.cljs
+++ b/frontend/src/app/main/ui/workspace/sidebar/layer_item.cljs
@@ -368,10 +368,16 @@
[:div.element-actions {:class (when ^boolean has-shapes? "is-parent")}
[:div.toggle-element {:class (when ^boolean hidden? "selected")
+ :title (if (:hidden item)
+ (tr "workspace.shape.menu.show")
+ (tr "workspace.shape.menu.hide"))
:on-click toggle-visibility}
(if ^boolean hidden? i/eye-closed i/eye)]
[:div.block-element {:class (when ^boolean blocked? "selected")
- :on-click toggle-blocking}
+ :on-click toggle-blocking
+ :title (if (:blocked item)
+ (tr "workspace.shape.menu.unlock")
+ (tr "workspace.shape.menu.lock"))}
(if ^boolean blocked? i/lock i/unlock)]]
(when ^boolean has-shapes?