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.html",
"./res/templates/search-menu.html", "./res/templates/search-menu.html",
"raw-loader!./src/services/GenericSearchWorker.js", "raw-loader!./src/services/GenericSearchWorker.js",
"raw-loader!./src/services/BareBonesSearchWorker.js",
'legacyRegistry' 'legacyRegistry'
], function ( ], function (
SearchController, SearchController,
@ -39,6 +40,7 @@ define([
searchTemplate, searchTemplate,
searchMenuTemplate, searchMenuTemplate,
searchWorkerText, searchWorkerText,
BareBonesSearchWorkerText,
legacyRegistry legacyRegistry
) { ) {
@ -115,6 +117,10 @@ define([
} }
], ],
"workers": [ "workers": [
{
"key": "bareBonesSearchWorker",
"scriptText": BareBonesSearchWorkerText
},
{ {
"key": "genericSearchWorker", "key": "genericSearchWorker",
"scriptText": searchWorkerText "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.pendingQueries = {};
this.useBareBones = true;
this.worker = this.startWorker(workerService); this.worker = this.startWorker(workerService);
this.indexOnMutation(topic); this.indexOnMutation(topic);
@ -101,8 +103,14 @@ define([
* @returns worker the created search worker. * @returns worker the created search worker.
*/ */
GenericSearchProvider.prototype.startWorker = function (workerService) { GenericSearchProvider.prototype.startWorker = function (workerService) {
var worker = workerService.run('genericSearchWorker'), var provider = this,
provider = this; worker;
if (this.useBareBones) {
worker = workerService.run('bareBonesSearchWorker');
} else {
worker = workerService.run('genericSearchWorker');
}
worker.addEventListener('message', function (messageEvent) { worker.addEventListener('message', function (messageEvent) {
provider.onWorkerMessage(messageEvent); provider.onWorkerMessage(messageEvent);
@ -242,18 +250,34 @@ define([
return; return;
} }
var pendingQuery = this.pendingQueries[event.data.queryId], var pendingQuery,
modelResults;
if (this.useBareBones) {
pendingQuery = this.pendingQueries[event.data.queryId];
modelResults = { modelResults = {
total: event.data.total total: event.data.total
}; };
modelResults.hits = event.data.results.map(function (hit) { modelResults.hits = event.data.results.map(function (hit) {
return { return {
id: hit.item.id, id: hit.id
model: hit.item.model, };
score: hit.matchCount });
} 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); pendingQuery.resolve(modelResults);
delete this.pendingQueries[event.data.queryId]; delete this.pendingQueries[event.data.queryId];

View File

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

View File

@ -18,10 +18,47 @@
<span class="c-tree__item__label">Loading...</span> <span class="c-tree__item__label">Loading...</span>
</div> </div>
</li> </li>
<tree-item v-for="child in children"
:key="child.id" <template v-if="children.length">
:node="child">
</tree-item> <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> </ul>
</li> </li>
</template> </template>
@ -29,6 +66,9 @@
<script> <script>
import viewControl from '../components/viewControl.vue'; import viewControl from '../components/viewControl.vue';
import ObjectLabel from '../components/ObjectLabel.vue'; import ObjectLabel from '../components/ObjectLabel.vue';
import Search from '../components/search.vue';
const PAGE_THRESHOLD = 50;
export default { export default {
name: 'tree-item', name: 'tree-item',
@ -44,7 +84,13 @@
loaded: false, loaded: false,
isNavigated: this.navigateToPath === this.openmct.router.currentLocation.path, isNavigated: this.navigateToPath === this.openmct.router.currentLocation.path,
children: [], children: [],
expanded: false expanded: false,
page: 1,
page_threshold: PAGE_THRESHOLD,
searchValue: '',
filteredChildren: [],
scrollTop: 0,
showSearchComponent: false
} }
}, },
computed: { computed: {
@ -55,6 +101,44 @@
} }
let parentKeyString = this.openmct.objects.makeKeyString(parent.identifier); let parentKeyString = this.openmct.objects.makeKeyString(parent.identifier);
return parentKeyString !== this.node.object.location; 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() { mounted() {
@ -97,6 +181,11 @@
this.composition.load().then(this.finishLoading); this.composition.load().then(this.finishLoading);
this.isLoading = true; this.isLoading = true;
} }
if (!isExpanded) {
this.page = 1;
this.showSearchComponent = false;
}
} }
}, },
methods: { methods: {
@ -126,11 +215,53 @@
} else if (oldPath === this.navigateToPath) { } else if (oldPath === this.navigateToPath) {
this.isNavigated = false; 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: { components: {
viewControl, viewControl,
ObjectLabel ObjectLabel,
Search
} }
} }
</script> </script>