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"