Compare commits

...

8 Commits

5 changed files with 258 additions and 15 deletions

View File

@ -29,6 +29,7 @@ define([
"./res/templates/search.html",
"./res/templates/search-menu.html",
"raw-loader!./src/services/GenericSearchWorker.js",
"raw-loader!./src/services/BareBonesSearchWorker.js",
'legacyRegistry'
], function (
SearchController,
@ -39,6 +40,7 @@ define([
searchTemplate,
searchMenuTemplate,
searchWorkerText,
BareBonesSearchWorkerText,
legacyRegistry
) {
@ -115,6 +117,10 @@ define([
}
],
"workers": [
{
"key": "bareBonesSearchWorker",
"scriptText": BareBonesSearchWorkerText
},
{
"key": "genericSearchWorker",
"scriptText": searchWorkerText

View File

@ -0,0 +1,80 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2018, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
/*global self*/
/**
* Module defining GenericSearchWorker. Created by shale on 07/21/2015.
*/
(function () {
// An array of objects composed of domain object IDs and models
// {id: domainObject's ID, model: domainObject's model}
var indexedItems = [];
function indexItem(id, model) {
indexedItems.push({
id: id,
name: model.name.toLowerCase()
});
}
/**
* Gets search results from the indexedItems based on provided search
* input. Returns matching results from indexedItems
*
* @param data An object which contains:
* * input: The original string which we are searching with
* * maxResults: The maximum number of search results desired
* * queryId: an id identifying this query, will be returned.
*/
function search(data) {
// This results dictionary will have domain object ID keys which
// point to the value the domain object's score.
var results,
input = data.input.trim().toLowerCase(),
message = {
request: 'search',
results: {},
total: 0,
queryId: data.queryId
};
results = indexedItems.filter((indexedItem) => {
return indexedItem.name.includes(input);
});
message.total = results.length;
message.results = results
.slice(0, data.maxResults);
return message;
}
self.onmessage = function (event) {
if (event.data.request === 'index') {
indexItem(event.data.id, event.data.model);
} else if (event.data.request === 'search') {
self.postMessage(search(event.data));
}
};
}());

View File

@ -58,6 +58,8 @@ define([
this.pendingQueries = {};
this.useBareBones = true;
this.worker = this.startWorker(workerService);
this.indexOnMutation(topic);
@ -101,8 +103,14 @@ define([
* @returns worker the created search worker.
*/
GenericSearchProvider.prototype.startWorker = function (workerService) {
var worker = workerService.run('genericSearchWorker'),
provider = this;
var provider = this,
worker;
if (this.useBareBones) {
worker = workerService.run('bareBonesSearchWorker');
} else {
worker = workerService.run('genericSearchWorker');
}
worker.addEventListener('message', function (messageEvent) {
provider.onWorkerMessage(messageEvent);
@ -242,18 +250,34 @@ define([
return;
}
var pendingQuery = this.pendingQueries[event.data.queryId],
var pendingQuery,
modelResults;
if (this.useBareBones) {
pendingQuery = this.pendingQueries[event.data.queryId];
modelResults = {
total: event.data.total
};
modelResults.hits = event.data.results.map(function (hit) {
return {
id: hit.item.id,
model: hit.item.model,
score: hit.matchCount
modelResults.hits = event.data.results.map(function (hit) {
return {
id: hit.id
};
});
} else {
pendingQuery = this.pendingQueries[event.data.queryId];
modelResults = {
total: event.data.total
};
});
modelResults.hits = event.data.results.map(function (hit) {
return {
id: hit.item.id,
model: hit.item.model,
score: hit.matchCount
};
});
}
pendingQuery.resolve(modelResults);
delete this.pendingQueries[event.data.queryId];

View File

@ -144,6 +144,8 @@
message.results = results
.slice(0, data.maxResults);
console.log(message);
return message;
}

View File

@ -18,10 +18,47 @@
<span class="c-tree__item__label">Loading...</span>
</div>
</li>
<tree-item v-for="child in children"
:key="child.id"
:node="child">
</tree-item>
<template v-if="children.length">
<template v-if="children.length > page_threshold">
<li v-show="!showSearchComponent"
@click="toggleSearchComponent"
class="c-tree__item-h"
style="font-size: 0.5em;">
<div class="c-tree__item icon-magnify">
</div>
</li>
<li v-show="showSearchComponent"
class="c-tree__item-h"
style="font-size: 0.7em">
<div class="c-tree__item">
<a class="c-tree__item__label c-object-label">
<search
:value="searchValue"
@input="searchChildren"
@clear="searchChildren"
style="min-width: 80%;">
</search>
<div style="padding: 2px; margin-left: 10%;"
class="icon-x"
@click="toggleSearchComponent">
</div>
</a>
</div>
</li>
</template>
<div :style="style"
@scroll="scrollPage"
ref="scrollParent">
<tree-item v-for="child in filteredAndPagedChildren"
:key="child.id"
:node="child">
</tree-item>
</div>
</template>
</ul>
</li>
</template>
@ -29,6 +66,9 @@
<script>
import viewControl from '../components/viewControl.vue';
import ObjectLabel from '../components/ObjectLabel.vue';
import Search from '../components/search.vue';
const PAGE_THRESHOLD = 50;
export default {
name: 'tree-item',
@ -44,7 +84,13 @@
loaded: false,
isNavigated: this.navigateToPath === this.openmct.router.currentLocation.path,
children: [],
expanded: false
expanded: false,
page: 1,
page_threshold: PAGE_THRESHOLD,
searchValue: '',
filteredChildren: [],
scrollTop: 0,
showSearchComponent: false
}
},
computed: {
@ -55,6 +101,44 @@
}
let parentKeyString = this.openmct.objects.makeKeyString(parent.identifier);
return parentKeyString !== this.node.object.location;
},
filteredAndPagedChildren() {
if (this.searchValue) {
this.filteredChildren = this.children.filter((child) => {
let searchLowCase = this.searchValue.toLowerCase(),
nameLowerCase = child.object.name.toLowerCase();
return nameLowerCase.includes(searchLowCase);
})
} else {
this.filteredChildren = this.children;
}
if (this.filteredChildren.length > this.page_threshold) {
let maxIndex = this.page * this.page_threshold,
minIndex = maxIndex - this.page_threshold;
return this.filteredChildren.slice(minIndex, maxIndex);
} else {
return this.filteredChildren;
}
},
lastPage() {
return Math.floor(this.filteredChildren.length / this.page_threshold);
},
style() {
let numChildren = this.filteredChildren.length;
if (!this.$refs.scrollParent || numChildren === 0) {
return {};
}
if ((numChildren * 20) > this.$refs.scrollParent.offsetHeight) {
return {
"overflow-y": 'scroll',
"max-height": (this.page_threshold * 10) + 'px'
}
}
}
},
mounted() {
@ -97,6 +181,11 @@
this.composition.load().then(this.finishLoading);
this.isLoading = true;
}
if (!isExpanded) {
this.page = 1;
this.showSearchComponent = false;
}
}
},
methods: {
@ -126,11 +215,53 @@
} else if (oldPath === this.navigateToPath) {
this.isNavigated = false;
}
},
nextPage() {
if (this.page < this.lastPage) {
this.page += 1;
}
},
previousPage() {
if (this.page >= 1) {
this.page -= 1;
}
},
searchChildren(input) {
this.searchValue = input;
this.page = 1;
},
scrollPage(event) {
let offsetHeight = event.target.offsetHeight,
scrollTop = event.target.scrollTop,
changePage = true;
window.clearTimeout(this.scrollLoading);
if (scrollTop > this.scrollTop && scrollTop > offsetHeight) {
this.scrollLoading = window.setTimeout(() => {
if (this.page < this.lastPage) {
this.nextPage();
event.target.scrollTop = 1;
}
}, 250);
} else if (this.scrollTop <= this.scrollTop && scrollTop <= 0) {
this.scrollLoading = window.setTimeout(() => {
if (this.page > 1) {
this.previousPage();
event.target.scrollTop = offsetHeight - 1;
}
}, 250);
}
this.scrollTop = scrollTop;
},
toggleSearchComponent() {
this.showSearchComponent = !this.showSearchComponent;
}
},
components: {
viewControl,
ObjectLabel
ObjectLabel,
Search
}
}
</script>