Compare commits

..

12 Commits

Author SHA1 Message Date
b59ed09274 changing how we pass in the options, so you can do one at a time, as well as return possible roles after login promise has resolved 2024-05-13 15:36:07 -07:00
810d580b18 [Telemetry Tables] Make sure tables auto scroll correctly on first load (#7720)
* run scroll method to scroll to top after initial load of historical data

* clarifying comment

* added e2e test to make sure tables auto scroll on mount

* adding descriptive comments

* adding in ascending check as well

* added new appAction for navigating to/in realtime, using it in table scroll test

* lint

---------

Co-authored-by: David Tsay <3614296+davetsay@users.noreply.github.com>
2024-05-09 11:53:11 -07:00
977792fae8 this is 2024. * observers no more. (#7715)
* this is 2024. `*` observers no more.

* add edit and save domain object helper functions

* add aria-labels and fix e2e tests to use new labels

* generate and save in local storage a condition set with telemetry and condition

* rename const

* move creation code out of generateLocalStorage since it is immutable

* remove function abstractions

* remove @localStorage test label

* Update e2e/tests/functional/plugins/conditionSet/conditionSet.e2e.spec.js

Co-authored-by: Jesse Mazzella <ozyx@users.noreply.github.com>

* Update e2e/tests/functional/plugins/conditionSet/conditionSet.e2e.spec.js

Co-authored-by: Jesse Mazzella <ozyx@users.noreply.github.com>

* Update e2e/tests/functional/plugins/conditionSet/conditionSet.e2e.spec.js

Co-authored-by: Jesse Mazzella <ozyx@users.noreply.github.com>

* Update e2e/tests/functional/plugins/conditionSet/conditionSet.e2e.spec.js

Co-authored-by: Jesse Mazzella <ozyx@users.noreply.github.com>

* remove unneccesary aria text

* remove unneccessary aria text

* use recommended playwright locators

* lint fix

* remove unneccesary steps now that child created directly in parent

---------

Co-authored-by: Jesse Mazzella <ozyx@users.noreply.github.com>
2024-05-09 11:19:55 -07:00
a69e300f1c chore: bump playwright to v1.44.0 (#7716) 2024-05-09 08:20:31 -07:00
17bc6cb722 chore(webpack): destruct version from pkgJson (#7713) 2024-05-07 08:52:41 -07:00
b3d3465734 [Darkmatter] Create new darkmatter theme (#7682)
* initial theme plugin setup, changes to layout frames

* update visual tests

* Changes to gauge, layout borders, and background

* Make background image a DIY theme variable. Fixes made to gauges. Deleted custom font.

* More changes to overall background colors. Added glass layer effect to menus

* changes to menu

* Fix to make theme easy to run

* Fix tab colors and add glass background to menus

* make highlightd corners longer

* Initial changes to font styles

* Add temporary numeric font style. Test numeric font in gauges.

* Initial changes to alphanumerics in layouts

* Updated variables

* update plugin.js file

* Fix highlighted corners on frames such that it uses outermost frame

* renaming theme plugin and rename branch

* fix button colors to be more readable

* change background image

* Fix bad merges from other theme files. Fix gauge and alphanumerics such that they dont have darkmatter borders

* more fixes

* Fix where mixin is used such that when an object's frame is hidden, highlgihts disappear

* remove blur from meter gauges

* Add comment about this theme being in beta mode

* Delete draft .scss file that is no longer needed

* Fix major accessibility issues

* Fix PR review comments

*  fix: Correct import file name for DarkMatter theme.

* Fix other theme code that was failing e2e tests

* Revert index.html

* Fix linting error

* Fix for failing percy test regarding padding

* Fix for failing percy test regarding padding part 2

* Fix for failing percy test regarding padding part 3

* Remove mixin that may be causing percy issue

* Another fix to resolve percy issue

* Add back some code that was deleted during debugging, and create new variables for the object padding

* Fix gradient clipping in inspector

* Restructure all constants-.scss files

* Change bg image to be square and NASA official picture

* Final fixes to darkmatter variable layouts

* Address PR comments

* Change darkmatter to darkmatterTheme

---------

Co-authored-by: John Hill <john.c.hill@nasa.gov>
Co-authored-by: Jesse Mazzella <jesse.d.mazzella@nasa.gov>
2024-04-25 16:06:07 -07:00
fb0d74e87f chore(deps-dev): bump vue from 3.4.19 to 3.4.24 (#7702)
Bumps [vue](https://github.com/vuejs/core) from 3.4.19 to 3.4.24.
- [Release notes](https://github.com/vuejs/core/releases)
- [Changelog](https://github.com/vuejs/core/blob/main/CHANGELOG.md)
- [Commits](https://github.com/vuejs/core/compare/v3.4.19...v3.4.24)

---
updated-dependencies:
- dependency-name: vue
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-04-22 13:19:00 -07:00
a961d7e3bf Fix nested Flexible Layout direction problem (#7637)
* Closes #7635
- More specific approach to CSS class application for column vs. row layouts.
- Added layout direction CSS classing to `c-fl-container__frames-holder`.
- Switched toolbar icon and titling for better parity with
'toggle' approach used elsewhere.
- Cleaned up duped property def in mixin.

* Addressing PR change requests
- Updated e2e test.
- New computed properties for layout direction.
- CSS code cleanup.

* fix selector in test

* fix more bad selectors

* fix changed title

---------

Co-authored-by: David Tsay <david.e.tsay@nasa.gov>
2024-04-18 23:38:11 +00:00
5a06b51c5a refactor: remove the final .bounds() call (#7695)
 refactor: Use getBounds method instead of bounds in IndependentTimeContext.
2024-04-17 16:19:21 +00:00
ef8b353d01 Improve performance of JSON import and add progress bar (#7654)
* add progress bar

* improves performance a lot

* fix test

* remove old function

* fix legit bug from test - well done tests

* add constant, better comments

---------

Co-authored-by: Jesse Mazzella <ozyx@users.noreply.github.com>
2024-04-16 21:23:31 +00:00
6c5b925454 chore: remove all usage of deprecated Time API methods (#7688)
* chore: remove all usage of deprecated Time API methods

* test: update unit test

* docs: Fix spacing and add clarity to TimeConductorBounds definition.

* test: add unit test coverage for new time api methods
2024-04-16 21:12:09 +00:00
e91aba2e37 Handle paste events for images and text properly (#7679)
* enable eval source maps for debugging

* split image and text paste handling
better event handling

* change back source maps

* image takes precedence over text

* break up notebook entry functions for re-use

* create hotkeys utils
add clipboard functions

* add notebook paste test

* add test for pasting to selected but not editing entry

* link tests to issue

* jsdoc addition

* jsdocs

* no need to import then export

Co-authored-by: Jesse Mazzella <ozyx@users.noreply.github.com>

* fix changed path

---------

Co-authored-by: Jesse Mazzella <ozyx@users.noreply.github.com>
2024-04-16 13:54:40 -07:00
74 changed files with 1723 additions and 343 deletions

View File

@ -5,7 +5,7 @@ orbs:
executors: executors:
pw-focal-development: pw-focal-development:
docker: docker:
- image: mcr.microsoft.com/playwright:v1.42.1-focal - image: mcr.microsoft.com/playwright:v1.44.0-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
@ -159,7 +159,7 @@ jobs:
steps: steps:
- build_and_install: - build_and_install:
node-version: lts/hydrogen node-version: lts/hydrogen
- run: npx playwright@1.42.1 install #Necessary for bare ubuntu machine - run: npx playwright@1.44.0 install #Necessary for bare ubuntu machine
- run: | - run: |
export $(cat src/plugins/persistence/couch/.env.ci | xargs) export $(cat src/plugins/persistence/couch/.env.ci | xargs)
docker-compose -f src/plugins/persistence/couch/couchdb-compose.yaml up --detach docker-compose -f src/plugins/persistence/couch/couchdb-compose.yaml up --detach

View File

@ -497,7 +497,8 @@
"checksnapshots", "checksnapshots",
"specced", "specced",
"composables", "composables",
"countup" "countup",
"darkmatter"
], ],
"dictionaries": ["npm", "softwareTerms", "node", "html", "css", "bash", "en_US", "en-gb", "misc"], "dictionaries": ["npm", "softwareTerms", "node", "html", "css", "bash", "en_US", "en-gb", "misc"],
"ignorePaths": [ "ignorePaths": [

View File

@ -37,7 +37,7 @@ jobs:
username: ${{ secrets.DOCKERHUB_USERNAME }} username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }} password: ${{ secrets.DOCKERHUB_TOKEN }}
- run: npx playwright@1.42.1 install - run: npx playwright@1.44.0 install
- name: Start CouchDB Docker Container and Init with Setup Scripts - name: Start CouchDB Docker Container and Init with Setup Scripts
run: | run: |

View File

@ -30,7 +30,7 @@ jobs:
restore-keys: | restore-keys: |
${{ runner.os }}-node- ${{ runner.os }}-node-
- run: npx playwright@1.42.1 install - run: npx playwright@1.44.0 install
- run: npm ci --no-audit --progress=false - run: npm ci --no-audit --progress=false
- name: Run E2E Tests (Repeated 10 Times) - name: Run E2E Tests (Repeated 10 Times)

View File

@ -28,7 +28,7 @@ jobs:
restore-keys: | restore-keys: |
${{ runner.os }}-node- ${{ runner.os }}-node-
- run: npx playwright@1.42.1 install - run: npx playwright@1.44.0 install
- run: npm ci --no-audit --progress=false - run: npm ci --no-audit --progress=false
- run: npm run test:perf:localhost - run: npm run test:perf:localhost
- run: npm run test:perf:contract - run: npm run test:perf:contract

View File

@ -33,7 +33,7 @@ jobs:
restore-keys: | restore-keys: |
${{ runner.os }}-node- ${{ runner.os }}-node-
- run: npx playwright@1.42.1 install - run: npx playwright@1.44.0 install
- run: npx playwright install chrome-beta - run: npx playwright install chrome-beta
- run: npm ci --no-audit --progress=false - run: npm ci --no-audit --progress=false
- run: npm run test:e2e:full -- --max-failures=40 - run: npm run test:e2e:full -- --max-failures=40

View File

@ -19,7 +19,7 @@ import { merge } from 'webpack-merge';
let gitRevision = 'error-retrieving-revision'; let gitRevision = 'error-retrieving-revision';
let gitBranch = 'error-retrieving-branch'; let gitBranch = 'error-retrieving-branch';
const packageDefinition = JSON.parse(fs.readFileSync(new URL('../package.json', import.meta.url))); const { version } = JSON.parse(fs.readFileSync(new URL('../package.json', import.meta.url)));
try { try {
gitRevision = execSync('git rev-parse HEAD').toString().trim(); gitRevision = execSync('git rev-parse HEAD').toString().trim();
@ -49,7 +49,8 @@ const config = {
couchDBChangesFeed: './src/plugins/persistence/couch/CouchChangesFeed.js', couchDBChangesFeed: './src/plugins/persistence/couch/CouchChangesFeed.js',
inMemorySearchWorker: './src/api/objects/InMemorySearchWorker.js', inMemorySearchWorker: './src/api/objects/InMemorySearchWorker.js',
espressoTheme: './src/plugins/themes/espresso-theme.scss', espressoTheme: './src/plugins/themes/espresso-theme.scss',
snowTheme: './src/plugins/themes/snow-theme.scss' snowTheme: './src/plugins/themes/snow-theme.scss',
darkmatterTheme: './src/plugins/themes/darkmatter-theme.scss'
}, },
output: { output: {
globalObject: 'this', globalObject: 'this',
@ -84,7 +85,7 @@ const config = {
}, },
plugins: [ plugins: [
new webpack.DefinePlugin({ new webpack.DefinePlugin({
__OPENMCT_VERSION__: `'${packageDefinition.version}'`, __OPENMCT_VERSION__: `'${version}'`,
__OPENMCT_BUILD_DATE__: `'${new Date()}'`, __OPENMCT_BUILD_DATE__: `'${new Date()}'`,
__OPENMCT_REVISION__: `'${gitRevision}'`, __OPENMCT_REVISION__: `'${gitRevision}'`,
__OPENMCT_BUILD_BRANCH__: `'${gitBranch}'`, __OPENMCT_BUILD_BRANCH__: `'${gitBranch}'`,

View File

@ -275,6 +275,17 @@ async function navigateToObjectWithFixedTimeBounds(page, url, start, end) {
); );
} }
/**
* Navigates directly to a given object url, in real-time mode.
* @param {import('@playwright/test').Page} page
* @param {string} url The url to the domainObject
*/
async function navigateToObjectWithRealTime(page, url, start = '1800000', end = '30000') {
await page.goto(
`${url}?tc.mode=local&tc.startDelta=${start}&tc.endDelta=${end}&tc.timeSystem=utc`
);
}
/** /**
* Open the given `domainObject`'s context menu from the object tree. * Open the given `domainObject`'s context menu from the object tree.
* Expands the path to the object and scrolls to it if necessary. * Expands the path to the object and scrolls to it if necessary.
@ -656,6 +667,7 @@ export {
getFocusedObjectUuid, getFocusedObjectUuid,
getHashUrlToDomainObject, getHashUrlToDomainObject,
navigateToObjectWithFixedTimeBounds, navigateToObjectWithFixedTimeBounds,
navigateToObjectWithRealTime,
openObjectTreeContextMenu, openObjectTreeContextMenu,
renameObjectFromContextMenu, renameObjectFromContextMenu,
setEndOffset, setEndOffset,

View File

@ -0,0 +1,47 @@
/*****************************************************************************
* 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.
*****************************************************************************/
const isMac = process.platform === 'darwin';
const modifier = isMac ? 'Meta' : 'Control';
/**
* @param {import('@playwright/test').Page} page
*/
async function selectAll(page) {
await page.keyboard.press(`${modifier}+KeyA`);
}
/**
* @param {import('@playwright/test').Page} page
*/
async function copy(page) {
await page.keyboard.press(`${modifier}+KeyC`);
}
/**
* @param {import('@playwright/test').Page} page
*/
async function paste(page) {
await page.keyboard.press(`${modifier}+KeyV`);
}
export { copy, paste, selectAll };

View File

@ -0,0 +1,23 @@
/*****************************************************************************
* 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.
*****************************************************************************/
export * from './clipboard.js';

View File

@ -28,16 +28,28 @@ import { fileURLToPath } from 'url';
/** /**
* @param {import('@playwright/test').Page} page * @param {import('@playwright/test').Page} page
* @param {string} text
*/ */
async function enterTextEntry(page, text) { async function enterTextEntry(page, text) {
// Click the 'Add Notebook Entry' area await addNotebookEntry(page);
await page.locator(NOTEBOOK_DROP_AREA).click(); await enterTextInLastEntry(page, text);
// enter text
await page.getByLabel('Notebook Entry Input').last().fill(text);
await commitEntry(page); await commitEntry(page);
} }
/**
* @param {import('@playwright/test').Page} page
*/
async function addNotebookEntry(page) {
await page.locator(NOTEBOOK_DROP_AREA).click();
}
/**
* @param {import('@playwright/test').Page} page
*/
async function enterTextInLastEntry(page, text) {
await page.getByLabel('Notebook Entry Input').last().fill(text);
}
/** /**
* @param {import('@playwright/test').Page} page * @param {import('@playwright/test').Page} page
*/ */
@ -140,10 +152,13 @@ async function createNotebookEntryAndTags(page, iterations = 1) {
} }
export { export {
addNotebookEntry,
commitEntry,
createNotebookAndEntry, createNotebookAndEntry,
createNotebookEntryAndTags, createNotebookEntryAndTags,
dragAndDropEmbed, dragAndDropEmbed,
enterTextEntry, enterTextEntry,
enterTextInLastEntry,
lockPage, lockPage,
startAndAddRestrictedNotebookObject startAndAddRestrictedNotebookObject
}; };

View File

@ -18,7 +18,7 @@
"@types/sinonjs__fake-timers": "8.1.5", "@types/sinonjs__fake-timers": "8.1.5",
"@percy/cli": "1.27.4", "@percy/cli": "1.27.4",
"@percy/playwright": "1.0.4", "@percy/playwright": "1.0.4",
"@playwright/test": "1.42.1", "@playwright/test": "1.44.0",
"@axe-core/playwright": "4.8.5", "@axe-core/playwright": "4.8.5",
"sinon": "17.0.0" "sinon": "17.0.0"
}, },

View File

@ -36,6 +36,13 @@ const config = {
browserName: 'chromium', browserName: 'chromium',
theme: 'snow' theme: 'snow'
} }
},
{
name: 'darkmatter-theme', //Runs the same visual tests but with darkmatter-theme
use: {
browserName: 'chromium',
theme: 'darkmatter-theme'
}
} }
], ],
reporter: [ reporter: [

View File

@ -371,7 +371,7 @@ test.describe('Basic Condition Set Use', () => {
// Validate that the condition set is evaluating and outputting // Validate that the condition set is evaluating and outputting
// the correct value when the underlying telemetry subscription is active. // the correct value when the underlying telemetry subscription is active.
let outputValue = page.locator('[aria-label="Current Output Value"]'); let outputValue = page.getByLabel('Current Output Value');
await expect(outputValue).toHaveText('false'); await expect(outputValue).toHaveText('false');
await page.goto(exampleTelemetry.url); await page.goto(exampleTelemetry.url);
@ -462,7 +462,7 @@ test.describe('Basic Condition Set Use', () => {
// Validate that the condition set is evaluating and outputting // Validate that the condition set is evaluating and outputting
// the correct value when the underlying telemetry subscription is active. // the correct value when the underlying telemetry subscription is active.
let outputValue = page.locator('[aria-label="Current Output Value"]'); let outputValue = page.getByLabel('Current Output Value');
await expect(outputValue).toHaveText('false'); await expect(outputValue).toHaveText('false');
await page.goto(exampleTelemetry.url); await page.goto(exampleTelemetry.url);
@ -475,3 +475,81 @@ test.describe('Basic Condition Set Use', () => {
}); });
}); });
}); });
test.describe('Condition Set Composition', () => {
let conditionSet;
let exampleTelemetry;
test.beforeEach(async ({ page }) => {
await page.goto('./', { waitUntil: 'domcontentloaded' });
// Create Condition Set
conditionSet = await createDomainObjectWithDefaults(page, {
type: 'Condition Set'
});
// Create Telemetry Object as child to Condition Set
exampleTelemetry = await createExampleTelemetryObject(page, conditionSet.uuid);
// Edit Condition Set
await page.goto(conditionSet.url);
await page.getByRole('button', { name: 'Edit Object' }).click();
// Add Condition to Condition Set
await page.getByRole('button', { name: 'Add Condition' }).click();
// Enter Condition Output
await page.getByLabel('Condition Name Input').first().fill('Negative');
await page.getByLabel('Condition Output Type').first().selectOption({ value: 'string' });
await page.getByLabel('Condition Output String').first().fill('Negative');
// Condition Trigger default is okay so no change needed to form
// Enter Condition Criterion
await page.getByLabel('Criterion Telemetry Selection').first().selectOption({ value: 'all' });
await page.getByLabel('Criterion Metadata Selection').first().selectOption({ value: 'sin' });
await page
.locator('select[aria-label="Criterion Comparison Selection"]')
.first()
.selectOption({ value: 'lessThan' });
await page.getByLabel('Criterion Input').first().fill('0');
// Save the Condition Set
await page.getByRole('button', { name: 'Save' }).click();
await page.getByRole('listitem', { name: 'Save and Finish Editing' }).click();
});
test('You can remove telemetry from a condition set with existing conditions', async ({
page
}) => {
test.info().annotations.push({
type: 'issue',
description: 'https://github.com/nasa/openmct/issues/7710'
});
await page.getByLabel('Expand My Items folder').click();
await page.getByLabel(`Expand ${conditionSet.name} conditionSet`).click();
await page
.getByLabel(`Navigate to ${exampleTelemetry.name}`, { exact: false })
.click({ button: 'right' });
await page
.getByLabel(`${exampleTelemetry.name} Context Menu`)
.getByRole('menuitem', { name: 'Remove' })
.click();
await page.getByRole('button', { name: 'OK', exact: true }).click();
await page
.getByLabel(`Navigate to ${conditionSet.name} conditionSet Object`, { exact: true })
.click();
await page.getByRole('button', { name: 'Edit Object' }).click();
await page.getByRole('tab', { name: 'Elements' }).click();
expect(
await page
.getByRole('tabpanel', { name: 'Inspector Views' })
.getByRole('listitem', { name: exampleTelemetry.name })
.count()
).toEqual(0);
});
});

View File

@ -78,8 +78,8 @@ test.describe('Flexible Layout', () => {
// Expand the 'My Items' folder in the left tree // Expand the 'My Items' folder in the left tree
await page.locator('.c-tree__item__view-control.c-disclosure-triangle').first().click(); await page.locator('.c-tree__item__view-control.c-disclosure-triangle').first().click();
// Add the Sine Wave Generator and Clock to the Flexible Layout // Add the Sine Wave Generator and Clock to the Flexible Layout
await sineWaveGeneratorTreeItem.dragTo(page.locator('.c-fl__container.is-empty').first()); await sineWaveGeneratorTreeItem.dragTo(page.locator('.c-fl-container.is-empty').first());
await clockTreeItem.dragTo(page.locator('.c-fl__container.is-empty')); await clockTreeItem.dragTo(page.locator('.c-fl-container.is-empty'));
// Check that panes can be dragged while Flexible Layout is in Edit mode // Check that panes can be dragged while Flexible Layout is in Edit mode
let dragWrapper = page let dragWrapper = page
.locator('.c-fl-container__frames-holder .c-fl-frame__drag-wrapper') .locator('.c-fl-container__frames-holder .c-fl-frame__drag-wrapper')
@ -105,8 +105,8 @@ test.describe('Flexible Layout', () => {
// Expand the 'My Items' folder in the left tree // Expand the 'My Items' folder in the left tree
await page.locator('.c-tree__item__view-control.c-disclosure-triangle').first().click(); await page.locator('.c-tree__item__view-control.c-disclosure-triangle').first().click();
// Add the Sine Wave Generator and Clock to the Flexible Layout // Add the Sine Wave Generator and Clock to the Flexible Layout
await sineWaveGeneratorTreeItem.dragTo(page.locator('.c-fl__container.is-empty').first()); await sineWaveGeneratorTreeItem.dragTo(page.locator('.c-fl-container.is-empty').first());
await clockTreeItem.dragTo(page.locator('.c-fl__container.is-empty')); await clockTreeItem.dragTo(page.locator('.c-fl-container.is-empty'));
// Click on the first frame to select it // Click on the first frame to select it
await page.locator('.c-fl-container__frame').first().click(); await page.locator('.c-fl-container__frame').first().click();
@ -122,7 +122,7 @@ test.describe('Flexible Layout', () => {
expect(await page.locator('.c-fl--rows').count()).toEqual(0); expect(await page.locator('.c-fl--rows').count()).toEqual(0);
// Change the layout to rows orientation // Change the layout to rows orientation
await page.getByTitle('Columns layout').click(); await page.getByTitle('Switch to rows layout').click();
// Assert the layout is in rows orientation // Assert the layout is in rows orientation
expect(await page.locator('.c-fl--rows').count()).toBeGreaterThan(0); expect(await page.locator('.c-fl--rows').count()).toBeGreaterThan(0);
@ -171,7 +171,7 @@ test.describe('Flexible Layout', () => {
// Expand the 'My Items' folder in the left tree // Expand the 'My Items' folder in the left tree
await page.locator('.c-tree__item__view-control.c-disclosure-triangle').first().click(); await page.locator('.c-tree__item__view-control.c-disclosure-triangle').first().click();
// Add the Sine Wave Generator to the Flexible Layout and save changes // Add the Sine Wave Generator to the Flexible Layout and save changes
await sineWaveGeneratorTreeItem.dragTo(page.locator('.c-fl__container.is-empty').first()); await sineWaveGeneratorTreeItem.dragTo(page.locator('.c-fl-container.is-empty').first());
await page.locator('button[title="Save"]').click(); await page.locator('button[title="Save"]').click();
await page.getByRole('listitem', { name: 'Save and Finish Editing' }).click(); await page.getByRole('listitem', { name: 'Save and Finish Editing' }).click();
@ -202,7 +202,7 @@ test.describe('Flexible Layout', () => {
// Expand the 'My Items' folder in the left tree // Expand the 'My Items' folder in the left tree
await page.locator('.c-tree__item__view-control.c-disclosure-triangle').click(); await page.locator('.c-tree__item__view-control.c-disclosure-triangle').click();
// Add the Sine Wave Generator to the Flexible Layout and save changes // Add the Sine Wave Generator to the Flexible Layout and save changes
await sineWaveGeneratorTreeItem.dragTo(page.locator('.c-fl__container.is-empty').first()); await sineWaveGeneratorTreeItem.dragTo(page.locator('.c-fl-container.is-empty').first());
await page.locator('button[title="Save"]').click(); await page.locator('button[title="Save"]').click();
await page.getByRole('listitem', { name: 'Save and Finish Editing' }).click(); await page.getByRole('listitem', { name: 'Save and Finish Editing' }).click();
@ -242,7 +242,7 @@ test.describe('Flexible Layout', () => {
name: new RegExp(exampleImageryObject.name) name: new RegExp(exampleImageryObject.name)
}); });
// Add the Sine Wave Generator to the Flexible Layout and save changes // Add the Sine Wave Generator to the Flexible Layout and save changes
await exampleImageryTreeItem.dragTo(page.locator('.c-fl__container.is-empty').first()); await exampleImageryTreeItem.dragTo(page.locator('.c-fl-container.is-empty').first());
await page.locator('button[title="Save"]').click(); await page.locator('button[title="Save"]').click();
await page.getByRole('listitem', { name: 'Save and Finish Editing' }).click(); await page.getByRole('listitem', { name: 'Save and Finish Editing' }).click();
@ -309,9 +309,9 @@ test.describe('Flexible Layout Toolbar Actions @localStorage', () => {
await page.getByRole('columnheader', { name: 'Container Handle 1' }).click(); await page.getByRole('columnheader', { name: 'Container Handle 1' }).click();
const flexRows = page.getByLabel('Flexible Layout Row'); const flexRows = page.getByLabel('Flexible Layout Row');
expect(await flexRows.count()).toEqual(0); expect(await flexRows.count()).toEqual(0);
await page.getByTitle('Columns layout').click(); await page.getByTitle('Switch to rows layout').click();
expect(await flexRows.count()).toEqual(1); expect(await flexRows.count()).toEqual(1);
await page.getByTitle('Rows layout').click(); await page.getByTitle('Switch to columns layout').click();
expect(await flexRows.count()).toEqual(0); expect(await flexRows.count()).toEqual(0);
}); });
}); });

View File

@ -27,6 +27,7 @@ This test suite is dedicated to tests which verify the basic operations surround
import { fileURLToPath } from 'url'; import { fileURLToPath } from 'url';
import { createDomainObjectWithDefaults } from '../../../../appActions.js'; import { createDomainObjectWithDefaults } from '../../../../appActions.js';
import { copy, paste, selectAll } from '../../../../helper/hotkeys/hotkeys.js';
import * as nbUtils from '../../../../helper/notebookUtils.js'; import * as nbUtils from '../../../../helper/notebookUtils.js';
import { expect, streamToString, test } from '../../../../pluginFixtures.js'; import { expect, streamToString, test } from '../../../../pluginFixtures.js';
@ -546,4 +547,53 @@ test.describe('Notebook entry tests', () => {
); );
await expect(secondLineOfBlockquoteText).toBeVisible(); await expect(secondLineOfBlockquoteText).toBeVisible();
}); });
/**
* Paste into notebook entry tests
*/
test('Can paste text into a notebook entry', async ({ page }) => {
test.info().annotations.push({
type: 'issue',
description: 'https://github.com/nasa/openmct/issues/7686'
});
const TEST_TEXT = 'This is a test';
const iterations = 20;
const EXPECTED_TEXT = TEST_TEXT.repeat(iterations);
await page.goto(notebookObject.url);
await nbUtils.addNotebookEntry(page);
await nbUtils.enterTextInLastEntry(page, TEST_TEXT);
await selectAll(page);
await copy(page);
for (let i = 0; i < iterations; i++) {
await paste(page);
}
await nbUtils.commitEntry(page);
await expect(page.locator(`text="${EXPECTED_TEXT}"`)).toBeVisible();
});
test('Prevents pasting text into selected notebook entry if not editing', async ({ page }) => {
test.info().annotations.push({
type: 'issue',
description: 'https://github.com/nasa/openmct/issues/7686'
});
const TEST_TEXT = 'This is a test';
await page.goto(notebookObject.url);
await nbUtils.addNotebookEntry(page);
await nbUtils.enterTextInLastEntry(page, TEST_TEXT);
await selectAll(page);
await copy(page);
await paste(page);
await nbUtils.commitEntry(page);
// This should not paste text into the entry
await paste(page);
await expect(await page.locator(`text="${TEST_TEXT.repeat(1)}"`).count()).toEqual(1);
await expect(await page.locator(`text="${TEST_TEXT.repeat(2)}"`).count()).toEqual(0);
});
}); });

View File

@ -22,8 +22,8 @@
import { import {
createDomainObjectWithDefaults, createDomainObjectWithDefaults,
setTimeConductorBounds, navigateToObjectWithRealTime,
setTimeConductorMode setTimeConductorBounds
} from '../../../../appActions.js'; } from '../../../../appActions.js';
import { expect, test } from '../../../../pluginFixtures.js'; import { expect, test } from '../../../../pluginFixtures.js';
@ -39,12 +39,52 @@ test.describe('Telemetry Table', () => {
type: 'Sine Wave Generator', type: 'Sine Wave Generator',
parent: table.uuid parent: table.uuid
}); });
await page.goto(table.url); await navigateToObjectWithRealTime(page, table.url);
await setTimeConductorMode(page, false);
const rows = page.getByLabel('table content').getByLabel('Table Row'); const rows = page.getByLabel('table content').getByLabel('Table Row');
await expect(rows).toHaveCount(50); await expect(rows).toHaveCount(50);
}); });
test('on load, auto scrolls to top for descending, and to bottom for ascending', async ({
page
}) => {
const sineWaveGenerator = await createDomainObjectWithDefaults(page, {
type: 'Sine Wave Generator',
parent: table.uuid
});
// verify in telemetry table object view
await navigateToObjectWithRealTime(page, table.url);
expect(await getScrollPosition(page)).toBe(0);
// verify in telemetry table view
await page.goto(sineWaveGenerator.url);
await page.getByLabel('Open the View Switcher Menu').click();
await page.getByText('Telemetry Table', { exact: true }).click();
expect(await getScrollPosition(page)).toBe(0);
// navigate back to table
await page.goto(table.url);
// go into edit mode
await page.getByLabel('Edit Object').click();
// change sort direction
await page.locator('thead div').filter({ hasText: 'Time' }).click();
// save view
await page.getByRole('button', { name: 'Save' }).click();
await page.getByRole('listitem', { name: 'Save and Finish Editing' }).click();
// navigate away and back
await page.goto(sineWaveGenerator.url);
await page.goto(table.url);
// verify scroll position
expect(await getScrollPosition(page, false)).toBeLessThan(1);
});
test('unpauses and filters data when paused by button and user changes bounds', async ({ test('unpauses and filters data when paused by button and user changes bounds', async ({
page page
}) => { }) => {
@ -183,3 +223,42 @@ test.describe('Telemetry Table', () => {
await page.click('button[title="Pause"]'); await page.click('button[title="Pause"]');
}); });
}); });
async function getScrollPosition(page, top = true) {
const tableBody = page.locator('.c-table__body-w');
// Wait for the scrollbar to appear
await tableBody.evaluate((node) => {
return new Promise((resolve) => {
function checkScroll() {
if (node.scrollHeight > node.clientHeight) {
resolve();
} else {
setTimeout(checkScroll, 100);
}
}
checkScroll();
});
});
// make sure there are rows
const rows = page.getByLabel('table content').getByLabel('Table Row');
await rows.first().waitFor();
// Using this to allow for rows to come and go, so we can truly test the scroll position
// eslint-disable-next-line playwright/no-wait-for-timeout
await page.waitForTimeout(1000);
const { scrollTop, clientHeight, scrollHeight } = await tableBody.evaluate((node) => ({
scrollTop: node.scrollTop,
clientHeight: node.clientHeight,
scrollHeight: node.scrollHeight
}));
// eslint-disable-next-line playwright/no-conditional-in-test
if (top) {
return scrollTop;
} else {
return Math.abs(scrollHeight - (scrollTop + clientHeight));
}
}

View File

@ -265,8 +265,8 @@ test.describe('Verify tooltips', () => {
name: 'Test Flexible Layout' name: 'Test Flexible Layout'
}); });
await page.dragAndDrop(`text=${sineWaveObject1.name}`, '.c-fl__container >> nth=0'); await page.dragAndDrop(`text=${sineWaveObject1.name}`, '.c-fl-container >> nth=0');
await page.dragAndDrop(`text=${sineWaveObject3.name}`, '.c-fl__container >> nth=1'); await page.dragAndDrop(`text=${sineWaveObject3.name}`, '.c-fl-container >> nth=1');
await page.locator('button[title="Save"]').click(); await page.locator('button[title="Save"]').click();
await page.getByRole('listitem', { name: 'Save and Finish Editing' }).click(); await page.getByRole('listitem', { name: 'Save and Finish Editing' }).click();

View File

@ -141,7 +141,7 @@ export default class ExampleUserProvider extends EventEmitter {
} }
getPossibleRoles() { getPossibleRoles() {
return this.user.getRoles(); return this.loginPromise.then(() => this.user.getRoles());
} }
getPossibleMissionActions() { getPossibleMissionActions() {

View File

@ -24,12 +24,11 @@ import ExampleUserProvider from './ExampleUserProvider.js';
const AUTO_LOGIN_USER = 'mct-user'; const AUTO_LOGIN_USER = 'mct-user';
const STATUS_ROLES = ['flight', 'driver']; const STATUS_ROLES = ['flight', 'driver'];
export default function ExampleUserPlugin( export default function ExampleUserPlugin(options = {}) {
{ autoLoginUser, statusRoles } = { const {
autoLoginUser: AUTO_LOGIN_USER, autoLoginUser = AUTO_LOGIN_USER,
statusRoles: STATUS_ROLES statusRoles = STATUS_ROLES
} } = options;
) {
return function install(openmct) { return function install(openmct) {
const userProvider = new ExampleUserProvider(openmct, { const userProvider = new ExampleUserProvider(openmct, {
statusRoles statusRoles

227
package-lock.json generated
View File

@ -84,7 +84,7 @@
"tiny-emitter": "2.1.0", "tiny-emitter": "2.1.0",
"typescript": "5.3.3", "typescript": "5.3.3",
"uuid": "9.0.1", "uuid": "9.0.1",
"vue": "3.4.19", "vue": "3.4.24",
"vue-eslint-parser": "9.4.2", "vue-eslint-parser": "9.4.2",
"vue-loader": "16.8.3", "vue-loader": "16.8.3",
"webpack": "5.90.3", "webpack": "5.90.3",
@ -104,7 +104,7 @@
"@axe-core/playwright": "4.8.5", "@axe-core/playwright": "4.8.5",
"@percy/cli": "1.27.4", "@percy/cli": "1.27.4",
"@percy/playwright": "1.0.4", "@percy/playwright": "1.0.4",
"@playwright/test": "1.42.1", "@playwright/test": "1.44.0",
"@types/sinonjs__fake-timers": "8.1.5", "@types/sinonjs__fake-timers": "8.1.5",
"sinon": "17.0.0" "sinon": "17.0.0"
} }
@ -398,9 +398,9 @@
} }
}, },
"node_modules/@babel/parser": { "node_modules/@babel/parser": {
"version": "7.24.0", "version": "7.24.4",
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.24.0.tgz", "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.24.4.tgz",
"integrity": "sha512-QuP/FxEAzMSjXygs8v4N9dvdXzEHN4W1oF3PxuWAtPo08UdM17u89RDMgjLn/mlc56iM0HlLmVkO/wgR+rDgHg==", "integrity": "sha512-zTvEBcghmeBma9QIGunWevvBAp4/Qu9Bdq+2k0Ot4fVMD6v3dsC9WOcRSKk7tRRyBM/53yKMJko9xOatGQAwSg==",
"dev": true, "dev": true,
"bin": { "bin": {
"parser": "bin/babel-parser.js" "parser": "bin/babel-parser.js"
@ -1559,12 +1559,12 @@
} }
}, },
"node_modules/@playwright/test": { "node_modules/@playwright/test": {
"version": "1.42.1", "version": "1.44.0",
"resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.42.1.tgz", "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.44.0.tgz",
"integrity": "sha512-Gq9rmS54mjBL/7/MvBaNOBwbfnh7beHvS6oS4srqXFcQHpQCV1+c8JXWE8VLPyRDhgS3H8x8A7hztqI9VnwrAQ==", "integrity": "sha512-rNX5lbNidamSUorBhB4XZ9SQTjAqfe5M+p37Z8ic0jPFBMo5iCtQz1kRWkEMg+rYOKSlVycpQmpqjSFq7LXOfg==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"playwright": "1.42.1" "playwright": "1.44.0"
}, },
"bin": { "bin": {
"playwright": "cli.js" "playwright": "cli.js"
@ -1996,103 +1996,103 @@
} }
}, },
"node_modules/@vue/reactivity": { "node_modules/@vue/reactivity": {
"version": "3.4.19", "version": "3.4.24",
"resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.4.19.tgz", "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.4.24.tgz",
"integrity": "sha512-+VcwrQvLZgEclGZRHx4O2XhyEEcKaBi50WbxdVItEezUf4fqRh838Ix6amWTdX0CNb/b6t3Gkz3eOebfcSt+UA==", "integrity": "sha512-nup3fSYg4i4LtNvu9slF/HF/0dkMQYfepUdORBcMSsankzRPzE7ypAFurpwyRBfU1i7Dn1kcwpYsE1wETSh91g==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"@vue/shared": "3.4.19" "@vue/shared": "3.4.24"
} }
}, },
"node_modules/@vue/reactivity/node_modules/@vue/shared": { "node_modules/@vue/reactivity/node_modules/@vue/shared": {
"version": "3.4.19", "version": "3.4.24",
"resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.4.19.tgz", "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.4.24.tgz",
"integrity": "sha512-/KliRRHMF6LoiThEy+4c1Z4KB/gbPrGjWwJR+crg2otgrf/egKzRaCPvJ51S5oetgsgXLfc4Rm5ZgrKHZrtMSw==", "integrity": "sha512-BW4tajrJBM9AGAknnyEw5tO2xTmnqgup0VTnDAMcxYmqOX0RG0b9aSUGAbEKolD91tdwpA6oCwbltoJoNzpItw==",
"dev": true "dev": true
}, },
"node_modules/@vue/runtime-core": { "node_modules/@vue/runtime-core": {
"version": "3.4.19", "version": "3.4.24",
"resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.4.19.tgz", "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.4.24.tgz",
"integrity": "sha512-/Z3tFwOrerJB/oyutmJGoYbuoadphDcJAd5jOuJE86THNZji9pYjZroQ2NFsZkTxOq0GJbb+s2kxTYToDiyZzw==", "integrity": "sha512-c7iMfj6cJMeAG3s5yOn9Rc5D9e2/wIuaozmGf/ICGCY3KV5H7mbTVdvEkd4ZshTq7RUZqj2k7LMJWVx+EBiY1g==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"@vue/reactivity": "3.4.19", "@vue/reactivity": "3.4.24",
"@vue/shared": "3.4.19" "@vue/shared": "3.4.24"
} }
}, },
"node_modules/@vue/runtime-core/node_modules/@vue/shared": { "node_modules/@vue/runtime-core/node_modules/@vue/shared": {
"version": "3.4.19", "version": "3.4.24",
"resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.4.19.tgz", "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.4.24.tgz",
"integrity": "sha512-/KliRRHMF6LoiThEy+4c1Z4KB/gbPrGjWwJR+crg2otgrf/egKzRaCPvJ51S5oetgsgXLfc4Rm5ZgrKHZrtMSw==", "integrity": "sha512-BW4tajrJBM9AGAknnyEw5tO2xTmnqgup0VTnDAMcxYmqOX0RG0b9aSUGAbEKolD91tdwpA6oCwbltoJoNzpItw==",
"dev": true "dev": true
}, },
"node_modules/@vue/runtime-dom": { "node_modules/@vue/runtime-dom": {
"version": "3.4.19", "version": "3.4.24",
"resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.4.19.tgz", "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.4.24.tgz",
"integrity": "sha512-IyZzIDqfNCF0OyZOauL+F4yzjMPN2rPd8nhqPP2N1lBn3kYqJpPHHru+83Rkvo2lHz5mW+rEeIMEF9qY3PB94g==", "integrity": "sha512-uXKzuh/Emfad2Y7Qm0ABsLZZV6H3mAJ5ZVqmAOlrNQRf+T5mxpPGZBfec1hkP41t6h6FwF6RSGCs/gd8WbuySQ==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"@vue/runtime-core": "3.4.19", "@vue/runtime-core": "3.4.24",
"@vue/shared": "3.4.19", "@vue/shared": "3.4.24",
"csstype": "^3.1.3" "csstype": "^3.1.3"
} }
}, },
"node_modules/@vue/runtime-dom/node_modules/@vue/shared": { "node_modules/@vue/runtime-dom/node_modules/@vue/shared": {
"version": "3.4.19", "version": "3.4.24",
"resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.4.19.tgz", "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.4.24.tgz",
"integrity": "sha512-/KliRRHMF6LoiThEy+4c1Z4KB/gbPrGjWwJR+crg2otgrf/egKzRaCPvJ51S5oetgsgXLfc4Rm5ZgrKHZrtMSw==", "integrity": "sha512-BW4tajrJBM9AGAknnyEw5tO2xTmnqgup0VTnDAMcxYmqOX0RG0b9aSUGAbEKolD91tdwpA6oCwbltoJoNzpItw==",
"dev": true "dev": true
}, },
"node_modules/@vue/server-renderer": { "node_modules/@vue/server-renderer": {
"version": "3.4.19", "version": "3.4.24",
"resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.4.19.tgz", "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.4.24.tgz",
"integrity": "sha512-eAj2p0c429RZyyhtMRnttjcSToch+kTWxFPHlzGMkR28ZbF1PDlTcmGmlDxccBuqNd9iOQ7xPRPAGgPVj+YpQw==", "integrity": "sha512-H+DLK4sQF6sRgzKyofmlEVBIV/9KrQU6HIV7nt6yIwSGGKvSwlV8pqJlebUKLpbXaNHugdSfAbP6YmXF69lxow==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"@vue/compiler-ssr": "3.4.19", "@vue/compiler-ssr": "3.4.24",
"@vue/shared": "3.4.19" "@vue/shared": "3.4.24"
}, },
"peerDependencies": { "peerDependencies": {
"vue": "3.4.19" "vue": "3.4.24"
} }
}, },
"node_modules/@vue/server-renderer/node_modules/@vue/compiler-core": { "node_modules/@vue/server-renderer/node_modules/@vue/compiler-core": {
"version": "3.4.19", "version": "3.4.24",
"resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.4.19.tgz", "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.4.24.tgz",
"integrity": "sha512-gj81785z0JNzRcU0Mq98E56e4ltO1yf8k5PQ+tV/7YHnbZkrM0fyFyuttnN8ngJZjbpofWE/m4qjKBiLl8Ju4w==", "integrity": "sha512-vbW/tgbwJYj62N/Ww99x0zhFTkZDTcGh3uwJEuadZ/nF9/xuFMC4693P9r+3sxGXISABpDKvffY5ApH9pmdd1A==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"@babel/parser": "^7.23.9", "@babel/parser": "^7.24.4",
"@vue/shared": "3.4.19", "@vue/shared": "3.4.24",
"entities": "^4.5.0", "entities": "^4.5.0",
"estree-walker": "^2.0.2", "estree-walker": "^2.0.2",
"source-map-js": "^1.0.2" "source-map-js": "^1.2.0"
} }
}, },
"node_modules/@vue/server-renderer/node_modules/@vue/compiler-dom": { "node_modules/@vue/server-renderer/node_modules/@vue/compiler-dom": {
"version": "3.4.19", "version": "3.4.24",
"resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.4.19.tgz", "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.4.24.tgz",
"integrity": "sha512-vm6+cogWrshjqEHTzIDCp72DKtea8Ry/QVpQRYoyTIg9k7QZDX6D8+HGURjtmatfgM8xgCFtJJaOlCaRYRK3QA==", "integrity": "sha512-4XgABML/4cNndVsQndG6BbGN7+EoisDwi3oXNovqL/4jdNhwvP8/rfRMTb6FxkxIxUUtg6AI1/qZvwfSjxJiWA==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"@vue/compiler-core": "3.4.19", "@vue/compiler-core": "3.4.24",
"@vue/shared": "3.4.19" "@vue/shared": "3.4.24"
} }
}, },
"node_modules/@vue/server-renderer/node_modules/@vue/compiler-ssr": { "node_modules/@vue/server-renderer/node_modules/@vue/compiler-ssr": {
"version": "3.4.19", "version": "3.4.24",
"resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.4.19.tgz", "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.4.24.tgz",
"integrity": "sha512-P0PLKC4+u4OMJ8sinba/5Z/iDT84uMRRlrWzadgLA69opCpI1gG4N55qDSC+dedwq2fJtzmGald05LWR5TFfLw==", "integrity": "sha512-ZsAtr4fhaUFnVcDqwW3bYCSDwq+9Gk69q2r/7dAHDrOMw41kylaMgOP4zRnn6GIEJkQznKgrMOGPMFnLB52RbQ==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"@vue/compiler-dom": "3.4.19", "@vue/compiler-dom": "3.4.24",
"@vue/shared": "3.4.19" "@vue/shared": "3.4.24"
} }
}, },
"node_modules/@vue/server-renderer/node_modules/@vue/shared": { "node_modules/@vue/server-renderer/node_modules/@vue/shared": {
"version": "3.4.19", "version": "3.4.24",
"resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.4.19.tgz", "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.4.24.tgz",
"integrity": "sha512-/KliRRHMF6LoiThEy+4c1Z4KB/gbPrGjWwJR+crg2otgrf/egKzRaCPvJ51S5oetgsgXLfc4Rm5ZgrKHZrtMSw==", "integrity": "sha512-BW4tajrJBM9AGAknnyEw5tO2xTmnqgup0VTnDAMcxYmqOX0RG0b9aSUGAbEKolD91tdwpA6oCwbltoJoNzpItw==",
"dev": true "dev": true
}, },
"node_modules/@vue/shared": { "node_modules/@vue/shared": {
@ -7778,15 +7778,12 @@
} }
}, },
"node_modules/magic-string": { "node_modules/magic-string": {
"version": "0.30.8", "version": "0.30.10",
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.8.tgz", "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.10.tgz",
"integrity": "sha512-ISQTe55T2ao7XtlAStud6qwYPZjE4GK1S/BeVPus4jrq6JuOnQ00YKQC581RWhR122W7msZV263KzVeLoqidyQ==", "integrity": "sha512-iIRwTIf0QKV3UAnYK4PU8uiEc4SRh5jX0mwpIwETPpHdhVM4f53RSwS/vXvN1JhGX+Cs7B8qIq3d6AH49O5fAQ==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"@jridgewell/sourcemap-codec": "^1.4.15" "@jridgewell/sourcemap-codec": "^1.4.15"
},
"engines": {
"node": ">=12"
} }
}, },
"node_modules/make-dir": { "node_modules/make-dir": {
@ -8836,12 +8833,12 @@
} }
}, },
"node_modules/playwright": { "node_modules/playwright": {
"version": "1.42.1", "version": "1.44.0",
"resolved": "https://registry.npmjs.org/playwright/-/playwright-1.42.1.tgz", "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.44.0.tgz",
"integrity": "sha512-PgwB03s2DZBcNRoW+1w9E+VkLBxweib6KTXM0M3tkiT4jVxKSi6PmVJ591J+0u10LUrgxB7dLRbiJqO5s2QPMg==", "integrity": "sha512-F9b3GUCLQ3Nffrfb6dunPOkE5Mh68tR7zN32L4jCk4FjQamgesGay7/dAAe1WaMEGV04DkdJfcJzjoCKygUaRQ==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"playwright-core": "1.42.1" "playwright-core": "1.44.0"
}, },
"bin": { "bin": {
"playwright": "cli.js" "playwright": "cli.js"
@ -8854,9 +8851,9 @@
} }
}, },
"node_modules/playwright-core": { "node_modules/playwright-core": {
"version": "1.42.1", "version": "1.44.0",
"resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.42.1.tgz", "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.44.0.tgz",
"integrity": "sha512-mxz6zclokgrke9p1vtdy/COWBH+eOZgYUVVU34C73M+4j4HLlQJHtfcqiqqxpP0o8HhMkflvfbquLX5dg6wlfA==", "integrity": "sha512-ZTbkNpFfYcGWohvTTl+xewITm7EOuqIqex0c7dNZ+aXsbrLj0qI8XlGKfPpipjm0Wny/4Lt4CJsWJk1stVS5qQ==",
"dev": true, "dev": true,
"bin": { "bin": {
"playwright-core": "cli.js" "playwright-core": "cli.js"
@ -8901,9 +8898,9 @@
} }
}, },
"node_modules/postcss": { "node_modules/postcss": {
"version": "8.4.35", "version": "8.4.38",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.35.tgz", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.38.tgz",
"integrity": "sha512-u5U8qYpBCpN13BsiEB0CbR1Hhh4Gc0zLFuedrHJKMctHCHAGrMdG0PRM/KErzAL3CU6/eckEtmHNB3x6e3c0vA==", "integrity": "sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A==",
"dev": true, "dev": true,
"funding": [ "funding": [
{ {
@ -8922,7 +8919,7 @@
"dependencies": { "dependencies": {
"nanoid": "^3.3.7", "nanoid": "^3.3.7",
"picocolors": "^1.0.0", "picocolors": "^1.0.0",
"source-map-js": "^1.0.2" "source-map-js": "^1.2.0"
}, },
"engines": { "engines": {
"node": "^10 || ^12 || >=14" "node": "^10 || ^12 || >=14"
@ -10366,9 +10363,9 @@
} }
}, },
"node_modules/source-map-js": { "node_modules/source-map-js": {
"version": "1.0.2", "version": "1.2.0",
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz",
"integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", "integrity": "sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==",
"dev": true, "dev": true,
"engines": { "engines": {
"node": ">=0.10.0" "node": ">=0.10.0"
@ -11241,16 +11238,16 @@
"dev": true "dev": true
}, },
"node_modules/vue": { "node_modules/vue": {
"version": "3.4.19", "version": "3.4.24",
"resolved": "https://registry.npmjs.org/vue/-/vue-3.4.19.tgz", "resolved": "https://registry.npmjs.org/vue/-/vue-3.4.24.tgz",
"integrity": "sha512-W/7Fc9KUkajFU8dBeDluM4sRGc/aa4YJnOYck8dkjgZoXtVsn3OeTGni66FV1l3+nvPA7VBFYtPioaGKUmEADw==", "integrity": "sha512-NPdx7dLGyHmKHGRRU5bMRYVE+rechR+KDU5R2tSTNG36PuMwbfAJ+amEvOAw7BPfZp5sQulNELSLm5YUkau+Sg==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"@vue/compiler-dom": "3.4.19", "@vue/compiler-dom": "3.4.24",
"@vue/compiler-sfc": "3.4.19", "@vue/compiler-sfc": "3.4.24",
"@vue/runtime-dom": "3.4.19", "@vue/runtime-dom": "3.4.24",
"@vue/server-renderer": "3.4.19", "@vue/server-renderer": "3.4.24",
"@vue/shared": "3.4.19" "@vue/shared": "3.4.24"
}, },
"peerDependencies": { "peerDependencies": {
"typescript": "*" "typescript": "*"
@ -11440,59 +11437,59 @@
} }
}, },
"node_modules/vue/node_modules/@vue/compiler-core": { "node_modules/vue/node_modules/@vue/compiler-core": {
"version": "3.4.19", "version": "3.4.24",
"resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.4.19.tgz", "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.4.24.tgz",
"integrity": "sha512-gj81785z0JNzRcU0Mq98E56e4ltO1yf8k5PQ+tV/7YHnbZkrM0fyFyuttnN8ngJZjbpofWE/m4qjKBiLl8Ju4w==", "integrity": "sha512-vbW/tgbwJYj62N/Ww99x0zhFTkZDTcGh3uwJEuadZ/nF9/xuFMC4693P9r+3sxGXISABpDKvffY5ApH9pmdd1A==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"@babel/parser": "^7.23.9", "@babel/parser": "^7.24.4",
"@vue/shared": "3.4.19", "@vue/shared": "3.4.24",
"entities": "^4.5.0", "entities": "^4.5.0",
"estree-walker": "^2.0.2", "estree-walker": "^2.0.2",
"source-map-js": "^1.0.2" "source-map-js": "^1.2.0"
} }
}, },
"node_modules/vue/node_modules/@vue/compiler-dom": { "node_modules/vue/node_modules/@vue/compiler-dom": {
"version": "3.4.19", "version": "3.4.24",
"resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.4.19.tgz", "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.4.24.tgz",
"integrity": "sha512-vm6+cogWrshjqEHTzIDCp72DKtea8Ry/QVpQRYoyTIg9k7QZDX6D8+HGURjtmatfgM8xgCFtJJaOlCaRYRK3QA==", "integrity": "sha512-4XgABML/4cNndVsQndG6BbGN7+EoisDwi3oXNovqL/4jdNhwvP8/rfRMTb6FxkxIxUUtg6AI1/qZvwfSjxJiWA==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"@vue/compiler-core": "3.4.19", "@vue/compiler-core": "3.4.24",
"@vue/shared": "3.4.19" "@vue/shared": "3.4.24"
} }
}, },
"node_modules/vue/node_modules/@vue/compiler-sfc": { "node_modules/vue/node_modules/@vue/compiler-sfc": {
"version": "3.4.19", "version": "3.4.24",
"resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.4.19.tgz", "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.4.24.tgz",
"integrity": "sha512-LQ3U4SN0DlvV0xhr1lUsgLCYlwQfUfetyPxkKYu7dkfvx7g3ojrGAkw0AERLOKYXuAGnqFsEuytkdcComei3Yg==", "integrity": "sha512-nRAlJUK02FTWfA2nuvNBAqsDZuERGFgxZ8sGH62XgFSvMxO2URblzulExsmj4gFZ8e+VAyDooU9oAoXfEDNxTA==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"@babel/parser": "^7.23.9", "@babel/parser": "^7.24.4",
"@vue/compiler-core": "3.4.19", "@vue/compiler-core": "3.4.24",
"@vue/compiler-dom": "3.4.19", "@vue/compiler-dom": "3.4.24",
"@vue/compiler-ssr": "3.4.19", "@vue/compiler-ssr": "3.4.24",
"@vue/shared": "3.4.19", "@vue/shared": "3.4.24",
"estree-walker": "^2.0.2", "estree-walker": "^2.0.2",
"magic-string": "^0.30.6", "magic-string": "^0.30.10",
"postcss": "^8.4.33", "postcss": "^8.4.38",
"source-map-js": "^1.0.2" "source-map-js": "^1.2.0"
} }
}, },
"node_modules/vue/node_modules/@vue/compiler-ssr": { "node_modules/vue/node_modules/@vue/compiler-ssr": {
"version": "3.4.19", "version": "3.4.24",
"resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.4.19.tgz", "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.4.24.tgz",
"integrity": "sha512-P0PLKC4+u4OMJ8sinba/5Z/iDT84uMRRlrWzadgLA69opCpI1gG4N55qDSC+dedwq2fJtzmGald05LWR5TFfLw==", "integrity": "sha512-ZsAtr4fhaUFnVcDqwW3bYCSDwq+9Gk69q2r/7dAHDrOMw41kylaMgOP4zRnn6GIEJkQznKgrMOGPMFnLB52RbQ==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"@vue/compiler-dom": "3.4.19", "@vue/compiler-dom": "3.4.24",
"@vue/shared": "3.4.19" "@vue/shared": "3.4.24"
} }
}, },
"node_modules/vue/node_modules/@vue/shared": { "node_modules/vue/node_modules/@vue/shared": {
"version": "3.4.19", "version": "3.4.24",
"resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.4.19.tgz", "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.4.24.tgz",
"integrity": "sha512-/KliRRHMF6LoiThEy+4c1Z4KB/gbPrGjWwJR+crg2otgrf/egKzRaCPvJ51S5oetgsgXLfc4Rm5ZgrKHZrtMSw==", "integrity": "sha512-BW4tajrJBM9AGAknnyEw5tO2xTmnqgup0VTnDAMcxYmqOX0RG0b9aSUGAbEKolD91tdwpA6oCwbltoJoNzpItw==",
"dev": true "dev": true
}, },
"node_modules/watchpack": { "node_modules/watchpack": {

View File

@ -87,7 +87,7 @@
"tiny-emitter": "2.1.0", "tiny-emitter": "2.1.0",
"typescript": "5.3.3", "typescript": "5.3.3",
"uuid": "9.0.1", "uuid": "9.0.1",
"vue": "3.4.19", "vue": "3.4.24",
"vue-eslint-parser": "9.4.2", "vue-eslint-parser": "9.4.2",
"vue-loader": "16.8.3", "vue-loader": "16.8.3",
"webpack": "5.90.3", "webpack": "5.90.3",

View File

@ -430,7 +430,7 @@ class IndependentTimeContext extends TimeContext {
this.followTimeContext(); this.followTimeContext();
// Emit bounds so that views that are changing context get the upstream bounds // Emit bounds so that views that are changing context get the upstream bounds
this.emit('bounds', this.bounds()); this.emit('bounds', this.getBounds());
this.emit(TIME_CONTEXT_EVENTS.boundsChanged, this.getBounds()); this.emit(TIME_CONTEXT_EVENTS.boundsChanged, this.getBounds());
} }

View File

@ -57,7 +57,7 @@ describe('The Time API', function () {
expect(api.timeOfInterest()).toBe(toi); expect(api.timeOfInterest()).toBe(toi);
}); });
it('Allows setting of valid bounds', function () { it('[Legacy TimeAPI]: Allows setting of valid bounds', function () {
bounds = { bounds = {
start: 0, start: 0,
end: 1 end: 1
@ -67,7 +67,17 @@ describe('The Time API', function () {
expect(api.bounds()).toEqual(bounds); expect(api.bounds()).toEqual(bounds);
}); });
it('Disallows setting of invalid bounds', function () { it('Allows setting of valid bounds', function () {
bounds = {
start: 0,
end: 1
};
expect(api.getBounds()).not.toBe(bounds);
expect(api.setBounds.bind(api, bounds)).not.toThrow();
expect(api.getBounds()).toEqual(bounds);
});
it('[Legacy TimeAPI]: Disallows setting of invalid bounds', function () {
bounds = { bounds = {
start: 1, start: 1,
end: 0 end: 0
@ -82,7 +92,22 @@ describe('The Time API', function () {
expect(api.bounds()).not.toEqual(bounds); expect(api.bounds()).not.toEqual(bounds);
}); });
it('Allows setting of previously registered time system with bounds', function () { it('Disallows setting of invalid bounds', function () {
bounds = {
start: 1,
end: 0
};
expect(api.getBounds()).not.toEqual(bounds);
expect(api.setBounds.bind(api, bounds)).toThrow();
expect(api.getBounds()).not.toEqual(bounds);
bounds = { start: 1 };
expect(api.getBounds()).not.toEqual(bounds);
expect(api.setBounds.bind(api, bounds)).toThrow();
expect(api.getBounds()).not.toEqual(bounds);
});
it('[Legacy TimeAPI]: Allows setting of previously registered time system with bounds', function () {
api.addTimeSystem(timeSystem); api.addTimeSystem(timeSystem);
expect(api.timeSystem()).not.toBe(timeSystem); expect(api.timeSystem()).not.toBe(timeSystem);
expect(function () { expect(function () {
@ -91,7 +116,16 @@ describe('The Time API', function () {
expect(api.timeSystem()).toEqual(timeSystem); expect(api.timeSystem()).toEqual(timeSystem);
}); });
it('Disallows setting of time system without bounds', function () { it('Allows setting of previously registered time system with bounds', function () {
api.addTimeSystem(timeSystem);
expect(api.getTimeSystem()).not.toBe(timeSystem);
expect(function () {
api.setTimeSystem(timeSystem, bounds);
}).not.toThrow();
expect(api.getTimeSystem()).toEqual(timeSystem);
});
it('[Legacy TimeAPI]: Disallows setting of time system without bounds', function () {
api.addTimeSystem(timeSystem); api.addTimeSystem(timeSystem);
expect(api.timeSystem()).not.toBe(timeSystem); expect(api.timeSystem()).not.toBe(timeSystem);
expect(function () { expect(function () {
@ -100,6 +134,32 @@ describe('The Time API', function () {
expect(api.timeSystem()).not.toBe(timeSystem); expect(api.timeSystem()).not.toBe(timeSystem);
}); });
it('Allows setting of time system without bounds', function () {
api.addTimeSystem(timeSystem);
expect(api.getTimeSystem()).not.toBe(timeSystem);
expect(function () {
api.setTimeSystem(timeSystemKey);
}).not.toThrow();
expect(api.getTimeSystem()).not.toBe(timeSystem);
});
it('Disallows setting of invalid time system', function () {
expect(function () {
api.setTimeSystem();
}).toThrow();
expect(function () {
api.setTimeSystem('invalidTimeSystemKey');
}).toThrow();
expect(function () {
api.setTimeSystem({
key: 'invalidTimeSystemKey'
});
}).toThrow();
expect(function () {
api.setTimeSystem(42);
}).toThrow();
});
it('allows setting of timesystem without bounds with clock', function () { it('allows setting of timesystem without bounds with clock', function () {
api.addTimeSystem(timeSystem); api.addTimeSystem(timeSystem);
api.addClock(clock); api.addClock(clock);
@ -114,7 +174,7 @@ describe('The Time API', function () {
expect(api.timeSystem()).toEqual(timeSystem); expect(api.timeSystem()).toEqual(timeSystem);
}); });
it('Emits an event when time system changes', function () { it('Emits a legacy event when time system changes', function () {
api.addTimeSystem(timeSystem); api.addTimeSystem(timeSystem);
expect(eventListener).not.toHaveBeenCalled(); expect(eventListener).not.toHaveBeenCalled();
api.on('timeSystem', eventListener); api.on('timeSystem', eventListener);
@ -122,6 +182,14 @@ describe('The Time API', function () {
expect(eventListener).toHaveBeenCalledWith(timeSystem); expect(eventListener).toHaveBeenCalledWith(timeSystem);
}); });
it('Emits an event when time system changes', function () {
api.addTimeSystem(timeSystem);
expect(eventListener).not.toHaveBeenCalled();
api.on('timeSystemChanged', eventListener);
api.timeSystem(timeSystemKey, bounds);
expect(eventListener).toHaveBeenCalledWith(timeSystem);
});
it('Emits an event when time of interest changes', function () { it('Emits an event when time of interest changes', function () {
expect(eventListener).not.toHaveBeenCalled(); expect(eventListener).not.toHaveBeenCalled();
api.on('timeOfInterest', eventListener); api.on('timeOfInterest', eventListener);
@ -129,13 +197,20 @@ describe('The Time API', function () {
expect(eventListener).toHaveBeenCalledWith(toi); expect(eventListener).toHaveBeenCalledWith(toi);
}); });
it('Emits an event when bounds change', function () { it('Emits a legacy event when bounds change', function () {
expect(eventListener).not.toHaveBeenCalled(); expect(eventListener).not.toHaveBeenCalled();
api.on('bounds', eventListener); api.on('bounds', eventListener);
api.bounds(bounds); api.bounds(bounds);
expect(eventListener).toHaveBeenCalledWith(bounds, false); expect(eventListener).toHaveBeenCalledWith(bounds, false);
}); });
it('Emits an event when bounds change', function () {
expect(eventListener).not.toHaveBeenCalled();
api.on('boundsChanged', eventListener);
api.bounds(bounds);
expect(eventListener).toHaveBeenCalledWith(bounds, false);
});
it('If bounds are set and TOI lies inside them, do not change TOI', function () { it('If bounds are set and TOI lies inside them, do not change TOI', function () {
api.timeOfInterest(6); api.timeOfInterest(6);
api.bounds({ api.bounds({
@ -154,13 +229,39 @@ describe('The Time API', function () {
expect(api.timeOfInterest()).toBeUndefined(); expect(api.timeOfInterest()).toBeUndefined();
}); });
it('Maintains delta during tick', function () {}); it('Maintains delta during tick', function () {
const initialBounds = { start: 100, end: 200 };
api.bounds(initialBounds);
const mockTickSource = jasmine.createSpyObj('mockTickSource', ['on', 'off', 'currentValue']);
mockTickSource.key = 'mct';
mockTickSource.currentValue.and.returnValue(150);
api.addClock(mockTickSource);
api.clock('mct', { start: 0, end: 100 });
it('Allows registered time system to be activated', function () {}); // Simulate a tick event
const tickCallback = mockTickSource.on.calls.mostRecent().args[1];
tickCallback(150);
const newBounds = api.bounds();
expect(newBounds.end - newBounds.start).toEqual(initialBounds.end - initialBounds.start);
});
it('Allows registered time system to be activated', function () {
api.addClock(clock);
api.clock(clockKey, { start: 0, end: 100 });
api.addTimeSystem(timeSystem);
api.timeSystem(timeSystemKey);
expect(api.timeSystem().key).toEqual(timeSystemKey);
});
it('Allows a registered tick source to be activated', function () { it('Allows a registered tick source to be activated', function () {
const mockTickSource = jasmine.createSpyObj('mockTickSource', ['on', 'off', 'currentValue']); const mockTickSource = jasmine.createSpyObj('mockTickSource', ['on', 'off', 'currentValue']);
mockTickSource.key = 'mockTickSource'; mockTickSource.key = 'mockTickSource';
mockTickSource.currentValue.and.returnValue(50);
api.addClock(mockTickSource);
api.clock(mockTickSource.key, { start: 0, end: 100 });
expect(mockTickSource.on).toHaveBeenCalledWith('tick', jasmine.any(Function));
}); });
describe(' when enabling a tick source', function () { describe(' when enabling a tick source', function () {
@ -184,7 +285,7 @@ describe('The Time API', function () {
api.addClock(anotherMockTickSource); api.addClock(anotherMockTickSource);
}); });
it('sets bounds based on current value', function () { it('[Legacy TimeAPI]: sets bounds based on current value', function () {
api.clock('mts', mockOffsets); api.clock('mts', mockOffsets);
expect(api.bounds()).toEqual({ expect(api.bounds()).toEqual({
start: 10, start: 10,
@ -192,23 +293,46 @@ describe('The Time API', function () {
}); });
}); });
it('a new tick listener is registered', function () { it('does not set bounds based on current value', function () {
api.setClock('mts');
expect(api.getBounds()).toEqual({});
});
it('does not set invalid clock', function () {
expect(function () {
api.setClock();
}).toThrow();
expect(function () {
api.setClock({});
}).toThrow();
expect(function () {
api.setClock('invalidClockKey');
}).toThrow();
});
it('[Legacy TimeAPI]: a new tick listener is registered', function () {
api.clock('mts', mockOffsets); api.clock('mts', mockOffsets);
expect(mockTickSource.on).toHaveBeenCalledWith('tick', jasmine.any(Function)); expect(mockTickSource.on).toHaveBeenCalledWith('tick', jasmine.any(Function));
}); });
it('a new tick listener is registered', function () {
api.setClock('mts', mockOffsets);
expect(mockTickSource.on).toHaveBeenCalledWith('tick', jasmine.any(Function));
});
it('listener of existing tick source is reregistered', function () { it('listener of existing tick source is reregistered', function () {
api.clock('mts', mockOffsets); api.clock('mts', mockOffsets);
api.clock('amts', mockOffsets); api.clock('amts', mockOffsets);
expect(mockTickSource.off).toHaveBeenCalledWith('tick', jasmine.any(Function)); expect(mockTickSource.off).toHaveBeenCalledWith('tick', jasmine.any(Function));
}); });
xit('Allows the active clock to be set and unset', function () { it('[Legacy TimeAPI]: Allows the active clock to be set and unset', function () {
expect(api.clock()).toBeUndefined(); expect(api.clock()).toBeUndefined();
api.clock('mts', mockOffsets); api.clock('mts', mockOffsets);
expect(api.clock()).toBeDefined(); expect(api.clock()).toBeDefined();
// api.stopClock(); // Unset the clock
// expect(api.clock()).toBeUndefined(); api.stopClock();
expect(api.clock()).toBeUndefined();
}); });
it('Provides a default time context', () => { it('Provides a default time context', () => {

View File

@ -20,7 +20,7 @@
* at runtime from the About dialog for additional information. * at runtime from the About dialog for additional information.
*****************************************************************************/ *****************************************************************************/
import EventEmitter from 'EventEmitter'; import EventEmitter from 'eventemitter3';
import { FIXED_MODE_KEY, MODES, REALTIME_MODE_KEY, TIME_CONTEXT_EVENTS } from './constants.js'; import { FIXED_MODE_KEY, MODES, REALTIME_MODE_KEY, TIME_CONTEXT_EVENTS } from './constants.js';
@ -34,7 +34,7 @@ import { FIXED_MODE_KEY, MODES, REALTIME_MODE_KEY, TIME_CONTEXT_EVENTS } from '.
/** /**
* @typedef {Object} TimeConductorBounds * @typedef {Object} TimeConductorBounds
* @property {number } start The start time displayed by the time conductor * @property {number} start The start time displayed by the time conductor
* in ms since epoch. Epoch determined by currently active time system * in ms since epoch. Epoch determined by currently active time system
* @property {number} end The end time displayed by the time conductor in ms * @property {number} end The end time displayed by the time conductor in ms
* since epoch. * since epoch.
@ -426,8 +426,8 @@ class TimeContext extends EventEmitter {
/** /**
* Set the time system of the TimeAPI. * Set the time system of the TimeAPI.
* Emits a "timeSystem" event with the new time system. * Emits a "timeSystem" event with the new time system.
* @param {TimeSystem | string} timeSystemOrKey * @param {TimeSystem | string} timeSystemOrKey The time system to set, or its key
* @param {TimeConductorBounds} bounds * @param {TimeConductorBounds} [bounds] Optional bounds to set
*/ */
setTimeSystem(timeSystemOrKey, bounds) { setTimeSystem(timeSystemOrKey, bounds) {
if (timeSystemOrKey === undefined) { if (timeSystemOrKey === undefined) {

View File

@ -61,7 +61,7 @@ export default class URLTimeSettingsSynchronizer {
TIME_EVENTS.forEach((event) => { TIME_EVENTS.forEach((event) => {
this.openmct.time.on(event, this.setUrlFromTimeApi); this.openmct.time.on(event, this.setUrlFromTimeApi);
}); });
this.openmct.time.on('bounds', this.updateBounds); this.openmct.time.on('boundsChanged', this.updateBounds);
} }
destroy() { destroy() {
@ -73,7 +73,7 @@ export default class URLTimeSettingsSynchronizer {
TIME_EVENTS.forEach((event) => { TIME_EVENTS.forEach((event) => {
this.openmct.time.off(event, this.setUrlFromTimeApi); this.openmct.time.off(event, this.setUrlFromTimeApi);
}); });
this.openmct.time.off('bounds', this.updateBounds); this.openmct.time.off('boundsChanged', this.updateBounds);
} }
updateTimeSettings() { updateTimeSettings() {

View File

@ -115,11 +115,11 @@ export default {
this.followTimeContext(); this.followTimeContext();
}, },
followTimeContext() { followTimeContext() {
this.timeContext.on('bounds', this.refreshData); this.timeContext.on('boundsChanged', this.refreshData);
}, },
stopFollowingTimeContext() { stopFollowingTimeContext() {
if (this.timeContext) { if (this.timeContext) {
this.timeContext.off('bounds', this.refreshData); this.timeContext.off('boundsChanged', this.refreshData);
} }
}, },
addToComposition(telemetryObject) { addToComposition(telemetryObject) {

View File

@ -105,11 +105,11 @@ export default {
this.followTimeContext(); this.followTimeContext();
}, },
followTimeContext() { followTimeContext() {
this.timeContext.on('bounds', this.reloadTelemetryOnBoundsChange); this.timeContext.on('boundsChanged', this.reloadTelemetryOnBoundsChange);
}, },
stopFollowingTimeContext() { stopFollowingTimeContext() {
if (this.timeContext) { if (this.timeContext) {
this.timeContext.off('bounds', this.reloadTelemetryOnBoundsChange); this.timeContext.off('boundsChanged', this.reloadTelemetryOnBoundsChange);
} }
}, },
addToComposition(telemetryObject) { addToComposition(telemetryObject) {

View File

@ -46,14 +46,6 @@ export default class ConditionManager extends EventEmitter {
applied: false applied: false
}; };
this.initialize(); this.initialize();
this.stopObservingForChanges = this.openmct.objects.observe(
this.conditionSetDomainObject,
'*',
(newDomainObject) => {
this.conditionSetDomainObject = newDomainObject;
}
);
} }
async requestLatestValue(endpoint) { async requestLatestValue(endpoint) {
@ -518,10 +510,6 @@ export default class ConditionManager extends EventEmitter {
Object.values(this.subscriptions).forEach((unsubscribe) => unsubscribe()); Object.values(this.subscriptions).forEach((unsubscribe) => unsubscribe());
delete this.subscriptions; delete this.subscriptions;
if (this.stopObservingForChanges) {
this.stopObservingForChanges();
}
this.conditions.forEach((condition) => { this.conditions.forEach((condition) => {
condition.destroy(); condition.destroy();
}); });

View File

@ -41,7 +41,7 @@ export default class StyleRuleManager extends EventEmitter {
}); });
this.initialize(styleConfigurationWithNoSelection); this.initialize(styleConfigurationWithNoSelection);
if (styleConfiguration.conditionSetIdentifier) { if (styleConfiguration.conditionSetIdentifier) {
this.openmct.time.on('bounds', this.refreshData); this.openmct.time.on('boundsChanged', this.refreshData);
this.subscribeToConditionSet(); this.subscribeToConditionSet();
} else { } else {
this.applyStaticStyle(); this.applyStaticStyle();
@ -216,7 +216,7 @@ export default class StyleRuleManager extends EventEmitter {
} }
if (!skipEventListeners) { if (!skipEventListeners) {
this.openmct.time.off('bounds', this.refreshData); this.openmct.time.off('boundsChanged', this.refreshData);
this.openmct.editor.off('isEditing', this.toggleSubscription); this.openmct.editor.off('isEditing', this.toggleSubscription);
} }

View File

@ -21,7 +21,11 @@
--> -->
<template> <template>
<section id="conditionCollection" :class="{ 'is-expanded': expanded }"> <section
id="conditionCollection"
:class="{ 'is-expanded': expanded }"
aria-label="Condition Set Condition Collection"
>
<div class="c-cs__header c-section__header"> <div class="c-cs__header c-section__header">
<span <span
class="c-disclosure-triangle c-tree__item__view-control is-enabled" class="c-disclosure-triangle c-tree__item__view-control is-enabled"

View File

@ -24,6 +24,7 @@
<div <div
class="c-condition-h" class="c-condition-h"
:class="{ 'is-drag-target': draggingOver }" :class="{ 'is-drag-target': draggingOver }"
aria-label="Condition Set Condition"
@dragover.prevent @dragover.prevent
@drop.prevent="dropCondition($event, conditionIndex)" @drop.prevent="dropCondition($event, conditionIndex)"
@dragenter="dragEnter($event, conditionIndex)" @dragenter="dragEnter($event, conditionIndex)"
@ -83,6 +84,7 @@
<input <input
v-model="condition.configuration.name" v-model="condition.configuration.name"
class="t-condition-input__name" class="t-condition-input__name"
aria-label="Condition Name Input"
type="text" type="text"
@change="persist" @change="persist"
/> />
@ -91,7 +93,11 @@
<span class="c-cdef__label">Output</span> <span class="c-cdef__label">Output</span>
<span class="c-cdef__controls"> <span class="c-cdef__controls">
<span class="c-cdef__control"> <span class="c-cdef__control">
<select v-model="selectedOutputSelection" @change="setOutputValue"> <select
v-model="selectedOutputSelection"
aria-label="Condition Output Type"
@change="setOutputValue"
>
<option v-for="option in outputOptions" :key="option" :value="option"> <option v-for="option in outputOptions" :key="option" :value="option">
{{ initCap(option) }} {{ initCap(option) }}
</option> </option>
@ -101,6 +107,7 @@
<input <input
v-if="selectedOutputSelection === outputOptions[2]" v-if="selectedOutputSelection === outputOptions[2]"
v-model="condition.configuration.output" v-model="condition.configuration.output"
aria-label="Condition Output String"
class="t-condition-name-input" class="t-condition-name-input"
type="text" type="text"
@change="persist" @change="persist"

View File

@ -21,7 +21,7 @@
--> -->
<template> <template>
<div class="c-cs" :class="{ 'is-stale': isStale }"> <div class="c-cs" :class="{ 'is-stale': isStale }" aria-label="Condition Set">
<section class="c-cs__current-output c-section"> <section class="c-cs__current-output c-section">
<div class="c-cs__content c-cs__current-output-value"> <div class="c-cs__content c-cs__current-output-value">
<span class="c-cs__current-output-value__label">Current Output</span> <span class="c-cs__current-output-value__label">Current Output</span>

View File

@ -21,7 +21,12 @@
--> -->
<template> <template>
<section v-show="isEditing" id="test-data" :class="{ 'is-expanded': expanded }"> <section
v-show="isEditing"
id="test-data"
:class="{ 'is-expanded': expanded }"
aria-label="Condition Set Test Data"
>
<div class="c-cs__header c-section__header"> <div class="c-cs__header c-section__header">
<span <span
class="c-disclosure-triangle c-tree__item__view-control is-enabled" class="c-disclosure-triangle c-tree__item__view-control is-enabled"

View File

@ -18,6 +18,7 @@
} }
&__value { &__value {
@include telemetryView();
@include isLimit(); @include isLimit();
} }

View File

@ -45,7 +45,7 @@
@object-drop-to="moveOrCreateNewFrame" @object-drop-to="moveOrCreateNewFrame"
/> />
<div role="row" class="c-fl-container__frames-holder"> <div role="row" class="c-fl-container__frames-holder" :class="flexLayoutCssClass">
<template v-for="(frame, i) in frames" :key="frame.id"> <template v-for="(frame, i) in frames" :key="frame.id">
<frame-component <frame-component
class="c-fl-container__frame" class="c-fl-container__frame"
@ -118,6 +118,9 @@ export default {
}, },
emits: ['new-frame', 'move-frame', 'persist'], emits: ['new-frame', 'move-frame', 'persist'],
computed: { computed: {
flexLayoutCssClass() {
return this.rowsLayout ? '--layout-rows' : '--layout-cols';
},
frames() { frames() {
return this.container.frames; return this.container.frames;
}, },

View File

@ -30,10 +30,8 @@
<div <div
class="c-fl__container-holder u-style-receiver js-style-receiver" class="c-fl__container-holder u-style-receiver js-style-receiver"
:class="{ :class="flexLayoutCssClass"
'c-fl--rows': rowsLayout === true :aria-label="`Flexible Layout ${rowsLayout ? 'Rows' : 'Columns'}`"
}"
:aria-label="`Flexible Layout ${rowsLayout ? 'Row' : 'Column'}`"
> >
<template v-for="(container, index) in containers" :key="`component-${container.id}`"> <template v-for="(container, index) in containers" :key="`component-${container.id}`">
<drop-hint <drop-hint
@ -45,7 +43,6 @@
/> />
<container-component <container-component
class="c-fl__container"
:index="index" :index="index"
:container="container" :container="container"
:rows-layout="rowsLayout" :rows-layout="rowsLayout"
@ -148,15 +145,11 @@ export default {
}; };
}, },
computed: { computed: {
layoutDirectionStr() {
if (this.rowsLayout) {
return 'Rows';
} else {
return 'Columns';
}
},
allContainersAreEmpty() { allContainersAreEmpty() {
return this.containers.every((container) => container.frames.length === 0); return this.containers.every((container) => container.frames.length === 0);
},
flexLayoutCssClass() {
return this.rowsLayout ? 'c-fl--rows' : 'c-fl--cols';
} }
}, },
created() { created() {

View File

@ -7,7 +7,6 @@
$majorOffset: 35%; $majorOffset: 35%;
content: ''; content: '';
display: block; display: block;
position: absolute;
@include grippy($c: $editFrameSelectedMovebarColorFg, $dir: $dir); @include grippy($c: $editFrameSelectedMovebarColorFg, $dir: $dir);
@if $dir == 'x' { @if $dir == 'x' {
top: $minorOffset; top: $minorOffset;
@ -35,18 +34,15 @@
flex: 1 1 100%; // Must be 100% to work flex: 1 1 100%; // Must be 100% to work
overflow: auto; overflow: auto;
// Columns by default // Controls layout of c-fl__container(s)
flex-direction: row; &[class*='--cols'] {
> * + * { flex-direction: row;
margin-left: 1px; column-gap: 1px;
} }
&[class*='--rows'] { &[class*='--rows'] {
flex-direction: column; flex-direction: column;
> * + * { row-gap: 1px;
margin-left: 0;
margin-top: 1px;
}
} }
} }
@ -119,10 +115,18 @@
&__frames-holder { &__frames-holder {
display: flex; display: flex;
flex: 1 1 100%; // Must be 100% to work flex: 1 1 100%; // Must be 100% to work
flex-direction: column; // Default flex-direction: row; // Default
align-content: stretch; align-content: stretch;
align-items: stretch; align-items: stretch;
overflow: hidden; // This sucks, but doing in the short-term overflow: hidden; // This sucks, but doing in the short-term
&.--layout-cols {
flex-direction: column !important;
}
&.--layout-rows {
flex-direction: row !important;
}
} }
.is-editing & { .is-editing & {

View File

@ -61,13 +61,13 @@ function ToolbarProvider(openmct) {
options: [ options: [
{ {
value: true, value: true,
icon: 'icon-columns', icon: 'icon-rows',
title: 'Columns layout' title: 'Switch to rows layout'
}, },
{ {
value: false, value: false,
icon: 'icon-rows', icon: 'icon-columns',
title: 'Rows layout' title: 'Switch to columns layout'
} }
] ]
}; };

View File

@ -164,7 +164,7 @@ describe('Gauge plugin', () => {
}); });
spyOn(openmct.telemetry, 'getLimits').and.returnValue({ limits: () => Promise.resolve() }); spyOn(openmct.telemetry, 'getLimits').and.returnValue({ limits: () => Promise.resolve() });
spyOn(openmct.telemetry, 'request').and.returnValue(Promise.resolve([randomValue])); spyOn(openmct.telemetry, 'request').and.returnValue(Promise.resolve([randomValue]));
spyOn(openmct.time, 'bounds').and.returnValue({ spyOn(openmct.time, 'getBounds').and.returnValue({
start: 1000, start: 1000,
end: 5000 end: 5000
}); });
@ -306,7 +306,7 @@ describe('Gauge plugin', () => {
}); });
spyOn(openmct.telemetry, 'getLimits').and.returnValue({ limits: () => Promise.resolve() }); spyOn(openmct.telemetry, 'getLimits').and.returnValue({ limits: () => Promise.resolve() });
spyOn(openmct.telemetry, 'request').and.returnValue(Promise.resolve([randomValue])); spyOn(openmct.telemetry, 'request').and.returnValue(Promise.resolve([randomValue]));
spyOn(openmct.time, 'bounds').and.returnValue({ spyOn(openmct.time, 'getBounds').and.returnValue({
start: 1000, start: 1000,
end: 5000 end: 5000
}); });
@ -448,7 +448,7 @@ describe('Gauge plugin', () => {
}); });
spyOn(openmct.telemetry, 'getLimits').and.returnValue({ limits: () => Promise.resolve() }); spyOn(openmct.telemetry, 'getLimits').and.returnValue({ limits: () => Promise.resolve() });
spyOn(openmct.telemetry, 'request').and.returnValue(Promise.resolve([randomValue])); spyOn(openmct.telemetry, 'request').and.returnValue(Promise.resolve([randomValue]));
spyOn(openmct.time, 'bounds').and.returnValue({ spyOn(openmct.time, 'getBounds').and.returnValue({
start: 1000, start: 1000,
end: 5000 end: 5000
}); });
@ -763,7 +763,7 @@ describe('Gauge plugin', () => {
}) })
}); });
spyOn(openmct.telemetry, 'request').and.returnValue(Promise.resolve([randomValue])); spyOn(openmct.telemetry, 'request').and.returnValue(Promise.resolve([randomValue]));
spyOn(openmct.time, 'bounds').and.returnValue({ spyOn(openmct.time, 'getBounds').and.returnValue({
start: 1000, start: 1000,
end: 5000 end: 5000
}); });

View File

@ -545,7 +545,7 @@ export default {
this.composition.load(); this.composition.load();
this.openmct.time.on('bounds', this.refreshData); this.openmct.time.on('boundsChanged', this.refreshData);
this.openmct.time.on('timeSystem', this.setTimeSystem); this.openmct.time.on('timeSystem', this.setTimeSystem);
this.setupClockChangedEvent((domainObject) => { this.setupClockChangedEvent((domainObject) => {
@ -561,7 +561,7 @@ export default {
this.unsubscribe(); this.unsubscribe();
} }
this.openmct.time.off('bounds', this.refreshData); this.openmct.time.off('boundsChanged', this.refreshData);
this.openmct.time.off('timeSystem', this.setTimeSystem); this.openmct.time.off('timeSystem', this.setTimeSystem);
}, },
methods: { methods: {

View File

@ -84,11 +84,12 @@ $meterNeedleBorderRadius: 5px;
fill: $colorGaugeValue; fill: $colorGaugeValue;
} }
&__needle-value { &__needle-value {
fill: $colorGaugeValue; fill: $colorGaugeNeedle;
} }
&__current-value-text { &__current-value-text {
fill: $colorGaugeTextValue; fill: $colorGaugeTextValue;
font-family: $heroFont; font-family: $numericFont;
} }
&__units-text, &__units-text,
@ -125,7 +126,8 @@ $meterNeedleBorderRadius: 5px;
// Filled area // Filled area
position: absolute; position: absolute;
background: $colorGaugeValue; background: $colorGaugeValue;
z-index: 1; box-shadow: $gaugeMeterValueShadow 0px 2px 10px 1px;
//z-index: 3;
} }
&__value-needle { &__value-needle {
@ -135,6 +137,7 @@ $meterNeedleBorderRadius: 5px;
content: ''; content: '';
display: block; display: block;
background: $colorGaugeValue; background: $colorGaugeValue;
} }
} }
@ -158,7 +161,7 @@ $meterNeedleBorderRadius: 5px;
&__current-value-text { &__current-value-text {
fill: $colorGaugeTextValue; fill: $colorGaugeTextValue;
font-family: $heroFont; font-family: $numericFont;
} }
.c-gauge__curval { .c-gauge__curval {

View File

@ -109,12 +109,12 @@ export default {
this.stopFollowingTimeContext(); this.stopFollowingTimeContext();
this.timeContext = this.openmct.time.getContextForView(this.objectPath); this.timeContext = this.openmct.time.getContextForView(this.objectPath);
this.timeContext.on('timeSystem', this.setScaleAndPlotImagery); this.timeContext.on('timeSystem', this.setScaleAndPlotImagery);
this.timeContext.on('bounds', this.updateViewBounds); this.timeContext.on('boundsChanged', this.updateViewBounds);
}, },
stopFollowingTimeContext() { stopFollowingTimeContext() {
if (this.timeContext) { if (this.timeContext) {
this.timeContext.off('timeSystem', this.setScaleAndPlotImagery); this.timeContext.off('timeSystem', this.setScaleAndPlotImagery);
this.timeContext.off('bounds', this.updateViewBounds); this.timeContext.off('boundsChanged', this.updateViewBounds);
} }
}, },
expand(imageTimestamp) { expand(imageTimestamp) {

View File

@ -144,24 +144,132 @@ export default class ImportAsJSONAction {
return Array.from(new Set([...objectIdentifiers, ...itemObjectReferences])); return Array.from(new Set([...objectIdentifiers, ...itemObjectReferences]));
} }
/**
* @private
* @param {Object} tree
* @param {string} namespace
* @returns {Object}
*/
_generateNewIdentifiers(tree, newNamespace) {
// For each domain object in the file, generate new ID, replace in tree
Object.keys(tree.openmct).forEach((domainObjectId) => {
const oldId = parseKeyString(domainObjectId);
/**
* Generates a map of old IDs to new IDs for efficient lookup during tree walking.
* This function considers cases where original namespaces are blank and updates those IDs as well.
*
* @param {Object} tree - The object tree containing the old IDs.
* @param {string} newNamespace - The namespace for the new IDs.
* @returns {Object} A map of old IDs to new IDs.
*/
_generateIdMap(tree, newNamespace) {
const idMap = {};
const keys = Object.keys(tree.openmct);
for (const oldIdKey of keys) {
const oldId = parseKeyString(oldIdKey);
const newId = { const newId = {
namespace: newNamespace, namespace: newNamespace,
key: uuid() key: uuid()
}; };
tree = this._rewriteId(oldId, newId, tree); const newIdKeyString = this.openmct.objects.makeKeyString(newId);
}, this);
// Update the map with the old and new ID key strings.
idMap[oldIdKey] = newIdKeyString;
// If the old namespace is blank, also map the non-namespaced ID.
if (!oldId.namespace) {
const nonNamespacedOldIdKey = oldId.key;
idMap[nonNamespacedOldIdKey] = newIdKeyString;
}
}
return idMap;
}
/**
* Walks through the object tree and updates IDs according to the provided ID map.
* @param {Object} obj - The current object being visited in the tree.
* @param {Object} idMap - A map of old IDs to new IDs for rewriting.
* @param {Object} importDialog - Optional progress dialog for import.
* @returns {Promise<Object>} The object with updated IDs.
*/
async _walkAndRewriteIds(obj, idMap, importDialog) {
// How many rewrites to do before yielding to the event loop
const UI_UPDATE_INTERVAL = 300;
// The percentage of the progress dialog to allocate to rewriting IDs
const PERCENT_OF_DIALOG = 80;
if (obj === null || obj === undefined) {
return obj;
}
if (typeof obj === 'string') {
const possibleId = idMap[obj];
if (possibleId) {
return possibleId;
} else {
return obj;
}
}
if (Object.hasOwn(obj, 'key') && Object.hasOwn(obj, 'namespace')) {
const oldId = this.openmct.objects.makeKeyString(obj);
const possibleId = idMap[oldId];
if (possibleId) {
const newIdParts = possibleId.split(':');
if (newIdParts.length >= 2) {
// new ID is namespaced, so update both the namespace and key
obj.namespace = newIdParts[0];
obj.key = newIdParts[1];
} else {
// old ID was not namespaced, so update the key only
obj.namespace = '';
obj.key = newIdParts[0];
}
}
return obj;
}
if (Array.isArray(obj)) {
for (let i = 0; i < obj.length; i++) {
obj[i] = await this._walkAndRewriteIds(obj[i], idMap); // Process each item in the array
}
return obj;
}
if (typeof obj === 'object') {
const newObj = {};
const keys = Object.keys(obj);
let processedCount = 0;
for (const key of keys) {
const value = obj[key];
const possibleId = idMap[key];
const newKey = possibleId || key;
newObj[newKey] = await this._walkAndRewriteIds(value, idMap);
// Optionally update the importDialog here, after each property has been processed
if (importDialog) {
processedCount++;
if (processedCount % UI_UPDATE_INTERVAL === 0) {
// yield to the event loop to allow the UI to update
await new Promise((resolve) => setTimeout(resolve, 0));
const percentPersisted = Math.ceil(PERCENT_OF_DIALOG * (processedCount / keys.length));
const message = `Rewriting ${processedCount} of ${keys.length} imported objects.`;
importDialog.updateProgress(percentPersisted, message);
}
}
}
return newObj;
}
// Return the input as-is for types that are not objects, strings, or arrays
return obj;
}
/**
* @private
* @param {Object} tree
* @returns {Promise<Object>}
*/
async _generateNewIdentifiers(tree, newNamespace, importDialog) {
const idMap = this._generateIdMap(tree, newNamespace);
tree.rootId = idMap[tree.rootId];
tree.openmct = await this._walkAndRewriteIds(tree.openmct, idMap, importDialog);
return tree; return tree;
} }
/** /**
@ -170,9 +278,16 @@ export default class ImportAsJSONAction {
* @param {Object} objTree * @param {Object} objTree
*/ */
async _importObjectTree(domainObject, objTree) { async _importObjectTree(domainObject, objTree) {
// make rewriting objects IDs 80% of the progress bar
const importDialog = this.openmct.overlays.progressDialog({
progressPerc: 0,
message: `Importing ${Object.keys(objTree.openmct).length} objects`,
iconClass: 'info',
title: 'Importing'
});
const objectsToCreate = []; const objectsToCreate = [];
const namespace = domainObject.identifier.namespace; const namespace = domainObject.identifier.namespace;
const tree = this._generateNewIdentifiers(objTree, namespace); const tree = await this._generateNewIdentifiers(objTree, namespace, importDialog);
const rootId = tree.rootId; const rootId = tree.rootId;
const rootObj = tree.openmct[rootId]; const rootObj = tree.openmct[rootId];
@ -182,11 +297,24 @@ export default class ImportAsJSONAction {
this._deepInstantiate(rootObj, tree.openmct, [], objectsToCreate); this._deepInstantiate(rootObj, tree.openmct, [], objectsToCreate);
try { try {
await Promise.all(objectsToCreate.map(this._instantiate, this)); let persistedObjects = 0;
// make saving objects objects 20% of the progress bar
await Promise.all(
objectsToCreate.map(async (objectToCreate) => {
persistedObjects++;
const percentPersisted =
Math.ceil(20 * (persistedObjects / objectsToCreate.length)) + 80;
const message = `Saving ${persistedObjects} of ${objectsToCreate.length} imported objects.`;
importDialog.updateProgress(percentPersisted, message);
await this._instantiate(objectToCreate);
})
);
} catch (error) { } catch (error) {
this.openmct.notifications.error('Error saving objects'); this.openmct.notifications.error('Error saving objects');
throw error; throw error;
} finally {
importDialog.dismiss();
} }
const compositionCollection = this.openmct.composition.get(domainObject); const compositionCollection = this.openmct.composition.get(domainObject);
@ -194,7 +322,8 @@ export default class ImportAsJSONAction {
this.openmct.objects.mutate(rootObj, 'location', domainObjectKeyString); this.openmct.objects.mutate(rootObj, 'location', domainObjectKeyString);
compositionCollection.add(rootObj); compositionCollection.add(rootObj);
} else { } else {
const dialog = this.openmct.overlays.dialog({ importDialog.dismiss();
const cannotImportDialog = this.openmct.overlays.dialog({
iconClass: 'alert', iconClass: 'alert',
message: "We're sorry, but you cannot import that object type into this object.", message: "We're sorry, but you cannot import that object type into this object.",
buttons: [ buttons: [
@ -202,7 +331,7 @@ export default class ImportAsJSONAction {
label: 'Ok', label: 'Ok',
emphasis: true, emphasis: true,
callback: function () { callback: function () {
dialog.dismiss(); cannotImportDialog.dismiss();
} }
} }
] ]
@ -217,43 +346,7 @@ export default class ImportAsJSONAction {
_instantiate(model) { _instantiate(model) {
return this.openmct.objects.save(model); return this.openmct.objects.save(model);
} }
/**
* @private
* @param {Object} oldId
* @param {Object} newId
* @param {Object} tree
* @returns {Object}
*/
_rewriteId(oldId, newId, tree) {
let newIdKeyString = this.openmct.objects.makeKeyString(newId);
let oldIdKeyString = this.openmct.objects.makeKeyString(oldId);
const newTreeString = JSON.stringify(tree).replace(
new RegExp(oldIdKeyString, 'g'),
newIdKeyString
);
const newTree = JSON.parse(newTreeString, (key, value) => {
if (
value !== undefined &&
value !== null &&
Object.prototype.hasOwnProperty.call(value, 'key') &&
Object.prototype.hasOwnProperty.call(value, 'namespace')
) {
// first check if key is messed up from regex and contains a colon
// if it does, repair it
if (value.key.includes(':')) {
const splitKey = value.key.split(':');
value.key = splitKey[1];
value.namespace = splitKey[0];
}
// now check if we need to replace the id
if (value.key === oldId.key && value.namespace === oldId.namespace) {
return newId;
}
}
return value;
});
return newTree;
}
/** /**
* @private * @private
* @param {Object} domainObject * @param {Object} domainObject

View File

@ -111,7 +111,6 @@ describe('The import JSON action', function () {
}); });
it('protects against prototype pollution', (done) => { it('protects against prototype pollution', (done) => {
spyOn(console, 'warn');
spyOn(openmct.forms, 'showForm').and.callFake(returnResponseWithPrototypePollution); spyOn(openmct.forms, 'showForm').and.callFake(returnResponseWithPrototypePollution);
unObserve = openmct.objects.observe(folderObject, '*', callback); unObserve = openmct.objects.observe(folderObject, '*', callback);
@ -123,8 +122,6 @@ describe('The import JSON action', function () {
Object.prototype.hasOwnProperty.call(newObject, '__proto__') || Object.prototype.hasOwnProperty.call(newObject, '__proto__') ||
Object.prototype.hasOwnProperty.call(Object.getPrototypeOf(newObject), 'toString'); Object.prototype.hasOwnProperty.call(Object.getPrototypeOf(newObject), 'toString');
// warning from openmct.objects.get
expect(console.warn).not.toHaveBeenCalled();
expect(hasPollutedProto).toBeFalse(); expect(hasPollutedProto).toBeFalse();
done(); done();
@ -192,6 +189,12 @@ describe('The import JSON action', function () {
type: 'folder' type: 'folder'
}; };
spyOn(openmct.objects, 'save').and.callFake((model) => Promise.resolve(model)); spyOn(openmct.objects, 'save').and.callFake((model) => Promise.resolve(model));
spyOn(openmct.overlays, 'progressDialog').and.callFake(() => {
return {
updateProgress: () => {},
dismiss: () => {}
};
});
try { try {
await importFromJSONAction.onSave(targetDomainObject, { await importFromJSONAction.onSave(targetDomainObject, {
selectFile: { body: JSON.stringify(incomingObject) } selectFile: { body: JSON.stringify(incomingObject) }

View File

@ -61,7 +61,7 @@ describe('The local time', () => {
}); });
it('can be set to be the main time system', () => { it('can be set to be the main time system', () => {
expect(openmct.time.getTimeSystem().key).toBe(LOCAL_SYSTEM_KEY); expect(openmct.time.timeSystem().key).toBe(LOCAL_SYSTEM_KEY);
}); });
it('uses the local-format time format', () => { it('uses the local-format time format', () => {

View File

@ -278,7 +278,7 @@ export default {
const bounds = this.openmct.time.getBounds(); const bounds = this.openmct.time.getBounds();
const isTimeBoundChanged = const isTimeBoundChanged =
this.embed.bounds.start !== bounds.start || this.embed.bounds.end !== bounds.end; this.embed.bounds.start !== bounds.start || this.embed.bounds.end !== bounds.end;
const isFixedTimespanMode = !this.openmct.time.clock(); const isFixedTimespanMode = this.openmct.time.isFixed();
let message = ''; let message = '';
if (isTimeBoundChanged) { if (isTimeBoundChanged) {

View File

@ -31,7 +31,7 @@
@drop.capture="cancelEditMode" @drop.capture="cancelEditMode"
@drop.prevent="dropOnEntry" @drop.prevent="dropOnEntry"
@click="selectAndEmitEntry($event, entry)" @click="selectAndEmitEntry($event, entry)"
@paste="addImageFromPaste" @paste="handlePaste"
> >
<div class="c-ne__time-and-content"> <div class="c-ne__time-and-content">
<div class="c-ne__time-and-creator-and-delete"> <div class="c-ne__time-and-creator-and-delete">
@ -368,6 +368,28 @@ export default {
} }
}, },
methods: { methods: {
handlePaste(event) {
const clipboardItems = Array.from(
(event.clipboardData || event.originalEvent.clipboardData).items
);
const hasClipboardText = clipboardItems.some(
(clipboardItem) => clipboardItem.kind === 'string'
);
const clipboardImages = clipboardItems.filter(
(clipboardItem) => clipboardItem.kind === 'file' && clipboardItem.type.includes('image')
);
const hasClipboardImages = clipboardImages?.length > 0;
if (hasClipboardImages) {
if (hasClipboardText) {
console.warn('Image and text kinds found in paste. Only processing images.');
}
this.addImageFromPaste(clipboardImages, event);
} else if (hasClipboardText) {
this.addTextFromPaste(event);
}
},
async addNewEmbed(objectPath) { async addNewEmbed(objectPath) {
const bounds = this.openmct.time.getBounds(); const bounds = this.openmct.time.getBounds();
const snapshotMeta = { const snapshotMeta = {
@ -384,32 +406,34 @@ export default {
this.manageEmbedLayout(); this.manageEmbedLayout();
}, },
async addImageFromPaste(event) { addTextFromPaste(event) {
const clipboardItems = Array.from( if (!this.editMode) {
(event.clipboardData || event.originalEvent.clipboardData).items
);
const hasImage = clipboardItems.some(
(clipboardItem) => clipboardItem.type.includes('image') && clipboardItem.kind === 'file'
);
// If the clipboard contained an image, prevent the paste event from reaching the textarea.
if (hasImage) {
event.preventDefault(); event.preventDefault();
} }
},
async addImageFromPaste(clipboardImages, event) {
event?.preventDefault();
let updated = false;
await Promise.all( await Promise.all(
Array.from(clipboardItems).map(async (clipboardItem) => { Array.from(clipboardImages).map(async (clipboardImage) => {
const isImage = clipboardItem.type.includes('image') && clipboardItem.kind === 'file'; const imageFile = clipboardImage.getAsFile();
if (isImage) { const imageEmbed = await createNewImageEmbed(imageFile, this.openmct, imageFile?.name);
const imageFile = clipboardItem.getAsFile();
const imageEmbed = await createNewImageEmbed(imageFile, this.openmct, imageFile?.name); if (!this.entry.embeds) {
if (!this.entry.embeds) { this.entry.embeds = [];
this.entry.embeds = [];
}
this.entry.embeds.push(imageEmbed);
} }
this.entry.embeds.push(imageEmbed);
updated = true;
}) })
); );
this.manageEmbedLayout();
this.timestampAndUpdate(); if (updated) {
this.manageEmbedLayout();
this.timestampAndUpdate();
}
}, },
convertMarkDownToHtml(text = '') { convertMarkDownToHtml(text = '') {
let markDownHtml = this.marked.parse(text, { let markDownHtml = this.marked.parse(text, {

View File

@ -199,7 +199,7 @@ export default {
this.updateViewBounds(this.timeContext.getBounds()); this.updateViewBounds(this.timeContext.getBounds());
this.timeContext.on('timeSystem', this.setScaleAndGenerateActivities); this.timeContext.on('timeSystem', this.setScaleAndGenerateActivities);
this.timeContext.on('bounds', this.updateViewBounds); this.timeContext.on('boundsChanged', this.updateViewBounds);
}, },
loadComposition() { loadComposition() {
if (this.composition) { if (this.composition) {
@ -211,7 +211,7 @@ export default {
stopFollowingTimeContext() { stopFollowingTimeContext() {
if (this.timeContext) { if (this.timeContext) {
this.timeContext.off('timeSystem', this.setScaleAndGenerateActivities); this.timeContext.off('timeSystem', this.setScaleAndGenerateActivities);
this.timeContext.off('bounds', this.updateViewBounds); this.timeContext.off('boundsChanged', this.updateViewBounds);
} }
}, },
showReplacePlanDialog(domainObject) { showReplacePlanDialog(domainObject) {

View File

@ -1860,8 +1860,8 @@ export default {
}, },
showSynchronizeDialog() { showSynchronizeDialog() {
const isLocalClock = this.timeContext.clock(); const isFixedTimespanMode = this.timeContext.isFixed();
if (isLocalClock !== undefined) { if (!isFixedTimespanMode) {
const message = ` const message = `
This action will change the Time Conductor to Fixed Timespan mode with this plot view's current time bounds. This action will change the Time Conductor to Fixed Timespan mode with this plot view's current time bounds.
Do you want to continue? Do you want to continue?

View File

@ -140,7 +140,7 @@ export default class PlotSeries extends Model {
//this triggers Model.destroy which in turn triggers destroy methods for other classes. //this triggers Model.destroy which in turn triggers destroy methods for other classes.
super.destroy(); super.destroy();
this.stopListening(); this.stopListening();
this.openmct.time.off('bounds', this.updateLimits); this.openmct.time.off('boundsChanged', this.updateLimits);
if (this.unsubscribe) { if (this.unsubscribe) {
this.unsubscribe(); this.unsubscribe();
@ -171,7 +171,7 @@ export default class PlotSeries extends Model {
this.limitEvaluator = this.openmct.telemetry.limitEvaluator(options.domainObject); this.limitEvaluator = this.openmct.telemetry.limitEvaluator(options.domainObject);
this.limitDefinition = this.openmct.telemetry.limitDefinition(options.domainObject); this.limitDefinition = this.openmct.telemetry.limitDefinition(options.domainObject);
this.limits = []; this.limits = [];
this.openmct.time.on('bounds', this.updateLimits); this.openmct.time.on('boundsChanged', this.updateLimits);
this.removeMutationListener = this.openmct.objects.observe( this.removeMutationListener = this.openmct.objects.observe(
this.domainObject, this.domainObject,
'name', 'name',

View File

@ -72,6 +72,7 @@ import SummaryWidget from './summaryWidget/plugin.js';
import Tabs from './tabs/plugin.js'; import Tabs from './tabs/plugin.js';
import TelemetryMean from './telemetryMean/plugin.js'; import TelemetryMean from './telemetryMean/plugin.js';
import TelemetryTablePlugin from './telemetryTable/plugin.js'; import TelemetryTablePlugin from './telemetryTable/plugin.js';
import DarkMatter from './themes/darkmatter.js';
import Espresso from './themes/espresso.js'; import Espresso from './themes/espresso.js';
import Snow from './themes/snow.js'; import Snow from './themes/snow.js';
import TimeConductorPlugin from './timeConductor/plugin.js'; import TimeConductorPlugin from './timeConductor/plugin.js';
@ -125,7 +126,6 @@ plugins.Plot = PlotPlugin;
plugins.BarChart = BarChartPlugin; plugins.BarChart = BarChartPlugin;
plugins.ScatterPlot = ScatterPlotPlugin; plugins.ScatterPlot = ScatterPlotPlugin;
plugins.TelemetryTable = TelemetryTablePlugin; plugins.TelemetryTable = TelemetryTablePlugin;
plugins.SummaryWidget = SummaryWidget; plugins.SummaryWidget = SummaryWidget;
plugins.TelemetryMean = TelemetryMean; plugins.TelemetryMean = TelemetryMean;
plugins.URLIndicator = URLIndicatorPlugin; plugins.URLIndicator = URLIndicatorPlugin;
@ -145,6 +145,7 @@ plugins.OpenInNewTabAction = OpenInNewTabAction;
plugins.ReloadAction = ReloadAction; plugins.ReloadAction = ReloadAction;
plugins.ClearData = ClearData; plugins.ClearData = ClearData;
plugins.WebPage = WebPagePlugin; plugins.WebPage = WebPagePlugin;
plugins.DarkmatterTheme = DarkMatter;
plugins.Espresso = Espresso; plugins.Espresso = Espresso;
plugins.Snow = Snow; plugins.Snow = Snow;
plugins.Condition = ConditionPlugin; plugins.Condition = ConditionPlugin;

View File

@ -149,6 +149,8 @@ export default class RemoteClock extends DefaultClock {
/** /**
* Waits for the clock to have a non-default tick value. * Waits for the clock to have a non-default tick value.
*
* @private
*/ */
#waitForReady() { #waitForReady() {
const waitForInitialTick = (resolve) => { const waitForInitialTick = (resolve) => {

View File

@ -611,7 +611,7 @@ describe('The Mean Telemetry Provider', function () {
} }
function createMockTimeApi() { function createMockTimeApi() {
return jasmine.createSpyObj('timeApi', ['getTimeSystem', 'setTimeSystem']); return jasmine.createSpyObj('timeApi', ['getTimeSystem']);
} }
function setTimeSystemTo(timeSystemKey) { function setTimeSystemTo(timeSystemKey) {

View File

@ -546,7 +546,7 @@ export default {
this.table.tableRows.on('sort', this.throttledUpdateVisibleRows); this.table.tableRows.on('sort', this.throttledUpdateVisibleRows);
this.table.tableRows.on('filter', this.throttledUpdateVisibleRows); this.table.tableRows.on('filter', this.throttledUpdateVisibleRows);
this.openmct.time.on('bounds', this.boundsChanged); this.openmct.time.on('boundsChanged', this.boundsChanged);
//Default sort //Default sort
this.sortOptions = this.table.tableRows.sortBy(); this.sortOptions = this.table.tableRows.sortBy();
@ -561,6 +561,9 @@ export default {
this.table.initialize(); this.table.initialize();
this.rescaleToContainer(); this.rescaleToContainer();
// Scroll to the top of the table after loading
this.addToAfterLoadActions(this.scroll);
}, },
beforeUnmount() { beforeUnmount() {
this.table.off('object-added', this.addObject); this.table.off('object-added', this.addObject);
@ -579,7 +582,7 @@ export default {
this.table.configuration.off('change', this.updateConfiguration); this.table.configuration.off('change', this.updateConfiguration);
this.openmct.time.off('bounds', this.boundsChanged); this.openmct.time.off('boundsChanged', this.boundsChanged);
this.table.configuration.destroy(); this.table.configuration.destroy();

View File

@ -0,0 +1,44 @@
/*****************************************************************************
* 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.
*****************************************************************************/
@import '../../styles/vendor/normalize-min';
@import '../../styles/constants';
@import '../../styles/constants-mobile.scss';
@import '../../styles/constants-darkmatter';
@import '../../styles/mixins';
@import '../../styles/animations';
@import '../../styles/about';
@import '../../styles/glyphs';
@import '../../styles/global';
@import '../../styles/status';
@import '../../styles/limits';
@import '../../styles/controls';
@import '../../styles/forms';
@import '../../styles/table';
@import '../../styles/legacy';
@import '../../styles/legacy-plots';
@import '../../styles/plotly';
@import '../../styles/legacy-messages';
@import '../../styles/vue-styles.scss';

View File

@ -0,0 +1,30 @@
/*****************************************************************************
* 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.
*****************************************************************************/
// Note: This darkmatter theme is in Beta and is not yet ready for prime time. It needs some more tweaking.
import { installTheme } from './installTheme.js';
export default function plugin() {
return function install(openmct) {
installTheme(openmct, 'darkmatter');
};
}

View File

@ -1,3 +1,25 @@
/*****************************************************************************
* 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.
*****************************************************************************/
@import '../../styles/vendor/normalize-min'; @import '../../styles/vendor/normalize-min';
@import '../../styles/constants'; @import '../../styles/constants';
@import '../../styles/constants-mobile.scss'; @import '../../styles/constants-mobile.scss';

View File

@ -1,3 +1,25 @@
/*****************************************************************************
* 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.
*****************************************************************************/
import { installTheme } from './installTheme.js'; import { installTheme } from './installTheme.js';
export default function plugin() { export default function plugin() {

View File

@ -1,3 +1,25 @@
/*****************************************************************************
* 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.
*****************************************************************************/
@import '../../styles/vendor/normalize-min'; @import '../../styles/vendor/normalize-min';
@import '../../styles/constants'; @import '../../styles/constants';
@import '../../styles/constants-mobile.scss'; @import '../../styles/constants-mobile.scss';

View File

@ -1,3 +1,25 @@
/*****************************************************************************
* 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.
*****************************************************************************/
import { installTheme } from './installTheme.js'; import { installTheme } from './installTheme.js';
export default function plugin() { export default function plugin() {

View File

@ -198,13 +198,13 @@ export default {
this.timeContext = this.openmct.time.getContextForView(this.objectPath); this.timeContext = this.openmct.time.getContextForView(this.objectPath);
this.getTimeSystems(); this.getTimeSystems();
this.updateViewBounds(); this.updateViewBounds();
this.timeContext.on('bounds', this.updateViewBounds); this.timeContext.on('boundsChanged', this.updateViewBounds);
this.timeContext.on('clock', this.updateViewBounds); this.timeContext.on('clockChanged', this.updateViewBounds);
}, },
stopFollowingTimeContext() { stopFollowingTimeContext() {
if (this.timeContext) { if (this.timeContext) {
this.timeContext.off('bounds', this.updateViewBounds); this.timeContext.off('boundsChanged', this.updateViewBounds);
this.timeContext.off('clock', this.updateViewBounds); this.timeContext.off('clockChanged', this.updateViewBounds);
} }
} }
} }

View File

@ -0,0 +1,592 @@
/*****************************************************************************
* 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.
*****************************************************************************/
/************************************************** DARKMATTER THEME*/
// Fonts
@import url('https://fonts.googleapis.com/css2?family=Roboto+Condensed&display=swap'); // Header font, Roboto Condensed. This is an alternative to the DIN Alt font, which is not available on Google Fonts.
@import url('https://fonts.googleapis.com/css2?family=Exo:ital,wght@0,100..900;1,100..900&display=swap'); // Body Font, Exo
@import url('https://fonts.googleapis.com/css2?family=Chakra+Petch&family=Roboto+Condensed&display=swap');// Temporary numeric font, Chakra Petch (need to import local font instead).
$heroFont: 'Teko', sans-serif;
$headerFont: 'Cabin Condensed', sans-serif;
$bodyFont: 'Exo', sans-serif;
$numericFont: 'Chakra Petch', sans-serif;
@mixin heroFont($size: 1em) {
font-family: $heroFont;
font-size: $size;
}
@mixin headerFont($size: 1em) {
font-family: $headerFont;
font-size: $size;
}
@mixin bodyFont($size: 1em) {
font-family: $bodyFont;
font-size: $size;
}
@mixin numericFont($size: 1em){
font-family: $numericFont;
font-size: $size;
}
@mixin discreteItem() {
background: $colorDiscreteItemBg;
border: none;
border-radius: $controlCr;
.c-input-inline:hover {
background: $colorBodyBg;
}
&--current-match {
background: $colorDiscreteItemCurrentBg;
}
}
@mixin discreteItemInnerElem() {
border: 1px solid rgba(#fff, 0.1);
border-radius: $controlCr;
}
@mixin themedButton($c: $colorBtnBg) {
background: radial-gradient(rgba($c, 1) , rgba($c, .7));
box-shadow: rgba(black, 0.5) 0 0.5px 2px;
}
@mixin telemetryView(){
border: 1px solid $colorBodyFg;
border-radius: $controlCr;
}
@mixin browseFrameBorder() { // Used on main object container to add highlighted corners to non-hidden frames.
border-image: radial-gradient(circle, #575757, #6c6c6c, #818181, #979797, #aeaeae);
border-style: solid;
padding: 10px;
$browseFrameCornerWidth: 4px;
background:
linear-gradient(to right, $browseFrameCornerColor, transparent $browseFrameCornerWidth) 0 0,
linear-gradient(to right, $browseFrameCornerColor, transparent $browseFrameCornerWidth) 0 100%,
linear-gradient(to left, $browseFrameCornerColor, transparent $browseFrameCornerWidth) 100% 0,
linear-gradient(to left, $browseFrameCornerColor, transparent $browseFrameCornerWidth) 100% 100%,
linear-gradient(to bottom, $browseFrameCornerColor, transparent $browseFrameCornerWidth) 0 0,
linear-gradient(to bottom, $browseFrameCornerColor, transparent $browseFrameCornerWidth) 100% 0,
linear-gradient(to top, $browseFrameCornerColor, transparent $browseFrameCornerWidth) 0 100%,
linear-gradient(to top, $browseFrameCornerColor, transparent $browseFrameCornerWidth) 100% 100%,
rgb(0, 0, 0, .4);
background-repeat: no-repeat;
background-size: 35px 35px;
border-radius: $interiorMarginLg;
box-shadow: 0px 0px 20px 2px rgb(140 140 140 / 20%);
}
// Functions
@function buttonBg($c: $colorBtnBg) {
@return radial-gradient(rgba($colorBodyBg, 1) , rgba($colorBodyBg, .6));
}
@function pullForward($val, $amt) {
@return lighten($val, $amt);
}
@function pushBack($val, $amt) {
@return darken($val, $amt);
}
/**************************************************** CONSTANTS */
$fontBaseSize: 12px;
$smallCr: 2px;
$controlCr: 3px;
$basicCr: 4px;
$shdwBtns: rgba(black, 0.2) 0 1px 2px;
$shdwBtnsOverlay: rgba(black, 0.5) 0 1px 5px;
// Base colors
$colorBodyBg: #17171B;
$colorBodyBgSubtle: pullForward($colorBodyBg, 5%);
$colorBodyBgSubtleHov: pullForward($colorBodyBg, 10%);
$colorBodyFg: #aaaaaa;
$colorBodyFgSubtle: #9c9c9c;
$colorBodyFgEm: #fff;
$colorGenBg: #222;
$colorHeadBg: rgba($colorBodyBg, .5);
$colorHeadFg: $colorBodyFg;
$colorKey: #1C67E3;
$colorKeyBg: #015fca;
$colorKeyFg: #fff; // Darker version of colorKey for use in major buttons
$colorKeyHov: lighten($colorKey, 10%);
$colorKeyBgHov: lighten($colorKeyBg, 10%);
$colorKeyFilter: invert(36%) sepia(10%) saturate(2512%) hue-rotate(170deg) brightness(100%) contrast(200%);
$colorKeyFilterHov: invert(63%) sepia(88%) saturate(3029%) hue-rotate(154deg) brightness(101%)
contrast(100%);
$colorKeySelectedBg: $colorKey;
$uiColor: #0093ff; // Resize bars, splitter bars, etc.
$colorInteriorBorder: rgba($colorBodyFg, 0.2);
$colorA: #ccc;
$colorAHov: #fff;
$filterHov: brightness(1.3) contrast(1.5); // Tree, location items
$filterHovSubtle: brightness(1.2) contrast(1.2);
$colorSelectedBg: rgba($colorKey, 0.3);
$colorSelectedFg: pullForward($colorBodyFg, 20%);
// Body constants
$bodyBg: $colorBodyBg url('../ui/layout/assets/images/darkmatter-bg.png') no-repeat center 85%; // Reference: https://science.nasa.gov/wp-content/uploads/2023/08/s2-1280.jpg?w=4096&format=webp
$bodyBgSize: cover;
$bodySize: 100vh;
// Object labels
$objectLabelTypeIconOpacity: 0.9;
$objectLabelNameColorFg: $colorBodyFgEm;
// Layout
$shellMainPad: 4px 0;
$shellPanePad: $interiorMargin, 7px;
$drawerBg: lighten($colorBodyBg, 5%);
$drawerFg: lighten($colorBodyFg, 5%);
$sideBarBg: $drawerBg;
$sideBarHeaderBg: rgba($colorBodyFg, 0.1);
$sideBarHeaderFg: rgba($colorBodyFg, 0.7);
// Status colors, mainly used for messaging and item ancillary symbols
$colorStatusFg: #888;
$colorStatusDefault: #ccc;
$colorStatusInfo: #60ba7b;
$colorStatusInfoFilter: invert(58%) sepia(44%) saturate(405%) hue-rotate(85deg) brightness(102%) contrast(92%);
$colorStatusAlert: #ffb66c;
$colorStatusAlertFilter: invert(78%) sepia(26%) saturate(1160%) hue-rotate(324deg) brightness(107%) contrast(101%);
$colorStatusError: #da0004;
$colorStatusErrorFilter: invert(10%) sepia(96%) saturate(4360%) hue-rotate(351deg) brightness(111%) contrast(115%);
$colorStatusBtnBg: #666; // Where is this used?
$colorStatusPartialBg: #3f5e8b;
$colorStatusCompleteBg: #457638;
$colorAlert: #ff8a0d;
$colorAlertFg: #fff;
$colorError: #ff3c00;
$colorErrorFg: #fff;
$colorWarningHi: #990000;
$colorWarningHiFg: #ff9594;
$colorWarningLo: #ff9900;
$colorWarningLoFg: #523400;
$colorDiagnostic: #a4b442;
$colorDiagnosticFg: #39461a;
$colorCommand: #3693bd;
$colorCommandFg: #fff;
$colorInfo: #2294a2;
$colorInfoFg: #fff;
$colorOk: #33cc33;
$colorOkFg: #fff;
$colorFilterBg: #44449c;
$colorFilterFg: #8984e9;
$colorFilter: $colorFilterFg; // Standalone against $colorBodyBg
// States
$colorPausedBg: #ff9900;
$colorPausedFg: #333;
// Time Colors
$colorTimeCommonFg: #eee;
$colorTimeFixed: #59554c;
$colorTimeFixedBg: $colorTimeFixed;
$colorTimeFixedFg: #eee;
$colorTimeFixedFgSubtle: #b2aa98;
$colorTimeFixedHov: pullForward($colorTimeFixed, 5%);
$colorTimeFixedSubtle: pushBack($colorTimeFixed, 20%);
$colorTimeFixedBtnBg: pullForward($colorTimeFixed, 5%);
$colorTimeFixedBtnFg: $colorTimeFixedFgSubtle;
$colorTimeFixedBtnBgMajor: #a09375;
$colorTimeFixedBtnFgMajor: #fff;
$colorTimeRealtime: #445890;
$colorTimeRealtimeBg: $colorTimeRealtime;
$colorTimeRealtimeFg: #eee;
$colorTimeRealtimeFgSubtle: #88b0ff;
$colorTimeRealtimeHov: pullForward($colorTimeRealtime, 5%);
$colorTimeRealtimeSubtle: pushBack($colorTimeRealtime, 20%);
$colorTimeRealtimeBtnBg: pullForward($colorTimeRealtime, 5%);
$colorTimeRealtimeBtnFg: $colorTimeRealtimeFgSubtle;
$colorTimeRealtimeBtnBgMajor: #588ffa;
$colorTimeRealtimeBtnFgMajor: #fff;
$colorTOI: $colorBodyFg; // was $timeControllerToiLineColor
$colorTOIHov: $colorTimeRealtime; // was $timeControllerToiLineColorHov
$timeConductorAxisHoverFilter: brightness(1.2);
$timeConductorActiveBg: $colorKey;
$timeConductorActivePanBg: #226074;
// Browse
$browseFrameColor: pullForward($colorBodyBg, 10%);
$browseFrameBorder: 1px solid rgb(89, 89, 89, .4); // Frames in Disp and Flex Layouts when frame is showing
$browseSelectableShdwHov: rgba($colorBodyFg, 0.3) 0 0 3px;
$browseSelectedBorder: 1px solid rgba($colorBodyFg, 0.4);
$filterItemHoverFg: brightness(1.2) contrast(1.1);
$interiorMarginObjectFrameVertical: 10px;
$interiorMarginObjectFrameHorizontal: 10px;
// Missing Items
$filterItemMissing: brightness(0.6) grayscale(1);
$opacityMissing: 0.5;
$borderMissing: 1px dashed $colorAlert !important;
$browseFrameCornerColor: $colorKey 4px; //Color used for the corners of frames
// Edit
$editUIColor: $uiColor; // Base color
$editUIColorBg: $editUIColor;
$editUIColorFg: #fff;
$editUIColorHov: pullForward(saturate($uiColor, 10%), 10%); // Hover color when $editUIColor is applied as a base color
$editUIBaseColor: #344b8d; // Base color, toolbar bg
$editUIBaseColorHov: pullForward($editUIBaseColor, 20%);
$editUIBaseColorFg: #ffffff; // Toolbar button icon colors, etc.
$editUIAreaBaseColor: pullForward(saturate($editUIBaseColor, 30%), 20%);
$editUIAreaShdw: $editUIAreaBaseColor 0 0 0 2px; // Edit area s-selected-parent
$editUIAreaShdwSelected: $editUIAreaBaseColor 0 0 0 3px; // Edit area s-selected
$editUIGridColorBg: rgba($editUIBaseColor, 0.2); // Background of layout editing area
$editUIGridColorFg: rgba(#000, 0.1); // Grid lines in layout editing area
$editDimensionsColor: #6a5ea6;
$editFrameColor: $browseFrameColor; // Solid or dotted border applied to non-selected frames in a layout; move-bar on frame hover
$editFrameBorder: 1px dotted $editFrameColor;
$editFrameColorHov: $editUIColor; // Solid border hover on frames; hover should not be applied to selected objects
$editFrameBorderHov: 1px solid $editFrameColorHov; // Hover on selectable frames
$editFrameColorSelected: #ffefc2; // Border of selected frames while editing
$editFrameColorHandleBg: $colorBodyBg; // Resize handle 'offset' color to make handle standout
$editFrameColorHandleFg: $editFrameColorSelected; // Resize handle main color
$editFrameSelectedShdw: rgba(black, 0.4) 0 1px 5px 1px;
$editFrameMovebarColorBg: $editFrameColor; // Movebar bg color
$editFrameMovebarColorFg: pullForward($editFrameMovebarColorBg, 20%); // Grippy lines, container size text
$editFrameHovMovebarColorBg: pullForward($editFrameMovebarColorBg, 10%); // Hover style
$editFrameHovMovebarColorFg: pullForward($editFrameMovebarColorFg, 10%);
$editFrameSelectedMovebarColorBg: pullForward($editFrameMovebarColorBg, 15%); // Selected style
$editFrameSelectedMovebarColorFg: pullForward($editFrameMovebarColorFg, 15%);
$editFrameMovebarH: 10px; // Height of move bar in layout frame
$editMarqueeBorder: 1px dashed $editFrameColorSelected;
$editFrameSelectedBorder: $editMarqueeBorder; // Selected frame element
// Icons
$colorIconAlias: #4af6f3;
$colorIconAliasForKeyFilter: #aaa;
// Holders
$colorTabsHolderBg: rgba(black, 0.2);
// Buttons and Controls
$colorBtnBg: pullForward($colorBodyBg, 20%);
$colorBtnBgHov: pullForward($colorBtnBg, 10%);
$shdwBtnHov: inset rgba(white, 10%) 0 0 0 100px;
$colorBtnFg: pullForward($colorBodyFg, 100%);
$colorBtnReverseFg: pullForward($colorBtnFg, 10%);
$colorBtnReverseBg: pullForward($colorBtnBg, 10%);
$colorBtnFgHov: $colorBtnFg;
$colorBtnMajorBg: $colorKey;
$colorBtnMajorBgHov: $colorKeyHov;
$colorBtnMajorFg: $colorKeyFg;
$colorBtnMajorFgHov: pushBack($colorBtnMajorFg, 10%);
$colorBtnCautionBg: $colorStatusAlert;
$colorBtnCautionBgHov: #f1504e;
$colorBtnCautionFg: $colorBtnBg;
$colorBtnActiveBg: $colorOk;
$colorBtnActiveFg: $colorOkFg;
$colorBtnSelectedBg: $colorSelectedBg;
$colorBtnSelectedFg: $colorSelectedFg;
$colorClickIconButton: $colorKey;
$colorClickIconButtonBgHov: rgba($colorKey, 0.3);
$colorClickIconButtonFgHov: $colorKeyHov;
$colorDropHint: $colorKey;
$colorDropHintBg: pushBack($colorDropHint, 10%);
$colorDropHintBgHov: $colorDropHint;
$colorDropHintFg: pullForward($colorDropHint, 40%);
$colorDisclosureCtrl: rgba($colorBodyFg, 0.5);
$colorDisclosureCtrlHov: rgba($colorBodyFg, 0.7);
$btnStdH: 24px;
$colorCursorGuide: rgba(white, 0.6);
$shdwCursorGuide: rgba(black, 0.4) 0 0 2px;
$colorLocalControlOvrBg: rgba($colorBodyBg, 0.8);
$colorSelectBg: $colorBtnBg; // This must be a solid color, not a gradient, due to usage of SVG bg in selects
$colorSelectFg: $colorBtnFg;
$colorSelectArw: lighten($colorBtnBg, 20%);
$shdwSelect: rgba(black, 0.5) 0 0.5px 3px;
$controlDisabledOpacity: 0.2;
// Menus
$colorMenuBg: rgba($colorBodyBg, 0.6);
$colorMenuFg: $colorBodyFg;
$colorMenuIc: $colorKey;
$filterMenu: brightness(1.4);
$colorMenuHovBg: rgba($colorKey, 0.5);
$colorMenuHovFg: $colorBodyFgEm;
$colorMenuHovIc: $colorMenuHovFg;
$colorMenuElementHilite: pullForward($colorMenuBg, 10%);
$shdwMenu: rgba(black, 0.8) 0 2px 10px;
$shdwMenuInner: inset 0 0 0 1px rgba(white, 0.2);
$shdwMenuText: none;
$menuItemPad: $interiorMargin, floor($interiorMargin * 1.25);
// Palettes and Swatches
$paletteItemBorderOuterColorSelected: black;
$paletteItemBorderInnerColorSelected: white;
$paletteItemBorderInnerColor: rgba($paletteItemBorderOuterColorSelected, 0.3);
$mixedSettingBg: (transparent rgba($editUIBaseColorHov, 0.7)); // Used in .c-click-icon--mixed
$mixedSettingBgSize: 5px;
// Forms
$colorCheck: $colorKey;
$colorFormRequired: $colorKey;
$colorFormValid: $colorOk;
$colorFormError: #990000;
$colorFormInvalid: #ff2200;
$colorFormFieldErrorBg: $colorFormError;
$colorFormFieldErrorFg: rgba(#fff, 0.6);
$colorFormLines: rgba(#000, 0.2);
$colorFormSectionHeaderBg: rgba(#000, 0.1);
$colorFormSectionHeaderFg: rgba($colorBodyFg, 0.8);
$colorInputBg: rgba(rgb(70, 70, 70), 0.3);
$colorInputBgHov: rgba(black, 0.1);
$colorInputFg: $colorBodyFg;
$colorFormText: pushBack($colorBodyFg, 10%);
$colorInputIcon: pushBack($colorBodyFg, 25%);
$colorFieldHint: pullForward($colorBodyFg, 40%);
$shdwInput: inset rgba(black, 0.4) 0 0 1px;
$shdwInputHov: inset rgba(black, 0.7) 0 0 2px;
$shdwInputFoc: inset rgba(black, 0.8) 0 0.25px 3px;
$formTBPad: $interiorMargin;
$formLRPad: $interiorMargin;
$formInputH: 22px;
$formRowCtrlsH: 14px;
// Inspector
$colorInspectorBg: pullForward($colorBodyBg, 5%);
$colorInspectorFg: $colorBodyFg;
$colorInspectorPropName: $colorBodyFg;
$colorInspectorPropVal: pullForward($colorInspectorFg, 15%);
$colorInspectorSectionHeaderBg: rgba($colorBodyBg, .75);
$colorInspectorSectionHeaderFg: pullForward($colorInspectorBg, 40%);
// Tabs
$colorTabBg: $colorBodyBg;
$colorTabFg: $colorBodyFgEm;
$colorTabCurrentBg: rgba($colorKey, .71);
$colorTabCurrentFg: $colorBodyFgEm;
$colorTabsBaseline: $colorBodyBg;
// Overlay
$colorOvrBlocker: rgba(black, 0.8);
$overlayCr: $interiorMargin;
// Indicator colors
$colorIndicatorAvailable: $colorKey;
$colorIndicatorDisabled: #555555;
$colorIndicatorOn: $colorOk;
$colorIndicatorOff: #777777;
$colorIndicatorBgHov: rgba($colorHeadFg, 0.1);
$colorIndicatorMenuBg: $colorHeadBg;
$colorIndicatorMenuBgShdw: rgba(white, 0.6) 0 0 6px;
$colorIndicatorMenuFg: $colorHeadFg;
$colorIndicatorMenuFgHov: pullForward($colorHeadFg, 10%);
// Staleness
$colorTelemStale: cyan;
$colorTelemStaleFg: #002a2a;
$styleTelemStale: italic;
// Limits
$colorLimitYellowBg: #b18b05;
$colorLimitYellowFg: #feeeb5;
$colorLimitYellowIc: #fdc707;
$colorLimitOrangeBg: #b36b00;
$colorLimitOrangeFg: #ffe0b2;
$colorLimitOrangeIc: #ff9900;
$colorLimitRedBg: #B60109;
$colorLimitRedFg: #ffa489;
$colorLimitRedIc: #ff4222;
$colorLimitPurpleBg: #891bb3;
$colorLimitPurpleFg: #edbeff;
$colorLimitPurpleIc: #c327ff;
$colorLimitCyanBg: #4ba6b3;
$colorLimitCyanFg: #d3faff;
$colorLimitCyanIc: #6bedff;
// Events
$colorEventPurpleFg: #aB8fff;
$colorEventRedFg: #ff9999;
$colorEventOrangeFg: #ff8800;
$colorEventYellowFg: #ffdb63;
$colorEventPurpleBg: #31204a;
$colorEventRedBg: #3c1616;
$colorEventOrangeBg: #3e2a13;
$colorEventYellowBg: #3e3316;
// Bubble colors
$colorInfoBubbleBg: #dddddd;
$colorInfoBubbleFg: #666;
$colorThumbsBubbleFg: pullForward($colorBodyFg, 10%);
$colorThumbsBubbleBg: pullForward($colorBodyBg, 10%);
// Items
$colorItemBg: buttonBg($colorBtnBg);
$colorItemBgHov: buttonBg(pullForward($colorBtnBg, 5%));
$colorListItemBg: transparent;
$colorListItemBgHov: rgba($colorKey, 0.1);
$colorItemFg: $colorBtnFg;
$colorItemFgDetails: $colorBodyFgSubtle;
$shdwItemText: none;
// Tabular
$colorTabBorder: pullForward($colorBodyBg, 10%);
$colorTabBodyBg: $colorBodyBg;
$colorTabBodyFg: pullForward($colorBodyFg, 20%);
$colorTabHeaderBg: #575757;
$colorTabHeaderFg: $colorBodyFg;
$colorTabHeaderBorder: $colorBodyBg;
$colorTabGroupHeaderBg: pullForward($colorBodyBg, 5%);
$colorTabGroupHeaderFg: pushBack($colorTabHeaderFg, 10%);
$colorSummaryBg: #2c2c2c;
$colorSummaryFg: rgba($colorBodyFg, 0.7);
$colorSummaryFgEm: $colorBodyFg;
// Plot
$colorPlotBg: rgba(black, 0.1);
$colorPlotFg: $colorBodyFg;
$colorPlotHash: $colorPlotFg;
$opacityPlotHash: 0.2;
$stylePlotHash: dashed;
$colorPlotAreaBorder: $colorInteriorBorder;
$colorPlotLabelFg: pushBack($colorPlotFg, 20%);
$legendHoverValueBg: rgba($colorBodyFg, 0.2);
$legendTableHeadBg: $colorTabHeaderBg;
$colorPlotLimitLineBg: rgba($colorBodyBg, 0.2);
// Gauges
$colorGaugeBase: $colorKeyBg;
$colorGaugeBg: rgba($colorGaugeBase, .35); // Gauge radial area background, meter background
$colorGaugeValue: $colorKeyBg; // Gauge value graphic (radial sweep, bar) color
$colorGaugeTextValue: #fff; // Radial gauge text value
$colorGaugeMeterTextValue: #fff; // Meter text value, overlaid on value bar
$colorGaugeRange: $colorBodyFg; // Range text color
$colorGaugeLimitHigh: rgba($colorLimitRedBg, .5);
$colorGaugeLimitLow: $colorGaugeLimitHigh;
$colorGaugeNeedle: $colorGaugeBase; // Color of needle in a needle gauge.
$transitionTimeGauge: 150ms; // CSS transition time used to smooth needle gauge and meter value transitions
$marginGaugeMeterValue: 10%; // Margin between meter value bar and bounds edges
$gaugeMeterValueShadow: rgba(255, 255, 255, .5);
// TODO: This is some code regarding how we can make Gauges include a border or glow. We may need to revisit this.
// padding: 5%;
// background: radial-gradient(circle, transparent 0%, transparent 65%, rgba(255, 255, 255,0.4) 64%, rgba(255,255,255,0) 70%)
// Time Strip and Lists
$colorPastBg: #444;
$colorPastFg: pushBack($colorBodyFg, 10%);
$colorPastFgEm: $colorBodyFg;
$colorCurrentBg: #666;
$colorCurrentFg: $colorBodyFg;
$colorCurrentFgEm: $colorBodyFgEm;
$colorCurrentBorder: $colorBodyBg;
$colorFutureBg: $colorPastBg;
$colorFutureFg: $colorCurrentFg;
$colorFutureFgEm: $colorCurrentFgEm;
$colorFutureBorder: $colorCurrentBorder;
$colorInProgressBg: $colorTimeRealtimeBg;
$colorInProgressFg: $colorTimeRealtimeFgSubtle;
$colorInProgressFgEm: $colorTimeRealtimeFg;
$colorGanttSelectedBorder: rgba(#fff, 0.3);
// Tree
$colorTreeBg: transparent;
$colorItemTreeHoverBg: rgba(#fff, 0.1);
$colorItemTreeHoverFg: #fff;
$colorItemTreeIcon: $colorKey;
$colorItemTreeIconHover: $colorItemTreeIcon;
$colorItemTreeFg: #ccc;
$colorItemTreeSelectedBg: $colorSelectedBg;
$colorItemTreeSelectedFg: $colorItemTreeHoverFg;
$filterItemTreeSelected: $filterHov;
$colorItemTreeSelectedIcon: $colorItemTreeSelectedFg;
$colorItemTreeEditingBg: pushBack($editUIColor, 20%);
$colorItemTreeEditingFg: $editUIColor;
$colorItemTreeEditingIcon: $editUIColor;
$colorItemTreeVC: $colorDisclosureCtrl;
$colorItemTreeVCHover: $colorDisclosureCtrlHov;
$colorItemTreeNewNode: rgba($colorBodyFg, 0.7);
$shdwItemTreeIcon: none;
// Layout frame controls
$frameControlsColorFg: white;
$frameControlsColorBg: $colorKey;
$frameControlsShdw: $shdwMenu;
// Images
$colorThumbHoverBg: $colorItemTreeHoverBg;
// Scrollbar
$scrollbarTrackSize: 7px;
$scrollbarTrackShdw: rgba(#000, 0.2) 0 1px 2px;
$scrollbarTrackColorBg: rgba(#000, 0.2);
$scrollbarThumbColor: pushBack($colorBodyBg, 50%);
$scrollbarThumbColorHov: $colorKey;
$scrollbarThumbColorMenu: pullForward($colorMenuBg, 10%);
$scrollbarThumbColorMenuHov: pullForward($scrollbarThumbColorMenu, 2%);
// Splitter
$splitterHandleD: 2px;
$splitterD: $splitterHandleD;
$splitterHandleHitMargin: 4px;
$colorSplitterBaseBg: $colorBodyBg;
$colorSplitterBg: pullForward($colorBodyBg, 10%);
$colorSplitterFg: $colorBodyBg;
$colorSplitterHover: $uiColor;
$colorSplitterActive: $colorKey;
$splitterBtnD: (16px, 35px); // height, width
$splitterBtnColorBg: $colorBtnBg;
$splitterBtnColorFg: #999;
$splitterBtnLabelColorFg: #9d9d9d;
$splitterCollapsedBtnColorBg: #222;
$splitterCollapsedBtnColorFg: #555;
$splitterCollapsedBtnColorBgHov: $colorKey;
$splitterCollapsedBtnColorFgHov: $colorKeyFg;
// Mobile
$colorMobilePaneLeft: pushBack($colorBodyBg, 2%);
$colorMobilePaneLeftTreeItemBg: rgba($colorBodyFg, 0.1);
$colorMobilePaneLeftTreeItemFg: $colorItemTreeFg;
$colorMobileSelectListTreeItemBg: rgba(#000, 0.05);
// About Screen
$colorAboutLink: #9bb5ff;
// Loading
$colorLoadingFg: #776ba2;
$colorLoadingBg: rgba($colorLoadingFg, 0.1);
// Transitions
$transInTime: 50ms;
$transOutTime: 250ms;
$transIn: all $transInTime ease-in-out;
$transOut: all $transOutTime ease-in-out;
$transInTransform: transform $transInTime ease-in-out;
$transOutTransform: transform $transOutTime ease-in-out;
$transInBounce: all 200ms cubic-bezier(0.47, 0.01, 0.25, 1.5);
$transInBounceBig: all 300ms cubic-bezier(0.2, 1.6, 0.6, 3);
// Discrete items
$createBtnTextTransform: uppercase;
$colorDiscreteItemBg: rgba($colorBodyFg, 0.1);
$colorDiscreteItemBgHov: rgba($colorBodyFg, 0.2);
$colorDiscreteItemCurrentBg: rgba($colorOk, 0.3);
$scrollContainer: $colorBodyBg;

View File

@ -25,6 +25,7 @@
$heroFont: 'Helvetica Neue', Helvetica, Arial, sans-serif; $heroFont: 'Helvetica Neue', Helvetica, Arial, sans-serif;
$headerFont: $heroFont; $headerFont: $heroFont;
$bodyFont: $heroFont; $bodyFont: $heroFont;
$numericFont: $heroFont;
@mixin heroFont($size: 1em) { @mixin heroFont($size: 1em) {
font-family: $heroFont; font-family: $heroFont;
@ -40,6 +41,7 @@ $bodyFont: $heroFont;
font-family: $bodyFont; font-family: $bodyFont;
font-size: $size; font-size: $size;
} }
@mixin discreteItem() { @mixin discreteItem() {
background: $colorDiscreteItemBg; background: $colorDiscreteItemBg;
border: none; border: none;
@ -64,6 +66,9 @@ $bodyFont: $heroFont;
box-shadow: rgba(black, 0.5) 0 0.5px 2px; box-shadow: rgba(black, 0.5) 0 0.5px 2px;
} }
@mixin telemetryView(){}
@mixin browseFrameBorder() {}
// Functions // Functions
@function buttonBg($c: $colorBtnBg) { @function buttonBg($c: $colorBtnBg) {
@return linear-gradient(lighten($c, 5%), $c); @return linear-gradient(lighten($c, 5%), $c);
@ -113,6 +118,11 @@ $filterHovSubtle: brightness(1.2) contrast(1.2);
$colorSelectedBg: rgba($colorKey, 0.3); $colorSelectedBg: rgba($colorKey, 0.3);
$colorSelectedFg: pullForward($colorBodyFg, 20%); $colorSelectedFg: pullForward($colorBodyFg, 20%);
// Body constants
$bodyBg: $colorBodyBg;
$bodyBgSize: cover;
$bodySize: 100%;
// Object labels // Object labels
$objectLabelTypeIconOpacity: 0.8; //JOHN $objectLabelTypeIconOpacity: 0.8; //JOHN
$objectLabelNameColorFg: lighten($colorBodyFg, 10%); $objectLabelNameColorFg: lighten($colorBodyFg, 10%);
@ -196,6 +206,8 @@ $browseFrameBorder: 1px solid $browseFrameColor; // Frames in Disp and Flex Layo
$browseSelectableShdwHov: rgba($colorBodyFg, 0.3) 0 0 3px; $browseSelectableShdwHov: rgba($colorBodyFg, 0.3) 0 0 3px;
$browseSelectedBorder: 1px solid rgba($colorBodyFg, 0.4); $browseSelectedBorder: 1px solid rgba($colorBodyFg, 0.4);
$filterItemHoverFg: brightness(1.2) contrast(1.1); $filterItemHoverFg: brightness(1.2) contrast(1.1);
$interiorMarginObjectFrameVertical: 0px;
$interiorMarginObjectFrameHorizontal: 3px;
// Missing Items // Missing Items
$filterItemMissing: brightness(0.6) grayscale(1); $filterItemMissing: brightness(0.6) grayscale(1);
@ -435,8 +447,10 @@ $colorGaugeMeterTextValue: $colorGaugeTextValue; // Meter text value, overlaid o
$colorGaugeRange: $colorBodyFg; // Range text color $colorGaugeRange: $colorBodyFg; // Range text color
$colorGaugeLimitHigh: rgba($colorLimitRedBg, 0.4); $colorGaugeLimitHigh: rgba($colorLimitRedBg, 0.4);
$colorGaugeLimitLow: $colorGaugeLimitHigh; $colorGaugeLimitLow: $colorGaugeLimitHigh;
$colorGaugeNeedle: rgba(#fff, 0);
$transitionTimeGauge: 150ms; // CSS transition time used to smooth needle gauge and meter value transitions $transitionTimeGauge: 150ms; // CSS transition time used to smooth needle gauge and meter value transitions
$marginGaugeMeterValue: 10%; // Margin between meter value bar and bounds edges $marginGaugeMeterValue: 10%; // Margin between meter value bar and bounds edges
$gaugeMeterValueShadow: rgba(255, 255, 255, 0);
// Time Strip and Lists // Time Strip and Lists
$colorPastBg: #444; $colorPastBg: #444;
@ -459,8 +473,8 @@ $colorGanttSelectedBorder: rgba(#fff, 0.3);
$colorTreeBg: transparent; $colorTreeBg: transparent;
$colorItemTreeHoverBg: rgba(#fff, 0.1); $colorItemTreeHoverBg: rgba(#fff, 0.1);
$colorItemTreeHoverFg: #fff; $colorItemTreeHoverFg: #fff;
$colorItemTreeIcon: $colorKey; // Used $colorItemTreeIcon: $colorKey;
$colorItemTreeIconHover: $colorItemTreeIcon; // Used $colorItemTreeIconHover: $colorItemTreeIcon;
$colorItemTreeFg: #ccc; $colorItemTreeFg: #ccc;
$colorItemTreeSelectedBg: $colorSelectedBg; $colorItemTreeSelectedBg: $colorSelectedBg;
$colorItemTreeSelectedFg: $colorItemTreeHoverFg; $colorItemTreeSelectedFg: $colorItemTreeHoverFg;

View File

@ -27,6 +27,7 @@
$heroFont: 'Teko', sans-serif; $heroFont: 'Teko', sans-serif;
$headerFont: 'Michroma', sans-serif; $headerFont: 'Michroma', sans-serif;
$bodyFont: 'Chakra Petch', sans-serif; $bodyFont: 'Chakra Petch', sans-serif;
$numericFont: $heroFont;
@mixin heroFont($size: 1em) { @mixin heroFont($size: 1em) {
font-family: $heroFont; font-family: $heroFont;
@ -65,6 +66,9 @@ $bodyFont: 'Chakra Petch', sans-serif;
box-shadow: rgba(black, 0.5) 0 0.5px 2px; box-shadow: rgba(black, 0.5) 0 0.5px 2px;
} }
@mixin telemetryView(){}
@mixin browseFrameBorder() {}
/**************************************************** OVERRIDES */ /**************************************************** OVERRIDES */
.c-frame { .c-frame {
&:not(.no-frame) { &:not(.no-frame) {
@ -130,6 +134,11 @@ $filterHovSubtle: brightness(1.2) contrast(1.2);
$colorSelectedBg: rgba($colorKey, 0.3); $colorSelectedBg: rgba($colorKey, 0.3);
$colorSelectedFg: pullForward($colorBodyFg, 20%); $colorSelectedFg: pullForward($colorBodyFg, 20%);
// Body constants
$bodyBg: $colorBodyBg;
$bodyBgSize: cover;
$bodySize: 100%;
// Object labels // Object labels
$objectLabelTypeIconOpacity: 0.7; $objectLabelTypeIconOpacity: 0.7;
$objectLabelNameColorFg: lighten($colorBodyFg, 10%); $objectLabelNameColorFg: lighten($colorBodyFg, 10%);
@ -213,6 +222,8 @@ $browseFrameBorder: 1px solid $browseFrameColor; // Frames in Disp and Flex Layo
$browseSelectableShdwHov: rgba($colorBodyFg, 0.3) 0 0 3px; $browseSelectableShdwHov: rgba($colorBodyFg, 0.3) 0 0 3px;
$browseSelectedBorder: 1px solid rgba($colorBodyFg, 0.4); $browseSelectedBorder: 1px solid rgba($colorBodyFg, 0.4);
$filterItemHoverFg: brightness(1.2) contrast(1.1); $filterItemHoverFg: brightness(1.2) contrast(1.1);
$interiorMarginObjectFrameVertical: 0px;
$interiorMarginObjectFrameHorizontal: 3px;
// Missing Items // Missing Items
$filterItemMissing: contrast(0.2); $filterItemMissing: contrast(0.2);
@ -452,8 +463,10 @@ $colorGaugeMeterTextValue: $colorGaugeTextValue; // Meter text value, overlaid o
$colorGaugeRange: $colorBodyFg; // Range text color $colorGaugeRange: $colorBodyFg; // Range text color
$colorGaugeLimitHigh: rgba($colorLimitRedBg, 0.4); $colorGaugeLimitHigh: rgba($colorLimitRedBg, 0.4);
$colorGaugeLimitLow: $colorGaugeLimitHigh; $colorGaugeLimitLow: $colorGaugeLimitHigh;
$colorGaugeNeedle: rgba(#fff, 0);
$transitionTimeGauge: 150ms; // CSS transition time used to smooth needle gauge and meter value transitions $transitionTimeGauge: 150ms; // CSS transition time used to smooth needle gauge and meter value transitions
$marginGaugeMeterValue: 10%; // Margin between meter value bar and bounds edges $marginGaugeMeterValue: 10%; // Margin between meter value bar and bounds edges
$gaugeMeterValueShadow: rgba(255, 255, 255, 0);
// Time Strip and Lists // Time Strip and Lists
$colorPastBg: #444; $colorPastBg: #444;
@ -476,8 +489,8 @@ $colorGanttSelectedBorder: rgba(#fff, 0.3);
$colorTreeBg: transparent; $colorTreeBg: transparent;
$colorItemTreeHoverBg: rgba(#fff, 0.03); $colorItemTreeHoverBg: rgba(#fff, 0.03);
$colorItemTreeHoverFg: #fff; $colorItemTreeHoverFg: #fff;
$colorItemTreeIcon: $colorKey; // Used $colorItemTreeIcon: $colorKey;
$colorItemTreeIconHover: $colorItemTreeIcon; // Used $colorItemTreeIconHover: $colorItemTreeIcon;
$colorItemTreeFg: $colorA; $colorItemTreeFg: $colorA;
$colorItemTreeSelectedBg: $colorSelectedBg; $colorItemTreeSelectedBg: $colorSelectedBg;
$colorItemTreeSelectedFg: $colorItemTreeHoverFg; $colorItemTreeSelectedFg: $colorItemTreeHoverFg;
@ -554,4 +567,4 @@ $createBtnTextTransform: uppercase;
$colorDiscreteItemBg: rgba($colorBodyFg, 0.1); $colorDiscreteItemBg: rgba($colorBodyFg, 0.1);
$colorDiscreteItemBgHov: rgba($colorBodyFg, 0.2); $colorDiscreteItemBgHov: rgba($colorBodyFg, 0.2);
$colorDiscreteItemCurrentBg: rgba($colorOk, 0.3); $colorDiscreteItemCurrentBg: rgba($colorOk, 0.3);
$scrollContainer: $colorBodyBg; $scrollContainer: $colorBodyBg;

View File

@ -25,6 +25,7 @@
$heroFont: 'Helvetica Neue', Helvetica, Arial, sans-serif; $heroFont: 'Helvetica Neue', Helvetica, Arial, sans-serif;
$headerFont: $heroFont; $headerFont: $heroFont;
$bodyFont: $heroFont; $bodyFont: $heroFont;
$numericFont: $heroFont;
@mixin heroFont($size: 1em) { @mixin heroFont($size: 1em) {
font-family: $heroFont; font-family: $heroFont;
@ -64,6 +65,9 @@ $bodyFont: $heroFont;
background: $c; background: $c;
} }
@mixin telemetryView(){}
@mixin browseFrameBorder() {}
// Functions // Functions
@function buttonBg($c: $colorBtnBg) { @function buttonBg($c: $colorBtnBg) {
@return $c; @return $c;
@ -113,6 +117,11 @@ $filterHovSubtle: hue-rotate(-8deg) brightness(0.5) contrast(1.2);
$colorSelectedBg: pushBack($colorKey, 40%); $colorSelectedBg: pushBack($colorKey, 40%);
$colorSelectedFg: pullForward($colorBodyFg, 10%); $colorSelectedFg: pullForward($colorBodyFg, 10%);
// Body constants
$bodyBg: $colorBodyBg;
$bodyBgSize: cover;
$bodySize: 100%;
// Object labels // Object labels
$objectLabelTypeIconOpacity: 0.5; $objectLabelTypeIconOpacity: 0.5;
$objectLabelNameColorFg: darken($colorBodyFg, 10%); $objectLabelNameColorFg: darken($colorBodyFg, 10%);
@ -196,6 +205,8 @@ $browseFrameBorder: 1px solid $browseFrameColor; // Frames in Disp and Flex Layo
$browseSelectableShdwHov: rgba($colorBodyFg, 0.3) 0 0 3px; $browseSelectableShdwHov: rgba($colorBodyFg, 0.3) 0 0 3px;
$browseSelectedBorder: 1px solid rgba($colorBodyFg, 0.4); $browseSelectedBorder: 1px solid rgba($colorBodyFg, 0.4);
$filterItemHoverFg: brightness(0.9); $filterItemHoverFg: brightness(0.9);
$interiorMarginObjectFrameVertical: 0px;
$interiorMarginObjectFrameHorizontal: 3px;
// Missing Items // Missing Items
$filterItemMissing: contrast(0.2); $filterItemMissing: contrast(0.2);
@ -435,8 +446,10 @@ $colorGaugeMeterTextValue: $colorGaugeTextValue; // Meter text value, overlaid o
$colorGaugeRange: $colorBodyFg; // Range text color $colorGaugeRange: $colorBodyFg; // Range text color
$colorGaugeLimitHigh: rgba($colorLimitRedBg, 0.2); $colorGaugeLimitHigh: rgba($colorLimitRedBg, 0.2);
$colorGaugeLimitLow: $colorGaugeLimitHigh; $colorGaugeLimitLow: $colorGaugeLimitHigh;
$colorGaugeNeedle: rgba(#fff, 0);
$transitionTimeGauge: 150ms; // CSS transition time used to smooth needle gauge and meter value transitions $transitionTimeGauge: 150ms; // CSS transition time used to smooth needle gauge and meter value transitions
$marginGaugeMeterValue: 10%; // Margin between meter value bar and bounds edges $marginGaugeMeterValue: 10%; // Margin between meter value bar and bounds edges
$gaugeMeterValueShadow: rgba(255, 255, 255, 0);
// Time Strip and Lists // Time Strip and Lists
$colorPastBg: #f0f0f0; $colorPastBg: #f0f0f0;
@ -459,8 +472,8 @@ $colorGanttSelectedBorder: #fff;
$colorTreeBg: transparent; $colorTreeBg: transparent;
$colorItemTreeHoverBg: rgba(black, 0.07); $colorItemTreeHoverBg: rgba(black, 0.07);
$colorItemTreeHoverFg: pullForward($colorBodyFg, 10%); $colorItemTreeHoverFg: pullForward($colorBodyFg, 10%);
$colorItemTreeIcon: $colorKey; // Used $colorItemTreeIcon: $colorKey;
$colorItemTreeIconHover: $colorItemTreeIcon; // Used $colorItemTreeIconHover: $colorItemTreeIcon;
$colorItemTreeFg: $colorBodyFg; $colorItemTreeFg: $colorBodyFg;
$colorItemTreeSelectedBg: $colorSelectedBg; $colorItemTreeSelectedBg: $colorSelectedBg;
$colorItemTreeSelectedFg: $colorItemTreeHoverFg; $colorItemTreeSelectedFg: $colorItemTreeHoverFg;

View File

@ -30,6 +30,7 @@
box-shadow: $shdwMenuInner, $shdwMenu; box-shadow: $shdwMenuInner, $shdwMenu;
} }
background: $colorMenuBg; background: $colorMenuBg;
backdrop-filter: blur($formRowCtrlsH);
color: $colorMenuFg; color: $colorMenuFg;
text-shadow: $shdwMenuText; text-shadow: $shdwMenuText;
padding: $interiorMarginSm; padding: $interiorMarginSm;

View File

@ -161,7 +161,7 @@ a {
body, body,
html { html {
height: 100%; height: $bodySize;
width: 100%; width: 100%;
} }
@ -173,7 +173,9 @@ body {
-webkit-font-smoothing: subpixel-antialiased; -webkit-font-smoothing: subpixel-antialiased;
-moz-osx-font-smoothing: grayscale; -moz-osx-font-smoothing: grayscale;
@include bodyFont($fontBaseSize); @include bodyFont($fontBaseSize);
background-color: $colorBodyBg; // background-color: $colorBodyBg;
background: $bodyBg;
background-size: $bodyBgSize;
color: $colorBodyFg; color: $colorBodyFg;
} }

View File

@ -365,6 +365,7 @@ mct-plot {
left: $m; left: $m;
z-index: 9; z-index: 9;
&__reset { &__reset {
transition: right 100ms; transition: right 100ms;
top: $m; top: $m;

View File

@ -925,3 +925,8 @@
$c: $colorPausedBg; $c: $colorPausedBg;
border: 1px solid $c; border: 1px solid $c;
} }
// @mixin telemetryView(){
// border: 1px solid $colorBodyFg;
// border-radius: $controlCr;
// }

View File

@ -1,6 +1,10 @@
.c-so-view { .c-so-view {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
height: 100%;
// &__container{
// display: contents;
// }
/*************************** HEADER */ /*************************** HEADER */
&__header { &__header {
@ -29,13 +33,18 @@
&:not(.c-so-view--no-frame) { &:not(.c-so-view--no-frame) {
border: $browseFrameBorder; border: $browseFrameBorder;
padding: 0 $interiorMarginSm; @include browseFrameBorder;
padding: $interiorMarginObjectFrameVertical $interiorMarginObjectFrameHorizontal;
.is-editing & { .is-editing & {
background: rgba($colorBodyBg, 0.8); background: rgba($colorBodyBg, 0.8);
@include browseFrameBorder;
} }
} }
/*************************** FRAME CONTROLS */ /*************************** FRAME CONTROLS */
&__frame-controls { &__frame-controls {
display: flex; display: flex;

Binary file not shown.

After

Width:  |  Height:  |  Size: 303 KiB

View File

@ -30,6 +30,7 @@
flex-flow: column nowrap; flex-flow: column nowrap;
overflow: hidden; overflow: hidden;
&__drawer { &__drawer {
background: $drawerBg; background: $drawerBg;
display: flex; display: flex;
@ -289,7 +290,6 @@
flex: 1 1 auto !important; flex: 1 1 auto !important;
height: 100%; // Chrome 73 overflow bug fix height: 100%; // Chrome 73 overflow bug fix
overflow: auto; overflow: auto;
> * + * { > * + * {
margin-top: $interiorMargin; margin-top: $interiorMargin;
} }

View File

@ -5,6 +5,7 @@
display: flex; display: flex;
flex: 1 1 auto; flex: 1 1 auto;
overflow: hidden; overflow: hidden;
&--horizontal, &--horizontal,
> .l-pane { > .l-pane {