mirror of
https://github.com/galaxyproject/galaxy.git
synced 2026-10-31 02:28:09 +08:00
Merge pull request #14391 from ahmedhamidawan/Impr_tool_search
Feature: Advanced Tool Search Menu
This commit is contained in:
@@ -6,15 +6,26 @@
|
||||
class="search-query"
|
||||
size="sm"
|
||||
autocomplete="off"
|
||||
:disabled="includeAdvBtn && showAdvanced"
|
||||
:placeholder="placeholder"
|
||||
@input="delayQuery"
|
||||
@change="setQuery"
|
||||
@keydown.esc="setQuery('')" />
|
||||
<b-input-group-append>
|
||||
<b-button
|
||||
v-if="includeAdvBtn"
|
||||
size="sm"
|
||||
:pressed="showAdvanced"
|
||||
:variant="showAdvanced ? 'info' : 'secondary'"
|
||||
@click="onToggle">
|
||||
<icon v-if="showAdvanced" icon="angle-double-up" />
|
||||
<icon v-else icon="angle-double-down" />
|
||||
</b-button>
|
||||
<b-button
|
||||
class="search-clear"
|
||||
size="sm"
|
||||
:title="titleClear | l"
|
||||
:disabled="includeAdvBtn && showAdvanced"
|
||||
data-description="reset query"
|
||||
@click="clearBox">
|
||||
<icon v-if="loading" icon="spinner" spin />
|
||||
@@ -42,6 +53,14 @@ export default {
|
||||
type: Number,
|
||||
default: 1000,
|
||||
},
|
||||
includeAdvBtn: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
showAdvanced: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
@@ -83,6 +102,9 @@ export default {
|
||||
this.setQuery("");
|
||||
this.$refs.toolInput.focus();
|
||||
},
|
||||
onToggle() {
|
||||
this.$emit("onToggle", !this.showAdvanced);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
<template>
|
||||
<b-button
|
||||
v-if="offset > 200"
|
||||
v-b-tooltip.noninteractive.hover
|
||||
class="ui-btn-back-to-top btn-circle"
|
||||
title="Scroll To Top"
|
||||
variant="info"
|
||||
@click="$emit('click')">
|
||||
<i class="fa fa-arrow-up" />
|
||||
</b-button>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
offset: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
@@ -0,0 +1,106 @@
|
||||
<template>
|
||||
<ToolsProvider v-slot="{ loading, result: itemsLoaded }" :filter-settings="filterSettings">
|
||||
<section class="overflow-auto h-100" @scroll="onScroll">
|
||||
<div class="mb-2">
|
||||
<span class="row mb-1">
|
||||
<span class="col">
|
||||
<h4 class="d-inline-block">Advanced Tool Search Results</h4>
|
||||
</span>
|
||||
</span>
|
||||
<span v-if="itemsLoaded.length !== 0 && hasFilters" class="row">
|
||||
<span class="col d-inline-block">
|
||||
Found {{ itemsLoaded.length }} tools for
|
||||
<a id="popover-filters" href="javascript:void(0)">filters</a>.
|
||||
<b-popover target="popover-filters" triggers="hover" placement="top">
|
||||
<template v-slot:title>Filters</template>
|
||||
<div v-for="(value, filter) in filterSettings" :key="filter">
|
||||
<b>{{ filter }}</b
|
||||
>: {{ value }}
|
||||
</div>
|
||||
</b-popover>
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
<div>
|
||||
<b-alert v-if="loading" class="m-2" variant="info" show>
|
||||
<LoadingSpan message="Loading Advanced Search Results" />
|
||||
</b-alert>
|
||||
<b-alert v-else-if="!hasFilters" class="m-2" variant="secondary" show>
|
||||
Please add filters to the menu in the Tool Panel.
|
||||
</b-alert>
|
||||
<b-alert v-else-if="!itemsLoaded || itemsLoaded.length == 0" class="m-2" variant="info" show>
|
||||
No tools found for the entered filters.
|
||||
</b-alert>
|
||||
<div v-else>
|
||||
<ToolAdvancedSearchResults :tools="itemsLoaded" />
|
||||
</div>
|
||||
</div>
|
||||
<ScrollToTopButton :offset="offset" @click="scrollToTop" />
|
||||
</section>
|
||||
</ToolsProvider>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import LoadingSpan from "components/LoadingSpan";
|
||||
import { ToolsProvider } from "components/providers/storeProviders";
|
||||
import ToolAdvancedSearchResults from "./ToolAdvancedSearchResults";
|
||||
import ScrollToTopButton from "./ScrollToTopButton";
|
||||
|
||||
export default {
|
||||
components: {
|
||||
LoadingSpan,
|
||||
ToolAdvancedSearchResults,
|
||||
ToolsProvider,
|
||||
ScrollToTopButton,
|
||||
},
|
||||
props: {
|
||||
name: {
|
||||
type: String,
|
||||
default: "",
|
||||
},
|
||||
panelSectionName: {
|
||||
type: String,
|
||||
default: "",
|
||||
},
|
||||
id: {
|
||||
type: String,
|
||||
default: "",
|
||||
},
|
||||
description: {
|
||||
type: String,
|
||||
default: "",
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
offset: 0,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
filterSettings() {
|
||||
const newFilterSettings = {};
|
||||
Object.entries(this.$props).forEach(([filter, value]) => {
|
||||
if (value && value !== "") {
|
||||
if (filter === "panelSectionName") {
|
||||
newFilterSettings["panel_section_name"] = value;
|
||||
} else {
|
||||
newFilterSettings[filter] = value;
|
||||
}
|
||||
}
|
||||
});
|
||||
return newFilterSettings;
|
||||
},
|
||||
hasFilters() {
|
||||
return Object.keys(this.filterSettings).length;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
onScroll(e) {
|
||||
this.offset = e.target.scrollTop;
|
||||
},
|
||||
scrollToTop() {
|
||||
this.$el.scrollTop = 0;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
@@ -0,0 +1,185 @@
|
||||
<template>
|
||||
<div v-infinite-scroll="loadMore" infinite-scroll-disabled="busy">
|
||||
<b-overlay :show="busy" opacity="0.5">
|
||||
<b-table ref="toolTable" striped bordered :fields="fields" :items="buffer">
|
||||
<template v-slot:cell(name)="row">
|
||||
<span v-if="!row.item.help_text"
|
||||
><b>{{ row.item.name }}</b> {{ row.item.description }}</span
|
||||
>
|
||||
<span v-else>
|
||||
<b-link href="javascript:void(0)" role="button" @click="toggleHelp(row)">
|
||||
<b>{{ row.item.name }}</b> {{ row.item.description }}
|
||||
</b-link>
|
||||
<p v-if="!row.item._showDetails && row.item.summary" v-html="row.item.summary" />
|
||||
</span>
|
||||
</template>
|
||||
<template v-slot:row-details="row">
|
||||
<b-card v-if="row.item.help_text">
|
||||
<p class="mb-1" v-html="row.item.help_text" />
|
||||
<a
|
||||
:href="row.item.target === 'galaxy_main' ? 'javascript:void(0)' : row.item.link"
|
||||
@click.stop="onOpen(row.item)">
|
||||
Click here to open the tool
|
||||
</a>
|
||||
</b-card>
|
||||
</template>
|
||||
<template v-slot:cell(section)="row">
|
||||
{{ row.item.panel_section_name }}
|
||||
</template>
|
||||
<template v-slot:cell(workflow)="row">
|
||||
<span
|
||||
v-if="row.item.is_workflow_compatible"
|
||||
v-b-tooltip.hover
|
||||
class="fa fa-check text-success"
|
||||
title="Is Workflow Compatible" />
|
||||
<span v-else v-b-tooltip.hover class="fa fa-times text-danger" title="Not Workflow Compatible" />
|
||||
</template>
|
||||
<template v-slot:cell(target)="row">
|
||||
<span
|
||||
v-if="row.item.target === 'galaxy_main'"
|
||||
v-b-tooltip.hover
|
||||
class="fa fa-check text-success"
|
||||
title="Is Local" />
|
||||
<span v-else v-b-tooltip.hover class="fa fa-times text-danger" title="Not Local" />
|
||||
</template>
|
||||
<template v-slot:cell(open)="row">
|
||||
<b-button
|
||||
v-b-tooltip.hover.top
|
||||
:title="'Open Tool' | localize"
|
||||
class="fa fa-play"
|
||||
size="sm"
|
||||
variant="primary"
|
||||
:href="row.item.target === 'galaxy_main' ? 'javascript:void(0)' : row.item.link"
|
||||
@click.stop="onOpen(row.item)" />
|
||||
</template>
|
||||
</b-table>
|
||||
</b-overlay>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
const loadTimeout = 100;
|
||||
const defaultBufferLen = 4;
|
||||
import _l from "utils/localization";
|
||||
import infiniteScroll from "vue-infinite-scroll";
|
||||
import { openGlobalUploadModal } from "components/Upload";
|
||||
import { fetchData } from "./services";
|
||||
|
||||
export default {
|
||||
directives: { infiniteScroll },
|
||||
props: {
|
||||
tools: {
|
||||
type: Array,
|
||||
default: null,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
buffer: {},
|
||||
bufferLen: defaultBufferLen,
|
||||
busy: false,
|
||||
fields: [
|
||||
{
|
||||
key: "name",
|
||||
label: _l("Name"),
|
||||
sortable: true,
|
||||
},
|
||||
{
|
||||
key: "section",
|
||||
label: _l("Section"),
|
||||
sortable: true,
|
||||
},
|
||||
{
|
||||
key: "workflow",
|
||||
label: _l("Workflow Compatible"),
|
||||
sortable: false,
|
||||
},
|
||||
{
|
||||
key: "target",
|
||||
label: _l("Local Tool"),
|
||||
sortable: false,
|
||||
},
|
||||
{
|
||||
key: "open",
|
||||
label: "",
|
||||
},
|
||||
],
|
||||
};
|
||||
},
|
||||
created() {
|
||||
const initData = { summary: "", help_text: "", _showDetails: false, is_fetched: false };
|
||||
this.buffer = this.tools.slice(0, this.bufferLen);
|
||||
this.buffer.forEach((tool) => Object.assign(tool, { ...initData }));
|
||||
this.buffer.forEach(async (tool, index) => {
|
||||
await this.fetchHelp(index);
|
||||
});
|
||||
},
|
||||
methods: {
|
||||
onOpen(tool) {
|
||||
if (tool.id === "upload1") {
|
||||
openGlobalUploadModal();
|
||||
} else if (tool.form_style === "regular") {
|
||||
// encode spaces in tool.id
|
||||
const toolId = tool.id;
|
||||
const toolVersion = tool.version;
|
||||
this.$router.push({ path: `/?tool_id=${encodeURIComponent(toolId)}&version=${toolVersion}` });
|
||||
}
|
||||
},
|
||||
async loadMore() {
|
||||
if (this.buffer.length < this.tools.length) {
|
||||
this.busy = true;
|
||||
setTimeout(() => {
|
||||
this.bufferLen += defaultBufferLen;
|
||||
this.buffer = this.tools.slice(0, this.bufferLen);
|
||||
this.buffer.forEach(async (tool, index) => {
|
||||
await this.fetchHelp(index);
|
||||
});
|
||||
this.busy = false;
|
||||
}, loadTimeout);
|
||||
}
|
||||
},
|
||||
toggleHelp(row) {
|
||||
row.toggleDetails();
|
||||
this.$refs.toolTable.refresh();
|
||||
},
|
||||
async fetchHelp(index) {
|
||||
const id = this.buffer[index]["id"];
|
||||
if (!this.buffer[index]["is_fetched"]) {
|
||||
this.buffer[index]["is_fetched"] = true; // tags the tool's help as being fetched
|
||||
await fetchData(`api/tools/${id}/build`).then((response) => {
|
||||
if (response.help && response.help != "\n") {
|
||||
this.buffer[index]["help_text"] = response.help;
|
||||
this.buffer[index]["summary"] = this.parseHelp(response.help);
|
||||
} else {
|
||||
this.buffer[index]["help_text"] = ""; // for cases where helpText == '\n'
|
||||
}
|
||||
});
|
||||
this.$refs.toolTable.refresh();
|
||||
}
|
||||
},
|
||||
parseHelp(help) {
|
||||
let summary = "";
|
||||
const parser = new DOMParser();
|
||||
const helpDoc = parser.parseFromString(help, "text/html");
|
||||
const xpaths = [
|
||||
"//strong[text()='What it does']/../following-sibling::*",
|
||||
"//strong[text()='What It Does']/../following-sibling::*",
|
||||
"//h1[text()='Synopsis']/following-sibling::*",
|
||||
"//strong[text()='Syntax']/../following-sibling::*",
|
||||
];
|
||||
const matches = [];
|
||||
xpaths.forEach((xpath) => {
|
||||
matches.push(
|
||||
helpDoc.evaluate(xpath, helpDoc, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue
|
||||
);
|
||||
});
|
||||
matches.forEach((match) => {
|
||||
if (match) {
|
||||
summary += match.innerHTML + "\n";
|
||||
}
|
||||
});
|
||||
return summary;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
@@ -0,0 +1,52 @@
|
||||
import { mount } from "@vue/test-utils";
|
||||
import { getLocalVue } from "jest/helpers";
|
||||
import ToolSearch from "./ToolSearch";
|
||||
const localVue = getLocalVue();
|
||||
|
||||
describe("ToolSearch", () => {
|
||||
it("test tools advanced filter panel", async () => {
|
||||
const wrapper = mount(ToolSearch, {
|
||||
propsData: {
|
||||
currentPanelView: "default",
|
||||
showAdvanced: false,
|
||||
},
|
||||
localVue,
|
||||
stubs: {
|
||||
icon: { template: "<div></div>" },
|
||||
},
|
||||
});
|
||||
|
||||
expect(wrapper.find("[description='advanced tool filters']").exists()).toBe(false);
|
||||
await wrapper.setProps({ showAdvanced: true });
|
||||
expect(wrapper.find("[description='advanced tool filters']").exists()).toBe(true);
|
||||
const filterInputs = {
|
||||
"[placeholder='any tool name']": "name-filter",
|
||||
"[placeholder='any section']": "section-filter",
|
||||
"[placeholder='any id']": "id-filter",
|
||||
"[placeholder='any description']": "desc-filter",
|
||||
};
|
||||
|
||||
// Now add filters in all input fields in the advanced menu
|
||||
Object.entries(filterInputs).forEach(([selector, value]) => {
|
||||
const filterInput = wrapper.find(selector);
|
||||
if (filterInput.vm && filterInput.props().type == "text") {
|
||||
filterInput.setValue(value);
|
||||
}
|
||||
});
|
||||
|
||||
// Test: values are stored in the filterSettings object
|
||||
expect(Object.values(wrapper.vm.filterSettings)).toEqual(Object.values(filterInputs));
|
||||
|
||||
// Test: clearing the filters
|
||||
const clearButton = wrapper.find("[description='clear filters']");
|
||||
expect(Object.values(wrapper.vm.filterSettings).length).toBe(4);
|
||||
await clearButton.trigger("click");
|
||||
expect(Object.values(wrapper.vm.filterSettings).length).toBe(0);
|
||||
|
||||
// Test: keyup.esc (should toggle the view out)
|
||||
const nameField = wrapper.find("[placeholder='any tool name']");
|
||||
expect(wrapper.emitted()["update:show-advanced"]).toBeUndefined();
|
||||
await nameField.trigger("keyup.esc");
|
||||
expect(wrapper.emitted()["update:show-advanced"].length - 1).toBeFalsy();
|
||||
});
|
||||
});
|
||||
@@ -1,5 +1,40 @@
|
||||
<template>
|
||||
<DelayedInput class="mb-3" :query="query" :loading="loading" :placeholder="placeholder" @change="checkQuery" />
|
||||
<div>
|
||||
<DelayedInput
|
||||
:class="!showAdvanced && 'mb-3'"
|
||||
:query="query"
|
||||
:loading="loading"
|
||||
:show-advanced="showAdvanced"
|
||||
include-adv-btn
|
||||
:placeholder="placeholder"
|
||||
@change="checkQuery"
|
||||
@onToggle="onToggle" />
|
||||
<div
|
||||
v-if="showAdvanced"
|
||||
class="mt-2"
|
||||
description="advanced tool filters"
|
||||
@keyup.enter="onSearch"
|
||||
@keyup.esc="onToggle(false)">
|
||||
<small>Filter by tool name:</small>
|
||||
<b-form-input v-model="filterSettings['name']" size="sm" placeholder="any tool name" />
|
||||
<small class="mt-1">Filter by section:</small>
|
||||
<b-form-input v-model="filterSettings['panelSectionName']" size="sm" placeholder="any section" />
|
||||
<small class="mt-1">Filter by id:</small>
|
||||
<b-form-input v-model="filterSettings['id']" size="sm" placeholder="any id" />
|
||||
<small class="mt-1">Filter by description:</small>
|
||||
<b-form-input v-model="filterSettings['description']" size="sm" placeholder="any description" />
|
||||
<div class="mt-3">
|
||||
<b-button class="mr-1" size="sm" variant="primary" @click="onSearch">
|
||||
<icon icon="search" />
|
||||
<span>{{ "Search" | localize }}</span>
|
||||
</b-button>
|
||||
<b-button size="sm" description="clear filters" @click="filterSettings = {}">
|
||||
<icon icon="redo" />
|
||||
<span>{{ "Clear" | localize }}</span>
|
||||
</b-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
@@ -26,12 +61,17 @@ export default {
|
||||
type: String,
|
||||
default: null,
|
||||
},
|
||||
showAdvanced: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
favorites: ["#favs", "#favorites", "#favourites"],
|
||||
minQueryLength: 3,
|
||||
loading: false,
|
||||
filterSettings: {},
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
@@ -65,6 +105,17 @@ export default {
|
||||
this.$emit("onResults", null);
|
||||
}
|
||||
},
|
||||
onSearch() {
|
||||
for (const [filter, value] of Object.entries(this.filterSettings)) {
|
||||
if (!value) {
|
||||
delete this.filterSettings[filter];
|
||||
}
|
||||
}
|
||||
this.$router.push({ path: "/tools/advanced_search", query: this.filterSettings });
|
||||
},
|
||||
onToggle(toggleAdvanced) {
|
||||
this.$emit("update:show-advanced", toggleAdvanced);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
import axios from "axios";
|
||||
import { rethrowSimple } from "utils/simple-error";
|
||||
import { getAppRoot } from "onload/loadConfig";
|
||||
|
||||
export async function fetchData(url) {
|
||||
try {
|
||||
const { data } = await axios.get(`${getAppRoot()}${url}`);
|
||||
return data;
|
||||
} catch (e) {
|
||||
rethrowSimple(e);
|
||||
}
|
||||
}
|
||||
@@ -3,8 +3,9 @@
|
||||
<div unselectable="on">
|
||||
<div class="unified-panel-header-inner">
|
||||
<nav class="d-flex justify-content-between mx-3 my-2">
|
||||
<h4 v-localize class="m-1">Tools</h4>
|
||||
<div class="panel-header-buttons">
|
||||
<h4 v-if="!showAdvanced" v-localize class="m-1">Tools</h4>
|
||||
<h4 v-else v-localize class="m-1">Advanced Tool Search</h4>
|
||||
<div v-if="!showAdvanced" class="panel-header-buttons">
|
||||
<b-button-group>
|
||||
<favorites-button :query="query" @onFavorites="onQuery" />
|
||||
<panel-view-button
|
||||
@@ -21,24 +22,27 @@
|
||||
<tool-search
|
||||
:current-panel-view="currentPanelView"
|
||||
:placeholder="titleSearchTools"
|
||||
:show-advanced.sync="showAdvanced"
|
||||
:query="query"
|
||||
@onQuery="onQuery"
|
||||
@onResults="onResults" />
|
||||
<upload-button />
|
||||
<div v-if="hasResults" class="pb-2">
|
||||
<b-button size="sm" class="w-100" @click="onToggle">
|
||||
<span :class="buttonIcon" />
|
||||
<span class="mr-1">{{ buttonText }}</span>
|
||||
</b-button>
|
||||
</div>
|
||||
<div v-else-if="queryTooShort" class="pb-2">
|
||||
<b-badge class="alert-danger w-100">Search string too short!</b-badge>
|
||||
</div>
|
||||
<div v-else-if="queryFinished" class="pb-2">
|
||||
<b-badge class="alert-danger w-100">No results found!</b-badge>
|
||||
</div>
|
||||
<section v-if="!showAdvanced">
|
||||
<upload-button />
|
||||
<div v-if="hasResults" class="pb-2">
|
||||
<b-button size="sm" class="w-100" @click="onToggle">
|
||||
<span :class="buttonIcon" />
|
||||
<span class="mr-1">{{ buttonText }}</span>
|
||||
</b-button>
|
||||
</div>
|
||||
<div v-else-if="queryTooShort" class="pb-2">
|
||||
<b-badge class="alert-danger w-100">Search string too short!</b-badge>
|
||||
</div>
|
||||
<div v-else-if="queryFinished" class="pb-2">
|
||||
<b-badge class="alert-danger w-100">No results found!</b-badge>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
<div class="unified-panel-body">
|
||||
<div v-if="!showAdvanced" class="unified-panel-body">
|
||||
<div class="toolMenuContainer">
|
||||
<div class="toolMenu">
|
||||
<tool-section
|
||||
@@ -106,6 +110,7 @@ export default {
|
||||
queryFilter: null,
|
||||
queryPending: false,
|
||||
showSections: false,
|
||||
showAdvanced: false,
|
||||
buttonText: "",
|
||||
buttonIcon: "",
|
||||
titleSearchTools: _l("search tools"),
|
||||
|
||||
@@ -190,3 +190,4 @@ export const StoreProvider = (storeAction, storeGetter, storeCountGetter = undef
|
||||
export const DatasetProvider = StoreProvider("fetchDataset", "getDataset");
|
||||
export const CollectionElementsProvider = StoreProvider("fetchCollectionElements", "getCollectionElements");
|
||||
export const HistoryItemsProvider = StoreProvider("fetchHistoryItems", "getHistoryItems", "getTotalMatchesCount");
|
||||
export const ToolsProvider = StoreProvider("fetchAllTools", "getTools");
|
||||
|
||||
@@ -37,6 +37,7 @@ import NewUserConfirmation from "components/login/NewUserConfirmation";
|
||||
import NewUserWelcome from "components/NewUserWelcome/NewUserWelcome";
|
||||
import Sharing from "components/Sharing/Sharing";
|
||||
import StoredWorkflowInvocations from "components/Workflow/StoredWorkflowInvocations";
|
||||
import ToolAdvancedSearch from "components/Panels/Common/ToolAdvancedSearch";
|
||||
import ToolsJson from "components/ToolsView/ToolsSchemaJson/ToolsJson";
|
||||
import ToolsView from "components/ToolsView/ToolsView";
|
||||
import TourList from "components/Tour/TourList";
|
||||
@@ -276,6 +277,15 @@ export function getRouter(Galaxy) {
|
||||
component: TourRunner,
|
||||
props: true,
|
||||
},
|
||||
{
|
||||
path: "tools/advanced_search",
|
||||
component: ToolAdvancedSearch,
|
||||
props: (route) => {
|
||||
return {
|
||||
...route.query,
|
||||
};
|
||||
},
|
||||
},
|
||||
{
|
||||
path: "tools/view",
|
||||
component: ToolsView,
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
export const state = {
|
||||
toolById: {},
|
||||
toolsList: [],
|
||||
};
|
||||
|
||||
import Vue from "vue";
|
||||
@@ -18,6 +19,36 @@ const getters = {
|
||||
return "...";
|
||||
}
|
||||
},
|
||||
getTools:
|
||||
(state) =>
|
||||
({ filterSettings }) => {
|
||||
// if no filters
|
||||
if (Object.keys(filterSettings).length == 0) {
|
||||
return [];
|
||||
}
|
||||
const allTools = state.toolsList;
|
||||
const returnedTools = [];
|
||||
|
||||
for (const tool in allTools) {
|
||||
let hasMatches = false;
|
||||
for (const [key, filterValue] of Object.entries(filterSettings)) {
|
||||
const actualValue = allTools[tool][key];
|
||||
if (filterValue) {
|
||||
if (!actualValue || !actualValue.toUpperCase().match(filterValue.toUpperCase())) {
|
||||
hasMatches = false;
|
||||
break;
|
||||
} else {
|
||||
hasMatches = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (hasMatches) {
|
||||
returnedTools.push(allTools[tool]);
|
||||
}
|
||||
}
|
||||
|
||||
return returnedTools;
|
||||
},
|
||||
};
|
||||
|
||||
const actions = {
|
||||
@@ -26,12 +57,24 @@ const actions = {
|
||||
const { data } = await axios.get(`${getAppRoot()}api/tools/${toolId}`);
|
||||
commit("saveToolForId", { toolId, toolData: data });
|
||||
},
|
||||
fetchAllTools: async ({ state, commit }) => {
|
||||
// Preventing store from being populated for every search: we fetch again only if:
|
||||
// store isn't already populated (initial fetch)
|
||||
if (state.toolsList.length === 0) {
|
||||
console.log("fetching all tools once");
|
||||
const { data } = await axios.get(`${getAppRoot()}api/tools?in_panel=False`);
|
||||
commit("saveTools", { toolsData: data });
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
const mutations = {
|
||||
saveToolForId: (state, { toolId, toolData }) => {
|
||||
Vue.set(state.toolById, toolId, toolData);
|
||||
},
|
||||
saveTools: (state, { toolsData }) => {
|
||||
state.toolsList = toolsData;
|
||||
},
|
||||
};
|
||||
|
||||
export const toolStore = {
|
||||
|
||||
@@ -102,6 +102,15 @@ $ui-margin-horizontal-large: $margin-v * 2;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.ui-btn-back-to-top {
|
||||
bottom: 2em;
|
||||
position: fixed;
|
||||
opacity: 0.5;
|
||||
}
|
||||
.ui-btn-back-to-top:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
// thumbnails
|
||||
.ui-thumbnails {
|
||||
.ui-thumbnails-item {
|
||||
|
||||
@@ -211,6 +211,7 @@ def app_pair(global_conf, load_app_kwds=None, wsgi_preflight=True, **kwargs):
|
||||
webapp.add_client_route("/datatypes")
|
||||
webapp.add_client_route("/login/start")
|
||||
webapp.add_client_route("/login/confirm")
|
||||
webapp.add_client_route("/tools/advanced_search")
|
||||
webapp.add_client_route("/tools/view")
|
||||
webapp.add_client_route("/tools/json")
|
||||
webapp.add_client_route("/tours")
|
||||
|
||||
Reference in New Issue
Block a user