From 008b652c620450a6d80cce56ddc237d5bcc59f6d Mon Sep 17 00:00:00 2001 From: Ahmed Awan <qe66653@umbc.edu> Date: Wed, 20 Mar 2024 15:34:54 -0500 Subject: [PATCH] add a `compact` version of `FilterMenu`; no collapse on search It groups `is` type boolean filters on one line, and in checkboxes. Adding filters applies them immediately, and there is no search button. --- client/src/components/Common/FilterMenu.vue | 49 +++++++++++++++---- .../components/Common/FilterMenuBoolean.vue | 15 ++++-- client/src/components/Grid/GridList.vue | 4 +- .../components/Workflow/WorkflowFilters.js | 12 ++--- .../src/components/Workflow/WorkflowList.vue | 2 +- 5 files changed, 59 insertions(+), 23 deletions(-) diff --git a/client/src/components/Common/FilterMenu.vue b/client/src/components/Common/FilterMenu.vue index cffb6aae40e4..f33bdb70960c 100644 --- a/client/src/components/Common/FilterMenu.vue +++ b/client/src/components/Common/FilterMenu.vue @@ -54,8 +54,8 @@ interface Props { searchError?: BackendFilterError; /** Whether the advanced menu is currently expanded */ showAdvanced?: boolean; - /** Whether to use a popover for the menu options */ - isPopover?: boolean; + /** What view to use for the menu */ + view?: "dropdown" | "popover" | "compact"; } const props = withDefaults(defineProps<Props>(), { @@ -66,7 +66,7 @@ const props = withDefaults(defineProps<Props>(), { menuType: "linked", showAdvanced: false, searchError: undefined, - isPopover: false, + view: "dropdown", }); const emit = defineEmits<{ @@ -135,6 +135,11 @@ function getValidFilter(filter: string): ValidFilter<any> { */ function onOption(filter: string, value: any) { filters.value[filter] = value; + + // for the compact view, we want to immediately search + if (props.view === "compact") { + onSearch(); + } } function onPopoverShown() { @@ -152,7 +157,11 @@ function onSearch() { emit("on-search", filters.value, newFilterText, newBackendFilter); } else { updateFilterText(newFilterText); - onToggle(); + + // for the compact view, we do not want to close the advanced menu + if (props.view !== "compact") { + onToggle(); + } } } @@ -194,8 +203,10 @@ function updateFilterText(newFilterText: string) { </BButton> <component - :is="!props.isPopover ? 'div' : BPopover" - v-if="(props.isPopover && toggleMenuButton) || props.menuType == 'standalone' || props.showAdvanced" + :is="props.view !== 'popover' ? 'div' : BPopover" + v-if=" + (props.view === 'popover' && toggleMenuButton) || props.menuType == 'standalone' || props.showAdvanced + " class="mt-2" :show.sync="localAdvancedToggle" :target="toggleMenuButton" @@ -206,11 +217,13 @@ function updateFilterText(newFilterText: string) { <span ref="advancedMenu"> <div v-for="filter in Object.keys(validFilters)" :key="filter"> <span v-if="validFilters[filter]?.menuItem"> + <!-- Boolean filters go in another section in compact view --> <FilterMenuBoolean - v-if="validFilters[filter]?.type == Boolean" + v-if="props.view !== 'compact' && validFilters[filter]?.type == Boolean" :name="filter" :filter="getValidFilter(filter)" :filters="filters" + :view="props.view" @change="onOption" @on-enter="onSearch" @on-esc="onToggle" /> @@ -246,7 +259,7 @@ function updateFilterText(newFilterText: string) { :identifier="identifier" @change="onOption" /> <FilterMenuInput - v-else + v-else-if="validFilters[filter]?.type !== Boolean" :name="filter" :filter="getValidFilter(filter)" :filters="filters" @@ -259,9 +272,26 @@ function updateFilterText(newFilterText: string) { </div> </span> + <!-- Compact view: Boolean filters go side by side --> + <div v-if="props.view === 'compact'" class="d-flex"> + <span v-for="filter in Object.keys(validFilters)" :key="filter"> + <FilterMenuBoolean + v-if="validFilters[filter]?.menuItem && validFilters[filter]?.type == Boolean" + class="mr-2 mt-1" + :name="filter" + :filter="getValidFilter(filter)" + :filters="filters" + :view="props.view" + @change="onOption" + @on-enter="onSearch" + @on-esc="onToggle" /> + </span> + </div> + <!-- Perform search or cancel out (or open help modal for whole Menu if exists) --> - <div class="mb-3 mt-2"> + <div class="mt-2"> <BButton + v-if="props.view !== 'compact'" :id="`${identifier}-advanced-filter-submit`" class="mr-1" size="sm" @@ -282,6 +312,7 @@ function updateFilterText(newFilterText: string) { <slot name="menu-help-text"></slot> </BModal> </div> + <hr v-if="props.showAdvanced" class="w-100" /> </component> </div> </template> diff --git a/client/src/components/Common/FilterMenuBoolean.vue b/client/src/components/Common/FilterMenuBoolean.vue index eb936d14663b..28f55941f0a6 100644 --- a/client/src/components/Common/FilterMenuBoolean.vue +++ b/client/src/components/Common/FilterMenuBoolean.vue @@ -1,5 +1,5 @@ <script setup lang="ts"> -import { BFormGroup, BFormRadioGroup } from "bootstrap-vue"; +import { BFormCheckbox, BFormGroup, BFormRadioGroup } from "bootstrap-vue"; import { computed } from "vue"; import type { ValidFilter } from "@/utils/filtering"; @@ -12,11 +12,15 @@ interface Props { filters: { [k: string]: FilterType; }; + view?: "dropdown" | "popover" | "compact"; } -const props = defineProps<Props>(); +const props = withDefaults(defineProps<Props>(), { + view: "dropdown", +}); const boolType = computed(() => props.filter.boolType || "default"); +const isCheckbox = computed(() => boolType.value === "is" && props.view === "compact"); const options = boolType.value == "default" @@ -50,10 +54,11 @@ const value = computed({ <template> <!-- eslint-disable-next-line vuejs-accessibility/no-static-element-interactions --> - <div @keyup.enter="emit('on-enter')" @keyup.esc="emit('on-esc')"> - <small>{{ props.filter.placeholder }}:</small> + <div :class="{ 'd-flex': isCheckbox }" @keyup.enter="emit('on-enter')" @keyup.esc="emit('on-esc')"> + <small :class="{ 'mr-1': isCheckbox }">{{ props.filter.placeholder }}:</small> - <BFormGroup class="m-0"> + <BFormCheckbox v-if="isCheckbox" v-model="value" :data-description="`filter ${props.name}`" /> + <BFormGroup v-else class="m-0"> <BFormRadioGroup v-model="value" :options="options" diff --git a/client/src/components/Grid/GridList.vue b/client/src/components/Grid/GridList.vue index 8c73ba0e5d44..5669e46481c3 100644 --- a/client/src/components/Grid/GridList.vue +++ b/client/src/components/Grid/GridList.vue @@ -292,8 +292,8 @@ watch(operationMessage, () => { :filter-class="filterClass" :filter-text.sync="filterText" :loading="initDataLoading || resultsLoading" - :show-advanced.sync="showAdvanced" /> - <hr v-if="showAdvanced" /> + :show-advanced.sync="showAdvanced" + view="compact" /> </div> <LoadingSpan v-if="initDataLoading" /> <span v-else-if="!isAvailable || hasInvalidFilters" variant="info" show> diff --git a/client/src/components/Workflow/WorkflowFilters.js b/client/src/components/Workflow/WorkflowFilters.js index 1c184ff0d4c8..1280b6e450a5 100644 --- a/client/src/components/Workflow/WorkflowFilters.js +++ b/client/src/components/Workflow/WorkflowFilters.js @@ -89,28 +89,28 @@ export function WorkflowFilters(activeList = "my") { { ...commonFilters, published: { - placeholder: "Filter on published workflows", + placeholder: "Published", type: Boolean, boolType: "is", handler: equals("published", "published", toBool), menuItem: true, }, importable: { - placeholder: "Filter on importable workflows", + placeholder: "Importable", type: Boolean, boolType: "is", handler: equals("importable", "importable", toBool), menuItem: true, }, shared_with_me: { - placeholder: "Filter on workflows shared with me", + placeholder: "Shared with me", type: Boolean, boolType: "is", handler: equals("shared_with_me", "shared_with_me", toBool), menuItem: true, }, deleted: { - placeholder: "Filter on deleted workflows", + placeholder: "Deleted", type: Boolean, boolType: "is", handler: equals("deleted", "deleted", toBool), @@ -133,7 +133,7 @@ export function WorkflowFilters(activeList = "my") { }, u: { handler: contains("u"), menuItem: false }, published: { - placeholder: "Filter on published workflows", + placeholder: "Published", type: Boolean, boolType: "is", handler: equals("published", "published", toBool), @@ -156,7 +156,7 @@ export function WorkflowFilters(activeList = "my") { }, u: { handler: contains("u"), menuItem: false }, shared_with_me: { - placeholder: "Filter on workflows shared with me", + placeholder: "Shared with me", type: Boolean, boolType: "is", handler: equals("shared_with_me", "shared_with_me", toBool), diff --git a/client/src/components/Workflow/WorkflowList.vue b/client/src/components/Workflow/WorkflowList.vue index 8fb59edb8ea1..8a0f770c88a2 100644 --- a/client/src/components/Workflow/WorkflowList.vue +++ b/client/src/components/Workflow/WorkflowList.vue @@ -218,7 +218,7 @@ onMounted(() => { :filter-text.sync="filterText" :loading="loading || overlay" has-help - is-popover + view="compact" :placeholder="searchPlaceHolder" :show-advanced.sync="showAdvanced"> <template v-slot:menu-help-text>