mirror of
https://github.com/penpot/penpot.git
synced 2025-04-10 22:11:23 -05: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;
|
||||
}
|
||||
}
|
||||
|
||||
#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-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))
|
||||
|
|
|
@ -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))}]]]))
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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"
|
||||
|
|
Loading…
Add table
Reference in a new issue