Compare commits

...

23 Commits

Author SHA1 Message Date
David Tsay
5f70ab7dd3 allow hiding cancel button 2022-03-25 19:14:54 -07:00
David Tsay
7f73e1e765 safety check for overlay 2022-03-25 19:14:40 -07:00
David Tsay
ea7cf080c3 only show required fields message if form has required fields 2022-03-25 14:01:10 -07:00
David Tsay
9b0762f201 all custom labels 2022-03-25 12:59:14 -07:00
David Tsay
31aa291672 add checkbox
(merged to master after this branch was cut)
2022-03-25 11:08:14 -07:00
David Tsay
65e8d8b23c warn should be alert 2022-03-22 22:43:52 -07:00
David Tsay
fdb35094f4 fix enum issue 2022-03-22 22:05:48 -07:00
David Tsay
3b99a12d7f use domain.source for sorting
do not add telemetry for incorrect timesystems
2022-03-22 10:54:45 -07:00
David Tsay
25ee2d8099 hack for vista timesystems 2022-03-16 10:29:37 -07:00
David Tsay
c14cc25977 clean up observers 2022-03-16 10:27:01 -07:00
David Tsay
50997270e9 separate name and composition listeners 2022-03-16 10:11:20 -07:00
David Tsay
67e3094c6c warn instead of error on missing timesystem so vista still functional 2022-03-14 16:10:18 -07:00
David Tsay
526e31d10c more identifier fixes to search 2022-03-14 16:01:23 -07:00
David Tsay
3b316ed491 fix identifier issues 2022-03-14 13:53:33 -07:00
David Tsay
282ead581a object get in onWorkerMessage call should use identifier 2022-03-14 11:21:04 -07:00
David Tsay
c4f18a4797 object get in indexing process should use identifier not key 2022-03-10 14:27:22 -08:00
David Tsay
c273e83093 debug for search 2022-03-10 14:26:40 -08:00
David Tsay
e4b9242864 hack to parse identifier if namespace and key was mashed 2022-03-01 16:10:53 -08:00
David Tsay
ceddadcac6 fix canvas fallback in plots
https://github.com/nasa/openmct/pull/4784
2022-01-26 14:27:33 -08:00
David Tsay
6e2437b09e fix navigate after create path 2022-01-18 11:19:35 -08:00
David Tsay
5a44931537 default to string not undefined 2022-01-13 17:15:24 -08:00
David Tsay
f165d9c064 Revert "bring back identifierService for getProvider"
This reverts commit 830f321f90.
2022-01-13 17:08:08 -08:00
David Tsay
613973d936 set namespace for malformed identifiers 2022-01-13 17:06:24 -08:00
12 changed files with 234 additions and 89 deletions

View File

@@ -1,5 +1,6 @@
import AutoCompleteField from './components/controls/AutoCompleteField.vue';
import ClockDisplayFormatField from './components/controls/ClockDisplayFormatField.vue';
import CheckBoxField from './components/controls/CheckBoxField.vue';
import Datetime from './components/controls/Datetime.vue';
import FileInput from './components/controls/FileInput.vue';
import Locator from './components/controls/Locator.vue';
@@ -12,6 +13,7 @@ import Vue from 'vue';
export const DEFAULT_CONTROLS_MAP = {
'autocomplete': AutoCompleteField,
'checkbox': CheckBoxField,
'composite': ClockDisplayFormatField,
'datetime': Datetime,
'file-input': FileInput,

View File

@@ -172,7 +172,9 @@ export default class FormsAPI {
function onFormSave(save) {
return () => {
overlay.dismiss();
if (overlay) {
overlay.dismiss();
}
if (save) {
save(changes);

View File

@@ -1,5 +1,5 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2021, United States Government
* Open MCT, Copyright (c) 2014-2022, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
@@ -24,49 +24,60 @@
<div class="c-form">
<div class="c-overlay__top-bar c-form__top-bar">
<div class="c-overlay__dialog-title">{{ model.title }}</div>
<div class="c-overlay__dialog-hint hint">All fields marked <span class="req icon-asterisk"></span> are required.</div>
<div
v-if="hasRequiredFields"
class="c-overlay__dialog-hint hint"
>All fields marked <span class="req icon-asterisk"></span> are required.</div>
</div>
<form name="mctForm"
class="c-form__contents"
autocomplete="off"
@submit.prevent
<form
name="mctForm"
class="c-form__contents"
autocomplete="off"
@submit.prevent
>
<div v-for="section in formSections"
:key="section.id"
class="c-form__section"
:class="section.cssClass"
<div
v-for="section in formSections"
:key="section.id"
class="c-form__section"
:class="section.cssClass"
>
<h2 v-if="section.name"
<h2
v-if="section.name"
class="c-form__section-header"
>
{{ section.name }}
</h2>
<div v-for="(row, index) in section.rows"
:key="row.id"
class="u-contents"
<div
v-for="(row, index) in section.rows"
:key="row.id"
class="u-contents"
>
<FormRow :css-class="section.cssClass"
:first="index < 1"
:row="row"
@onChange="onChange"
<FormRow
:css-class="section.cssClass"
:first="index < 1"
:row="row"
@onChange="onChange"
/>
</div>
</div>
</form>
<div class="mct-form__controls c-overlay__button-bar c-form__bottom-bar">
<button tabindex="0"
:disabled="isInvalid"
class="c-button c-button--major"
@click="onSave"
<button
tabindex="0"
:disabled="isInvalid"
class="c-button c-button--major"
@click="onSave"
>
OK
{{ submitLabel }}
</button>
<button tabindex="0"
class="c-button"
@click="onDismiss"
<button
v-if="!hideCancel"
tabindex="0"
class="c-button"
@click="onDismiss"
>
Cancel
{{ cancelLabel }}
</button>
</div>
</div>
@@ -100,11 +111,42 @@ export default {
};
},
computed: {
hasRequiredFields() {
return this.model.sections.some(section =>
section.rows.some(row => row.required));
},
isInvalid() {
return Object.entries(this.invalidProperties)
.some(([key, value]) => {
return value;
});
},
submitLabel() {
if (
this.model.buttons
&& this.model.buttons.submit
&& this.model.buttons.submit.label
) {
return this.model.buttons.submit.label;
}
return 'OK';
},
cancelLabel() {
if (
this.model.buttons
&& this.model.buttons.cancel
&& this.model.buttons.cancel.label
) {
return this.model.buttons.submit.label;
}
return 'Cancel';
},
hideCancel() {
return this.model.buttons
&& this.model.buttons.cancel
&& this.model.buttons.cancel.hide === true;
}
},
mounted() {

View File

@@ -75,10 +75,12 @@ export default {
rowClass() {
let cssClass = this.cssClass;
if (this.row.required) {
cssClass = `${cssClass} req`;
if (!this.row.required) {
return;
}
cssClass = `${cssClass} req`;
if (this.visited && this.valid !== undefined) {
if (this.valid === true) {
cssClass = `${cssClass} valid`;

View File

@@ -0,0 +1,62 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2022, 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.
*****************************************************************************/
<template>
<span class="form-control shell">
<span
class="field control"
:class="model.cssClass"
>
<input
type="checkbox"
:checked="isChecked"
@input="toggleCheckBox"
>
</span>
</span>
</template>
<script>
export default {
props: {
model: {
type: Object,
required: true
}
},
data() {
return {
isChecked: this.model.value
};
},
methods: {
toggleCheckBox() {
this.isChecked = !this.isChecked;
const data = {
model: this.model,
value: this.isChecked
};
this.$emit('onChange', data);
}
}
};
</script>

View File

@@ -1,5 +1,5 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2021, United States Government
* Open MCT, Copyright (c) 2014-2022, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
@@ -36,13 +36,14 @@ class InMemorySearchProvider {
*/
this.MAX_CONCURRENT_REQUESTS = 100;
/**
* If max results is not specified in query, use this as default.
*/
* If max results is not specified in query, use this as default.
*/
this.DEFAULT_MAX_RESULTS = 100;
this.openmct = openmct;
this.indexedIds = {};
this.indexedCompositions = {};
this.idsToIndex = [];
this.pendingIndex = {};
this.pendingRequests = 0;
@@ -58,7 +59,6 @@ class InMemorySearchProvider {
this.onWorkerMessageError = this.onWorkerMessageError.bind(this);
this.onerror = this.onWorkerError.bind(this);
this.startIndexing = this.startIndexing.bind(this);
this.onMutationOfIndexedObject = this.onMutationOfIndexedObject.bind(this);
this.openmct.on('start', this.startIndexing);
this.openmct.on('destroy', () => {
@@ -68,6 +68,9 @@ class InMemorySearchProvider {
this.worker.port.onmessageerror = null;
this.worker.port.close();
}
this.destroyObservers(this.indexedIds);
this.destroyObservers(this.indexedCompositions);
});
}
@@ -137,7 +140,7 @@ class InMemorySearchProvider {
};
modelResults.hits = await Promise.all(event.data.results.map(async (hit) => {
const identifier = this.openmct.objects.parseKeyString(hit.keyString);
const domainObject = await this.openmct.objects.get(identifier.key);
const domainObject = await this.openmct.objects.get(identifier);
return domainObject;
}));
@@ -213,29 +216,52 @@ class InMemorySearchProvider {
}
}
onMutationOfIndexedObject(domainObject) {
onNameMutation(domainObject, name) {
const provider = this;
provider.index(domainObject.identifier, domainObject);
domainObject.name = name;
provider.index(domainObject);
}
onCompositionMutation(domainObject, composition) {
const provider = this;
const indexedComposition = domainObject.composition;
const identifiersToIndex = composition
.filter(identifier => !indexedComposition
.some(indexedIdentifier => this.openmct.objects
.areIdsEqual([identifier, indexedIdentifier])));
identifiersToIndex.forEach(identifier => {
this.openmct.objects.get(identifier).then(objectToIndex => provider.index(objectToIndex));
});
}
/**
* Pass an id and model to the worker to be indexed. If the model has
* composition, schedule those ids for later indexing.
* Pass a domainObject to the worker to be indexed.
* If the object has composition, schedule those ids for later indexing.
* Watch for object changes and re-index object and children if so
*
* @private
* @param id a model id
* @param model a model
* @param domainObject a domainObject
*/
async index(id, domainObject) {
async index(domainObject) {
const provider = this;
const keyString = this.openmct.objects.makeKeyString(id);
const keyString = this.openmct.objects.makeKeyString(domainObject.identifier);
if (!this.indexedIds[keyString]) {
this.openmct.objects.observe(domainObject, `*`, this.onMutationOfIndexedObject);
this.indexedIds[keyString] = this.openmct.objects.observe(
domainObject,
'name',
this.onNameMutation.bind(this, domainObject)
);
this.indexedCompositions[keyString] = this.openmct.objects.observe(
domainObject,
'composition',
this.onCompositionMutation.bind(this, domainObject)
);
}
this.indexedIds[keyString] = true;
if ((id.key !== 'ROOT')) {
if ((keyString !== 'ROOT')) {
if (this.worker) {
this.worker.port.postMessage({
request: 'index',
@@ -247,15 +273,12 @@ class InMemorySearchProvider {
}
}
const composition = this.openmct.composition.registry.find(foundComposition => {
return foundComposition.appliesTo(domainObject);
});
const composition = this.openmct.composition.get(domainObject);
if (composition) {
const childIdentifiers = await composition.load(domainObject);
childIdentifiers.forEach(function (childIdentifier) {
provider.scheduleForIndexing(childIdentifier);
});
if (composition !== undefined) {
const children = await composition.load();
children.forEach(child => provider.scheduleForIndexing(child.identifier));
}
}
@@ -271,12 +294,12 @@ class InMemorySearchProvider {
const provider = this;
this.pendingRequests += 1;
const identifier = await this.openmct.objects.parseKeyString(keyString);
const domainObject = await this.openmct.objects.get(identifier.key);
const domainObject = await this.openmct.objects.get(keyString);
delete provider.pendingIndex[keyString];
try {
if (domainObject) {
await provider.index(identifier, domainObject);
await provider.index(domainObject);
}
} catch (error) {
console.warn('Failed to index domain object ' + keyString, error);
@@ -305,9 +328,9 @@ class InMemorySearchProvider {
}
/**
* A local version of the same SharedWorker function
* if we don't have SharedWorkers available (e.g., iOS)
*/
* A local version of the same SharedWorker function
* if we don't have SharedWorkers available (e.g., iOS)
*/
localIndexItem(keyString, model) {
this.localIndexedItems[keyString] = {
type: model.type,
@@ -347,6 +370,16 @@ class InMemorySearchProvider {
};
this.onWorkerMessage(eventToReturn);
}
destroyObservers(observers) {
Object.entries(observers).forEach(([keyString, unobserve]) => {
if (typeof unobserve === 'function') {
unobserve();
}
delete observers[keyString];
});
}
}
export default InMemorySearchProvider;

View File

@@ -33,8 +33,10 @@
port.onmessage = function (event) {
if (event.data.request === 'index') {
console.log('onmessage index: ', event.data);
indexItem(event.data.keyString, event.data.model);
} else if (event.data.request === 'search') {
console.log('onmessage search: ', event.data);
port.postMessage(search(event.data));
}
};
@@ -77,6 +79,8 @@
queryId: data.queryId
};
console.log('indexed on search: ', indexedItems);
results = Object.values(indexedItems).filter((indexedItem) => {
return indexedItem.name.toLowerCase().includes(input);
});

View File

@@ -43,9 +43,6 @@ function ObjectAPI(typeRegistry, openmct) {
this.providers = {};
this.rootRegistry = new RootRegistry();
this.inMemorySearchProvider = new InMemorySearchProvider(openmct);
this.injectIdentifierService = function () {
this.identifierService = this.openmct.$injector.get("identifierService");
};
this.rootProvider = new RootObjectProvider(this.rootRegistry);
this.cache = {};
@@ -66,32 +63,17 @@ ObjectAPI.prototype.supersecretSetFallbackProvider = function (p) {
this.fallbackProvider = p;
};
/**
* @private
*/
ObjectAPI.prototype.getIdentifierService = function () {
// Lazily acquire identifier service
if (!this.identifierService) {
this.injectIdentifierService();
}
return this.identifierService;
};
/**
* Retrieve the provider for a given identifier.
* @private
*/
ObjectAPI.prototype.getProvider = function (identifier) {
const keyString = utils.makeKeyString(identifier);
const identifierService = this.openmct.$injector.get("identifierService");
const namespace = identifierService.parse(keyString).getSpace();
if (identifier.key === 'ROOT') {
return this.rootProvider;
}
return this.providers[namespace] || this.fallbackProvider;
return this.providers[identifier.namespace] || this.fallbackProvider;
};
/**

View File

@@ -55,6 +55,11 @@ define([
*/
function parseKeyString(keyString) {
if (isIdentifier(keyString)) {
// hack to workaround a bug mashing keyString into identifier.key
if (!keyString.namespace && keyString.key.includes(':')) {
return parseKeyString(keyString.key);
}
return keyString;
}

View File

@@ -25,6 +25,7 @@ import EventEmitter from 'EventEmitter';
const ERRORS = {
TIMESYSTEM_KEY: 'All telemetry metadata must have a telemetry value with a key that matches the key of the active time system.',
TIMESYSTEM_KEY_NOTIFICATION: 'Telemetry metadata does not match the active time system.',
LOADED: 'Telemetry Collection has already been loaded.'
};
@@ -266,6 +267,10 @@ export class TelemetryCollection extends EventEmitter {
this.lastBounds = bounds;
if (isTick) {
if (this.timeKey === undefined) {
return;
}
// need to check futureBuffer and need to check
// if anything has fallen out of bounds
let startIndex = 0;
@@ -305,7 +310,6 @@ export class TelemetryCollection extends EventEmitter {
if (added.length > 0) {
this.emit('add', added);
}
} else {
// user bounds change, reset
this._reset();
@@ -325,12 +329,14 @@ export class TelemetryCollection extends EventEmitter {
let domains = this.metadata.valuesForHints(['domain']);
let domain = domains.find((d) => d.key === timeSystem.key);
if (domain === undefined) {
this._error(ERRORS.TIMESYSTEM_KEY);
if (domain !== undefined) {
// timeKey is used to create a dummy datum used for sorting
this.timeKey = domain.source;
} else {
this._warn(ERRORS.TIMESYSTEM_KEY);
this.openmct.notifications.alert(ERRORS.TIMESYSTEM_KEY_NOTIFICATION)
}
// timeKey is used to create a dummy datum used for sorting
this.timeKey = domain.source; // this defaults to key if no source is set
let metadataValue = this.metadata.value(timeSystem.key) || { format: timeSystem.key };
let valueFormatter = this.openmct.telemetry.getValueFormatter(metadataValue);
@@ -400,4 +406,8 @@ export class TelemetryCollection extends EventEmitter {
_error(message) {
throw new Error(message);
}
_warn(message) {
console.warn(message);
}
}

View File

@@ -66,10 +66,11 @@ export default class CreateAction extends PropertiesAction {
});
const parentDomainObject = parentDomainObjectPath[0];
const namespace = parentDomainObject.identifier.namespace || parentDomainObject.key || '';
this.domainObject.modified = Date.now();
this.domainObject.location = this.openmct.objects.makeKeyString(parentDomainObject.identifier);
this.domainObject.identifier.namespace = parentDomainObject.identifier.namespace;
this.domainObject.identifier.namespace = namespace;
// Show saving progress dialog
let dialog = this.openmct.overlays.progressDialog({
@@ -107,7 +108,7 @@ export default class CreateAction extends PropertiesAction {
}
const url = '#/browse/' + objectPath
.map(object => object && this.openmct.objects.makeKeyString(object.identifier.key))
.map(object => object && this.openmct.objects.makeKeyString(object.identifier))
.reverse()
.join('/');

View File

@@ -278,7 +278,7 @@ export default {
// Have to throw away the old canvas elements and replace with new
// canvas elements in order to get new drawing contexts.
const div = document.createElement('div');
div.innerHTML = this.TEMPLATE;
div.innerHTML = this.canvasTemplate + this.canvasTemplate;
const mainCanvas = div.querySelectorAll("canvas")[1];
const overlayCanvas = div.querySelectorAll("canvas")[0];
this.canvas.parentNode.replaceChild(mainCanvas, this.canvas);