mirror of
https://github.com/nasa/openmct.git
synced 2025-04-24 13:06:01 +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
|
- image: mcr.microsoft.com/playwright:v1.45.2-focal
|
||||||
environment:
|
environment:
|
||||||
NODE_ENV: development # Needed to ensure 'dist' folder created and devDependencies installed
|
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_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_LOGLEVEL: "debug" # Enable DEBUG level logging for Percy (Issue: https://github.com/nasa/openmct/issues/5742)
|
||||||
PERCY_PARALLEL_TOTAL: 2
|
PERCY_PARALLEL_TOTAL: 2
|
||||||
ubuntu:
|
ubuntu:
|
||||||
machine:
|
machine:
|
||||||
@ -17,7 +17,7 @@ executors:
|
|||||||
docker_layer_caching: true
|
docker_layer_caching: true
|
||||||
commands:
|
commands:
|
||||||
build_and_install:
|
build_and_install:
|
||||||
description: 'All steps used to build and install.'
|
description: "All steps used to build and install."
|
||||||
parameters:
|
parameters:
|
||||||
node-version:
|
node-version:
|
||||||
type: string
|
type: string
|
||||||
@ -27,7 +27,7 @@ commands:
|
|||||||
node-version: << parameters.node-version >>
|
node-version: << parameters.node-version >>
|
||||||
- node/install-packages
|
- node/install-packages
|
||||||
generate_and_store_version_and_filesystem_artifacts:
|
generate_and_store_version_and_filesystem_artifacts:
|
||||||
description: 'Track important packages and files'
|
description: "Track important packages and files"
|
||||||
steps:
|
steps:
|
||||||
- run: |
|
- run: |
|
||||||
[[ $EUID -ne 0 ]] && (sudo mkdir -p /tmp/artifacts && sudo chmod 777 /tmp/artifacts) || (mkdir -p /tmp/artifacts && chmod 777 /tmp/artifacts)
|
[[ $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:
|
- store_artifacts:
|
||||||
path: /tmp/artifacts/
|
path: /tmp/artifacts/
|
||||||
generate_e2e_code_cov_report:
|
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:
|
parameters:
|
||||||
suite:
|
suite:
|
||||||
type: string
|
type: string
|
||||||
@ -102,7 +102,7 @@ jobs:
|
|||||||
node-version: lts/hydrogen
|
node-version: lts/hydrogen
|
||||||
- when: #Only install chrome-beta when running the 'full' suite to save $$$
|
- when: #Only install chrome-beta when running the 'full' suite to save $$$
|
||||||
condition:
|
condition:
|
||||||
equal: ['full', <<parameters.suite>>]
|
equal: ["full", <<parameters.suite>>]
|
||||||
steps:
|
steps:
|
||||||
- run: npx playwright install chrome-beta
|
- run: npx playwright install chrome-beta
|
||||||
- run:
|
- run:
|
||||||
@ -259,6 +259,8 @@ workflows:
|
|||||||
- visual-a11y:
|
- visual-a11y:
|
||||||
name: visual-a11y-ci
|
name: visual-a11y-ci
|
||||||
suite: ci
|
suite: ci
|
||||||
|
- perf-test
|
||||||
|
- mem-test
|
||||||
|
|
||||||
the-nightly: #These jobs do not run on PRs, but against master at night
|
the-nightly: #These jobs do not run on PRs, but against master at night
|
||||||
jobs:
|
jobs:
|
||||||
@ -282,7 +284,7 @@ workflows:
|
|||||||
- e2e-couchdb
|
- e2e-couchdb
|
||||||
triggers:
|
triggers:
|
||||||
- schedule:
|
- schedule:
|
||||||
cron: '0 0 * * *'
|
cron: "0 0 * * *"
|
||||||
filters:
|
filters:
|
||||||
branches:
|
branches:
|
||||||
only:
|
only:
|
||||||
|
@ -20,6 +20,13 @@ module.exports = {
|
|||||||
'playwright/no-get-by-title': 'error',
|
'playwright/no-get-by-title': 'error',
|
||||||
'playwright/prefer-comparison-matcher': '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.use({ failOnConsoleError: false });
|
||||||
test('Editing object properties should generate a single persistence operation', async ({
|
test('Editing object properties should generate a single persistence operation', async ({
|
||||||
page
|
page
|
||||||
@ -170,7 +170,7 @@ test.describe('Persistence operations @couchdb', () => {
|
|||||||
})
|
})
|
||||||
.toEqual(1);
|
.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,
|
page,
|
||||||
openmctConfig
|
openmctConfig
|
||||||
}) => {
|
}) => {
|
||||||
|
@ -429,7 +429,7 @@ test.describe('Display Layout', () => {
|
|||||||
await expect(page.getByText('2021-12-30 01:11:00.000Z')).toBeHidden();
|
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
|
page
|
||||||
}) => {
|
}) => {
|
||||||
await setFixedTimeMode(page);
|
await setFixedTimeMode(page);
|
||||||
|
@ -24,20 +24,26 @@
|
|||||||
This test suite is dedicated to tests which verify the basic operations surrounding Notebooks with CouchDB.
|
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 { createDomainObjectWithDefaults } from '../../../../appActions.js';
|
||||||
import * as nbUtils from '../../../../helper/notebookUtils.js';
|
import * as nbUtils from '../../../../helper/notebookUtils.js';
|
||||||
import { expect, test } from '../../../../pluginFixtures.js';
|
import { expect, test } from '../../../../pluginFixtures.js';
|
||||||
|
|
||||||
test.describe('Notebook Tests with CouchDB @couchdb', () => {
|
test.describe('Notebook Tests with CouchDB @couchdb @network', () => {
|
||||||
let testNotebook;
|
let testNotebook;
|
||||||
|
|
||||||
test.beforeEach(async ({ page }) => {
|
test.beforeEach(async ({ page }) => {
|
||||||
//Navigate to baseURL
|
// Navigate to baseURL
|
||||||
await page.goto('./', { waitUntil: 'domcontentloaded' });
|
await page.goto('./', { waitUntil: 'networkidle' });
|
||||||
|
|
||||||
// Create Notebook
|
// Create Notebook
|
||||||
testNotebook = await createDomainObjectWithDefaults(page, { type: '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 }) => {
|
test('Inspect Notebook Entry Network Requests', async ({ page }) => {
|
||||||
@ -58,7 +64,7 @@ test.describe('Notebook Tests with CouchDB @couchdb', () => {
|
|||||||
page.getByLabel('Add Page').click()
|
page.getByLabel('Add Page').click()
|
||||||
]);
|
]);
|
||||||
// Ensures that there are no other network requests
|
// Ensures that there are no other network requests
|
||||||
await page.waitForLoadState('domcontentloaded');
|
await page.waitForLoadState('networkidle');
|
||||||
|
|
||||||
// Assert that only two requests are made
|
// Assert that only two requests are made
|
||||||
// Network Requests are:
|
// Network Requests are:
|
||||||
@ -77,7 +83,7 @@ test.describe('Notebook Tests with CouchDB @couchdb', () => {
|
|||||||
// 2) The shared worker event from 👆 POST request
|
// 2) The shared worker event from 👆 POST request
|
||||||
notebookElementsRequests = [];
|
notebookElementsRequests = [];
|
||||||
await nbUtils.enterTextEntry(page, 'First Entry');
|
await nbUtils.enterTextEntry(page, 'First Entry');
|
||||||
await page.waitForLoadState('domcontentloaded');
|
await page.waitForLoadState('networkidle');
|
||||||
expect(notebookElementsRequests.length).toBeLessThanOrEqual(2);
|
expect(notebookElementsRequests.length).toBeLessThanOrEqual(2);
|
||||||
|
|
||||||
// Add some tags
|
// Add some tags
|
||||||
@ -141,7 +147,7 @@ test.describe('Notebook Tests with CouchDB @couchdb', () => {
|
|||||||
// 4) The shared worker event from 👆 POST request
|
// 4) The shared worker event from 👆 POST request
|
||||||
notebookElementsRequests = [];
|
notebookElementsRequests = [];
|
||||||
await nbUtils.enterTextEntry(page, 'Fourth Entry');
|
await nbUtils.enterTextEntry(page, 'Fourth Entry');
|
||||||
page.waitForLoadState('domcontentloaded');
|
page.waitForLoadState('networkidle');
|
||||||
|
|
||||||
expect(filterNonFetchRequests(notebookElementsRequests).length).toBeLessThanOrEqual(4);
|
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
|
// 4) The shared worker event from 👆 POST request
|
||||||
notebookElementsRequests = [];
|
notebookElementsRequests = [];
|
||||||
await nbUtils.enterTextEntry(page, 'Fifth Entry');
|
await nbUtils.enterTextEntry(page, 'Fifth Entry');
|
||||||
page.waitForLoadState('domcontentloaded');
|
page.waitForLoadState('networkidle');
|
||||||
|
|
||||||
expect(filterNonFetchRequests(notebookElementsRequests).length).toBeLessThanOrEqual(4);
|
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
|
// 4) The shared worker event from 👆 POST request
|
||||||
notebookElementsRequests = [];
|
notebookElementsRequests = [];
|
||||||
await nbUtils.enterTextEntry(page, 'Sixth Entry');
|
await nbUtils.enterTextEntry(page, 'Sixth Entry');
|
||||||
page.waitForLoadState('domcontentloaded');
|
page.waitForLoadState('networkidle');
|
||||||
|
|
||||||
expect(filterNonFetchRequests(notebookElementsRequests).length).toBeLessThanOrEqual(4);
|
expect(filterNonFetchRequests(notebookElementsRequests).length).toBeLessThanOrEqual(4);
|
||||||
});
|
});
|
||||||
@ -227,7 +233,7 @@ async function addTagAndAwaitNetwork(page, tagName) {
|
|||||||
page.locator(`[aria-label="Autocomplete Options"] >> text=${tagName}`).click(),
|
page.locator(`[aria-label="Autocomplete Options"] >> text=${tagName}`).click(),
|
||||||
expect(page.locator(`[aria-label="Tag"]:has-text("${tagName}")`)).toBeVisible()
|
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 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);
|
await page.goto(tabsView.url);
|
||||||
|
|
||||||
// select first tab
|
// 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
|
// ensure table header visible
|
||||||
await expect(page.getByRole('searchbox', { name: 'message filter input' })).toBeVisible();
|
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
|
// no canvas (i.e., sine wave generator) in the document should be visible
|
||||||
await expect(page.locator('canvas[id=webglContext]')).toBeHidden();
|
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', () => {
|
test.describe('Tabs View CRUD', () => {
|
||||||
|
@ -168,7 +168,7 @@ test.describe('Grand Search', () => {
|
|||||||
await expect(page.getByText('No results found')).toBeVisible();
|
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
|
// Create a folder object
|
||||||
const folderName = uuid();
|
const folderName = uuid();
|
||||||
await createDomainObjectWithDefaults(page, {
|
await createDomainObjectWithDefaults(page, {
|
||||||
@ -191,7 +191,7 @@ test.describe('Grand Search', () => {
|
|||||||
await expect(searchResults).toContainText(folderName);
|
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({
|
test.info().annotations.push({
|
||||||
type: 'issue',
|
type: 'issue',
|
||||||
description: 'https://github.com/nasa/openmct/issues/6179'
|
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');
|
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;
|
let requestWasAborted = false;
|
||||||
await createObjectsForSearch(page);
|
await createObjectsForSearch(page);
|
||||||
page.on('requestfailed', (request) => {
|
page.on('requestfailed', (request) => {
|
||||||
|
@ -28,7 +28,7 @@ test.describe('Main Tree', () => {
|
|||||||
await page.goto('./', { waitUntil: 'domcontentloaded' });
|
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
|
page
|
||||||
}) => {
|
}) => {
|
||||||
test.info().annotations.push({
|
test.info().annotations.push({
|
||||||
@ -88,7 +88,7 @@ test.describe('Main Tree', () => {
|
|||||||
).toBeVisible();
|
).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
|
page
|
||||||
}) => {
|
}) => {
|
||||||
test.info().annotations.push({
|
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
|
page
|
||||||
}) => {
|
}) => {
|
||||||
let requestWasAborted = false;
|
let requestWasAborted = false;
|
||||||
|
@ -164,7 +164,7 @@ test.describe('Performance tests', () => {
|
|||||||
await page.evaluate(() => window.performance.mark('background-image-visible'));
|
await page.evaluate(() => window.performance.mark('background-image-visible'));
|
||||||
|
|
||||||
// Get Current number of images in thumbstrip
|
// 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();
|
const thumbCount = await page.locator('.c-imagery__thumb').count();
|
||||||
console.log('number of thumbs rendered ' + thumbCount);
|
console.log('number of thumbs rendered ' + thumbCount);
|
||||||
await page.locator('.c-imagery__thumb').last().click();
|
await page.locator('.c-imagery__thumb').last().click();
|
||||||
|
@ -64,7 +64,7 @@ test.describe('Tabs View', () => {
|
|||||||
page.goto(tabsView.url);
|
page.goto(tabsView.url);
|
||||||
|
|
||||||
// select first tab
|
// 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
|
// ensure table header visible
|
||||||
await expect(page.getByRole('searchbox', { name: 'message filter input' })).toBeVisible();
|
await expect(page.getByRole('searchbox', { name: 'message filter input' })).toBeVisible();
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "openmct",
|
"name": "openmct",
|
||||||
"version": "4.0.0-next",
|
"version": "4.0.0",
|
||||||
"description": "The Open MCT core platform",
|
"description": "The Open MCT core platform",
|
||||||
"module": "dist/openmct.js",
|
"module": "dist/openmct.js",
|
||||||
"main": "dist/openmct.js",
|
"main": "dist/openmct.js",
|
||||||
@ -160,4 +160,4 @@
|
|||||||
"keywords": [
|
"keywords": [
|
||||||
"nasa"
|
"nasa"
|
||||||
]
|
]
|
||||||
}
|
}
|
@ -231,26 +231,20 @@ export default class TelemetryAPI {
|
|||||||
* @returns {TelemetryRequestOptions} the options, with defaults filled in
|
* @returns {TelemetryRequestOptions} the options, with defaults filled in
|
||||||
*/
|
*/
|
||||||
standardizeRequestOptions(options = {}) {
|
standardizeRequestOptions(options = {}) {
|
||||||
if (!Object.hasOwn(options, 'start')) {
|
if (!Object.hasOwn(options, 'timeContext')) {
|
||||||
const bounds = options.timeContext?.getBounds();
|
options.timeContext = this.openmct.time;
|
||||||
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, 'domain')) {
|
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;
|
return options;
|
||||||
|
@ -269,36 +269,40 @@ describe('Telemetry API', () => {
|
|||||||
|
|
||||||
await telemetryAPI.request(domainObject);
|
await telemetryAPI.request(domainObject);
|
||||||
const { signal } = new AbortController();
|
const { signal } = new AbortController();
|
||||||
expect(telemetryProvider.supportsRequest).toHaveBeenCalledWith(jasmine.any(Object), {
|
expect(telemetryProvider.supportsRequest).toHaveBeenCalledWith(domainObject, {
|
||||||
signal,
|
signal,
|
||||||
start: 0,
|
start: 0,
|
||||||
end: 1,
|
end: 1,
|
||||||
domain: 'system'
|
domain: 'system',
|
||||||
|
timeContext: openmct.time
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(telemetryProvider.request).toHaveBeenCalledWith(jasmine.any(Object), {
|
expect(telemetryProvider.request).toHaveBeenCalledWith(domainObject, {
|
||||||
signal,
|
signal,
|
||||||
start: 0,
|
start: 0,
|
||||||
end: 1,
|
end: 1,
|
||||||
domain: 'system'
|
domain: 'system',
|
||||||
|
timeContext: openmct.time
|
||||||
});
|
});
|
||||||
|
|
||||||
telemetryProvider.supportsRequest.calls.reset();
|
telemetryProvider.supportsRequest.calls.reset();
|
||||||
telemetryProvider.request.calls.reset();
|
telemetryProvider.request.calls.reset();
|
||||||
|
|
||||||
await telemetryAPI.request(domainObject, {});
|
await telemetryAPI.request(domainObject, {});
|
||||||
expect(telemetryProvider.supportsRequest).toHaveBeenCalledWith(jasmine.any(Object), {
|
expect(telemetryProvider.supportsRequest).toHaveBeenCalledWith(domainObject, {
|
||||||
signal,
|
signal,
|
||||||
start: 0,
|
start: 0,
|
||||||
end: 1,
|
end: 1,
|
||||||
domain: 'system'
|
domain: 'system',
|
||||||
|
timeContext: openmct.time
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(telemetryProvider.request).toHaveBeenCalledWith(jasmine.any(Object), {
|
expect(telemetryProvider.request).toHaveBeenCalledWith(domainObject, {
|
||||||
signal,
|
signal,
|
||||||
start: 0,
|
start: 0,
|
||||||
end: 1,
|
end: 1,
|
||||||
domain: 'system'
|
domain: 'system',
|
||||||
|
timeContext: openmct.time
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -313,18 +317,20 @@ describe('Telemetry API', () => {
|
|||||||
domain: 'someDomain'
|
domain: 'someDomain'
|
||||||
});
|
});
|
||||||
const { signal } = new AbortController();
|
const { signal } = new AbortController();
|
||||||
expect(telemetryProvider.supportsRequest).toHaveBeenCalledWith(jasmine.any(Object), {
|
expect(telemetryProvider.supportsRequest).toHaveBeenCalledWith(domainObject, {
|
||||||
start: 20,
|
start: 20,
|
||||||
end: 30,
|
end: 30,
|
||||||
domain: 'someDomain',
|
domain: 'someDomain',
|
||||||
signal
|
signal,
|
||||||
|
timeContext: openmct.time
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(telemetryProvider.request).toHaveBeenCalledWith(jasmine.any(Object), {
|
expect(telemetryProvider.request).toHaveBeenCalledWith(domainObject, {
|
||||||
start: 20,
|
start: 20,
|
||||||
end: 30,
|
end: 30,
|
||||||
domain: 'someDomain',
|
domain: 'someDomain',
|
||||||
signal
|
signal,
|
||||||
|
timeContext: openmct.time
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
describe('telemetry batching support', () => {
|
describe('telemetry batching support', () => {
|
||||||
|
@ -62,9 +62,6 @@ export default class TelemetryCollection extends EventEmitter {
|
|||||||
this.futureBuffer = [];
|
this.futureBuffer = [];
|
||||||
this.parseTime = undefined;
|
this.parseTime = undefined;
|
||||||
this.metadata = this.openmct.telemetry.getMetadata(domainObject);
|
this.metadata = this.openmct.telemetry.getMetadata(domainObject);
|
||||||
if (!Object.hasOwn(options, 'timeContext')) {
|
|
||||||
options.timeContext = this.openmct.time;
|
|
||||||
}
|
|
||||||
this.options = options;
|
this.options = options;
|
||||||
this.unsubscribe = undefined;
|
this.unsubscribe = undefined;
|
||||||
this.pageState = undefined;
|
this.pageState = undefined;
|
||||||
@ -84,6 +81,9 @@ export default class TelemetryCollection extends EventEmitter {
|
|||||||
this._error(LOADED_ERROR);
|
this._error(LOADED_ERROR);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!Object.hasOwn(this.options, 'timeContext')) {
|
||||||
|
this.options.timeContext = this.openmct.time;
|
||||||
|
}
|
||||||
this._setTimeSystem(this.options.timeContext.getTimeSystem());
|
this._setTimeSystem(this.options.timeContext.getTimeSystem());
|
||||||
this.lastBounds = this.options.timeContext.getBounds();
|
this.lastBounds = this.options.timeContext.getBounds();
|
||||||
this._watchBounds();
|
this._watchBounds();
|
||||||
@ -128,7 +128,7 @@ export default class TelemetryCollection extends EventEmitter {
|
|||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
async _requestHistoricalTelemetry() {
|
async _requestHistoricalTelemetry() {
|
||||||
let options = this.openmct.telemetry.standardizeRequestOptions({ ...this.options });
|
const options = this.openmct.telemetry.standardizeRequestOptions({ ...this.options });
|
||||||
const historicalProvider = this.openmct.telemetry.findRequestProvider(
|
const historicalProvider = this.openmct.telemetry.findRequestProvider(
|
||||||
this.domainObject,
|
this.domainObject,
|
||||||
options
|
options
|
||||||
|
@ -321,8 +321,16 @@ class IndependentTimeContext extends TimeContext {
|
|||||||
return this.upstreamTimeContext.setMode(...arguments);
|
return this.upstreamTimeContext.setMode(...arguments);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mode === MODES.realtime && this.activeClock === undefined) {
|
if (mode === MODES.realtime) {
|
||||||
throw `Unknown clock. Has a clock been registered with 'addClock'?`;
|
// 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) {
|
if (mode !== this.mode) {
|
||||||
|
@ -134,30 +134,30 @@ class TimeAPI extends GlobalTimeContext {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Get or set an independent time context which follows the TimeAPI timeSystem,
|
* Get or set an independent time context which follows the TimeAPI timeSystem,
|
||||||
* but with different offsets for a given domain object
|
* but with different bounds for a given domain object
|
||||||
* @param {string} key The identifier key of the domain object these offsets are set for
|
* @param {string} keyString The keyString identifier 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 {TimeConductorBounds | ClockOffsets} boundsOrOffsets either bounds if in fixed mode, or offsets if in realtime mode
|
||||||
* @param {key | string} clockKey the real time clock key currently in use
|
* @param {string} clockKey the key for the real time clock to use
|
||||||
*/
|
*/
|
||||||
addIndependentContext(key, value, clockKey) {
|
addIndependentContext(keyString, boundsOrOffsets, clockKey) {
|
||||||
let timeContext = this.getIndependentContext(key);
|
let timeContext = this.getIndependentContext(keyString);
|
||||||
|
|
||||||
//stop following upstream time context since the view has it's own
|
//stop following upstream time context since the view has it's own
|
||||||
timeContext.resetContext();
|
timeContext.resetContext();
|
||||||
|
|
||||||
if (clockKey) {
|
if (clockKey) {
|
||||||
timeContext.setClock(clockKey);
|
timeContext.setClock(clockKey);
|
||||||
timeContext.setMode(REALTIME_MODE_KEY, value);
|
timeContext.setMode(REALTIME_MODE_KEY, boundsOrOffsets);
|
||||||
} else {
|
} 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
|
// 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 () => {
|
return () => {
|
||||||
//follow any upstream time context
|
//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) {
|
} else if (bounds.start > bounds.end) {
|
||||||
return {
|
return {
|
||||||
valid: false,
|
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 TEXT_LEFT_PADDING = 5;
|
||||||
const ROW_PADDING = 5;
|
const ROW_PADDING = 5;
|
||||||
const SWIMLANE_PADDING = 3;
|
const SWIMLANE_PADDING = 3;
|
||||||
const RESIZE_POLL_INTERVAL = 200;
|
|
||||||
const ROW_HEIGHT = 22;
|
const ROW_HEIGHT = 22;
|
||||||
const MAX_TEXT_WIDTH = 300;
|
const MAX_TEXT_WIDTH = 300;
|
||||||
const MIN_ACTIVITY_WIDTH = 2;
|
const MIN_ACTIVITY_WIDTH = 2;
|
||||||
@ -143,13 +142,15 @@ export default {
|
|||||||
this.canvasContext = canvas.getContext('2d');
|
this.canvasContext = canvas.getContext('2d');
|
||||||
this.setDimensions();
|
this.setDimensions();
|
||||||
this.setTimeContext();
|
this.setTimeContext();
|
||||||
this.resizeTimer = setInterval(this.resize, RESIZE_POLL_INTERVAL);
|
|
||||||
this.handleConfigurationChange(this.configuration);
|
this.handleConfigurationChange(this.configuration);
|
||||||
this.planViewConfiguration.on('change', this.handleConfigurationChange);
|
this.planViewConfiguration.on('change', this.handleConfigurationChange);
|
||||||
this.loadComposition();
|
this.loadComposition();
|
||||||
|
|
||||||
|
this.resizeObserver = new ResizeObserver(this.resize);
|
||||||
|
this.resizeObserver.observe(this.$refs.plan);
|
||||||
},
|
},
|
||||||
beforeUnmount() {
|
beforeUnmount() {
|
||||||
clearInterval(this.resizeTimer);
|
this.resizeObserver.disconnect();
|
||||||
this.stopFollowingTimeContext();
|
this.stopFollowingTimeContext();
|
||||||
if (this.unlisten) {
|
if (this.unlisten) {
|
||||||
this.unlisten();
|
this.unlisten();
|
||||||
|
@ -575,6 +575,7 @@ export default {
|
|||||||
this.buildCanvasElements();
|
this.buildCanvasElements();
|
||||||
this.drawAPI = DrawLoader.getFallbackDrawAPI(this.canvas, this.overlay);
|
this.drawAPI = DrawLoader.getFallbackDrawAPI(this.canvas, this.overlay);
|
||||||
this.$emit('plot-reinitialize-canvas');
|
this.$emit('plot-reinitialize-canvas');
|
||||||
|
console.warn(`📈 fallback to 2D canvas`);
|
||||||
},
|
},
|
||||||
removeChartElement(series) {
|
removeChartElement(series) {
|
||||||
const elements = this.seriesElements.get(toRaw(series));
|
const elements = this.seriesElements.get(toRaw(series));
|
||||||
|
@ -152,15 +152,18 @@ export default class RemoteClock extends DefaultClock {
|
|||||||
*/
|
*/
|
||||||
#waitForReady() {
|
#waitForReady() {
|
||||||
const waitForInitialTick = (resolve) => {
|
const waitForInitialTick = (resolve) => {
|
||||||
if (this.lastTick > 0) {
|
const tickListener = () => {
|
||||||
const offsets = this.openmct.time.getClockOffsets();
|
if (this.lastTick > 0) {
|
||||||
resolve({
|
const offsets = this.openmct.time.getClockOffsets();
|
||||||
start: this.lastTick + offsets.start,
|
this.openmct.time.off('tick', tickListener); // Unregister the tick listener
|
||||||
end: this.lastTick + offsets.end
|
resolve({
|
||||||
});
|
start: this.lastTick + offsets.start,
|
||||||
} else {
|
end: this.lastTick + offsets.end
|
||||||
setTimeout(() => waitForInitialTick(resolve), 100);
|
});
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
this.openmct.time.on('tick', tickListener);
|
||||||
};
|
};
|
||||||
|
|
||||||
return new Promise(waitForInitialTick);
|
return new Promise(waitForInitialTick);
|
||||||
|
@ -20,16 +20,43 @@
|
|||||||
* at runtime from the About dialog for additional information.
|
* 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) {
|
function remoteClockRequestInterceptor(openmct, _remoteClockIdentifier, waitForBounds) {
|
||||||
let remoteClockLoaded = false;
|
let remoteClockLoaded = false;
|
||||||
|
|
||||||
return {
|
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
|
// Get the activeClock from the Global Time Context
|
||||||
|
/** @type {import("../../api/time/TimeContext").default} */
|
||||||
const { activeClock } = openmct.time;
|
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;
|
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) => {
|
invoke: async (request) => {
|
||||||
const timeContext = request?.timeContext ?? openmct.time;
|
const timeContext = request?.timeContext ?? openmct.time;
|
||||||
|
|
||||||
|
@ -38,11 +38,11 @@
|
|||||||
v-for="(tab, index) in tabsList"
|
v-for="(tab, index) in tabsList"
|
||||||
:ref="tab.keyString"
|
:ref="tab.keyString"
|
||||||
:key="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"
|
class="c-tab c-tabs-view__tab js-tab"
|
||||||
role="tab"
|
role="tab"
|
||||||
:class="{
|
:class="{
|
||||||
'is-current': isCurrent(tab)
|
'is-current': tab.keyString === currentTab.keyString
|
||||||
}"
|
}"
|
||||||
@click="showTab(tab, index)"
|
@click="showTab(tab, index)"
|
||||||
@mouseover.ctrl="showToolTip(tab)"
|
@mouseover.ctrl="showToolTip(tab)"
|
||||||
@ -74,7 +74,7 @@
|
|||||||
:key="tab.keyString"
|
:key="tab.keyString"
|
||||||
:style="getTabStyles(tab)"
|
:style="getTabStyles(tab)"
|
||||||
class="c-tabs-view__object-holder"
|
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
|
<ObjectView
|
||||||
v-if="shouldLoadTab(tab)"
|
v-if="shouldLoadTab(tab)"
|
||||||
@ -353,7 +353,10 @@ export default {
|
|||||||
this.internalDomainObject = domainObject;
|
this.internalDomainObject = domainObject;
|
||||||
},
|
},
|
||||||
persistCurrentTabIndex(index) {
|
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) {
|
storeCurrentTabIndexInURL(index) {
|
||||||
let currentTabIndexInURL = this.openmct.router.getSearchParam(this.searchTabKey);
|
let currentTabIndexInURL = this.openmct.router.getSearchParam(this.searchTabKey);
|
||||||
|
@ -25,6 +25,7 @@ import _ from 'lodash';
|
|||||||
|
|
||||||
import StalenessUtils from '../../utils/staleness.js';
|
import StalenessUtils from '../../utils/staleness.js';
|
||||||
import TableRowCollection from './collections/TableRowCollection.js';
|
import TableRowCollection from './collections/TableRowCollection.js';
|
||||||
|
import { MODE } from './constants.js';
|
||||||
import TelemetryTableColumn from './TelemetryTableColumn.js';
|
import TelemetryTableColumn from './TelemetryTableColumn.js';
|
||||||
import TelemetryTableConfiguration from './TelemetryTableConfiguration.js';
|
import TelemetryTableConfiguration from './TelemetryTableConfiguration.js';
|
||||||
import TelemetryTableNameColumn from './TelemetryTableNameColumn.js';
|
import TelemetryTableNameColumn from './TelemetryTableNameColumn.js';
|
||||||
@ -119,7 +120,7 @@ export default class TelemetryTable extends EventEmitter {
|
|||||||
this.rowLimit = rowLimit;
|
this.rowLimit = rowLimit;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.telemetryMode === 'performance') {
|
if (this.telemetryMode === MODE.PERFORMANCE) {
|
||||||
this.tableRows.setLimit(this.rowLimit);
|
this.tableRows.setLimit(this.rowLimit);
|
||||||
} else {
|
} else {
|
||||||
this.tableRows.removeLimit();
|
this.tableRows.removeLimit();
|
||||||
@ -129,14 +130,7 @@ export default class TelemetryTable extends EventEmitter {
|
|||||||
createTableRowCollections() {
|
createTableRowCollections() {
|
||||||
this.tableRows = new TableRowCollection();
|
this.tableRows = new TableRowCollection();
|
||||||
|
|
||||||
//Fetch any persisted default sort
|
const sortOptions = this.configuration.getSortOptions();
|
||||||
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'
|
|
||||||
};
|
|
||||||
|
|
||||||
this.updateRowLimit();
|
this.updateRowLimit();
|
||||||
|
|
||||||
@ -171,11 +165,10 @@ export default class TelemetryTable extends EventEmitter {
|
|||||||
|
|
||||||
this.removeTelemetryCollection(keyString);
|
this.removeTelemetryCollection(keyString);
|
||||||
|
|
||||||
let sortOptions = this.configuration.getConfiguration().sortOptions;
|
let sortOptions = this.configuration.getSortOptions();
|
||||||
requestOptions.order =
|
requestOptions.order = sortOptions.direction;
|
||||||
sortOptions?.direction ?? (this.telemetryMode === 'performance' ? 'desc' : 'asc');
|
|
||||||
|
|
||||||
if (this.telemetryMode === 'performance') {
|
if (this.telemetryMode === MODE.PERFORMANCE) {
|
||||||
requestOptions.size = this.rowLimit;
|
requestOptions.size = this.rowLimit;
|
||||||
requestOptions.enforceSize = true;
|
requestOptions.enforceSize = true;
|
||||||
}
|
}
|
||||||
@ -442,12 +435,13 @@ export default class TelemetryTable extends EventEmitter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
sortBy(sortOptions) {
|
sortBy(sortOptions) {
|
||||||
this.tableRows.sortBy(sortOptions);
|
this.configuration.setSortOptions(sortOptions);
|
||||||
|
|
||||||
if (this.openmct.editor.isEditing()) {
|
if (this.telemetryMode === MODE.PERFORMANCE) {
|
||||||
let configuration = this.configuration.getConfiguration();
|
this.tableRows.setSortOptions(sortOptions);
|
||||||
configuration.sortOptions = sortOptions;
|
this.clearAndResubscribe();
|
||||||
this.configuration.updateConfiguration(configuration);
|
} else {
|
||||||
|
this.tableRows.sortBy(sortOptions);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -23,7 +23,11 @@
|
|||||||
import { EventEmitter } from 'eventemitter3';
|
import { EventEmitter } from 'eventemitter3';
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
|
|
||||||
|
import { ORDER } from './constants';
|
||||||
|
|
||||||
export default class TelemetryTableConfiguration extends EventEmitter {
|
export default class TelemetryTableConfiguration extends EventEmitter {
|
||||||
|
#sortOptions;
|
||||||
|
|
||||||
constructor(domainObject, openmct, options) {
|
constructor(domainObject, openmct, options) {
|
||||||
super();
|
super();
|
||||||
|
|
||||||
@ -44,6 +48,26 @@ export default class TelemetryTableConfiguration extends EventEmitter {
|
|||||||
this.notPersistable = !this.openmct.objects.isPersistable(this.domainObject.identifier);
|
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() {
|
getConfiguration() {
|
||||||
let configuration = this.domainObject.configuration || {};
|
let configuration = this.domainObject.configuration || {};
|
||||||
configuration.hiddenColumns = configuration.hiddenColumns || {};
|
configuration.hiddenColumns = configuration.hiddenColumns || {};
|
||||||
|
@ -20,6 +20,8 @@
|
|||||||
* at runtime from the About dialog for additional information.
|
* at runtime from the About dialog for additional information.
|
||||||
*****************************************************************************/
|
*****************************************************************************/
|
||||||
|
|
||||||
|
import { MODE } from './constants.js';
|
||||||
|
|
||||||
export default function getTelemetryTableType(options) {
|
export default function getTelemetryTableType(options) {
|
||||||
let { telemetryMode, persistModeChange, rowLimit } = options;
|
let { telemetryMode, persistModeChange, rowLimit } = options;
|
||||||
|
|
||||||
@ -36,11 +38,11 @@ export default function getTelemetryTableType(options) {
|
|||||||
control: 'select',
|
control: 'select',
|
||||||
options: [
|
options: [
|
||||||
{
|
{
|
||||||
value: 'performance',
|
value: MODE.PERFORMANCE,
|
||||||
name: 'Limited (Performance) Mode'
|
name: 'Limited (Performance) Mode'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
value: 'unlimited',
|
value: MODE.UNLIMITED,
|
||||||
name: 'Unlimited Mode'
|
name: 'Unlimited Mode'
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
@ -22,6 +22,7 @@
|
|||||||
import { EventEmitter } from 'eventemitter3';
|
import { EventEmitter } from 'eventemitter3';
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
|
|
||||||
|
import { ORDER } from '../constants.js';
|
||||||
/**
|
/**
|
||||||
* @constructor
|
* @constructor
|
||||||
*/
|
*/
|
||||||
@ -149,13 +150,13 @@ export default class TableRowCollection extends EventEmitter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
insertOrUpdateRows(rowsToAdd, addToBeginning) {
|
insertOrUpdateRows(rowsToAdd, addToBeginning) {
|
||||||
rowsToAdd.forEach((row) => {
|
rowsToAdd.forEach((row, addRowsIndex) => {
|
||||||
const index = this.getInPlaceUpdateIndex(row);
|
const index = this.getInPlaceUpdateIndex(row);
|
||||||
if (index > -1) {
|
if (index > -1) {
|
||||||
this.updateRowInPlace(row, index);
|
this.updateRowInPlace(row, index);
|
||||||
} else {
|
} else {
|
||||||
if (addToBeginning) {
|
if (addToBeginning) {
|
||||||
this.rows.unshift(row);
|
this.rows.splice(addRowsIndex, 0, row);
|
||||||
} else {
|
} else {
|
||||||
this.rows.push(row);
|
this.rows.push(row);
|
||||||
}
|
}
|
||||||
@ -208,7 +209,7 @@ export default class TableRowCollection extends EventEmitter {
|
|||||||
const val1 = this.getValueForSortColumn(row1);
|
const val1 = this.getValueForSortColumn(row1);
|
||||||
const val2 = this.getValueForSortColumn(row2);
|
const val2 = this.getValueForSortColumn(row2);
|
||||||
|
|
||||||
if (this.sortOptions.direction === 'asc') {
|
if (this.sortOptions.direction === ORDER.ASCENDING) {
|
||||||
return val1 <= val2 ? row1 : row2;
|
return val1 <= val2 ? row1 : row2;
|
||||||
} else {
|
} else {
|
||||||
return val1 >= val2 ? row1 : row2;
|
return val1 >= val2 ? row1 : row2;
|
||||||
@ -272,7 +273,7 @@ export default class TableRowCollection extends EventEmitter {
|
|||||||
*/
|
*/
|
||||||
sortBy(sortOptions) {
|
sortBy(sortOptions) {
|
||||||
if (arguments.length > 0) {
|
if (arguments.length > 0) {
|
||||||
this.sortOptions = sortOptions;
|
this.setSortOptions(sortOptions);
|
||||||
this.rows = _.orderBy(
|
this.rows = _.orderBy(
|
||||||
this.rows,
|
this.rows,
|
||||||
(row) => row.getParsedValue(sortOptions.key),
|
(row) => row.getParsedValue(sortOptions.key),
|
||||||
@ -285,6 +286,10 @@ export default class TableRowCollection extends EventEmitter {
|
|||||||
return Object.assign({}, this.sortOptions);
|
return Object.assign({}, this.sortOptions);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setSortOptions(sortOptions) {
|
||||||
|
this.sortOptions = sortOptions;
|
||||||
|
}
|
||||||
|
|
||||||
setColumnFilter(columnKey, filter) {
|
setColumnFilter(columnKey, filter) {
|
||||||
filter = filter.trim().toLowerCase();
|
filter = filter.trim().toLowerCase();
|
||||||
let wasBlank = this.columnFilters[columnKey] === undefined;
|
let wasBlank = this.columnFilters[columnKey] === undefined;
|
||||||
@ -373,7 +378,7 @@ export default class TableRowCollection extends EventEmitter {
|
|||||||
|
|
||||||
getRows() {
|
getRows() {
|
||||||
if (this.rowLimit && this.rows.length > this.rowLimit) {
|
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);
|
return this.rows.slice(0, this.rowLimit);
|
||||||
} else {
|
} else {
|
||||||
return this.rows.slice(-this.rowLimit);
|
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 Search from '../../../ui/components/SearchComponent.vue';
|
||||||
import ToggleSwitch from '../../../ui/components/ToggleSwitch.vue';
|
import ToggleSwitch from '../../../ui/components/ToggleSwitch.vue';
|
||||||
import { useResizeObserver } from '../../../ui/composables/resize.js';
|
import { useResizeObserver } from '../../../ui/composables/resize.js';
|
||||||
|
import { MODE, ORDER } from '../constants.js';
|
||||||
import SizingRow from './SizingRow.vue';
|
import SizingRow from './SizingRow.vue';
|
||||||
import TableColumnHeader from './TableColumnHeader.vue';
|
import TableColumnHeader from './TableColumnHeader.vue';
|
||||||
import TableFooterIndicator from './TableFooterIndicator.vue';
|
import TableFooterIndicator from './TableFooterIndicator.vue';
|
||||||
@ -713,7 +714,7 @@ export default {
|
|||||||
sortBy(columnKey) {
|
sortBy(columnKey) {
|
||||||
let timeSystemKey = this.openmct.time.getTimeSystem().key;
|
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.confirmUnlimitedMode('Switch to Unlimited Telemetry and Sort', () => {
|
||||||
this.initiateSort(columnKey);
|
this.initiateSort(columnKey);
|
||||||
});
|
});
|
||||||
@ -724,15 +725,15 @@ export default {
|
|||||||
initiateSort(columnKey) {
|
initiateSort(columnKey) {
|
||||||
// If sorting by the same column, flip the sort direction.
|
// If sorting by the same column, flip the sort direction.
|
||||||
if (this.sortOptions.key === columnKey) {
|
if (this.sortOptions.key === columnKey) {
|
||||||
if (this.sortOptions.direction === 'asc') {
|
if (this.sortOptions.direction === ORDER.ASCENDING) {
|
||||||
this.sortOptions.direction = 'desc';
|
this.sortOptions.direction = ORDER.DESCENDING;
|
||||||
} else {
|
} else {
|
||||||
this.sortOptions.direction = 'asc';
|
this.sortOptions.direction = ORDER.ASCENDING;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
this.sortOptions = {
|
this.sortOptions = {
|
||||||
key: columnKey,
|
key: columnKey,
|
||||||
direction: 'desc'
|
direction: ORDER.DESCENDING
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -751,7 +752,7 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
shouldAutoScroll() {
|
shouldAutoScroll() {
|
||||||
if (this.sortOptions.direction === 'desc') {
|
if (this.sortOptions.direction === ORDER.DESCENDING) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -844,7 +845,7 @@ export default {
|
|||||||
return justTheData;
|
return justTheData;
|
||||||
},
|
},
|
||||||
exportAllDataAsCSV() {
|
exportAllDataAsCSV() {
|
||||||
if (this.telemetryMode === 'performance') {
|
if (this.telemetryMode === MODE.PERFORMANCE) {
|
||||||
this.confirmUnlimitedMode('Switch to Unlimited Telemetry and Export', () => {
|
this.confirmUnlimitedMode('Switch to Unlimited Telemetry and Export', () => {
|
||||||
const data = this.getTableRowData();
|
const data = this.getTableRowData();
|
||||||
|
|
||||||
@ -1226,7 +1227,8 @@ export default {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
updateTelemetryMode() {
|
updateTelemetryMode() {
|
||||||
this.telemetryMode = this.telemetryMode === 'unlimited' ? 'performance' : 'unlimited';
|
this.telemetryMode =
|
||||||
|
this.telemetryMode === MODE.UNLIMITED ? MODE.PERFORMANCE : MODE.UNLIMITED;
|
||||||
|
|
||||||
if (this.persistModeChange) {
|
if (this.persistModeChange) {
|
||||||
this.table.configuration.setTelemetryMode(this.telemetryMode);
|
this.table.configuration.setTelemetryMode(this.telemetryMode);
|
||||||
@ -1236,7 +1238,7 @@ export default {
|
|||||||
|
|
||||||
const timeSystemKey = this.openmct.time.getTimeSystem().key;
|
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(
|
this.openmct.notifications.info(
|
||||||
'Switched to Performance Mode: Table now sorted by time for optimized efficiency.'
|
'Switched to Performance Mode: Table now sorted by time for optimized efficiency.'
|
||||||
);
|
);
|
||||||
|
@ -62,6 +62,8 @@
|
|||||||
<script>
|
<script>
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
|
|
||||||
|
import { MODE } from '../constants.js';
|
||||||
|
|
||||||
const FILTER_INDICATOR_LABEL = 'Filters:';
|
const FILTER_INDICATOR_LABEL = 'Filters:';
|
||||||
const FILTER_INDICATOR_LABEL_MIXED = 'Mixed Filters:';
|
const FILTER_INDICATOR_LABEL_MIXED = 'Mixed Filters:';
|
||||||
const FILTER_INDICATOR_TITLE = 'Data filters are being applied to this view.';
|
const FILTER_INDICATOR_TITLE = 'Data filters are being applied to this view.';
|
||||||
@ -81,7 +83,7 @@ export default {
|
|||||||
},
|
},
|
||||||
telemetryMode: {
|
telemetryMode: {
|
||||||
type: String,
|
type: String,
|
||||||
default: 'performance'
|
default: MODE.PERFORMANCE
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
emits: ['telemetry-mode-change'],
|
emits: ['telemetry-mode-change'],
|
||||||
@ -103,7 +105,7 @@ export default {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
isUnlimitedMode() {
|
isUnlimitedMode() {
|
||||||
return this.telemetryMode === 'unlimited';
|
return this.telemetryMode === MODE.UNLIMITED;
|
||||||
},
|
},
|
||||||
label() {
|
label() {
|
||||||
if (this.hasMixedFilters) {
|
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.
|
* at runtime from the About dialog for additional information.
|
||||||
*****************************************************************************/
|
*****************************************************************************/
|
||||||
|
|
||||||
|
import { MODE } from './constants.js';
|
||||||
import TableConfigurationViewProvider from './TableConfigurationViewProvider.js';
|
import TableConfigurationViewProvider from './TableConfigurationViewProvider.js';
|
||||||
import getTelemetryTableType from './TelemetryTableType.js';
|
import getTelemetryTableType from './TelemetryTableType.js';
|
||||||
import TelemetryTableViewProvider from './TelemetryTableViewProvider.js';
|
import TelemetryTableViewProvider from './TelemetryTableViewProvider.js';
|
||||||
import TelemetryTableViewActions from './ViewActions.js';
|
import TelemetryTableViewActions from './ViewActions.js';
|
||||||
|
|
||||||
export default function plugin(
|
export default function plugin(
|
||||||
options = { telemetryMode: 'performance', persistModeChange: true, rowLimit: 50 }
|
options = { telemetryMode: MODE.PERFORMANCE, persistModeChange: true, rowLimit: 50 }
|
||||||
) {
|
) {
|
||||||
return function install(openmct) {
|
return function install(openmct) {
|
||||||
openmct.objectViews.addProvider(new TelemetryTableViewProvider(openmct, options));
|
openmct.objectViews.addProvider(new TelemetryTableViewProvider(openmct, options));
|
||||||
|
@ -28,6 +28,7 @@ import {
|
|||||||
} from 'utils/testing';
|
} from 'utils/testing';
|
||||||
import { nextTick } from 'vue';
|
import { nextTick } from 'vue';
|
||||||
|
|
||||||
|
import { MODE } from './constants.js';
|
||||||
import TablePlugin from './plugin.js';
|
import TablePlugin from './plugin.js';
|
||||||
|
|
||||||
class MockDataTransfer {
|
class MockDataTransfer {
|
||||||
@ -198,7 +199,7 @@ describe('the plugin', () => {
|
|||||||
},
|
},
|
||||||
persistModeChange: true,
|
persistModeChange: true,
|
||||||
rowLimit: 50,
|
rowLimit: 50,
|
||||||
telemetryMode: 'performance'
|
telemetryMode: MODE.PERFORMANCE
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
const testTelemetry = [
|
const testTelemetry = [
|
||||||
|
@ -41,21 +41,16 @@ import { TIME_CONTEXT_EVENTS } from '../../api/time/constants.js';
|
|||||||
import utcMultiTimeFormat from './utcMultiTimeFormat.js';
|
import utcMultiTimeFormat from './utcMultiTimeFormat.js';
|
||||||
|
|
||||||
const PADDING = 1;
|
const PADDING = 1;
|
||||||
const DEFAULT_DURATION_FORMATTER = 'duration';
|
|
||||||
const PIXELS_PER_TICK = 100;
|
const PIXELS_PER_TICK = 100;
|
||||||
const PIXELS_PER_TICK_WIDE = 200;
|
const PIXELS_PER_TICK_WIDE = 200;
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
inject: ['openmct'],
|
inject: ['openmct', 'isFixedTimeMode'],
|
||||||
props: {
|
props: {
|
||||||
viewBounds: {
|
viewBounds: {
|
||||||
type: Object,
|
type: Object,
|
||||||
required: true
|
required: true
|
||||||
},
|
},
|
||||||
isFixed: {
|
|
||||||
type: Boolean,
|
|
||||||
required: true
|
|
||||||
},
|
|
||||||
altPressed: {
|
altPressed: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
required: true
|
required: true
|
||||||
@ -198,22 +193,8 @@ export default {
|
|||||||
this.axisElement.call(this.xAxis);
|
this.axisElement.call(this.xAxis);
|
||||||
this.setScale();
|
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) {
|
dragStart($event) {
|
||||||
if (this.isFixed) {
|
if (this.isFixedTimeMode) {
|
||||||
this.dragStartX = $event.clientX;
|
this.dragStartX = $event.clientX;
|
||||||
|
|
||||||
if (this.altPressed) {
|
if (this.altPressed) {
|
||||||
|
@ -23,8 +23,8 @@
|
|||||||
<div v-if="readOnly === false" ref="clockButton" class="c-tc-input-popup__options">
|
<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">
|
<div class="c-menu-button c-ctrl-wrapper c-ctrl-wrapper--menus-left">
|
||||||
<button
|
<button
|
||||||
class="c-button--menu js-clock-button"
|
class="c-button--menu js-clock-button c-icon-button"
|
||||||
:class="[buttonCssClass, selectedClock.cssClass]"
|
:class="selectedClock.cssClass"
|
||||||
aria-label="Time Conductor Clock Menu"
|
aria-label="Time Conductor Clock Menu"
|
||||||
@click.prevent.stop="showClocksMenu"
|
@click.prevent.stop="showClocksMenu"
|
||||||
>
|
>
|
||||||
@ -38,18 +38,8 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { TIME_CONTEXT_EVENTS } from '../../api/time/constants.js';
|
|
||||||
import clockMixin from './clock-mixin.js';
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
mixins: [clockMixin],
|
inject: ['openmct', 'configuration', 'clock', 'getAllClockMetadata', 'getClockMetadata'],
|
||||||
inject: {
|
|
||||||
openmct: 'openmct',
|
|
||||||
configuration: {
|
|
||||||
from: 'configuration',
|
|
||||||
default: undefined
|
|
||||||
}
|
|
||||||
},
|
|
||||||
props: {
|
props: {
|
||||||
readOnly: {
|
readOnly: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
@ -58,21 +48,13 @@ export default {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
emits: ['clock-updated'],
|
computed: {
|
||||||
data() {
|
selectedClock() {
|
||||||
const activeClock = this.getActiveClock();
|
return this.getClockMetadata(this.clock);
|
||||||
|
}
|
||||||
return {
|
|
||||||
selectedClock: activeClock ? this.getClockMetadata(activeClock) : undefined,
|
|
||||||
clocks: []
|
|
||||||
};
|
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
this.loadClocks(this.configuration.menuOptions);
|
this.clocks = this.getAllClockMetadata(this.configuration.menuOptions);
|
||||||
this.openmct.time.on(TIME_CONTEXT_EVENTS.clockChanged, this.setViewFromClock);
|
|
||||||
},
|
|
||||||
unmounted() {
|
|
||||||
this.openmct.time.off(TIME_CONTEXT_EVENTS.clockChanged, this.setViewFromClock);
|
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
showClocksMenu() {
|
showClocksMenu() {
|
||||||
@ -86,52 +68,6 @@ export default {
|
|||||||
};
|
};
|
||||||
|
|
||||||
this.dismiss = this.openmct.menus.showSuperMenu(x, y, this.clocks, menuOptions);
|
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-zooming': isZooming },
|
||||||
{ 'is-panning': isPanning },
|
{ 'is-panning': isPanning },
|
||||||
{ 'alt-pressed': altPressed },
|
{ 'alt-pressed': altPressed },
|
||||||
isFixed ? 'is-fixed-mode' : 'is-realtime-mode'
|
isFixedTimeMode ? 'is-fixed-mode' : 'is-realtime-mode'
|
||||||
]"
|
]"
|
||||||
>
|
>
|
||||||
<ConductorModeIcon class="c-conductor__mode-icon" />
|
<ConductorModeIcon class="c-conductor__mode-icon" />
|
||||||
<div class="c-compact-tc__setting-value u-fade-truncate">
|
<div class="c-compact-tc__setting-value u-fade-truncate">
|
||||||
<ConductorMode :mode="mode" :read-only="true" />
|
<ConductorMode :read-only="true" />
|
||||||
<ConductorClock :read-only="true" />
|
<ConductorClock :read-only="true" />
|
||||||
<ConductorTimeSystem :read-only="true" />
|
<ConductorTimeSystem :read-only="true" />
|
||||||
</div>
|
</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" />
|
<ConductorInputsRealtime v-else :input-bounds="viewBounds" :read-only="true" />
|
||||||
<ConductorAxis
|
<ConductorAxis
|
||||||
v-if="isFixed"
|
v-if="isFixedTimeMode"
|
||||||
class="c-conductor__ticks"
|
class="c-conductor__ticks"
|
||||||
:view-bounds="viewBounds"
|
:view-bounds="viewBounds"
|
||||||
:is-fixed="isFixed"
|
|
||||||
:alt-pressed="altPressed"
|
:alt-pressed="altPressed"
|
||||||
@end-pan="endPan"
|
@end-pan="endPan"
|
||||||
@end-zoom="endZoom"
|
@end-zoom="endZoom"
|
||||||
@pan-axis="pan"
|
@pan-axis="pan"
|
||||||
@zoom-axis="zoom"
|
@zoom-axis="zoom"
|
||||||
/>
|
/>
|
||||||
<div
|
<ConductorHistory
|
||||||
role="button"
|
v-if="!isIndependent"
|
||||||
class="c-not-button c-not-button--compact c-compact-tc__gear icon-gear"
|
class="c-conductor__history-select"
|
||||||
aria-label="Time Conductor Settings"
|
title="Select and apply previously entered time intervals."
|
||||||
></div>
|
/>
|
||||||
|
|
||||||
<ConductorPopUp
|
<ConductorPopUp
|
||||||
v-if="showConductorPopup"
|
v-if="showConductorPopup"
|
||||||
ref="conductorPopup"
|
ref="conductorPopup"
|
||||||
:bottom="false"
|
:bottom="false"
|
||||||
:position-x="positionX"
|
:position-x="positionX"
|
||||||
:position-y="positionY"
|
:position-y="positionY"
|
||||||
:is-fixed="isFixed"
|
|
||||||
@popup-loaded="initializePopup"
|
@popup-loaded="initializePopup"
|
||||||
@mode-updated="saveMode"
|
|
||||||
@clock-updated="saveClock"
|
|
||||||
@fixed-bounds-updated="saveFixedBounds"
|
|
||||||
@clock-offsets-updated="saveClockOffsets"
|
|
||||||
@dismiss="clearPopup"
|
@dismiss="clearPopup"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<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 ConductorAxis from './ConductorAxis.vue';
|
||||||
import ConductorClock from './ConductorClock.vue';
|
import ConductorClock from './ConductorClock.vue';
|
||||||
|
import ConductorHistory from './ConductorHistory.vue';
|
||||||
import ConductorInputsFixed from './ConductorInputsFixed.vue';
|
import ConductorInputsFixed from './ConductorInputsFixed.vue';
|
||||||
import ConductorInputsRealtime from './ConductorInputsRealtime.vue';
|
import ConductorInputsRealtime from './ConductorInputsRealtime.vue';
|
||||||
import ConductorMode from './ConductorMode.vue';
|
import ConductorMode from './ConductorMode.vue';
|
||||||
@ -90,14 +78,19 @@ import ConductorModeIcon from './ConductorModeIcon.vue';
|
|||||||
import ConductorPopUp from './ConductorPopUp.vue';
|
import ConductorPopUp from './ConductorPopUp.vue';
|
||||||
import conductorPopUpManager from './conductorPopUpManager.js';
|
import conductorPopUpManager from './conductorPopUpManager.js';
|
||||||
import ConductorTimeSystem from './ConductorTimeSystem.vue';
|
import ConductorTimeSystem from './ConductorTimeSystem.vue';
|
||||||
|
import { useClock } from './useClock.js';
|
||||||
const DEFAULT_DURATION_FORMATTER = 'duration';
|
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 {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
ConductorTimeSystem,
|
ConductorTimeSystem,
|
||||||
ConductorClock,
|
ConductorClock,
|
||||||
ConductorMode,
|
ConductorMode,
|
||||||
|
ConductorHistory,
|
||||||
ConductorInputsRealtime,
|
ConductorInputsRealtime,
|
||||||
ConductorInputsFixed,
|
ConductorInputsFixed,
|
||||||
ConductorAxis,
|
ConductorAxis,
|
||||||
@ -106,38 +99,50 @@ export default {
|
|||||||
},
|
},
|
||||||
mixins: [conductorPopUpManager],
|
mixins: [conductorPopUpManager],
|
||||||
inject: ['openmct', 'configuration'],
|
inject: ['openmct', 'configuration'],
|
||||||
data() {
|
setup(props) {
|
||||||
const isFixed = this.openmct.time.isFixed();
|
const openmct = inject('openmct');
|
||||||
const bounds = this.openmct.time.getBounds();
|
const { timeContext } = useTimeContext(openmct);
|
||||||
const offsets = this.openmct.time.getClockOffsets();
|
const {
|
||||||
const timeSystem = this.openmct.time.getTimeSystem();
|
timeSystemKey,
|
||||||
const timeFormatter = this.getFormatter(timeSystem.timeFormat);
|
timeSystemFormatter,
|
||||||
const durationFormatter = this.getFormatter(
|
timeSystemDurationFormatter,
|
||||||
timeSystem.durationFormat || DEFAULT_DURATION_FORMATTER
|
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 {
|
return {
|
||||||
timeSystem,
|
timeSystemFormatter,
|
||||||
timeFormatter,
|
isFixedTimeMode,
|
||||||
durationFormatter,
|
bounds
|
||||||
offsets: {
|
};
|
||||||
start: offsets && durationFormatter.format(Math.abs(offsets.start)),
|
},
|
||||||
end: offsets && durationFormatter.format(Math.abs(offsets.end))
|
data() {
|
||||||
},
|
return {
|
||||||
bounds: {
|
|
||||||
start: bounds.start,
|
|
||||||
end: bounds.end
|
|
||||||
},
|
|
||||||
formattedBounds: {
|
|
||||||
start: timeFormatter.format(bounds.start),
|
|
||||||
end: timeFormatter.format(bounds.end)
|
|
||||||
},
|
|
||||||
viewBounds: {
|
viewBounds: {
|
||||||
start: bounds.start,
|
start: this.bounds.start,
|
||||||
end: bounds.end
|
end: this.bounds.end
|
||||||
},
|
},
|
||||||
isFixed,
|
|
||||||
isUTCBased: timeSystem.isUTCBased,
|
|
||||||
showDatePicker: false,
|
showDatePicker: false,
|
||||||
showConductorPopup: false,
|
showConductorPopup: false,
|
||||||
altPressed: false,
|
altPressed: false,
|
||||||
@ -146,38 +151,30 @@ export default {
|
|||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
mode() {
|
formattedBounds() {
|
||||||
return this.isFixed ? FIXED_MODE_KEY : REALTIME_MODE_KEY;
|
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() {
|
mounted() {
|
||||||
document.addEventListener('keydown', this.handleKeyDown);
|
document.addEventListener('keydown', this.handleKeyDown);
|
||||||
document.addEventListener('keyup', this.handleKeyUp);
|
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() {
|
beforeUnmount() {
|
||||||
document.removeEventListener('keydown', this.handleKeyDown);
|
document.removeEventListener('keydown', this.handleKeyDown);
|
||||||
document.removeEventListener('keyup', this.handleKeyUp);
|
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: {
|
methods: {
|
||||||
handleNewBounds(bounds, isTick) {
|
|
||||||
if (this.openmct.time.isRealTime() || !isTick) {
|
|
||||||
this.setBounds(bounds);
|
|
||||||
this.setViewFromBounds(bounds);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
setBounds(bounds) {
|
|
||||||
this.bounds = bounds;
|
|
||||||
},
|
|
||||||
handleKeyDown(event) {
|
handleKeyDown(event) {
|
||||||
if (event.key === 'Alt') {
|
if (event.key === 'Alt') {
|
||||||
this.altPressed = true;
|
this.altPressed = true;
|
||||||
@ -190,7 +187,7 @@ export default {
|
|||||||
},
|
},
|
||||||
pan(bounds) {
|
pan(bounds) {
|
||||||
this.isPanning = true;
|
this.isPanning = true;
|
||||||
this.setViewFromBounds(bounds);
|
this.setViewBounds(bounds);
|
||||||
},
|
},
|
||||||
endPan(bounds) {
|
endPan(bounds) {
|
||||||
this.isPanning = false;
|
this.isPanning = false;
|
||||||
@ -203,8 +200,8 @@ export default {
|
|||||||
this.isZooming = false;
|
this.isZooming = false;
|
||||||
} else {
|
} else {
|
||||||
this.isZooming = true;
|
this.isZooming = true;
|
||||||
this.formattedBounds.start = this.timeFormatter.format(bounds.start);
|
this.formattedBounds.start = this.timeSystemFormatter.format(bounds.start);
|
||||||
this.formattedBounds.end = this.timeFormatter.format(bounds.end);
|
this.formattedBounds.end = this.timeSystemFormatter.format(bounds.end);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
endZoom(bounds) {
|
endZoom(bounds) {
|
||||||
@ -212,49 +209,12 @@ export default {
|
|||||||
if (bounds) {
|
if (bounds) {
|
||||||
this.openmct.time.setBounds(bounds);
|
this.openmct.time.setBounds(bounds);
|
||||||
} else {
|
} else {
|
||||||
this.setViewFromBounds(this.bounds);
|
this.setViewBounds(this.bounds);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
setTimeSystem(timeSystem) {
|
setViewBounds(bounds) {
|
||||||
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);
|
|
||||||
this.viewBounds.start = bounds.start;
|
this.viewBounds.start = bounds.start;
|
||||||
this.viewBounds.end = bounds.end;
|
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">
|
<div class="c-menu-button c-ctrl-wrapper c-ctrl-wrapper--menus-left">
|
||||||
<button
|
<button
|
||||||
aria-label="Time Conductor History"
|
aria-label="Time Conductor History"
|
||||||
class="c-button--menu c-history-button icon-history"
|
class="c-button--minor c-history-button icon-history c-icon-button"
|
||||||
:class="buttonCssClass"
|
|
||||||
@click.prevent.stop="showHistoryMenu"
|
@click.prevent.stop="showHistoryMenu"
|
||||||
>
|
/>
|
||||||
<span class="c-button__label">History</span>
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@ -47,15 +44,6 @@ import UTCTimeFormat from '../utcTimeSystem/UTCTimeFormat.js';
|
|||||||
|
|
||||||
export default {
|
export default {
|
||||||
inject: ['openmct', 'configuration'],
|
inject: ['openmct', 'configuration'],
|
||||||
props: {
|
|
||||||
buttonCssClass: {
|
|
||||||
type: String,
|
|
||||||
required: false,
|
|
||||||
default() {
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
data() {
|
data() {
|
||||||
const mode = this.openmct.time.getMode();
|
const mode = this.openmct.time.getMode();
|
||||||
|
|
||||||
|
@ -20,14 +20,8 @@
|
|||||||
at runtime from the About dialog for additional information.
|
at runtime from the About dialog for additional information.
|
||||||
-->
|
-->
|
||||||
<template>
|
<template>
|
||||||
<TimePopupFixed
|
<DateTimePopupFixed v-if="delimiter && !readOnly" :delimiter="delimiter" @focus="$event.target.select()" @dismiss="dismiss" />
|
||||||
v-if="readOnly === false"
|
<TimePopupFixed v-else-if="!readOnly" @focus="$event.target.select()" @dismiss="dismiss" />
|
||||||
:input-bounds="bounds"
|
|
||||||
:input-time-system="timeSystem"
|
|
||||||
@focus="$event.target.select()"
|
|
||||||
@update="setBoundsFromView"
|
|
||||||
@dismiss="dismiss"
|
|
||||||
/>
|
|
||||||
<div v-else class="c-compact-tc__setting-wrapper">
|
<div v-else class="c-compact-tc__setting-wrapper">
|
||||||
<div
|
<div
|
||||||
class="c-compact-tc__setting-value u-fade-truncate--lg --no-sep"
|
class="c-compact-tc__setting-value u-fade-truncate--lg --no-sep"
|
||||||
@ -48,29 +42,16 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import _ from 'lodash';
|
|
||||||
|
|
||||||
import { TIME_CONTEXT_EVENTS } from '../../api/time/constants.js';
|
|
||||||
import TimePopupFixed from './TimePopupFixed.vue';
|
import TimePopupFixed from './TimePopupFixed.vue';
|
||||||
|
import DateTimePopupFixed from './DateTimePopupFixed.vue';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
TimePopupFixed
|
TimePopupFixed,
|
||||||
|
DateTimePopupFixed
|
||||||
},
|
},
|
||||||
inject: ['openmct'],
|
inject: ['openmct', 'timeContext', 'bounds', 'timeSystemFormatter'],
|
||||||
props: {
|
props: {
|
||||||
inputBounds: {
|
|
||||||
type: Object,
|
|
||||||
default() {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
objectPath: {
|
|
||||||
type: Array,
|
|
||||||
default() {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
},
|
|
||||||
readOnly: {
|
readOnly: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
default() {
|
default() {
|
||||||
@ -84,94 +65,19 @@ export default {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
emits: ['bounds-updated', 'dismiss-inputs-fixed'],
|
emits: ['dismiss-inputs-fixed'],
|
||||||
data() {
|
computed: {
|
||||||
const timeSystem = this.openmct.time.getTimeSystem();
|
delimiter() {
|
||||||
const timeFormatter = this.getFormatter(timeSystem.timeFormat);
|
return this.timeSystemFormatter.getDelimiter?.();
|
||||||
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
|
|
||||||
},
|
},
|
||||||
inputBounds: {
|
formattedBounds() {
|
||||||
handler(newBounds) {
|
return {
|
||||||
this.handleNewBounds(newBounds);
|
start: this.timeSystemFormatter.format(this.bounds.start),
|
||||||
},
|
end: this.timeSystemFormatter.format(this.bounds.end)
|
||||||
deep: true
|
};
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
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: {
|
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() {
|
dismiss() {
|
||||||
this.$emit('dismiss-inputs-fixed');
|
this.$emit('dismiss-inputs-fixed');
|
||||||
}
|
}
|
||||||
|
@ -22,29 +22,28 @@
|
|||||||
<template>
|
<template>
|
||||||
<TimePopupRealtime
|
<TimePopupRealtime
|
||||||
v-if="readOnly === false"
|
v-if="readOnly === false"
|
||||||
:offsets="offsets"
|
:offsets="formattedOffsets"
|
||||||
@focus="$event.target.select()"
|
@focus="$event.target.select()"
|
||||||
@update="timePopUpdate"
|
|
||||||
@dismiss="dismiss"
|
@dismiss="dismiss"
|
||||||
/>
|
/>
|
||||||
<div v-else class="c-compact-tc__setting-wrapper">
|
<div v-else class="c-compact-tc__setting-wrapper">
|
||||||
<div
|
<div
|
||||||
v-if="!compact"
|
v-if="!compact"
|
||||||
class="c-compact-tc__setting-value icon-minus u-fade-truncate--lg --no-sep"
|
class="c-compact-tc__setting-value icon-minus u-fade-truncate--lg --no-sep"
|
||||||
:aria-label="`Start offset: ${offsets.start}`"
|
:aria-label="`Start offset: ${formattedOffsets.start}`"
|
||||||
:title="`Start offset: ${offsets.start}`"
|
:title="`Start offset: ${formattedOffsets.start}`"
|
||||||
>
|
>
|
||||||
{{ offsets.start }}
|
{{ formattedOffsets.start }}
|
||||||
</div>
|
</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__bounds__start-end-sep icon-arrows-right-left"></div>
|
||||||
<div
|
<div
|
||||||
v-if="!compact"
|
v-if="!compact"
|
||||||
class="c-compact-tc__setting-value icon-plus u-fade-truncate--lg"
|
class="c-compact-tc__setting-value icon-plus u-fade-truncate--lg"
|
||||||
:class="{ '--no-sep': compact }"
|
:class="{ '--no-sep': compact }"
|
||||||
:aria-label="`End offset: ${offsets.end}`"
|
:aria-label="`End offset: ${formattedOffsets.end}`"
|
||||||
:title="`End offset: ${offsets.end}`"
|
:title="`End offset: ${formattedOffsets.end}`"
|
||||||
>
|
>
|
||||||
{{ offsets.end }}
|
{{ formattedOffsets.end }}
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
class="c-compact-tc__setting-value icon-clock c-compact-tc__current-update u-fade-truncate--lg --no-sep"
|
class="c-compact-tc__setting-value icon-clock c-compact-tc__current-update u-fade-truncate--lg --no-sep"
|
||||||
@ -58,31 +57,21 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import _ from 'lodash';
|
|
||||||
|
|
||||||
import { TIME_CONTEXT_EVENTS } from '../../api/time/constants.js';
|
|
||||||
import TimePopupRealtime from './TimePopupRealtime.vue';
|
import TimePopupRealtime from './TimePopupRealtime.vue';
|
||||||
|
|
||||||
const DEFAULT_DURATION_FORMATTER = 'duration';
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
TimePopupRealtime
|
TimePopupRealtime
|
||||||
},
|
},
|
||||||
inject: ['openmct'],
|
inject: [
|
||||||
|
'openmct',
|
||||||
|
'bounds',
|
||||||
|
'clock',
|
||||||
|
'offsets',
|
||||||
|
'timeSystemFormatter',
|
||||||
|
'timeSystemDurationFormatter'
|
||||||
|
],
|
||||||
props: {
|
props: {
|
||||||
objectPath: {
|
|
||||||
type: Array,
|
|
||||||
default() {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
},
|
|
||||||
inputBounds: {
|
|
||||||
type: Object,
|
|
||||||
default() {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
readOnly: {
|
readOnly: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
default() {
|
default() {
|
||||||
@ -96,167 +85,34 @@ export default {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
emits: ['offsets-updated', 'dismiss-inputs-realtime'],
|
emits: ['dismiss-inputs-realtime'],
|
||||||
data() {
|
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 {
|
return {
|
||||||
showTCInputStart: false,
|
currentValue: this.clock.currentValue()
|
||||||
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
|
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
watch: {
|
computed: {
|
||||||
objectPath: {
|
formattedOffsets() {
|
||||||
handler(newPath, oldPath) {
|
return {
|
||||||
if (newPath === oldPath) {
|
start: this.timeSystemDurationFormatter.format(Math.abs(this.offsets.start)),
|
||||||
return;
|
end: this.timeSystemDurationFormatter.format(Math.abs(this.offsets.end))
|
||||||
}
|
};
|
||||||
|
|
||||||
this.setTimeContext();
|
|
||||||
},
|
|
||||||
deep: true
|
|
||||||
},
|
},
|
||||||
inputBounds: {
|
formattedCurrentValue() {
|
||||||
handler(newBounds) {
|
return this.timeSystemFormatter.format(this.currentValue);
|
||||||
this.handleNewBounds(newBounds);
|
|
||||||
},
|
|
||||||
deep: true
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
mounted() {
|
watch: {
|
||||||
this.handleNewBounds = _.throttle(this.handleNewBounds, 300, {
|
bounds() {
|
||||||
leading: true,
|
this.updateCurrentValue();
|
||||||
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();
|
|
||||||
},
|
},
|
||||||
methods: {
|
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() {
|
updateCurrentValue() {
|
||||||
const currentValue = this.timeContext.getClock().currentValue();
|
this.currentValue = this.clock.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
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
dismiss() {
|
dismiss() {
|
||||||
this.$emit('dismiss-inputs-realtime');
|
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 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">
|
<div class="c-menu-button c-ctrl-wrapper c-ctrl-wrapper--menus-left">
|
||||||
<button
|
<button
|
||||||
class="c-button--menu js-mode-button"
|
class="c-button--menu js-mode-button c-icon-button"
|
||||||
:class="[buttonCssClass, selectedMode.cssClass]"
|
:class="selectedMode.cssClass"
|
||||||
aria-label="Time Conductor Mode Menu"
|
aria-label="Time Conductor Mode Menu"
|
||||||
@click.prevent.stop="showModesMenu"
|
@click.prevent.stop="showModesMenu"
|
||||||
>
|
>
|
||||||
@ -43,20 +43,9 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import modeMixin from './mode-mixin.js';
|
|
||||||
|
|
||||||
const TEST_IDS = true;
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
mixins: [modeMixin],
|
inject: ['openmct', 'timeMode', 'getAllModeMetadata', 'getModeMetadata'],
|
||||||
inject: ['openmct', 'configuration'],
|
|
||||||
props: {
|
props: {
|
||||||
mode: {
|
|
||||||
type: String,
|
|
||||||
default() {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
readOnly: {
|
readOnly: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
default() {
|
default() {
|
||||||
@ -64,24 +53,20 @@ export default {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
emits: ['mode-updated'],
|
|
||||||
data() {
|
data() {
|
||||||
const mode = this.openmct.time.getMode();
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
selectedMode: this.getModeMetadata(mode, TEST_IDS),
|
selectedMode: this.getModeMetadata(this.timeMode)
|
||||||
modes: []
|
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
mode: {
|
timeMode: {
|
||||||
handler(newMode) {
|
handler() {
|
||||||
this.setViewFromMode(newMode);
|
this.setView();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
this.loadModes();
|
this.modes = this.getAllModeMetadata();
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
showModesMenu() {
|
showModesMenu() {
|
||||||
@ -96,13 +81,8 @@ export default {
|
|||||||
|
|
||||||
this.dismiss = this.openmct.menus.showSuperMenu(x, y, this.modes, menuOptions);
|
this.dismiss = this.openmct.menus.showSuperMenu(x, y, this.modes, menuOptions);
|
||||||
},
|
},
|
||||||
setViewFromMode(mode) {
|
setView() {
|
||||||
this.selectedMode = this.getModeMetadata(mode, TEST_IDS);
|
this.selectedMode = this.getModeMetadata(this.timeMode);
|
||||||
},
|
|
||||||
setMode(mode) {
|
|
||||||
this.setViewFromMode(mode);
|
|
||||||
|
|
||||||
this.$emit('mode-updated', mode);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -5,66 +5,36 @@
|
|||||||
v-if="isIndependent"
|
v-if="isIndependent"
|
||||||
class="c-conductor__mode-select"
|
class="c-conductor__mode-select"
|
||||||
title="Sets the Time Conductor's mode."
|
title="Sets the Time Conductor's mode."
|
||||||
:mode="timeOptionMode"
|
|
||||||
@independent-mode-updated="saveIndependentMode"
|
|
||||||
/>
|
/>
|
||||||
<ConductorMode
|
<ConductorMode
|
||||||
v-else
|
v-else
|
||||||
class="c-conductor__mode-select"
|
class="c-conductor__mode-select"
|
||||||
title="Sets the Time Conductor's mode."
|
title="Sets the Time Conductor's mode."
|
||||||
:button-css-class="'c-icon-button'"
|
|
||||||
@mode-updated="saveMode"
|
|
||||||
/>
|
/>
|
||||||
<IndependentClock
|
<IndependentClock
|
||||||
v-if="isIndependent"
|
v-if="isIndependent"
|
||||||
class="c-conductor__mode-select"
|
class="c-conductor__mode-select"
|
||||||
title="Sets the Time Conductor's clock."
|
title="Sets the Time Conductor's clock."
|
||||||
:clock="timeOptionClock"
|
|
||||||
:button-css-class="'c-icon-button'"
|
|
||||||
@independent-clock-updated="saveIndependentClock"
|
|
||||||
/>
|
/>
|
||||||
<ConductorClock
|
<ConductorClock
|
||||||
v-else
|
v-else
|
||||||
class="c-conductor__mode-select"
|
class="c-conductor__mode-select"
|
||||||
title="Sets the Time Conductor's clock."
|
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 -->
|
<!-- TODO: Time system and history must work even with ITC later -->
|
||||||
<ConductorTimeSystem
|
<ConductorTimeSystem
|
||||||
v-if="!isIndependent"
|
v-if="!isIndependent"
|
||||||
class="c-conductor__time-system-select"
|
class="c-conductor__time-system-select"
|
||||||
title="Sets the Time Conductor's time system."
|
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>
|
</div>
|
||||||
<ConductorInputsFixed
|
<ConductorInputsFixed v-if="isFixedTimeMode" @dismiss-inputs-fixed="dismiss" />
|
||||||
v-if="isFixed"
|
<ConductorInputsRealtime v-else @dismiss-inputs-realtime="dismiss" />
|
||||||
: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"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { TIME_CONTEXT_EVENTS } from '../../api/time/constants.js';
|
|
||||||
import ConductorClock from './ConductorClock.vue';
|
import ConductorClock from './ConductorClock.vue';
|
||||||
import ConductorHistory from './ConductorHistory.vue';
|
|
||||||
import ConductorInputsFixed from './ConductorInputsFixed.vue';
|
import ConductorInputsFixed from './ConductorInputsFixed.vue';
|
||||||
import ConductorInputsRealtime from './ConductorInputsRealtime.vue';
|
import ConductorInputsRealtime from './ConductorInputsRealtime.vue';
|
||||||
import ConductorMode from './ConductorMode.vue';
|
import ConductorMode from './ConductorMode.vue';
|
||||||
@ -79,17 +49,10 @@ export default {
|
|||||||
IndependentMode,
|
IndependentMode,
|
||||||
IndependentClock,
|
IndependentClock,
|
||||||
ConductorTimeSystem,
|
ConductorTimeSystem,
|
||||||
ConductorHistory,
|
|
||||||
ConductorInputsFixed,
|
ConductorInputsFixed,
|
||||||
ConductorInputsRealtime
|
ConductorInputsRealtime
|
||||||
},
|
},
|
||||||
inject: {
|
inject: ['openmct', 'isFixedTimeMode'],
|
||||||
openmct: 'openmct',
|
|
||||||
configuration: {
|
|
||||||
from: 'configuration',
|
|
||||||
default: undefined
|
|
||||||
}
|
|
||||||
},
|
|
||||||
props: {
|
props: {
|
||||||
positionX: {
|
positionX: {
|
||||||
type: Number,
|
type: Number,
|
||||||
@ -99,57 +62,20 @@ export default {
|
|||||||
type: Number,
|
type: Number,
|
||||||
required: true
|
required: true
|
||||||
},
|
},
|
||||||
isFixed: {
|
|
||||||
type: Boolean,
|
|
||||||
required: true
|
|
||||||
},
|
|
||||||
isIndependent: {
|
isIndependent: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
default() {
|
default() {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
timeOptions: {
|
|
||||||
type: Object,
|
|
||||||
default() {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
bottom: {
|
bottom: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
default() {
|
default() {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
},
|
|
||||||
objectPath: {
|
|
||||||
type: Array,
|
|
||||||
default() {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
emits: [
|
emits: ['popup-loaded', 'dismiss'],
|
||||||
'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
|
|
||||||
}
|
|
||||||
};
|
|
||||||
},
|
|
||||||
computed: {
|
computed: {
|
||||||
position() {
|
position() {
|
||||||
const position = {
|
const position = {
|
||||||
@ -164,84 +90,16 @@ export default {
|
|||||||
},
|
},
|
||||||
popupClasses() {
|
popupClasses() {
|
||||||
const value = this.bottom ? 'c-tc-input-popup--bottom ' : '';
|
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 ' : '';
|
const independentClass = this.isIndependent ? 'itc-popout ' : '';
|
||||||
|
|
||||||
return `${independentClass}${value}c-tc-input-popup--${mode}`;
|
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() {
|
mounted() {
|
||||||
this.$emit('popup-loaded');
|
this.$emit('popup-loaded');
|
||||||
this.setTimeContext();
|
|
||||||
},
|
|
||||||
beforeUnmount() {
|
|
||||||
this.stopFollowingTimeContext();
|
|
||||||
},
|
},
|
||||||
methods: {
|
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() {
|
dismiss() {
|
||||||
this.$emit('dismiss');
|
this.$emit('dismiss');
|
||||||
}
|
}
|
||||||
|
@ -26,8 +26,7 @@
|
|||||||
class="c-ctrl-wrapper c-ctrl-wrapper--menus-up"
|
class="c-ctrl-wrapper c-ctrl-wrapper--menus-up"
|
||||||
>
|
>
|
||||||
<button
|
<button
|
||||||
class="c-button--menu c-time-system-button"
|
class="c-button--menu c-time-system-button c-icon-button"
|
||||||
:class="[buttonCssClass]"
|
|
||||||
aria-label="Time Conductor Time System"
|
aria-label="Time Conductor Time System"
|
||||||
@click.prevent.stop="showTimeSystemMenu"
|
@click.prevent.stop="showTimeSystemMenu"
|
||||||
>
|
>
|
||||||
@ -50,13 +49,6 @@ import { TIME_CONTEXT_EVENTS } from '../../api/time/constants.js';
|
|||||||
export default {
|
export default {
|
||||||
inject: ['openmct', 'configuration'],
|
inject: ['openmct', 'configuration'],
|
||||||
props: {
|
props: {
|
||||||
buttonCssClass: {
|
|
||||||
type: String,
|
|
||||||
required: false,
|
|
||||||
default() {
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
},
|
|
||||||
readOnly: {
|
readOnly: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
default() {
|
default() {
|
||||||
|
@ -107,10 +107,6 @@ export default {
|
|||||||
type: String,
|
type: String,
|
||||||
default: undefined
|
default: undefined
|
||||||
},
|
},
|
||||||
formatter: {
|
|
||||||
type: Object,
|
|
||||||
required: true
|
|
||||||
},
|
|
||||||
bottom: {
|
bottom: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
default() {
|
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>
|
<template>
|
||||||
<form ref="fixedDeltaInput">
|
<form ref="fixedDeltaInput">
|
||||||
<div class="c-tc-input-popup__input-grid">
|
<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">Start</div>
|
||||||
<div class="pr-time-label pr-time-label-start-time">Time</div>
|
<div class="pr-time-label pr-time-label-end-time">End</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
|
<div class="pr-time-input pr-time-input-start">
|
||||||
class="pr-time-input pr-time-input--date pr-time-input--input-and-button pr-time-input-start-date"
|
|
||||||
>
|
|
||||||
<input
|
<input
|
||||||
ref="startDate"
|
ref="start"
|
||||||
v-model="formattedBounds.start"
|
v-model="formattedBounds.start"
|
||||||
class="c-input--datetime"
|
class="c-input--datetime"
|
||||||
type="text"
|
type="text"
|
||||||
autocorrect="off"
|
autocorrect="off"
|
||||||
spellcheck="false"
|
spellcheck="false"
|
||||||
aria-label="Start date"
|
aria-label="Start time"
|
||||||
@input="validateAllBounds('startDate')"
|
@input="validateInput('start')"
|
||||||
|
@change="reportValidity('start')"
|
||||||
/>
|
/>
|
||||||
<DatePicker
|
<DatePicker
|
||||||
v-if="isUTCBased"
|
v-if="isTimeSystemUTCBased"
|
||||||
class="c-ctrl-wrapper--menus-right"
|
class="c-ctrl-wrapper--menus-left"
|
||||||
:default-date-time="formattedBounds.start"
|
:default-date-time="formattedBounds.start"
|
||||||
:formatter="timeFormatter"
|
@date-selected="dateSelected($event, 'start')"
|
||||||
@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')"
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</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__start-end-sep icon-arrows-right-left"></div>
|
||||||
|
|
||||||
<div
|
<div class="pr-time-input pr-time-input-end">
|
||||||
class="pr-time-input pr-time-input--date pr-time-input--input-and-button pr-time-input-end-date"
|
|
||||||
>
|
|
||||||
<input
|
<input
|
||||||
ref="endDate"
|
ref="end"
|
||||||
v-model="formattedBounds.end"
|
v-model="formattedBounds.end"
|
||||||
class="c-input--datetime"
|
class="c-input--datetime"
|
||||||
type="text"
|
type="text"
|
||||||
autocorrect="off"
|
autocorrect="off"
|
||||||
spellcheck="false"
|
spellcheck="false"
|
||||||
aria-label="End date"
|
aria-label="End time"
|
||||||
@input="validateAllBounds('endDate')"
|
@input="validateInput('end')"
|
||||||
|
@change="reportValidity('end')"
|
||||||
/>
|
/>
|
||||||
<DatePicker
|
<DatePicker
|
||||||
v-if="isUTCBased"
|
v-if="isTimeSystemUTCBased"
|
||||||
class="c-ctrl-wrapper--menus-left"
|
class="c-ctrl-wrapper--menus-left"
|
||||||
:default-date-time="formattedBounds.end"
|
:default-date-time="formattedBounds.end"
|
||||||
:formatter="timeFormatter"
|
@date-selected="dateSelected($event, 'end')"
|
||||||
@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')"
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="pr-time-input pr-time-input--buttons">
|
<div class="pr-time-input pr-time-input--buttons">
|
||||||
<button
|
<button
|
||||||
class="c-button c-button--major icon-check"
|
class="c-button c-button--major icon-check"
|
||||||
:disabled="isDisabled"
|
:disabled="hasInputValidityError"
|
||||||
aria-label="Submit time bounds"
|
aria-label="Submit time bounds"
|
||||||
@click.prevent="handleFormSubmission(true)"
|
@click.prevent="handleFormSubmission(true)"
|
||||||
></button>
|
></button>
|
||||||
@ -96,118 +86,79 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import _ from 'lodash';
|
|
||||||
|
|
||||||
import DatePicker from './DatePicker.vue';
|
import DatePicker from './DatePicker.vue';
|
||||||
|
|
||||||
const DEFAULT_DURATION_FORMATTER = 'duration';
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
DatePicker
|
DatePicker
|
||||||
},
|
},
|
||||||
inject: ['openmct'],
|
inject: [
|
||||||
props: {
|
'openmct',
|
||||||
inputBounds: {
|
'configuration',
|
||||||
type: Object,
|
'isTimeSystemUTCBased',
|
||||||
required: true
|
'timeContext',
|
||||||
},
|
'timeSystemKey',
|
||||||
inputTimeSystem: {
|
'timeSystemFormatter',
|
||||||
type: Object,
|
'timeSystemDurationFormatter',
|
||||||
required: true
|
'bounds'
|
||||||
}
|
],
|
||||||
},
|
emits: ['dismiss'],
|
||||||
emits: ['update', 'dismiss'],
|
|
||||||
data() {
|
data() {
|
||||||
const timeSystem = this.openmct.time.getTimeSystem();
|
|
||||||
const bounds = this.openmct.time.getBounds();
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
timeFormatter: this.getFormatter(timeSystem.timeFormat),
|
formattedBounds: {},
|
||||||
durationFormatter: this.getFormatter(timeSystem.durationFormat || DEFAULT_DURATION_FORMATTER),
|
inputValidityMap: {
|
||||||
bounds: {
|
start: { valid: true },
|
||||||
start: bounds.start,
|
end: { valid: true }
|
||||||
end: bounds.end
|
|
||||||
},
|
},
|
||||||
formattedBounds: {
|
logicalValidityMap: {
|
||||||
start: '',
|
limit: { valid: true },
|
||||||
end: '',
|
bounds: { valid: true }
|
||||||
startTime: '',
|
}
|
||||||
endTime: ''
|
|
||||||
},
|
|
||||||
isUTCBased: timeSystem.isUTCBased,
|
|
||||||
isDisabled: false
|
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
watch: {
|
computed: {
|
||||||
inputBounds: {
|
hasInputValidityError() {
|
||||||
handler(newBounds) {
|
return Object.values(this.inputValidityMap).some((isValid) => !isValid.valid);
|
||||||
this.handleNewBounds(newBounds);
|
|
||||||
},
|
|
||||||
deep: true
|
|
||||||
},
|
},
|
||||||
inputTimeSystem: {
|
hasLogicalValidationErrors() {
|
||||||
handler(newTimeSystem) {
|
return Object.values(this.logicalValidityMap).some((isValid) => !isValid.valid);
|
||||||
this.setTimeSystem(newTimeSystem);
|
},
|
||||||
},
|
isValid() {
|
||||||
deep: true
|
return !this.hasInputValidityError && !this.hasLogicalValidationErrors;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
created() {
|
watch: {
|
||||||
this.handleNewBounds = _.throttle(this.handleNewBounds, 300);
|
bounds: {
|
||||||
|
handler() {
|
||||||
|
this.setViewFromBounds();
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
this.setTimeSystem(JSON.parse(JSON.stringify(this.openmct.time.getTimeSystem())));
|
this.setViewFromBounds();
|
||||||
this.setViewFromBounds(this.bounds);
|
|
||||||
},
|
},
|
||||||
beforeUnmount() {
|
beforeUnmount() {
|
||||||
this.clearAllValidation();
|
this.clearAllValidation();
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
handleNewBounds(bounds) {
|
setViewFromBounds() {
|
||||||
this.setBounds(bounds);
|
const start = this.timeSystemFormatter.format(this.bounds.start);
|
||||||
this.setViewFromBounds(bounds);
|
const end = this.timeSystemFormatter.format(this.bounds.end);
|
||||||
},
|
|
||||||
clearAllValidation() {
|
this.formattedBounds = {
|
||||||
[this.$refs.startDate, this.$refs.endDate].forEach(this.clearValidationForInput);
|
start,
|
||||||
},
|
end
|
||||||
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;
|
|
||||||
},
|
},
|
||||||
setBoundsFromView(dismiss) {
|
setBoundsFromView(dismiss) {
|
||||||
if (this.$refs.fixedDeltaInput.checkValidity()) {
|
if (this.$refs.fixedDeltaInput.checkValidity()) {
|
||||||
let start = this.timeFormatter.parse(
|
const start = this.timeSystemFormatter.parse(this.formattedBounds.start);
|
||||||
`${this.formattedBounds.start} ${this.formattedBounds.startTime}`
|
const end = this.timeSystemFormatter.parse(this.formattedBounds.end);
|
||||||
);
|
|
||||||
let end = this.timeFormatter.parse(
|
|
||||||
`${this.formattedBounds.end} ${this.formattedBounds.endTime}`
|
|
||||||
);
|
|
||||||
|
|
||||||
this.$emit('update', { start, end });
|
this.timeContext.setBounds({
|
||||||
|
start,
|
||||||
|
end
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (dismiss) {
|
if (dismiss) {
|
||||||
@ -215,101 +166,107 @@ export default {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
handleFormSubmission(shouldDismiss) {
|
clearAllValidation() {
|
||||||
this.validateAllBounds('startDate');
|
Object.keys(this.inputValidityMap).forEach(this.clearValidation);
|
||||||
this.validateAllBounds('endDate');
|
},
|
||||||
|
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);
|
this.setBoundsFromView(shouldDismiss);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
validateAllBounds(ref) {
|
validateInput(refName) {
|
||||||
this.isDisabled = false;
|
this.clearAllValidation();
|
||||||
|
|
||||||
if (!this.areBoundsFormatsValid()) {
|
const validationResult = this.timeSystemFormatter.validate(this.formattedBounds[refName])
|
||||||
this.isDisabled = true;
|
? { valid: true }
|
||||||
return false;
|
: { valid: false, message: `Invalid Time` };
|
||||||
}
|
|
||||||
|
|
||||||
let validationResult = { valid: true };
|
this.inputValidityMap[refName] = validationResult;
|
||||||
const currentInput = this.$refs[ref];
|
},
|
||||||
|
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) => {
|
this.logicalValidityMap.bounds = this.timeContext.validateBounds(bounds);
|
||||||
let boundsValues = {
|
},
|
||||||
start: this.timeFormatter.parse(
|
validateLimit() {
|
||||||
`${this.formattedBounds.start} ${this.formattedBounds.startTime}`
|
const bounds = {
|
||||||
),
|
start: this.timeSystemFormatter.parse(this.formattedBounds.start),
|
||||||
end: this.timeFormatter.parse(
|
end: this.timeSystemFormatter.parse(this.formattedBounds.end)
|
||||||
`${this.formattedBounds.end} ${this.formattedBounds.endTime}`
|
};
|
||||||
)
|
|
||||||
|
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
|
} else {
|
||||||
// const limit = this.getBoundsLimit();
|
this.logicalValidityMap.limit = { valid: true };
|
||||||
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);
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
areBoundsFormatsValid() {
|
reportValidity(refName) {
|
||||||
return [this.$refs.startDate, this.$refs.endDate].every((input) => {
|
const input = this.getInput(refName);
|
||||||
const formattedDate =
|
const validationResult = this.inputValidityMap[refName];
|
||||||
input === this.$refs.startDate
|
|
||||||
? `${this.formattedBounds.start} ${this.formattedBounds.startTime}`
|
|
||||||
: `${this.formattedBounds.end} ${this.formattedBounds.endTime}`;
|
|
||||||
|
|
||||||
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) {
|
if (validationResult.valid !== true) {
|
||||||
input.setCustomValidity(validationResult.message);
|
input.setCustomValidity(validationResult.message);
|
||||||
input.title = validationResult.message;
|
input.title = validationResult.message;
|
||||||
this.isDisabled = true;
|
|
||||||
} else {
|
} else {
|
||||||
input.setCustomValidity('');
|
input.setCustomValidity('');
|
||||||
input.title = '';
|
input.title = '';
|
||||||
}
|
}
|
||||||
|
|
||||||
this.$refs.fixedDeltaInput.reportValidity();
|
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) {
|
getInput(refName) {
|
||||||
this.formattedBounds.start = this.timeFormatter.format(date).split(' ')[0];
|
if (Object.keys(this.inputValidityMap).includes(refName)) {
|
||||||
this.validateAllBounds('startDate');
|
return this.$refs[refName];
|
||||||
},
|
}
|
||||||
endDateSelected(date) {
|
|
||||||
this.formattedBounds.end = this.timeFormatter.format(date).split(' ')[0];
|
return this.$refs.start;
|
||||||
this.validateAllBounds('endDate');
|
|
||||||
},
|
},
|
||||||
hide($event) {
|
hide($event) {
|
||||||
if ($event.target.className.indexOf('c-button icon-x') > -1) {
|
if ($event.target.className.indexOf('c-button icon-x') > -1) {
|
||||||
this.$emit('dismiss');
|
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>
|
<template>
|
||||||
<form ref="deltaInput">
|
<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 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-mins">Mins</div>
|
||||||
<div class="pr-time-label pr-time-label-minus-secs">Secs</div>
|
<div class="pr-time-label pr-time-label-minus-secs">Secs</div>
|
||||||
@ -142,14 +164,17 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
import { nextTick } from 'vue';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
inject: ['timeContext', 'timeSystemDurationFormatter'],
|
||||||
props: {
|
props: {
|
||||||
offsets: {
|
offsets: {
|
||||||
type: Object,
|
type: Object,
|
||||||
required: true
|
required: true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
emits: ['update', 'dismiss'],
|
emits: ['dismiss'],
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
startInputHrs: '00',
|
startInputHrs: '00',
|
||||||
@ -164,6 +189,7 @@ export default {
|
|||||||
watch: {
|
watch: {
|
||||||
offsets: {
|
offsets: {
|
||||||
handler() {
|
handler() {
|
||||||
|
console.log('REMOVE THIS');
|
||||||
this.setOffsets();
|
this.setOffsets();
|
||||||
},
|
},
|
||||||
deep: true
|
deep: true
|
||||||
@ -206,18 +232,23 @@ export default {
|
|||||||
this.isDisabled = disabled;
|
this.isDisabled = disabled;
|
||||||
},
|
},
|
||||||
submit() {
|
submit() {
|
||||||
this.$emit('update', {
|
const formattedStartOffset = [
|
||||||
start: {
|
this.startInputHrs,
|
||||||
hours: this.startInputHrs,
|
this.startInputMins,
|
||||||
minutes: this.startInputMins,
|
this.startInputSecs
|
||||||
seconds: this.startInputSecs
|
].join(':');
|
||||||
},
|
const formattedEndOffset = [this.endInputHrs, this.endInputMins, this.endInputSecs].join(':');
|
||||||
end: {
|
|
||||||
hours: this.endInputHrs,
|
let startOffset = 0 - this.timeSystemDurationFormatter.parse(formattedStartOffset);
|
||||||
minutes: this.endInputMins,
|
let endOffset = this.timeSystemDurationFormatter.parse(formattedEndOffset);
|
||||||
seconds: this.endInputSecs
|
|
||||||
}
|
const offsets = {
|
||||||
});
|
start: startOffset,
|
||||||
|
end: endOffset
|
||||||
|
};
|
||||||
|
|
||||||
|
this.timeContext.setClockOffsets(offsets);
|
||||||
|
|
||||||
this.$emit('dismiss');
|
this.$emit('dismiss');
|
||||||
},
|
},
|
||||||
hide($event) {
|
hide($event) {
|
||||||
@ -234,13 +265,13 @@ export default {
|
|||||||
this[ref] = cv.toString().padStart(2, '0');
|
this[ref] = cv.toString().padStart(2, '0');
|
||||||
this.validate();
|
this.validate();
|
||||||
},
|
},
|
||||||
setOffsets() {
|
async setOffsets() {
|
||||||
[this.startInputHrs, this.startInputMins, this.startInputSecs] =
|
[this.startInputHrs, this.startInputMins, this.startInputSecs] =
|
||||||
this.offsets.start.split(':');
|
this.offsets.start.split(':');
|
||||||
[this.endInputHrs, this.endInputMins, this.endInputSecs] = this.offsets.end.split(':');
|
[this.endInputHrs, this.endInputMins, this.endInputSecs] = this.offsets.end.split(':');
|
||||||
this.$nextTick(() => {
|
|
||||||
this.numberSelect('startInputHrs');
|
await nextTick();
|
||||||
});
|
this.numberSelect('startInputHrs');
|
||||||
},
|
},
|
||||||
numberSelect(input) {
|
numberSelect(input) {
|
||||||
if (this.$refs[input] === undefined || this.$refs[input] === null) {
|
if (this.$refs[input] === undefined || this.$refs[input] === null) {
|
||||||
|
@ -1,13 +1,4 @@
|
|||||||
export default {
|
export default {
|
||||||
props: {
|
|
||||||
buttonCssClass: {
|
|
||||||
type: String,
|
|
||||||
required: false,
|
|
||||||
default() {
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
methods: {
|
methods: {
|
||||||
loadClocks(menuOptions) {
|
loadClocks(menuOptions) {
|
||||||
let clocks;
|
let clocks;
|
||||||
|
@ -629,7 +629,6 @@
|
|||||||
}
|
}
|
||||||
&-label-end-time{
|
&-label-end-time{
|
||||||
grid-area: eTime;
|
grid-area: eTime;
|
||||||
|
|
||||||
}
|
}
|
||||||
&-input-end-date{
|
&-input-end-date{
|
||||||
grid-area: eDateInput;
|
grid-area: eDateInput;
|
||||||
@ -641,6 +640,14 @@
|
|||||||
grid-area: blank;
|
grid-area: blank;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FIXED TIME MODE non utc
|
||||||
|
&-input-start{
|
||||||
|
grid-area: sInput;
|
||||||
|
}
|
||||||
|
&-input-end{
|
||||||
|
grid-area: eInput;
|
||||||
|
}
|
||||||
|
|
||||||
//REAL TIME MODE
|
//REAL TIME MODE
|
||||||
&-label-minus-hrs{
|
&-label-minus-hrs{
|
||||||
grid-area: labelMinusHrs;
|
grid-area: labelMinusHrs;
|
||||||
@ -691,14 +698,20 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
&--fixed-mode {
|
&--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-columns: 1fr 1fr 1fr 1fr 1fr 2fr;
|
||||||
grid-template-areas:
|
grid-template-areas:
|
||||||
"sDate sTime . eDate eTime ."
|
"sDate sTime . eDate eTime ."
|
||||||
"sDateInput sTimeInput arrowIcon eDateInput eTimeInput buttons";
|
"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(){
|
@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-columns: repeat(2, max-content) 1fr;
|
||||||
grid-template-areas:
|
grid-template-areas:
|
||||||
"sDate sTime ."
|
"sDate sTime ."
|
||||||
@ -708,19 +721,29 @@
|
|||||||
padding: 2px;
|
padding: 2px;
|
||||||
overflow: hidden;
|
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 {
|
&--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-columns: 1fr 1fr 1fr 1fr 1fr 1fr 1fr 2fr;
|
||||||
grid-template-areas:
|
grid-template-areas:
|
||||||
"labelMinusHrs labelMinusMins labelMinusSecs . labelPlusHrs labelPlusMins labelPlusSecs ."
|
"labelMinusHrs labelMinusMins labelMinusSecs . labelPlusHrs labelPlusMins labelPlusSecs ."
|
||||||
"inputMinusHrs inputMinusMins inputMinusSecs arrowIcon inputPlusHrs inputPlusMins inputPlusSecs buttons";
|
"inputMinusHrs inputMinusMins inputMinusSecs arrowIcon inputPlusHrs inputPlusMins inputPlusSecs buttons";
|
||||||
}
|
}
|
||||||
@include phonePortrait(){
|
@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-columns: repeat(3, max-content) 1fr;
|
||||||
grid-template-areas:
|
grid-template-areas:
|
||||||
"labelMinusHrs labelMinusMins labelMinusSecs ."
|
"labelMinusHrs labelMinusMins labelMinusSecs ."
|
||||||
@ -733,7 +756,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&__input-grid {
|
&__input-grid, &__input-grid-utc {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-row-gap: $interiorMargin;
|
grid-row-gap: $interiorMargin;
|
||||||
grid-column-gap: $interiorMarginSm;
|
grid-column-gap: $interiorMarginSm;
|
||||||
|
@ -25,7 +25,7 @@
|
|||||||
<button
|
<button
|
||||||
v-if="selectedClock"
|
v-if="selectedClock"
|
||||||
class="c-icon-button c-button--menu js-clock-button"
|
class="c-icon-button c-button--menu js-clock-button"
|
||||||
:class="[buttonCssClass, selectedClock.cssClass]"
|
:class="selectedClock.cssClass"
|
||||||
aria-label="Independent Time Conductor Clock Menu"
|
aria-label="Independent Time Conductor Clock Menu"
|
||||||
@click.prevent.stop="showClocksMenu"
|
@click.prevent.stop="showClocksMenu"
|
||||||
>
|
>
|
||||||
@ -36,20 +36,9 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<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 {
|
export default {
|
||||||
mixins: [toggleMixin, clockMixin],
|
inject: ['openmct', 'clock', 'getAllClockMetadata', 'getClockMetadata'],
|
||||||
inject: ['openmct'],
|
|
||||||
props: {
|
props: {
|
||||||
clock: {
|
|
||||||
type: String,
|
|
||||||
default() {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
enabled: {
|
enabled: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
default() {
|
default() {
|
||||||
@ -57,33 +46,20 @@ export default {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
emits: ['independent-clock-updated'],
|
computed: {
|
||||||
data() {
|
selectedClock() {
|
||||||
const activeClock = this.getActiveClock();
|
return this.getClockMetadata(this.clock);
|
||||||
|
}
|
||||||
return {
|
|
||||||
selectedClock: activeClock ? this.getClockMetadata(activeClock) : undefined,
|
|
||||||
clocks: []
|
|
||||||
};
|
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
clock(newClock, oldClock) {
|
|
||||||
this.setViewFromClock(newClock);
|
|
||||||
},
|
|
||||||
enabled(newValue, oldValue) {
|
enabled(newValue, oldValue) {
|
||||||
if (newValue !== undefined && newValue !== oldValue && newValue === true) {
|
if (newValue !== undefined && newValue !== oldValue && newValue === true) {
|
||||||
this.setViewFromClock(this.clock);
|
this.setViewFromClock(this.clock);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
beforeUnmount() {
|
mounted() {
|
||||||
this.openmct.time.off(TIME_CONTEXT_EVENTS.clockChanged, this.setViewFromClock);
|
this.clocks = this.getAllClockMetadata();
|
||||||
},
|
|
||||||
mounted: function () {
|
|
||||||
this.loadClocks();
|
|
||||||
this.setViewFromClock(this.clock);
|
|
||||||
|
|
||||||
this.openmct.time.on(TIME_CONTEXT_EVENTS.clockChanged, this.setViewFromClock);
|
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
showClocksMenu() {
|
showClocksMenu() {
|
||||||
@ -95,27 +71,11 @@ export default {
|
|||||||
menuClass: 'c-conductor__clock-menu c-super-menu--sm',
|
menuClass: 'c-conductor__clock-menu c-super-menu--sm',
|
||||||
placement: this.openmct.menus.menuPlacement.BOTTOM_RIGHT
|
placement: this.openmct.menus.menuPlacement.BOTTOM_RIGHT
|
||||||
};
|
};
|
||||||
|
|
||||||
this.openmct.menus.showSuperMenu(x, y, this.clocks, menuOptions);
|
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) {
|
setClock(clockKey) {
|
||||||
this.setViewFromClock(clockKey);
|
this.setViewFromClock(clockKey);
|
||||||
|
|
||||||
this.$emit('independent-clock-updated', clockKey);
|
|
||||||
},
|
},
|
||||||
setViewFromClock(clockOrKey) {
|
setViewFromClock(clockOrKey) {
|
||||||
let clock = clockOrKey;
|
let clock = clockOrKey;
|
||||||
|
@ -23,8 +23,8 @@
|
|||||||
<div ref="modeMenuButton" class="c-ctrl-wrapper c-ctrl-wrapper--menus-up">
|
<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">
|
<div class="c-menu-button c-ctrl-wrapper c-ctrl-wrapper--menus-left">
|
||||||
<button
|
<button
|
||||||
class="c-icon-button c-button--menu js-mode-button"
|
class="c-button--menu js-mode-button c-icon-button"
|
||||||
:class="[buttonCssClass, selectedMode.cssClass]"
|
:class="selectedMode.cssClass"
|
||||||
aria-label="Independent Time Conductor Mode Menu"
|
aria-label="Independent Time Conductor Mode Menu"
|
||||||
@click.prevent.stop="showModesMenu"
|
@click.prevent.stop="showModesMenu"
|
||||||
>
|
>
|
||||||
@ -35,19 +35,10 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import toggleMixin from '../../../ui/mixins/toggle-mixin.js';
|
|
||||||
import modeMixin from '../mode-mixin.js';
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
mixins: [toggleMixin, modeMixin],
|
inject: ['openmct', 'timeMode', 'getAllModeMetadata', 'getModeMetadata'],
|
||||||
inject: ['openmct'],
|
|
||||||
props: {
|
props: {
|
||||||
mode: {
|
|
||||||
type: String,
|
|
||||||
default() {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
enabled: {
|
enabled: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
default() {
|
default() {
|
||||||
@ -55,27 +46,25 @@ export default {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
emits: ['independent-mode-updated'],
|
data() {
|
||||||
data: function () {
|
|
||||||
return {
|
return {
|
||||||
selectedMode: this.getModeMetadata(this.mode),
|
selectedMode: this.getModeMetadata(this.timeMode)
|
||||||
modes: []
|
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
mode: {
|
timeMode: {
|
||||||
handler(newMode) {
|
handler() {
|
||||||
this.setViewFromMode(newMode);
|
this.setView();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
enabled(newValue, oldValue) {
|
enabled(newValue, oldValue) {
|
||||||
if (newValue !== undefined && newValue !== oldValue && newValue === true) {
|
if (newValue !== undefined && newValue !== oldValue && newValue === true) {
|
||||||
this.setViewFromMode(this.mode);
|
this.setView();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
mounted: function () {
|
mounted: function () {
|
||||||
this.loadModes();
|
this.modes = this.getAllModeMetadata();
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
showModesMenu() {
|
showModesMenu() {
|
||||||
@ -89,13 +78,8 @@ export default {
|
|||||||
};
|
};
|
||||||
this.openmct.menus.showSuperMenu(x, y, this.modes, menuOptions);
|
this.openmct.menus.showSuperMenu(x, y, this.modes, menuOptions);
|
||||||
},
|
},
|
||||||
setViewFromMode(mode) {
|
setView() {
|
||||||
this.selectedMode = this.getModeMetadata(mode);
|
this.selectedMode = this.getModeMetadata(this.timeMode);
|
||||||
},
|
|
||||||
setMode(mode) {
|
|
||||||
this.setViewFromMode(mode);
|
|
||||||
|
|
||||||
this.$emit('independent-mode-updated', mode);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -24,7 +24,7 @@
|
|||||||
ref="timeConductorOptionsHolder"
|
ref="timeConductorOptionsHolder"
|
||||||
class="c-compact-tc"
|
class="c-compact-tc"
|
||||||
:class="[
|
: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 }
|
{ 'is-expanded': independentTCEnabled }
|
||||||
]"
|
]"
|
||||||
aria-label="Independent Time Conductor Panel"
|
aria-label="Independent Time Conductor Panel"
|
||||||
@ -42,7 +42,6 @@
|
|||||||
<ConductorInputsFixed
|
<ConductorInputsFixed
|
||||||
v-if="showFixedInputs"
|
v-if="showFixedInputs"
|
||||||
class="c-compact-tc__bounds--fixed"
|
class="c-compact-tc__bounds--fixed"
|
||||||
:object-path="objectPath"
|
|
||||||
:read-only="true"
|
:read-only="true"
|
||||||
:compact="true"
|
:compact="true"
|
||||||
/>
|
/>
|
||||||
@ -50,45 +49,38 @@
|
|||||||
<ConductorInputsRealtime
|
<ConductorInputsRealtime
|
||||||
v-if="showRealtimeInputs"
|
v-if="showRealtimeInputs"
|
||||||
class="c-compact-tc__bounds--real-time"
|
class="c-compact-tc__bounds--real-time"
|
||||||
:object-path="objectPath"
|
|
||||||
:read-only="true"
|
:read-only="true"
|
||||||
:compact="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
|
<ConductorPopUp
|
||||||
v-if="showConductorPopup"
|
v-if="showConductorPopup"
|
||||||
ref="conductorPopup"
|
ref="conductorPopup"
|
||||||
:object-path="objectPath"
|
|
||||||
:is-independent="true"
|
:is-independent="true"
|
||||||
:time-options="timeOptions"
|
|
||||||
:is-fixed="isFixed"
|
|
||||||
:bottom="true"
|
:bottom="true"
|
||||||
:position-x="positionX"
|
:position-x="positionX"
|
||||||
:position-y="positionY"
|
:position-y="positionY"
|
||||||
@popup-loaded="initializePopup"
|
@popup-loaded="initializePopup"
|
||||||
@independent-mode-updated="saveMode"
|
|
||||||
@independent-clock-updated="saveClock"
|
|
||||||
@fixed-bounds-updated="saveFixedBounds"
|
|
||||||
@clock-offsets-updated="saveClockOffsets"
|
|
||||||
@dismiss="clearPopup"
|
@dismiss="clearPopup"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
import { inject, provide, toRaw } from 'vue';
|
||||||
|
|
||||||
import ConductorModeIcon from '@/plugins/timeConductor/ConductorModeIcon.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 ToggleSwitch from '../../../ui/components/ToggleSwitch.vue';
|
||||||
import ConductorInputsFixed from '../ConductorInputsFixed.vue';
|
import ConductorInputsFixed from '../ConductorInputsFixed.vue';
|
||||||
import ConductorInputsRealtime from '../ConductorInputsRealtime.vue';
|
import ConductorInputsRealtime from '../ConductorInputsRealtime.vue';
|
||||||
import ConductorPopUp from '../ConductorPopUp.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';
|
import independentTimeConductorPopUpManager from './independentTimeConductorPopUpManager.js';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
@ -100,13 +92,7 @@ export default {
|
|||||||
ToggleSwitch
|
ToggleSwitch
|
||||||
},
|
},
|
||||||
mixins: [independentTimeConductorPopUpManager],
|
mixins: [independentTimeConductorPopUpManager],
|
||||||
inject: {
|
inject: ['openmct'],
|
||||||
openmct: 'openmct',
|
|
||||||
configuration: {
|
|
||||||
from: 'configuration',
|
|
||||||
default: undefined
|
|
||||||
}
|
|
||||||
},
|
|
||||||
props: {
|
props: {
|
||||||
domainObject: {
|
domainObject: {
|
||||||
type: Object,
|
type: Object,
|
||||||
@ -117,210 +103,257 @@ export default {
|
|||||||
required: true
|
required: true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
emits: ['updated'],
|
setup(props) {
|
||||||
data() {
|
const openmct = inject('openmct');
|
||||||
const fixedOffsets = this.openmct.time.getBounds();
|
const { timeContext } = useTimeContext(openmct, () => props.objectPath);
|
||||||
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
|
|
||||||
};
|
|
||||||
|
|
||||||
timeOptions.clock = timeOptions.clock ?? clock;
|
const {
|
||||||
timeOptions.mode = timeOptions.mode ?? mode;
|
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
|
provide('timeContext', timeContext);
|
||||||
if (timeOptions.mode.key) {
|
provide('timeSystemKey', timeSystemKey);
|
||||||
timeOptions.mode = timeOptions.mode.key;
|
provide('timeSystemFormatter', timeSystemFormatter);
|
||||||
}
|
provide('timeSystemDurationFormatter', timeSystemDurationFormatter);
|
||||||
|
provide('isTimeSystemUTCBased', isTimeSystemUTCBased);
|
||||||
const isFixed = timeOptions.mode === FIXED_MODE_KEY;
|
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 {
|
return {
|
||||||
timeOptions,
|
timeContext,
|
||||||
isFixed,
|
timeMode,
|
||||||
independentTCEnabled: this.domainObject.configuration.useIndependentTime === true,
|
clock,
|
||||||
viewBounds: {
|
timeSystemFormatter,
|
||||||
start: fixedOffsets.start,
|
isFixedTimeMode,
|
||||||
end: fixedOffsets.end
|
isRealTimeMode,
|
||||||
}
|
bounds,
|
||||||
|
isTick,
|
||||||
|
offsets
|
||||||
|
};
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
keyString: this.openmct.objects.makeKeyString(this.domainObject.identifier),
|
||||||
|
independentTCEnabled: this.domainObject.configuration.useIndependentTime === true
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
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() {
|
toggleTitle() {
|
||||||
return `${this.independentTCEnabled ? 'Disable' : 'Enable'} Independent Time Conductor`;
|
return `${this.independentTCEnabled ? 'Disable' : 'Enable'} Independent Time Conductor`;
|
||||||
},
|
},
|
||||||
showFixedInputs() {
|
showFixedInputs() {
|
||||||
return this.isFixed && this.independentTCEnabled;
|
return this.isFixedTimeMode && this.independentTCEnabled;
|
||||||
},
|
},
|
||||||
showRealtimeInputs() {
|
showRealtimeInputs() {
|
||||||
return !this.isFixed && this.independentTCEnabled;
|
return this.isRealTimeMode && this.independentTCEnabled;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
domainObject: {
|
myKeyString() {
|
||||||
handler(domainObject) {
|
console.log(`object changed`);
|
||||||
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
|
|
||||||
},
|
},
|
||||||
objectPath: {
|
independentTCEnabled() {
|
||||||
handler(newPath, oldPath) {
|
this.handleIndependentTimeConductorChange();
|
||||||
//domain object or view has probably changed
|
},
|
||||||
this.setTimeContext();
|
timeContext() {
|
||||||
},
|
const keyString = this.openmct.objects.makeKeyString(this.domainObject.identifier);
|
||||||
deep: true
|
|
||||||
|
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() {
|
created() {
|
||||||
|
// this.initialize();
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.setTimeOptions();
|
||||||
|
|
||||||
this.initialize();
|
this.initialize();
|
||||||
},
|
},
|
||||||
beforeUnmount() {
|
beforeUnmount() {
|
||||||
this.stopFollowingTimeContext();
|
this.unregisterIndependentTimeContext?.();
|
||||||
this.destroyIndependentTime();
|
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
initialize() {
|
initialize() {
|
||||||
this.keyString = this.openmct.objects.makeKeyString(this.domainObject.identifier);
|
|
||||||
this.setTimeContext();
|
|
||||||
|
|
||||||
if (this.independentTCEnabled) {
|
if (this.independentTCEnabled) {
|
||||||
this.registerIndependentTimeOffsets();
|
this.registerIndependentTimeContext();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
handleIndependentTimeConductorChange() {
|
||||||
|
if (this.independentTCEnabled) {
|
||||||
|
this.registerIndependentTimeContext();
|
||||||
|
} else {
|
||||||
|
this.clearPopup();
|
||||||
|
this.unregisterIndependentTimeContext?.();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
toggleIndependentTC() {
|
toggleIndependentTC() {
|
||||||
this.independentTCEnabled = !this.independentTCEnabled;
|
this.independentTCEnabled = !this.independentTCEnabled;
|
||||||
|
|
||||||
if (this.independentTCEnabled) {
|
|
||||||
this.registerIndependentTimeOffsets();
|
|
||||||
} else {
|
|
||||||
this.clearPopup();
|
|
||||||
this.destroyIndependentTime();
|
|
||||||
}
|
|
||||||
|
|
||||||
this.openmct.objects.mutate(
|
this.openmct.objects.mutate(
|
||||||
this.domainObject,
|
this.domainObject,
|
||||||
'configuration.useIndependentTime',
|
'configuration.useIndependentTime',
|
||||||
this.independentTCEnabled
|
this.independentTCEnabled
|
||||||
);
|
);
|
||||||
|
|
||||||
},
|
},
|
||||||
setTimeContext() {
|
setTimeOptions() {
|
||||||
if (this.timeContext) {
|
this.timeOptions = toRaw(this.domainObject.configuration.timeOptions);
|
||||||
this.stopFollowingTimeContext();
|
if (!this.timeOptions) {
|
||||||
|
this.timeOptions = {
|
||||||
|
clockOffsets: this.offsets,
|
||||||
|
fixedOffsets: this.bounds
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
this.timeContext = this.openmct.time.getContextForView(this.objectPath);
|
if (!this.timeOptions.clock) {
|
||||||
this.timeContext.on(TIME_CONTEXT_EVENTS.clockChanged, this.setTimeOptionsClock);
|
// can remove openmct.time.getClock() if timeContexts have clock in fixed time
|
||||||
this.timeContext.on(TIME_CONTEXT_EVENTS.modeChanged, this.setTimeOptionsMode);
|
this.timeOptions.clock = this.clock?.key ?? this.openmct.time.getClock().key;
|
||||||
},
|
}
|
||||||
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
|
|
||||||
});
|
|
||||||
|
|
||||||
this.updateTimeOptions(newOptions);
|
if (!this.timeOptions.mode) {
|
||||||
},
|
this.timeOptions.mode = this.timeMode;
|
||||||
saveMode(mode) {
|
}
|
||||||
this.isFixed = mode === FIXED_MODE_KEY;
|
|
||||||
const newOptions = this.updateTimeOptionProperty({
|
|
||||||
mode: mode
|
|
||||||
});
|
|
||||||
|
|
||||||
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) {
|
saveFixedBounds() {
|
||||||
const newOptions = this.updateTimeOptionProperty({
|
this.timeOptions.fixedOffsets = this.bounds;
|
||||||
clock
|
this.updateTimeOptions();
|
||||||
});
|
|
||||||
|
|
||||||
this.updateTimeOptions(newOptions);
|
|
||||||
},
|
},
|
||||||
updateTimeOptions(options) {
|
saveClockOffsets() {
|
||||||
this.timeOptions = options;
|
this.timeOptions.clockOffsets = this.offsets;
|
||||||
|
this.updateTimeOptions();
|
||||||
this.registerIndependentTimeOffsets();
|
},
|
||||||
this.$emit('updated', this.timeOptions); // no longer use this, but may be used elsewhere
|
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);
|
this.openmct.objects.mutate(this.domainObject, 'configuration.timeOptions', this.timeOptions);
|
||||||
},
|
},
|
||||||
registerIndependentTimeOffsets() {
|
registerIndependentTimeContext() {
|
||||||
const timeContext = this.openmct.time.getIndependentContext(this.keyString);
|
const bounds = this.timeOptions.fixedOffsets ?? this.bounds;
|
||||||
let offsets;
|
const offsets = this.timeOptions.clockOffsets ?? this.offsets;
|
||||||
|
const clockKey = this.timeOptions.clock || this.clock.key;
|
||||||
|
|
||||||
if (this.isFixed) {
|
const independentTimeContextBoundsOrOffsets = this.isFixedTimeMode ? bounds : offsets;
|
||||||
offsets = this.timeOptions.fixedOffsets ?? this.timeContext.getBounds();
|
const independentTimeContextClockKey = this.isFixedTimeMode ? undefined : clockKey;
|
||||||
} else {
|
|
||||||
offsets = this.timeOptions.clockOffsets ?? this.openmct.time.getClockOffsets();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!timeContext.hasOwnContext()) {
|
const independentTimeContext = this.openmct.time.getIndependentContext(this.keyString);
|
||||||
this.unregisterIndependentTime = this.openmct.time.addIndependentContext(
|
|
||||||
|
if (!independentTimeContext.hasOwnContext()) {
|
||||||
|
this.unregisterIndependentTimeContext = this.openmct.time.addIndependentContext(
|
||||||
this.keyString,
|
this.keyString,
|
||||||
offsets,
|
independentTimeContextBoundsOrOffsets,
|
||||||
this.isFixed ? undefined : this.timeOptions.clock
|
independentTimeContextClockKey
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
if (!this.isFixed) {
|
// if (this.isRealTimeMode) {
|
||||||
timeContext.setClock(this.timeOptions.clock);
|
// independentTimeContext.setClock(this.timeOptions.clock);
|
||||||
}
|
// }
|
||||||
|
|
||||||
timeContext.setMode(this.timeOptions.mode, offsets);
|
// independentTimeContext.setMode(this.timeOptions.mode, independentTimeContextBoundsOrOffsets);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
destroyIndependentTime() {
|
registerIndependentTimeOffsets() {
|
||||||
if (this.unregisterIndependentTime) {
|
// const timeContext = this.openmct.time.getIndependentContext(this.keyString);
|
||||||
this.unregisterIndependentTime();
|
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';
|
import { FIXED_MODE_KEY, REALTIME_MODE_KEY } from '../../api/time/constants.js';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
props: {
|
|
||||||
buttonCssClass: {
|
|
||||||
type: String,
|
|
||||||
required: false,
|
|
||||||
default() {
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
methods: {
|
methods: {
|
||||||
loadModes() {
|
loadModes() {
|
||||||
this.modes = [FIXED_MODE_KEY, REALTIME_MODE_KEY].map(this.getModeMetadata);
|
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
|
//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) {
|
if (clockOffsets && defaultMode === FIXED_MODE_KEY) {
|
||||||
openmct.time.setClockOffsets(clockOffsets);
|
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.setMode(defaultMode, defaultClock ? clockOffsets : defaultBounds);
|
||||||
openmct.time.setBounds(clockOffsets);
|
openmct.time.setTimeSystem(defaults.timeSystem, defaultBounds);
|
||||||
}
|
|
||||||
|
|
||||||
openmct.on('start', function () {
|
openmct.on('start', function () {
|
||||||
mountComponent(openmct, config);
|
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 {
|
export default class UTCTimeFormat {
|
||||||
constructor() {
|
constructor() {
|
||||||
this.key = 'utc';
|
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 = {
|
this.DATE_FORMATS = {
|
||||||
PRECISION_DEFAULT: this.DATE_FORMAT,
|
PRECISION_DEFAULT: this.DATE_FORMAT,
|
||||||
PRECISION_DEFAULT_WITH_ZULU: this.DATE_FORMAT + 'Z',
|
PRECISION_DEFAULT_WITH_ZULU: this.DATE_FORMAT + 'Z',
|
||||||
PRECISION_SECONDS: 'YYYY-MM-DD HH:mm:ss',
|
PRECISION_SECONDS: `YYYY-MM-DD${this.DATE_DELIMITER}HH:mm:ss`,
|
||||||
PRECISION_MINUTES: 'YYYY-MM-DD HH:mm',
|
PRECISION_MINUTES: `YYYY-MM-DD${this.DATE_DELIMITER}HH:mm`,
|
||||||
PRECISION_DAYS: 'YYYY-MM-DD',
|
PRECISION_DAYS: 'YYYY-MM-DD',
|
||||||
PRECISION_SECONDS_TIME_ONLY: 'HH:mm:ss',
|
PRECISION_SECONDS_TIME_ONLY: 'HH:mm:ss',
|
||||||
PRECISION_MINUTES_TIME_ONLY: 'HH:mm'
|
PRECISION_MINUTES_TIME_ONLY: 'HH:mm'
|
||||||
@ -85,4 +86,8 @@ export default class UTCTimeFormat {
|
|||||||
validate(text) {
|
validate(text) {
|
||||||
return moment.utc(text, Object.values(this.DATE_FORMATS), true).isValid();
|
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-grid-off: '\ea39';
|
||||||
$glyph-icon-camera: '\ea3a';
|
$glyph-icon-camera: '\ea3a';
|
||||||
$glyph-icon-folders-collapse: '\ea3b';
|
$glyph-icon-folders-collapse: '\ea3b';
|
||||||
|
$glyph-icon-multiline: '\ea3c';
|
||||||
|
$glyph-icon-singleline: '\ea3d';
|
||||||
$glyph-icon-activity: '\eb00';
|
$glyph-icon-activity: '\eb00';
|
||||||
$glyph-icon-activity-mode: '\eb01';
|
$glyph-icon-activity-mode: '\eb01';
|
||||||
$glyph-icon-autoflow-tabular: '\eb02';
|
$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-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-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-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" 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-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" />
|
<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 */
|
/******************************************************** PROGRESS BAR */
|
||||||
@keyframes progressIndeterminate {
|
@keyframes progressIndeterminate {
|
||||||
0% {
|
0% {
|
||||||
left: 0;
|
transform:scaleX(0);
|
||||||
width: 0;
|
|
||||||
}
|
}
|
||||||
70% {
|
90% {
|
||||||
left: 0;
|
transform:scaleX(1);
|
||||||
width: 100%;
|
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
100% {
|
100% {
|
||||||
left: 100%;
|
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -24,11 +21,10 @@
|
|||||||
|
|
||||||
&__bar {
|
&__bar {
|
||||||
background: $colorProgressBar;
|
background: $colorProgressBar;
|
||||||
height: 100%;
|
transform-origin: left;
|
||||||
min-height: $progressBarMinH;
|
|
||||||
|
|
||||||
&.--indeterminate {
|
&.--indeterminate {
|
||||||
position: absolute;
|
@include abs();
|
||||||
animation: progressIndeterminate 1.5s ease-in infinite;
|
animation: progressIndeterminate 1.5s ease-in infinite;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -32,21 +32,25 @@
|
|||||||
class="l-shell__head"
|
class="l-shell__head"
|
||||||
:class="{
|
:class="{
|
||||||
'l-shell__head--expanded': headExpanded,
|
'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" />
|
<CreateButton class="l-shell__create-button" />
|
||||||
<GrandSearch ref="grand-search" />
|
<GrandSearch ref="grand-search" />
|
||||||
<StatusIndicators />
|
<StatusIndicators ref="indicatorsComponent" />
|
||||||
<button
|
<button
|
||||||
class="l-shell__head__collapse-button c-icon-button"
|
class="l-shell__head__button"
|
||||||
:class="
|
:class="indicatorsMultilineCssClass"
|
||||||
headExpanded
|
:aria-label="indicatorsMultilineLabel"
|
||||||
? 'l-shell__head__collapse-button--collapse'
|
:title="indicatorsMultilineLabel"
|
||||||
: 'l-shell__head__collapse-button--expand'
|
@click="toggleIndicatorsMultiline"
|
||||||
"
|
></button>
|
||||||
:aria-label="`Click to ${headExpanded ? 'collapse' : 'expand'} items`"
|
<button
|
||||||
:title="`Click to ${headExpanded ? 'collapse' : 'expand'} items`"
|
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"
|
@click="toggleShellHead"
|
||||||
></button>
|
></button>
|
||||||
<NotificationBanner />
|
<NotificationBanner />
|
||||||
@ -167,6 +171,8 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
import { computed, nextTick, onMounted, onUnmounted, ref } from 'vue';
|
||||||
|
|
||||||
import ObjectView from '../components/ObjectView.vue';
|
import ObjectView from '../components/ObjectView.vue';
|
||||||
import Inspector from '../inspector/InspectorPanel.vue';
|
import Inspector from '../inspector/InspectorPanel.vue';
|
||||||
import Toolbar from '../toolbar/ToolbarContainer.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 NotificationBanner from './status-bar/NotificationBanner.vue';
|
||||||
import StatusIndicators from './status-bar/StatusIndicators.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 {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
Inspector,
|
Inspector,
|
||||||
@ -198,13 +208,120 @@ export default {
|
|||||||
RecentObjectsList
|
RecentObjectsList
|
||||||
},
|
},
|
||||||
inject: ['openmct'],
|
inject: ['openmct'],
|
||||||
data: function () {
|
setup() {
|
||||||
let storedHeadProps = window.localStorage.getItem('openmct-shell-head');
|
let resizeObserver;
|
||||||
let headExpanded = true;
|
let element;
|
||||||
if (storedHeadProps) {
|
|
||||||
headExpanded = JSON.parse(storedHeadProps).expanded;
|
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 {
|
return {
|
||||||
fullScreen: false,
|
fullScreen: false,
|
||||||
conductorComponent: undefined,
|
conductorComponent: undefined,
|
||||||
@ -213,7 +330,6 @@ export default {
|
|||||||
actionCollection: undefined,
|
actionCollection: undefined,
|
||||||
triggerSync: false,
|
triggerSync: false,
|
||||||
triggerReset: false,
|
triggerReset: false,
|
||||||
headExpanded,
|
|
||||||
isResizing: false,
|
isResizing: false,
|
||||||
disableClearButton: false
|
disableClearButton: false
|
||||||
};
|
};
|
||||||
@ -261,16 +377,6 @@ export default {
|
|||||||
document.msExitFullscreen();
|
document.msExitFullscreen();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
toggleShellHead() {
|
|
||||||
this.headExpanded = !this.headExpanded;
|
|
||||||
|
|
||||||
window.localStorage.setItem(
|
|
||||||
'openmct-shell-head',
|
|
||||||
JSON.stringify({
|
|
||||||
expanded: this.headExpanded
|
|
||||||
})
|
|
||||||
);
|
|
||||||
},
|
|
||||||
fullScreenToggle() {
|
fullScreenToggle() {
|
||||||
if (this.fullScreen) {
|
if (this.fullScreen) {
|
||||||
this.fullScreen = false;
|
this.fullScreen = false;
|
||||||
|
@ -24,6 +24,7 @@
|
|||||||
<div class="l-browse-bar__start">
|
<div class="l-browse-bar__start">
|
||||||
<button
|
<button
|
||||||
v-if="hasParent"
|
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"
|
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"
|
title="Navigate up to parent"
|
||||||
@click="goToParent"
|
@click="goToParent"
|
||||||
|
@ -213,27 +213,14 @@
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
background: $colorHeadBg;
|
background: $colorHeadBg;
|
||||||
display: grid;
|
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;
|
grid-column-gap: $interiorMargin;
|
||||||
min-height: 34px;
|
|
||||||
padding: $interiorMargin $interiorMargin + 2;
|
padding: $interiorMargin $interiorMargin + 2;
|
||||||
|
|
||||||
.l-shell__head__collapse-button {
|
.l-shell__head__button {
|
||||||
color: $colorBtnMajorBg;
|
color: $colorBtnMajorBg;
|
||||||
flex: 0 0 auto;
|
flex: 0 0 auto;
|
||||||
font-size: 0.9em;
|
font-size: 0.9em;
|
||||||
|
|
||||||
&--collapse {
|
|
||||||
&:before {
|
|
||||||
content: $glyph-icon-items-collapse;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&--expand {
|
|
||||||
&:before {
|
|
||||||
content: $glyph-icon-items-expand;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
&-section {
|
&-section {
|
||||||
@ -271,14 +258,21 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
&__indicators {
|
&__indicators {
|
||||||
|
// Style as multiline by default
|
||||||
|
display: flex;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
font-size: 11px;
|
font-size: 11px;
|
||||||
|
min-height: 25px;
|
||||||
justify-content: flex-end;
|
justify-content: flex-end;
|
||||||
|
|
||||||
.l-shell__head--expanded & {
|
.l-shell__head--indicators-single-line & {
|
||||||
// Force elements to wrap down when width constrained
|
flex-wrap: nowrap;
|
||||||
height: 24px;
|
justify-content: flex-start; // Overflow detection doesn't work with flex-end.
|
||||||
overflow: hidden;
|
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.
|
at runtime from the About dialog for additional information.
|
||||||
-->
|
-->
|
||||||
<template>
|
<template>
|
||||||
<div class="l-shell__head-section l-shell__indicators">
|
<div ref="indicatorsContainer" class="l-shell__head-section l-shell__indicators">
|
||||||
<component
|
<component
|
||||||
:is="indicator.value.vueComponent"
|
:is="indicator.value.vueComponent"
|
||||||
v-for="indicator in sortedIndicators"
|
v-for="indicator in sortedIndicators"
|
||||||
@ -28,9 +28,15 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { shallowRef } from 'vue';
|
import { defineExpose, ref, shallowRef } from 'vue';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
inject: ['openmct'],
|
inject: ['openmct'],
|
||||||
|
setup() {
|
||||||
|
const indicatorsContainer = ref(null);
|
||||||
|
|
||||||
|
defineExpose({ indicatorsContainer });
|
||||||
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
indicators: this.openmct.indicators.getIndicatorObjectsByPriority().map(shallowRef)
|
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
|
* as represented by the Administrator of the National Aeronautics and Space
|
||||||
* Administration. All rights reserved.
|
* Administration. All rights reserved.
|
||||||
*
|
*
|
||||||
@ -49,20 +49,19 @@ class VisibilityObserver {
|
|||||||
* Constructs a VisibilityObserver instance to manage visibility-based requestAnimationFrame calls.
|
* Constructs a VisibilityObserver instance to manage visibility-based requestAnimationFrame calls.
|
||||||
*
|
*
|
||||||
* @param {HTMLElement} element - The DOM element to observe for visibility changes.
|
* @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.
|
* @throws {Error} If element is not provided.
|
||||||
*/
|
*/
|
||||||
constructor(element, rootContainer) {
|
constructor(element) {
|
||||||
if (!element || !rootContainer) {
|
if (!element) {
|
||||||
throw new Error(`VisibilityObserver must be created with an element and a rootContainer.`);
|
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.#element = element;
|
||||||
this.isIntersecting = true;
|
this.isIntersecting = true;
|
||||||
this.calledOnce = false;
|
|
||||||
const options = {
|
this.#observer = new IntersectionObserver(this.#observerCallback);
|
||||||
root: rootContainer
|
this.#observer.observe(this.#element);
|
||||||
};
|
|
||||||
this.#observer = new IntersectionObserver(this.#observerCallback, options);
|
|
||||||
this.lastUnfiredFunc = null;
|
this.lastUnfiredFunc = null;
|
||||||
this.renderWhenVisible = this.renderWhenVisible.bind(this);
|
this.renderWhenVisible = this.renderWhenVisible.bind(this);
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user