diff --git a/lib/public/app.css b/lib/public/app.css index bb67bc66bd..0e88f93174 100644 --- a/lib/public/app.css +++ b/lib/public/app.css @@ -718,6 +718,68 @@ label { opacity: 0.5; } +.active-filters-indicator { + position: relative; + z-index: 10; + background-color: white; + border-radius: .25rem; + padding: var(--space-xs) var(--space-s) var(--space-xs) var(--space-s); + margin: 0 0 0 var(--space-s); +} + +.active-filters-indicator:has(+ .clear-filter-icon-container) { + border-right: 0; + border-radius: .25rem 0 0 .25rem +} + +.clear-filter-icon-container { + background-color: white; + border-radius: 0 .25rem .25rem 0; + font-weight: 700; + cursor: pointer; +} + +.clear-filter-icon { + padding: var(--space-xs); + background-color: white; + color: var(--color-danger); + position: relative; + border-radius: 0 .25rem .25rem 0; + z-index: 10; +} + +.clear-filter-icon:hover { + background-color: var(--color-danger); + color: white; +} + +.inactive { + opacity: 0.5; + pointer-events: none; +} + +.pulse-green { + --pulse-color: 102, 255, 7; + animation: pulse 2s infinite; +} + +.pulse-red { + --pulse-color: 206, 42, 42; + animation: pulse 2s infinite; +} + +@keyframes pulse { + 0% { + box-shadow: 0 0 0px rgba(var(--pulse-color), 0.6); + } + 50% { + box-shadow: 0 0 10px rgba(var(--pulse-color), 0.9); + } + 100% { + box-shadow: 0 0 0px rgba(var(--pulse-color), 0.6); + } +} + /** * Breakpoints : * small : x < 600 (default styles) diff --git a/lib/public/components/Filters/common/filtersPanelPopover.js b/lib/public/components/Filters/common/filtersPanelPopover.js index 171dda6e8d..648427db34 100644 --- a/lib/public/components/Filters/common/filtersPanelPopover.js +++ b/lib/public/components/Filters/common/filtersPanelPopover.js @@ -42,18 +42,21 @@ const filtersToggleTrigger = () => h('button#openFilterToggle.btn.btn.btn-primar * Button component that resets all filters upon click * * @param {FilteringModel|OverviewPageModel} filteringModel the FilteringModel + * @param {bool} [isIcon=false] if the component is rendered as a regular button with text or as a component with an 'X' icon * @returns {Component} the reset button component */ -const resetFiltersButton = (filteringModel) => h( - 'button#reset-filters.btn.btn-danger', - { +const resetFiltersButton = (filteringModel, isIcon = false) => { + const attributes = { disabled: !filteringModel.isAnyFilterActive(), onclick: () => filteringModel.resetFiltering ? filteringModel.resetFiltering(true, true) : filteringModel.reset(true, true), - }, - 'Reset all filters', -); + }; + + return isIcon + ? h('.clear-filter-icon-container.btn-group-item.last-item.pulse-red', attributes, h('.clear-filter-icon.b1.b-danger', 'X')) + : h('button#reset-filters.btn.btn-danger', attributes, 'Reset all filters'); +}; /** * Create main header of the filters panel @@ -168,6 +171,31 @@ const pasteButtonOption = (model) => { }, 'Paste filters'); }; +/** + * A indicates if any filters are currently active on the page + * + * @param {FilteringModel} model the filtering model + * @returns {Component} the active filters indicator + */ +const activeFilterIndicator = (model) => { + // Sometimes, the overview model is passed to filterPanelPopover instead of the filteringmodel (e.g. envirionments) + const { filteringModel = model } = model; + + const hasActiveFilters = filteringModel.isAnyFilterActive(); + const innerText = `Filters ${hasActiveFilters ? 'Active' : 'Inactive'}`; + + let indicator = '.active-filters-indicator.b1'; + indicator += hasActiveFilters ? '.b-success.success.pulse-green' : '.inactive'; + + const children = [h(indicator, innerText)]; + + if (hasActiveFilters) { + children.push(resetFiltersButton(filteringModel, true)); + } + + return h('.flex-row.items-center', children); +}; + /** * Return component composed of the filter popover button and a dropdown trigger * @@ -195,6 +223,7 @@ export const filtersPanelPopover = (filteringModel, filtersConfiguration, config ], ), ), + activeFilterIndicator(filteringModel), ], ); }; diff --git a/lib/public/views/Runs/Overview/RunsWithQcModel.js b/lib/public/views/Runs/Overview/RunsWithQcModel.js index cc1687cf30..e9ec4f36c5 100644 --- a/lib/public/views/Runs/Overview/RunsWithQcModel.js +++ b/lib/public/views/Runs/Overview/RunsWithQcModel.js @@ -78,7 +78,7 @@ export class RunsWithQcModel extends RunsOverviewModel { this._observablesQcFlagsSummaryDependsOn$ = null; // This filter instance will be added as a sub-filter for a MultiCompositionFilter and a GaqFilter later. - this._mcReproducibleAsNotBad = new ToggleFilterModel(); + this._mcReproducibleAsNotBad = new ToggleFilterModel(false, true); this._runDetectorsSelectionModel = new RunDetectorsSelectionModel(); this._runDetectorsSelectionModel.bubbleTo(this);