mirror of
https://github.com/nasa/openmct.git
synced 2025-04-19 08:36:24 +00:00
Compare commits
65 Commits
master
...
omm-r5.3.1
Author | SHA1 | Date | |
---|---|---|---|
|
51453c04d0 | ||
|
1d655e5ccf | ||
|
e25bfb7291 | ||
|
38457fd262 | ||
|
9ad00a226c | ||
|
16a4ca7a9c | ||
|
eab689dbce | ||
|
9cfbb966e2 | ||
|
50e60e7824 | ||
|
6e2f667b50 | ||
|
cef02d60b1 | ||
|
98f8bc494a | ||
|
cbb5dcea39 | ||
|
e2a17b5f06 | ||
|
e86c2a1d11 | ||
|
a0291879e9 | ||
|
e664afd468 | ||
|
d786452abe | ||
|
5cce8d747c | ||
|
3922ade19f | ||
|
e2092a7b17 | ||
|
9065158ac9 | ||
|
9924f53128 | ||
|
eb314a6fab | ||
|
fbf145c240 | ||
|
b3c99aef80 | ||
|
f1cf12d412 | ||
|
7ace3d00be | ||
|
1d77368c7c | ||
|
1aa30975e5 | ||
|
f3493907a2 | ||
|
0cbdbc6807 | ||
|
0254367cd5 | ||
|
a0299820ed | ||
|
0168f92a23 | ||
|
9e56a22bd9 | ||
|
e479c913e5 | ||
|
9a577923d9 | ||
|
1bb49ead0a | ||
|
5dba73e83c | ||
|
0776003f40 | ||
|
11adbd283a | ||
|
852ee74094 | ||
|
bd1c7d9da0 | ||
|
23e12fa0d1 | ||
|
3788a132c4 | ||
|
4f37d955eb | ||
|
2c559457f0 | ||
|
00c9d2920b | ||
|
e624821ab1 | ||
|
15126fd550 | ||
|
5adc86c71e | ||
|
a11fcc3231 | ||
|
d0a77637c0 | ||
|
eeda4c62fc | ||
|
10457a583e | ||
|
ff605a0a0d | ||
|
f56a8a1f5d | ||
|
c8f8a098f2 | ||
|
31be2cba3d | ||
|
eeb4f995a4 | ||
|
40373abfe3 | ||
|
86d4244ace | ||
|
da299e9b95 | ||
|
f0dcf2ba21 |
@ -8,8 +8,8 @@ executors:
|
||||
- image: mcr.microsoft.com/playwright:v1.45.2-focal
|
||||
environment:
|
||||
NODE_ENV: development # Needed to ensure 'dist' folder created and devDependencies installed
|
||||
PERCY_POSTINSTALL_BROWSER: 'true' # Needed to store the percy browser in cache deps
|
||||
PERCY_LOGLEVEL: 'debug' # Enable DEBUG level logging for Percy (Issue: https://github.com/nasa/openmct/issues/5742)
|
||||
PERCY_POSTINSTALL_BROWSER: "true" # Needed to store the percy browser in cache deps
|
||||
PERCY_LOGLEVEL: "debug" # Enable DEBUG level logging for Percy (Issue: https://github.com/nasa/openmct/issues/5742)
|
||||
PERCY_PARALLEL_TOTAL: 2
|
||||
ubuntu:
|
||||
machine:
|
||||
@ -17,7 +17,7 @@ executors:
|
||||
docker_layer_caching: true
|
||||
commands:
|
||||
build_and_install:
|
||||
description: 'All steps used to build and install.'
|
||||
description: "All steps used to build and install."
|
||||
parameters:
|
||||
node-version:
|
||||
type: string
|
||||
@ -27,7 +27,7 @@ commands:
|
||||
node-version: << parameters.node-version >>
|
||||
- node/install-packages
|
||||
generate_and_store_version_and_filesystem_artifacts:
|
||||
description: 'Track important packages and files'
|
||||
description: "Track important packages and files"
|
||||
steps:
|
||||
- run: |
|
||||
[[ $EUID -ne 0 ]] && (sudo mkdir -p /tmp/artifacts && sudo chmod 777 /tmp/artifacts) || (mkdir -p /tmp/artifacts && chmod 777 /tmp/artifacts)
|
||||
@ -38,7 +38,7 @@ commands:
|
||||
- store_artifacts:
|
||||
path: /tmp/artifacts/
|
||||
generate_e2e_code_cov_report:
|
||||
description: 'Generate e2e code coverage artifacts and publish to codecov.io. Needed to that we can ignore the exit code status of the npm run test'
|
||||
description: "Generate e2e code coverage artifacts and publish to codecov.io. Needed to that we can ignore the exit code status of the npm run test"
|
||||
parameters:
|
||||
suite:
|
||||
type: string
|
||||
@ -102,7 +102,7 @@ jobs:
|
||||
node-version: lts/hydrogen
|
||||
- when: #Only install chrome-beta when running the 'full' suite to save $$$
|
||||
condition:
|
||||
equal: ['full', <<parameters.suite>>]
|
||||
equal: ["full", <<parameters.suite>>]
|
||||
steps:
|
||||
- run: npx playwright install chrome-beta
|
||||
- run:
|
||||
@ -259,6 +259,8 @@ workflows:
|
||||
- visual-a11y:
|
||||
name: visual-a11y-ci
|
||||
suite: ci
|
||||
- perf-test
|
||||
- mem-test
|
||||
|
||||
the-nightly: #These jobs do not run on PRs, but against master at night
|
||||
jobs:
|
||||
@ -282,7 +284,7 @@ workflows:
|
||||
- e2e-couchdb
|
||||
triggers:
|
||||
- schedule:
|
||||
cron: '0 0 * * *'
|
||||
cron: "0 0 * * *"
|
||||
filters:
|
||||
branches:
|
||||
only:
|
||||
|
@ -20,6 +20,13 @@ module.exports = {
|
||||
'playwright/no-get-by-title': 'error',
|
||||
'playwright/prefer-comparison-matcher': 'error'
|
||||
}
|
||||
},
|
||||
{
|
||||
// Disable no-raw-locators for .contract.perf.spec.js files until https://github.com/grafana/xk6-browser/issues/1226
|
||||
files: ['**/*.contract.perf.spec.js'],
|
||||
rules: {
|
||||
'playwright/no-raw-locators': 'off'
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
|
@ -130,7 +130,7 @@ test.describe('Persistence operations @addInit', () => {
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('Persistence operations @couchdb', () => {
|
||||
test.describe('Persistence operations @couchdb @network', () => {
|
||||
test.use({ failOnConsoleError: false });
|
||||
test('Editing object properties should generate a single persistence operation', async ({
|
||||
page
|
||||
@ -170,7 +170,7 @@ test.describe('Persistence operations @couchdb', () => {
|
||||
})
|
||||
.toEqual(1);
|
||||
});
|
||||
test('Can create an object after a conflict error @couchdb @2p', async ({
|
||||
test('Can create an object after a conflict error @couchdb @network @2p', async ({
|
||||
page,
|
||||
openmctConfig
|
||||
}) => {
|
||||
|
@ -429,7 +429,7 @@ test.describe('Display Layout', () => {
|
||||
await expect(page.getByText('2021-12-30 01:11:00.000Z')).toBeHidden();
|
||||
});
|
||||
|
||||
test('When multiple plots are contained in a layout, we only ask for annotations once @couchdb', async ({
|
||||
test('When multiple plots are contained in a layout, we only ask for annotations once @couchdb @network', async ({
|
||||
page
|
||||
}) => {
|
||||
await setFixedTimeMode(page);
|
||||
|
@ -24,20 +24,26 @@
|
||||
This test suite is dedicated to tests which verify the basic operations surrounding Notebooks with CouchDB.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Disable no-networkidle eslint rule until we can engineer more deterministic network-event
|
||||
* driven tests.
|
||||
*/
|
||||
/* eslint-disable playwright/no-networkidle */
|
||||
|
||||
import { createDomainObjectWithDefaults } from '../../../../appActions.js';
|
||||
import * as nbUtils from '../../../../helper/notebookUtils.js';
|
||||
import { expect, test } from '../../../../pluginFixtures.js';
|
||||
|
||||
test.describe('Notebook Tests with CouchDB @couchdb', () => {
|
||||
test.describe('Notebook Tests with CouchDB @couchdb @network', () => {
|
||||
let testNotebook;
|
||||
|
||||
test.beforeEach(async ({ page }) => {
|
||||
//Navigate to baseURL
|
||||
await page.goto('./', { waitUntil: 'domcontentloaded' });
|
||||
// Navigate to baseURL
|
||||
await page.goto('./', { waitUntil: 'networkidle' });
|
||||
|
||||
// Create Notebook
|
||||
testNotebook = await createDomainObjectWithDefaults(page, { type: 'Notebook' });
|
||||
await page.goto(testNotebook.url, { waitUntil: 'domcontentloaded' });
|
||||
await page.goto(testNotebook.url, { waitUntil: 'networkidle' });
|
||||
});
|
||||
|
||||
test('Inspect Notebook Entry Network Requests', async ({ page }) => {
|
||||
@ -58,7 +64,7 @@ test.describe('Notebook Tests with CouchDB @couchdb', () => {
|
||||
page.getByLabel('Add Page').click()
|
||||
]);
|
||||
// Ensures that there are no other network requests
|
||||
await page.waitForLoadState('domcontentloaded');
|
||||
await page.waitForLoadState('networkidle');
|
||||
|
||||
// Assert that only two requests are made
|
||||
// Network Requests are:
|
||||
@ -77,7 +83,7 @@ test.describe('Notebook Tests with CouchDB @couchdb', () => {
|
||||
// 2) The shared worker event from 👆 POST request
|
||||
notebookElementsRequests = [];
|
||||
await nbUtils.enterTextEntry(page, 'First Entry');
|
||||
await page.waitForLoadState('domcontentloaded');
|
||||
await page.waitForLoadState('networkidle');
|
||||
expect(notebookElementsRequests.length).toBeLessThanOrEqual(2);
|
||||
|
||||
// Add some tags
|
||||
@ -141,7 +147,7 @@ test.describe('Notebook Tests with CouchDB @couchdb', () => {
|
||||
// 4) The shared worker event from 👆 POST request
|
||||
notebookElementsRequests = [];
|
||||
await nbUtils.enterTextEntry(page, 'Fourth Entry');
|
||||
page.waitForLoadState('domcontentloaded');
|
||||
page.waitForLoadState('networkidle');
|
||||
|
||||
expect(filterNonFetchRequests(notebookElementsRequests).length).toBeLessThanOrEqual(4);
|
||||
|
||||
@ -153,7 +159,7 @@ test.describe('Notebook Tests with CouchDB @couchdb', () => {
|
||||
// 4) The shared worker event from 👆 POST request
|
||||
notebookElementsRequests = [];
|
||||
await nbUtils.enterTextEntry(page, 'Fifth Entry');
|
||||
page.waitForLoadState('domcontentloaded');
|
||||
page.waitForLoadState('networkidle');
|
||||
|
||||
expect(filterNonFetchRequests(notebookElementsRequests).length).toBeLessThanOrEqual(4);
|
||||
|
||||
@ -164,7 +170,7 @@ test.describe('Notebook Tests with CouchDB @couchdb', () => {
|
||||
// 4) The shared worker event from 👆 POST request
|
||||
notebookElementsRequests = [];
|
||||
await nbUtils.enterTextEntry(page, 'Sixth Entry');
|
||||
page.waitForLoadState('domcontentloaded');
|
||||
page.waitForLoadState('networkidle');
|
||||
|
||||
expect(filterNonFetchRequests(notebookElementsRequests).length).toBeLessThanOrEqual(4);
|
||||
});
|
||||
@ -227,7 +233,7 @@ async function addTagAndAwaitNetwork(page, tagName) {
|
||||
page.locator(`[aria-label="Autocomplete Options"] >> text=${tagName}`).click(),
|
||||
expect(page.locator(`[aria-label="Tag"]:has-text("${tagName}")`)).toBeVisible()
|
||||
]);
|
||||
await page.waitForLoadState('domcontentloaded');
|
||||
await page.waitForLoadState('networkidle');
|
||||
}
|
||||
|
||||
/**
|
||||
@ -246,5 +252,5 @@ async function removeTagAndAwaitNetwork(page, tagName) {
|
||||
)
|
||||
]);
|
||||
await expect(page.locator(`[aria-label="Tag"]:has-text("${tagName}")`)).toBeHidden();
|
||||
await page.waitForLoadState('domcontentloaded');
|
||||
await page.waitForLoadState('networkidle');
|
||||
}
|
||||
|
@ -57,7 +57,7 @@ test.describe('Tabs View', () => {
|
||||
await page.goto(tabsView.url);
|
||||
|
||||
// select first tab
|
||||
await page.getByLabel(`${table.name} tab`, { exact: true }).click();
|
||||
await page.getByLabel(`${table.name} tab - selected`, { exact: true }).click();
|
||||
// ensure table header visible
|
||||
await expect(page.getByRole('searchbox', { name: 'message filter input' })).toBeVisible();
|
||||
|
||||
@ -92,6 +92,38 @@ test.describe('Tabs View', () => {
|
||||
// no canvas (i.e., sine wave generator) in the document should be visible
|
||||
await expect(page.locator('canvas[id=webglContext]')).toBeHidden();
|
||||
});
|
||||
|
||||
test('Changing the displayed tab should not be persisted if the view is locked', async ({
|
||||
page
|
||||
}) => {
|
||||
await page.goto(tabsView.url);
|
||||
//lock the view
|
||||
await page.getByLabel('Unlocked for editing, click to lock.', { exact: true }).click();
|
||||
// get the initial tab index
|
||||
const initialTab = page.getByLabel(/- selected/);
|
||||
// switch to a different tab in the view
|
||||
const swgTab = page.getByLabel(`${sineWaveGenerator.name} tab`, { exact: true });
|
||||
await swgTab.click();
|
||||
await page.getByLabel(`${sineWaveGenerator.name} Object View`).isVisible();
|
||||
// navigate away from the tabbed view and back
|
||||
await page.getByRole('treeitem', { name: 'My Items' }).click();
|
||||
await page.goto(tabsView.url);
|
||||
// check that the initial tab is displayed
|
||||
const lockedSelectedTab = page.getByLabel(/- selected/);
|
||||
await expect(lockedSelectedTab).toHaveText(await initialTab.textContent());
|
||||
|
||||
//unlock the view
|
||||
await page.getByLabel('Locked for editing. Click to unlock.', { exact: true }).click();
|
||||
// switch to a different tab in the view
|
||||
await swgTab.click();
|
||||
await page.getByLabel(`${sineWaveGenerator.name} Object View`).isVisible();
|
||||
// navigate away from the tabbed view and back
|
||||
await page.getByRole('treeitem', { name: 'My Items' }).click();
|
||||
await page.goto(tabsView.url);
|
||||
// check that the newly selected tab is displayed
|
||||
const unlockedSelectedTab = page.getByLabel(/- selected/);
|
||||
await expect(unlockedSelectedTab).toBeVisible();
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('Tabs View CRUD', () => {
|
||||
|
@ -168,7 +168,7 @@ test.describe('Grand Search', () => {
|
||||
await expect(page.getByText('No results found')).toBeVisible();
|
||||
});
|
||||
|
||||
test('Validate single object in search result @couchdb', async ({ page }) => {
|
||||
test('Validate single object in search result @couchdb @network', async ({ page }) => {
|
||||
// Create a folder object
|
||||
const folderName = uuid();
|
||||
await createDomainObjectWithDefaults(page, {
|
||||
@ -191,7 +191,7 @@ test.describe('Grand Search', () => {
|
||||
await expect(searchResults).toContainText(folderName);
|
||||
});
|
||||
|
||||
test('Search results are debounced @couchdb', async ({ page }) => {
|
||||
test('Search results are debounced @couchdb @network', async ({ page }) => {
|
||||
test.info().annotations.push({
|
||||
type: 'issue',
|
||||
description: 'https://github.com/nasa/openmct/issues/6179'
|
||||
@ -221,7 +221,9 @@ test.describe('Grand Search', () => {
|
||||
await expect(page.getByRole('list', { name: 'Object Results' })).toContainText('Clock A');
|
||||
});
|
||||
|
||||
test('Slowly typing after search debounce will abort requests @couchdb', async ({ page }) => {
|
||||
test('Slowly typing after search debounce will abort requests @couchdb @network', async ({
|
||||
page
|
||||
}) => {
|
||||
let requestWasAborted = false;
|
||||
await createObjectsForSearch(page);
|
||||
page.on('requestfailed', (request) => {
|
||||
|
@ -28,7 +28,7 @@ test.describe('Main Tree', () => {
|
||||
await page.goto('./', { waitUntil: 'domcontentloaded' });
|
||||
});
|
||||
|
||||
test('Creating a child object within a folder and immediately opening it shows the created object in the tree @couchdb', async ({
|
||||
test('Creating a child object within a folder and immediately opening it shows the created object in the tree @couchdb @network', async ({
|
||||
page
|
||||
}) => {
|
||||
test.info().annotations.push({
|
||||
@ -88,7 +88,7 @@ test.describe('Main Tree', () => {
|
||||
).toBeVisible();
|
||||
});
|
||||
|
||||
test('Creating a child object on one tab and expanding its parent on the other shows the correct composition @couchdb @2p', async ({
|
||||
test('Creating a child object on one tab and expanding its parent on the other shows the correct composition @couchdb @network @2p', async ({
|
||||
page
|
||||
}) => {
|
||||
test.info().annotations.push({
|
||||
@ -187,7 +187,7 @@ test.describe('Main Tree', () => {
|
||||
]);
|
||||
});
|
||||
});
|
||||
test('Opening and closing an item before the request has been fulfilled will abort the request @couchdb', async ({
|
||||
test('Opening and closing an item before the request has been fulfilled will abort the request @couchdb @network', async ({
|
||||
page
|
||||
}) => {
|
||||
let requestWasAborted = false;
|
||||
|
@ -164,7 +164,7 @@ test.describe('Performance tests', () => {
|
||||
await page.evaluate(() => window.performance.mark('background-image-visible'));
|
||||
|
||||
// Get Current number of images in thumbstrip
|
||||
await page.locator('.c-imagery__thumb').waitFor({ state: 'visible' });
|
||||
await expect(page.locator('.c-imagery__thumb').last()).toBeInViewport();
|
||||
const thumbCount = await page.locator('.c-imagery__thumb').count();
|
||||
console.log('number of thumbs rendered ' + thumbCount);
|
||||
await page.locator('.c-imagery__thumb').last().click();
|
||||
|
@ -64,7 +64,7 @@ test.describe('Tabs View', () => {
|
||||
page.goto(tabsView.url);
|
||||
|
||||
// select first tab
|
||||
await page.getByLabel(`${table.name} tab`, { exact: true }).click();
|
||||
await page.getByLabel(`${table.name} tab - selected`, { exact: true }).click();
|
||||
// ensure table header visible
|
||||
await expect(page.getByRole('searchbox', { name: 'message filter input' })).toBeVisible();
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "openmct",
|
||||
"version": "4.0.0-next",
|
||||
"version": "4.0.0",
|
||||
"description": "The Open MCT core platform",
|
||||
"module": "dist/openmct.js",
|
||||
"main": "dist/openmct.js",
|
||||
@ -160,4 +160,4 @@
|
||||
"keywords": [
|
||||
"nasa"
|
||||
]
|
||||
}
|
||||
}
|
@ -231,26 +231,20 @@ export default class TelemetryAPI {
|
||||
* @returns {TelemetryRequestOptions} the options, with defaults filled in
|
||||
*/
|
||||
standardizeRequestOptions(options = {}) {
|
||||
if (!Object.hasOwn(options, 'start')) {
|
||||
const bounds = options.timeContext?.getBounds();
|
||||
if (bounds?.start) {
|
||||
options.start = options.timeContext.getBounds().start;
|
||||
} else {
|
||||
options.start = this.openmct.time.getBounds().start;
|
||||
}
|
||||
}
|
||||
|
||||
if (!Object.hasOwn(options, 'end')) {
|
||||
const bounds = options.timeContext?.getBounds();
|
||||
if (bounds?.end) {
|
||||
options.end = options.timeContext.getBounds().end;
|
||||
} else {
|
||||
options.end = this.openmct.time.getBounds().end;
|
||||
}
|
||||
if (!Object.hasOwn(options, 'timeContext')) {
|
||||
options.timeContext = this.openmct.time;
|
||||
}
|
||||
|
||||
if (!Object.hasOwn(options, 'domain')) {
|
||||
options.domain = this.openmct.time.getTimeSystem().key;
|
||||
options.domain = options.timeContext.getTimeSystem().key;
|
||||
}
|
||||
|
||||
if (!Object.hasOwn(options, 'start')) {
|
||||
options.start = options.timeContext.getBounds().start;
|
||||
}
|
||||
|
||||
if (!Object.hasOwn(options, 'end')) {
|
||||
options.end = options.timeContext.getBounds().end;
|
||||
}
|
||||
|
||||
return options;
|
||||
|
@ -269,36 +269,40 @@ describe('Telemetry API', () => {
|
||||
|
||||
await telemetryAPI.request(domainObject);
|
||||
const { signal } = new AbortController();
|
||||
expect(telemetryProvider.supportsRequest).toHaveBeenCalledWith(jasmine.any(Object), {
|
||||
expect(telemetryProvider.supportsRequest).toHaveBeenCalledWith(domainObject, {
|
||||
signal,
|
||||
start: 0,
|
||||
end: 1,
|
||||
domain: 'system'
|
||||
domain: 'system',
|
||||
timeContext: openmct.time
|
||||
});
|
||||
|
||||
expect(telemetryProvider.request).toHaveBeenCalledWith(jasmine.any(Object), {
|
||||
expect(telemetryProvider.request).toHaveBeenCalledWith(domainObject, {
|
||||
signal,
|
||||
start: 0,
|
||||
end: 1,
|
||||
domain: 'system'
|
||||
domain: 'system',
|
||||
timeContext: openmct.time
|
||||
});
|
||||
|
||||
telemetryProvider.supportsRequest.calls.reset();
|
||||
telemetryProvider.request.calls.reset();
|
||||
|
||||
await telemetryAPI.request(domainObject, {});
|
||||
expect(telemetryProvider.supportsRequest).toHaveBeenCalledWith(jasmine.any(Object), {
|
||||
expect(telemetryProvider.supportsRequest).toHaveBeenCalledWith(domainObject, {
|
||||
signal,
|
||||
start: 0,
|
||||
end: 1,
|
||||
domain: 'system'
|
||||
domain: 'system',
|
||||
timeContext: openmct.time
|
||||
});
|
||||
|
||||
expect(telemetryProvider.request).toHaveBeenCalledWith(jasmine.any(Object), {
|
||||
expect(telemetryProvider.request).toHaveBeenCalledWith(domainObject, {
|
||||
signal,
|
||||
start: 0,
|
||||
end: 1,
|
||||
domain: 'system'
|
||||
domain: 'system',
|
||||
timeContext: openmct.time
|
||||
});
|
||||
});
|
||||
|
||||
@ -313,18 +317,20 @@ describe('Telemetry API', () => {
|
||||
domain: 'someDomain'
|
||||
});
|
||||
const { signal } = new AbortController();
|
||||
expect(telemetryProvider.supportsRequest).toHaveBeenCalledWith(jasmine.any(Object), {
|
||||
expect(telemetryProvider.supportsRequest).toHaveBeenCalledWith(domainObject, {
|
||||
start: 20,
|
||||
end: 30,
|
||||
domain: 'someDomain',
|
||||
signal
|
||||
signal,
|
||||
timeContext: openmct.time
|
||||
});
|
||||
|
||||
expect(telemetryProvider.request).toHaveBeenCalledWith(jasmine.any(Object), {
|
||||
expect(telemetryProvider.request).toHaveBeenCalledWith(domainObject, {
|
||||
start: 20,
|
||||
end: 30,
|
||||
domain: 'someDomain',
|
||||
signal
|
||||
signal,
|
||||
timeContext: openmct.time
|
||||
});
|
||||
});
|
||||
describe('telemetry batching support', () => {
|
||||
|
@ -62,9 +62,6 @@ export default class TelemetryCollection extends EventEmitter {
|
||||
this.futureBuffer = [];
|
||||
this.parseTime = undefined;
|
||||
this.metadata = this.openmct.telemetry.getMetadata(domainObject);
|
||||
if (!Object.hasOwn(options, 'timeContext')) {
|
||||
options.timeContext = this.openmct.time;
|
||||
}
|
||||
this.options = options;
|
||||
this.unsubscribe = undefined;
|
||||
this.pageState = undefined;
|
||||
@ -84,6 +81,9 @@ export default class TelemetryCollection extends EventEmitter {
|
||||
this._error(LOADED_ERROR);
|
||||
}
|
||||
|
||||
if (!Object.hasOwn(this.options, 'timeContext')) {
|
||||
this.options.timeContext = this.openmct.time;
|
||||
}
|
||||
this._setTimeSystem(this.options.timeContext.getTimeSystem());
|
||||
this.lastBounds = this.options.timeContext.getBounds();
|
||||
this._watchBounds();
|
||||
@ -128,7 +128,7 @@ export default class TelemetryCollection extends EventEmitter {
|
||||
* @private
|
||||
*/
|
||||
async _requestHistoricalTelemetry() {
|
||||
let options = this.openmct.telemetry.standardizeRequestOptions({ ...this.options });
|
||||
const options = this.openmct.telemetry.standardizeRequestOptions({ ...this.options });
|
||||
const historicalProvider = this.openmct.telemetry.findRequestProvider(
|
||||
this.domainObject,
|
||||
options
|
||||
|
@ -321,8 +321,16 @@ class IndependentTimeContext extends TimeContext {
|
||||
return this.upstreamTimeContext.setMode(...arguments);
|
||||
}
|
||||
|
||||
if (mode === MODES.realtime && this.activeClock === undefined) {
|
||||
throw `Unknown clock. Has a clock been registered with 'addClock'?`;
|
||||
if (mode === MODES.realtime) {
|
||||
// TODO: This should probably happen up front in creating an independent time context
|
||||
// TODO: not just in time every time setMode is called
|
||||
if (this.activeClock === undefined) {
|
||||
this.activeClock = this.globalTimeContext.getClock();
|
||||
}
|
||||
|
||||
if (this.activeClock === undefined) {
|
||||
throw `Unknown clock. Has a clock been registered with 'addClock'?`;
|
||||
}
|
||||
}
|
||||
|
||||
if (mode !== this.mode) {
|
||||
|
@ -134,30 +134,30 @@ class TimeAPI extends GlobalTimeContext {
|
||||
|
||||
/**
|
||||
* Get or set an independent time context which follows the TimeAPI timeSystem,
|
||||
* but with different offsets for a given domain object
|
||||
* @param {string} key The identifier key of the domain object these offsets are set for
|
||||
* @param {ClockOffsets | TimeConductorBounds} value This maintains a sliding time window of a fixed width that automatically updates
|
||||
* @param {key | string} clockKey the real time clock key currently in use
|
||||
* but with different bounds for a given domain object
|
||||
* @param {string} keyString The keyString identifier of the domain object these offsets are set for
|
||||
* @param {TimeConductorBounds | ClockOffsets} boundsOrOffsets either bounds if in fixed mode, or offsets if in realtime mode
|
||||
* @param {string} clockKey the key for the real time clock to use
|
||||
*/
|
||||
addIndependentContext(key, value, clockKey) {
|
||||
let timeContext = this.getIndependentContext(key);
|
||||
addIndependentContext(keyString, boundsOrOffsets, clockKey) {
|
||||
let timeContext = this.getIndependentContext(keyString);
|
||||
|
||||
//stop following upstream time context since the view has it's own
|
||||
timeContext.resetContext();
|
||||
|
||||
if (clockKey) {
|
||||
timeContext.setClock(clockKey);
|
||||
timeContext.setMode(REALTIME_MODE_KEY, value);
|
||||
timeContext.setMode(REALTIME_MODE_KEY, boundsOrOffsets);
|
||||
} else {
|
||||
timeContext.setMode(FIXED_MODE_KEY, value);
|
||||
timeContext.setMode(FIXED_MODE_KEY, boundsOrOffsets);
|
||||
}
|
||||
|
||||
// Notify any nested views to update, pass in the viewKey so that particular view can skip getting an upstream context
|
||||
this.emit('refreshContext', key);
|
||||
this.emit('refreshContext', keyString);
|
||||
|
||||
return () => {
|
||||
//follow any upstream time context
|
||||
this.emit('removeOwnContext', key);
|
||||
this.emit('removeOwnContext', keyString);
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -196,7 +196,7 @@ class TimeContext extends EventEmitter {
|
||||
} else if (bounds.start > bounds.end) {
|
||||
return {
|
||||
valid: false,
|
||||
message: 'Specified start date exceeds end bound'
|
||||
message: 'Start bound exceeds end bound'
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -69,7 +69,6 @@ const INNER_TEXT_PADDING = 15;
|
||||
const TEXT_LEFT_PADDING = 5;
|
||||
const ROW_PADDING = 5;
|
||||
const SWIMLANE_PADDING = 3;
|
||||
const RESIZE_POLL_INTERVAL = 200;
|
||||
const ROW_HEIGHT = 22;
|
||||
const MAX_TEXT_WIDTH = 300;
|
||||
const MIN_ACTIVITY_WIDTH = 2;
|
||||
@ -143,13 +142,15 @@ export default {
|
||||
this.canvasContext = canvas.getContext('2d');
|
||||
this.setDimensions();
|
||||
this.setTimeContext();
|
||||
this.resizeTimer = setInterval(this.resize, RESIZE_POLL_INTERVAL);
|
||||
this.handleConfigurationChange(this.configuration);
|
||||
this.planViewConfiguration.on('change', this.handleConfigurationChange);
|
||||
this.loadComposition();
|
||||
|
||||
this.resizeObserver = new ResizeObserver(this.resize);
|
||||
this.resizeObserver.observe(this.$refs.plan);
|
||||
},
|
||||
beforeUnmount() {
|
||||
clearInterval(this.resizeTimer);
|
||||
this.resizeObserver.disconnect();
|
||||
this.stopFollowingTimeContext();
|
||||
if (this.unlisten) {
|
||||
this.unlisten();
|
||||
|
@ -575,6 +575,7 @@ export default {
|
||||
this.buildCanvasElements();
|
||||
this.drawAPI = DrawLoader.getFallbackDrawAPI(this.canvas, this.overlay);
|
||||
this.$emit('plot-reinitialize-canvas');
|
||||
console.warn(`📈 fallback to 2D canvas`);
|
||||
},
|
||||
removeChartElement(series) {
|
||||
const elements = this.seriesElements.get(toRaw(series));
|
||||
|
@ -152,15 +152,18 @@ export default class RemoteClock extends DefaultClock {
|
||||
*/
|
||||
#waitForReady() {
|
||||
const waitForInitialTick = (resolve) => {
|
||||
if (this.lastTick > 0) {
|
||||
const offsets = this.openmct.time.getClockOffsets();
|
||||
resolve({
|
||||
start: this.lastTick + offsets.start,
|
||||
end: this.lastTick + offsets.end
|
||||
});
|
||||
} else {
|
||||
setTimeout(() => waitForInitialTick(resolve), 100);
|
||||
}
|
||||
const tickListener = () => {
|
||||
if (this.lastTick > 0) {
|
||||
const offsets = this.openmct.time.getClockOffsets();
|
||||
this.openmct.time.off('tick', tickListener); // Unregister the tick listener
|
||||
resolve({
|
||||
start: this.lastTick + offsets.start,
|
||||
end: this.lastTick + offsets.end
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
this.openmct.time.on('tick', tickListener);
|
||||
};
|
||||
|
||||
return new Promise(waitForInitialTick);
|
||||
|
@ -20,16 +20,43 @@
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
|
||||
/**
|
||||
* Intercepts requests to ensure the remote clock is ready.
|
||||
*
|
||||
* @param {import('../../openmct').OpenMCT} openmct - The OpenMCT instance.
|
||||
* @param {import('../../openmct').Identifier} _remoteClockIdentifier - The identifier for the remote clock.
|
||||
* @param {Function} waitForBounds - A function that returns a promise resolving to the initial bounds.
|
||||
* @returns {Object} The request interceptor.
|
||||
*/
|
||||
function remoteClockRequestInterceptor(openmct, _remoteClockIdentifier, waitForBounds) {
|
||||
let remoteClockLoaded = false;
|
||||
|
||||
return {
|
||||
appliesTo: () => {
|
||||
/**
|
||||
* Determines if the interceptor applies to the given request.
|
||||
*
|
||||
* @param {Object} _ - Unused parameter.
|
||||
* @param {import('../../api/telemetry/TelemetryAPI').TelemetryRequestOptions} request - The request object.
|
||||
* @returns {boolean} True if the interceptor applies, false otherwise.
|
||||
*/
|
||||
appliesTo: (_, request) => {
|
||||
// Get the activeClock from the Global Time Context
|
||||
/** @type {import("../../api/time/TimeContext").default} */
|
||||
const { activeClock } = openmct.time;
|
||||
|
||||
// this type of request does not rely on clock having bounds
|
||||
if (request.strategy === 'latest' && request.timeContext.isRealTime()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return activeClock?.key === 'remote-clock' && !remoteClockLoaded;
|
||||
},
|
||||
/**
|
||||
* Invokes the interceptor to modify the request.
|
||||
*
|
||||
* @param {Object} request - The request object.
|
||||
* @returns {Promise<Object>} The modified request object.
|
||||
*/
|
||||
invoke: async (request) => {
|
||||
const timeContext = request?.timeContext ?? openmct.time;
|
||||
|
||||
|
@ -38,11 +38,11 @@
|
||||
v-for="(tab, index) in tabsList"
|
||||
:ref="tab.keyString"
|
||||
:key="tab.keyString"
|
||||
:aria-label="`${tab.domainObject.name} tab`"
|
||||
:aria-label="`${tab.domainObject.name} tab${tab.keyString === currentTab.keyString ? ' - selected' : ''}`"
|
||||
class="c-tab c-tabs-view__tab js-tab"
|
||||
role="tab"
|
||||
:class="{
|
||||
'is-current': isCurrent(tab)
|
||||
'is-current': tab.keyString === currentTab.keyString
|
||||
}"
|
||||
@click="showTab(tab, index)"
|
||||
@mouseover.ctrl="showToolTip(tab)"
|
||||
@ -74,7 +74,7 @@
|
||||
:key="tab.keyString"
|
||||
:style="getTabStyles(tab)"
|
||||
class="c-tabs-view__object-holder"
|
||||
:class="{ 'c-tabs-view__object-holder--hidden': !isCurrent(tab) }"
|
||||
:class="{ 'c-tabs-view__object-holder--hidden': tab.keyString !== currentTab.keyString }"
|
||||
>
|
||||
<ObjectView
|
||||
v-if="shouldLoadTab(tab)"
|
||||
@ -353,7 +353,10 @@ export default {
|
||||
this.internalDomainObject = domainObject;
|
||||
},
|
||||
persistCurrentTabIndex(index) {
|
||||
this.openmct.objects.mutate(this.internalDomainObject, 'currentTabIndex', index);
|
||||
//only persist if the domain object is not locked. The object mutate API will deal with whether the object is persistable or not.
|
||||
if (!this.internalDomainObject.locked) {
|
||||
this.openmct.objects.mutate(this.internalDomainObject, 'currentTabIndex', index);
|
||||
}
|
||||
},
|
||||
storeCurrentTabIndexInURL(index) {
|
||||
let currentTabIndexInURL = this.openmct.router.getSearchParam(this.searchTabKey);
|
||||
|
@ -25,6 +25,7 @@ import _ from 'lodash';
|
||||
|
||||
import StalenessUtils from '../../utils/staleness.js';
|
||||
import TableRowCollection from './collections/TableRowCollection.js';
|
||||
import { MODE } from './constants.js';
|
||||
import TelemetryTableColumn from './TelemetryTableColumn.js';
|
||||
import TelemetryTableConfiguration from './TelemetryTableConfiguration.js';
|
||||
import TelemetryTableNameColumn from './TelemetryTableNameColumn.js';
|
||||
@ -119,7 +120,7 @@ export default class TelemetryTable extends EventEmitter {
|
||||
this.rowLimit = rowLimit;
|
||||
}
|
||||
|
||||
if (this.telemetryMode === 'performance') {
|
||||
if (this.telemetryMode === MODE.PERFORMANCE) {
|
||||
this.tableRows.setLimit(this.rowLimit);
|
||||
} else {
|
||||
this.tableRows.removeLimit();
|
||||
@ -129,14 +130,7 @@ export default class TelemetryTable extends EventEmitter {
|
||||
createTableRowCollections() {
|
||||
this.tableRows = new TableRowCollection();
|
||||
|
||||
//Fetch any persisted default sort
|
||||
let sortOptions = this.configuration.getConfiguration().sortOptions;
|
||||
|
||||
//If no persisted sort order, default to sorting by time system, descending.
|
||||
sortOptions = sortOptions || {
|
||||
key: this.openmct.time.getTimeSystem().key,
|
||||
direction: 'desc'
|
||||
};
|
||||
const sortOptions = this.configuration.getSortOptions();
|
||||
|
||||
this.updateRowLimit();
|
||||
|
||||
@ -171,11 +165,10 @@ export default class TelemetryTable extends EventEmitter {
|
||||
|
||||
this.removeTelemetryCollection(keyString);
|
||||
|
||||
let sortOptions = this.configuration.getConfiguration().sortOptions;
|
||||
requestOptions.order =
|
||||
sortOptions?.direction ?? (this.telemetryMode === 'performance' ? 'desc' : 'asc');
|
||||
let sortOptions = this.configuration.getSortOptions();
|
||||
requestOptions.order = sortOptions.direction;
|
||||
|
||||
if (this.telemetryMode === 'performance') {
|
||||
if (this.telemetryMode === MODE.PERFORMANCE) {
|
||||
requestOptions.size = this.rowLimit;
|
||||
requestOptions.enforceSize = true;
|
||||
}
|
||||
@ -442,12 +435,13 @@ export default class TelemetryTable extends EventEmitter {
|
||||
}
|
||||
|
||||
sortBy(sortOptions) {
|
||||
this.tableRows.sortBy(sortOptions);
|
||||
this.configuration.setSortOptions(sortOptions);
|
||||
|
||||
if (this.openmct.editor.isEditing()) {
|
||||
let configuration = this.configuration.getConfiguration();
|
||||
configuration.sortOptions = sortOptions;
|
||||
this.configuration.updateConfiguration(configuration);
|
||||
if (this.telemetryMode === MODE.PERFORMANCE) {
|
||||
this.tableRows.setSortOptions(sortOptions);
|
||||
this.clearAndResubscribe();
|
||||
} else {
|
||||
this.tableRows.sortBy(sortOptions);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -23,7 +23,11 @@
|
||||
import { EventEmitter } from 'eventemitter3';
|
||||
import _ from 'lodash';
|
||||
|
||||
import { ORDER } from './constants';
|
||||
|
||||
export default class TelemetryTableConfiguration extends EventEmitter {
|
||||
#sortOptions;
|
||||
|
||||
constructor(domainObject, openmct, options) {
|
||||
super();
|
||||
|
||||
@ -44,6 +48,26 @@ export default class TelemetryTableConfiguration extends EventEmitter {
|
||||
this.notPersistable = !this.openmct.objects.isPersistable(this.domainObject.identifier);
|
||||
}
|
||||
|
||||
getSortOptions() {
|
||||
return (
|
||||
this.#sortOptions ||
|
||||
this.getConfiguration().sortOptions || {
|
||||
key: this.openmct.time.getTimeSystem().key,
|
||||
direction: ORDER.DESCENDING
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
setSortOptions(sortOptions) {
|
||||
this.#sortOptions = sortOptions;
|
||||
|
||||
if (this.openmct.editor.isEditing()) {
|
||||
let configuration = this.getConfiguration();
|
||||
configuration.sortOptions = sortOptions;
|
||||
this.updateConfiguration(configuration);
|
||||
}
|
||||
}
|
||||
|
||||
getConfiguration() {
|
||||
let configuration = this.domainObject.configuration || {};
|
||||
configuration.hiddenColumns = configuration.hiddenColumns || {};
|
||||
|
@ -20,6 +20,8 @@
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
|
||||
import { MODE } from './constants.js';
|
||||
|
||||
export default function getTelemetryTableType(options) {
|
||||
let { telemetryMode, persistModeChange, rowLimit } = options;
|
||||
|
||||
@ -36,11 +38,11 @@ export default function getTelemetryTableType(options) {
|
||||
control: 'select',
|
||||
options: [
|
||||
{
|
||||
value: 'performance',
|
||||
value: MODE.PERFORMANCE,
|
||||
name: 'Limited (Performance) Mode'
|
||||
},
|
||||
{
|
||||
value: 'unlimited',
|
||||
value: MODE.UNLIMITED,
|
||||
name: 'Unlimited Mode'
|
||||
}
|
||||
],
|
||||
|
@ -22,6 +22,7 @@
|
||||
import { EventEmitter } from 'eventemitter3';
|
||||
import _ from 'lodash';
|
||||
|
||||
import { ORDER } from '../constants.js';
|
||||
/**
|
||||
* @constructor
|
||||
*/
|
||||
@ -149,13 +150,13 @@ export default class TableRowCollection extends EventEmitter {
|
||||
}
|
||||
|
||||
insertOrUpdateRows(rowsToAdd, addToBeginning) {
|
||||
rowsToAdd.forEach((row) => {
|
||||
rowsToAdd.forEach((row, addRowsIndex) => {
|
||||
const index = this.getInPlaceUpdateIndex(row);
|
||||
if (index > -1) {
|
||||
this.updateRowInPlace(row, index);
|
||||
} else {
|
||||
if (addToBeginning) {
|
||||
this.rows.unshift(row);
|
||||
this.rows.splice(addRowsIndex, 0, row);
|
||||
} else {
|
||||
this.rows.push(row);
|
||||
}
|
||||
@ -208,7 +209,7 @@ export default class TableRowCollection extends EventEmitter {
|
||||
const val1 = this.getValueForSortColumn(row1);
|
||||
const val2 = this.getValueForSortColumn(row2);
|
||||
|
||||
if (this.sortOptions.direction === 'asc') {
|
||||
if (this.sortOptions.direction === ORDER.ASCENDING) {
|
||||
return val1 <= val2 ? row1 : row2;
|
||||
} else {
|
||||
return val1 >= val2 ? row1 : row2;
|
||||
@ -272,7 +273,7 @@ export default class TableRowCollection extends EventEmitter {
|
||||
*/
|
||||
sortBy(sortOptions) {
|
||||
if (arguments.length > 0) {
|
||||
this.sortOptions = sortOptions;
|
||||
this.setSortOptions(sortOptions);
|
||||
this.rows = _.orderBy(
|
||||
this.rows,
|
||||
(row) => row.getParsedValue(sortOptions.key),
|
||||
@ -285,6 +286,10 @@ export default class TableRowCollection extends EventEmitter {
|
||||
return Object.assign({}, this.sortOptions);
|
||||
}
|
||||
|
||||
setSortOptions(sortOptions) {
|
||||
this.sortOptions = sortOptions;
|
||||
}
|
||||
|
||||
setColumnFilter(columnKey, filter) {
|
||||
filter = filter.trim().toLowerCase();
|
||||
let wasBlank = this.columnFilters[columnKey] === undefined;
|
||||
@ -373,7 +378,7 @@ export default class TableRowCollection extends EventEmitter {
|
||||
|
||||
getRows() {
|
||||
if (this.rowLimit && this.rows.length > this.rowLimit) {
|
||||
if (this.sortOptions.direction === 'desc') {
|
||||
if (this.sortOptions.direction === ORDER.DESCENDING) {
|
||||
return this.rows.slice(0, this.rowLimit);
|
||||
} else {
|
||||
return this.rows.slice(-this.rowLimit);
|
||||
|
@ -296,6 +296,7 @@ import ProgressBar from '../../../ui/components/ProgressBar.vue';
|
||||
import Search from '../../../ui/components/SearchComponent.vue';
|
||||
import ToggleSwitch from '../../../ui/components/ToggleSwitch.vue';
|
||||
import { useResizeObserver } from '../../../ui/composables/resize.js';
|
||||
import { MODE, ORDER } from '../constants.js';
|
||||
import SizingRow from './SizingRow.vue';
|
||||
import TableColumnHeader from './TableColumnHeader.vue';
|
||||
import TableFooterIndicator from './TableFooterIndicator.vue';
|
||||
@ -713,7 +714,7 @@ export default {
|
||||
sortBy(columnKey) {
|
||||
let timeSystemKey = this.openmct.time.getTimeSystem().key;
|
||||
|
||||
if (this.telemetryMode === 'performance' && columnKey !== timeSystemKey) {
|
||||
if (this.telemetryMode === MODE.PERFORMANCE && columnKey !== timeSystemKey) {
|
||||
this.confirmUnlimitedMode('Switch to Unlimited Telemetry and Sort', () => {
|
||||
this.initiateSort(columnKey);
|
||||
});
|
||||
@ -724,15 +725,15 @@ export default {
|
||||
initiateSort(columnKey) {
|
||||
// If sorting by the same column, flip the sort direction.
|
||||
if (this.sortOptions.key === columnKey) {
|
||||
if (this.sortOptions.direction === 'asc') {
|
||||
this.sortOptions.direction = 'desc';
|
||||
if (this.sortOptions.direction === ORDER.ASCENDING) {
|
||||
this.sortOptions.direction = ORDER.DESCENDING;
|
||||
} else {
|
||||
this.sortOptions.direction = 'asc';
|
||||
this.sortOptions.direction = ORDER.ASCENDING;
|
||||
}
|
||||
} else {
|
||||
this.sortOptions = {
|
||||
key: columnKey,
|
||||
direction: 'desc'
|
||||
direction: ORDER.DESCENDING
|
||||
};
|
||||
}
|
||||
|
||||
@ -751,7 +752,7 @@ export default {
|
||||
}
|
||||
},
|
||||
shouldAutoScroll() {
|
||||
if (this.sortOptions.direction === 'desc') {
|
||||
if (this.sortOptions.direction === ORDER.DESCENDING) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -844,7 +845,7 @@ export default {
|
||||
return justTheData;
|
||||
},
|
||||
exportAllDataAsCSV() {
|
||||
if (this.telemetryMode === 'performance') {
|
||||
if (this.telemetryMode === MODE.PERFORMANCE) {
|
||||
this.confirmUnlimitedMode('Switch to Unlimited Telemetry and Export', () => {
|
||||
const data = this.getTableRowData();
|
||||
|
||||
@ -1226,7 +1227,8 @@ export default {
|
||||
});
|
||||
},
|
||||
updateTelemetryMode() {
|
||||
this.telemetryMode = this.telemetryMode === 'unlimited' ? 'performance' : 'unlimited';
|
||||
this.telemetryMode =
|
||||
this.telemetryMode === MODE.UNLIMITED ? MODE.PERFORMANCE : MODE.UNLIMITED;
|
||||
|
||||
if (this.persistModeChange) {
|
||||
this.table.configuration.setTelemetryMode(this.telemetryMode);
|
||||
@ -1236,7 +1238,7 @@ export default {
|
||||
|
||||
const timeSystemKey = this.openmct.time.getTimeSystem().key;
|
||||
|
||||
if (this.telemetryMode === 'performance' && this.sortOptions.key !== timeSystemKey) {
|
||||
if (this.telemetryMode === MODE.PERFORMANCE && this.sortOptions.key !== timeSystemKey) {
|
||||
this.openmct.notifications.info(
|
||||
'Switched to Performance Mode: Table now sorted by time for optimized efficiency.'
|
||||
);
|
||||
|
@ -62,6 +62,8 @@
|
||||
<script>
|
||||
import _ from 'lodash';
|
||||
|
||||
import { MODE } from '../constants.js';
|
||||
|
||||
const FILTER_INDICATOR_LABEL = 'Filters:';
|
||||
const FILTER_INDICATOR_LABEL_MIXED = 'Mixed Filters:';
|
||||
const FILTER_INDICATOR_TITLE = 'Data filters are being applied to this view.';
|
||||
@ -81,7 +83,7 @@ export default {
|
||||
},
|
||||
telemetryMode: {
|
||||
type: String,
|
||||
default: 'performance'
|
||||
default: MODE.PERFORMANCE
|
||||
}
|
||||
},
|
||||
emits: ['telemetry-mode-change'],
|
||||
@ -103,7 +105,7 @@ export default {
|
||||
});
|
||||
},
|
||||
isUnlimitedMode() {
|
||||
return this.telemetryMode === 'unlimited';
|
||||
return this.telemetryMode === MODE.UNLIMITED;
|
||||
},
|
||||
label() {
|
||||
if (this.hasMixedFilters) {
|
||||
|
11
src/plugins/telemetryTable/constants.js
Normal file
11
src/plugins/telemetryTable/constants.js
Normal file
@ -0,0 +1,11 @@
|
||||
const ORDER = {
|
||||
ASCENDING: 'asc',
|
||||
DESCENDING: 'desc'
|
||||
};
|
||||
|
||||
const MODE = {
|
||||
PERFORMANCE: 'performance',
|
||||
UNLIMITED: 'unlimited'
|
||||
};
|
||||
|
||||
export { MODE, ORDER };
|
@ -20,13 +20,14 @@
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
|
||||
import { MODE } from './constants.js';
|
||||
import TableConfigurationViewProvider from './TableConfigurationViewProvider.js';
|
||||
import getTelemetryTableType from './TelemetryTableType.js';
|
||||
import TelemetryTableViewProvider from './TelemetryTableViewProvider.js';
|
||||
import TelemetryTableViewActions from './ViewActions.js';
|
||||
|
||||
export default function plugin(
|
||||
options = { telemetryMode: 'performance', persistModeChange: true, rowLimit: 50 }
|
||||
options = { telemetryMode: MODE.PERFORMANCE, persistModeChange: true, rowLimit: 50 }
|
||||
) {
|
||||
return function install(openmct) {
|
||||
openmct.objectViews.addProvider(new TelemetryTableViewProvider(openmct, options));
|
||||
|
@ -28,6 +28,7 @@ import {
|
||||
} from 'utils/testing';
|
||||
import { nextTick } from 'vue';
|
||||
|
||||
import { MODE } from './constants.js';
|
||||
import TablePlugin from './plugin.js';
|
||||
|
||||
class MockDataTransfer {
|
||||
@ -198,7 +199,7 @@ describe('the plugin', () => {
|
||||
},
|
||||
persistModeChange: true,
|
||||
rowLimit: 50,
|
||||
telemetryMode: 'performance'
|
||||
telemetryMode: MODE.PERFORMANCE
|
||||
}
|
||||
};
|
||||
const testTelemetry = [
|
||||
|
@ -41,21 +41,16 @@ import { TIME_CONTEXT_EVENTS } from '../../api/time/constants.js';
|
||||
import utcMultiTimeFormat from './utcMultiTimeFormat.js';
|
||||
|
||||
const PADDING = 1;
|
||||
const DEFAULT_DURATION_FORMATTER = 'duration';
|
||||
const PIXELS_PER_TICK = 100;
|
||||
const PIXELS_PER_TICK_WIDE = 200;
|
||||
|
||||
export default {
|
||||
inject: ['openmct'],
|
||||
inject: ['openmct', 'isFixedTimeMode'],
|
||||
props: {
|
||||
viewBounds: {
|
||||
type: Object,
|
||||
required: true
|
||||
},
|
||||
isFixed: {
|
||||
type: Boolean,
|
||||
required: true
|
||||
},
|
||||
altPressed: {
|
||||
type: Boolean,
|
||||
required: true
|
||||
@ -198,22 +193,8 @@ export default {
|
||||
this.axisElement.call(this.xAxis);
|
||||
this.setScale();
|
||||
},
|
||||
getActiveFormatter() {
|
||||
let timeSystem = this.openmct.time.getTimeSystem();
|
||||
|
||||
if (this.isFixed) {
|
||||
return this.getFormatter(timeSystem.timeFormat);
|
||||
} else {
|
||||
return this.getFormatter(timeSystem.durationFormat || DEFAULT_DURATION_FORMATTER);
|
||||
}
|
||||
},
|
||||
getFormatter(key) {
|
||||
return this.openmct.telemetry.getValueFormatter({
|
||||
format: key
|
||||
}).formatter;
|
||||
},
|
||||
dragStart($event) {
|
||||
if (this.isFixed) {
|
||||
if (this.isFixedTimeMode) {
|
||||
this.dragStartX = $event.clientX;
|
||||
|
||||
if (this.altPressed) {
|
||||
|
@ -23,8 +23,8 @@
|
||||
<div v-if="readOnly === false" ref="clockButton" class="c-tc-input-popup__options">
|
||||
<div class="c-menu-button c-ctrl-wrapper c-ctrl-wrapper--menus-left">
|
||||
<button
|
||||
class="c-button--menu js-clock-button"
|
||||
:class="[buttonCssClass, selectedClock.cssClass]"
|
||||
class="c-button--menu js-clock-button c-icon-button"
|
||||
:class="selectedClock.cssClass"
|
||||
aria-label="Time Conductor Clock Menu"
|
||||
@click.prevent.stop="showClocksMenu"
|
||||
>
|
||||
@ -38,18 +38,8 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { TIME_CONTEXT_EVENTS } from '../../api/time/constants.js';
|
||||
import clockMixin from './clock-mixin.js';
|
||||
|
||||
export default {
|
||||
mixins: [clockMixin],
|
||||
inject: {
|
||||
openmct: 'openmct',
|
||||
configuration: {
|
||||
from: 'configuration',
|
||||
default: undefined
|
||||
}
|
||||
},
|
||||
inject: ['openmct', 'configuration', 'clock', 'getAllClockMetadata', 'getClockMetadata'],
|
||||
props: {
|
||||
readOnly: {
|
||||
type: Boolean,
|
||||
@ -58,21 +48,13 @@ export default {
|
||||
}
|
||||
}
|
||||
},
|
||||
emits: ['clock-updated'],
|
||||
data() {
|
||||
const activeClock = this.getActiveClock();
|
||||
|
||||
return {
|
||||
selectedClock: activeClock ? this.getClockMetadata(activeClock) : undefined,
|
||||
clocks: []
|
||||
};
|
||||
computed: {
|
||||
selectedClock() {
|
||||
return this.getClockMetadata(this.clock);
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.loadClocks(this.configuration.menuOptions);
|
||||
this.openmct.time.on(TIME_CONTEXT_EVENTS.clockChanged, this.setViewFromClock);
|
||||
},
|
||||
unmounted() {
|
||||
this.openmct.time.off(TIME_CONTEXT_EVENTS.clockChanged, this.setViewFromClock);
|
||||
this.clocks = this.getAllClockMetadata(this.configuration.menuOptions);
|
||||
},
|
||||
methods: {
|
||||
showClocksMenu() {
|
||||
@ -86,52 +68,6 @@ export default {
|
||||
};
|
||||
|
||||
this.dismiss = this.openmct.menus.showSuperMenu(x, y, this.clocks, menuOptions);
|
||||
},
|
||||
setClock(clockKey) {
|
||||
const option = {
|
||||
clockKey
|
||||
};
|
||||
let configuration = this.getMatchingConfig({
|
||||
clock: clockKey,
|
||||
timeSystem: this.openmct.time.getTimeSystem().key
|
||||
});
|
||||
|
||||
if (configuration === undefined) {
|
||||
configuration = this.getMatchingConfig({
|
||||
clock: clockKey
|
||||
});
|
||||
|
||||
option.timeSystem = configuration.timeSystem;
|
||||
option.bounds = configuration.bounds;
|
||||
|
||||
// this.openmct.time.setTimeSystem(configuration.timeSystem, configuration.bounds);
|
||||
}
|
||||
|
||||
const offsets = this.openmct.time.getClockOffsets() ?? configuration.clockOffsets;
|
||||
option.offsets = offsets;
|
||||
|
||||
this.$emit('clock-updated', option);
|
||||
},
|
||||
getMatchingConfig(options) {
|
||||
const matchers = {
|
||||
clock(config) {
|
||||
return options.clock === config.clock;
|
||||
},
|
||||
timeSystem(config) {
|
||||
return options.timeSystem === config.timeSystem;
|
||||
}
|
||||
};
|
||||
|
||||
function configMatches(config) {
|
||||
return Object.keys(options).reduce((match, option) => {
|
||||
return match && matchers[option](config);
|
||||
}, true);
|
||||
}
|
||||
|
||||
return this.configuration.menuOptions.filter(configMatches)[0];
|
||||
},
|
||||
setViewFromClock(clock) {
|
||||
this.selectedClock = this.getClockMetadata(clock);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@ -27,62 +27,50 @@
|
||||
{ 'is-zooming': isZooming },
|
||||
{ 'is-panning': isPanning },
|
||||
{ 'alt-pressed': altPressed },
|
||||
isFixed ? 'is-fixed-mode' : 'is-realtime-mode'
|
||||
isFixedTimeMode ? 'is-fixed-mode' : 'is-realtime-mode'
|
||||
]"
|
||||
>
|
||||
<ConductorModeIcon class="c-conductor__mode-icon" />
|
||||
<div class="c-compact-tc__setting-value u-fade-truncate">
|
||||
<ConductorMode :mode="mode" :read-only="true" />
|
||||
<ConductorMode :read-only="true" />
|
||||
<ConductorClock :read-only="true" />
|
||||
<ConductorTimeSystem :read-only="true" />
|
||||
</div>
|
||||
<ConductorInputsFixed v-if="isFixed" :input-bounds="viewBounds" :read-only="true" />
|
||||
<ConductorInputsFixed v-if="isFixedTimeMode" :input-bounds="viewBounds" :read-only="true" />
|
||||
<ConductorInputsRealtime v-else :input-bounds="viewBounds" :read-only="true" />
|
||||
<ConductorAxis
|
||||
v-if="isFixed"
|
||||
v-if="isFixedTimeMode"
|
||||
class="c-conductor__ticks"
|
||||
:view-bounds="viewBounds"
|
||||
:is-fixed="isFixed"
|
||||
:alt-pressed="altPressed"
|
||||
@end-pan="endPan"
|
||||
@end-zoom="endZoom"
|
||||
@pan-axis="pan"
|
||||
@zoom-axis="zoom"
|
||||
/>
|
||||
<div
|
||||
role="button"
|
||||
class="c-not-button c-not-button--compact c-compact-tc__gear icon-gear"
|
||||
aria-label="Time Conductor Settings"
|
||||
></div>
|
||||
|
||||
<ConductorHistory
|
||||
v-if="!isIndependent"
|
||||
class="c-conductor__history-select"
|
||||
title="Select and apply previously entered time intervals."
|
||||
/>
|
||||
<ConductorPopUp
|
||||
v-if="showConductorPopup"
|
||||
ref="conductorPopup"
|
||||
:bottom="false"
|
||||
:position-x="positionX"
|
||||
:position-y="positionY"
|
||||
:is-fixed="isFixed"
|
||||
@popup-loaded="initializePopup"
|
||||
@mode-updated="saveMode"
|
||||
@clock-updated="saveClock"
|
||||
@fixed-bounds-updated="saveFixedBounds"
|
||||
@clock-offsets-updated="saveClockOffsets"
|
||||
@dismiss="clearPopup"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import _ from 'lodash';
|
||||
import { inject, provide } from 'vue';
|
||||
|
||||
import {
|
||||
FIXED_MODE_KEY,
|
||||
MODES,
|
||||
REALTIME_MODE_KEY,
|
||||
TIME_CONTEXT_EVENTS
|
||||
} from '../../api/time/constants.js';
|
||||
import ConductorAxis from './ConductorAxis.vue';
|
||||
import ConductorClock from './ConductorClock.vue';
|
||||
import ConductorHistory from './ConductorHistory.vue';
|
||||
import ConductorInputsFixed from './ConductorInputsFixed.vue';
|
||||
import ConductorInputsRealtime from './ConductorInputsRealtime.vue';
|
||||
import ConductorMode from './ConductorMode.vue';
|
||||
@ -90,14 +78,19 @@ import ConductorModeIcon from './ConductorModeIcon.vue';
|
||||
import ConductorPopUp from './ConductorPopUp.vue';
|
||||
import conductorPopUpManager from './conductorPopUpManager.js';
|
||||
import ConductorTimeSystem from './ConductorTimeSystem.vue';
|
||||
|
||||
const DEFAULT_DURATION_FORMATTER = 'duration';
|
||||
import { useClock } from './useClock.js';
|
||||
import { useClockOffsets } from './useClockOffsets.js';
|
||||
import { useTimeBounds } from './useTimeBounds.js';
|
||||
import { useTimeContext } from './useTimeContext.js';
|
||||
import { useTimeMode } from './useTimeMode.js';
|
||||
import { useTimeSystem } from './useTimeSystem.js';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
ConductorTimeSystem,
|
||||
ConductorClock,
|
||||
ConductorMode,
|
||||
ConductorHistory,
|
||||
ConductorInputsRealtime,
|
||||
ConductorInputsFixed,
|
||||
ConductorAxis,
|
||||
@ -106,38 +99,50 @@ export default {
|
||||
},
|
||||
mixins: [conductorPopUpManager],
|
||||
inject: ['openmct', 'configuration'],
|
||||
data() {
|
||||
const isFixed = this.openmct.time.isFixed();
|
||||
const bounds = this.openmct.time.getBounds();
|
||||
const offsets = this.openmct.time.getClockOffsets();
|
||||
const timeSystem = this.openmct.time.getTimeSystem();
|
||||
const timeFormatter = this.getFormatter(timeSystem.timeFormat);
|
||||
const durationFormatter = this.getFormatter(
|
||||
timeSystem.durationFormat || DEFAULT_DURATION_FORMATTER
|
||||
);
|
||||
setup(props) {
|
||||
const openmct = inject('openmct');
|
||||
const { timeContext } = useTimeContext(openmct);
|
||||
const {
|
||||
timeSystemKey,
|
||||
timeSystemFormatter,
|
||||
timeSystemDurationFormatter,
|
||||
isTimeSystemUTCBased
|
||||
} = useTimeSystem(openmct, timeContext);
|
||||
const { timeMode, isFixedTimeMode, isRealTimeMode, getAllModeMetadata, getModeMetadata } =
|
||||
useTimeMode(openmct, timeContext);
|
||||
const { bounds, isTick } = useTimeBounds(openmct, timeContext);
|
||||
const { clock, getAllClockMetadata, getClockMetadata } = useClock(openmct, timeContext);
|
||||
const { offsets } = useClockOffsets(openmct, timeContext);
|
||||
|
||||
provide('timeSystemKey', timeSystemKey);
|
||||
provide('timeSystemFormatter', timeSystemFormatter);
|
||||
provide('timeSystemDurationFormatter', timeSystemDurationFormatter);
|
||||
provide('isTimeSystemUTCBased', isTimeSystemUTCBased);
|
||||
provide('timeContext', timeContext);
|
||||
provide('timeMode', timeMode);
|
||||
provide('isFixedTimeMode', isFixedTimeMode);
|
||||
provide('isRealTimeMode', isRealTimeMode);
|
||||
provide('getAllModeMetadata', getAllModeMetadata);
|
||||
provide('getModeMetadata', getModeMetadata);
|
||||
provide('bounds', bounds);
|
||||
provide('isTick', isTick);
|
||||
provide('offsets', offsets);
|
||||
provide('clock', clock);
|
||||
provide('getAllClockMetadata', getAllClockMetadata);
|
||||
provide('getClockMetadata', getClockMetadata);
|
||||
|
||||
return {
|
||||
timeSystem,
|
||||
timeFormatter,
|
||||
durationFormatter,
|
||||
offsets: {
|
||||
start: offsets && durationFormatter.format(Math.abs(offsets.start)),
|
||||
end: offsets && durationFormatter.format(Math.abs(offsets.end))
|
||||
},
|
||||
bounds: {
|
||||
start: bounds.start,
|
||||
end: bounds.end
|
||||
},
|
||||
formattedBounds: {
|
||||
start: timeFormatter.format(bounds.start),
|
||||
end: timeFormatter.format(bounds.end)
|
||||
},
|
||||
timeSystemFormatter,
|
||||
isFixedTimeMode,
|
||||
bounds
|
||||
};
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
viewBounds: {
|
||||
start: bounds.start,
|
||||
end: bounds.end
|
||||
start: this.bounds.start,
|
||||
end: this.bounds.end
|
||||
},
|
||||
isFixed,
|
||||
isUTCBased: timeSystem.isUTCBased,
|
||||
showDatePicker: false,
|
||||
showConductorPopup: false,
|
||||
altPressed: false,
|
||||
@ -146,38 +151,30 @@ export default {
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
mode() {
|
||||
return this.isFixed ? FIXED_MODE_KEY : REALTIME_MODE_KEY;
|
||||
formattedBounds() {
|
||||
return {
|
||||
start: this.timeSystemFormatter.format(this.bounds.start),
|
||||
end: this.timeSystemFormatter.format(this.bounds.end)
|
||||
};
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
bounds: {
|
||||
handler() {
|
||||
this.setViewBounds(this.bounds);
|
||||
},
|
||||
deep: true
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
document.addEventListener('keydown', this.handleKeyDown);
|
||||
document.addEventListener('keyup', this.handleKeyUp);
|
||||
|
||||
this.setTimeSystem(this.copy(this.openmct.time.getTimeSystem()));
|
||||
|
||||
this.openmct.time.on(TIME_CONTEXT_EVENTS.boundsChanged, _.throttle(this.handleNewBounds, 300));
|
||||
this.openmct.time.on(TIME_CONTEXT_EVENTS.timeSystemChanged, this.setTimeSystem);
|
||||
this.openmct.time.on(TIME_CONTEXT_EVENTS.modeChanged, this.setMode);
|
||||
},
|
||||
beforeUnmount() {
|
||||
document.removeEventListener('keydown', this.handleKeyDown);
|
||||
document.removeEventListener('keyup', this.handleKeyUp);
|
||||
|
||||
this.openmct.time.off(TIME_CONTEXT_EVENTS.boundsChanged, _.throttle(this.handleNewBounds, 300));
|
||||
this.openmct.time.off(TIME_CONTEXT_EVENTS.timeSystemChanged, this.setTimeSystem);
|
||||
this.openmct.time.off(TIME_CONTEXT_EVENTS.modeChanged, this.setMode);
|
||||
},
|
||||
methods: {
|
||||
handleNewBounds(bounds, isTick) {
|
||||
if (this.openmct.time.isRealTime() || !isTick) {
|
||||
this.setBounds(bounds);
|
||||
this.setViewFromBounds(bounds);
|
||||
}
|
||||
},
|
||||
setBounds(bounds) {
|
||||
this.bounds = bounds;
|
||||
},
|
||||
handleKeyDown(event) {
|
||||
if (event.key === 'Alt') {
|
||||
this.altPressed = true;
|
||||
@ -190,7 +187,7 @@ export default {
|
||||
},
|
||||
pan(bounds) {
|
||||
this.isPanning = true;
|
||||
this.setViewFromBounds(bounds);
|
||||
this.setViewBounds(bounds);
|
||||
},
|
||||
endPan(bounds) {
|
||||
this.isPanning = false;
|
||||
@ -203,8 +200,8 @@ export default {
|
||||
this.isZooming = false;
|
||||
} else {
|
||||
this.isZooming = true;
|
||||
this.formattedBounds.start = this.timeFormatter.format(bounds.start);
|
||||
this.formattedBounds.end = this.timeFormatter.format(bounds.end);
|
||||
this.formattedBounds.start = this.timeSystemFormatter.format(bounds.start);
|
||||
this.formattedBounds.end = this.timeSystemFormatter.format(bounds.end);
|
||||
}
|
||||
},
|
||||
endZoom(bounds) {
|
||||
@ -212,49 +209,12 @@ export default {
|
||||
if (bounds) {
|
||||
this.openmct.time.setBounds(bounds);
|
||||
} else {
|
||||
this.setViewFromBounds(this.bounds);
|
||||
this.setViewBounds(this.bounds);
|
||||
}
|
||||
},
|
||||
setTimeSystem(timeSystem) {
|
||||
this.timeSystem = timeSystem;
|
||||
this.timeFormatter = this.getFormatter(timeSystem.timeFormat);
|
||||
this.durationFormatter = this.getFormatter(
|
||||
timeSystem.durationFormat || DEFAULT_DURATION_FORMATTER
|
||||
);
|
||||
this.isUTCBased = timeSystem.isUTCBased;
|
||||
},
|
||||
setMode() {
|
||||
this.isFixed = this.openmct.time.isFixed();
|
||||
},
|
||||
setViewFromBounds(bounds) {
|
||||
this.formattedBounds.start = this.timeFormatter.format(bounds.start);
|
||||
this.formattedBounds.end = this.timeFormatter.format(bounds.end);
|
||||
setViewBounds(bounds) {
|
||||
this.viewBounds.start = bounds.start;
|
||||
this.viewBounds.end = bounds.end;
|
||||
},
|
||||
getFormatter(key) {
|
||||
return this.openmct.telemetry.getValueFormatter({
|
||||
format: key
|
||||
}).formatter;
|
||||
},
|
||||
getBoundsForMode(mode) {
|
||||
const isRealTime = mode === MODES.realtime;
|
||||
return isRealTime ? this.openmct.time.getClockOffsets() : this.openmct.time.getBounds();
|
||||
},
|
||||
saveFixedBounds(bounds) {
|
||||
this.openmct.time.setBounds(bounds);
|
||||
},
|
||||
saveClockOffsets(offsets) {
|
||||
this.openmct.time.setClockOffsets(offsets);
|
||||
},
|
||||
saveClock(clockOptions) {
|
||||
this.openmct.time.setClock(clockOptions.clockKey);
|
||||
},
|
||||
saveMode(mode) {
|
||||
this.openmct.time.setMode(mode, this.getBoundsForMode(mode));
|
||||
},
|
||||
copy(object) {
|
||||
return JSON.parse(JSON.stringify(object));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@ -24,12 +24,9 @@
|
||||
<div class="c-menu-button c-ctrl-wrapper c-ctrl-wrapper--menus-left">
|
||||
<button
|
||||
aria-label="Time Conductor History"
|
||||
class="c-button--menu c-history-button icon-history"
|
||||
:class="buttonCssClass"
|
||||
class="c-button--minor c-history-button icon-history c-icon-button"
|
||||
@click.prevent.stop="showHistoryMenu"
|
||||
>
|
||||
<span class="c-button__label">History</span>
|
||||
</button>
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@ -47,15 +44,6 @@ import UTCTimeFormat from '../utcTimeSystem/UTCTimeFormat.js';
|
||||
|
||||
export default {
|
||||
inject: ['openmct', 'configuration'],
|
||||
props: {
|
||||
buttonCssClass: {
|
||||
type: String,
|
||||
required: false,
|
||||
default() {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
},
|
||||
data() {
|
||||
const mode = this.openmct.time.getMode();
|
||||
|
||||
|
@ -20,14 +20,8 @@
|
||||
at runtime from the About dialog for additional information.
|
||||
-->
|
||||
<template>
|
||||
<TimePopupFixed
|
||||
v-if="readOnly === false"
|
||||
:input-bounds="bounds"
|
||||
:input-time-system="timeSystem"
|
||||
@focus="$event.target.select()"
|
||||
@update="setBoundsFromView"
|
||||
@dismiss="dismiss"
|
||||
/>
|
||||
<DateTimePopupFixed v-if="delimiter && !readOnly" :delimiter="delimiter" @focus="$event.target.select()" @dismiss="dismiss" />
|
||||
<TimePopupFixed v-else-if="!readOnly" @focus="$event.target.select()" @dismiss="dismiss" />
|
||||
<div v-else class="c-compact-tc__setting-wrapper">
|
||||
<div
|
||||
class="c-compact-tc__setting-value u-fade-truncate--lg --no-sep"
|
||||
@ -48,29 +42,16 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import _ from 'lodash';
|
||||
|
||||
import { TIME_CONTEXT_EVENTS } from '../../api/time/constants.js';
|
||||
import TimePopupFixed from './TimePopupFixed.vue';
|
||||
import DateTimePopupFixed from './DateTimePopupFixed.vue';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
TimePopupFixed
|
||||
TimePopupFixed,
|
||||
DateTimePopupFixed
|
||||
},
|
||||
inject: ['openmct'],
|
||||
inject: ['openmct', 'timeContext', 'bounds', 'timeSystemFormatter'],
|
||||
props: {
|
||||
inputBounds: {
|
||||
type: Object,
|
||||
default() {
|
||||
return undefined;
|
||||
}
|
||||
},
|
||||
objectPath: {
|
||||
type: Array,
|
||||
default() {
|
||||
return [];
|
||||
}
|
||||
},
|
||||
readOnly: {
|
||||
type: Boolean,
|
||||
default() {
|
||||
@ -84,94 +65,19 @@ export default {
|
||||
}
|
||||
}
|
||||
},
|
||||
emits: ['bounds-updated', 'dismiss-inputs-fixed'],
|
||||
data() {
|
||||
const timeSystem = this.openmct.time.getTimeSystem();
|
||||
const timeFormatter = this.getFormatter(timeSystem.timeFormat);
|
||||
let bounds = this.inputBounds || this.openmct.time.getBounds();
|
||||
|
||||
return {
|
||||
timeSystem,
|
||||
timeFormatter,
|
||||
bounds: {
|
||||
start: bounds.start,
|
||||
end: bounds.end
|
||||
},
|
||||
formattedBounds: {
|
||||
start: timeFormatter.format(bounds.start),
|
||||
end: timeFormatter.format(bounds.end)
|
||||
},
|
||||
isUTCBased: timeSystem.isUTCBased
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
objectPath: {
|
||||
handler(newPath, oldPath) {
|
||||
if (newPath === oldPath) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.setTimeContext();
|
||||
},
|
||||
deep: true
|
||||
emits: ['dismiss-inputs-fixed'],
|
||||
computed: {
|
||||
delimiter() {
|
||||
return this.timeSystemFormatter.getDelimiter?.();
|
||||
},
|
||||
inputBounds: {
|
||||
handler(newBounds) {
|
||||
this.handleNewBounds(newBounds);
|
||||
},
|
||||
deep: true
|
||||
formattedBounds() {
|
||||
return {
|
||||
start: this.timeSystemFormatter.format(this.bounds.start),
|
||||
end: this.timeSystemFormatter.format(this.bounds.end)
|
||||
};
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.handleNewBounds = _.throttle(this.handleNewBounds, 300);
|
||||
this.setTimeSystem(JSON.parse(JSON.stringify(this.openmct.time.getTimeSystem())));
|
||||
this.openmct.time.on(TIME_CONTEXT_EVENTS.timeSystemChanged, this.setTimeSystem);
|
||||
this.setTimeContext();
|
||||
},
|
||||
beforeUnmount() {
|
||||
this.openmct.time.off(TIME_CONTEXT_EVENTS.timeSystemChanged, this.setTimeSystem);
|
||||
this.stopFollowingTimeContext();
|
||||
},
|
||||
methods: {
|
||||
setTimeContext() {
|
||||
this.stopFollowingTimeContext();
|
||||
this.timeContext = this.openmct.time.getContextForView(this.objectPath);
|
||||
|
||||
this.handleNewBounds(this.timeContext.getBounds());
|
||||
this.timeContext.on(TIME_CONTEXT_EVENTS.boundsChanged, this.handleNewBounds);
|
||||
},
|
||||
stopFollowingTimeContext() {
|
||||
if (this.timeContext) {
|
||||
this.timeContext.off(TIME_CONTEXT_EVENTS.boundsChanged, this.handleNewBounds);
|
||||
}
|
||||
},
|
||||
handleNewBounds(bounds) {
|
||||
this.setBounds(bounds);
|
||||
this.setViewFromBounds(bounds);
|
||||
},
|
||||
setBounds(bounds) {
|
||||
this.bounds = bounds;
|
||||
},
|
||||
setViewFromBounds(bounds) {
|
||||
this.formattedBounds.start = this.timeFormatter.format(bounds.start);
|
||||
this.formattedBounds.end = this.timeFormatter.format(bounds.end);
|
||||
},
|
||||
setTimeSystem(timeSystem) {
|
||||
this.timeSystem = timeSystem;
|
||||
this.timeFormatter = this.getFormatter(timeSystem.timeFormat);
|
||||
this.isUTCBased = timeSystem.isUTCBased;
|
||||
},
|
||||
getFormatter(key) {
|
||||
return this.openmct.telemetry.getValueFormatter({
|
||||
format: key
|
||||
}).formatter;
|
||||
},
|
||||
setBoundsFromView(bounds) {
|
||||
this.$emit('bounds-updated', {
|
||||
start: bounds.start,
|
||||
end: bounds.end
|
||||
});
|
||||
},
|
||||
dismiss() {
|
||||
this.$emit('dismiss-inputs-fixed');
|
||||
}
|
||||
|
@ -22,29 +22,28 @@
|
||||
<template>
|
||||
<TimePopupRealtime
|
||||
v-if="readOnly === false"
|
||||
:offsets="offsets"
|
||||
:offsets="formattedOffsets"
|
||||
@focus="$event.target.select()"
|
||||
@update="timePopUpdate"
|
||||
@dismiss="dismiss"
|
||||
/>
|
||||
<div v-else class="c-compact-tc__setting-wrapper">
|
||||
<div
|
||||
v-if="!compact"
|
||||
class="c-compact-tc__setting-value icon-minus u-fade-truncate--lg --no-sep"
|
||||
:aria-label="`Start offset: ${offsets.start}`"
|
||||
:title="`Start offset: ${offsets.start}`"
|
||||
:aria-label="`Start offset: ${formattedOffsets.start}`"
|
||||
:title="`Start offset: ${formattedOffsets.start}`"
|
||||
>
|
||||
{{ offsets.start }}
|
||||
{{ formattedOffsets.start }}
|
||||
</div>
|
||||
<div v-if="!compact" class="c-compact-tc__bounds__start-end-sep icon-arrows-right-left"></div>
|
||||
<div
|
||||
v-if="!compact"
|
||||
class="c-compact-tc__setting-value icon-plus u-fade-truncate--lg"
|
||||
:class="{ '--no-sep': compact }"
|
||||
:aria-label="`End offset: ${offsets.end}`"
|
||||
:title="`End offset: ${offsets.end}`"
|
||||
:aria-label="`End offset: ${formattedOffsets.end}`"
|
||||
:title="`End offset: ${formattedOffsets.end}`"
|
||||
>
|
||||
{{ offsets.end }}
|
||||
{{ formattedOffsets.end }}
|
||||
</div>
|
||||
<div
|
||||
class="c-compact-tc__setting-value icon-clock c-compact-tc__current-update u-fade-truncate--lg --no-sep"
|
||||
@ -58,31 +57,21 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import _ from 'lodash';
|
||||
|
||||
import { TIME_CONTEXT_EVENTS } from '../../api/time/constants.js';
|
||||
import TimePopupRealtime from './TimePopupRealtime.vue';
|
||||
|
||||
const DEFAULT_DURATION_FORMATTER = 'duration';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
TimePopupRealtime
|
||||
},
|
||||
inject: ['openmct'],
|
||||
inject: [
|
||||
'openmct',
|
||||
'bounds',
|
||||
'clock',
|
||||
'offsets',
|
||||
'timeSystemFormatter',
|
||||
'timeSystemDurationFormatter'
|
||||
],
|
||||
props: {
|
||||
objectPath: {
|
||||
type: Array,
|
||||
default() {
|
||||
return [];
|
||||
}
|
||||
},
|
||||
inputBounds: {
|
||||
type: Object,
|
||||
default() {
|
||||
return undefined;
|
||||
}
|
||||
},
|
||||
readOnly: {
|
||||
type: Boolean,
|
||||
default() {
|
||||
@ -96,167 +85,34 @@ export default {
|
||||
}
|
||||
}
|
||||
},
|
||||
emits: ['offsets-updated', 'dismiss-inputs-realtime'],
|
||||
emits: ['dismiss-inputs-realtime'],
|
||||
data() {
|
||||
const timeSystem = this.openmct.time.getTimeSystem();
|
||||
const durationFormatter = this.getFormatter(
|
||||
timeSystem.durationFormat || DEFAULT_DURATION_FORMATTER
|
||||
);
|
||||
const timeFormatter = this.getFormatter(timeSystem.timeFormat);
|
||||
const bounds = this.bounds ?? this.openmct.time.getBounds();
|
||||
const offsets = this.offsets ?? this.openmct.time.getClockOffsets();
|
||||
const currentValue = this.openmct.time.getClock()?.currentValue();
|
||||
|
||||
return {
|
||||
showTCInputStart: false,
|
||||
showTCInputEnd: false,
|
||||
durationFormatter,
|
||||
timeFormatter,
|
||||
bounds: {
|
||||
start: bounds.start,
|
||||
end: bounds.end
|
||||
},
|
||||
offsets: {
|
||||
start: offsets && durationFormatter.format(Math.abs(offsets.start)),
|
||||
end: offsets && durationFormatter.format(Math.abs(offsets.end))
|
||||
},
|
||||
formattedBounds: {
|
||||
start: timeFormatter.format(bounds.start),
|
||||
end: timeFormatter.format(bounds.end)
|
||||
},
|
||||
currentValue,
|
||||
formattedCurrentValue: timeFormatter.format(currentValue),
|
||||
isUTCBased: timeSystem.isUTCBased
|
||||
currentValue: this.clock.currentValue()
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
objectPath: {
|
||||
handler(newPath, oldPath) {
|
||||
if (newPath === oldPath) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.setTimeContext();
|
||||
},
|
||||
deep: true
|
||||
computed: {
|
||||
formattedOffsets() {
|
||||
return {
|
||||
start: this.timeSystemDurationFormatter.format(Math.abs(this.offsets.start)),
|
||||
end: this.timeSystemDurationFormatter.format(Math.abs(this.offsets.end))
|
||||
};
|
||||
},
|
||||
inputBounds: {
|
||||
handler(newBounds) {
|
||||
this.handleNewBounds(newBounds);
|
||||
},
|
||||
deep: true
|
||||
formattedCurrentValue() {
|
||||
return this.timeSystemFormatter.format(this.currentValue);
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.handleNewBounds = _.throttle(this.handleNewBounds, 300, {
|
||||
leading: true,
|
||||
trailing: false
|
||||
});
|
||||
this.setTimeSystem(this.copy(this.openmct.time.getTimeSystem()));
|
||||
this.openmct.time.on(TIME_CONTEXT_EVENTS.timeSystemChanged, this.setTimeSystem);
|
||||
this.setTimeContext();
|
||||
},
|
||||
beforeUnmount() {
|
||||
this.openmct.time.off(TIME_CONTEXT_EVENTS.timeSystemChanged, this.setTimeSystem);
|
||||
this.stopFollowingTime();
|
||||
watch: {
|
||||
bounds() {
|
||||
this.updateCurrentValue();
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
followTime() {
|
||||
const bounds = this.timeContext
|
||||
? this.timeContext.getBounds()
|
||||
: this.openmct.time.getBounds();
|
||||
const offsets = this.timeContext
|
||||
? this.timeContext.getClockOffsets()
|
||||
: this.openmct.time.getClockOffsets();
|
||||
|
||||
this.handleNewBounds(bounds);
|
||||
this.setViewFromOffsets(offsets);
|
||||
|
||||
if (this.timeContext) {
|
||||
this.timeContext.on(TIME_CONTEXT_EVENTS.boundsChanged, this.handleNewBounds);
|
||||
this.timeContext.on(TIME_CONTEXT_EVENTS.clockOffsetsChanged, this.setViewFromOffsets);
|
||||
} else {
|
||||
this.openmct.time.on(TIME_CONTEXT_EVENTS.boundsChanged, this.handleNewBounds);
|
||||
this.openmct.time.on(TIME_CONTEXT_EVENTS.clockOffsetsChanged, this.setViewFromOffsets);
|
||||
}
|
||||
},
|
||||
stopFollowingTime() {
|
||||
if (this.timeContext) {
|
||||
this.timeContext.off(TIME_CONTEXT_EVENTS.boundsChanged, this.handleNewBounds);
|
||||
this.timeContext.off(TIME_CONTEXT_EVENTS.clockOffsetsChanged, this.setViewFromOffsets);
|
||||
} else {
|
||||
this.openmct.time.off(TIME_CONTEXT_EVENTS.boundsChanged, this.handleNewBounds);
|
||||
this.openmct.time.off(TIME_CONTEXT_EVENTS.clockOffsetsChanged, this.setViewFromOffsets);
|
||||
}
|
||||
},
|
||||
setTimeContext() {
|
||||
this.stopFollowingTime();
|
||||
this.timeContext = this.openmct.time.getContextForView(this.objectPath);
|
||||
this.followTime();
|
||||
},
|
||||
handleNewBounds(bounds, isTick) {
|
||||
if (this.timeContext.isRealTime() || !isTick) {
|
||||
this.setBounds(bounds);
|
||||
this.setViewFromBounds(bounds);
|
||||
this.updateCurrentValue();
|
||||
}
|
||||
},
|
||||
setViewFromOffsets(offsets) {
|
||||
if (offsets) {
|
||||
this.offsets.start = this.durationFormatter.format(Math.abs(offsets.start));
|
||||
this.offsets.end = this.durationFormatter.format(Math.abs(offsets.end));
|
||||
}
|
||||
},
|
||||
setBounds(bounds) {
|
||||
this.bounds = bounds;
|
||||
},
|
||||
setViewFromBounds(bounds) {
|
||||
this.formattedBounds.start = this.timeFormatter.format(bounds.start);
|
||||
this.formattedBounds.end = this.timeFormatter.format(bounds.end);
|
||||
},
|
||||
updateCurrentValue() {
|
||||
const currentValue = this.timeContext.getClock().currentValue();
|
||||
|
||||
if (currentValue !== undefined) {
|
||||
this.setCurrentValue(currentValue);
|
||||
}
|
||||
},
|
||||
setCurrentValue(value) {
|
||||
this.currentValue = value;
|
||||
this.formattedCurrentValue = this.timeFormatter.format(value);
|
||||
},
|
||||
setTimeSystem(timeSystem) {
|
||||
this.timeSystem = timeSystem;
|
||||
this.timeFormatter = this.getFormatter(timeSystem.timeFormat);
|
||||
this.durationFormatter = this.getFormatter(
|
||||
timeSystem.durationFormat || DEFAULT_DURATION_FORMATTER
|
||||
);
|
||||
this.isUTCBased = timeSystem.isUTCBased;
|
||||
},
|
||||
getFormatter(key) {
|
||||
return this.openmct.telemetry.getValueFormatter({
|
||||
format: key
|
||||
}).formatter;
|
||||
},
|
||||
timePopUpdate({ start, end }) {
|
||||
this.offsets.start = [start.hours, start.minutes, start.seconds].join(':');
|
||||
this.offsets.end = [end.hours, end.minutes, end.seconds].join(':');
|
||||
this.setOffsetsFromView();
|
||||
},
|
||||
setOffsetsFromView() {
|
||||
let startOffset = 0 - this.durationFormatter.parse(this.offsets.start);
|
||||
let endOffset = this.durationFormatter.parse(this.offsets.end);
|
||||
|
||||
this.$emit('offsets-updated', {
|
||||
start: startOffset,
|
||||
end: endOffset
|
||||
});
|
||||
this.currentValue = this.clock.currentValue();
|
||||
},
|
||||
dismiss() {
|
||||
this.$emit('dismiss-inputs-realtime');
|
||||
},
|
||||
copy(object) {
|
||||
return JSON.parse(JSON.stringify(object));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@ -23,8 +23,8 @@
|
||||
<div v-if="readOnly === false" ref="modeButton" class="c-tc-input-popup__options">
|
||||
<div class="c-menu-button c-ctrl-wrapper c-ctrl-wrapper--menus-left">
|
||||
<button
|
||||
class="c-button--menu js-mode-button"
|
||||
:class="[buttonCssClass, selectedMode.cssClass]"
|
||||
class="c-button--menu js-mode-button c-icon-button"
|
||||
:class="selectedMode.cssClass"
|
||||
aria-label="Time Conductor Mode Menu"
|
||||
@click.prevent.stop="showModesMenu"
|
||||
>
|
||||
@ -43,20 +43,9 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import modeMixin from './mode-mixin.js';
|
||||
|
||||
const TEST_IDS = true;
|
||||
|
||||
export default {
|
||||
mixins: [modeMixin],
|
||||
inject: ['openmct', 'configuration'],
|
||||
inject: ['openmct', 'timeMode', 'getAllModeMetadata', 'getModeMetadata'],
|
||||
props: {
|
||||
mode: {
|
||||
type: String,
|
||||
default() {
|
||||
return undefined;
|
||||
}
|
||||
},
|
||||
readOnly: {
|
||||
type: Boolean,
|
||||
default() {
|
||||
@ -64,24 +53,20 @@ export default {
|
||||
}
|
||||
}
|
||||
},
|
||||
emits: ['mode-updated'],
|
||||
data() {
|
||||
const mode = this.openmct.time.getMode();
|
||||
|
||||
return {
|
||||
selectedMode: this.getModeMetadata(mode, TEST_IDS),
|
||||
modes: []
|
||||
selectedMode: this.getModeMetadata(this.timeMode)
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
mode: {
|
||||
handler(newMode) {
|
||||
this.setViewFromMode(newMode);
|
||||
timeMode: {
|
||||
handler() {
|
||||
this.setView();
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.loadModes();
|
||||
this.modes = this.getAllModeMetadata();
|
||||
},
|
||||
methods: {
|
||||
showModesMenu() {
|
||||
@ -96,13 +81,8 @@ export default {
|
||||
|
||||
this.dismiss = this.openmct.menus.showSuperMenu(x, y, this.modes, menuOptions);
|
||||
},
|
||||
setViewFromMode(mode) {
|
||||
this.selectedMode = this.getModeMetadata(mode, TEST_IDS);
|
||||
},
|
||||
setMode(mode) {
|
||||
this.setViewFromMode(mode);
|
||||
|
||||
this.$emit('mode-updated', mode);
|
||||
setView() {
|
||||
this.selectedMode = this.getModeMetadata(this.timeMode);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@ -5,66 +5,36 @@
|
||||
v-if="isIndependent"
|
||||
class="c-conductor__mode-select"
|
||||
title="Sets the Time Conductor's mode."
|
||||
:mode="timeOptionMode"
|
||||
@independent-mode-updated="saveIndependentMode"
|
||||
/>
|
||||
<ConductorMode
|
||||
v-else
|
||||
class="c-conductor__mode-select"
|
||||
title="Sets the Time Conductor's mode."
|
||||
:button-css-class="'c-icon-button'"
|
||||
@mode-updated="saveMode"
|
||||
/>
|
||||
<IndependentClock
|
||||
v-if="isIndependent"
|
||||
class="c-conductor__mode-select"
|
||||
title="Sets the Time Conductor's clock."
|
||||
:clock="timeOptionClock"
|
||||
:button-css-class="'c-icon-button'"
|
||||
@independent-clock-updated="saveIndependentClock"
|
||||
/>
|
||||
<ConductorClock
|
||||
v-else
|
||||
class="c-conductor__mode-select"
|
||||
title="Sets the Time Conductor's clock."
|
||||
:button-css-class="'c-icon-button'"
|
||||
@clock-updated="saveClock"
|
||||
/>
|
||||
<!-- TODO: Time system and history must work even with ITC later -->
|
||||
<ConductorTimeSystem
|
||||
v-if="!isIndependent"
|
||||
class="c-conductor__time-system-select"
|
||||
title="Sets the Time Conductor's time system."
|
||||
:button-css-class="'c-icon-button'"
|
||||
/>
|
||||
<ConductorHistory
|
||||
v-if="!isIndependent"
|
||||
class="c-conductor__history-select"
|
||||
title="Select and apply previously entered time intervals."
|
||||
:button-css-class="'c-icon-button'"
|
||||
/>
|
||||
</div>
|
||||
<ConductorInputsFixed
|
||||
v-if="isFixed"
|
||||
:input-bounds="bounds"
|
||||
:object-path="objectPath"
|
||||
@bounds-updated="saveFixedBounds"
|
||||
@dismiss-inputs-fixed="dismiss"
|
||||
/>
|
||||
<ConductorInputsRealtime
|
||||
v-else
|
||||
:input-bounds="bounds"
|
||||
:object-path="objectPath"
|
||||
@offsets-updated="saveClockOffsets"
|
||||
@dismiss-inputs-realtime="dismiss"
|
||||
/>
|
||||
<ConductorInputsFixed v-if="isFixedTimeMode" @dismiss-inputs-fixed="dismiss" />
|
||||
<ConductorInputsRealtime v-else @dismiss-inputs-realtime="dismiss" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { TIME_CONTEXT_EVENTS } from '../../api/time/constants.js';
|
||||
import ConductorClock from './ConductorClock.vue';
|
||||
import ConductorHistory from './ConductorHistory.vue';
|
||||
import ConductorInputsFixed from './ConductorInputsFixed.vue';
|
||||
import ConductorInputsRealtime from './ConductorInputsRealtime.vue';
|
||||
import ConductorMode from './ConductorMode.vue';
|
||||
@ -79,17 +49,10 @@ export default {
|
||||
IndependentMode,
|
||||
IndependentClock,
|
||||
ConductorTimeSystem,
|
||||
ConductorHistory,
|
||||
ConductorInputsFixed,
|
||||
ConductorInputsRealtime
|
||||
},
|
||||
inject: {
|
||||
openmct: 'openmct',
|
||||
configuration: {
|
||||
from: 'configuration',
|
||||
default: undefined
|
||||
}
|
||||
},
|
||||
inject: ['openmct', 'isFixedTimeMode'],
|
||||
props: {
|
||||
positionX: {
|
||||
type: Number,
|
||||
@ -99,57 +62,20 @@ export default {
|
||||
type: Number,
|
||||
required: true
|
||||
},
|
||||
isFixed: {
|
||||
type: Boolean,
|
||||
required: true
|
||||
},
|
||||
isIndependent: {
|
||||
type: Boolean,
|
||||
default() {
|
||||
return false;
|
||||
}
|
||||
},
|
||||
timeOptions: {
|
||||
type: Object,
|
||||
default() {
|
||||
return undefined;
|
||||
}
|
||||
},
|
||||
bottom: {
|
||||
type: Boolean,
|
||||
default() {
|
||||
return false;
|
||||
}
|
||||
},
|
||||
objectPath: {
|
||||
type: Array,
|
||||
default() {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
},
|
||||
emits: [
|
||||
'popup-loaded',
|
||||
'dismiss',
|
||||
'independent-clock-updated',
|
||||
'fixed-bounds-updated',
|
||||
'clock-offsets-updated',
|
||||
'clock-updated',
|
||||
'mode-updated',
|
||||
'independent-mode-updated'
|
||||
],
|
||||
data() {
|
||||
const bounds = this.openmct.time.getBounds();
|
||||
const timeSystem = this.openmct.time.getTimeSystem();
|
||||
|
||||
return {
|
||||
timeSystem,
|
||||
bounds: {
|
||||
start: bounds.start,
|
||||
end: bounds.end
|
||||
}
|
||||
};
|
||||
},
|
||||
emits: ['popup-loaded', 'dismiss'],
|
||||
computed: {
|
||||
position() {
|
||||
const position = {
|
||||
@ -164,84 +90,16 @@ export default {
|
||||
},
|
||||
popupClasses() {
|
||||
const value = this.bottom ? 'c-tc-input-popup--bottom ' : '';
|
||||
const mode = this.isFixed ? 'fixed-mode' : 'realtime-mode';
|
||||
const mode = this.isFixedTimeMode ? 'fixed-mode' : 'realtime-mode';
|
||||
const independentClass = this.isIndependent ? 'itc-popout ' : '';
|
||||
|
||||
return `${independentClass}${value}c-tc-input-popup--${mode}`;
|
||||
},
|
||||
timeOptionMode() {
|
||||
return this.timeOptions?.mode;
|
||||
},
|
||||
timeOptionClock() {
|
||||
return this.timeOptions?.clock;
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
objectPath: {
|
||||
handler(newPath, oldPath) {
|
||||
//domain object or view has probably changed
|
||||
if (newPath === oldPath) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.setTimeContext();
|
||||
},
|
||||
deep: true
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.$emit('popup-loaded');
|
||||
this.setTimeContext();
|
||||
},
|
||||
beforeUnmount() {
|
||||
this.stopFollowingTimeContext();
|
||||
},
|
||||
methods: {
|
||||
setTimeContext() {
|
||||
if (this.timeContext) {
|
||||
this.stopFollowingTimeContext();
|
||||
}
|
||||
|
||||
this.timeContext = this.openmct.time.getContextForView(this.objectPath);
|
||||
|
||||
this.timeContext.on(TIME_CONTEXT_EVENTS.clockChanged, this.setViewFromClock);
|
||||
this.timeContext.on(TIME_CONTEXT_EVENTS.boundsChanged, this.setBounds);
|
||||
|
||||
this.setViewFromClock(this.timeContext.getClock());
|
||||
this.setBounds(this.timeContext.getBounds());
|
||||
},
|
||||
stopFollowingTimeContext() {
|
||||
this.timeContext.off(TIME_CONTEXT_EVENTS.clockChanged, this.setViewFromClock);
|
||||
this.timeContext.off(TIME_CONTEXT_EVENTS.boundsChanged, this.setBounds);
|
||||
},
|
||||
setViewFromClock() {
|
||||
this.bounds = this.isFixed
|
||||
? this.timeContext.getBounds()
|
||||
: this.openmct.time.getClockOffsets();
|
||||
},
|
||||
setBounds(bounds, isTick) {
|
||||
if (this.isFixed || !isTick) {
|
||||
this.bounds = bounds;
|
||||
}
|
||||
},
|
||||
saveFixedBounds(bounds) {
|
||||
this.$emit('fixed-bounds-updated', bounds);
|
||||
},
|
||||
saveClockOffsets(offsets) {
|
||||
this.$emit('clock-offsets-updated', offsets);
|
||||
},
|
||||
saveClock(clockOptions) {
|
||||
this.$emit('clock-updated', clockOptions);
|
||||
},
|
||||
saveMode(mode) {
|
||||
this.$emit('mode-updated', mode);
|
||||
},
|
||||
saveIndependentMode(mode) {
|
||||
this.$emit('independent-mode-updated', mode);
|
||||
},
|
||||
saveIndependentClock(clockKey) {
|
||||
this.$emit('independent-clock-updated', clockKey);
|
||||
},
|
||||
dismiss() {
|
||||
this.$emit('dismiss');
|
||||
}
|
||||
|
@ -26,8 +26,7 @@
|
||||
class="c-ctrl-wrapper c-ctrl-wrapper--menus-up"
|
||||
>
|
||||
<button
|
||||
class="c-button--menu c-time-system-button"
|
||||
:class="[buttonCssClass]"
|
||||
class="c-button--menu c-time-system-button c-icon-button"
|
||||
aria-label="Time Conductor Time System"
|
||||
@click.prevent.stop="showTimeSystemMenu"
|
||||
>
|
||||
@ -50,13 +49,6 @@ import { TIME_CONTEXT_EVENTS } from '../../api/time/constants.js';
|
||||
export default {
|
||||
inject: ['openmct', 'configuration'],
|
||||
props: {
|
||||
buttonCssClass: {
|
||||
type: String,
|
||||
required: false,
|
||||
default() {
|
||||
return '';
|
||||
}
|
||||
},
|
||||
readOnly: {
|
||||
type: Boolean,
|
||||
default() {
|
||||
|
@ -107,10 +107,6 @@ export default {
|
||||
type: String,
|
||||
default: undefined
|
||||
},
|
||||
formatter: {
|
||||
type: Object,
|
||||
required: true
|
||||
},
|
||||
bottom: {
|
||||
type: Boolean,
|
||||
default() {
|
||||
|
316
src/plugins/timeConductor/DateTimePopupFixed.vue
Normal file
316
src/plugins/timeConductor/DateTimePopupFixed.vue
Normal file
@ -0,0 +1,316 @@
|
||||
<!--
|
||||
Open MCT, Copyright (c) 2014-2024, 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>
|
||||
<form ref="fixedDeltaInput">
|
||||
<div class="c-tc-input-popup__input-grid-utc">
|
||||
<div class="pr-time-label pr-time-label-start-date"><em>Start</em> Date</div>
|
||||
<div class="pr-time-label pr-time-label-start-time">Time</div>
|
||||
<div class="pr-time-label pr-time-label-end-date"><em>End</em> Date</div>
|
||||
<div class="pr-time-label pr-time-label-end-time">Time</div>
|
||||
|
||||
<div
|
||||
class="pr-time-input pr-time-input--date pr-time-input--input-and-button pr-time-input-start-date"
|
||||
>
|
||||
<DatePicker
|
||||
v-if="canSplitDateTime"
|
||||
class="c-ctrl-wrapper--menus-right"
|
||||
:default-date-time="formattedBounds.startDate"
|
||||
@date-selected="startDateSelected"
|
||||
/>
|
||||
<input
|
||||
ref="startDate"
|
||||
v-model="formattedBounds.startDate"
|
||||
class="c-input--datetime"
|
||||
type="text"
|
||||
autocorrect="off"
|
||||
spellcheck="false"
|
||||
aria-label="Start date"
|
||||
@input="validateInput('startDate')"
|
||||
@change="reportValidity('startDate')"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="pr-time-input pr-time-input--time pr-time-input-start-time">
|
||||
<input
|
||||
ref="startTime"
|
||||
v-model="formattedBounds.startTime"
|
||||
class="c-input--datetime"
|
||||
type="text"
|
||||
autocorrect="off"
|
||||
spellcheck="false"
|
||||
aria-label="Start time"
|
||||
@input="validateInput('startTime')"
|
||||
@change="reportValidity('startTime')"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="pr-time-input pr-time-input__start-end-sep icon-arrows-right-left"></div>
|
||||
|
||||
<div
|
||||
class="pr-time-input pr-time-input--date pr-time-input--input-and-button pr-time-input-end-date"
|
||||
>
|
||||
<DatePicker
|
||||
v-if="canSplitDateTime"
|
||||
class="c-ctrl-wrapper--menus-left"
|
||||
:default-date-time="formattedBounds.endDate"
|
||||
@date-selected="endDateSelected"
|
||||
/>
|
||||
<input
|
||||
ref="endDate"
|
||||
v-model="formattedBounds.endDate"
|
||||
class="c-input--datetime"
|
||||
type="text"
|
||||
autocorrect="off"
|
||||
spellcheck="false"
|
||||
aria-label="End date"
|
||||
@input="validateInput('endDate')"
|
||||
@change="reportValidity('endDate')"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="pr-time-input pr-time-input--time pr-time-input-end-time">
|
||||
<input
|
||||
ref="endTime"
|
||||
v-model="formattedBounds.endTime"
|
||||
class="c-input--datetime"
|
||||
type="text"
|
||||
autocorrect="off"
|
||||
spellcheck="false"
|
||||
aria-label="End time"
|
||||
@input="validateInput('endTime')"
|
||||
@change="reportValidity('endTime')"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="pr-time-input pr-time-input--buttons">
|
||||
<button
|
||||
class="c-button c-button--major icon-check"
|
||||
:disabled="hasInputValidityError"
|
||||
aria-label="Submit time bounds"
|
||||
@click.prevent="handleFormSubmission(true)"
|
||||
></button>
|
||||
<button
|
||||
class="c-button icon-x"
|
||||
aria-label="Discard changes and close time popup"
|
||||
@click.prevent="hide"
|
||||
></button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import DatePicker from './DatePicker.vue';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
DatePicker
|
||||
},
|
||||
inject: [
|
||||
'openmct',
|
||||
'isTimeSystemUTCBased',
|
||||
'timeContext',
|
||||
'timeSystemKey',
|
||||
'timeSystemFormatter',
|
||||
'timeSystemDurationFormatter',
|
||||
'bounds'
|
||||
],
|
||||
props: {
|
||||
delimiter: {
|
||||
type: String,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
emits: ['dismiss'],
|
||||
data() {
|
||||
return {
|
||||
formattedBounds: {},
|
||||
inputValidityMap: {
|
||||
startDate: { valid: true },
|
||||
startTime: { valid: true },
|
||||
endDate: { valid: true },
|
||||
endTime: { valid: true }
|
||||
},
|
||||
logicalValidityMap: {
|
||||
limit: { valid: true },
|
||||
bounds: { valid: true }
|
||||
}
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
hasInputValidityError() {
|
||||
return Object.values(this.inputValidityMap).some((isValid) => !isValid.valid);
|
||||
},
|
||||
hasLogicalValidationErrors() {
|
||||
return Object.values(this.logicalValidityMap).some((isValid) => !isValid.valid);
|
||||
},
|
||||
isValid() {
|
||||
return !this.hasInputValidityError && !this.hasLogicalValidationErrors;
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
bounds: {
|
||||
handler() {
|
||||
this.setViewFromBounds();
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.setViewFromBounds();
|
||||
},
|
||||
beforeUnmount() {
|
||||
this.clearAllValidation();
|
||||
},
|
||||
methods: {
|
||||
setViewFromBounds() {
|
||||
this.formattedBounds.startDate = this.timeSystemFormatter
|
||||
.format(this.bounds.start)
|
||||
.split(this.delimiter)[0];
|
||||
this.formattedBounds.endDate = this.timeSystemFormatter
|
||||
.format(this.bounds.end)
|
||||
.split(this.delimiter)[0];
|
||||
this.formattedBounds.startTime = this.timeSystemDurationFormatter.format(
|
||||
Math.abs(this.bounds.start)
|
||||
);
|
||||
this.formattedBounds.endTime = this.timeSystemDurationFormatter.format(
|
||||
Math.abs(this.bounds.end)
|
||||
);
|
||||
},
|
||||
setBoundsFromView(dismiss) {
|
||||
if (this.$refs.fixedDeltaInput.checkValidity()) {
|
||||
const start = this.timeSystemFormatter.parse(
|
||||
`${this.formattedBounds.startDate}${this.delimiter}${this.formattedBounds.startTime}`
|
||||
);
|
||||
const end = this.timeSystemFormatter.parse(
|
||||
`${this.formattedBounds.endDate}${this.delimiter}${this.formattedBounds.endTime}`
|
||||
);
|
||||
|
||||
this.timeContext.setBounds({
|
||||
start,
|
||||
end
|
||||
});
|
||||
}
|
||||
|
||||
if (dismiss) {
|
||||
this.$emit('dismiss');
|
||||
|
||||
return false;
|
||||
}
|
||||
},
|
||||
clearAllValidation() {
|
||||
Object.keys(this.inputValidityMap).forEach(this.clearValidation);
|
||||
},
|
||||
clearValidation(refName) {
|
||||
const input = this.getInput(refName);
|
||||
|
||||
input.setCustomValidity('');
|
||||
input.title = '';
|
||||
},
|
||||
handleFormSubmission(shouldDismiss) {
|
||||
this.validateLimit();
|
||||
this.reportValidity('limit');
|
||||
this.validateBounds();
|
||||
this.reportValidity('bounds');
|
||||
|
||||
if (this.isValid) {
|
||||
this.setBoundsFromView(shouldDismiss);
|
||||
}
|
||||
},
|
||||
validateInput(refName) {
|
||||
this.clearAllValidation();
|
||||
|
||||
const inputType = refName.includes('Date') ? 'Date' : 'Time';
|
||||
const formatter = inputType === 'Date' ? this.timeSystemFormatter : this.durationFormatter;
|
||||
const validationResult = formatter.validate(this.formattedBounds[refName])
|
||||
? { valid: true }
|
||||
: { valid: false, message: `Invalid ${inputType}` };
|
||||
|
||||
this.inputValidityMap[refName] = validationResult;
|
||||
},
|
||||
validateBounds() {
|
||||
const bounds = {
|
||||
start: this.timeSystemFormatter.parse(
|
||||
`${this.formattedBounds.startDate}${this.delimiter}${this.formattedBounds.startTime}`
|
||||
),
|
||||
end: this.timeSystemFormatter.parse(
|
||||
`${this.formattedBounds.endDate}${this.delimiter}${this.formattedBounds.endTime}`
|
||||
)
|
||||
};
|
||||
|
||||
this.logicalValidityMap.bounds = this.timeContext.validateBounds(bounds);
|
||||
},
|
||||
validateLimit(bounds) {
|
||||
const limit = this.configuration?.menuOptions
|
||||
?.filter((option) => option.timeSystem === this.timeSystemKey)
|
||||
?.find((option) => option.limit)?.limit;
|
||||
|
||||
if (this.isTimeSystemUTCBased && limit && bounds.end - bounds.start > limit) {
|
||||
this.logicalValidityMap.limit = {
|
||||
valid: false,
|
||||
message: 'Start and end difference exceeds allowable limit of ${limit}'
|
||||
};
|
||||
} else {
|
||||
this.logicalValidityMap.limit = { valid: true };
|
||||
}
|
||||
},
|
||||
reportValidity(refName) {
|
||||
const input = this.getInput(refName);
|
||||
const validationResult = this.inputValidityMap[refName] ?? this.logicalValidityMap[refName];
|
||||
|
||||
if (validationResult.valid !== true) {
|
||||
input.setCustomValidity(validationResult.message);
|
||||
input.title = validationResult.message;
|
||||
} else {
|
||||
input.setCustomValidity('');
|
||||
input.title = '';
|
||||
}
|
||||
|
||||
this.$refs.fixedDeltaInput.reportValidity();
|
||||
},
|
||||
getInput(refName) {
|
||||
if (Object.keys(this.inputValidityMap).includes(refName)) {
|
||||
return this.$refs[refName];
|
||||
}
|
||||
|
||||
return this.$refs.startDate;
|
||||
},
|
||||
startDateSelected(date) {
|
||||
this.formattedBounds.startDate = this.timeSystemFormatter
|
||||
.format(date)
|
||||
.split(this.delimiter)[0];
|
||||
this.validateInput('startDate');
|
||||
this.reportValidity('startDate');
|
||||
},
|
||||
endDateSelected(date) {
|
||||
this.formattedBounds.endDate = this.timeSystemFormatter.format(date).split(this.delimiter)[0];
|
||||
this.validateInput('endDate');
|
||||
this.reportValidity('endDate');
|
||||
},
|
||||
hide($event) {
|
||||
if ($event.target.className.indexOf('c-button icon-x') > -1) {
|
||||
this.$emit('dismiss');
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
@ -1,87 +1,77 @@
|
||||
<!--
|
||||
Open MCT, Copyright (c) 2014-2024, 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>
|
||||
<form ref="fixedDeltaInput">
|
||||
<div class="c-tc-input-popup__input-grid">
|
||||
<div class="pr-time-label pr-time-label-start-date"><em>Start</em> Date</div>
|
||||
<div class="pr-time-label pr-time-label-start-time">Time</div>
|
||||
<div class="pr-time-label pr-time-label-end-date"><em>End</em> Date</div>
|
||||
<div class="pr-time-label pr-time-label-end-time">Time</div>
|
||||
<div class="pr-time-label pr-time-label-start-time">Start</div>
|
||||
<div class="pr-time-label pr-time-label-end-time">End</div>
|
||||
|
||||
<div
|
||||
class="pr-time-input pr-time-input--date pr-time-input--input-and-button pr-time-input-start-date"
|
||||
>
|
||||
<div class="pr-time-input pr-time-input-start">
|
||||
<input
|
||||
ref="startDate"
|
||||
ref="start"
|
||||
v-model="formattedBounds.start"
|
||||
class="c-input--datetime"
|
||||
type="text"
|
||||
autocorrect="off"
|
||||
spellcheck="false"
|
||||
aria-label="Start date"
|
||||
@input="validateAllBounds('startDate')"
|
||||
aria-label="Start time"
|
||||
@input="validateInput('start')"
|
||||
@change="reportValidity('start')"
|
||||
/>
|
||||
<DatePicker
|
||||
v-if="isUTCBased"
|
||||
class="c-ctrl-wrapper--menus-right"
|
||||
v-if="isTimeSystemUTCBased"
|
||||
class="c-ctrl-wrapper--menus-left"
|
||||
:default-date-time="formattedBounds.start"
|
||||
:formatter="timeFormatter"
|
||||
@date-selected="startDateSelected"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="pr-time-input pr-time-input--time pr-time-input-start-time">
|
||||
<input
|
||||
ref="startTime"
|
||||
v-model="formattedBounds.startTime"
|
||||
class="c-input--datetime"
|
||||
type="text"
|
||||
autocorrect="off"
|
||||
spellcheck="false"
|
||||
aria-label="Start time"
|
||||
@input="validateAllBounds('startDate')"
|
||||
@date-selected="dateSelected($event, 'start')"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="pr-time-input pr-time-input__start-end-sep icon-arrows-right-left"></div>
|
||||
|
||||
<div
|
||||
class="pr-time-input pr-time-input--date pr-time-input--input-and-button pr-time-input-end-date"
|
||||
>
|
||||
<div class="pr-time-input pr-time-input-end">
|
||||
<input
|
||||
ref="endDate"
|
||||
ref="end"
|
||||
v-model="formattedBounds.end"
|
||||
class="c-input--datetime"
|
||||
type="text"
|
||||
autocorrect="off"
|
||||
spellcheck="false"
|
||||
aria-label="End date"
|
||||
@input="validateAllBounds('endDate')"
|
||||
aria-label="End time"
|
||||
@input="validateInput('end')"
|
||||
@change="reportValidity('end')"
|
||||
/>
|
||||
<DatePicker
|
||||
v-if="isUTCBased"
|
||||
v-if="isTimeSystemUTCBased"
|
||||
class="c-ctrl-wrapper--menus-left"
|
||||
:default-date-time="formattedBounds.end"
|
||||
:formatter="timeFormatter"
|
||||
@date-selected="endDateSelected"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="pr-time-input pr-time-input--time pr-time-input-end-time">
|
||||
<input
|
||||
ref="endTime"
|
||||
v-model="formattedBounds.endTime"
|
||||
class="c-input--datetime"
|
||||
type="text"
|
||||
autocorrect="off"
|
||||
spellcheck="false"
|
||||
aria-label="End time"
|
||||
@input="validateAllBounds('endDate')"
|
||||
@date-selected="dateSelected($event, 'end')"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="pr-time-input pr-time-input--buttons">
|
||||
<button
|
||||
class="c-button c-button--major icon-check"
|
||||
:disabled="isDisabled"
|
||||
:disabled="hasInputValidityError"
|
||||
aria-label="Submit time bounds"
|
||||
@click.prevent="handleFormSubmission(true)"
|
||||
></button>
|
||||
@ -96,118 +86,79 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import _ from 'lodash';
|
||||
|
||||
import DatePicker from './DatePicker.vue';
|
||||
|
||||
const DEFAULT_DURATION_FORMATTER = 'duration';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
DatePicker
|
||||
},
|
||||
inject: ['openmct'],
|
||||
props: {
|
||||
inputBounds: {
|
||||
type: Object,
|
||||
required: true
|
||||
},
|
||||
inputTimeSystem: {
|
||||
type: Object,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
emits: ['update', 'dismiss'],
|
||||
inject: [
|
||||
'openmct',
|
||||
'configuration',
|
||||
'isTimeSystemUTCBased',
|
||||
'timeContext',
|
||||
'timeSystemKey',
|
||||
'timeSystemFormatter',
|
||||
'timeSystemDurationFormatter',
|
||||
'bounds'
|
||||
],
|
||||
emits: ['dismiss'],
|
||||
data() {
|
||||
const timeSystem = this.openmct.time.getTimeSystem();
|
||||
const bounds = this.openmct.time.getBounds();
|
||||
|
||||
return {
|
||||
timeFormatter: this.getFormatter(timeSystem.timeFormat),
|
||||
durationFormatter: this.getFormatter(timeSystem.durationFormat || DEFAULT_DURATION_FORMATTER),
|
||||
bounds: {
|
||||
start: bounds.start,
|
||||
end: bounds.end
|
||||
formattedBounds: {},
|
||||
inputValidityMap: {
|
||||
start: { valid: true },
|
||||
end: { valid: true }
|
||||
},
|
||||
formattedBounds: {
|
||||
start: '',
|
||||
end: '',
|
||||
startTime: '',
|
||||
endTime: ''
|
||||
},
|
||||
isUTCBased: timeSystem.isUTCBased,
|
||||
isDisabled: false
|
||||
logicalValidityMap: {
|
||||
limit: { valid: true },
|
||||
bounds: { valid: true }
|
||||
}
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
inputBounds: {
|
||||
handler(newBounds) {
|
||||
this.handleNewBounds(newBounds);
|
||||
},
|
||||
deep: true
|
||||
computed: {
|
||||
hasInputValidityError() {
|
||||
return Object.values(this.inputValidityMap).some((isValid) => !isValid.valid);
|
||||
},
|
||||
inputTimeSystem: {
|
||||
handler(newTimeSystem) {
|
||||
this.setTimeSystem(newTimeSystem);
|
||||
},
|
||||
deep: true
|
||||
hasLogicalValidationErrors() {
|
||||
return Object.values(this.logicalValidityMap).some((isValid) => !isValid.valid);
|
||||
},
|
||||
isValid() {
|
||||
return !this.hasInputValidityError && !this.hasLogicalValidationErrors;
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.handleNewBounds = _.throttle(this.handleNewBounds, 300);
|
||||
watch: {
|
||||
bounds: {
|
||||
handler() {
|
||||
this.setViewFromBounds();
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.setTimeSystem(JSON.parse(JSON.stringify(this.openmct.time.getTimeSystem())));
|
||||
this.setViewFromBounds(this.bounds);
|
||||
this.setViewFromBounds();
|
||||
},
|
||||
beforeUnmount() {
|
||||
this.clearAllValidation();
|
||||
},
|
||||
methods: {
|
||||
handleNewBounds(bounds) {
|
||||
this.setBounds(bounds);
|
||||
this.setViewFromBounds(bounds);
|
||||
},
|
||||
clearAllValidation() {
|
||||
[this.$refs.startDate, this.$refs.endDate].forEach(this.clearValidationForInput);
|
||||
},
|
||||
clearValidationForInput(input) {
|
||||
if (input) {
|
||||
input.setCustomValidity('');
|
||||
input.title = '';
|
||||
}
|
||||
},
|
||||
setBounds(bounds) {
|
||||
this.bounds = bounds;
|
||||
},
|
||||
setViewFromBounds(bounds) {
|
||||
this.formattedBounds.start = this.timeFormatter.format(bounds.start).split(' ')[0];
|
||||
this.formattedBounds.end = this.timeFormatter.format(bounds.end).split(' ')[0];
|
||||
this.formattedBounds.startTime = this.durationFormatter.format(Math.abs(bounds.start));
|
||||
this.formattedBounds.endTime = this.durationFormatter.format(Math.abs(bounds.end));
|
||||
},
|
||||
setTimeSystem(timeSystem) {
|
||||
this.timeFormatter = this.getFormatter(timeSystem.timeFormat);
|
||||
this.durationFormatter = this.getFormatter(
|
||||
timeSystem.durationFormat || DEFAULT_DURATION_FORMATTER
|
||||
);
|
||||
this.isUTCBased = timeSystem.isUTCBased;
|
||||
},
|
||||
getFormatter(key) {
|
||||
return this.openmct.telemetry.getValueFormatter({
|
||||
format: key
|
||||
}).formatter;
|
||||
setViewFromBounds() {
|
||||
const start = this.timeSystemFormatter.format(this.bounds.start);
|
||||
const end = this.timeSystemFormatter.format(this.bounds.end);
|
||||
|
||||
this.formattedBounds = {
|
||||
start,
|
||||
end
|
||||
};
|
||||
},
|
||||
setBoundsFromView(dismiss) {
|
||||
if (this.$refs.fixedDeltaInput.checkValidity()) {
|
||||
let start = this.timeFormatter.parse(
|
||||
`${this.formattedBounds.start} ${this.formattedBounds.startTime}`
|
||||
);
|
||||
let end = this.timeFormatter.parse(
|
||||
`${this.formattedBounds.end} ${this.formattedBounds.endTime}`
|
||||
);
|
||||
const start = this.timeSystemFormatter.parse(this.formattedBounds.start);
|
||||
const end = this.timeSystemFormatter.parse(this.formattedBounds.end);
|
||||
|
||||
this.$emit('update', { start, end });
|
||||
this.timeContext.setBounds({
|
||||
start,
|
||||
end
|
||||
});
|
||||
}
|
||||
|
||||
if (dismiss) {
|
||||
@ -215,101 +166,107 @@ export default {
|
||||
return false;
|
||||
}
|
||||
},
|
||||
handleFormSubmission(shouldDismiss) {
|
||||
this.validateAllBounds('startDate');
|
||||
this.validateAllBounds('endDate');
|
||||
clearAllValidation() {
|
||||
Object.keys(this.inputValidityMap).forEach(this.clearValidation);
|
||||
},
|
||||
clearValidation(refName) {
|
||||
const input = this.getInput(refName);
|
||||
|
||||
if (!this.isDisabled) {
|
||||
input.setCustomValidity('');
|
||||
input.title = '';
|
||||
},
|
||||
handleFormSubmission(shouldDismiss) {
|
||||
this.validateLimit();
|
||||
this.validateBounds();
|
||||
this.reportLogicalValidity();
|
||||
|
||||
if (this.isValid) {
|
||||
this.setBoundsFromView(shouldDismiss);
|
||||
}
|
||||
},
|
||||
validateAllBounds(ref) {
|
||||
this.isDisabled = false;
|
||||
validateInput(refName) {
|
||||
this.clearAllValidation();
|
||||
|
||||
if (!this.areBoundsFormatsValid()) {
|
||||
this.isDisabled = true;
|
||||
return false;
|
||||
}
|
||||
const validationResult = this.timeSystemFormatter.validate(this.formattedBounds[refName])
|
||||
? { valid: true }
|
||||
: { valid: false, message: `Invalid Time` };
|
||||
|
||||
let validationResult = { valid: true };
|
||||
const currentInput = this.$refs[ref];
|
||||
this.inputValidityMap[refName] = validationResult;
|
||||
},
|
||||
validateBounds() {
|
||||
const bounds = {
|
||||
start: this.timeSystemFormatter.parse(this.formattedBounds.start),
|
||||
end: this.timeSystemFormatter.parse(this.formattedBounds.end)
|
||||
};
|
||||
|
||||
return [this.$refs.startDate, this.$refs.endDate].every((input) => {
|
||||
let boundsValues = {
|
||||
start: this.timeFormatter.parse(
|
||||
`${this.formattedBounds.start} ${this.formattedBounds.startTime}`
|
||||
),
|
||||
end: this.timeFormatter.parse(
|
||||
`${this.formattedBounds.end} ${this.formattedBounds.endTime}`
|
||||
)
|
||||
this.logicalValidityMap.bounds = this.timeContext.validateBounds(bounds);
|
||||
},
|
||||
validateLimit() {
|
||||
const bounds = {
|
||||
start: this.timeSystemFormatter.parse(this.formattedBounds.start),
|
||||
end: this.timeSystemFormatter.parse(this.formattedBounds.end)
|
||||
};
|
||||
|
||||
const limit = this.configuration?.menuOptions
|
||||
?.filter((option) => option.timeSystem === this.timeSystemKey)
|
||||
?.find((option) => option.limit)?.limit;
|
||||
|
||||
if (this.isTimeSystemUTCBased && limit && bounds.end - bounds.start > limit) {
|
||||
this.logicalValidityMap.limit = {
|
||||
valid: false,
|
||||
message: `Start and end difference exceeds allowable limit of ${limit}`
|
||||
};
|
||||
//TODO: Do we need limits here? We have conductor limits disabled right now
|
||||
// const limit = this.getBoundsLimit();
|
||||
const limit = false;
|
||||
|
||||
if (this.isUTCBased && limit && boundsValues.end - boundsValues.start > limit) {
|
||||
if (input === currentInput) {
|
||||
validationResult = {
|
||||
valid: false,
|
||||
message: 'Start and end difference exceeds allowable limit'
|
||||
};
|
||||
}
|
||||
} else if (input === currentInput) {
|
||||
validationResult = this.openmct.time.validateBounds(boundsValues);
|
||||
}
|
||||
|
||||
return this.handleValidationResults(input, validationResult);
|
||||
});
|
||||
} else {
|
||||
this.logicalValidityMap.limit = { valid: true };
|
||||
}
|
||||
},
|
||||
areBoundsFormatsValid() {
|
||||
return [this.$refs.startDate, this.$refs.endDate].every((input) => {
|
||||
const formattedDate =
|
||||
input === this.$refs.startDate
|
||||
? `${this.formattedBounds.start} ${this.formattedBounds.startTime}`
|
||||
: `${this.formattedBounds.end} ${this.formattedBounds.endTime}`;
|
||||
reportValidity(refName) {
|
||||
const input = this.getInput(refName);
|
||||
const validationResult = this.inputValidityMap[refName];
|
||||
|
||||
const validationResult = this.timeFormatter.validate(formattedDate)
|
||||
? { valid: true }
|
||||
: { valid: false, message: 'Invalid date' };
|
||||
|
||||
return this.handleValidationResults(input, validationResult);
|
||||
});
|
||||
},
|
||||
getBoundsLimit() {
|
||||
const configuration = this.configuration.menuOptions
|
||||
.filter((option) => option.timeSystem === this.timeSystem.key)
|
||||
.find((option) => option.limit);
|
||||
|
||||
const limit = configuration ? configuration.limit : undefined;
|
||||
|
||||
return limit;
|
||||
},
|
||||
handleValidationResults(input, validationResult) {
|
||||
if (validationResult.valid !== true) {
|
||||
input.setCustomValidity(validationResult.message);
|
||||
input.title = validationResult.message;
|
||||
this.isDisabled = true;
|
||||
} else {
|
||||
input.setCustomValidity('');
|
||||
input.title = '';
|
||||
}
|
||||
|
||||
this.$refs.fixedDeltaInput.reportValidity();
|
||||
},
|
||||
reportLogicalValidity() {
|
||||
const input = this.getInput();
|
||||
const boundsValidationResult = this.logicalValidityMap.bounds;
|
||||
const limitValidationResult = this.logicalValidityMap.limit;
|
||||
|
||||
return validationResult.valid;
|
||||
if (boundsValidationResult.valid !== true) {
|
||||
input.setCustomValidity(boundsValidationResult.message);
|
||||
input.title = boundsValidationResult.message;
|
||||
} else if (limitValidationResult.valid !== true) {
|
||||
input.setCustomValidity(limitValidationResult.message);
|
||||
input.title = limitValidationResult.message;
|
||||
} else {
|
||||
input.setCustomValidity('');
|
||||
input.title = '';
|
||||
}
|
||||
|
||||
this.$refs.fixedDeltaInput.reportValidity();
|
||||
},
|
||||
startDateSelected(date) {
|
||||
this.formattedBounds.start = this.timeFormatter.format(date).split(' ')[0];
|
||||
this.validateAllBounds('startDate');
|
||||
},
|
||||
endDateSelected(date) {
|
||||
this.formattedBounds.end = this.timeFormatter.format(date).split(' ')[0];
|
||||
this.validateAllBounds('endDate');
|
||||
getInput(refName) {
|
||||
if (Object.keys(this.inputValidityMap).includes(refName)) {
|
||||
return this.$refs[refName];
|
||||
}
|
||||
|
||||
return this.$refs.start;
|
||||
},
|
||||
hide($event) {
|
||||
if ($event.target.className.indexOf('c-button icon-x') > -1) {
|
||||
this.$emit('dismiss');
|
||||
}
|
||||
},
|
||||
dateSelected(date, refName) {
|
||||
this.formattedBounds[refName] = this.timeSystemFormatter.format(date);
|
||||
this.validateInput(refName);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@ -1,6 +1,28 @@
|
||||
<!--
|
||||
Open MCT, Copyright (c) 2014-2024, 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>
|
||||
<form ref="deltaInput">
|
||||
<div class="c-tc-input-popup__input-grid">
|
||||
<div class="c-tc-input-popup__input-grid-utc">
|
||||
<div class="pr-time-label icon-minus pr-time-label-minus-hrs">Hrs</div>
|
||||
<div class="pr-time-label pr-time-label-minus-mins">Mins</div>
|
||||
<div class="pr-time-label pr-time-label-minus-secs">Secs</div>
|
||||
@ -142,14 +164,17 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { nextTick } from 'vue';
|
||||
|
||||
export default {
|
||||
inject: ['timeContext', 'timeSystemDurationFormatter'],
|
||||
props: {
|
||||
offsets: {
|
||||
type: Object,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
emits: ['update', 'dismiss'],
|
||||
emits: ['dismiss'],
|
||||
data() {
|
||||
return {
|
||||
startInputHrs: '00',
|
||||
@ -164,6 +189,7 @@ export default {
|
||||
watch: {
|
||||
offsets: {
|
||||
handler() {
|
||||
console.log('REMOVE THIS');
|
||||
this.setOffsets();
|
||||
},
|
||||
deep: true
|
||||
@ -206,18 +232,23 @@ export default {
|
||||
this.isDisabled = disabled;
|
||||
},
|
||||
submit() {
|
||||
this.$emit('update', {
|
||||
start: {
|
||||
hours: this.startInputHrs,
|
||||
minutes: this.startInputMins,
|
||||
seconds: this.startInputSecs
|
||||
},
|
||||
end: {
|
||||
hours: this.endInputHrs,
|
||||
minutes: this.endInputMins,
|
||||
seconds: this.endInputSecs
|
||||
}
|
||||
});
|
||||
const formattedStartOffset = [
|
||||
this.startInputHrs,
|
||||
this.startInputMins,
|
||||
this.startInputSecs
|
||||
].join(':');
|
||||
const formattedEndOffset = [this.endInputHrs, this.endInputMins, this.endInputSecs].join(':');
|
||||
|
||||
let startOffset = 0 - this.timeSystemDurationFormatter.parse(formattedStartOffset);
|
||||
let endOffset = this.timeSystemDurationFormatter.parse(formattedEndOffset);
|
||||
|
||||
const offsets = {
|
||||
start: startOffset,
|
||||
end: endOffset
|
||||
};
|
||||
|
||||
this.timeContext.setClockOffsets(offsets);
|
||||
|
||||
this.$emit('dismiss');
|
||||
},
|
||||
hide($event) {
|
||||
@ -234,13 +265,13 @@ export default {
|
||||
this[ref] = cv.toString().padStart(2, '0');
|
||||
this.validate();
|
||||
},
|
||||
setOffsets() {
|
||||
async setOffsets() {
|
||||
[this.startInputHrs, this.startInputMins, this.startInputSecs] =
|
||||
this.offsets.start.split(':');
|
||||
[this.endInputHrs, this.endInputMins, this.endInputSecs] = this.offsets.end.split(':');
|
||||
this.$nextTick(() => {
|
||||
this.numberSelect('startInputHrs');
|
||||
});
|
||||
|
||||
await nextTick();
|
||||
this.numberSelect('startInputHrs');
|
||||
},
|
||||
numberSelect(input) {
|
||||
if (this.$refs[input] === undefined || this.$refs[input] === null) {
|
||||
|
@ -1,13 +1,4 @@
|
||||
export default {
|
||||
props: {
|
||||
buttonCssClass: {
|
||||
type: String,
|
||||
required: false,
|
||||
default() {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
loadClocks(menuOptions) {
|
||||
let clocks;
|
||||
|
@ -629,7 +629,6 @@
|
||||
}
|
||||
&-label-end-time{
|
||||
grid-area: eTime;
|
||||
|
||||
}
|
||||
&-input-end-date{
|
||||
grid-area: eDateInput;
|
||||
@ -641,6 +640,14 @@
|
||||
grid-area: blank;
|
||||
}
|
||||
|
||||
// FIXED TIME MODE non utc
|
||||
&-input-start{
|
||||
grid-area: sInput;
|
||||
}
|
||||
&-input-end{
|
||||
grid-area: eInput;
|
||||
}
|
||||
|
||||
//REAL TIME MODE
|
||||
&-label-minus-hrs{
|
||||
grid-area: labelMinusHrs;
|
||||
@ -691,14 +698,20 @@
|
||||
}
|
||||
|
||||
&--fixed-mode {
|
||||
.c-tc-input-popup__input-grid {
|
||||
.c-tc-input-popup__input-grid-utc {
|
||||
grid-template-columns: 1fr 1fr 1fr 1fr 1fr 2fr;
|
||||
grid-template-areas:
|
||||
"sDate sTime . eDate eTime ."
|
||||
"sDateInput sTimeInput arrowIcon eDateInput eTimeInput buttons";
|
||||
}
|
||||
.c-tc-input-popup__input-grid {
|
||||
grid-template-columns: 1fr 1fr 1fr 2fr;
|
||||
grid-template-areas:
|
||||
"sTime . eTime ."
|
||||
"sInput arrowIcon eInput buttons";
|
||||
}
|
||||
@include phonePortrait(){
|
||||
.c-tc-input-popup__input-grid {
|
||||
.c-tc-input-popup__input-grid-utc {
|
||||
grid-template-columns: repeat(2, max-content) 1fr;
|
||||
grid-template-areas:
|
||||
"sDate sTime ."
|
||||
@ -708,19 +721,29 @@
|
||||
padding: 2px;
|
||||
overflow: hidden;
|
||||
}
|
||||
.c-tc-input-popup__input-grid {
|
||||
grid-template-columns: repeat(2, max-content) 1fr;
|
||||
grid-template-areas:
|
||||
"sTime ."
|
||||
"sInput ."
|
||||
"eTime ."
|
||||
"eInput buttons";
|
||||
padding: 2px;
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
&--realtime-mode {
|
||||
.c-tc-input-popup__input-grid {
|
||||
.c-tc-input-popup__input-grid, .c-tc-input-popup__input-grid-utc {
|
||||
grid-template-columns: 1fr 1fr 1fr 1fr 1fr 1fr 1fr 2fr;
|
||||
grid-template-areas:
|
||||
"labelMinusHrs labelMinusMins labelMinusSecs . labelPlusHrs labelPlusMins labelPlusSecs ."
|
||||
"inputMinusHrs inputMinusMins inputMinusSecs arrowIcon inputPlusHrs inputPlusMins inputPlusSecs buttons";
|
||||
}
|
||||
@include phonePortrait(){
|
||||
.c-tc-input-popup__input-grid {
|
||||
.c-tc-input-popup__input-grid, .c-tc-input-popup__input-grid-utc {
|
||||
grid-template-columns: repeat(3, max-content) 1fr;
|
||||
grid-template-areas:
|
||||
"labelMinusHrs labelMinusMins labelMinusSecs ."
|
||||
@ -733,7 +756,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
&__input-grid {
|
||||
&__input-grid, &__input-grid-utc {
|
||||
display: grid;
|
||||
grid-row-gap: $interiorMargin;
|
||||
grid-column-gap: $interiorMarginSm;
|
||||
|
@ -25,7 +25,7 @@
|
||||
<button
|
||||
v-if="selectedClock"
|
||||
class="c-icon-button c-button--menu js-clock-button"
|
||||
:class="[buttonCssClass, selectedClock.cssClass]"
|
||||
:class="selectedClock.cssClass"
|
||||
aria-label="Independent Time Conductor Clock Menu"
|
||||
@click.prevent.stop="showClocksMenu"
|
||||
>
|
||||
@ -36,20 +36,9 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { TIME_CONTEXT_EVENTS } from '../../../api/time/constants.js';
|
||||
import toggleMixin from '../../../ui/mixins/toggle-mixin.js';
|
||||
import clockMixin from '../clock-mixin.js';
|
||||
|
||||
export default {
|
||||
mixins: [toggleMixin, clockMixin],
|
||||
inject: ['openmct'],
|
||||
inject: ['openmct', 'clock', 'getAllClockMetadata', 'getClockMetadata'],
|
||||
props: {
|
||||
clock: {
|
||||
type: String,
|
||||
default() {
|
||||
return undefined;
|
||||
}
|
||||
},
|
||||
enabled: {
|
||||
type: Boolean,
|
||||
default() {
|
||||
@ -57,33 +46,20 @@ export default {
|
||||
}
|
||||
}
|
||||
},
|
||||
emits: ['independent-clock-updated'],
|
||||
data() {
|
||||
const activeClock = this.getActiveClock();
|
||||
|
||||
return {
|
||||
selectedClock: activeClock ? this.getClockMetadata(activeClock) : undefined,
|
||||
clocks: []
|
||||
};
|
||||
computed: {
|
||||
selectedClock() {
|
||||
return this.getClockMetadata(this.clock);
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
clock(newClock, oldClock) {
|
||||
this.setViewFromClock(newClock);
|
||||
},
|
||||
enabled(newValue, oldValue) {
|
||||
if (newValue !== undefined && newValue !== oldValue && newValue === true) {
|
||||
this.setViewFromClock(this.clock);
|
||||
}
|
||||
}
|
||||
},
|
||||
beforeUnmount() {
|
||||
this.openmct.time.off(TIME_CONTEXT_EVENTS.clockChanged, this.setViewFromClock);
|
||||
},
|
||||
mounted: function () {
|
||||
this.loadClocks();
|
||||
this.setViewFromClock(this.clock);
|
||||
|
||||
this.openmct.time.on(TIME_CONTEXT_EVENTS.clockChanged, this.setViewFromClock);
|
||||
mounted() {
|
||||
this.clocks = this.getAllClockMetadata();
|
||||
},
|
||||
methods: {
|
||||
showClocksMenu() {
|
||||
@ -95,27 +71,11 @@ export default {
|
||||
menuClass: 'c-conductor__clock-menu c-super-menu--sm',
|
||||
placement: this.openmct.menus.menuPlacement.BOTTOM_RIGHT
|
||||
};
|
||||
|
||||
this.openmct.menus.showSuperMenu(x, y, this.clocks, menuOptions);
|
||||
},
|
||||
getMenuOptions() {
|
||||
let currentGlobalClock = this.getActiveClock();
|
||||
|
||||
//Create copy of active clock so the time API does not get reactified.
|
||||
currentGlobalClock = Object.assign(
|
||||
{},
|
||||
{
|
||||
name: currentGlobalClock.name,
|
||||
clock: currentGlobalClock.key,
|
||||
timeSystem: this.openmct.time.getTimeSystem().key
|
||||
}
|
||||
);
|
||||
|
||||
return [currentGlobalClock];
|
||||
},
|
||||
setClock(clockKey) {
|
||||
this.setViewFromClock(clockKey);
|
||||
|
||||
this.$emit('independent-clock-updated', clockKey);
|
||||
},
|
||||
setViewFromClock(clockOrKey) {
|
||||
let clock = clockOrKey;
|
||||
|
@ -23,8 +23,8 @@
|
||||
<div ref="modeMenuButton" class="c-ctrl-wrapper c-ctrl-wrapper--menus-up">
|
||||
<div class="c-menu-button c-ctrl-wrapper c-ctrl-wrapper--menus-left">
|
||||
<button
|
||||
class="c-icon-button c-button--menu js-mode-button"
|
||||
:class="[buttonCssClass, selectedMode.cssClass]"
|
||||
class="c-button--menu js-mode-button c-icon-button"
|
||||
:class="selectedMode.cssClass"
|
||||
aria-label="Independent Time Conductor Mode Menu"
|
||||
@click.prevent.stop="showModesMenu"
|
||||
>
|
||||
@ -35,19 +35,10 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import toggleMixin from '../../../ui/mixins/toggle-mixin.js';
|
||||
import modeMixin from '../mode-mixin.js';
|
||||
|
||||
export default {
|
||||
mixins: [toggleMixin, modeMixin],
|
||||
inject: ['openmct'],
|
||||
inject: ['openmct', 'timeMode', 'getAllModeMetadata', 'getModeMetadata'],
|
||||
props: {
|
||||
mode: {
|
||||
type: String,
|
||||
default() {
|
||||
return undefined;
|
||||
}
|
||||
},
|
||||
enabled: {
|
||||
type: Boolean,
|
||||
default() {
|
||||
@ -55,27 +46,25 @@ export default {
|
||||
}
|
||||
}
|
||||
},
|
||||
emits: ['independent-mode-updated'],
|
||||
data: function () {
|
||||
data() {
|
||||
return {
|
||||
selectedMode: this.getModeMetadata(this.mode),
|
||||
modes: []
|
||||
selectedMode: this.getModeMetadata(this.timeMode)
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
mode: {
|
||||
handler(newMode) {
|
||||
this.setViewFromMode(newMode);
|
||||
timeMode: {
|
||||
handler() {
|
||||
this.setView();
|
||||
}
|
||||
},
|
||||
enabled(newValue, oldValue) {
|
||||
if (newValue !== undefined && newValue !== oldValue && newValue === true) {
|
||||
this.setViewFromMode(this.mode);
|
||||
this.setView();
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted: function () {
|
||||
this.loadModes();
|
||||
this.modes = this.getAllModeMetadata();
|
||||
},
|
||||
methods: {
|
||||
showModesMenu() {
|
||||
@ -89,13 +78,8 @@ export default {
|
||||
};
|
||||
this.openmct.menus.showSuperMenu(x, y, this.modes, menuOptions);
|
||||
},
|
||||
setViewFromMode(mode) {
|
||||
this.selectedMode = this.getModeMetadata(mode);
|
||||
},
|
||||
setMode(mode) {
|
||||
this.setViewFromMode(mode);
|
||||
|
||||
this.$emit('independent-mode-updated', mode);
|
||||
setView() {
|
||||
this.selectedMode = this.getModeMetadata(this.timeMode);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@ -24,7 +24,7 @@
|
||||
ref="timeConductorOptionsHolder"
|
||||
class="c-compact-tc"
|
||||
:class="[
|
||||
isFixed ? 'is-fixed-mode' : independentTCEnabled ? 'is-realtime-mode' : 'is-fixed-mode',
|
||||
isFixedTimeMode ? 'is-fixed-mode' : independentTCEnabled ? 'is-realtime-mode' : 'is-fixed-mode',
|
||||
{ 'is-expanded': independentTCEnabled }
|
||||
]"
|
||||
aria-label="Independent Time Conductor Panel"
|
||||
@ -42,7 +42,6 @@
|
||||
<ConductorInputsFixed
|
||||
v-if="showFixedInputs"
|
||||
class="c-compact-tc__bounds--fixed"
|
||||
:object-path="objectPath"
|
||||
:read-only="true"
|
||||
:compact="true"
|
||||
/>
|
||||
@ -50,45 +49,38 @@
|
||||
<ConductorInputsRealtime
|
||||
v-if="showRealtimeInputs"
|
||||
class="c-compact-tc__bounds--real-time"
|
||||
:object-path="objectPath"
|
||||
:read-only="true"
|
||||
:compact="true"
|
||||
/>
|
||||
<div
|
||||
v-if="independentTCEnabled"
|
||||
role="button"
|
||||
class="c-not-button c-not-button--compact c-compact-tc__gear icon-gear"
|
||||
aria-label="Independent Time Conductor Settings"
|
||||
></div>
|
||||
|
||||
<ConductorPopUp
|
||||
v-if="showConductorPopup"
|
||||
ref="conductorPopup"
|
||||
:object-path="objectPath"
|
||||
:is-independent="true"
|
||||
:time-options="timeOptions"
|
||||
:is-fixed="isFixed"
|
||||
:bottom="true"
|
||||
:position-x="positionX"
|
||||
:position-y="positionY"
|
||||
@popup-loaded="initializePopup"
|
||||
@independent-mode-updated="saveMode"
|
||||
@independent-clock-updated="saveClock"
|
||||
@fixed-bounds-updated="saveFixedBounds"
|
||||
@clock-offsets-updated="saveClockOffsets"
|
||||
@dismiss="clearPopup"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { inject, provide, toRaw } from 'vue';
|
||||
|
||||
import ConductorModeIcon from '@/plugins/timeConductor/ConductorModeIcon.vue';
|
||||
|
||||
import { FIXED_MODE_KEY, TIME_CONTEXT_EVENTS } from '../../../api/time/constants.js';
|
||||
import ToggleSwitch from '../../../ui/components/ToggleSwitch.vue';
|
||||
import ConductorInputsFixed from '../ConductorInputsFixed.vue';
|
||||
import ConductorInputsRealtime from '../ConductorInputsRealtime.vue';
|
||||
import ConductorPopUp from '../ConductorPopUp.vue';
|
||||
import { useClock } from '../useClock.js';
|
||||
import { useClockOffsets } from '../useClockOffsets.js';
|
||||
import { useTimeBounds } from '../useTimeBounds.js';
|
||||
import { useTimeContext } from '../useTimeContext.js';
|
||||
import { useTimeMode } from '../useTimeMode.js';
|
||||
import { useTimeSystem } from '../useTimeSystem.js';
|
||||
import independentTimeConductorPopUpManager from './independentTimeConductorPopUpManager.js';
|
||||
|
||||
export default {
|
||||
@ -100,13 +92,7 @@ export default {
|
||||
ToggleSwitch
|
||||
},
|
||||
mixins: [independentTimeConductorPopUpManager],
|
||||
inject: {
|
||||
openmct: 'openmct',
|
||||
configuration: {
|
||||
from: 'configuration',
|
||||
default: undefined
|
||||
}
|
||||
},
|
||||
inject: ['openmct'],
|
||||
props: {
|
||||
domainObject: {
|
||||
type: Object,
|
||||
@ -117,210 +103,257 @@ export default {
|
||||
required: true
|
||||
}
|
||||
},
|
||||
emits: ['updated'],
|
||||
data() {
|
||||
const fixedOffsets = this.openmct.time.getBounds();
|
||||
const clockOffsets = this.openmct.time.getClockOffsets();
|
||||
const clock = this.openmct.time.getClock().key;
|
||||
const mode = this.openmct.time.getMode();
|
||||
const timeOptions = this.domainObject.configuration.timeOptions ?? {
|
||||
clockOffsets,
|
||||
fixedOffsets
|
||||
};
|
||||
setup(props) {
|
||||
const openmct = inject('openmct');
|
||||
const { timeContext } = useTimeContext(openmct, () => props.objectPath);
|
||||
|
||||
timeOptions.clock = timeOptions.clock ?? clock;
|
||||
timeOptions.mode = timeOptions.mode ?? mode;
|
||||
const {
|
||||
timeSystemKey,
|
||||
timeSystemFormatter,
|
||||
timeSystemDurationFormatter,
|
||||
isTimeSystemUTCBased
|
||||
} = useTimeSystem(openmct, timeContext);
|
||||
const { timeMode, isFixedTimeMode, isRealTimeMode, getAllModeMetadata, getModeMetadata } =
|
||||
useTimeMode(openmct, timeContext);
|
||||
const { bounds, isTick } = useTimeBounds(openmct, timeContext);
|
||||
const { clock, getAllClockMetadata, getClockMetadata } = useClock(openmct, timeContext);
|
||||
const { offsets } = useClockOffsets(openmct, timeContext);
|
||||
|
||||
// check for older configurations that stored a key
|
||||
if (timeOptions.mode.key) {
|
||||
timeOptions.mode = timeOptions.mode.key;
|
||||
}
|
||||
|
||||
const isFixed = timeOptions.mode === FIXED_MODE_KEY;
|
||||
provide('timeContext', timeContext);
|
||||
provide('timeSystemKey', timeSystemKey);
|
||||
provide('timeSystemFormatter', timeSystemFormatter);
|
||||
provide('timeSystemDurationFormatter', timeSystemDurationFormatter);
|
||||
provide('isTimeSystemUTCBased', isTimeSystemUTCBased);
|
||||
provide('timeMode', timeMode);
|
||||
provide('isFixedTimeMode', isFixedTimeMode);
|
||||
provide('isRealTimeMode', isRealTimeMode);
|
||||
provide('getAllModeMetadata', getAllModeMetadata);
|
||||
provide('getModeMetadata', getModeMetadata);
|
||||
provide('bounds', bounds);
|
||||
provide('isTick', isTick);
|
||||
provide('offsets', offsets);
|
||||
provide('clock', clock);
|
||||
provide('getAllClockMetadata', getAllClockMetadata);
|
||||
provide('getClockMetadata', getClockMetadata);
|
||||
|
||||
return {
|
||||
timeOptions,
|
||||
isFixed,
|
||||
independentTCEnabled: this.domainObject.configuration.useIndependentTime === true,
|
||||
viewBounds: {
|
||||
start: fixedOffsets.start,
|
||||
end: fixedOffsets.end
|
||||
}
|
||||
timeContext,
|
||||
timeMode,
|
||||
clock,
|
||||
timeSystemFormatter,
|
||||
isFixedTimeMode,
|
||||
isRealTimeMode,
|
||||
bounds,
|
||||
isTick,
|
||||
offsets
|
||||
};
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
keyString: this.openmct.objects.makeKeyString(this.domainObject.identifier),
|
||||
independentTCEnabled: this.domainObject.configuration.useIndependentTime === true
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
myKeyString() {
|
||||
const identifier = this.domainObject.identifier;
|
||||
return this.openmct.objects.makeKeyString(identifier);
|
||||
},
|
||||
do() {
|
||||
console.log(this.objectPath[0]);
|
||||
return this.objectPath[0];
|
||||
},
|
||||
// itcEnabled() {
|
||||
// console.log(`itcEnabled: ${this.domainObject.configuration.useIndependentTime === true}`);
|
||||
// return this.domainObject.configuration.useIndependentTime === true;
|
||||
// },
|
||||
configuration() {
|
||||
console.log('why does this not fire when watch domainObject fires?');
|
||||
return this.domainObject.configuration && {};
|
||||
},
|
||||
toggleTitle() {
|
||||
return `${this.independentTCEnabled ? 'Disable' : 'Enable'} Independent Time Conductor`;
|
||||
},
|
||||
showFixedInputs() {
|
||||
return this.isFixed && this.independentTCEnabled;
|
||||
return this.isFixedTimeMode && this.independentTCEnabled;
|
||||
},
|
||||
showRealtimeInputs() {
|
||||
return !this.isFixed && this.independentTCEnabled;
|
||||
return this.isRealTimeMode && this.independentTCEnabled;
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
domainObject: {
|
||||
handler(domainObject) {
|
||||
const key = this.openmct.objects.makeKeyString(domainObject.identifier);
|
||||
if (key !== this.keyString) {
|
||||
//domain object has changed
|
||||
this.destroyIndependentTime();
|
||||
|
||||
this.independentTCEnabled = domainObject.configuration.useIndependentTime === true;
|
||||
this.timeOptions = domainObject.configuration.timeOptions ?? {
|
||||
clockOffsets: this.openmct.time.getClockOffsets(),
|
||||
fixedOffsets: this.openmct.time.getBounds()
|
||||
};
|
||||
|
||||
// these may not be set due to older configurations
|
||||
this.timeOptions.clock = this.timeOptions.clock ?? this.openmct.time.getClock().key;
|
||||
this.timeOptions.mode = this.timeOptions.mode ?? this.openmct.time.getMode();
|
||||
|
||||
// check for older configurations that stored a key
|
||||
if (this.timeOptions.mode.key) {
|
||||
this.timeOptions.mode = this.timeOptions.mode.key;
|
||||
}
|
||||
|
||||
this.isFixed = this.timeOptions.mode === FIXED_MODE_KEY;
|
||||
|
||||
this.initialize();
|
||||
}
|
||||
},
|
||||
deep: true
|
||||
myKeyString() {
|
||||
console.log(`object changed`);
|
||||
},
|
||||
objectPath: {
|
||||
handler(newPath, oldPath) {
|
||||
//domain object or view has probably changed
|
||||
this.setTimeContext();
|
||||
},
|
||||
deep: true
|
||||
independentTCEnabled() {
|
||||
this.handleIndependentTimeConductorChange();
|
||||
},
|
||||
timeContext() {
|
||||
const keyString = this.openmct.objects.makeKeyString(this.domainObject.identifier);
|
||||
|
||||
if (keyString !== this.keyString) {
|
||||
//domain object in object view has changed (via tree navigation)
|
||||
this.unregisterIndependentTimeContext?.();
|
||||
this.keyString = keyString;
|
||||
|
||||
this.independentTCEnabled = this.domainObject.configuration.useIndependentTime === true;
|
||||
|
||||
this.setTimeOptions();
|
||||
|
||||
this.initialize();
|
||||
}
|
||||
},
|
||||
independentTCEnabled() {
|
||||
this.handleIndependentTimeConductorChange();
|
||||
},
|
||||
clock() {
|
||||
if (this.independentTCEnabled) {
|
||||
this.saveClock();
|
||||
}
|
||||
},
|
||||
timeMode() {
|
||||
if (this.independentTCEnabled) {
|
||||
this.saveMode();
|
||||
}
|
||||
},
|
||||
clockOffsets() {
|
||||
if (this.independentTCEnabled) {
|
||||
this.saveClockOffsets();
|
||||
}
|
||||
},
|
||||
bounds() {
|
||||
if (this.independentTCEnabled && this.isTick === false) {
|
||||
this.saveFixedBounds();
|
||||
}
|
||||
}
|
||||
},
|
||||
created() {
|
||||
// this.initialize();
|
||||
},
|
||||
mounted() {
|
||||
this.setTimeOptions();
|
||||
|
||||
this.initialize();
|
||||
},
|
||||
beforeUnmount() {
|
||||
this.stopFollowingTimeContext();
|
||||
this.destroyIndependentTime();
|
||||
this.unregisterIndependentTimeContext?.();
|
||||
},
|
||||
methods: {
|
||||
initialize() {
|
||||
this.keyString = this.openmct.objects.makeKeyString(this.domainObject.identifier);
|
||||
this.setTimeContext();
|
||||
|
||||
if (this.independentTCEnabled) {
|
||||
this.registerIndependentTimeOffsets();
|
||||
this.registerIndependentTimeContext();
|
||||
}
|
||||
},
|
||||
handleIndependentTimeConductorChange() {
|
||||
if (this.independentTCEnabled) {
|
||||
this.registerIndependentTimeContext();
|
||||
} else {
|
||||
this.clearPopup();
|
||||
this.unregisterIndependentTimeContext?.();
|
||||
}
|
||||
},
|
||||
toggleIndependentTC() {
|
||||
this.independentTCEnabled = !this.independentTCEnabled;
|
||||
|
||||
if (this.independentTCEnabled) {
|
||||
this.registerIndependentTimeOffsets();
|
||||
} else {
|
||||
this.clearPopup();
|
||||
this.destroyIndependentTime();
|
||||
}
|
||||
|
||||
this.openmct.objects.mutate(
|
||||
this.domainObject,
|
||||
'configuration.useIndependentTime',
|
||||
this.independentTCEnabled
|
||||
);
|
||||
|
||||
},
|
||||
setTimeContext() {
|
||||
if (this.timeContext) {
|
||||
this.stopFollowingTimeContext();
|
||||
setTimeOptions() {
|
||||
this.timeOptions = toRaw(this.domainObject.configuration.timeOptions);
|
||||
if (!this.timeOptions) {
|
||||
this.timeOptions = {
|
||||
clockOffsets: this.offsets,
|
||||
fixedOffsets: this.bounds
|
||||
};
|
||||
}
|
||||
|
||||
this.timeContext = this.openmct.time.getContextForView(this.objectPath);
|
||||
this.timeContext.on(TIME_CONTEXT_EVENTS.clockChanged, this.setTimeOptionsClock);
|
||||
this.timeContext.on(TIME_CONTEXT_EVENTS.modeChanged, this.setTimeOptionsMode);
|
||||
},
|
||||
stopFollowingTimeContext() {
|
||||
this.timeContext.off(TIME_CONTEXT_EVENTS.clockChanged, this.setTimeOptionsClock);
|
||||
this.timeContext.off(TIME_CONTEXT_EVENTS.modeChanged, this.setTimeOptionsMode);
|
||||
},
|
||||
setTimeOptionsClock(clock) {
|
||||
this.setTimeOptionsOffsets();
|
||||
this.timeOptions.clock = clock.key;
|
||||
},
|
||||
setTimeOptionsMode(mode) {
|
||||
this.setTimeOptionsOffsets();
|
||||
this.timeOptions.mode = mode;
|
||||
},
|
||||
setTimeOptionsOffsets() {
|
||||
this.timeOptions.clockOffsets =
|
||||
this.timeOptions.clockOffsets ?? this.timeContext.getClockOffsets();
|
||||
this.timeOptions.fixedOffsets = this.timeOptions.fixedOffsets ?? this.timeContext.getBounds();
|
||||
},
|
||||
saveFixedBounds(bounds) {
|
||||
const newOptions = this.updateTimeOptionProperty({
|
||||
fixedOffsets: bounds
|
||||
});
|
||||
this.updateTimeOptions(newOptions);
|
||||
},
|
||||
saveClockOffsets(offsets) {
|
||||
const newOptions = this.updateTimeOptionProperty({
|
||||
clockOffsets: offsets
|
||||
});
|
||||
if (!this.timeOptions.clock) {
|
||||
// can remove openmct.time.getClock() if timeContexts have clock in fixed time
|
||||
this.timeOptions.clock = this.clock?.key ?? this.openmct.time.getClock().key;
|
||||
}
|
||||
|
||||
this.updateTimeOptions(newOptions);
|
||||
},
|
||||
saveMode(mode) {
|
||||
this.isFixed = mode === FIXED_MODE_KEY;
|
||||
const newOptions = this.updateTimeOptionProperty({
|
||||
mode: mode
|
||||
});
|
||||
if (!this.timeOptions.mode) {
|
||||
this.timeOptions.mode = this.timeMode;
|
||||
}
|
||||
|
||||
this.updateTimeOptions(newOptions);
|
||||
// check for older configurations that stored a key
|
||||
if (this.timeOptions.mode.key) {
|
||||
this.timeOptions.mode = this.timeOptions.mode.key;
|
||||
}
|
||||
},
|
||||
saveClock(clock) {
|
||||
const newOptions = this.updateTimeOptionProperty({
|
||||
clock
|
||||
});
|
||||
|
||||
this.updateTimeOptions(newOptions);
|
||||
saveFixedBounds() {
|
||||
this.timeOptions.fixedOffsets = this.bounds;
|
||||
this.updateTimeOptions();
|
||||
},
|
||||
updateTimeOptions(options) {
|
||||
this.timeOptions = options;
|
||||
|
||||
this.registerIndependentTimeOffsets();
|
||||
this.$emit('updated', this.timeOptions); // no longer use this, but may be used elsewhere
|
||||
saveClockOffsets() {
|
||||
this.timeOptions.clockOffsets = this.offsets;
|
||||
this.updateTimeOptions();
|
||||
},
|
||||
saveMode() {
|
||||
this.timeOptions.mode = this.timeMode;
|
||||
this.updateTimeOptions();
|
||||
},
|
||||
saveClock() {
|
||||
this.timeOptions.clock = this.clock?.key;
|
||||
this.updateTimeOptions();
|
||||
},
|
||||
updateTimeOptions() {
|
||||
this.registerIndependentTimeContext();
|
||||
this.openmct.objects.mutate(this.domainObject, 'configuration.timeOptions', this.timeOptions);
|
||||
},
|
||||
registerIndependentTimeOffsets() {
|
||||
const timeContext = this.openmct.time.getIndependentContext(this.keyString);
|
||||
let offsets;
|
||||
registerIndependentTimeContext() {
|
||||
const bounds = this.timeOptions.fixedOffsets ?? this.bounds;
|
||||
const offsets = this.timeOptions.clockOffsets ?? this.offsets;
|
||||
const clockKey = this.timeOptions.clock || this.clock.key;
|
||||
|
||||
if (this.isFixed) {
|
||||
offsets = this.timeOptions.fixedOffsets ?? this.timeContext.getBounds();
|
||||
} else {
|
||||
offsets = this.timeOptions.clockOffsets ?? this.openmct.time.getClockOffsets();
|
||||
}
|
||||
const independentTimeContextBoundsOrOffsets = this.isFixedTimeMode ? bounds : offsets;
|
||||
const independentTimeContextClockKey = this.isFixedTimeMode ? undefined : clockKey;
|
||||
|
||||
if (!timeContext.hasOwnContext()) {
|
||||
this.unregisterIndependentTime = this.openmct.time.addIndependentContext(
|
||||
const independentTimeContext = this.openmct.time.getIndependentContext(this.keyString);
|
||||
|
||||
if (!independentTimeContext.hasOwnContext()) {
|
||||
this.unregisterIndependentTimeContext = this.openmct.time.addIndependentContext(
|
||||
this.keyString,
|
||||
offsets,
|
||||
this.isFixed ? undefined : this.timeOptions.clock
|
||||
independentTimeContextBoundsOrOffsets,
|
||||
independentTimeContextClockKey
|
||||
);
|
||||
} else {
|
||||
if (!this.isFixed) {
|
||||
timeContext.setClock(this.timeOptions.clock);
|
||||
}
|
||||
// if (this.isRealTimeMode) {
|
||||
// independentTimeContext.setClock(this.timeOptions.clock);
|
||||
// }
|
||||
|
||||
timeContext.setMode(this.timeOptions.mode, offsets);
|
||||
// independentTimeContext.setMode(this.timeOptions.mode, independentTimeContextBoundsOrOffsets);
|
||||
}
|
||||
},
|
||||
destroyIndependentTime() {
|
||||
if (this.unregisterIndependentTime) {
|
||||
this.unregisterIndependentTime();
|
||||
registerIndependentTimeOffsets() {
|
||||
// const timeContext = this.openmct.time.getIndependentContext(this.keyString);
|
||||
const clockKey = this.timeOptions.clock || this.clock.key;
|
||||
let offsets;
|
||||
|
||||
if (this.isFixedTimeMode) {
|
||||
offsets = this.timeOptions.fixedOffsets ?? this.bounds;
|
||||
} else {
|
||||
offsets = this.timeOptions.clockOffsets ?? this.offsets;
|
||||
}
|
||||
|
||||
if (!this.timeContext.hasOwnContext()) {
|
||||
this.unregisterIndependentTimeContext = this.openmct.time.addIndependentContext(
|
||||
this.keyString,
|
||||
offsets,
|
||||
this.isFixedTimeMode ? undefined : clockKey
|
||||
);
|
||||
} else {
|
||||
console.log('removed code');
|
||||
// if (this.isRealTimeMode) {
|
||||
// this.timeContext.setClock(this.timeOptions.clock);
|
||||
// }
|
||||
|
||||
// this.timeContext.setMode(this.timeOptions.mode, offsets);
|
||||
}
|
||||
},
|
||||
updateTimeOptionProperty(option) {
|
||||
return Object.assign({}, this.timeOptions, option);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@ -1,15 +1,6 @@
|
||||
import { FIXED_MODE_KEY, REALTIME_MODE_KEY } from '../../api/time/constants.js';
|
||||
|
||||
export default {
|
||||
props: {
|
||||
buttonCssClass: {
|
||||
type: String,
|
||||
required: false,
|
||||
default() {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
loadModes() {
|
||||
this.modes = [FIXED_MODE_KEY, REALTIME_MODE_KEY].map(this.getModeMetadata);
|
||||
|
@ -140,17 +140,13 @@ export default function (config) {
|
||||
}
|
||||
}
|
||||
|
||||
openmct.time.setMode(defaultMode, defaultClock ? clockOffsets : defaultBounds);
|
||||
openmct.time.setTimeSystem(defaults.timeSystem, defaultBounds);
|
||||
|
||||
//We are going to set the clockOffsets in fixed time mode since the conductor components down the line need these
|
||||
if (clockOffsets && defaultMode === FIXED_MODE_KEY) {
|
||||
openmct.time.setClockOffsets(clockOffsets);
|
||||
}
|
||||
//We are going to set the fixed time bounds in realtime time mode since the conductor components down the line need these
|
||||
if (defaultBounds && defaultMode === REALTIME_MODE_KEY) {
|
||||
openmct.time.setBounds(clockOffsets);
|
||||
}
|
||||
|
||||
openmct.time.setMode(defaultMode, defaultClock ? clockOffsets : defaultBounds);
|
||||
openmct.time.setTimeSystem(defaults.timeSystem, defaultBounds);
|
||||
|
||||
openmct.on('start', function () {
|
||||
mountComponent(openmct, config);
|
||||
|
151
src/plugins/timeConductor/useClock.js
Normal file
151
src/plugins/timeConductor/useClock.js
Normal file
@ -0,0 +1,151 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT Web, Copyright (c) 2014-2024, United States Government
|
||||
* as represented by the Administrator of the National Aeronautics and Space
|
||||
* Administration. All rights reserved.
|
||||
*
|
||||
* Open MCT Web 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 Web 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.
|
||||
*****************************************************************************/
|
||||
|
||||
import { onBeforeUnmount, shallowRef, watch } from 'vue';
|
||||
|
||||
import { TIME_CONTEXT_EVENTS } from '../../api/time/constants.js';
|
||||
|
||||
/**
|
||||
* Provides reactive `clock` which is reactive to a time context,
|
||||
* as well as a function to observe and update the component's clock,
|
||||
* which automatically stops observing when the component is unmounted.
|
||||
*
|
||||
* @param {OpenMCT} [openmct] the Open MCT API
|
||||
* @param {Array} objectPath The view's objectPath
|
||||
* @returns {{
|
||||
* clock: import('vue').Ref<string>,
|
||||
* getAllClockMetadata: () => Object,
|
||||
* getClockMetadata: () => Object
|
||||
* }}
|
||||
*/
|
||||
export function useClock(openmct, timeContext) {
|
||||
let stopObservingClock;
|
||||
|
||||
const clock = shallowRef(timeContext.value.getClock());
|
||||
|
||||
onBeforeUnmount(() => stopObservingClock?.());
|
||||
|
||||
watch(
|
||||
timeContext,
|
||||
(newContext, oldContext) => {
|
||||
oldContext?.off(TIME_CONTEXT_EVENTS.clockChanged, updateClock);
|
||||
observeClock();
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
|
||||
function observeClock() {
|
||||
timeContext.value.on(TIME_CONTEXT_EVENTS.clockChanged, updateClock);
|
||||
stopObservingClock = () => timeContext.value.off(TIME_CONTEXT_EVENTS.clockChanged, updateClock);
|
||||
}
|
||||
|
||||
function getAllClockMetadata(menuOptions) {
|
||||
const clocks = menuOptions
|
||||
? menuOptions
|
||||
.map((menuOption) => menuOption.clock)
|
||||
.filter((key, index, array) => key !== undefined && array.indexOf(key) === index)
|
||||
.map((clockKey) => openmct.time.getAllClocks().find((_clock) => _clock.key === clockKey))
|
||||
: openmct.time.getAllClocks();
|
||||
|
||||
const clockMetadata = clocks.map(getClockMetadata);
|
||||
|
||||
return clockMetadata;
|
||||
}
|
||||
|
||||
function getClockMetadata(_clock) {
|
||||
if (_clock === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
const clockMetadata = {
|
||||
key: _clock.key,
|
||||
name: _clock.name,
|
||||
description: 'Uses the system clock as the current time basis. ' + _clock.description,
|
||||
cssClass: _clock.cssClass || 'icon-clock',
|
||||
onItemClicked: () => setClock(_clock.key)
|
||||
};
|
||||
|
||||
return clockMetadata;
|
||||
}
|
||||
|
||||
function setClock(key) {
|
||||
timeContext.value.setClock(key);
|
||||
}
|
||||
|
||||
function updateClock(_clock) {
|
||||
clock.value = _clock;
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO: bring this back. we lost this in the last refactor.
|
||||
* changing clock requires a timesystem check.
|
||||
*
|
||||
function setClockWithOptions() {
|
||||
const option = {
|
||||
clockKey
|
||||
};
|
||||
let configuration = this.getMatchingConfig({
|
||||
clock: clockKey,
|
||||
timeSystem: this.openmct.time.getTimeSystem().key
|
||||
});
|
||||
|
||||
if (configuration === undefined) {
|
||||
configuration = this.getMatchingConfig({
|
||||
clock: clockKey
|
||||
});
|
||||
|
||||
option.timeSystem = configuration.timeSystem;
|
||||
option.bounds = configuration.bounds;
|
||||
|
||||
// this.openmct.time.setTimeSystem(configuration.timeSystem, configuration.bounds);
|
||||
}
|
||||
|
||||
const offsets = this.openmct.time.getClockOffsets() ?? configuration.clockOffsets;
|
||||
option.offsets = offsets;
|
||||
}
|
||||
|
||||
function getMatchingConfig(options) {
|
||||
const matchers = {
|
||||
clock(config) {
|
||||
return options.clock === config.clock;
|
||||
},
|
||||
timeSystem(config) {
|
||||
return options.timeSystem === config.timeSystem;
|
||||
}
|
||||
};
|
||||
|
||||
function configMatches(config) {
|
||||
return Object.keys(options).reduce((match, option) => {
|
||||
return match && matchers[option](config);
|
||||
}, true);
|
||||
}
|
||||
|
||||
return this.configuration.menuOptions.filter(configMatches)[0];
|
||||
}
|
||||
*/
|
||||
|
||||
return {
|
||||
clock,
|
||||
getAllClockMetadata,
|
||||
getClockMetadata
|
||||
};
|
||||
}
|
68
src/plugins/timeConductor/useClockOffsets.js
Normal file
68
src/plugins/timeConductor/useClockOffsets.js
Normal file
@ -0,0 +1,68 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT Web, Copyright (c) 2014-2024, United States Government
|
||||
* as represented by the Administrator of the National Aeronautics and Space
|
||||
* Administration. All rights reserved.
|
||||
*
|
||||
* Open MCT Web 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 Web 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.
|
||||
*****************************************************************************/
|
||||
|
||||
import { onBeforeUnmount, shallowRef, watch } from 'vue';
|
||||
|
||||
import { TIME_CONTEXT_EVENTS } from '../../api/time/constants.js';
|
||||
|
||||
/**
|
||||
* Provides reactive `offsets`,
|
||||
* as well as a function to observe and update offsets changes,
|
||||
* which automatically stops observing when the component is unmounted.
|
||||
*
|
||||
* @param {OpenMCT} [openmct] the Open MCT API
|
||||
* @param {TimeContext} [timeContext] the time context to use for time API clock offsets events
|
||||
* @returns {{
|
||||
* observeClockOffsets: () => void,
|
||||
* offsets: import('vue').Ref<object>,
|
||||
* }}
|
||||
*/
|
||||
export function useClockOffsets(openmct, timeContext) {
|
||||
let stopObservingClockOffsets;
|
||||
|
||||
const offsets = shallowRef(timeContext.value.getClockOffsets());
|
||||
|
||||
onBeforeUnmount(() => stopObservingClockOffsets?.());
|
||||
|
||||
watch(
|
||||
timeContext,
|
||||
(newContext, oldContext) => {
|
||||
oldContext?.off(TIME_CONTEXT_EVENTS.clockOffsetsChanged, updateClockOffsets);
|
||||
observeClockOffsets();
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
|
||||
function observeClockOffsets() {
|
||||
timeContext.value.on(TIME_CONTEXT_EVENTS.clockOffsetsChanged, updateClockOffsets);
|
||||
stopObservingClockOffsets = () =>
|
||||
timeContext.value.off(TIME_CONTEXT_EVENTS.clockOffsetsChanged, updateClockOffsets);
|
||||
}
|
||||
|
||||
function updateClockOffsets(_offsets) {
|
||||
offsets.value = _offsets;
|
||||
}
|
||||
|
||||
return {
|
||||
offsets
|
||||
};
|
||||
}
|
74
src/plugins/timeConductor/useTimeBounds.js
Normal file
74
src/plugins/timeConductor/useTimeBounds.js
Normal file
@ -0,0 +1,74 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT Web, Copyright (c) 2014-2024, United States Government
|
||||
* as represented by the Administrator of the National Aeronautics and Space
|
||||
* Administration. All rights reserved.
|
||||
*
|
||||
* Open MCT Web 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 Web 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.
|
||||
*****************************************************************************/
|
||||
|
||||
import { onBeforeUnmount, ref, shallowRef, watch } from 'vue';
|
||||
|
||||
import { TIME_CONTEXT_EVENTS } from '../../api/time/constants.js';
|
||||
import throttle from '../../utils/throttle.js';
|
||||
|
||||
const THROTTLE_RATE = 300;
|
||||
|
||||
/**
|
||||
* Provides reactive `bounds`,
|
||||
* as well as a function to observe and update bounds changes,
|
||||
* which automatically stops observing when the component is unmounted.
|
||||
*
|
||||
* @param {OpenMCT} [openmct] the Open MCT API
|
||||
* @param {Array} objectPath The view's objectPath
|
||||
* @returns {{
|
||||
* bounds: import('vue').Ref<object>,
|
||||
* isTick: import('vue').Ref<boolean>
|
||||
* }}
|
||||
*/
|
||||
export function useTimeBounds(openmct, timeContext) {
|
||||
let stopObservingTimeBounds;
|
||||
|
||||
const bounds = shallowRef(timeContext.value.getBounds());
|
||||
const isTick = ref(false);
|
||||
|
||||
onBeforeUnmount(() => stopObservingTimeBounds?.());
|
||||
|
||||
watch(
|
||||
timeContext,
|
||||
(newContext, oldContext) => {
|
||||
oldContext?.off(TIME_CONTEXT_EVENTS.boundsChanged, throttle(updateTimeBounds, THROTTLE_RATE));
|
||||
observeTimeBounds();
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
|
||||
function observeTimeBounds() {
|
||||
timeContext.value.on(TIME_CONTEXT_EVENTS.boundsChanged, throttle(updateTimeBounds, THROTTLE_RATE));
|
||||
stopObservingTimeBounds = () =>
|
||||
timeContext.value.off(TIME_CONTEXT_EVENTS.boundsChanged, throttle(updateTimeBounds, THROTTLE_RATE));
|
||||
}
|
||||
|
||||
function updateTimeBounds(_timeBounds, _isTick) {
|
||||
bounds.value = _timeBounds;
|
||||
isTick.value = _isTick;
|
||||
}
|
||||
|
||||
return {
|
||||
isTick,
|
||||
bounds
|
||||
};
|
||||
}
|
53
src/plugins/timeConductor/useTimeContext.js
Normal file
53
src/plugins/timeConductor/useTimeContext.js
Normal file
@ -0,0 +1,53 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT Web, Copyright (c) 2014-2024, United States Government
|
||||
* as represented by the Administrator of the National Aeronautics and Space
|
||||
* Administration. All rights reserved.
|
||||
*
|
||||
* Open MCT Web 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 Web 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.
|
||||
*****************************************************************************/
|
||||
|
||||
import { shallowRef, toValue, watchEffect } from 'vue';
|
||||
|
||||
/**
|
||||
* @typedef {import('@/api/time/TimeContext.js').default} TimeContext
|
||||
* @typedef {import('@/api/time/GlobalTimeContext.js').default} GlobalTimeContext
|
||||
*/
|
||||
|
||||
/**
|
||||
* Provides the reactive TimeContext
|
||||
* for the view's objectPath,
|
||||
* or the GlobalTimeContext if objectPath is undefined.
|
||||
*
|
||||
* @param {OpenMCT} openmct the Open MCT API
|
||||
* @param {Array} objectPath The view's objectPath
|
||||
* @returns {{
|
||||
* timeContext: TimeContext | GlobalTimeContext
|
||||
* }}
|
||||
*/
|
||||
export function useTimeContext(openmct, objectPath) {
|
||||
const timeContext = shallowRef(null);
|
||||
|
||||
watchEffect(() => getTimeContext());
|
||||
|
||||
function getTimeContext() {
|
||||
const path = toValue(objectPath);
|
||||
|
||||
timeContext.value = path !== undefined ? openmct.time.getContextForView(path) : openmct.time;
|
||||
}
|
||||
|
||||
return { timeContext };
|
||||
}
|
107
src/plugins/timeConductor/useTimeMode.js
Normal file
107
src/plugins/timeConductor/useTimeMode.js
Normal file
@ -0,0 +1,107 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT Web, Copyright (c) 2014-2024, United States Government
|
||||
* as represented by the Administrator of the National Aeronautics and Space
|
||||
* Administration. All rights reserved.
|
||||
*
|
||||
* Open MCT Web 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 Web 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.
|
||||
*****************************************************************************/
|
||||
|
||||
import { computed, onBeforeUnmount, ref, watch } from 'vue';
|
||||
|
||||
import {
|
||||
FIXED_MODE_KEY,
|
||||
REALTIME_MODE_KEY,
|
||||
TIME_CONTEXT_EVENTS
|
||||
} from '../../api/time/constants.js';
|
||||
|
||||
/**
|
||||
* Provides reactive `timeMode` which is reactive to a time context,
|
||||
* as well as a function to observe and update the component's time mode,
|
||||
* which automatically stops observing when the component is unmounted.
|
||||
*
|
||||
* @param {OpenMCT} openmct the Open MCT API
|
||||
* @param {Array} objectPath The view's objectPath
|
||||
* @returns {{
|
||||
* timeMode: import('vue').Ref<string>,
|
||||
* isFixedTimeMode: import('vue').Ref<boolean>,
|
||||
* isRealTimeMode: import('vue').Ref<boolean>
|
||||
* }}
|
||||
*/
|
||||
export function useTimeMode(openmct, timeContext) {
|
||||
let stopObservingTimeMode;
|
||||
|
||||
const timeMode = ref(timeContext.value.getMode());
|
||||
const isFixedTimeMode = computed(() => timeMode.value === FIXED_MODE_KEY);
|
||||
const isRealTimeMode = computed(() => timeMode.value === REALTIME_MODE_KEY);
|
||||
|
||||
onBeforeUnmount(() => stopObservingTimeMode?.());
|
||||
|
||||
watch(
|
||||
timeContext,
|
||||
(newContext, oldContext) => {
|
||||
oldContext?.off(TIME_CONTEXT_EVENTS.modeChanged, updateTimeMode);
|
||||
observeTimeMode();
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
|
||||
function observeTimeMode() {
|
||||
timeContext.value.on(TIME_CONTEXT_EVENTS.modeChanged, updateTimeMode);
|
||||
stopObservingTimeMode = () => timeContext.value.off(TIME_CONTEXT_EVENTS.modeChanged, updateTimeMode);
|
||||
}
|
||||
|
||||
function getAllModeMetadata() {
|
||||
return [FIXED_MODE_KEY, REALTIME_MODE_KEY].map(getModeMetadata);
|
||||
}
|
||||
|
||||
function getModeMetadata(key) {
|
||||
const fixedModeMetadata = {
|
||||
key: FIXED_MODE_KEY,
|
||||
name: 'Fixed Timespan',
|
||||
description: 'Query and explore data that falls between two fixed datetimes.',
|
||||
cssClass: 'icon-tabular',
|
||||
onItemClicked: () => setTimeMode(key)
|
||||
};
|
||||
|
||||
const realTimeModeMetadata = {
|
||||
key: REALTIME_MODE_KEY,
|
||||
name: 'Real-Time',
|
||||
description:
|
||||
'Monitor streaming data in real-time. The Time Conductor and displays will automatically advance themselves based on the active clock.',
|
||||
cssClass: 'icon-clock',
|
||||
onItemClicked: () => setTimeMode(key)
|
||||
};
|
||||
|
||||
return key === FIXED_MODE_KEY ? fixedModeMetadata : realTimeModeMetadata;
|
||||
}
|
||||
|
||||
function setTimeMode(_timeMode) {
|
||||
timeContext.value.setMode(_timeMode);
|
||||
}
|
||||
|
||||
function updateTimeMode(_timeMode) {
|
||||
timeMode.value = _timeMode;
|
||||
}
|
||||
|
||||
return {
|
||||
timeMode,
|
||||
getAllModeMetadata,
|
||||
getModeMetadata,
|
||||
isFixedTimeMode,
|
||||
isRealTimeMode
|
||||
};
|
||||
}
|
96
src/plugins/timeConductor/useTimeSystem.js
Normal file
96
src/plugins/timeConductor/useTimeSystem.js
Normal file
@ -0,0 +1,96 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT Web, Copyright (c) 2014-2024, United States Government
|
||||
* as represented by the Administrator of the National Aeronautics and Space
|
||||
* Administration. All rights reserved.
|
||||
*
|
||||
* Open MCT Web 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 Web 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.
|
||||
*****************************************************************************/
|
||||
|
||||
import { onBeforeUnmount, ref, watch } from 'vue';
|
||||
|
||||
import { TIME_CONTEXT_EVENTS } from '../../api/time/constants.js';
|
||||
|
||||
const DEFAULT_DURATION_FORMATTER = 'duration';
|
||||
|
||||
/**
|
||||
* TODO: could probably use a shallowRef for the timeSystem... and all the other components as well.
|
||||
*
|
||||
* Provides a reactive destructuring of the component's current time system,
|
||||
* as well as a function to observe and update the component's time system,
|
||||
* which automatically stops observing when the component is unmounted.
|
||||
*
|
||||
* @param {OpenMCT} openmct the Open MCT API
|
||||
* @param {Array} objectPath The view's objectPath
|
||||
* @returns {{
|
||||
* timeSystemKey: import('vue').Ref<string>,
|
||||
* timeSystemFormatter: import('vue').Ref<() => void>,
|
||||
* timeSystemDurationFormatter: import('vue').Ref<() => void>,
|
||||
* isTimeSystemUTCBased: import('vue').Ref<boolean>
|
||||
* }}
|
||||
*/
|
||||
export function useTimeSystem(openmct, timeContext) {
|
||||
let stopObservingTimeSystem;
|
||||
|
||||
const initialTimeSystem = timeContext.value.getTimeSystem();
|
||||
|
||||
const timeSystemKey = ref(initialTimeSystem.key);
|
||||
const timeSystemFormatter = ref(getFormatter(openmct, initialTimeSystem.timeFormat));
|
||||
const timeSystemDurationFormatter = ref(
|
||||
getFormatter(openmct, initialTimeSystem.durationFormat || DEFAULT_DURATION_FORMATTER)
|
||||
);
|
||||
const isTimeSystemUTCBased = ref(initialTimeSystem.isUTCBased);
|
||||
|
||||
onBeforeUnmount(() => stopObservingTimeSystem?.());
|
||||
|
||||
watch(
|
||||
timeContext,
|
||||
(newContext, oldContext) => {
|
||||
oldContext?.off(TIME_CONTEXT_EVENTS.timeSystemChanged, updateTimeSystem);
|
||||
observeTimeSystem();
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
|
||||
function observeTimeSystem() {
|
||||
timeContext.value.on(TIME_CONTEXT_EVENTS.timeSystemChanged, updateTimeSystem);
|
||||
stopObservingTimeSystem = () =>
|
||||
timeContext.value.off(TIME_CONTEXT_EVENTS.timeSystemChanged, updateTimeSystem);
|
||||
}
|
||||
|
||||
function updateTimeSystem(timeSystem) {
|
||||
timeSystemKey.value = timeSystem.key;
|
||||
timeSystemFormatter.value = getFormatter(openmct, timeSystem.timeFormat);
|
||||
timeSystemDurationFormatter.value = getFormatter(
|
||||
openmct,
|
||||
timeSystem.durationFormat || DEFAULT_DURATION_FORMATTER
|
||||
);
|
||||
isTimeSystemUTCBased.value = timeSystem.isUTCBased;
|
||||
}
|
||||
|
||||
return {
|
||||
timeSystemKey,
|
||||
timeSystemFormatter,
|
||||
timeSystemDurationFormatter,
|
||||
isTimeSystemUTCBased
|
||||
};
|
||||
}
|
||||
|
||||
function getFormatter(openmct, key) {
|
||||
return openmct.telemetry.getValueFormatter({
|
||||
format: key
|
||||
}).formatter;
|
||||
}
|
@ -32,12 +32,13 @@ import moment from 'moment';
|
||||
export default class UTCTimeFormat {
|
||||
constructor() {
|
||||
this.key = 'utc';
|
||||
this.DATE_FORMAT = 'YYYY-MM-DD HH:mm:ss.SSS';
|
||||
this.DATE_DELIMITER = ' ';
|
||||
this.DATE_FORMAT = `YYYY-MM-DD${this.DATE_DELIMITER}HH:mm:ss.SSS`;
|
||||
this.DATE_FORMATS = {
|
||||
PRECISION_DEFAULT: this.DATE_FORMAT,
|
||||
PRECISION_DEFAULT_WITH_ZULU: this.DATE_FORMAT + 'Z',
|
||||
PRECISION_SECONDS: 'YYYY-MM-DD HH:mm:ss',
|
||||
PRECISION_MINUTES: 'YYYY-MM-DD HH:mm',
|
||||
PRECISION_SECONDS: `YYYY-MM-DD${this.DATE_DELIMITER}HH:mm:ss`,
|
||||
PRECISION_MINUTES: `YYYY-MM-DD${this.DATE_DELIMITER}HH:mm`,
|
||||
PRECISION_DAYS: 'YYYY-MM-DD',
|
||||
PRECISION_SECONDS_TIME_ONLY: 'HH:mm:ss',
|
||||
PRECISION_MINUTES_TIME_ONLY: 'HH:mm'
|
||||
@ -85,4 +86,8 @@ export default class UTCTimeFormat {
|
||||
validate(text) {
|
||||
return moment.utc(text, Object.values(this.DATE_FORMATS), true).isValid();
|
||||
}
|
||||
|
||||
getDelimiter() {
|
||||
return this.DATE_DELIMITER;
|
||||
}
|
||||
}
|
||||
|
@ -229,6 +229,8 @@ $glyph-icon-grid-on: '\ea38';
|
||||
$glyph-icon-grid-off: '\ea39';
|
||||
$glyph-icon-camera: '\ea3a';
|
||||
$glyph-icon-folders-collapse: '\ea3b';
|
||||
$glyph-icon-multiline: '\ea3c';
|
||||
$glyph-icon-singleline: '\ea3d';
|
||||
$glyph-icon-activity: '\eb00';
|
||||
$glyph-icon-activity-mode: '\eb01';
|
||||
$glyph-icon-autoflow-tabular: '\eb02';
|
||||
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -123,6 +123,8 @@
|
||||
<glyph unicode="" glyph-name="icon-grid-off" d="M256 344.6l128 157.6v9.8h8l104 128h-112v256h-128v-256h-256v-128h256v-167.4zM184 256h-184v-128h80l104 128zM768 423.4l-128-157.6v-9.8h-8l-104-128h112v-256h128v256h256v128h-256v167.4zM840 512h184v128h-80l-104-128zM832 896l-832-1024h192l832 1024h-192z" />
|
||||
<glyph unicode="" glyph-name="icon-camera" d="M896 640h-128l-128 256h-256l-128-256h-128c-70.601-0.227-127.773-57.399-128-127.978v-512.022c0.227-70.601 57.399-127.773 127.978-128h768.022c70.601 0.227 127.773 57.399 128 127.978v512.022c-0.227 70.601-57.399 127.773-127.978 128h-0.022zM512 32c-141.385 0-256 114.615-256 256s114.615 256 256 256c141.385 0 256-114.615 256-256v0c0-141.385-114.615-256-256-256v0z" />
|
||||
<glyph unicode="" glyph-name="icon-folders-collapse" d="M896 576v-448c-0.215-70.606-57.394-127.785-127.979-128h-576.021c0.215-70.606 57.394-127.785 127.979-128h576.021c70.606 0.215 127.785 57.394 128 127.979v448.021c-0.215 70.606-57.394 127.785-127.979 128h-0.021zM832 192v448c-0.215 70.606-57.394 127.785-127.979 128h-192.021l-101.5 82.74c-24.88 24.9-74.040 45.26-109.24 45.26h-237.26c-35.305-0.102-63.898-28.695-64-63.99v-640.010c0.215-70.606 57.394-127.785 127.979-128h576.021c70.606 0.215 127.785 57.394 128 127.979v0.021zM128 252v516l256-260z" />
|
||||
<glyph unicode="" glyph-name="icon-multiline" d="M832.4 767.4c22.8 0 38-11.8 45-19 7-7 19-22.4 19-45v-640c0-22.8-11.8-38-19-45-7-7-22.4-19-45-19h-640c-22.8 0-38 11.8-45 19-7 7-19 22.4-19 45v640c0 22.8 11.8 38 19 45 7 7 22.4 19 45 19h640zM832.4 895.4h-640c-105.6 0-192-86.4-192-192v-640c0-105.6 86.4-192 192-192h640c105.6 0 192 86.4 192 192v640c0 105.6-86.4 192-192 192v0zM256.4 575.4h512v-128h-512v128zM384.4 319.4h384v-128h-384v128z" />
|
||||
<glyph unicode="" glyph-name="icon-singleline" d="M832.4 895.4h-640c-105.6 0-192-86.4-192-192v-640c0-105.6 86.4-192 192-192h640c105.6 0 192 86.4 192 192v640c0 105.6-86.4 192-192 192zM896.4 447.4h-512v-128h512v-256c0-22.8-11.8-38-19-45-7-7-22.4-19-45-19h-640c-22.8 0-38 11.8-45 19-7 7-19 22.4-19 45v640c0 22.8 11.8 38 19 45 7 7 22.4 19 45 19h640c22.8 0 38-11.8 45-19 7-7 19-22.4 19-45v-256z" />
|
||||
<glyph unicode="" glyph-name="icon-activity" d="M576 832h-256l320-320h-290.256c-44.264 76.516-126.99 128-221.744 128h-128v-512h128c94.754 0 177.48 51.484 221.744 128h290.256l-320-320h256l448 448-448 448z" />
|
||||
<glyph unicode="" glyph-name="icon-activity-mode" d="M512 896c-214.8 0-398.8-132.4-474.8-320h90.8c56.8 0 108-24.8 143-64h241l-192 192h256l320-320-320-320h-256l192 192h-241c-35-39.2-86.2-64-143-64h-90.8c76-187.6 259.8-320 474.8-320 282.8 0 512 229.2 512 512s-229.2 512-512 512z" />
|
||||
<glyph unicode="" glyph-name="icon-autoflow-tabular" d="M192 896c-105.6 0-192-86.4-192-192v-640c0-105.6 86.4-192 192-192h64v1024h-64zM384 896h256v-1024h-256v1024zM832 896h-64v-704h256v512c0 105.6-86.4 192-192 192z" />
|
||||
|
Before Width: | Height: | Size: 70 KiB After Width: | Height: | Size: 71 KiB |
Binary file not shown.
Binary file not shown.
@ -1,16 +1,13 @@
|
||||
/******************************************************** PROGRESS BAR */
|
||||
@keyframes progressIndeterminate {
|
||||
0% {
|
||||
left: 0;
|
||||
width: 0;
|
||||
transform:scaleX(0);
|
||||
}
|
||||
70% {
|
||||
left: 0;
|
||||
width: 100%;
|
||||
90% {
|
||||
transform:scaleX(1);
|
||||
opacity: 1;
|
||||
}
|
||||
100% {
|
||||
left: 100%;
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
@ -24,11 +21,10 @@
|
||||
|
||||
&__bar {
|
||||
background: $colorProgressBar;
|
||||
height: 100%;
|
||||
min-height: $progressBarMinH;
|
||||
transform-origin: left;
|
||||
|
||||
&.--indeterminate {
|
||||
position: absolute;
|
||||
@include abs();
|
||||
animation: progressIndeterminate 1.5s ease-in infinite;
|
||||
}
|
||||
}
|
||||
|
@ -32,21 +32,25 @@
|
||||
class="l-shell__head"
|
||||
:class="{
|
||||
'l-shell__head--expanded': headExpanded,
|
||||
'l-shell__head--minify-indicators': !headExpanded
|
||||
'l-shell__head--minify-indicators': !headExpanded,
|
||||
'l-shell__head--indicators-single-line': !indicatorsMultiline
|
||||
}"
|
||||
>
|
||||
<CreateButton class="l-shell__create-button" />
|
||||
<GrandSearch ref="grand-search" />
|
||||
<StatusIndicators />
|
||||
<StatusIndicators ref="indicatorsComponent" />
|
||||
<button
|
||||
class="l-shell__head__collapse-button c-icon-button"
|
||||
:class="
|
||||
headExpanded
|
||||
? 'l-shell__head__collapse-button--collapse'
|
||||
: 'l-shell__head__collapse-button--expand'
|
||||
"
|
||||
:aria-label="`Click to ${headExpanded ? 'collapse' : 'expand'} items`"
|
||||
:title="`Click to ${headExpanded ? 'collapse' : 'expand'} items`"
|
||||
class="l-shell__head__button"
|
||||
:class="indicatorsMultilineCssClass"
|
||||
:aria-label="indicatorsMultilineLabel"
|
||||
:title="indicatorsMultilineLabel"
|
||||
@click="toggleIndicatorsMultiline"
|
||||
></button>
|
||||
<button
|
||||
class="l-shell__head__button"
|
||||
:class="headExpanded ? 'icon-items-collapse' : 'icon-items-expand'"
|
||||
:aria-label="`Show ${headExpanded ? 'icon only' : 'icon and name'}`"
|
||||
:title="`Show ${headExpanded ? 'icon only' : 'icon and name'}`"
|
||||
@click="toggleShellHead"
|
||||
></button>
|
||||
<NotificationBanner />
|
||||
@ -167,6 +171,8 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { computed, nextTick, onMounted, onUnmounted, ref } from 'vue';
|
||||
|
||||
import ObjectView from '../components/ObjectView.vue';
|
||||
import Inspector from '../inspector/InspectorPanel.vue';
|
||||
import Toolbar from '../toolbar/ToolbarContainer.vue';
|
||||
@ -181,6 +187,10 @@ import GrandSearch from './search/GrandSearch.vue';
|
||||
import NotificationBanner from './status-bar/NotificationBanner.vue';
|
||||
import StatusIndicators from './status-bar/StatusIndicators.vue';
|
||||
|
||||
const SHELL_HEAD_LOCAL_STORAGE_KEY = 'openmct-shell-head';
|
||||
const DEFAULT_HEAD_EXPANDED = true;
|
||||
const DEFAULT_INDICATORS_MULTILINE = true;
|
||||
|
||||
export default {
|
||||
components: {
|
||||
Inspector,
|
||||
@ -198,13 +208,120 @@ export default {
|
||||
RecentObjectsList
|
||||
},
|
||||
inject: ['openmct'],
|
||||
data: function () {
|
||||
let storedHeadProps = window.localStorage.getItem('openmct-shell-head');
|
||||
let headExpanded = true;
|
||||
if (storedHeadProps) {
|
||||
headExpanded = JSON.parse(storedHeadProps).expanded;
|
||||
setup() {
|
||||
let resizeObserver;
|
||||
let element;
|
||||
|
||||
const storedHeadProps = localStorage.getItem(SHELL_HEAD_LOCAL_STORAGE_KEY);
|
||||
const storedHeadPropsObject = JSON.parse(storedHeadProps);
|
||||
const storedHeadExpanded = storedHeadPropsObject?.expanded;
|
||||
const storedIndicatorsMultiline = storedHeadPropsObject?.multiline;
|
||||
|
||||
// template ref of StatusIndicators component
|
||||
const indicatorsComponent = ref(null);
|
||||
|
||||
const width = ref(null);
|
||||
const scrollWidth = ref(null);
|
||||
const headExpanded = ref(storedHeadExpanded ?? DEFAULT_HEAD_EXPANDED);
|
||||
const indicatorsMultiline = ref(storedIndicatorsMultiline ?? DEFAULT_INDICATORS_MULTILINE);
|
||||
|
||||
const isOverflowing = computed(() => scrollWidth.value > width.value);
|
||||
const indicatorsMultilineCssClass = computed(() => {
|
||||
const multilineClass = indicatorsMultiline.value ? 'icon-singleline' : 'icon-multiline';
|
||||
const overflowingClass =
|
||||
isOverflowing.value && !indicatorsMultiline.value
|
||||
? 'c-button c-button--major'
|
||||
: 'c-icon-button';
|
||||
return `${multilineClass} ${overflowingClass}`;
|
||||
});
|
||||
const indicatorsMultilineLabel = computed(() => {
|
||||
return `Display as ${indicatorsMultiline.value ? 'single line' : 'multiple lines'}`;
|
||||
});
|
||||
|
||||
const initialHeadProps = JSON.stringify({
|
||||
expanded: headExpanded.value,
|
||||
multiline: indicatorsMultiline.value
|
||||
});
|
||||
|
||||
if (initialHeadProps !== storedHeadProps) {
|
||||
localStorage.setItem(SHELL_HEAD_LOCAL_STORAGE_KEY, initialHeadProps);
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
resizeObserver = new ResizeObserver((entries) => {
|
||||
width.value = entries[0].target.clientWidth;
|
||||
scrollWidth.value = entries[0].target.scrollWidth;
|
||||
});
|
||||
|
||||
// indicatorsContainer is a template ref inside of indicatorsComponent
|
||||
element = indicatorsComponent.value.$refs.indicatorsContainer;
|
||||
|
||||
if (!indicatorsMultiline.value) {
|
||||
observeIndicatorsOverflow();
|
||||
}
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
resizeObserver.disconnect();
|
||||
});
|
||||
|
||||
function observeIndicatorsOverflow() {
|
||||
resizeObserver.observe(element);
|
||||
}
|
||||
|
||||
function unObserveIndicatorsOverflow() {
|
||||
resizeObserver.unobserve(element);
|
||||
}
|
||||
|
||||
function checkIndicatorsElementWidths() {
|
||||
if (!indicatorsMultiline.value) {
|
||||
width.value = element.clientWidth;
|
||||
scrollWidth.value = element.scrollWidth;
|
||||
}
|
||||
}
|
||||
|
||||
async function toggleShellHead() {
|
||||
headExpanded.value = !headExpanded.value;
|
||||
setLocalStorageShellHead();
|
||||
|
||||
// nextTick is used because the element width on toggle is updated using css
|
||||
await nextTick();
|
||||
checkIndicatorsElementWidths();
|
||||
}
|
||||
|
||||
function toggleIndicatorsMultiline() {
|
||||
indicatorsMultiline.value = !indicatorsMultiline.value;
|
||||
setLocalStorageShellHead();
|
||||
|
||||
if (indicatorsMultiline.value) {
|
||||
unObserveIndicatorsOverflow();
|
||||
} else {
|
||||
observeIndicatorsOverflow();
|
||||
}
|
||||
}
|
||||
|
||||
function setLocalStorageShellHead() {
|
||||
localStorage.setItem(
|
||||
SHELL_HEAD_LOCAL_STORAGE_KEY,
|
||||
JSON.stringify({
|
||||
expanded: headExpanded.value,
|
||||
multiline: indicatorsMultiline.value
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
return {
|
||||
indicatorsComponent,
|
||||
isOverflowing,
|
||||
headExpanded,
|
||||
indicatorsMultiline,
|
||||
indicatorsMultilineCssClass,
|
||||
indicatorsMultilineLabel,
|
||||
toggleIndicatorsMultiline,
|
||||
toggleShellHead
|
||||
};
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
fullScreen: false,
|
||||
conductorComponent: undefined,
|
||||
@ -213,7 +330,6 @@ export default {
|
||||
actionCollection: undefined,
|
||||
triggerSync: false,
|
||||
triggerReset: false,
|
||||
headExpanded,
|
||||
isResizing: false,
|
||||
disableClearButton: false
|
||||
};
|
||||
@ -261,16 +377,6 @@ export default {
|
||||
document.msExitFullscreen();
|
||||
}
|
||||
},
|
||||
toggleShellHead() {
|
||||
this.headExpanded = !this.headExpanded;
|
||||
|
||||
window.localStorage.setItem(
|
||||
'openmct-shell-head',
|
||||
JSON.stringify({
|
||||
expanded: this.headExpanded
|
||||
})
|
||||
);
|
||||
},
|
||||
fullScreenToggle() {
|
||||
if (this.fullScreen) {
|
||||
this.fullScreen = false;
|
||||
|
@ -24,6 +24,7 @@
|
||||
<div class="l-browse-bar__start">
|
||||
<button
|
||||
v-if="hasParent"
|
||||
aria-label="Navigate up to parent"
|
||||
class="l-browse-bar__nav-to-parent-button c-icon-button c-icon-button--major icon-arrow-nav-to-parent"
|
||||
title="Navigate up to parent"
|
||||
@click="goToParent"
|
||||
|
@ -213,27 +213,14 @@
|
||||
align-items: center;
|
||||
background: $colorHeadBg;
|
||||
display: grid;
|
||||
grid-template-columns: min-content 1fr 3fr repeat(3, min-content);
|
||||
grid-template-columns: min-content 1fr 3fr repeat(4, min-content);
|
||||
grid-column-gap: $interiorMargin;
|
||||
min-height: 34px;
|
||||
padding: $interiorMargin $interiorMargin + 2;
|
||||
|
||||
.l-shell__head__collapse-button {
|
||||
.l-shell__head__button {
|
||||
color: $colorBtnMajorBg;
|
||||
flex: 0 0 auto;
|
||||
font-size: 0.9em;
|
||||
|
||||
&--collapse {
|
||||
&:before {
|
||||
content: $glyph-icon-items-collapse;
|
||||
}
|
||||
}
|
||||
|
||||
&--expand {
|
||||
&:before {
|
||||
content: $glyph-icon-items-expand;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&-section {
|
||||
@ -271,14 +258,21 @@
|
||||
}
|
||||
|
||||
&__indicators {
|
||||
// Style as multiline by default
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
font-size: 11px;
|
||||
min-height: 25px;
|
||||
justify-content: flex-end;
|
||||
|
||||
.l-shell__head--expanded & {
|
||||
// Force elements to wrap down when width constrained
|
||||
height: 24px;
|
||||
.l-shell__head--indicators-single-line & {
|
||||
flex-wrap: nowrap;
|
||||
justify-content: flex-start; // Overflow detection doesn't work with flex-end.
|
||||
overflow: hidden;
|
||||
|
||||
> *:first-child {
|
||||
margin-left: auto; // Mimics justify-content: flex-end when in single line mode.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -17,7 +17,7 @@
|
||||
at runtime from the About dialog for additional information.
|
||||
-->
|
||||
<template>
|
||||
<div class="l-shell__head-section l-shell__indicators">
|
||||
<div ref="indicatorsContainer" class="l-shell__head-section l-shell__indicators">
|
||||
<component
|
||||
:is="indicator.value.vueComponent"
|
||||
v-for="indicator in sortedIndicators"
|
||||
@ -28,9 +28,15 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { shallowRef } from 'vue';
|
||||
import { defineExpose, ref, shallowRef } from 'vue';
|
||||
|
||||
export default {
|
||||
inject: ['openmct'],
|
||||
setup() {
|
||||
const indicatorsContainer = ref(null);
|
||||
|
||||
defineExpose({ indicatorsContainer });
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
indicators: this.openmct.indicators.getIndicatorObjectsByPriority().map(shallowRef)
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT, Copyright (c) 2014-2024, United States Government
|
||||
* Open MCT, Copyright (c) 2014-2023, United States Government
|
||||
* as represented by the Administrator of the National Aeronautics and Space
|
||||
* Administration. All rights reserved.
|
||||
*
|
||||
@ -49,20 +49,19 @@ class VisibilityObserver {
|
||||
* Constructs a VisibilityObserver instance to manage visibility-based requestAnimationFrame calls.
|
||||
*
|
||||
* @param {HTMLElement} element - The DOM element to observe for visibility changes.
|
||||
* @param {HTMLElement} rootContainer - The DOM element that is the root of the viewport.
|
||||
* @throws {Error} If element is not provided.
|
||||
*/
|
||||
constructor(element, rootContainer) {
|
||||
if (!element || !rootContainer) {
|
||||
throw new Error(`VisibilityObserver must be created with an element and a rootContainer.`);
|
||||
constructor(element) {
|
||||
if (!element) {
|
||||
throw new Error(`VisibilityObserver must be created with an element`);
|
||||
}
|
||||
// set the id to some random 4 letters
|
||||
this.id = Math.random().toString(36).substring(2, 6);
|
||||
this.#element = element;
|
||||
this.isIntersecting = true;
|
||||
this.calledOnce = false;
|
||||
const options = {
|
||||
root: rootContainer
|
||||
};
|
||||
this.#observer = new IntersectionObserver(this.#observerCallback, options);
|
||||
|
||||
this.#observer = new IntersectionObserver(this.#observerCallback);
|
||||
this.#observer.observe(this.#element);
|
||||
this.lastUnfiredFunc = null;
|
||||
this.renderWhenVisible = this.renderWhenVisible.bind(this);
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user