From d5a80a29caf9d5cce936b09005fd852d226ea5f6 Mon Sep 17 00:00:00 2001 From: DmitriiKrasnov <80889022+DmitriiKrasnov@users.noreply.github.com> Date: Wed, 30 Mar 2022 21:44:54 +0500 Subject: [PATCH] Gui issue 731 filter species (#803) * Metabolic pathways visualisation (#731): species column and filter * Metabolic pathways visualisation (#731): #issuecomment-1082810606 * Metabolic pathways visualisation (#731): get species for filtering from track * GUI Pathways: localStorage key for columns change (migration), force fetching pathways on panel activated Co-authored-by: Mikhail Rodichenko --- .../ngbGenesTableContextMenu.controller.js | 4 +- .../ngbInternalPathwaysTable/index.js | 5 +- .../ngbInternalPathwaysTable.controller.js | 10 +- .../ngbInternalPathwaysTable.service.js | 129 +++++++++++++- .../ngbInternalPathwaysTableFilter/index.js | 13 ++ .../ngbInternalPathwaysFilterList/index.js | 14 ++ .../ngbFilterList.elements.js | 153 +++++++++++++++++ ...ngbInternalPathwaysFilterList.component.js | 10 ++ ...gbInternalPathwaysFilterList.controller.js | 161 ++++++++++++++++++ .../ngbInternalPathwaysFilterList.scss | 28 +++ .../ngbInternalPathwaysFilterList.tpl.html | 34 ++++ ...gbInternalPathwaysTableFilter.component.js | 9 + ...bInternalPathwaysTableFilter.controller.js | 25 +++ .../ngbInternalPathwaysTableFilter.scss | 7 + .../ngbInternalPathwaysTableFilter.tpl.html | 5 + .../ngbInternalPathwaysTable_header.tpl.html | 1 + .../ngbPathways/ngbPathways.service.js | 2 +- .../ngbPathwaysAnnotationAddDlg.controller.js | 2 +- .../ngbPathwaysPanel.controller.js | 7 +- .../ngbPathways/ngbPathwaysPanel.html | 1 - .../ngbTracksView/ngbTrack/ngbTrack.events.js | 8 +- .../ngbVariantsTable.service.js | 4 +- .../genome/genome-data-service.js | 14 ++ 23 files changed, 625 insertions(+), 21 deletions(-) create mode 100644 client/client/app/components/ngbPathways/ngbInternalPathwaysTable/ngbInternalPathwaysTableFilter/index.js create mode 100644 client/client/app/components/ngbPathways/ngbInternalPathwaysTable/ngbInternalPathwaysTableFilter/ngbInternalPathwaysFilterList/index.js create mode 100644 client/client/app/components/ngbPathways/ngbInternalPathwaysTable/ngbInternalPathwaysTableFilter/ngbInternalPathwaysFilterList/ngbFilterList.elements.js create mode 100644 client/client/app/components/ngbPathways/ngbInternalPathwaysTable/ngbInternalPathwaysTableFilter/ngbInternalPathwaysFilterList/ngbInternalPathwaysFilterList.component.js create mode 100644 client/client/app/components/ngbPathways/ngbInternalPathwaysTable/ngbInternalPathwaysTableFilter/ngbInternalPathwaysFilterList/ngbInternalPathwaysFilterList.controller.js create mode 100644 client/client/app/components/ngbPathways/ngbInternalPathwaysTable/ngbInternalPathwaysTableFilter/ngbInternalPathwaysFilterList/ngbInternalPathwaysFilterList.scss create mode 100644 client/client/app/components/ngbPathways/ngbInternalPathwaysTable/ngbInternalPathwaysTableFilter/ngbInternalPathwaysFilterList/ngbInternalPathwaysFilterList.tpl.html create mode 100644 client/client/app/components/ngbPathways/ngbInternalPathwaysTable/ngbInternalPathwaysTableFilter/ngbInternalPathwaysTableFilter.component.js create mode 100644 client/client/app/components/ngbPathways/ngbInternalPathwaysTable/ngbInternalPathwaysTableFilter/ngbInternalPathwaysTableFilter.controller.js create mode 100644 client/client/app/components/ngbPathways/ngbInternalPathwaysTable/ngbInternalPathwaysTableFilter/ngbInternalPathwaysTableFilter.scss create mode 100644 client/client/app/components/ngbPathways/ngbInternalPathwaysTable/ngbInternalPathwaysTableFilter/ngbInternalPathwaysTableFilter.tpl.html diff --git a/client/client/app/components/ngbGenesTablePanel/ngbGenesTable/ngbGenesTableContextMenu/ngbGenesTableContextMenu.controller.js b/client/client/app/components/ngbGenesTablePanel/ngbGenesTable/ngbGenesTableContextMenu/ngbGenesTableContextMenu.controller.js index 3872fe001..f5b4d7ed2 100644 --- a/client/client/app/components/ngbGenesTablePanel/ngbGenesTable/ngbGenesTableContextMenu/ngbGenesTableContextMenu.controller.js +++ b/client/client/app/components/ngbGenesTablePanel/ngbGenesTable/ngbGenesTableContextMenu/ngbGenesTableContextMenu.controller.js @@ -126,7 +126,9 @@ export default class NgbGenesTableContextMenuController extends BaseController { layoutChange.displayed = true; this.dispatcher.emitSimpleEvent('layout:item:change', {layoutChange}); const readInfo = { - search: this.entity[`${this.ngbGenesTableService.defaultPrefix}featureName`] + search: this.entity[`${this.ngbGenesTableService.defaultPrefix}featureName`], + speciesList: [], + rewriteSpecies: true }; const data = { ...readInfo, diff --git a/client/client/app/components/ngbPathways/ngbInternalPathwaysTable/index.js b/client/client/app/components/ngbPathways/ngbInternalPathwaysTable/index.js index f2accbf01..48fb90547 100644 --- a/client/client/app/components/ngbPathways/ngbInternalPathwaysTable/index.js +++ b/client/client/app/components/ngbPathways/ngbInternalPathwaysTable/index.js @@ -3,10 +3,13 @@ import angular from 'angular'; import component from './ngbInternalPathwaysTable.component'; import controller from './ngbInternalPathwaysTable.controller'; import './ngbInternalPathwaysTable.scss'; + import service from './ngbInternalPathwaysTable.service'; +import ngbInternalPathwaysTableFilter from './ngbInternalPathwaysTableFilter'; + export default angular - .module('ngbInternalPathwaysTable', []) + .module('ngbInternalPathwaysTable', [ngbInternalPathwaysTableFilter]) .service('ngbInternalPathwaysTableService', service.instance) .controller(controller.UID, controller) .component('ngbInternalPathwaysTable', component) diff --git a/client/client/app/components/ngbPathways/ngbInternalPathwaysTable/ngbInternalPathwaysTable.controller.js b/client/client/app/components/ngbPathways/ngbInternalPathwaysTable/ngbInternalPathwaysTable.controller.js index 7bb12bc2a..2c846d2b1 100644 --- a/client/client/app/components/ngbPathways/ngbInternalPathwaysTable/ngbInternalPathwaysTable.controller.js +++ b/client/client/app/components/ngbPathways/ngbInternalPathwaysTable/ngbInternalPathwaysTable.controller.js @@ -7,6 +7,7 @@ const RESIZE_DELAY = 300; export default class ngbInternalPathwaysTableController extends baseController { dispatcher; isProgressShown = true; + displayInternalPathwaysFilter = true; errorMessageList = []; debounce = (new Debounce()).debounce; gridOptions = { @@ -39,7 +40,9 @@ export default class ngbInternalPathwaysTableController extends baseController { events = { 'pathways:internalPathways:page:change': this.loadData.bind(this), 'pathways:internalPathways:search': this.initialize.bind(this), - 'read:show:pathways': this.loadData.bind(this) + 'pathways:internalPathways:species:loaded': this.initialize.bind(this), + 'read:show:pathways': this.loadData.bind(this), + 'pathways:internalPathways:refresh': this.loadData.bind(this), }; constructor($scope, $timeout, dispatcher, @@ -55,6 +58,7 @@ export default class ngbInternalPathwaysTableController extends baseController { uiGridConstants, }); + this.displayInternalPathwaysFilter = this.ngbInternalPathwaysTableService.displayInternalPathwaysFilter; this.initEvents(); } @@ -85,7 +89,9 @@ export default class ngbInternalPathwaysTableController extends baseController { }); } }); - await this.loadData(); + if (this.ngbInternalPathwaysTableService.isInitialized) { + await this.loadData(); + } this.isProgressShown = false; } diff --git a/client/client/app/components/ngbPathways/ngbInternalPathwaysTable/ngbInternalPathwaysTable.service.js b/client/client/app/components/ngbPathways/ngbInternalPathwaysTable/ngbInternalPathwaysTable.service.js index d52ff8beb..a1c66b2e0 100644 --- a/client/client/app/components/ngbPathways/ngbInternalPathwaysTable/ngbInternalPathwaysTable.service.js +++ b/client/client/app/components/ngbPathways/ngbInternalPathwaysTable/ngbInternalPathwaysTable.service.js @@ -1,17 +1,19 @@ import ClientPaginationService from '../../../shared/services/clientPaginationService'; const DEFAULT_INTERNAL_PATHWAYS_COLUMNS = [ - 'name', 'description', 'source' + 'name', 'description', 'source', 'organisms' ]; const DEFAULT_ORDERBY_INTERNAL_PATHWAYS_COLUMNS = { 'name': 'name', 'description': 'description', - 'source': 'source' + 'source': 'source', + 'organisms': 'organisms' }; const INTERNAL_PATHWAYS_COLUMN_TITLES = { name: 'Map', description: 'Description', - source: 'Source' + source: 'Source', + organisms: 'Species' }; const DATABASE_SOURCES = { CUSTOM: 'Custom', @@ -20,12 +22,16 @@ const DATABASE_SOURCES = { }; const FIRST_PAGE = 1; const PAGE_SIZE = 11; +const blockFilterInternalPathwaysTimeout = 500; export default class ngbInternalPathwaysTableService extends ClientPaginationService { + _blockFilterInternalPathways; + constructor(dispatcher, genomeDataService) { super(dispatcher, FIRST_PAGE, PAGE_SIZE, 'pathways:internalPathways:page:change'); this.dispatcher = dispatcher; this.genomeDataService = genomeDataService; + this.loadSpeciesList(); } _internalPathways; @@ -40,6 +46,12 @@ export default class ngbInternalPathwaysTableService extends ClientPaginationSer return this._pageError; } + _isInitialized = false; + + get isInitialized() { + return this._isInitialized; + } + get columnTitleMap() { return INTERNAL_PATHWAYS_COLUMN_TITLES; } @@ -48,35 +60,75 @@ export default class ngbInternalPathwaysTableService extends ClientPaginationSer return DEFAULT_ORDERBY_INTERNAL_PATHWAYS_COLUMNS; } + _internalPathwaysFilter = {}; + + get internalPathwaysFilter() { + return this._internalPathwaysFilter; + } + get internalPathwaysColumns() { - if (!localStorage.getItem('internalPathwaysColumnNames')) { - localStorage.setItem('internalPathwaysColumnNames', JSON.stringify(DEFAULT_INTERNAL_PATHWAYS_COLUMNS)); + if (!localStorage.getItem('internalPathwaysColumns')) { + localStorage.setItem('internalPathwaysColumns', JSON.stringify(DEFAULT_INTERNAL_PATHWAYS_COLUMNS)); } - return JSON.parse(localStorage.getItem('internalPathwaysColumnNames')); + return JSON.parse(localStorage.getItem('internalPathwaysColumns')); } set internalPathwaysColumns(columns) { - localStorage.setItem('internalPathwaysColumnNames', JSON.stringify(columns || [])); + localStorage.setItem('internalPathwaysColumns', JSON.stringify(columns || [])); + } + + _displayInternalPathwaysFilter = true; + + get displayInternalPathwaysFilter() { + if (this._displayInternalPathwaysFilter !== undefined) { + return this._displayInternalPathwaysFilter; + } else { + this._displayInternalPathwaysFilter = JSON.parse(localStorage.getItem('displayInternalPathwaysFilter')) || false; + return this._displayInternalPathwaysFilter; + } + } + + _speciesList; + + get speciesList() { + return this._speciesList || []; + } + + set speciesList(value) { + this._speciesList = (value || []); } static instance(dispatcher, genomeDataService) { return new ngbInternalPathwaysTableService(dispatcher, genomeDataService); } + async loadSpeciesList() { + this.speciesList = await this.genomeDataService.getSpeciesList(); + this._isInitialized = true; + this.dispatcher.emitSimpleEvent('pathways:internalPathways:species:loaded'); + } + async searchInternalPathways(currentSearch) { this._internalPathways = await this.loadInternalPathways(currentSearch); this.dispatcher.emitSimpleEvent('internalPathways:result:change'); } async loadInternalPathways(currentSearch) { + if (currentSearch.rewriteSpecies) { + this.internalPathwaysFilter.organisms = currentSearch.speciesList; + currentSearch.speciesList = []; + currentSearch.rewriteSpecies = false; + } const filter = { pagingInfo: { pageSize: this.pageSize, pageNum: this.currentPage }, sortInfo: this.orderBy ? this.orderBy[0] : null, - term: currentSearch + term: currentSearch.search || '', + taxIds: this.internalPathwaysFilter.organisms || [] }; + const data = await this.genomeDataService.getInternalPathwaysLoad(filter); if (data.error) { this.totalPages = 0; @@ -125,6 +177,27 @@ export default class ngbInternalPathwaysTableService extends ClientPaginationSer }; break; } + case 'organisms': { + columnSettings = { + cellTemplate: `
{{row.entity.organismsViewValue}}
`, + enableHiding: false, + enableSorting: true, + enableFiltering: true, + field: 'organisms', + headerCellTemplate: headerCells, + name: this.columnTitleMap[column], + filterApplied: () => this.internalPathwaysFieldIsFiltered(column), + menuItems: [ + { + title: 'Clear column filter', + action: () => this.clearInternalPathwaysFieldFilter(column), + shown: () => this.internalPathwaysFieldIsFiltered(column) + } + ], + }; + break; + } default: { columnSettings = { enableHiding: false, @@ -150,8 +223,40 @@ export default class ngbInternalPathwaysTableService extends ClientPaginationSer return result; } + clearInternalPathwaysFilter() { + if (this._blockFilterInternalPathways) { + clearTimeout(this._blockFilterInternalPathways); + this._blockFilterInternalPathways = null; + } + this._internalPathwaysFilter = {}; + this.dispatcher.emit('pathways:internalPathways:refresh'); + this._blockFilterInternalPathways = setTimeout(() => { + this._blockFilterInternalPathways = null; + }, blockFilterInternalPathwaysTimeout); + } + + internalPathwaysFieldIsFiltered(fieldName) { + return this.internalPathwaysFilter[fieldName] !== undefined; + } + + clearInternalPathwaysFieldFilter(fieldName) { + this.internalPathwaysFilter[fieldName] = undefined; + this.dispatcher.emit('pathways:internalPathways:refresh'); + } + + canScheduleFilterInternalPathways() { + return !this._blockFilterInternalPathways; + } + + scheduleFilterInternalPathways() { + if (this._blockFilterInternalPathways) { + return; + } + this.dispatcher.emit('pathways:internalPathways:refresh'); + } + _formatServerToClient(internalPathways) { - return { + const result = { id: internalPathways.pathwayId, name: internalPathways.prettyName || internalPathways.name, description: internalPathways.pathwayDesc, @@ -159,5 +264,11 @@ export default class ngbInternalPathwaysTableService extends ClientPaginationSer source: DATABASE_SOURCES[internalPathways.databaseSource] || internalPathways.databaseSource, organisms: internalPathways.organisms }; + if (internalPathways.organisms) { + result.organismsViewValue = internalPathways.organisms + .map(organism => organism.speciesName || organism.taxId) + .join(', '); + } + return result; } } diff --git a/client/client/app/components/ngbPathways/ngbInternalPathwaysTable/ngbInternalPathwaysTableFilter/index.js b/client/client/app/components/ngbPathways/ngbInternalPathwaysTable/ngbInternalPathwaysTableFilter/index.js new file mode 100644 index 000000000..92ecf7cfa --- /dev/null +++ b/client/client/app/components/ngbPathways/ngbInternalPathwaysTable/ngbInternalPathwaysTableFilter/index.js @@ -0,0 +1,13 @@ +import angular from 'angular'; +import filterList from './ngbInternalPathwaysFilterList'; + +// Import internal modules +import component from './ngbInternalPathwaysTableFilter.component'; +import controller from './ngbInternalPathwaysTableFilter.controller'; +import './ngbInternalPathwaysTableFilter.scss'; + +// Import external modules +export default angular.module('ngbInternalPathwaysTableFilter', [filterList]) + .controller(controller.UID, controller) + .component('ngbInternalPathwaysTableFilter', component) + .name; diff --git a/client/client/app/components/ngbPathways/ngbInternalPathwaysTable/ngbInternalPathwaysTableFilter/ngbInternalPathwaysFilterList/index.js b/client/client/app/components/ngbPathways/ngbInternalPathwaysTable/ngbInternalPathwaysTableFilter/ngbInternalPathwaysFilterList/index.js new file mode 100644 index 000000000..35c08a03b --- /dev/null +++ b/client/client/app/components/ngbPathways/ngbInternalPathwaysTable/ngbInternalPathwaysTableFilter/ngbInternalPathwaysFilterList/index.js @@ -0,0 +1,14 @@ +import angular from 'angular'; +import scroller from '../../../../../shared/filter/filterList/ngbFilterList.scroller'; + +// Import internal modules +import component from './ngbInternalPathwaysFilterList.component'; +import controller from './ngbInternalPathwaysFilterList.controller'; +import './ngbInternalPathwaysFilterList.scss'; + +// Import external modules +export default angular.module('ngbInternalPathwaysFilterList', []) + .directive('preventParentScroll', scroller) + .controller(controller.UID, controller) + .component('ngbInternalPathwaysFilterList', component) + .name; diff --git a/client/client/app/components/ngbPathways/ngbInternalPathwaysTable/ngbInternalPathwaysTableFilter/ngbInternalPathwaysFilterList/ngbFilterList.elements.js b/client/client/app/components/ngbPathways/ngbInternalPathwaysTable/ngbInternalPathwaysTableFilter/ngbInternalPathwaysFilterList/ngbFilterList.elements.js new file mode 100644 index 000000000..cf32f910a --- /dev/null +++ b/client/client/app/components/ngbPathways/ngbInternalPathwaysTable/ngbInternalPathwaysTableFilter/ngbInternalPathwaysFilterList/ngbFilterList.elements.js @@ -0,0 +1,153 @@ +const MAX_ITEMS_TO_DISPLAY = 100; +const Math = window.Math; + +export default class ListElements { + + listFn; + onSearchStartCallback; + onSearchFinishedCallback; + preLoadedList = { + model: [], + view: [] + }; + fullList = null; + token; + + constructor(listFn, callbacks) { + this.listFn = listFn; + let list; + if (listFn instanceof Array) { + list = listFn; + this.listFn = (searchCriteria) => { + if (!searchCriteria || !searchCriteria.length) { + return { + model: list, + view: list + }; + } + const filteredList = list.filter(item => item.toLowerCase().indexOf(searchCriteria.toLowerCase()) === 0); + return { + model: filteredList, + view: filteredList + }; + }; + } else if(listFn.hasOwnProperty('model')) { + list = { + model: listFn.model + }; + if (listFn.hasOwnProperty('view')) { + list.view = listFn.view; + } else { + list.view = listFn.model; + } + this.listFn = (searchCriteria) => { + if (!searchCriteria || !searchCriteria.length) { + return { + model: list, + view: list + }; + } + const result = { + model: [], + view: [] + }; + list.model.forEach((item, index) => { + if (item.toLowerCase().indexOf(searchCriteria.toLowerCase()) === 0) { + result.model.push(item); + result.view.push(list.view[index]); + } + }); + return result; + }; + } + const {onSearchStartCallback, onSearchFinishedCallback} = callbacks; + this.onSearchStartCallback = onSearchStartCallback; + this.onSearchFinishedCallback = onSearchFinishedCallback; + this.refreshList(); + } + + _isLoading = false; + + get isLoading() { + return this._isLoading; + } + + refreshList(searchString) { + this._isLoading = true; + const token = this.token = {}; + if (this.onSearchStartCallback) { + this.onSearchStartCallback(searchString); + } + (async() => { + let items; + let shouldUpdateScope = true; + if ((!searchString || !searchString.length) && this.fullList) { + items = this.fullList; + shouldUpdateScope = false; + } else { + if (!this.fullList || this.fullList.model.length === 0) { + items = await this.listFn(searchString); + } else if (searchString && searchString.length > 0) { + if (this.fullList && this.fullList.model.length) { + const filteredList = this.fullList.model.filter(i => i.toLowerCase().indexOf(searchString.toLowerCase()) === 0); + items = { + model: filteredList, + view: filteredList + }; + shouldUpdateScope = false; + } else { + items = await this.listFn(searchString); + shouldUpdateScope = true; + } + } else { + items = this.fullList; + shouldUpdateScope = false; + } + if (!searchString || !searchString.length) { + this.fullList = items; + } + } + if (token === this.token) { + this.token = null; + this._isLoading = false; + this.preLoadedList = { + model: [], + view: [] + }; + if (items.model.length < MAX_ITEMS_TO_DISPLAY) { + this.preLoadedList.model = items.model; + this.preLoadedList.view = items.view; + } else { + this.preLoadedList = { + model: [{ + placeholder: true, + message: `Too much results (${items.model.length}). Top ${MAX_ITEMS_TO_DISPLAY} are shown` + }], + view: [{placeholder: true}] + }; + this.preLoadedList.model.push({divider: true}); + this.preLoadedList.view.push({divider: true}); + for (let i = 0; i < Math.min(items.model.length, MAX_ITEMS_TO_DISPLAY); i++) { + this.preLoadedList.model.push(items.model[i]); + this.preLoadedList.view.push(items.view[i]); + } + } + if (this.onSearchFinishedCallback) { + this.onSearchFinishedCallback(searchString, shouldUpdateScope); + } + } + })(); + } + + getItemAtIndex(index) { + if (index >= this.preLoadedList.model.length) { + return null; + } + return this.preLoadedList.model[index]; + } + + getLength() { + return this.preLoadedList.model.length; + } + +} diff --git a/client/client/app/components/ngbPathways/ngbInternalPathwaysTable/ngbInternalPathwaysTableFilter/ngbInternalPathwaysFilterList/ngbInternalPathwaysFilterList.component.js b/client/client/app/components/ngbPathways/ngbInternalPathwaysTable/ngbInternalPathwaysTableFilter/ngbInternalPathwaysFilterList/ngbInternalPathwaysFilterList.component.js new file mode 100644 index 000000000..1e3878712 --- /dev/null +++ b/client/client/app/components/ngbPathways/ngbInternalPathwaysTable/ngbInternalPathwaysTableFilter/ngbInternalPathwaysFilterList/ngbInternalPathwaysFilterList.component.js @@ -0,0 +1,10 @@ +import controller from './ngbInternalPathwaysFilterList.controller'; + +export default { + bindings: { + list: '<', + field: '<' + }, + controller: controller.UID, + template: require('./ngbInternalPathwaysFilterList.tpl.html') +}; diff --git a/client/client/app/components/ngbPathways/ngbInternalPathwaysTable/ngbInternalPathwaysTableFilter/ngbInternalPathwaysFilterList/ngbInternalPathwaysFilterList.controller.js b/client/client/app/components/ngbPathways/ngbInternalPathwaysTable/ngbInternalPathwaysTableFilter/ngbInternalPathwaysFilterList/ngbInternalPathwaysFilterList.controller.js new file mode 100644 index 000000000..a5d8469bf --- /dev/null +++ b/client/client/app/components/ngbPathways/ngbInternalPathwaysTable/ngbInternalPathwaysTableFilter/ngbInternalPathwaysFilterList/ngbInternalPathwaysFilterList.controller.js @@ -0,0 +1,161 @@ +import ListElements from './ngbFilterList.elements'; + +export default class ngbInternalPathwaysFilterListController { + listIsDisplayed = false; + hideListTimeout = null; + scope; + input; + selectedItems = []; + displayText = ''; + listElements = null; + _hideListIsPrevented = false; + + constructor($scope, $element, ngbInternalPathwaysTableService) { + this.scope = $scope; + this.ngbInternalPathwaysTableService = ngbInternalPathwaysTableService; + this.input = $element.find('.ngb-filter-input')[0]; + this.listElements = new ListElements(this.list, { + onSearchFinishedCallback: this.searchFinished.bind(this) + }); + this.scope.$watch('$ctrl.list', () => { + this.listElements = new ListElements(this.list, { + onSearchFinishedCallback: this.searchFinished.bind(this) + }); + }); + switch (this.field.field) { + case 'organisms': { + if (this.ngbInternalPathwaysTableService.internalPathwaysFilter.organisms) { + this.selectedItems = this.ngbInternalPathwaysTableService.speciesList + .filter(organism => this.ngbInternalPathwaysTableService.internalPathwaysFilter.organisms.indexOf(organism.taxId) >= 0) + .map(organism => organism.taxId); + this.displayText = [...this.selectedItems].join(', '); + } + break; + } + } + } + + static get UID() { + return 'ngbInternalPathwaysFilterListController'; + } + + searchFinished(searchString, shouldUpdateScope) { + const parts = this.displayText.split(',').map(part => part.trim().toLowerCase()); + let last = ''; + if (parts.length) { + last = parts[parts.length - 1]; + parts.splice(parts.length - 1, 1); + } + if (this.listElements.fullList && this.listElements.fullList.model.length > 0) { + this.selectedItems = this.listElements.fullList.model.filter(item => parts.indexOf(item.toLowerCase()) >= 0); + const [fullMatch] = this.listElements.fullList.model.filter(item => item.toLowerCase() === last.toLowerCase()); + if (fullMatch) { + this.selectedItems.push(fullMatch); + } + } + if (shouldUpdateScope) { + this.scope.$apply(); + } + } + + displayList() { + if (this.hideListTimeout) { + clearTimeout(this.hideListTimeout); + this.hideListTimeout = null; + } + this.listIsDisplayed = true; + } + + preventListFromClosing() { + this._hideListIsPrevented = true; + this.input.focus(); + } + + stopPreventListFromClosing() { + this._hideListIsPrevented = false; + this.input.focus(); + } + + hideList() { + if (this.hideListTimeout) { + clearTimeout(this.hideListTimeout); + this.hideListTimeout = null; + } + if (this._hideListIsPrevented) { + return; + } + this.listIsDisplayed = false; + this.apply(); + this.scope.$apply(); + } + + hideListDelayed() { + if (this.hideListTimeout) { + clearTimeout(this.hideListTimeout); + this.hideListTimeout = null; + } + this.hideListTimeout = setTimeout(::this.hideList, 100); + } + + itemIsSelected(item) { + return this.selectedItems.filter(listItem => listItem.toLowerCase() === item.toLowerCase()).length > 0; + } + + inputChanged() { + const textParts = (this.displayText || '').split(','); + const lastPart = textParts[textParts.length - 1].trim().toLowerCase(); + this.listElements.refreshList(lastPart); + } + + didClickOnItem(item) { + if (this.hideListTimeout) { + clearTimeout(this.hideListTimeout); + this.hideListTimeout = null; + } + this.input.focus(); + const index = this.selectedItems.indexOf(item); + if (index >= 0) { + this.selectedItems.splice(index, 1); + } else { + this.selectedItems.push(item); + } + if (this.selectedItems.length) { + this.displayText = [...this.selectedItems, ''].join(', '); + } else { + this.displayText = ''; + } + this.listElements.refreshList(null); + } + + apply() { + const parts = this.displayText.split(',').map(part => part.trim().toLowerCase()); + if (this.listElements.fullList && this.listElements.length > 0) { + this.selectedItems = this.listElements.fullList.model.filter(item => parts.indexOf(item.toLowerCase()) >= 0); + } else { + this.selectedItems = parts.filter(part => part !== ''); + } + this.displayText = this.selectedItems.join(', '); + this.listIsDisplayed = false; + if (!this.ngbInternalPathwaysTableService.canScheduleFilterInternalPathways()) { + return; + } + switch (this.field.field) { + case 'organisms': { + const selectedItemsLowerCase = this.selectedItems.map(i => i.toLowerCase()); + const prevValue = (this.ngbInternalPathwaysTableService.internalPathwaysFilter.organisms || []); + prevValue.sort(); + const prevValueStr = JSON.stringify(prevValue).toUpperCase(); + const currValue = this.ngbInternalPathwaysTableService.speciesList + .filter(organism => selectedItemsLowerCase.indexOf(organism.taxId.toString()) >= 0) + .map(organism => organism.taxId); + currValue.sort(); + const currValueStr = JSON.stringify(currValue).toUpperCase(); + if (currValueStr !== prevValueStr) { + this.ngbInternalPathwaysTableService.internalPathwaysFilter.organisms = currValue; + this.ngbInternalPathwaysTableService.scheduleFilterInternalPathways(); + } + break; + } + } + } +} diff --git a/client/client/app/components/ngbPathways/ngbInternalPathwaysTable/ngbInternalPathwaysTableFilter/ngbInternalPathwaysFilterList/ngbInternalPathwaysFilterList.scss b/client/client/app/components/ngbPathways/ngbInternalPathwaysTable/ngbInternalPathwaysTableFilter/ngbInternalPathwaysFilterList/ngbInternalPathwaysFilterList.scss new file mode 100644 index 000000000..8ad6ae33d --- /dev/null +++ b/client/client/app/components/ngbPathways/ngbInternalPathwaysTable/ngbInternalPathwaysTableFilter/ngbInternalPathwaysFilterList/ngbInternalPathwaysFilterList.scss @@ -0,0 +1,28 @@ +ngb-variants-filter-list { + md-select { + margin: 0; + padding: 0; + } +} + +.ngb-filter-list-container { + background-color: #ffffff; + position: fixed; + width: 200px; + max-height: 300px; + overflow-y: scroll; + z-index: 10; + .ngb-filter-list-item { + min-height: 20px; + padding: 3px; + .ngb-filter-list-item-placeholder { + font-style: italic; + } + } + .ngb-filter-list-loading { + padding: 10px; + } +} +.ngb-filter-list-container-wide { + width: 400px; +} diff --git a/client/client/app/components/ngbPathways/ngbInternalPathwaysTable/ngbInternalPathwaysTableFilter/ngbInternalPathwaysFilterList/ngbInternalPathwaysFilterList.tpl.html b/client/client/app/components/ngbPathways/ngbInternalPathwaysTable/ngbInternalPathwaysTableFilter/ngbInternalPathwaysFilterList/ngbInternalPathwaysFilterList.tpl.html new file mode 100644 index 000000000..912c76668 --- /dev/null +++ b/client/client/app/components/ngbPathways/ngbInternalPathwaysTable/ngbInternalPathwaysTableFilter/ngbInternalPathwaysFilterList/ngbInternalPathwaysFilterList.tpl.html @@ -0,0 +1,34 @@ + +
+
+ + {{$ctrl.listElements.preLoadedList.view[index].taxId}} + {{$ctrl.listElements.preLoadedList.view[index].speciesName.toUpperCase()}} + + {{item.message}} + +
+
+ +
+
diff --git a/client/client/app/components/ngbPathways/ngbInternalPathwaysTable/ngbInternalPathwaysTableFilter/ngbInternalPathwaysTableFilter.component.js b/client/client/app/components/ngbPathways/ngbInternalPathwaysTable/ngbInternalPathwaysTableFilter/ngbInternalPathwaysTableFilter.component.js new file mode 100644 index 000000000..fc3c0346b --- /dev/null +++ b/client/client/app/components/ngbPathways/ngbInternalPathwaysTable/ngbInternalPathwaysTableFilter/ngbInternalPathwaysTableFilter.component.js @@ -0,0 +1,9 @@ +import controller from './ngbInternalPathwaysTableFilter.controller'; + +export default { + bindings: { + column: '<' + }, + controller: controller.UID, + template: require('./ngbInternalPathwaysTableFilter.tpl.html') +}; diff --git a/client/client/app/components/ngbPathways/ngbInternalPathwaysTable/ngbInternalPathwaysTableFilter/ngbInternalPathwaysTableFilter.controller.js b/client/client/app/components/ngbPathways/ngbInternalPathwaysTable/ngbInternalPathwaysTableFilter/ngbInternalPathwaysTableFilter.controller.js new file mode 100644 index 000000000..0156313dd --- /dev/null +++ b/client/client/app/components/ngbPathways/ngbInternalPathwaysTable/ngbInternalPathwaysTableFilter/ngbInternalPathwaysTableFilter.controller.js @@ -0,0 +1,25 @@ +export default class ngbInternalPathwaysTableFilterController { + + isRange = false; + isString = false; + isList = false; + + constructor(ngbInternalPathwaysTableService) { + switch (this.column.field) { + case 'organisms': { + this.isList = true; + this.list = async () => new Promise((resolve) => { + resolve({ + model: ngbInternalPathwaysTableService.speciesList.map(e => e.taxId.toString()), + view: ngbInternalPathwaysTableService.speciesList + }); + }); + break; + } + } + } + + static get UID() { + return 'ngbInternalPathwaysTableFilterController'; + } +} diff --git a/client/client/app/components/ngbPathways/ngbInternalPathwaysTable/ngbInternalPathwaysTableFilter/ngbInternalPathwaysTableFilter.scss b/client/client/app/components/ngbPathways/ngbInternalPathwaysTable/ngbInternalPathwaysTableFilter/ngbInternalPathwaysTableFilter.scss new file mode 100644 index 000000000..4e659fb36 --- /dev/null +++ b/client/client/app/components/ngbPathways/ngbInternalPathwaysTable/ngbInternalPathwaysTableFilter/ngbInternalPathwaysTableFilter.scss @@ -0,0 +1,7 @@ +ngb-internal-pathways-table-filter { + padding-left: 1px; + padding-right: 3px; + input.ngb-filter-input { + height: 14px; + } +} diff --git a/client/client/app/components/ngbPathways/ngbInternalPathwaysTable/ngbInternalPathwaysTableFilter/ngbInternalPathwaysTableFilter.tpl.html b/client/client/app/components/ngbPathways/ngbInternalPathwaysTable/ngbInternalPathwaysTableFilter/ngbInternalPathwaysTableFilter.tpl.html new file mode 100644 index 000000000..84d74184e --- /dev/null +++ b/client/client/app/components/ngbPathways/ngbInternalPathwaysTable/ngbInternalPathwaysTableFilter/ngbInternalPathwaysTableFilter.tpl.html @@ -0,0 +1,5 @@ +
+ +
diff --git a/client/client/app/components/ngbPathways/ngbInternalPathwaysTable/ngbInternalPathwaysTable_header.tpl.html b/client/client/app/components/ngbPathways/ngbInternalPathwaysTable/ngbInternalPathwaysTable_header.tpl.html index 7f7c08195..b6f450a87 100644 --- a/client/client/app/components/ngbPathways/ngbInternalPathwaysTable/ngbInternalPathwaysTable_header.tpl.html +++ b/client/client/app/components/ngbPathways/ngbInternalPathwaysTable/ngbInternalPathwaysTable_header.tpl.html @@ -57,4 +57,5 @@ + diff --git a/client/client/app/components/ngbPathways/ngbPathways.service.js b/client/client/app/components/ngbPathways/ngbPathways.service.js index 1ff91be25..15a4dc889 100644 --- a/client/client/app/components/ngbPathways/ngbPathways.service.js +++ b/client/client/app/components/ngbPathways/ngbPathways.service.js @@ -82,7 +82,7 @@ export default class ngbPathwaysService { initEvents() { this.dispatcher.on('read:show:pathways', data => { - this.currentSearch = data ? data.search : null; + this.currentSearch = data || null; }); } diff --git a/client/client/app/components/ngbPathways/ngbPathwaysAnnotationDlg/ngbPathwaysAnnotationAddDlg.controller.js b/client/client/app/components/ngbPathways/ngbPathwaysAnnotationDlg/ngbPathwaysAnnotationAddDlg.controller.js index 8a659c749..cdaacdee6 100644 --- a/client/client/app/components/ngbPathways/ngbPathwaysAnnotationDlg/ngbPathwaysAnnotationAddDlg.controller.js +++ b/client/client/app/components/ngbPathways/ngbPathwaysAnnotationDlg/ngbPathwaysAnnotationAddDlg.controller.js @@ -26,7 +26,7 @@ export default class ngbPathwaysAnnotationAddDlgController { } get isStateValid() { - return this.annotation.type !== undefined + return this.annotation.type !== undefined && this.annotation.type !== null && (this.annotation.type === this.ngbPathwaysAnnotationService.annotationTypeList.MANUAL || this.annotation.header !== undefined); } diff --git a/client/client/app/components/ngbPathways/ngbPathwaysPanel.controller.js b/client/client/app/components/ngbPathways/ngbPathwaysPanel.controller.js index 97f56637c..960fdd36b 100644 --- a/client/client/app/components/ngbPathways/ngbPathwaysPanel.controller.js +++ b/client/client/app/components/ngbPathways/ngbPathwaysPanel.controller.js @@ -25,6 +25,7 @@ export default class ngbPathwaysPanelController extends baseController { this.pathwaysStates = this.ngbPathwaysService.pathwaysStates; this.initEvents(); this.changeState(this.ngbPathwaysService.currentState); + this.searchPathway(); } static get UID() { @@ -48,7 +49,11 @@ export default class ngbPathwaysPanelController extends baseController { } searchPathway() { - this.ngbPathwaysService.currentSearch = this.searchRequest; + this.ngbPathwaysService.currentSearch = { + search: this.searchRequest, + speciesList: [], + rewriteSpecies: true + }; this.dispatcher.emitSimpleEvent('pathways:internalPathways:search'); this.changeState('INTERNAL_PATHWAYS'); } diff --git a/client/client/app/components/ngbPathways/ngbPathwaysPanel.html b/client/client/app/components/ngbPathways/ngbPathwaysPanel.html index 0cb9a315a..c62c485d0 100644 --- a/client/client/app/components/ngbPathways/ngbPathwaysPanel.html +++ b/client/client/app/components/ngbPathways/ngbPathwaysPanel.html @@ -10,7 +10,6 @@ Search diff --git a/client/client/app/components/ngbTracksView/ngbTrack/ngbTrack.events.js b/client/client/app/components/ngbTracksView/ngbTrack/ngbTrack.events.js index fd7bf4eed..f3d80b3f2 100644 --- a/client/client/app/components/ngbTracksView/ngbTrack/ngbTrack.events.js +++ b/client/client/app/components/ngbTracksView/ngbTrack/ngbTrack.events.js @@ -220,6 +220,10 @@ export default class ngbTrackEvents { if (data.feature.feature && data.feature.name) { const layoutChange = this.appLayout.Panels.pathways; layoutChange.displayed = true; + const [reference] = this.projectContext.references.filter(ref => ref.id === track.referenceId); + const speciesList = reference && reference.species + ? [reference.species.taxId] + : null; menuData.push({ events: [ { @@ -228,7 +232,9 @@ export default class ngbTrackEvents { }, { data: { - search: data.feature.name + speciesList, + search: data.feature.name, + rewriteSpecies: true }, name: 'read:show:pathways' }], diff --git a/client/client/app/components/ngbVariantsTablePanel/ngbVariantsTable/ngbVariantsTable.service.js b/client/client/app/components/ngbVariantsTablePanel/ngbVariantsTable/ngbVariantsTable.service.js index d6cbecae4..9dc2f0566 100644 --- a/client/client/app/components/ngbVariantsTablePanel/ngbVariantsTable/ngbVariantsTable.service.js +++ b/client/client/app/components/ngbVariantsTablePanel/ngbVariantsTable/ngbVariantsTable.service.js @@ -1,5 +1,3 @@ -import headerCells from './ngbVariantsTable_header.tpl.html'; - export default class ngbVariantsTableService { static instance(projectContext, genomeDataService, projectDataService, variantsTableMessages, uiGridConstants) { @@ -115,7 +113,7 @@ export default class ngbVariantsTableService { break; case 'sampleNames': { columnSettings = { - cellTemplate: `
{{COL_FIELD | array}}
`, + cellTemplate: '
{{COL_FIELD | array}}
', enableHiding: false, field: 'sampleNames', headerCellTemplate: headerCells, diff --git a/client/client/dataServices/genome/genome-data-service.js b/client/client/dataServices/genome/genome-data-service.js index 198101ddc..db5364312 100644 --- a/client/client/dataServices/genome/genome-data-service.js +++ b/client/client/dataServices/genome/genome-data-service.js @@ -237,6 +237,20 @@ export class GenomeDataService extends DataService { }); } + getSpeciesList() { + return new Promise((resolve, reject) => { + this.get('pathway/species') + .then((data) => { + if (data) { + resolve(data); + } else { + resolve([]); + } + }) + .catch(reject); + }); + } + getInternalPathwaysLoad(filter) { // return this._internalPathways; return new Promise((resolve, reject) => {