mirror of
https://github.com/penpot/penpot.git
synced 2026-05-24 09:23:40 +00:00
✨ Polish workspace find and replace UX (#9687)
* ✨ Polish workspace find and replace UX Co-authored-by: Cursor <cursoragent@cursor.com> * ✨ Add toggle mode button This button toggles between search and search and replace modes * ♻️ Refactor and CSS cleanup --------- Co-authored-by: Cursor <cursoragent@cursor.com> Co-authored-by: Luis de Dios <luis.dedios@kaleidos.net>
This commit is contained in:
parent
3cfd1e1a48
commit
e2ed6a488d
@ -1493,18 +1493,47 @@
|
||||
(update [_ state]
|
||||
(assoc-in state [:workspace-global :clipboard-style] style))))
|
||||
|
||||
(defn open-layers-search
|
||||
(defn- layers-search-config
|
||||
[mode]
|
||||
(ptk/reify ::open-layers-search
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(assoc-in state [:workspace-local :layers-panel-search] mode))))
|
||||
{:open? true
|
||||
:mode mode
|
||||
:scope (if (= mode :find-and-replace) :canvas :layers)
|
||||
:find-replace-mode? (= mode :find-and-replace)})
|
||||
|
||||
(def clear-layers-search
|
||||
(ptk/reify ::clear-layers-search
|
||||
(defn- layers-search-active?
|
||||
[current target]
|
||||
(and (:open? current false)
|
||||
(= (:scope current) (:scope target))
|
||||
(= (:find-replace-mode? current) (:find-replace-mode? target))))
|
||||
|
||||
(defn open-layers-search
|
||||
([mode] (open-layers-search mode nil))
|
||||
([mode options]
|
||||
(let [force? (boolean (:force? options))]
|
||||
(ptk/reify ::open-layers-search
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(let [target (layers-search-config mode)
|
||||
current (get-in state [:workspace-local :layers-search])]
|
||||
(if (and (not force?)
|
||||
(layers-search-active? current target))
|
||||
(update state :workspace-local dissoc :layers-search)
|
||||
(assoc-in state [:workspace-local :layers-search] target))))))))
|
||||
|
||||
(def close-layers-search
|
||||
(ptk/reify ::close-layers-search
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(update state :workspace-local dissoc :layers-panel-search))))
|
||||
(update state :workspace-local dissoc :layers-search))))
|
||||
|
||||
(defn update-layers-search-scope
|
||||
[scope]
|
||||
(ptk/reify ::update-layers-search-scope
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(if (get-in state [:workspace-local :layers-search])
|
||||
(assoc-in state [:workspace-local :layers-search :scope] scope)
|
||||
state))))
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;; Exports
|
||||
@ -1565,6 +1594,8 @@
|
||||
;; Highlight
|
||||
(dm/export dwh/highlight-shape)
|
||||
(dm/export dwh/dehighlight-shape)
|
||||
(dm/export dwh/set-search-match-highlight)
|
||||
(dm/export dwh/clear-search-match-highlight)
|
||||
|
||||
;; Shape flags
|
||||
(dm/export dwsh/update-shape-flags)
|
||||
|
||||
@ -27,3 +27,29 @@
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(update-in state [:workspace-local :highlighted] disj id))))
|
||||
|
||||
(defn set-search-match-highlight
|
||||
"Highlight the active find/replace match on canvas and sidebar."
|
||||
[current-id match-ids]
|
||||
(dm/assert! (uuid? current-id))
|
||||
(let [match-ids (set match-ids)]
|
||||
(ptk/reify ::set-search-match-highlight
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(let [highlighted (-> (get-in state [:workspace-local :highlighted] #{})
|
||||
(set/difference match-ids)
|
||||
(conj current-id))]
|
||||
(-> state
|
||||
(assoc-in [:workspace-local :search-match-highlight] current-id)
|
||||
(assoc-in [:workspace-local :highlighted] highlighted)))))))
|
||||
|
||||
(defn clear-search-match-highlight
|
||||
[match-ids]
|
||||
(let [match-ids (set match-ids)]
|
||||
(ptk/reify ::clear-search-match-highlight
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(-> state
|
||||
(update-in [:workspace-local :highlighted]
|
||||
#(set/difference (or % #{}) match-ids))
|
||||
(update :workspace-local dissoc :search-match-highlight))))))
|
||||
|
||||
@ -1206,18 +1206,24 @@
|
||||
[ids search replacement]
|
||||
(ptk/reify ::replace-text-in-shapes
|
||||
ptk/WatchEvent
|
||||
(watch [_ _ _]
|
||||
(let [undo-group (uuid/next)]
|
||||
(rx/of
|
||||
(dwsh/update-shapes
|
||||
ids
|
||||
(fn [shape]
|
||||
(if (and (= :text (:type shape)) (some? (:content shape)))
|
||||
(let [new-content (txt/replace-text-in-content (:content shape) search replacement)
|
||||
new-name (txt/generate-shape-name (txt/content->text new-content))]
|
||||
(-> shape (assoc :content new-content) (assoc :name new-name)))
|
||||
shape))
|
||||
{:attrs #{:content :name} :undo-group undo-group}))))))
|
||||
(watch [_ state _]
|
||||
(let [undo-group (uuid/next)
|
||||
update-event
|
||||
(dwsh/update-shapes
|
||||
ids
|
||||
(fn [shape]
|
||||
(if (and (= :text (:type shape)) (some? (:content shape)))
|
||||
(let [new-content (txt/replace-text-in-content (:content shape) search replacement)
|
||||
new-name (txt/generate-shape-name (txt/content->text new-content))]
|
||||
(-> shape (assoc :content new-content) (assoc :name new-name)))
|
||||
shape))
|
||||
{:attrs #{:content :name} :undo-group undo-group})]
|
||||
(rx/concat
|
||||
(rx/of update-event)
|
||||
(if (features/active-feature? state "render-wasm/v1")
|
||||
(->> (rx/from ids)
|
||||
(rx/map #(dwwt/resize-wasm-text-debounce % {:undo-group undo-group})))
|
||||
(rx/empty)))))))
|
||||
|
||||
;; -- Text Editor v3
|
||||
|
||||
|
||||
@ -13,7 +13,8 @@
|
||||
[rumext.v2 :as mf]))
|
||||
|
||||
(mf/defc search-bar*
|
||||
[{:keys [id class value placeholder icon-id auto-focus on-change on-clear on-submit children]}]
|
||||
[{:keys [id class value placeholder icon-id auto-focus input-ref
|
||||
on-change on-clear on-submit on-key-down children]}]
|
||||
(let [handle-change
|
||||
(mf/use-fn
|
||||
(mf/deps on-change)
|
||||
@ -31,8 +32,11 @@
|
||||
|
||||
handle-key-down
|
||||
(mf/use-fn
|
||||
(mf/deps on-submit)
|
||||
(mf/deps on-submit on-key-down)
|
||||
(fn [event]
|
||||
(when (fn? on-key-down)
|
||||
(on-key-down event))
|
||||
|
||||
(let [enter? (kbd/enter? event)
|
||||
esc? (kbd/esc? event)
|
||||
node (dom/get-target event)]
|
||||
@ -53,6 +57,7 @@
|
||||
:size "s"
|
||||
:class (stl/css :icon)}])
|
||||
[:input {:id id
|
||||
:ref input-ref
|
||||
:class (stl/css :search-input)
|
||||
:on-change handle-change
|
||||
:value value
|
||||
|
||||
@ -474,10 +474,10 @@
|
||||
#(st/emit! (dw/select-all)))
|
||||
|
||||
find
|
||||
(mf/use-fn (fn [] (on-close) (st/emit! (dw/open-layers-search :find))))
|
||||
(mf/use-fn (fn [] (on-close) (st/emit! (dw/open-layers-search :find {:force? true}))))
|
||||
|
||||
find-and-replace
|
||||
(mf/use-fn (fn [] (on-close) (st/emit! (dw/open-layers-search :find-and-replace))))
|
||||
(mf/use-fn (fn [] (on-close) (st/emit! (dw/open-layers-search :find-and-replace {:force? true}))))
|
||||
|
||||
undo
|
||||
(mf/use-fn
|
||||
|
||||
@ -48,7 +48,10 @@
|
||||
(let [{:keys [enter leave]} @sidebar-hover-queue
|
||||
|
||||
enter (set/difference enter leave)
|
||||
leave (set/difference leave enter)]
|
||||
leave (set/difference leave enter)
|
||||
search-match (get-in @st/state [:workspace-local :search-match-highlight])
|
||||
leave (cond-> leave
|
||||
(some? search-match) (disj search-match))]
|
||||
|
||||
(reset! sidebar-hover-queue {:enter #{} :leave #{}})
|
||||
(reset! sidebar-hover-pending? false)
|
||||
|
||||
@ -19,7 +19,9 @@
|
||||
[app.main.store :as st]
|
||||
[app.main.ui.components.search-bar :refer [search-bar*]]
|
||||
[app.main.ui.components.title-bar :refer [title-bar*]]
|
||||
[app.main.ui.ds.buttons.button :refer [button*]]
|
||||
[app.main.ui.ds.buttons.icon-button :refer [icon-button*]]
|
||||
[app.main.ui.ds.controls.input :refer [input*]]
|
||||
[app.main.ui.ds.foundations.assets.icon :refer [icon*] :as i]
|
||||
[app.main.ui.hooks :as hooks]
|
||||
[app.main.ui.notifications.badge :refer [badge-notification]]
|
||||
@ -30,6 +32,7 @@
|
||||
[app.util.keyboard :as kbd]
|
||||
[app.util.rxops :refer [throttle-fn]]
|
||||
[app.util.shape-icon :as usi]
|
||||
[app.util.timers :as ts]
|
||||
[beicon.v2.core :as rx]
|
||||
[cuerdas.core :as str]
|
||||
[goog.events :as events]
|
||||
@ -120,30 +123,28 @@
|
||||
[:> hooks/sortable-container* {}
|
||||
(for [obj shapes]
|
||||
(if (cfh/frame-shape? obj)
|
||||
[:> frame-wrapper*
|
||||
{:item obj
|
||||
:rename-id rename-id
|
||||
:selected selected
|
||||
:highlighted highlighted
|
||||
:index (unchecked-get obj "__$__counter")
|
||||
:objects objects
|
||||
:key (dm/str (get obj :id))
|
||||
:is-sortable true
|
||||
:is-filtered is-filtered
|
||||
:parent-size parent-size
|
||||
:depth -1}]
|
||||
[:> layer-item*
|
||||
{:item obj
|
||||
:rename-id rename-id
|
||||
:selected selected
|
||||
:highlighted highlighted
|
||||
:index (unchecked-get obj "__$__counter")
|
||||
:objects objects
|
||||
:key (dm/str (get obj :id))
|
||||
:is-sortable true
|
||||
:is-filtered is-filtered
|
||||
:depth -1
|
||||
:parent-size parent-size}]))]]))
|
||||
[:> frame-wrapper* {:item obj
|
||||
:rename-id rename-id
|
||||
:selected selected
|
||||
:highlighted highlighted
|
||||
:index (unchecked-get obj "__$__counter")
|
||||
:objects objects
|
||||
:key (dm/str (get obj :id))
|
||||
:is-sortable true
|
||||
:is-filtered is-filtered
|
||||
:parent-size parent-size
|
||||
:depth -1}]
|
||||
[:> layer-item* {:item obj
|
||||
:rename-id rename-id
|
||||
:selected selected
|
||||
:highlighted highlighted
|
||||
:index (unchecked-get obj "__$__counter")
|
||||
:objects objects
|
||||
:key (dm/str (get obj :id))
|
||||
:is-sortable true
|
||||
:is-filtered is-filtered
|
||||
:depth -1
|
||||
:parent-size parent-size}]))]]))
|
||||
|
||||
(mf/defc layers-tree-wrapper*
|
||||
{::mf/private true}
|
||||
@ -175,21 +176,22 @@
|
||||
{::mf/wrap [mf/memo #(mf/throttle % 300)]
|
||||
::mf/private true}
|
||||
[{:keys [objects parent-size]}]
|
||||
(let [selected (use-selected-shapes)
|
||||
root (get objects uuid/zero)]
|
||||
(let [selected (use-selected-shapes)
|
||||
highlighted (mf/deref ref:highlighted-shapes)
|
||||
root (get objects uuid/zero)]
|
||||
[:ul {:class (stl/css :element-list)}
|
||||
(for [[index id] (d/enumerate (:shapes root))]
|
||||
(when-let [obj (get objects id)]
|
||||
[:> layer-item*
|
||||
{:item obj
|
||||
:selected selected
|
||||
:index index
|
||||
:objects objects
|
||||
:key id
|
||||
:is-sortable false
|
||||
:is-filtered true
|
||||
:depth -1
|
||||
:parent-size parent-size}]))]))
|
||||
[:> layer-item* {:item obj
|
||||
:selected selected
|
||||
:highlighted highlighted
|
||||
:index index
|
||||
:objects objects
|
||||
:key id
|
||||
:is-sortable false
|
||||
:is-filtered true
|
||||
:depth -1
|
||||
:parent-size parent-size}]))]))
|
||||
|
||||
(defn calc-reparented-objects
|
||||
[objects]
|
||||
@ -208,8 +210,8 @@
|
||||
|
||||
;; --- Layers Toolbox
|
||||
|
||||
(def ^:private ref:layers-panel-search
|
||||
(l/derived (l/key :layers-panel-search) refs/workspace-local))
|
||||
(def ^:private ref:layers-search
|
||||
(l/derived (l/key :layers-search) refs/workspace-local))
|
||||
|
||||
;; FIXME: optimize
|
||||
(defn- match-filters?
|
||||
@ -242,6 +244,21 @@
|
||||
(false? (:masked-group shape))))
|
||||
(and (contains? filters :mask) (true? (:masked-group shape))))))))
|
||||
|
||||
(mf/defc radio-button*
|
||||
{::mf/private true}
|
||||
[{:keys [name checked text on-change]}]
|
||||
[:label {:class (stl/css-case :radio-label true
|
||||
:selected checked)}
|
||||
[:span {:class (stl/css-case :radio-icon true
|
||||
:checked checked)}]
|
||||
[:input {:type "radio"
|
||||
:name name
|
||||
:class (stl/css :radio-input)
|
||||
:checked checked
|
||||
:on-change on-change}]
|
||||
[:span {:class (stl/css :radio-text)}
|
||||
text]])
|
||||
|
||||
(defn use-search
|
||||
[page objects]
|
||||
(let [state* (mf/use-state
|
||||
@ -254,7 +271,7 @@
|
||||
:filters #{}
|
||||
:num-items 100
|
||||
:current-match-idx 0}))
|
||||
layers-search-request (mf/deref ref:layers-panel-search)
|
||||
layers-search (mf/deref ref:layers-search)
|
||||
state (deref state*)
|
||||
current-filters (:filters state)
|
||||
current-items (:num-items state)
|
||||
@ -265,13 +282,18 @@
|
||||
find-replace-mode? (:find-replace-mode? state)
|
||||
search-scope (:search-scope state)
|
||||
current-match-idx (:current-match-idx state)
|
||||
search-input-ref (mf/use-ref nil)
|
||||
|
||||
clear-search-text
|
||||
(mf/use-fn
|
||||
#(swap! state* assoc :search-text "" :num-items 100 :current-match-idx 0))
|
||||
#(swap! state* assoc
|
||||
:search-text ""
|
||||
:num-items 100
|
||||
:current-match-idx 0))
|
||||
|
||||
toggle-filters
|
||||
(mf/use-fn #(swap! state* update :show-menu not))
|
||||
(mf/use-fn
|
||||
#(swap! state* update :show-menu not))
|
||||
|
||||
on-toggle-filters-click
|
||||
(mf/use-fn
|
||||
@ -280,51 +302,92 @@
|
||||
(toggle-filters)))
|
||||
|
||||
hide-menu
|
||||
(mf/use-fn #(swap! state* assoc :show-menu false))
|
||||
(mf/use-fn
|
||||
#(swap! state* assoc :show-menu false))
|
||||
|
||||
on-key-down
|
||||
(mf/use-fn (fn [event] (when (kbd/esc? event) (hide-menu))))
|
||||
(mf/use-fn
|
||||
(fn [event]
|
||||
(when (kbd/esc? event)
|
||||
(hide-menu))))
|
||||
|
||||
update-search-text
|
||||
(mf/use-fn
|
||||
(fn [value _event]
|
||||
(swap! state* assoc :search-text value :num-items 100 :current-match-idx 0)))
|
||||
(fn [value]
|
||||
(swap! state* assoc
|
||||
:search-text value
|
||||
:num-items 100
|
||||
:current-match-idx 0)))
|
||||
|
||||
update-replace-text
|
||||
(mf/use-fn (fn [value _event] (swap! state* assoc :replace-text value)))
|
||||
(mf/use-fn
|
||||
(fn [event]
|
||||
(let [value (dom/get-target-val event)]
|
||||
(swap! state* assoc :replace-text value))))
|
||||
|
||||
clear-replace-text
|
||||
(mf/use-fn #(swap! state* assoc :replace-text ""))
|
||||
f-key? (kbd/is-key-ignore-case? "f")
|
||||
h-key? (kbd/is-key-ignore-case? "h")
|
||||
|
||||
handle-find-shortcut-keydown
|
||||
(mf/use-fn
|
||||
(fn [event]
|
||||
(when (kbd/mod? event)
|
||||
(cond
|
||||
(f-key? event)
|
||||
(do
|
||||
(dom/prevent-default event)
|
||||
(dom/stop-propagation event)
|
||||
(st/emit! (dw/open-layers-search :find)))
|
||||
|
||||
(h-key? event)
|
||||
(do
|
||||
(dom/prevent-default event)
|
||||
(dom/stop-propagation event)
|
||||
(st/emit! (dw/open-layers-search :find-and-replace)))))))
|
||||
|
||||
set-search-scope
|
||||
(mf/use-fn
|
||||
(fn [scope]
|
||||
(swap! state* assoc :search-scope scope :num-items 100 :current-match-idx 0)))
|
||||
(swap! state* assoc
|
||||
:search-scope scope
|
||||
:num-items 100
|
||||
:current-match-idx 0)
|
||||
(st/emit! (dw/update-layers-search-scope scope))))
|
||||
|
||||
toggle-mode
|
||||
(mf/use-fn
|
||||
(mf/deps find-replace-mode?)
|
||||
(fn []
|
||||
(let [mode (if find-replace-mode? :find :find-and-replace)]
|
||||
(st/emit! (dw/open-layers-search mode {:force? true})))))
|
||||
|
||||
toggle-search
|
||||
(mf/use-fn
|
||||
(mf/deps show-search?)
|
||||
(fn [event]
|
||||
(let [node (dom/get-current-target event)]
|
||||
(dom/blur! node)
|
||||
(swap! state* (fn [state]
|
||||
(-> state
|
||||
(assoc :search-text "" :replace-text "" :filters #{})
|
||||
(assoc :show-menu false :find-replace-mode? false)
|
||||
(assoc :search-scope :layers :num-items 100 :current-match-idx 0)
|
||||
(update :show-search not)))))))
|
||||
(if show-search?
|
||||
(st/emit! dw/close-layers-search)
|
||||
(st/emit! (dw/open-layers-search :find {:force? true}))))))
|
||||
|
||||
remove-filter
|
||||
(mf/use-fn
|
||||
(fn [event]
|
||||
(let [fkey (-> (dom/get-current-target event) (dom/get-data "filter") (keyword))]
|
||||
(let [fkey (-> (dom/get-current-target event)
|
||||
(dom/get-data "filter")
|
||||
(keyword))]
|
||||
(swap! state* (fn [state]
|
||||
(-> state (update :filters disj fkey) (assoc :num-items 100)))))))
|
||||
(-> state
|
||||
(update :filters disj fkey)
|
||||
(assoc :num-items 100)))))))
|
||||
|
||||
add-filter
|
||||
(mf/use-fn
|
||||
(fn [event]
|
||||
(dom/stop-propagation event)
|
||||
(let [key (-> (dom/get-current-target event) (dom/get-data "filter") (keyword))]
|
||||
(let [key (-> (dom/get-current-target event)
|
||||
(dom/get-data "filter") (keyword))]
|
||||
(swap! state* (fn [state]
|
||||
(-> state
|
||||
(update :filters conj key)
|
||||
@ -374,7 +437,8 @@
|
||||
(fn [_]
|
||||
(when (pos? text-match-count)
|
||||
(swap! state* update :current-match-idx
|
||||
(fn [idx] (mod (inc idx) text-match-count))))))
|
||||
(fn [idx]
|
||||
(mod (inc idx) text-match-count))))))
|
||||
|
||||
navigate-prev
|
||||
(mf/use-fn
|
||||
@ -382,7 +446,8 @@
|
||||
(fn [_]
|
||||
(when (pos? text-match-count)
|
||||
(swap! state* update :current-match-idx
|
||||
(fn [idx] (mod (+ (dec idx) text-match-count) text-match-count))))))
|
||||
(fn [idx]
|
||||
(mod (+ (dec idx) text-match-count) text-match-count))))))
|
||||
|
||||
handle-replace
|
||||
(mf/use-fn
|
||||
@ -403,6 +468,24 @@
|
||||
(st/emit! (dwt/replace-text-in-shapes text-match-ids current-search replace-text))
|
||||
(st/emit! (dwt/replace-layer-names-in-shapes text-match-ids current-search replace-text))))))
|
||||
|
||||
on-replace-keydown
|
||||
(mf/use-fn
|
||||
(mf/deps handle-replace)
|
||||
(fn [event]
|
||||
(when (or (kbd/enter? event) (kbd/space? event))
|
||||
(dom/prevent-default event)
|
||||
(dom/stop-propagation event)
|
||||
(handle-replace event))))
|
||||
|
||||
on-replace-all-keydown
|
||||
(mf/use-fn
|
||||
(mf/deps handle-replace-all)
|
||||
(fn [event]
|
||||
(when (or (kbd/enter? event) (kbd/space? event))
|
||||
(dom/prevent-default event)
|
||||
(dom/stop-propagation event)
|
||||
(handle-replace-all event))))
|
||||
|
||||
filtered-objects
|
||||
(mf/with-memo [active? filtered-objects-all current-items]
|
||||
(when active?
|
||||
@ -424,201 +507,242 @@
|
||||
(events/unlistenByKey key1)
|
||||
(events/unlistenByKey key2))))
|
||||
|
||||
(mf/with-effect [layers-search-request]
|
||||
(when (some? layers-search-request)
|
||||
(let [replace-mode? (= layers-search-request :find-and-replace)]
|
||||
(mf/with-effect [layers-search]
|
||||
(if-let [{:keys [open? find-replace-mode? scope]} layers-search]
|
||||
(when open?
|
||||
(swap! state* (fn [s]
|
||||
(-> s
|
||||
(assoc :show-search true :find-replace-mode? replace-mode?)
|
||||
(assoc :search-scope (if replace-mode? :canvas :layers))
|
||||
(assoc :search-text "" :replace-text "" :current-match-idx 0)))))
|
||||
(st/emit! dw/clear-layers-search)))
|
||||
(let [mode-changed? (not= (:find-replace-mode? s) find-replace-mode?)
|
||||
opening? (not (:show-search s))]
|
||||
(-> s
|
||||
(assoc :show-search true
|
||||
:find-replace-mode? find-replace-mode?
|
||||
:search-scope scope)
|
||||
(cond-> (or opening? mode-changed?)
|
||||
(assoc :search-text "" :replace-text "" :current-match-idx 0)))))))
|
||||
(swap! state* (fn [state]
|
||||
(-> state
|
||||
(assoc :search-text ""
|
||||
:replace-text ""
|
||||
:filters #{})
|
||||
(assoc :show-menu false
|
||||
:find-replace-mode? false)
|
||||
(assoc :search-scope :layers
|
||||
:num-items 100
|
||||
:current-match-idx 0)
|
||||
(assoc :show-search false))))))
|
||||
|
||||
(mf/with-effect [(get layers-search :scope)]
|
||||
(when (and layers-search (:open? layers-search))
|
||||
(swap! state* assoc :search-scope (:scope layers-search))))
|
||||
|
||||
(mf/with-effect [layers-search show-search?]
|
||||
(when (and layers-search (:open? layers-search) show-search?)
|
||||
(ts/raf
|
||||
(fn []
|
||||
(when-let [node (mf/ref-val search-input-ref)]
|
||||
(dom/focus! node))))))
|
||||
|
||||
(mf/with-effect [find-replace-mode? show-search? safe-match-idx text-match-ids]
|
||||
(let [match-ids text-match-ids]
|
||||
(when (and find-replace-mode? show-search? (seq match-ids))
|
||||
(let [current-id (nth match-ids safe-match-idx)]
|
||||
(st/emit! (dw/set-search-match-highlight current-id match-ids))))
|
||||
(fn []
|
||||
(when (seq match-ids)
|
||||
(st/emit! (dw/clear-search-match-highlight match-ids))))))
|
||||
|
||||
[filtered-objects
|
||||
handle-show-more
|
||||
#(mf/html
|
||||
(if show-search?
|
||||
[:*
|
||||
[:div {:class (stl/css :tool-window-bar :search)}
|
||||
[:> search-bar* {:on-change update-search-text
|
||||
[:div {:class (stl/css :tool-window-bar)}
|
||||
[:> search-bar* {:input-ref search-input-ref
|
||||
:class (stl/css :search-item)
|
||||
:on-change update-search-text
|
||||
:value current-search
|
||||
:on-clear clear-search-text
|
||||
:on-key-down handle-find-shortcut-keydown
|
||||
:placeholder (tr "workspace.sidebar.layers.search")}
|
||||
[:button {:on-click on-toggle-filters-click
|
||||
:class (stl/css-case :filter-button true :opened show-menu? :active active?)}
|
||||
[:> icon* {:icon-id i/filter}]]]
|
||||
[:> icon-button* {:variant "secondary"
|
||||
:class (stl/css :filter-button)
|
||||
:aria-pressed show-menu?
|
||||
:aria-label (tr "workspace.sidebar.layers.filter")
|
||||
:on-click on-toggle-filters-click
|
||||
:icon i/filter}]]
|
||||
[:> icon-button* {:variant "ghost"
|
||||
:aria-pressed find-replace-mode?
|
||||
:aria-label (tr "workspace.sidebar.layers.search-and-replace")
|
||||
:on-click toggle-mode
|
||||
:icon i/menu}]
|
||||
[:> icon-button* {:variant "ghost"
|
||||
:aria-label (tr "labels.close")
|
||||
:on-click toggle-search
|
||||
:icon i/close}]]
|
||||
|
||||
[:div {:class (stl/css :search-scope-row)}
|
||||
[:label {:class (stl/css-case :scope-option true :scope-selected (= :canvas search-scope))}
|
||||
[:span {:class (stl/css-case :scope-radio true :scope-radio-checked (= :canvas search-scope))}]
|
||||
[:input {:type "radio" :name "search-scope" :class (stl/css :scope-radio-input)
|
||||
:checked (= :canvas search-scope)
|
||||
:on-change (fn [_] (set-search-scope :canvas))}]
|
||||
[:span {:class (stl/css :scope-label)}
|
||||
(tr "workspace.sidebar.layers.search-scope-canvas")]]
|
||||
[:label {:class (stl/css-case :scope-option true :scope-selected (= :layers search-scope))}
|
||||
[:span {:class (stl/css-case :scope-radio true :scope-radio-checked (= :layers search-scope))}]
|
||||
[:input {:type "radio" :name "search-scope" :class (stl/css :scope-radio-input)
|
||||
:checked (= :layers search-scope)
|
||||
:on-change (fn [_] (set-search-scope :layers))}]
|
||||
[:span {:class (stl/css :scope-label)}
|
||||
(tr "workspace.sidebar.layers.search-scope-layers")]]]
|
||||
[:div {:class (stl/css :replace-wrapper)}
|
||||
(when ^boolean find-replace-mode?
|
||||
[:div {:class (stl/css :replace-row)}
|
||||
[:> input* {:type "text"
|
||||
:placeholder (tr "workspace.sidebar.layers.replace-placeholder")
|
||||
:on-key-down handle-find-shortcut-keydown
|
||||
:on-change update-replace-text}]
|
||||
|
||||
(when ^boolean find-replace-mode?
|
||||
[:*
|
||||
[:div {:class (stl/css :tool-window-bar :replace-row)}
|
||||
[:div {:class (stl/css :replace-input-wrapper)}
|
||||
[:input {:class (stl/css :replace-input)
|
||||
:value replace-text
|
||||
:placeholder (tr "workspace.sidebar.layers.replace-placeholder")
|
||||
:on-change (fn [event]
|
||||
(update-replace-text (dom/get-target-val event) event))}]
|
||||
(when (not= "" replace-text)
|
||||
[:button {:class (stl/css :clear-icon) :on-click clear-replace-text}
|
||||
[:> icon* {:icon-id i/delete-text :size "s"}]])]
|
||||
(when (d/not-empty? current-search)
|
||||
(if (pos? text-match-count)
|
||||
[:div {:class (stl/css :match-navigation)}
|
||||
[:span {:class (stl/css :match-count)}
|
||||
[:div {:class (stl/css :replace-match-navigation)}
|
||||
[:span {:class (stl/css :replace-match-count)}
|
||||
(dm/str (inc safe-match-idx) " / " text-match-count)]
|
||||
[:> icon-button* {:variant "ghost" :aria-label (tr "labels.previous")
|
||||
:on-click navigate-prev :icon i/arrow-up}]
|
||||
[:> icon-button* {:variant "ghost" :aria-label (tr "labels.next")
|
||||
:on-click navigate-next :icon i/arrow-down}]]
|
||||
[:span {:class (stl/css :no-matches)}
|
||||
(tr "workspace.sidebar.layers.no-matches")]))]
|
||||
[:div {:class (stl/css :replace-match-count)}
|
||||
(tr "workspace.sidebar.layers.no-matches")]))])
|
||||
|
||||
[:div {:class (stl/css :replace-scope-row)}
|
||||
[:> radio-button* {:name "search-scope"
|
||||
:checked (= :canvas search-scope)
|
||||
:text (tr "workspace.sidebar.layers.search-scope-canvas")
|
||||
:on-change (partial set-search-scope :canvas)}]
|
||||
[:> radio-button* {:name "search-scope"
|
||||
:checked (= :layers search-scope)
|
||||
:text (tr "workspace.sidebar.layers.search-scope-layers")
|
||||
:on-change (partial set-search-scope :layers)}]]
|
||||
|
||||
(when ^boolean find-replace-mode?
|
||||
[:div {:class (stl/css :replace-actions-row)}
|
||||
[:button {:class (stl/css :replace-button)
|
||||
:on-click handle-replace
|
||||
:disabled (or (zero? text-match-count) (str/empty? current-search))}
|
||||
[:> button* {:variant "secondary"
|
||||
:class (stl/css :replace-actions-button)
|
||||
:on-click handle-replace
|
||||
:on-key-down on-replace-keydown
|
||||
:disabled (or (zero? text-match-count) (str/empty? current-search))}
|
||||
(tr "workspace.sidebar.layers.replace")]
|
||||
[:button {:class (stl/css :replace-button)
|
||||
:on-click handle-replace-all
|
||||
:disabled (or (zero? text-match-count) (str/empty? current-search))}
|
||||
(tr "workspace.sidebar.layers.replace-all")]]])
|
||||
[:> button* {:variant "secondary"
|
||||
:class (stl/css :replace-actions-button)
|
||||
:on-click handle-replace-all
|
||||
:on-key-down on-replace-all-keydown
|
||||
:disabled (or (zero? text-match-count) (str/empty? current-search))}
|
||||
(tr "workspace.sidebar.layers.replace-all")]])
|
||||
|
||||
[:div {:class (stl/css :active-filters)}
|
||||
(for [fkey current-filters]
|
||||
(let [fname (d/name fkey)
|
||||
[:div {:class (stl/css :active-filters)}
|
||||
(for [fkey current-filters]
|
||||
(let [fname (d/name fkey)
|
||||
|
||||
name (case fkey
|
||||
:frame (tr "workspace.sidebar.layers.frames")
|
||||
:group (tr "workspace.sidebar.layers.groups")
|
||||
:mask (tr "workspace.sidebar.layers.masks")
|
||||
:component (tr "workspace.sidebar.layers.components")
|
||||
:text (tr "workspace.sidebar.layers.texts")
|
||||
:image (tr "workspace.sidebar.layers.images")
|
||||
:shape (tr "workspace.sidebar.layers.shapes")
|
||||
(tr fkey))
|
||||
filter-icon (usi/get-shape-icon-by-type fkey)]
|
||||
name (case fkey
|
||||
:frame (tr "workspace.sidebar.layers.frames")
|
||||
:group (tr "workspace.sidebar.layers.groups")
|
||||
:mask (tr "workspace.sidebar.layers.masks")
|
||||
:component (tr "workspace.sidebar.layers.components")
|
||||
:text (tr "workspace.sidebar.layers.texts")
|
||||
:image (tr "workspace.sidebar.layers.images")
|
||||
:shape (tr "workspace.sidebar.layers.shapes")
|
||||
(tr fkey))
|
||||
filter-icon (usi/get-shape-icon-by-type fkey)]
|
||||
|
||||
[:button {:class (stl/css :layer-filter)
|
||||
:key fname
|
||||
:data-filter fname
|
||||
:on-click remove-filter}
|
||||
[:> icon* {:icon-id filter-icon :size "s" :class (stl/css :layer-filter-icon)}]
|
||||
[:span {:class (stl/css :layer-filter-name)}
|
||||
name]
|
||||
[:> icon* {:icon-id i/close-small :class (stl/css :layer-filter-close)}]]))]
|
||||
[:button {:class (stl/css :layer-filter)
|
||||
:key fname
|
||||
:data-filter fname
|
||||
:on-click remove-filter}
|
||||
[:> icon* {:icon-id filter-icon :size "s" :class (stl/css :layer-filter-icon)}]
|
||||
[:span {:class (stl/css :layer-filter-name)}
|
||||
name]
|
||||
[:> icon* {:icon-id i/close-small :class (stl/css :layer-filter-close)}]]))]
|
||||
|
||||
(when ^boolean show-menu?
|
||||
[:ul {:class (stl/css :filters-container)}
|
||||
[:li {:class (stl/css-case :filter-menu-item true
|
||||
:selected (contains? current-filters :frame))
|
||||
:data-filter "frame"
|
||||
:on-click add-filter}
|
||||
[:div {:class (stl/css :filter-menu-item-name-wrapper)}
|
||||
[:> icon* {:icon-id i/board :size "s" :class (stl/css :filter-menu-item-icon)}]
|
||||
[:span {:class (stl/css :filter-menu-item-name)}
|
||||
(tr "workspace.sidebar.layers.frames")]]
|
||||
(when ^boolean show-menu?
|
||||
[:ul {:class (stl/css :filters-container)}
|
||||
[:li {:class (stl/css-case :filter-menu-item true
|
||||
:selected (contains? current-filters :frame))
|
||||
:data-filter "frame"
|
||||
:on-click add-filter}
|
||||
[:div {:class (stl/css :filter-menu-item-name-wrapper)}
|
||||
[:> icon* {:icon-id i/board :size "s" :class (stl/css :filter-menu-item-icon)}]
|
||||
[:span {:class (stl/css :filter-menu-item-name)}
|
||||
(tr "workspace.sidebar.layers.frames")]]
|
||||
|
||||
(when (contains? current-filters :frame)
|
||||
[:> icon* {:icon-id i/tick :size "s" :class (stl/css :filter-menu-item-tick)}])]
|
||||
(when (contains? current-filters :frame)
|
||||
[:> icon* {:icon-id i/tick :size "s" :class (stl/css :filter-menu-item-tick)}])]
|
||||
|
||||
[:li {:class (stl/css-case :filter-menu-item true
|
||||
:selected (contains? current-filters :group))
|
||||
:data-filter "group"
|
||||
:on-click add-filter}
|
||||
[:div {:class (stl/css :filter-menu-item-name-wrapper)}
|
||||
[:> icon* {:icon-id i/group :size "s" :class (stl/css :filter-menu-item-icon)}]
|
||||
[:span {:class (stl/css :filter-menu-item-name)}
|
||||
(tr "workspace.sidebar.layers.groups")]]
|
||||
[:li {:class (stl/css-case :filter-menu-item true
|
||||
:selected (contains? current-filters :group))
|
||||
:data-filter "group"
|
||||
:on-click add-filter}
|
||||
[:div {:class (stl/css :filter-menu-item-name-wrapper)}
|
||||
[:> icon* {:icon-id i/group :size "s" :class (stl/css :filter-menu-item-icon)}]
|
||||
[:span {:class (stl/css :filter-menu-item-name)}
|
||||
(tr "workspace.sidebar.layers.groups")]]
|
||||
|
||||
(when (contains? current-filters :group)
|
||||
[:> icon* {:icon-id i/tick :size "s" :class (stl/css :filter-menu-item-tick)}])]
|
||||
(when (contains? current-filters :group)
|
||||
[:> icon* {:icon-id i/tick :size "s" :class (stl/css :filter-menu-item-tick)}])]
|
||||
|
||||
[:li {:class (stl/css-case :filter-menu-item true
|
||||
:selected (contains? current-filters :mask))
|
||||
:data-filter "mask"
|
||||
:on-click add-filter}
|
||||
[:div {:class (stl/css :filter-menu-item-name-wrapper)}
|
||||
[:> icon* {:icon-id i/mask :size "s" :class (stl/css :filter-menu-item-icon)}]
|
||||
[:span {:class (stl/css :filter-menu-item-name)}
|
||||
(tr "workspace.sidebar.layers.masks")]]
|
||||
[:li {:class (stl/css-case :filter-menu-item true
|
||||
:selected (contains? current-filters :mask))
|
||||
:data-filter "mask"
|
||||
:on-click add-filter}
|
||||
[:div {:class (stl/css :filter-menu-item-name-wrapper)}
|
||||
[:> icon* {:icon-id i/mask :size "s" :class (stl/css :filter-menu-item-icon)}]
|
||||
[:span {:class (stl/css :filter-menu-item-name)}
|
||||
(tr "workspace.sidebar.layers.masks")]]
|
||||
|
||||
(when (contains? current-filters :mask)
|
||||
[:> icon* {:icon-id i/tick :size "s" :class (stl/css :filter-menu-item-tick)}])]
|
||||
(when (contains? current-filters :mask)
|
||||
[:> icon* {:icon-id i/tick :size "s" :class (stl/css :filter-menu-item-tick)}])]
|
||||
|
||||
[:li {:class (stl/css-case :filter-menu-item true
|
||||
:selected (contains? current-filters :component))
|
||||
:data-filter "component"
|
||||
:on-click add-filter}
|
||||
[:div {:class (stl/css :filter-menu-item-name-wrapper)}
|
||||
[:> icon* {:icon-id i/component :size "s" :class (stl/css :filter-menu-item-icon)}]
|
||||
[:span {:class (stl/css :filter-menu-item-name)}
|
||||
(tr "workspace.sidebar.layers.components")]]
|
||||
[:li {:class (stl/css-case :filter-menu-item true
|
||||
:selected (contains? current-filters :component))
|
||||
:data-filter "component"
|
||||
:on-click add-filter}
|
||||
[:div {:class (stl/css :filter-menu-item-name-wrapper)}
|
||||
[:> icon* {:icon-id i/component :size "s" :class (stl/css :filter-menu-item-icon)}]
|
||||
[:span {:class (stl/css :filter-menu-item-name)}
|
||||
(tr "workspace.sidebar.layers.components")]]
|
||||
|
||||
(when (contains? current-filters :component)
|
||||
[:> icon* {:icon-id i/tick :size "s" :class (stl/css :filter-menu-item-tick)}])]
|
||||
(when (contains? current-filters :component)
|
||||
[:> icon* {:icon-id i/tick :size "s" :class (stl/css :filter-menu-item-tick)}])]
|
||||
|
||||
[:li {:class (stl/css-case :filter-menu-item true
|
||||
:selected (contains? current-filters :text))
|
||||
:data-filter "text"
|
||||
:on-click add-filter}
|
||||
[:div {:class (stl/css :filter-menu-item-name-wrapper)}
|
||||
[:> icon* {:icon-id i/text :size "s" :class (stl/css :filter-menu-item-icon)}]
|
||||
[:span {:class (stl/css :filter-menu-item-name)}
|
||||
(tr "workspace.sidebar.layers.texts")]]
|
||||
[:li {:class (stl/css-case :filter-menu-item true
|
||||
:selected (contains? current-filters :text))
|
||||
:data-filter "text"
|
||||
:on-click add-filter}
|
||||
[:div {:class (stl/css :filter-menu-item-name-wrapper)}
|
||||
[:> icon* {:icon-id i/text :size "s" :class (stl/css :filter-menu-item-icon)}]
|
||||
[:span {:class (stl/css :filter-menu-item-name)}
|
||||
(tr "workspace.sidebar.layers.texts")]]
|
||||
|
||||
(when (contains? current-filters :text)
|
||||
[:> icon* {:icon-id i/tick :size "s" :class (stl/css :filter-menu-item-tick)}])]
|
||||
(when (contains? current-filters :text)
|
||||
[:> icon* {:icon-id i/tick :size "s" :class (stl/css :filter-menu-item-tick)}])]
|
||||
|
||||
[:li {:class (stl/css-case :filter-menu-item true
|
||||
:selected (contains? current-filters :image))
|
||||
:data-filter "image"
|
||||
:on-click add-filter}
|
||||
[:div {:class (stl/css :filter-menu-item-name-wrapper)}
|
||||
[:> icon* {:icon-id i/img :size "s" :class (stl/css :filter-menu-item-icon)}]
|
||||
[:span {:class (stl/css :filter-menu-item-name)}
|
||||
(tr "workspace.sidebar.layers.images")]]
|
||||
[:li {:class (stl/css-case :filter-menu-item true
|
||||
:selected (contains? current-filters :image))
|
||||
:data-filter "image"
|
||||
:on-click add-filter}
|
||||
[:div {:class (stl/css :filter-menu-item-name-wrapper)}
|
||||
[:> icon* {:icon-id i/img :size "s" :class (stl/css :filter-menu-item-icon)}]
|
||||
[:span {:class (stl/css :filter-menu-item-name)}
|
||||
(tr "workspace.sidebar.layers.images")]]
|
||||
|
||||
(when (contains? current-filters :image)
|
||||
[:> icon* {:icon-id i/tick :size "s" :class (stl/css :filter-menu-item-tick)}])]
|
||||
(when (contains? current-filters :image)
|
||||
[:> icon* {:icon-id i/tick :size "s" :class (stl/css :filter-menu-item-tick)}])]
|
||||
|
||||
[:li {:class (stl/css-case :filter-menu-item true
|
||||
:selected (contains? current-filters :shape))
|
||||
:data-filter "shape"
|
||||
:on-click add-filter}
|
||||
[:div {:class (stl/css :filter-menu-item-name-wrapper)}
|
||||
[:> icon* {:icon-id i/path :size "s" :class (stl/css :filter-menu-item-icon)}]
|
||||
[:span {:class (stl/css :filter-menu-item-name)}
|
||||
(tr "workspace.sidebar.layers.shapes")]]
|
||||
[:li {:class (stl/css-case :filter-menu-item true
|
||||
:selected (contains? current-filters :shape))
|
||||
:data-filter "shape"
|
||||
:on-click add-filter}
|
||||
[:div {:class (stl/css :filter-menu-item-name-wrapper)}
|
||||
[:> icon* {:icon-id i/path :size "s" :class (stl/css :filter-menu-item-icon)}]
|
||||
[:span {:class (stl/css :filter-menu-item-name)}
|
||||
(tr "workspace.sidebar.layers.shapes")]]
|
||||
|
||||
(when (contains? current-filters :shape)
|
||||
[:> icon* {:icon-id i/tick :size "s" :class (stl/css :filter-menu-item-tick)}])]])]
|
||||
(when (contains? current-filters :shape)
|
||||
[:> icon* {:icon-id i/tick :size "s" :class (stl/css :filter-menu-item-tick)}])]])]]
|
||||
|
||||
[:div {:class (stl/css :tool-window-bar)}
|
||||
[:> title-bar* {:collapsable false
|
||||
:class (stl/css :tool-window-bar-title)
|
||||
:title (:name page)
|
||||
:on-btn-click toggle-search
|
||||
:btn-icon "search"
|
||||
:btn-title (tr "labels.search")}]]))]))
|
||||
|
||||
|
||||
(defn- on-scroll
|
||||
[event]
|
||||
(let [children (dom/get-elements-by-class "sticky-children")
|
||||
@ -699,19 +823,24 @@
|
||||
(mf/use-fn
|
||||
#(st/emit! (dw/toggle-focus-mode)))]
|
||||
|
||||
[:div#layers {:class (stl/css :layers) :data-testid "layer-tree"}
|
||||
[:div {:id "layers"
|
||||
:class (stl/css :layers)
|
||||
:data-testid "layer-tree"}
|
||||
|
||||
(if (d/not-empty? focus)
|
||||
[:div {:class (stl/css :tool-window-bar)}
|
||||
[:button {:class (stl/css :focus-title)
|
||||
:on-click toogle-focus-mode}
|
||||
[:span {:class (stl/css :back-button)}
|
||||
[:> icon* {:icon-id i/arrow}]]
|
||||
[:span {:class (stl/css :focus-back-button)}
|
||||
[:> icon* {:icon-id i/arrow-left}]]
|
||||
|
||||
[:div {:class (stl/css :focus-name)}
|
||||
(or title (tr "workspace.sidebar.layers"))]
|
||||
|
||||
[:div {:class (stl/css :focus-mode-tag-wrapper)}
|
||||
[:& badge-notification {:content (tr "workspace.focus.focus-mode") :size :small :is-focus true}]]]]
|
||||
[:& badge-notification {:content (tr "workspace.focus.focus-mode")
|
||||
:size :small
|
||||
:is-focus true}]]]]
|
||||
|
||||
(filter-component))
|
||||
|
||||
@ -724,11 +853,11 @@
|
||||
:key (dm/str page-id)
|
||||
:parent-size size-parent}]
|
||||
[:div {:ref lazy-load-ref}]]
|
||||
|
||||
[:div {:on-scroll on-scroll
|
||||
:class (stl/css :tool-window-content)
|
||||
:data-scroll-container true
|
||||
:style {:display (when (some? filtered-objects) "none")}}
|
||||
|
||||
[:> layers-tree-wrapper* {:objects filtered-objects
|
||||
:key (dm/str page-id)
|
||||
:is-filtered true
|
||||
|
||||
@ -5,144 +5,100 @@
|
||||
// Copyright (c) KALEIDOS INC
|
||||
|
||||
@use "refactor/common-refactor.scss" as deprecated;
|
||||
@use "ds/borders.scss" as *;
|
||||
@use "ds/mixins.scss" as *;
|
||||
@use "ds/sizes.scss" as *;
|
||||
@use "ds/spacing.scss" as *;
|
||||
@use "ds/typography.scss" as t;
|
||||
@use "ds/_sizes.scss" as *;
|
||||
@use "ds/_utils.scss" as *;
|
||||
|
||||
.element-list {
|
||||
display: grid;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.tool-window-bar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
height: deprecated.$s-32;
|
||||
min-height: deprecated.$s-32;
|
||||
margin: deprecated.$s-8 0 deprecated.$s-4 deprecated.$s-8;
|
||||
padding-right: deprecated.$s-12;
|
||||
|
||||
&.search {
|
||||
padding: 0 deprecated.$s-12 0 deprecated.$s-8;
|
||||
gap: deprecated.$s-4;
|
||||
|
||||
.filter-button {
|
||||
@include deprecated.flex-center;
|
||||
@include deprecated.button-style;
|
||||
|
||||
height: deprecated.$s-32;
|
||||
width: deprecated.$s-32;
|
||||
margin: 0;
|
||||
border: deprecated.$s-1 solid var(--color-background-tertiary);
|
||||
border-radius: deprecated.$br-8 deprecated.$br-2 deprecated.$br-2 deprecated.$br-8;
|
||||
background-color: var(--color-background-tertiary);
|
||||
|
||||
svg {
|
||||
height: deprecated.$s-16;
|
||||
width: deprecated.$s-16;
|
||||
stroke: var(--icon-foreground);
|
||||
}
|
||||
|
||||
&:focus {
|
||||
border: deprecated.$s-1 solid var(--input-border-color-focus);
|
||||
outline: 0;
|
||||
background-color: var(--input-background-color-active);
|
||||
color: var(--input-foreground-color-active);
|
||||
|
||||
svg {
|
||||
background-color: var(--input-background-color-active);
|
||||
}
|
||||
}
|
||||
|
||||
&:hover {
|
||||
border: deprecated.$s-1 solid var(--input-border-color-hover);
|
||||
background-color: var(--input-background-color-hover);
|
||||
|
||||
svg {
|
||||
background-color: var(--input-background-color-hover);
|
||||
stroke: var(--button-foreground-hover);
|
||||
}
|
||||
}
|
||||
|
||||
&.opened {
|
||||
@extend %button-icon-selected;
|
||||
}
|
||||
}
|
||||
}
|
||||
gap: var(--sp-xs);
|
||||
padding: var(--sp-m) var(--sp-m) 0 var(--sp-m);
|
||||
}
|
||||
|
||||
.page-name {
|
||||
@include deprecated.uppercase-title-typography;
|
||||
|
||||
padding: 0 deprecated.$s-12;
|
||||
color: var(--title-foreground-color);
|
||||
.tool-window-bar-title {
|
||||
margin-block-end: var(--sp-s);
|
||||
}
|
||||
|
||||
.icon-search {
|
||||
@extend %button-tertiary;
|
||||
.tool-window-content {
|
||||
--calculated-height: calc(#{px2rem(136)} + var(--height, #{$sz-200}));
|
||||
|
||||
height: deprecated.$s-32;
|
||||
width: deprecated.$s-28;
|
||||
border-radius: deprecated.$br-8;
|
||||
margin-right: deprecated.$s-8;
|
||||
padding: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
block-size: calc(100vh - var(--calculated-height));
|
||||
inline-size: calc(var(--left-sidebar-width) + var(--depth) * var(--layer-indentation-size));
|
||||
overflow: auto;
|
||||
scrollbar-gutter: stable;
|
||||
}
|
||||
|
||||
svg {
|
||||
@extend %button-icon;
|
||||
|
||||
stroke: var(--icon-foreground);
|
||||
}
|
||||
.filter-button {
|
||||
border-start-end-radius: 0;
|
||||
border-end-end-radius: 0;
|
||||
}
|
||||
|
||||
.focus-title {
|
||||
@include deprecated.button-style;
|
||||
|
||||
border: none;
|
||||
background: none;
|
||||
block-size: var(--sp-xxxl);
|
||||
inline-size: 100%;
|
||||
display: grid;
|
||||
grid-template-columns: auto 1fr auto;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
margin-block-end: var(--sp-s);
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.back-button {
|
||||
@include deprecated.flex-center;
|
||||
|
||||
height: deprecated.$s-32;
|
||||
width: deprecated.$s-24;
|
||||
padding: 0 deprecated.$s-4 0 deprecated.$s-8;
|
||||
|
||||
svg {
|
||||
@extend %button-icon-small;
|
||||
|
||||
stroke: var(--icon-foreground);
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
.focus-back-button {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
block-size: var(--sp-xxxl);
|
||||
padding-inline-start: var(--sp-s);
|
||||
color: var(--icon-foreground);
|
||||
}
|
||||
|
||||
.focus-name {
|
||||
@include deprecated.text-ellipsis;
|
||||
@include deprecated.body-small-typography;
|
||||
@include t.use-typography("body-small");
|
||||
@include text-ellipsis;
|
||||
|
||||
padding-left: deprecated.$s-4;
|
||||
padding-inline-end: var(--sp-xs);
|
||||
color: var(--title-foreground-color);
|
||||
}
|
||||
|
||||
.focus-mode-tag-wrapper {
|
||||
@include deprecated.flex-center;
|
||||
|
||||
height: 100%;
|
||||
margin-right: deprecated.$s-12;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
block-size: 100%;
|
||||
margin-inline-end: var(--sp-m);
|
||||
}
|
||||
|
||||
.active-filters {
|
||||
@include deprecated.flex-row;
|
||||
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--sp-xs);
|
||||
flex-wrap: wrap;
|
||||
margin: 0 deprecated.$s-12;
|
||||
margin: 0 var(--sp-m);
|
||||
}
|
||||
|
||||
.layer-filter {
|
||||
@extend %button-tag;
|
||||
|
||||
gap: deprecated.$s-6;
|
||||
height: deprecated.$s-24;
|
||||
margin: deprecated.$s-2 0;
|
||||
border-radius: deprecated.$br-6;
|
||||
gap: px2rem(6);
|
||||
block-size: var(--sp-xxl);
|
||||
margin: var(--sp-xxs) 0;
|
||||
border-radius: $br-6;
|
||||
background-color: var(--pill-background-color);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.layer-filter-icon,
|
||||
@ -151,9 +107,11 @@
|
||||
}
|
||||
|
||||
.layer-filter-name {
|
||||
@include deprecated.flex-center;
|
||||
@include deprecated.body-small-typography;
|
||||
@include t.use-typography("body-small");
|
||||
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: var(--pill-foreground-color);
|
||||
}
|
||||
|
||||
@ -161,34 +119,117 @@
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.replace-wrapper {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: var(--sp-xs) var(--sp-m);
|
||||
gap: var(--sp-xs);
|
||||
}
|
||||
|
||||
.replace-row {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: var(--sp-xs);
|
||||
}
|
||||
|
||||
.replace-scope-row {
|
||||
display: flex;
|
||||
gap: var(--sp-l);
|
||||
align-items: center;
|
||||
block-size: $sz-32;
|
||||
}
|
||||
|
||||
.replace-actions-row {
|
||||
display: flex;
|
||||
gap: var(--sp-xs);
|
||||
}
|
||||
|
||||
.replace-actions-button {
|
||||
justify-content: center;
|
||||
flex: 1 1 0;
|
||||
}
|
||||
|
||||
.replace-match-navigation {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--sp-xs);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.replace-match-count {
|
||||
@include t.use-typography("body-small");
|
||||
|
||||
color: var(--color-foreground-secondary);
|
||||
white-space: nowrap;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0 var(--sp-xs);
|
||||
}
|
||||
|
||||
.radio-label {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: px2rem(6);
|
||||
|
||||
&.selected {
|
||||
.radio-text {
|
||||
color: var(--color-foreground-primary);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.radio-icon {
|
||||
block-size: var(--sp-m);
|
||||
inline-size: var(--sp-m);
|
||||
border: $b-1 solid var(--color-foreground-secondary);
|
||||
border-radius: 50%;
|
||||
background-color: transparent;
|
||||
flex-shrink: 0;
|
||||
|
||||
&.checked {
|
||||
border-color: var(--color-accent-primary);
|
||||
background-color: var(--color-accent-primary);
|
||||
box-shadow: inset 0 0 0 var(--sp-xxs) var(--color-background-primary);
|
||||
}
|
||||
}
|
||||
|
||||
.radio-input {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.radio-text {
|
||||
@include t.use-typography("body-small");
|
||||
|
||||
color: var(--color-foreground-secondary);
|
||||
}
|
||||
|
||||
.filters-container {
|
||||
@extend %menu-dropdown;
|
||||
|
||||
position: absolute;
|
||||
left: deprecated.$s-20;
|
||||
width: deprecated.$s-192;
|
||||
inline-size: $sz-192;
|
||||
|
||||
.filter-menu-item {
|
||||
@include deprecated.body-small-typography;
|
||||
@include t.use-typography("body-small");
|
||||
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
width: 100%;
|
||||
padding: deprecated.$s-6;
|
||||
border-radius: deprecated.$br-8;
|
||||
inline-size: 100%;
|
||||
padding: px2rem(6);
|
||||
border-radius: $br-8;
|
||||
|
||||
.filter-menu-item-name-wrapper {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: deprecated.$s-8;
|
||||
gap: var(--sp-s);
|
||||
|
||||
.filter-menu-item-icon {
|
||||
color: var(--menu-foreground-color);
|
||||
}
|
||||
|
||||
.filter-menu-item-name {
|
||||
padding-top: deprecated.$s-2;
|
||||
padding-block-start: var(--sp-xxs);
|
||||
color: var(--menu-foreground-color);
|
||||
}
|
||||
}
|
||||
@ -234,173 +275,3 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.tool-window-content {
|
||||
--calculated-height: calc(#{deprecated.$s-136} + var(--height, #{deprecated.$s-200}));
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: calc(100vh - var(--calculated-height));
|
||||
width: calc(var(--left-sidebar-width) + var(--depth) * var(--layer-indentation-size));
|
||||
overflow: auto;
|
||||
scrollbar-gutter: stable;
|
||||
}
|
||||
|
||||
.replace-row {
|
||||
padding: 0 deprecated.$s-12;
|
||||
gap: deprecated.$s-4;
|
||||
}
|
||||
|
||||
.search-scope-row {
|
||||
display: flex;
|
||||
gap: deprecated.$s-16;
|
||||
padding: deprecated.$s-4 deprecated.$s-12 deprecated.$s-8;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.scope-option {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: deprecated.$s-6;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.scope-radio {
|
||||
width: deprecated.$s-12;
|
||||
height: deprecated.$s-12;
|
||||
border: deprecated.$s-1 solid var(--color-foreground-secondary);
|
||||
border-radius: 50%;
|
||||
background-color: transparent;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.scope-radio-checked {
|
||||
border-color: var(--color-accent-primary);
|
||||
background-color: var(--color-accent-primary);
|
||||
box-shadow: inset 0 0 0 deprecated.$s-2 var(--color-background-primary);
|
||||
}
|
||||
|
||||
.scope-radio-input {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.scope-label {
|
||||
@include deprecated.body-small-typography;
|
||||
|
||||
color: var(--color-foreground-secondary);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.scope-selected .scope-label {
|
||||
color: var(--color-foreground-primary);
|
||||
}
|
||||
|
||||
.replace-actions-row {
|
||||
display: flex;
|
||||
gap: deprecated.$s-4;
|
||||
padding: 0 deprecated.$s-12 deprecated.$s-8;
|
||||
}
|
||||
|
||||
.replace-input-wrapper {
|
||||
@include deprecated.flex-center;
|
||||
|
||||
flex: 1;
|
||||
height: deprecated.$s-32;
|
||||
border: deprecated.$s-1 solid var(--search-bar-input-border-color);
|
||||
border-radius: deprecated.$br-8;
|
||||
background-color: var(--search-bar-input-background-color);
|
||||
|
||||
&:hover {
|
||||
border: deprecated.$s-1 solid var(--input-border-color-hover);
|
||||
background-color: var(--input-background-color-hover);
|
||||
|
||||
.replace-input {
|
||||
background-color: var(--input-background-color-hover);
|
||||
}
|
||||
}
|
||||
|
||||
&:focus-within {
|
||||
background-color: var(--input-background-color-active);
|
||||
color: var(--input-foreground-color-active);
|
||||
border: deprecated.$s-1 solid var(--input-border-color-focus);
|
||||
|
||||
.replace-input {
|
||||
background-color: var(--input-background-color-active);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.replace-input {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
margin: 0 deprecated.$s-8;
|
||||
border: 0;
|
||||
background-color: var(--input-background-color);
|
||||
font-size: deprecated.$fs-12;
|
||||
color: var(--input-foreground-color);
|
||||
border-radius: deprecated.$br-8;
|
||||
|
||||
&:focus {
|
||||
outline: none;
|
||||
}
|
||||
}
|
||||
|
||||
.replace-button {
|
||||
@include deprecated.body-small-typography;
|
||||
@include deprecated.button-style;
|
||||
|
||||
flex: 1;
|
||||
height: deprecated.$s-28;
|
||||
padding: 0 deprecated.$s-8;
|
||||
border: deprecated.$s-1 solid var(--color-background-tertiary);
|
||||
border-radius: deprecated.$br-8;
|
||||
background-color: var(--color-background-tertiary);
|
||||
color: var(--color-foreground-primary);
|
||||
white-space: nowrap;
|
||||
text-transform: uppercase;
|
||||
|
||||
&:hover:not(:disabled) {
|
||||
border: deprecated.$s-1 solid var(--input-border-color-hover);
|
||||
background-color: var(--input-background-color-hover);
|
||||
}
|
||||
|
||||
&:disabled {
|
||||
opacity: 0.4;
|
||||
cursor: default;
|
||||
}
|
||||
}
|
||||
|
||||
.match-navigation {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: deprecated.$s-2;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.match-count {
|
||||
@include deprecated.body-small-typography;
|
||||
|
||||
color: var(--color-foreground-secondary);
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.no-matches {
|
||||
@include deprecated.body-small-typography;
|
||||
|
||||
color: var(--color-foreground-secondary);
|
||||
white-space: nowrap;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.clear-icon {
|
||||
@extend %button-tag;
|
||||
|
||||
flex: 0 0 deprecated.$s-32;
|
||||
height: 100%;
|
||||
color: var(--color-icon-default);
|
||||
}
|
||||
|
||||
.element-list {
|
||||
display: grid;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
@ -111,6 +111,8 @@
|
||||
(tr "shortcuts.duplicate")
|
||||
(tr "shortcuts.escape")
|
||||
(tr "shortcuts.export-shapes")
|
||||
(tr "shortcuts.find")
|
||||
(tr "shortcuts.find-and-replace")
|
||||
(tr "shortcuts.fit-all")
|
||||
(tr "shortcuts.flip-horizontal")
|
||||
(tr "shortcuts.flip-vertical")
|
||||
@ -160,6 +162,7 @@
|
||||
(tr "shortcuts.open-viewer")
|
||||
(tr "shortcuts.open-workspace")
|
||||
(tr "shortcuts.paste")
|
||||
(tr "shortcuts.paste-replace")
|
||||
(tr "shortcuts.prev-frame")
|
||||
(tr "shortcuts.redo")
|
||||
(tr "shortcuts.rename")
|
||||
|
||||
@ -4662,6 +4662,14 @@ msgstr "Cancel"
|
||||
msgid "shortcuts.export-shapes"
|
||||
msgstr "Export shapes"
|
||||
|
||||
#: src/app/main/ui/workspace/sidebar/shortcuts.cljs
|
||||
msgid "shortcuts.find"
|
||||
msgstr "Find"
|
||||
|
||||
#: src/app/main/ui/workspace/sidebar/shortcuts.cljs
|
||||
msgid "shortcuts.find-and-replace"
|
||||
msgstr "Find and replace"
|
||||
|
||||
#: src/app/main/ui/workspace/sidebar/shortcuts.cljs:114
|
||||
msgid "shortcuts.fit-all"
|
||||
msgstr "Zoom to fit all"
|
||||
@ -4866,6 +4874,10 @@ msgstr " or "
|
||||
msgid "shortcuts.paste"
|
||||
msgstr "Paste"
|
||||
|
||||
#: src/app/main/ui/workspace/sidebar/shortcuts.cljs
|
||||
msgid "shortcuts.paste-replace"
|
||||
msgstr "Paste and replace"
|
||||
|
||||
#: src/app/main/ui/workspace/sidebar/shortcuts.cljs:111
|
||||
#, unused
|
||||
msgid "shortcuts.paste-props"
|
||||
@ -8148,7 +8160,10 @@ msgstr "Masks"
|
||||
|
||||
#: src/app/main/ui/workspace/sidebar/layers.cljs:293
|
||||
msgid "workspace.sidebar.layers.search"
|
||||
msgstr "Search layers"
|
||||
msgstr "Find…"
|
||||
|
||||
msgid "workspace.sidebar.layers.search-and-replace"
|
||||
msgstr "Find and replace"
|
||||
|
||||
#: src/app/main/ui/workspace/sidebar/layers.cljs:316, src/app/main/ui/workspace/sidebar/layers.cljs:410
|
||||
msgid "workspace.sidebar.layers.shapes"
|
||||
@ -8180,11 +8195,11 @@ msgstr "No matches"
|
||||
|
||||
#: src/app/main/ui/workspace/sidebar/layers.cljs
|
||||
msgid "workspace.sidebar.layers.search-scope-layers"
|
||||
msgstr "Search layers"
|
||||
msgstr "Layer names"
|
||||
|
||||
#: src/app/main/ui/workspace/sidebar/layers.cljs
|
||||
msgid "workspace.sidebar.layers.search-scope-canvas"
|
||||
msgstr "Search on canvas"
|
||||
msgstr "Text content"
|
||||
|
||||
#: src/app/main/ui/inspect/attributes/svg.cljs:56, src/app/main/ui/workspace/sidebar/options/menus/svg_attrs.cljs:101
|
||||
msgid "workspace.sidebar.options.svg-attrs.title"
|
||||
|
||||
@ -5897,6 +5897,14 @@ msgstr "Rehacer"
|
||||
msgid "workspace.header.menu.select-all"
|
||||
msgstr "Seleccionar todo"
|
||||
|
||||
#: src/app/main/ui/workspace/main_menu.cljs
|
||||
msgid "workspace.header.menu.find"
|
||||
msgstr "Buscar"
|
||||
|
||||
#: src/app/main/ui/workspace/main_menu.cljs
|
||||
msgid "workspace.header.menu.find-and-replace"
|
||||
msgstr "Buscar y reemplazar"
|
||||
|
||||
#: src/app/main/ui/workspace/main_menu.cljs:423
|
||||
msgid "workspace.header.menu.show-artboard-names"
|
||||
msgstr "Mostrar nombres de tableros"
|
||||
@ -7968,6 +7976,9 @@ msgstr "Máscaras"
|
||||
msgid "workspace.sidebar.layers.search"
|
||||
msgstr "Buscar capas"
|
||||
|
||||
msgid "workspace.sidebar.layers.search-and-replace"
|
||||
msgstr "Buscar y reemplazar"
|
||||
|
||||
#: src/app/main/ui/workspace/sidebar/layers.cljs:316, src/app/main/ui/workspace/sidebar/layers.cljs:410
|
||||
msgid "workspace.sidebar.layers.shapes"
|
||||
msgstr "Formas"
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user