diff --git a/frontend/resources/images/icons/exclude.svg b/frontend/resources/images/icons/exclude.svg
new file mode 100644
index 000000000..843d6c0d0
--- /dev/null
+++ b/frontend/resources/images/icons/exclude.svg
@@ -0,0 +1 @@
+
diff --git a/frontend/resources/images/icons/filter.svg b/frontend/resources/images/icons/filter.svg
new file mode 100644
index 000000000..0ca8a5ab8
--- /dev/null
+++ b/frontend/resources/images/icons/filter.svg
@@ -0,0 +1 @@
+
diff --git a/frontend/resources/styles/main/partials/sidebar-layers.scss b/frontend/resources/styles/main/partials/sidebar-layers.scss
index 432f8cfc8..3f8e90146 100644
--- a/frontend/resources/styles/main/partials/sidebar-layers.scss
+++ b/frontend/resources/styles/main/partials/sidebar-layers.scss
@@ -295,3 +295,97 @@ span.element-name {
padding: 1px;
}
}
+
+#layers {
+ .tool-window-bar {
+ display: flex;
+ justify-content: space-between;
+ height: 32px;
+ margin-top: 8px;
+
+ &.search {
+ .search-box {
+ border: 1px solid $color-primary;
+ border-radius: 4px;
+ height: 32px;
+ width: 100%;
+ display: flex;
+ align-items: center;
+ input {
+ border: 0;
+ width: 100%;
+ background-color: $color-gray-50;
+ color: $color-white;
+ font-size: 12px;
+ height: 16px;
+ }
+ span {
+ height: 16px;
+ overflow: hidden;
+ }
+ .filter,
+ .clear {
+ width: 35px;
+ &.active {
+ svg {
+ fill: $color-primary;
+ }
+ }
+ }
+ }
+ }
+
+ svg {
+ width: 16px;
+ height: 16px;
+ margin: 0 2px 0 5px;
+ cursor: pointer;
+ }
+ }
+}
+.active-filters {
+ margin-top: 5px;
+ line-height: 26px;
+ font-size: 11px;
+ margin: 0 0.5rem;
+ span {
+ background-color: $color-primary;
+ color: $color-black;
+ padding: 3px 5px;
+ margin: 0 2px;
+ border-radius: 4px;
+ cursor: pointer;
+ svg {
+ width: 7px;
+ height: 7px;
+ vertical-align: middle;
+ margin-left: 5px;
+ }
+ }
+}
+
+.filters-container {
+ position: absolute;
+ display: flex;
+ flex-direction: column;
+ top: 40px;
+ left: 8px;
+ background-color: $color-white;
+ color: $color-gray-50;
+ border-radius: 4px;
+ span {
+ padding: 10px 20px 10px 10px;
+ border-radius: 4px;
+ svg {
+ width: 16px;
+ height: 16px;
+ margin-right: 10px;
+ vertical-align: middle;
+ fill: $color-gray-30;
+ }
+
+ &:hover {
+ background-color: $color-primary-lighter;
+ }
+ }
+}
diff --git a/frontend/src/app/main/ui/icons.cljs b/frontend/src/app/main/ui/icons.cljs
index d6ff5f0da..944f46aae 100644
--- a/frontend/src/app/main/ui/icons.cljs
+++ b/frontend/src/app/main/ui/icons.cljs
@@ -52,6 +52,7 @@
(def easing-ease-in (icon-xref :easing-ease-in))
(def easing-ease-out (icon-xref :easing-ease-out))
(def easing-ease-in-out (icon-xref :easing-ease-in-out))
+(def exclude (icon-xref :exclude))
(def exit (icon-xref :exit))
(def export (icon-xref :export))
(def eye (icon-xref :eye))
@@ -67,6 +68,7 @@
(def grid-snap (icon-xref :grid-snap))
(def help (icon-xref :help))
(def icon-empty (icon-xref :icon-empty))
+(def icon-filter (icon-xref :filter))
(def icon-list (icon-xref :icon-list))
(def icon-lock (icon-xref :icon-lock))
(def icon-set (icon-xref :icon-set))
diff --git a/frontend/src/app/main/ui/workspace/sidebar/layers.cljs b/frontend/src/app/main/ui/workspace/sidebar/layers.cljs
index b361be875..de27f21ed 100644
--- a/frontend/src/app/main/ui/workspace/sidebar/layers.cljs
+++ b/frontend/src/app/main/ui/workspace/sidebar/layers.cljs
@@ -304,13 +304,59 @@
{::mf/wrap-props false
::mf/wrap [mf/memo #(mf/throttle % 200)]}
[props]
- (let [objects (-> (obj/get props "objects")
+ (let [search (obj/get props "search")
+ filters (obj/get props "filters")
+ filters (if (some #{:shape} filters)
+ (conj filters :rect :circle :path :bool)
+ filters)
+ objects (-> (obj/get props "objects")
(hooks/use-equal-memo))
objects (mf/use-memo
(mf/deps objects)
- #(strip-objects objects))]
+ #(strip-objects objects))
+
+ reparented-objects (d/mapm (fn [_ val]
+ (assoc val :parent-id uuid/zero :shapes nil))
+ objects)
+
+ reparented-shapes (->> reparented-objects
+ keys
+ (filter #(not= uuid/zero %))
+ vec)
+
+ reparented-objects (update reparented-objects uuid/zero assoc :shapes reparented-shapes)
+
+ search-and-filters (mf/use-callback
+ (mf/deps search filters)
+ (fn [[id shape]]
+ (or
+ (= uuid/zero id)
+ (and
+ (str/includes? (str/lower (:name shape)) (str/lower search))
+ (or
+ (empty? filters)
+ (and
+ (some #{:component} filters)
+ (contains? shape :component-id))
+ (let [direct_filters (filter #{:frame :rect :circle :path :bool :image :text} filters)]
+ (some #{(:type shape)} direct_filters))
+ (and
+ (some #{:group} filters)
+ (and (= :group (:type shape))
+ (not (contains? shape :component-id))
+ (or (not (contains? shape :masked-group?)) (false? (:masked-group? shape)))))
+ (and
+ (some #{:mask} filters)
+ (true? (:masked-group? shape))))))))
+
+ objects (if (and (= "" search) (empty? filters))
+ objects
+ (into {} (filter search-and-filters
+ reparented-objects)))]
+
[:& layers-tree {:objects objects}]))
+
;; --- Layers Toolbox
(mf/defc layers-toolbox
@@ -320,7 +366,11 @@
focus (mf/deref refs/workspace-focus-selected)
objects (hooks/with-focus-objects (:objects page) focus)
title (when (= 1 (count focus)) (get-in objects [(first focus) :name]))
-
+ filter-state (mf/use-state {:show-search-box false
+ :show-filters-menu false
+ :search-text ""
+ :active-filters {}})
+
on-scroll
(fn [event]
(let [target (dom/get-target event)
@@ -328,26 +378,92 @@
frames (dom/get-elements-by-class "type-frame")
last-hidden-frame (->> frames
(filter #(< (- (:top (dom/get-bounding-rect %)) target-top) 0))
- last)]
- (doseq [frame frames]
+ last)]
+ (doseq [frame frames]
(dom/remove-class! frame "sticky"))
-
+
(when last-hidden-frame
- (dom/add-class! last-hidden-frame "sticky"))))]
-
+ (dom/add-class! last-hidden-frame "sticky"))))
+ clear-search-text #(swap! filter-state assoc :search-text "")
+ update-search-text (fn [event]
+ (let [value (-> event dom/get-target dom/get-value)]
+ (swap! filter-state assoc :search-text value)))
+ toggle-search (fn []
+ (swap! filter-state assoc :search-text "")
+ (swap! filter-state assoc :active-filters {})
+ (swap! filter-state assoc :show-filters-menu false)
+ (swap! filter-state update :show-search-box not))
+ toggle-filters #(swap! filter-state update :show-filters-menu not)
+
+
+ remove-filter
+ (mf/use-callback
+ (mf/deps @filter-state)
+ (fn [key]
+ (fn [_]
+ (swap! filter-state update :active-filters dissoc key))))
+
+ add-filter
+ (mf/use-callback
+ (mf/deps @filter-state (:show-filters-menu @filter-state))
+ (fn [key value]
+ (fn [_]
+ (swap! filter-state update :active-filters assoc key value)
+ (toggle-filters))))]
+
+
[:div#layers.tool-window
(if (d/not-empty? focus)
[:div.tool-window-bar
[:div.focus-title
[:button.back-button
{:on-click #(st/emit! (dw/toggle-focus-mode))}
- i/arrow-slide ]
+ i/arrow-slide]
[:span (or title (tr "workspace.focus.selection"))]
[:div.focus-mode (tr "workspace.focus.focus-mode")]]]
- [:div.tool-window-bar
- [:span (:name page)]])
+
+ (if (:show-search-box @filter-state)
+ [:*
+ [:div.tool-window-bar.search
+ [:span.search-box
+ [:span.filter {:on-click toggle-filters
+ :class (dom/classnames :active (or
+ (:show-filters-menu @filter-state)
+ (not-empty (:active-filters @filter-state))))}
+ i/icon-filter]
+ [:span
+ [:input {:on-change update-search-text
+ :value (:search-text @filter-state)
+ :auto-focus (:show-search-box @filter-state)
+ :placeholder (tr "workspace.sidebar.layers.search")}]]
+ (when (not (= "" (:search-text @filter-state)))
+ [:span.clear {:on-click clear-search-text} i/exclude])]
+ [:span {:on-click toggle-search} i/cross]
+ ]
+ [:div.active-filters
+ (for [f (:active-filters @filter-state)]
+ [:span {:on-click (remove-filter (key f))}
+ (tr (val f)) i/cross])
+ ]
+
+
+ (when (:show-filters-menu @filter-state)
+ [:div.filters-container
+ [:span{:on-click (add-filter :frame "workspace.sidebar.layers.frames")} i/artboard (tr "workspace.sidebar.layers.frames")]
+ [:span{:on-click (add-filter :group "workspace.sidebar.layers.groups")} i/folder (tr "workspace.sidebar.layers.groups")]
+ [:span{:on-click (add-filter :mask "workspace.sidebar.layers.masks")} i/mask (tr "workspace.sidebar.layers.masks")]
+ [:span{:on-click (add-filter :component "workspace.sidebar.layers.components")} i/component (tr "workspace.sidebar.layers.components")]
+ [:span{:on-click (add-filter :text "workspace.sidebar.layers.texts")} i/text (tr "workspace.sidebar.layers.texts")]
+ [:span{:on-click (add-filter :image "workspace.sidebar.layers.images")} i/image (tr "workspace.sidebar.layers.images")]
+ [:span{:on-click (add-filter :shape "workspace.sidebar.layers.shapes")} i/curve (tr "workspace.sidebar.layers.shapes")]])]
+
+ [:div.tool-window-bar
+ [:span (:name page)]
+ [:span {:on-click toggle-search} i/search]]))
[:div.tool-window-content {:on-scroll on-scroll}
[:& layers-tree-wrapper {:key (:id page)
- :objects objects}]]]))
+ :objects objects
+ :search (:search-text @filter-state)
+ :filters (keys (:active-filters @filter-state))}]]]))
diff --git a/frontend/translations/en.po b/frontend/translations/en.po
index 180e7def9..7e7dbb6fc 100644
--- a/frontend/translations/en.po
+++ b/frontend/translations/en.po
@@ -3377,6 +3377,30 @@ msgstr "History (%s)"
msgid "workspace.sidebar.layers"
msgstr "Layers"
+msgid "workspace.sidebar.layers.search"
+msgstr "Search layers"
+
+msgid "workspace.sidebar.layers.frames"
+msgstr "Artboards"
+
+msgid "workspace.sidebar.layers.groups"
+msgstr "Groups"
+
+msgid "workspace.sidebar.layers.masks"
+msgstr "Masks"
+
+msgid "workspace.sidebar.layers.components"
+msgstr "Components"
+
+msgid "workspace.sidebar.layers.texts"
+msgstr "Texts"
+
+msgid "workspace.sidebar.layers.images"
+msgstr "Images"
+
+msgid "workspace.sidebar.layers.shapes"
+msgstr "Shapes"
+
#: src/app/main/ui/workspace/sidebar/options/menus/svg_attrs.cljs, src/app/main/ui/handoff/attributes/svg.cljs
msgid "workspace.sidebar.options.svg-attrs.title"
msgstr "Imported SVG Attributes"
diff --git a/frontend/translations/es.po b/frontend/translations/es.po
index d0794d6e5..36ae34aa4 100644
--- a/frontend/translations/es.po
+++ b/frontend/translations/es.po
@@ -3391,6 +3391,30 @@ msgstr "Historial (%s)"
msgid "workspace.sidebar.layers"
msgstr "Capas"
+msgid "workspace.sidebar.layers.search"
+msgstr "Buscar capas"
+
+msgid "workspace.sidebar.layers.frames"
+msgstr "Paneles"
+
+msgid "workspace.sidebar.layers.groups"
+msgstr "Grupos"
+
+msgid "workspace.sidebar.layers.masks"
+msgstr "Máscaras"
+
+msgid "workspace.sidebar.layers.components"
+msgstr "Componentes"
+
+msgid "workspace.sidebar.layers.texts"
+msgstr "Textos"
+
+msgid "workspace.sidebar.layers.images"
+msgstr "Imágenes"
+
+msgid "workspace.sidebar.layers.shapes"
+msgstr "Formas"
+
#: src/app/main/ui/workspace/sidebar/options/menus/svg_attrs.cljs, src/app/main/ui/handoff/attributes/svg.cljs
msgid "workspace.sidebar.options.svg-attrs.title"
msgstr "Atributos del SVG Importado"