mirror of
https://github.com/penpot/penpot.git
synced 2025-05-31 17:06:10 +02:00
✨ Search and filter layers
This commit is contained in:
parent
81adcd03fb
commit
3bae4839bd
7 changed files with 274 additions and 12 deletions
1
frontend/resources/images/icons/exclude.svg
Normal file
1
frontend/resources/images/icons/exclude.svg
Normal file
|
@ -0,0 +1 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="482 -308 500 500"><path d="M555-235a250 250 0 1 1 354 354 250 250 0 0 1-354-354zM732-95l-88-88-36 35 89 89-89 88 36 36 88-89 88 89 36-36-89-88 89-89-36-35z"/></svg>
|
After Width: | Height: | Size: 214 B |
1
frontend/resources/images/icons/filter.svg
Normal file
1
frontend/resources/images/icons/filter.svg
Normal file
|
@ -0,0 +1 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="299 -306 500 500"><path d="M799-306v89H299v-89zM696 194H402v-89h294zm52-206H351v-89h397z"/></svg>
|
After Width: | Height: | Size: 147 B |
|
@ -295,3 +295,97 @@ span.element-name {
|
||||||
padding: 1px;
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -52,6 +52,7 @@
|
||||||
(def easing-ease-in (icon-xref :easing-ease-in))
|
(def easing-ease-in (icon-xref :easing-ease-in))
|
||||||
(def easing-ease-out (icon-xref :easing-ease-out))
|
(def easing-ease-out (icon-xref :easing-ease-out))
|
||||||
(def easing-ease-in-out (icon-xref :easing-ease-in-out))
|
(def easing-ease-in-out (icon-xref :easing-ease-in-out))
|
||||||
|
(def exclude (icon-xref :exclude))
|
||||||
(def exit (icon-xref :exit))
|
(def exit (icon-xref :exit))
|
||||||
(def export (icon-xref :export))
|
(def export (icon-xref :export))
|
||||||
(def eye (icon-xref :eye))
|
(def eye (icon-xref :eye))
|
||||||
|
@ -67,6 +68,7 @@
|
||||||
(def grid-snap (icon-xref :grid-snap))
|
(def grid-snap (icon-xref :grid-snap))
|
||||||
(def help (icon-xref :help))
|
(def help (icon-xref :help))
|
||||||
(def icon-empty (icon-xref :icon-empty))
|
(def icon-empty (icon-xref :icon-empty))
|
||||||
|
(def icon-filter (icon-xref :filter))
|
||||||
(def icon-list (icon-xref :icon-list))
|
(def icon-list (icon-xref :icon-list))
|
||||||
(def icon-lock (icon-xref :icon-lock))
|
(def icon-lock (icon-xref :icon-lock))
|
||||||
(def icon-set (icon-xref :icon-set))
|
(def icon-set (icon-xref :icon-set))
|
||||||
|
|
|
@ -304,13 +304,59 @@
|
||||||
{::mf/wrap-props false
|
{::mf/wrap-props false
|
||||||
::mf/wrap [mf/memo #(mf/throttle % 200)]}
|
::mf/wrap [mf/memo #(mf/throttle % 200)]}
|
||||||
[props]
|
[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))
|
(hooks/use-equal-memo))
|
||||||
objects (mf/use-memo
|
objects (mf/use-memo
|
||||||
(mf/deps objects)
|
(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-tree {:objects objects}]))
|
||||||
|
|
||||||
|
|
||||||
;; --- Layers Toolbox
|
;; --- Layers Toolbox
|
||||||
|
|
||||||
(mf/defc layers-toolbox
|
(mf/defc layers-toolbox
|
||||||
|
@ -320,6 +366,10 @@
|
||||||
focus (mf/deref refs/workspace-focus-selected)
|
focus (mf/deref refs/workspace-focus-selected)
|
||||||
objects (hooks/with-focus-objects (:objects page) focus)
|
objects (hooks/with-focus-objects (:objects page) focus)
|
||||||
title (when (= 1 (count focus)) (get-in objects [(first focus) :name]))
|
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
|
on-scroll
|
||||||
(fn [event]
|
(fn [event]
|
||||||
|
@ -333,7 +383,34 @@
|
||||||
(dom/remove-class! frame "sticky"))
|
(dom/remove-class! frame "sticky"))
|
||||||
|
|
||||||
(when last-hidden-frame
|
(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
|
[:div#layers.tool-window
|
||||||
(if (d/not-empty? focus)
|
(if (d/not-empty? focus)
|
||||||
|
@ -345,9 +422,48 @@
|
||||||
[:span (or title (tr "workspace.focus.selection"))]
|
[:span (or title (tr "workspace.focus.selection"))]
|
||||||
[:div.focus-mode (tr "workspace.focus.focus-mode")]]]
|
[:div.focus-mode (tr "workspace.focus.focus-mode")]]]
|
||||||
|
|
||||||
|
|
||||||
|
(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
|
[:div.tool-window-bar
|
||||||
[:span (:name page)]])
|
[:span (:name page)]
|
||||||
|
[:span {:on-click toggle-search} i/search]]))
|
||||||
|
|
||||||
[:div.tool-window-content {:on-scroll on-scroll}
|
[:div.tool-window-content {:on-scroll on-scroll}
|
||||||
[:& layers-tree-wrapper {:key (:id page)
|
[:& layers-tree-wrapper {:key (:id page)
|
||||||
:objects objects}]]]))
|
:objects objects
|
||||||
|
:search (:search-text @filter-state)
|
||||||
|
:filters (keys (:active-filters @filter-state))}]]]))
|
||||||
|
|
|
@ -3377,6 +3377,30 @@ msgstr "History (%s)"
|
||||||
msgid "workspace.sidebar.layers"
|
msgid "workspace.sidebar.layers"
|
||||||
msgstr "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
|
#: 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"
|
msgid "workspace.sidebar.options.svg-attrs.title"
|
||||||
msgstr "Imported SVG Attributes"
|
msgstr "Imported SVG Attributes"
|
||||||
|
|
|
@ -3391,6 +3391,30 @@ msgstr "Historial (%s)"
|
||||||
msgid "workspace.sidebar.layers"
|
msgid "workspace.sidebar.layers"
|
||||||
msgstr "Capas"
|
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
|
#: 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"
|
msgid "workspace.sidebar.options.svg-attrs.title"
|
||||||
msgstr "Atributos del SVG Importado"
|
msgstr "Atributos del SVG Importado"
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue