Skip to content

Commit

Permalink
Merge pull request #18034 from ahmedhamidawan/add_section_filter_tool…
Browse files Browse the repository at this point in the history
…_search

Add a button that filters out a tool section in the tool panel
  • Loading branch information
dannon authored May 3, 2024
2 parents 72e00b2 + b048f15 commit 509961f
Show file tree
Hide file tree
Showing 8 changed files with 153 additions and 108 deletions.
4 changes: 2 additions & 2 deletions client/src/components/History/HistoryScrollList.vue
Original file line number Diff line number Diff line change
Expand Up @@ -220,8 +220,8 @@ async function loadMore(noScroll = false) {
<template>
<div :class="isMultiviewPanel ? 'unified-panel' : 'flex-column-overflow'">
<div class="unified-panel-controls">
<BBadge v-if="props.filter && !validFilter" class="alert-danger w-100 mb-2">
Search string too short!
<BBadge v-if="props.filter && !validFilter" class="alert-warning w-100 mb-2">
Search term is too short
</BBadge>
<BAlert v-else-if="!busy && hasNoResults" class="mb-2" variant="danger" show>No histories found.</BAlert>
</div>
Expand Down
35 changes: 15 additions & 20 deletions client/src/components/Panels/Common/ToolPanelLabel.vue
Original file line number Diff line number Diff line change
@@ -1,29 +1,24 @@
<script setup lang="ts">
import { computed } from "vue";
import type { ToolSectionLabel } from "@/stores/toolStore";
import ToolPanelLinks from "./ToolPanelLinks.vue";
const props = defineProps<{
definition: ToolSectionLabel;
}>();
const description = computed(() => props.definition.description || undefined);
</script>

<template>
<div v-b-tooltip.topright.hover.noninteractive class="tool-panel-label" tabindex="0" :title="description">
{{ definition.text }}
<ToolPanelLinks :links="definition.links" />
<ToolPanelLinks :links="definition.links || undefined" />
</div>
</template>

<script>
import ToolPanelLinks from "./ToolPanelLinks";
export default {
components: { ToolPanelLinks },
props: {
definition: {
type: Object,
required: true,
},
},
computed: {
description() {
return this.definition.description;
},
},
};
</script>

<style scoped lang="scss">
.tool-panel-label {
&:deep(.tool-panel-links) {
Expand Down
2 changes: 1 addition & 1 deletion client/src/components/Panels/Common/ToolSearch.vue
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,7 @@ watch(
function checkQuery(q: string) {
emit("onQuery", q);
if (q && q.length >= MIN_QUERY_LENGTH) {
if (q.trim() && q.trim().length >= MIN_QUERY_LENGTH) {
if (FAVORITES.includes(q)) {
post({ type: "favoriteTools" });
} else {
Expand Down
130 changes: 71 additions & 59 deletions client/src/components/Panels/Common/ToolSection.vue
Original file line number Diff line number Diff line change
@@ -1,73 +1,65 @@
<script setup lang="ts">
import { library } from "@fortawesome/fontawesome-svg-core";
import { faFilter } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/vue-fontawesome";
import { useEventBus } from "@vueuse/core";
import { computed, onMounted, onUnmounted, ref, watch } from "vue";
import { useConfig } from "@/composables/config";
import { type ToolSectionLabel, useToolStore } from "@/stores/toolStore";
import { type Tool as ToolType, type ToolSection, type ToolSectionLabel, useToolStore } from "@/stores/toolStore";
import ariaAlert from "@/utils/ariaAlert";
import Tool from "./Tool.vue";
import ToolPanelLabel from "./ToolPanelLabel.vue";
import ToolPanelLinks from "./ToolPanelLinks.vue";
library.add(faFilter);
const emit = defineEmits<{
(e: "onClick", tool: any, evt: Event): void;
(e: "onFilter", filter: string): void;
(e: "onOperation", tool: any, evt: Event): void;
}>();
const eventBus = useEventBus<string>("open-tool-section");
const props = defineProps({
category: {
type: Object,
required: true,
},
queryFilter: {
type: String,
default: "",
},
disableFilter: {
type: Boolean,
},
hideName: {
type: Boolean,
},
operationTitle: {
type: String,
default: "",
},
operationIcon: {
type: String,
default: "",
},
toolKey: {
type: String,
default: "",
},
sectionName: {
type: String,
default: "default",
},
expanded: {
type: Boolean,
default: false,
},
sortItems: {
type: Boolean,
default: true,
},
interface Props {
category: ToolSection | ToolType | ToolSectionLabel;
queryFilter?: string;
disableFilter?: boolean;
hideName?: boolean;
operationTitle?: string;
operationIcon?: string;
toolKey?: string;
sectionName?: string;
expanded?: boolean;
sortItems?: boolean;
hasFilterButton?: boolean;
}
const props = withDefaults(defineProps<Props>(), {
queryFilter: "",
disableFilter: false,
hideName: false,
operationTitle: "",
operationIcon: "",
toolKey: "",
sectionName: "default",
expanded: false,
sortItems: true,
hasFilterButton: false,
});
const { config, isConfigLoaded } = useConfig();
const toolStore = useToolStore();
const elems = computed(() => {
if (props.category.elems !== undefined && props.category.elems.length > 0) {
return props.category.elems;
if (toolSection.value.elems !== undefined && toolSection.value.elems.length > 0) {
return toolSection.value.elems;
}
if (props.category.tools !== undefined && props.category.tools.length > 0) {
return props.category.tools.map((toolId: string) => {
const tool = toolStore.getToolForId(toolId);
if (toolSection.value.tools !== undefined && toolSection.value.tools.length > 0) {
return toolSection.value.tools.map((toolId) => {
const tool = toolStore.getToolForId(toolId as string);
if (!tool && typeof toolId !== "string") {
return toolId as ToolSectionLabel;
} else {
Expand All @@ -78,11 +70,14 @@ const elems = computed(() => {
return [];
});
const name = computed(() => props.category.title || props.category.name);
const isSection = computed(() => props.category.tools !== undefined || props.category.elems !== undefined);
const toolSection = computed(() => props.category as ToolSection);
const toolSectionLabel = computed(() => props.category as ToolSectionLabel);
const name = computed(() => toolSection.value.title || toolSection.value.name);
const isSection = computed(() => toolSection.value.tools !== undefined || toolSection.value.elems !== undefined);
const hasElements = computed(() => elems.value.length > 0);
const title = computed(() => props.category.description);
const links = computed(() => props.category.links || {});
const title = computed(() => props.category.description || undefined);
const links = computed(() => toolSection.value.links || {});
const opened = ref(props.expanded || checkFilter());
Expand All @@ -96,12 +91,12 @@ const sortedElements = computed(() => {
isConfigLoaded.value &&
config.value.toolbox_auto_sort === true &&
props.sortItems === true &&
!elems.value.some((el: ToolSectionLabel) => el.text !== undefined && el.text !== "")
!elems.value.some((el) => (el as ToolSectionLabel).text !== undefined && (el as ToolSectionLabel).text !== "")
) {
const elements = [...elems.value];
const sorted = elements.sort((a, b) => {
const aNameLower = a.name.toLowerCase();
const bNameLower = b.name.toLowerCase();
const aNameLower = (a as ToolSection).name.toLowerCase();
const bNameLower = (b as ToolSection).name.toLowerCase();
if (aNameLower > bNameLower) {
return 1;
} else if (aNameLower < bNameLower) {
Expand Down Expand Up @@ -166,18 +161,31 @@ function toggleMenu(nextState = !opened.value) {
v-b-tooltip.topright.hover.noninteractive
:class="['toolSectionTitle', `tool-menu-section-${sectionName}`]"
:title="title">
<a class="title-link" href="javascript:void(0)" @click="toggleMenu()">
<span class="name">
{{ name }}
</span>
<ToolPanelLinks :links="links" />
<a
class="title-link d-flex justify-content-between align-items-center"
href="javascript:void(0)"
@click="toggleMenu()">
<div>
<span class="name">
{{ name }}
</span>
<ToolPanelLinks :links="links" />
</div>
<button
v-if="isSection && props.hasFilterButton"
v-b-tooltip.hover.noninteractive.bottom
title="Show full section"
class="inline-icon-button"
@click.stop="emit('onFilter', `section:${toolSection.name}`)">
<FontAwesomeIcon :icon="faFilter" />
</button>
</a>
</div>
<transition name="slide">
<div v-if="opened" data-description="opened tool panel section">
<template v-for="[key, el] in sortedElements">
<ToolPanelLabel
v-if="category.text || el.model_class === 'ToolSectionLabel'"
v-if="toolSectionLabel.text || el.model_class === 'ToolSectionLabel'"
:key="key"
:definition="el" />
<Tool
Expand All @@ -196,7 +204,7 @@ function toggleMenu(nextState = !opened.value) {
</transition>
</div>
<div v-else>
<ToolPanelLabel v-if="category.text" :definition="category" />
<ToolPanelLabel v-if="toolSectionLabel.text" :definition="toolSectionLabel" />
<Tool
v-else
:tool="category"
Expand All @@ -211,6 +219,10 @@ function toggleMenu(nextState = !opened.value) {
<style lang="scss" scoped>
@import "scss/theme/blue.scss";
.inline-icon-button {
font-size: 75%;
padding: 0em 0.5em;
}
.tool-panel-label {
background: darken($panel-bg-color, 5%);
border-left: 0.25rem solid darken($panel-bg-color, 25%);
Expand Down
35 changes: 21 additions & 14 deletions client/src/components/Panels/ToolBox.vue
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,6 @@ const queryPending = ref(false);
const showSections = ref(props.workflow);
const results: Ref<string[]> = ref([]);
const resultPanel: Ref<Record<string, Tool | ToolSectionType> | null> = ref(null);
const buttonText = ref("");
const buttonIcon = ref("");
const closestTerm: Ref<string | null> = ref(null);
const toolStore = useToolStore();
Expand All @@ -66,15 +64,15 @@ const propShowAdvanced = computed({
});
const query = computed({
get: () => {
return props.panelQuery;
return props.panelQuery.trim();
},
set: (q: string) => {
queryPending.value = true;
emit("update:panel-query", q);
},
});
const { currentPanel } = storeToRefs(toolStore);
const { currentPanel, currentPanelView } = storeToRefs(toolStore);
const hasResults = computed(() => results.value.length > 0);
const queryTooShort = computed(() => query.value && query.value.length < 3);
const queryFinished = computed(() => query.value && queryPending.value != true);
Expand Down Expand Up @@ -172,6 +170,9 @@ const workflowSection = computed(() => {
}
});
const buttonIcon = computed(() => (showSections.value ? faEyeSlash : faEye));
const buttonText = computed(() => (showSections.value ? localize("Hide Sections") : localize("Show Sections")));
function onInsertModule(module: Record<string, any>, event: Event) {
event.preventDefault();
emit("onInsertModule", module.name, module.title);
Expand Down Expand Up @@ -220,18 +221,22 @@ function onResults(
}
closestTerm.value = closestMatch;
queryFilter.value = hasResults.value ? query.value : null;
setButtonText();
queryPending.value = false;
}
function onToggle() {
showSections.value = !showSections.value;
setButtonText();
function onSectionFilter(filter: string) {
if (query.value !== filter) {
query.value = filter;
if (!showSections.value) {
onToggle();
}
} else {
query.value = "";
}
}
function setButtonText() {
buttonText.value = showSections.value ? localize("Hide Sections") : localize("Show Sections");
buttonIcon.value = showSections.value ? "fa-eye-slash" : "fa-eye";
function onToggle() {
showSections.value = !showSections.value;
}
</script>

Expand All @@ -257,10 +262,10 @@ function setButtonText() {
</b-button>
</div>
<div v-else-if="queryTooShort" class="pb-2">
<b-badge class="alert-danger w-100">Search string too short!</b-badge>
<b-badge class="alert-info w-100">Search term is too short</b-badge>
</div>
<div v-else-if="queryFinished && !hasResults" class="pb-2">
<b-badge class="alert-danger w-100">No results found!</b-badge>
<b-badge class="alert-warning w-100">No results found</b-badge>
</div>
<div v-if="closestTerm" class="pb-2">
<b-badge class="alert-danger w-100">
Expand Down Expand Up @@ -299,7 +304,9 @@ function setButtonText() {
v-if="panel"
:category="panel || {}"
:query-filter="queryFilter || undefined"
@onClick="onToolClick" />
:has-filter-button="hasResults && currentPanelView === 'default'"
@onClick="onToolClick"
@onFilter="onSectionFilter" />
</div>
</div>
<ToolSection
Expand Down
8 changes: 8 additions & 0 deletions client/src/components/Panels/utilities.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,14 @@ describe("test helpers in tool searching utilities", () => {
tools: Object.values(tempToolsList.tools),
panel: tempToolPanel.default,
},
{
// section is searchable if provided "section:"
q: "section:Lift-Over",
expectedResults: ["liftOver1"],
keys: { description: 1, name: 2 },
tools: toolsList,
panel: toolsListInPanel,
},
// if at least couple words match, return results
{
q: "filter datasets",
Expand Down
Loading

0 comments on commit 509961f

Please sign in to comment.