Compare commits
1 Commits
time-api-e
...
release/2.
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b5beffe1c9 |
@@ -2,7 +2,7 @@ version: 2.1
|
||||
executors:
|
||||
pw-focal-development:
|
||||
docker:
|
||||
- image: mcr.microsoft.com/playwright:v1.32.3-focal
|
||||
- image: mcr.microsoft.com/playwright:v1.29.0-focal
|
||||
environment:
|
||||
NODE_ENV: development # Needed to ensure 'dist' folder created and devDependencies installed
|
||||
PERCY_POSTINSTALL_BROWSER: 'true' # Needed to store the percy browser in cache deps
|
||||
@@ -162,7 +162,7 @@ jobs:
|
||||
steps:
|
||||
- build_and_install:
|
||||
node-version: <<parameters.node-version>>
|
||||
- run: npx playwright@1.32.3 install #Necessary for bare ubuntu machine
|
||||
- run: npx playwright@1.29.0 install #Necessary for bare ubuntu machine
|
||||
- run: |
|
||||
export $(cat src/plugins/persistence/couch/.env.ci | xargs)
|
||||
docker-compose -f src/plugins/persistence/couch/couchdb-compose.yaml up --detach
|
||||
|
||||
2
.github/dependabot.yml
vendored
2
.github/dependabot.yml
vendored
@@ -27,8 +27,6 @@ updates:
|
||||
update-types: ["version-update:semver-patch"]
|
||||
- dependency-name: "moment-timezone"
|
||||
update-types: ["version-update:semver-patch"]
|
||||
- dependency-name: "@types/lodash"
|
||||
update-types: ["version-update:semver-patch"]
|
||||
- package-ecosystem: "github-actions"
|
||||
directory: "/"
|
||||
schedule:
|
||||
|
||||
2
.github/workflows/e2e-couchdb.yml
vendored
2
.github/workflows/e2e-couchdb.yml
vendored
@@ -14,7 +14,7 @@ jobs:
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 'lts/gallium'
|
||||
- run: npx playwright@1.32.3 install
|
||||
- run: npx playwright@1.29.0 install
|
||||
- run: npm install
|
||||
- name: Start CouchDB Docker Container and Init with Setup Scripts
|
||||
run : |
|
||||
|
||||
4
.github/workflows/e2e-pr.yml
vendored
4
.github/workflows/e2e-pr.yml
vendored
@@ -29,10 +29,10 @@ jobs:
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: '16'
|
||||
- run: npx playwright@1.32.3 install
|
||||
- run: npx playwright@1.29.0 install
|
||||
- run: npx playwright install chrome-beta
|
||||
- run: npm install
|
||||
- run: npm run test:e2e:full -- --max-failures=40
|
||||
- run: npm run test:e2e:full -- --maxFailures=40
|
||||
- run: npm run cov:e2e:report || true
|
||||
- shell: bash
|
||||
env:
|
||||
|
||||
@@ -53,11 +53,7 @@ module.exports = merge(common, {
|
||||
},
|
||||
client: {
|
||||
progress: true,
|
||||
overlay: {
|
||||
// Disable overlay for runtime errors.
|
||||
// See: https://github.com/webpack/webpack-dev-server/issues/4771
|
||||
runtimeErrors: false
|
||||
}
|
||||
overlay: true
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -74,6 +74,7 @@ async function createDomainObjectWithDefaults(page, { type, name, parent = 'mine
|
||||
// Navigate to the parent object. This is necessary to create the object
|
||||
// in the correct location, such as a folder, layout, or plot.
|
||||
await page.goto(`${parentUrl}?hideTree=true`);
|
||||
await page.waitForLoadState('networkidle');
|
||||
|
||||
//Click the Create button
|
||||
await page.click('button:has-text("Create")');
|
||||
|
||||
@@ -9,7 +9,7 @@ const NUM_WORKERS = 2;
|
||||
|
||||
/** @type {import('@playwright/test').PlaywrightTestConfig} */
|
||||
const config = {
|
||||
retries: 2, //Retries 2 times for a total of 3 runs. When running sharded and with max-failures=5, this should ensure that flake is managed without failing the full suite
|
||||
retries: 2, //Retries 2 times for a total of 3 runs. When running sharded and with maxFailures = 5, this should ensure that flake is managed without failing the full suite
|
||||
testDir: 'tests',
|
||||
testIgnore: '**/*.perf.spec.js', //Ignore performance tests and define in playwright-perfromance.config.js
|
||||
timeout: 60 * 1000,
|
||||
|
||||
@@ -25,7 +25,7 @@ const { createDomainObjectWithDefaults, createNotification, expandEntireTree } =
|
||||
|
||||
test.describe('AppActions', () => {
|
||||
test('createDomainObjectsWithDefaults', async ({ page }) => {
|
||||
await page.goto('./', { waitUntil: 'domcontentloaded' });
|
||||
await page.goto('./', { waitUntil: 'networkidle' });
|
||||
|
||||
const e2eFolder = await createDomainObjectWithDefaults(page, {
|
||||
type: 'Folder',
|
||||
@@ -86,7 +86,7 @@ test.describe('AppActions', () => {
|
||||
});
|
||||
});
|
||||
test("createNotification", async ({ page }) => {
|
||||
await page.goto('./', { waitUntil: 'domcontentloaded' });
|
||||
await page.goto('./', { waitUntil: 'networkidle' });
|
||||
await createNotification(page, {
|
||||
message: 'Test info notification',
|
||||
severity: 'info'
|
||||
@@ -110,7 +110,7 @@ test.describe('AppActions', () => {
|
||||
await page.locator('[aria-label="Dismiss"]').click();
|
||||
});
|
||||
test('expandEntireTree', async ({ page }) => {
|
||||
await page.goto('./', { waitUntil: 'domcontentloaded' });
|
||||
await page.goto('./', { waitUntil: 'networkidle' });
|
||||
|
||||
const rootFolder = await createDomainObjectWithDefaults(page, {
|
||||
type: 'Folder'
|
||||
|
||||
@@ -32,7 +32,7 @@ test.describe('baseFixtures tests', () => {
|
||||
test('Verify that tests fail if console.error is thrown', async ({ page }) => {
|
||||
test.fail();
|
||||
//Go to baseURL
|
||||
await page.goto('./', { waitUntil: 'domcontentloaded' });
|
||||
await page.goto('./', { waitUntil: 'networkidle' });
|
||||
|
||||
//Verify that ../fixtures.js detects console log errors
|
||||
await Promise.all([
|
||||
@@ -43,7 +43,7 @@ test.describe('baseFixtures tests', () => {
|
||||
});
|
||||
test('Verify that tests pass if console.warn is thrown', async ({ page }) => {
|
||||
//Go to baseURL
|
||||
await page.goto('./', { waitUntil: 'domcontentloaded' });
|
||||
await page.goto('./', { waitUntil: 'networkidle' });
|
||||
|
||||
//Verify that ../fixtures.js detects console log errors
|
||||
await Promise.all([
|
||||
|
||||
@@ -63,7 +63,7 @@ test.describe('Renaming Timer Object', () => {
|
||||
let timer;
|
||||
test.beforeEach(async ({ page }) => {
|
||||
// Open a browser, navigate to the main page, and wait until all network events to resolve
|
||||
await page.goto('./', { waitUntil: 'domcontentloaded' });
|
||||
await page.goto('./', { waitUntil: 'networkidle' });
|
||||
|
||||
// We provide some helper functions in appActions like `createDomainObjectWithDefaults()`.
|
||||
// This example will create a Timer object with default properties, under the root folder:
|
||||
|
||||
@@ -36,7 +36,7 @@ const { test, expect } = require('../../pluginFixtures.js');
|
||||
|
||||
test('Generate Visual Test Data @localStorage', async ({ page, context }) => {
|
||||
//Go to baseURL
|
||||
await page.goto('./', { waitUntil: 'domcontentloaded' });
|
||||
await page.goto('./', { waitUntil: 'networkidle' });
|
||||
const overlayPlot = await createDomainObjectWithDefaults(page, { type: 'Overlay Plot' });
|
||||
|
||||
// click create button
|
||||
|
||||
@@ -30,7 +30,7 @@ test.describe('recycled_local_storage @localStorage', () => {
|
||||
//We may want to do some additional level of verification of this file. For now, we just verify that it exists and can be used in a test suite.
|
||||
test.use({ storageState: './e2e/test-data/recycled_local_storage.json' });
|
||||
test('Can use recycled_local_storage file', async ({ page }) => {
|
||||
await page.goto('./', { waitUntil: 'domcontentloaded' });
|
||||
await page.goto('./', { waitUntil: 'networkidle' });
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -29,7 +29,7 @@ const { test, expect } = require('../../baseFixtures.js');
|
||||
test.describe('Branding tests', () => {
|
||||
test('About Modal launches with basic branding properties', async ({ page }) => {
|
||||
// Go to baseURL
|
||||
await page.goto('./', { waitUntil: 'domcontentloaded' });
|
||||
await page.goto('./', { waitUntil: 'networkidle' });
|
||||
|
||||
// Click About button
|
||||
await page.click('.l-shell__app-logo');
|
||||
@@ -47,7 +47,7 @@ test.describe('Branding tests', () => {
|
||||
});
|
||||
test('Verify Links in About Modal @2p', async ({ page }) => {
|
||||
// Go to baseURL
|
||||
await page.goto('./', { waitUntil: 'domcontentloaded' });
|
||||
await page.goto('./', { waitUntil: 'networkidle' });
|
||||
|
||||
// Click About button
|
||||
await page.click('.l-shell__app-logo');
|
||||
|
||||
@@ -100,7 +100,7 @@ test.describe("CouchDB initialization with mocked responses @couchdb", () => {
|
||||
&& req.method() === 'GET');
|
||||
|
||||
// Go to baseURL.
|
||||
await page.goto('./', { waitUntil: 'domcontentloaded' });
|
||||
await page.goto('./', { waitUntil: 'networkidle' });
|
||||
|
||||
// Wait for both requests to resolve.
|
||||
await Promise.all([
|
||||
|
||||
@@ -30,7 +30,7 @@ const { createDomainObjectWithDefaults } = require('../../../appActions');
|
||||
test.describe('Example Event Generator CRUD Operations', () => {
|
||||
test('Can create a Test Event Generator and it results in the table View', async ({ page }) => {
|
||||
//Go to baseURL
|
||||
await page.goto('./', { waitUntil: 'domcontentloaded' });
|
||||
await page.goto('./', { waitUntil: 'networkidle' });
|
||||
|
||||
//Create a name for the object
|
||||
const newObjectName = 'Test Event Generator';
|
||||
|
||||
@@ -32,7 +32,7 @@ test.describe('Sine Wave Generator', () => {
|
||||
test.skip(browserName === 'firefox', 'This test needs to be updated to work with firefox');
|
||||
|
||||
//Go to baseURL
|
||||
await page.goto('./', { waitUntil: 'domcontentloaded' });
|
||||
await page.goto('./', { waitUntil: 'networkidle' });
|
||||
|
||||
//Click the Create button
|
||||
await page.click('button:has-text("Create")');
|
||||
|
||||
@@ -36,7 +36,7 @@ const imageFilePath = 'e2e/test-data/rick.jpg';
|
||||
test.describe('Form Validation Behavior', () => {
|
||||
test('Required Field indicators appear if title is empty and can be corrected', async ({ page }) => {
|
||||
//Go to baseURL
|
||||
await page.goto('./', { waitUntil: 'domcontentloaded' });
|
||||
await page.goto('./', { waitUntil: 'networkidle' });
|
||||
|
||||
await page.click('button:has-text("Create")');
|
||||
await page.click(':nth-match(:text("Folder"), 2)');
|
||||
@@ -77,7 +77,7 @@ test.describe('Form File Input Behavior', () => {
|
||||
});
|
||||
|
||||
test('Can select a JSON file type', async ({ page }) => {
|
||||
await page.goto('./', { waitUntil: 'domcontentloaded' });
|
||||
await page.goto('./', { waitUntil: 'networkidle' });
|
||||
|
||||
await page.getByRole('button', { name: ' Create ' }).click();
|
||||
await page.getByRole('menuitem', { name: 'JSON File Input Object' }).click();
|
||||
@@ -91,7 +91,7 @@ test.describe('Form File Input Behavior', () => {
|
||||
});
|
||||
|
||||
test('Can select an image file type', async ({ page }) => {
|
||||
await page.goto('./', { waitUntil: 'domcontentloaded' });
|
||||
await page.goto('./', { waitUntil: 'networkidle' });
|
||||
|
||||
await page.getByRole('button', { name: ' Create ' }).click();
|
||||
await page.getByRole('menuitem', { name: 'Image File Input Object' }).click();
|
||||
@@ -117,7 +117,7 @@ test.describe('Persistence operations @addInit', () => {
|
||||
type: 'issue',
|
||||
description: 'https://github.com/nasa/openmct/issues/4323'
|
||||
});
|
||||
await page.goto('./', { waitUntil: 'domcontentloaded' });
|
||||
await page.goto('./', { waitUntil: 'networkidle' });
|
||||
|
||||
await page.click('button:has-text("Create")');
|
||||
|
||||
@@ -138,7 +138,7 @@ test.describe('Persistence operations @couchdb', () => {
|
||||
description: 'https://github.com/nasa/openmct/issues/5616'
|
||||
});
|
||||
|
||||
await page.goto('./', { waitUntil: 'domcontentloaded' });
|
||||
await page.goto('./', { waitUntil: 'networkidle' });
|
||||
|
||||
// Create a new 'Clock' object with default settings
|
||||
const clock = await createDomainObjectWithDefaults(page, {
|
||||
|
||||
@@ -36,7 +36,7 @@ test.describe('Persistence operations @addInit', () => {
|
||||
});
|
||||
|
||||
test('Non-persistable objects should not show persistence related actions', async ({ page }) => {
|
||||
await page.goto('./', { waitUntil: 'domcontentloaded' });
|
||||
await page.goto('./', { waitUntil: 'networkidle' });
|
||||
|
||||
await page.locator('text=Persistence Testing').first().click({
|
||||
button: 'right'
|
||||
|
||||
@@ -35,7 +35,7 @@ test.describe('Notifications List', () => {
|
||||
});
|
||||
|
||||
// Go to baseURL
|
||||
await page.goto('./', { waitUntil: 'domcontentloaded' });
|
||||
await page.goto('./', { waitUntil: 'networkidle' });
|
||||
|
||||
// Create an error notification with the message "Error message"
|
||||
await createNotification(page, {
|
||||
@@ -80,7 +80,7 @@ test.describe('Notification Overlay', () => {
|
||||
});
|
||||
|
||||
// Go to baseURL
|
||||
await page.goto('./', { waitUntil: 'domcontentloaded' });
|
||||
await page.goto('./', { waitUntil: 'networkidle' });
|
||||
|
||||
// Create a new Display Layout object
|
||||
await createDomainObjectWithDefaults(page, { type: 'Display Layout' });
|
||||
|
||||
@@ -29,7 +29,7 @@ const { getPreciseDuration } = require('../../../../src/utils/duration');
|
||||
test.describe("Gantt Chart", () => {
|
||||
let ganttChart;
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await page.goto('./', { waitUntil: 'domcontentloaded' });
|
||||
await page.goto('./', { waitUntil: 'networkidle' });
|
||||
ganttChart = await createDomainObjectWithDefaults(page, {
|
||||
type: 'Gantt Chart'
|
||||
});
|
||||
|
||||
@@ -27,7 +27,7 @@ const { assertPlanActivities } = require('../../../helper/planningUtils');
|
||||
test.describe("Plan", () => {
|
||||
let plan;
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await page.goto('./', { waitUntil: 'domcontentloaded' });
|
||||
await page.goto('./', { waitUntil: 'networkidle' });
|
||||
plan = await createPlanFromJSON(page, {
|
||||
json: testPlan1
|
||||
});
|
||||
|
||||
@@ -80,7 +80,7 @@ test.describe("Time Strip", () => {
|
||||
const activityBounds = page.locator('.activity-bounds');
|
||||
|
||||
// Goto baseURL
|
||||
await page.goto('./', { waitUntil: 'domcontentloaded' });
|
||||
await page.goto('./', { waitUntil: 'networkidle' });
|
||||
|
||||
const timestrip = await test.step("Create a Time Strip", async () => {
|
||||
const createdTimeStrip = await createDomainObjectWithDefaults(page, { type: 'Time Strip' });
|
||||
|
||||
@@ -34,7 +34,7 @@ test.describe('Clock Generator CRUD Operations', () => {
|
||||
description: 'https://github.com/nasa/openmct/issues/4878'
|
||||
});
|
||||
//Go to baseURL
|
||||
await page.goto('./', { waitUntil: 'domcontentloaded' });
|
||||
await page.goto('./', { waitUntil: 'networkidle' });
|
||||
|
||||
//Click the Create button
|
||||
await page.click('button:has-text("Create")');
|
||||
|
||||
@@ -37,7 +37,7 @@ test.describe.serial('Condition Set CRUD Operations on @localStorage', () => {
|
||||
//TODO: This needs to be refactored
|
||||
const context = await browser.newContext();
|
||||
const page = await context.newPage();
|
||||
await page.goto('./', { waitUntil: 'domcontentloaded' });
|
||||
await page.goto('./', { waitUntil: 'networkidle' });
|
||||
await page.click('button:has-text("Create")');
|
||||
|
||||
await page.locator('li[role="menuitem"]:has-text("Condition Set")').click();
|
||||
@@ -148,7 +148,7 @@ test.describe.serial('Condition Set CRUD Operations on @localStorage', () => {
|
||||
});
|
||||
test('condition set object can be deleted by Search Tree Actions menu on @localStorage', async ({ page }) => {
|
||||
//Navigate to baseURL
|
||||
await page.goto('./', { waitUntil: 'domcontentloaded' });
|
||||
await page.goto('./', { waitUntil: 'networkidle' });
|
||||
|
||||
//Assertions on loaded Condition Set in main view. This is a stateful transition step after page.goto()
|
||||
await expect(page.locator('a:has-text("Unnamed Condition Set Condition Set") >> nth=0')).toBeVisible();
|
||||
@@ -182,7 +182,7 @@ test.describe.serial('Condition Set CRUD Operations on @localStorage', () => {
|
||||
test.describe('Basic Condition Set Use', () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
// Open a browser, navigate to the main page, and wait until all network events to resolve
|
||||
await page.goto('./', { waitUntil: 'domcontentloaded' });
|
||||
await page.goto('./', { waitUntil: 'networkidle' });
|
||||
});
|
||||
test('Can add a condition', async ({ page }) => {
|
||||
// Create a new condition set
|
||||
@@ -247,7 +247,7 @@ test.describe('Basic Condition Set Use', () => {
|
||||
});
|
||||
test('ConditionSet should output blank instead of the default value', async ({ page }) => {
|
||||
//Navigate to baseURL
|
||||
await page.goto('./', { waitUntil: 'domcontentloaded' });
|
||||
await page.goto('./', { waitUntil: 'networkidle' });
|
||||
|
||||
//Click the Create button
|
||||
await page.click('button:has-text("Create")');
|
||||
|
||||
@@ -27,7 +27,7 @@ test.describe('Display Layout', () => {
|
||||
/** @type {import('../../../../appActions').CreatedObjectInfo} */
|
||||
let sineWaveObject;
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await page.goto('./', { waitUntil: 'domcontentloaded' });
|
||||
await page.goto('./', { waitUntil: 'networkidle' });
|
||||
await setRealTimeMode(page);
|
||||
|
||||
// Create Sine Wave Generator
|
||||
|
||||
@@ -27,7 +27,7 @@ test.describe('Flexible Layout', () => {
|
||||
let sineWaveObject;
|
||||
let clockObject;
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await page.goto('./', { waitUntil: 'domcontentloaded' });
|
||||
await page.goto('./', { waitUntil: 'networkidle' });
|
||||
|
||||
// Create Sine Wave Generator
|
||||
sineWaveObject = await createDomainObjectWithDefaults(page, {
|
||||
|
||||
@@ -31,7 +31,7 @@ const uuid = require('uuid').v4;
|
||||
test.describe('Gauge', () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
// Open a browser, navigate to the main page, and wait until all networkevents to resolve
|
||||
await page.goto('./', { waitUntil: 'domcontentloaded' });
|
||||
await page.goto('./', { waitUntil: 'networkidle' });
|
||||
});
|
||||
|
||||
test('Can add and remove telemetry sources @unstable', async ({ page }) => {
|
||||
|
||||
@@ -37,7 +37,7 @@ const thumbnailUrlParamsRegexp = /\?w=100&h=100/;
|
||||
test.describe('Example Imagery Object', () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
//Go to baseURL
|
||||
await page.goto('./', { waitUntil: 'domcontentloaded' });
|
||||
await page.goto('./', { waitUntil: 'networkidle' });
|
||||
|
||||
// Create a default 'Example Imagery' object
|
||||
const exampleImagery = await createDomainObjectWithDefaults(page, { type: 'Example Imagery' });
|
||||
@@ -178,7 +178,7 @@ test.describe('Example Imagery in Display Layout', () => {
|
||||
let displayLayout;
|
||||
test.beforeEach(async ({ page }) => {
|
||||
// Go to baseURL
|
||||
await page.goto('./', { waitUntil: 'domcontentloaded' });
|
||||
await page.goto('./', { waitUntil: 'networkidle' });
|
||||
|
||||
displayLayout = await createDomainObjectWithDefaults(page, { type: 'Display Layout' });
|
||||
await page.goto(displayLayout.url);
|
||||
@@ -317,7 +317,7 @@ test.describe('Example Imagery in Display Layout', () => {
|
||||
test.describe('Example Imagery in Flexible layout', () => {
|
||||
let flexibleLayout;
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await page.goto('./', { waitUntil: 'domcontentloaded' });
|
||||
await page.goto('./', { waitUntil: 'networkidle' });
|
||||
|
||||
flexibleLayout = await createDomainObjectWithDefaults(page, { type: 'Flexible Layout' });
|
||||
await page.goto(flexibleLayout.url);
|
||||
@@ -359,7 +359,7 @@ test.describe('Example Imagery in Flexible layout', () => {
|
||||
test.describe('Example Imagery in Tabs View', () => {
|
||||
let tabsView;
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await page.goto('./', { waitUntil: 'domcontentloaded' });
|
||||
await page.goto('./', { waitUntil: 'networkidle' });
|
||||
|
||||
tabsView = await createDomainObjectWithDefaults(page, { type: 'Tabs View' });
|
||||
await page.goto(tabsView.url);
|
||||
@@ -395,7 +395,7 @@ test.describe('Example Imagery in Tabs View', () => {
|
||||
test.describe('Example Imagery in Time Strip', () => {
|
||||
let timeStripObject;
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await page.goto('./', { waitUntil: 'domcontentloaded' });
|
||||
await page.goto('./', { waitUntil: 'networkidle' });
|
||||
timeStripObject = await createDomainObjectWithDefaults(page, {
|
||||
type: 'Time Strip'
|
||||
});
|
||||
|
||||
@@ -25,7 +25,7 @@ const { createDomainObjectWithDefaults, setStartOffset, setFixedTimeMode, setRea
|
||||
|
||||
test.describe('Testing LAD table configuration', () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await page.goto('./', { waitUntil: 'domcontentloaded' });
|
||||
await page.goto('./', { waitUntil: 'networkidle' });
|
||||
|
||||
// Create LAD table
|
||||
const ladTable = await createDomainObjectWithDefaults(page, {
|
||||
@@ -139,7 +139,7 @@ test.describe('Testing LAD table configuration', () => {
|
||||
test.describe('Testing LAD table @unstable', () => {
|
||||
let sineWaveObject;
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await page.goto('./', { waitUntil: 'domcontentloaded' });
|
||||
await page.goto('./', { waitUntil: 'networkidle' });
|
||||
await setRealTimeMode(page);
|
||||
|
||||
// Create Sine Wave Generator
|
||||
|
||||
@@ -72,7 +72,7 @@ test.describe('Notebook section tests', () => {
|
||||
//The following test cases are associated with Notebook Sections
|
||||
test.beforeEach(async ({ page }) => {
|
||||
//Navigate to baseURL
|
||||
await page.goto('./', { waitUntil: 'domcontentloaded' });
|
||||
await page.goto('./', { waitUntil: 'networkidle' });
|
||||
|
||||
// Create Notebook
|
||||
await createDomainObjectWithDefaults(page, {
|
||||
@@ -133,7 +133,7 @@ test.describe('Notebook page tests', () => {
|
||||
//The following test cases are associated with Notebook Pages
|
||||
test.beforeEach(async ({ page }) => {
|
||||
//Navigate to baseURL
|
||||
await page.goto('./', { waitUntil: 'domcontentloaded' });
|
||||
await page.goto('./', { waitUntil: 'networkidle' });
|
||||
|
||||
// Create Notebook
|
||||
await createDomainObjectWithDefaults(page, {
|
||||
@@ -201,7 +201,7 @@ test.describe('Notebook page tests', () => {
|
||||
test.describe('Notebook export tests', () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
//Navigate to baseURL
|
||||
await page.goto('./', { waitUntil: 'domcontentloaded' });
|
||||
await page.goto('./', { waitUntil: 'networkidle' });
|
||||
|
||||
// Create Notebook
|
||||
await createDomainObjectWithDefaults(page, {
|
||||
@@ -243,7 +243,7 @@ test.describe('Notebook entry tests', () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
// eslint-disable-next-line no-undef
|
||||
await page.addInitScript({ path: path.join(__dirname, '../../../../helper/', 'addInitNotebookWithUrls.js') });
|
||||
await page.goto('./', { waitUntil: 'domcontentloaded' });
|
||||
await page.goto('./', { waitUntil: 'networkidle' });
|
||||
|
||||
notebookObject = await createDomainObjectWithDefaults(page, {
|
||||
type: NOTEBOOK_NAME
|
||||
@@ -260,7 +260,7 @@ test.describe('Notebook entry tests', () => {
|
||||
});
|
||||
test('When an object is dropped into a notebook, a new entry is created and it should be focused @unstable', async ({ page }) => {
|
||||
// Create Overlay Plot
|
||||
const overlayPlot = await createDomainObjectWithDefaults(page, {
|
||||
await createDomainObjectWithDefaults(page, {
|
||||
type: 'Overlay Plot'
|
||||
});
|
||||
|
||||
@@ -270,17 +270,17 @@ test.describe('Notebook entry tests', () => {
|
||||
// Reveal the notebook in the tree
|
||||
await page.getByTitle('Show selected item in tree').click();
|
||||
|
||||
await page.dragAndDrop(`role=treeitem[name=/${overlayPlot.name}/]`, '.c-notebook__drag-area');
|
||||
await page.dragAndDrop('role=treeitem[name=/Dropped Overlay Plot/]', '.c-notebook__drag-area');
|
||||
|
||||
const embed = page.locator('.c-ne__embed__link');
|
||||
const embedName = await embed.textContent();
|
||||
|
||||
await expect(embed).toHaveClass(/icon-plot-overlay/);
|
||||
expect(embedName).toBe(overlayPlot.name);
|
||||
expect(embedName).toBe('Dropped Overlay Plot');
|
||||
});
|
||||
test('When an object is dropped into a notebooks existing entry, it should be focused @unstable', async ({ page }) => {
|
||||
// Create Overlay Plot
|
||||
const overlayPlot = await createDomainObjectWithDefaults(page, {
|
||||
await createDomainObjectWithDefaults(page, {
|
||||
type: 'Overlay Plot'
|
||||
});
|
||||
|
||||
@@ -291,14 +291,14 @@ test.describe('Notebook entry tests', () => {
|
||||
await page.getByTitle('Show selected item in tree').click();
|
||||
|
||||
await nbUtils.enterTextEntry(page, 'Entry to drop into');
|
||||
await page.dragAndDrop(`role=treeitem[name=/${overlayPlot.name}/]`, 'text=Entry to drop into');
|
||||
await page.dragAndDrop('role=treeitem[name=/Dropped Overlay Plot/]', 'text=Entry to drop into');
|
||||
|
||||
const existingEntry = page.locator('.c-ne__content', { has: page.locator('text="Entry to drop into"') });
|
||||
const embed = existingEntry.locator('.c-ne__embed__link');
|
||||
const embedName = await embed.textContent();
|
||||
|
||||
await expect(embed).toHaveClass(/icon-plot-overlay/);
|
||||
expect(embedName).toBe(overlayPlot.name);
|
||||
expect(embedName).toBe('Dropped Overlay Plot');
|
||||
});
|
||||
test.fixme('new entries persist through navigation events without save', async ({ page }) => {});
|
||||
test('previous and new entries can be deleted', async ({ page }) => {
|
||||
|
||||
@@ -66,7 +66,7 @@ test.describe('Snapshot Menu tests', () => {
|
||||
test.describe('Snapshot Container tests', () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
//Navigate to baseURL
|
||||
await page.goto('./', { waitUntil: 'domcontentloaded' });
|
||||
await page.goto('./', { waitUntil: 'networkidle' });
|
||||
|
||||
// Create Notebook
|
||||
// const notebook = await createDomainObjectWithDefaults(page, {
|
||||
|
||||
@@ -33,7 +33,7 @@ test.describe('Notebook Tests with CouchDB @couchdb', () => {
|
||||
|
||||
test.beforeEach(async ({ page }) => {
|
||||
//Navigate to baseURL
|
||||
await page.goto('./', { waitUntil: 'domcontentloaded' });
|
||||
await page.goto('./', { waitUntil: 'networkidle' });
|
||||
|
||||
// Create Notebook
|
||||
testNotebook = await createDomainObjectWithDefaults(page, {type: 'Notebook' });
|
||||
|
||||
@@ -202,7 +202,7 @@ test.describe('can export restricted notebook as text', () => {
|
||||
async function startAndAddRestrictedNotebookObject(page) {
|
||||
// eslint-disable-next-line no-undef
|
||||
await page.addInitScript({ path: path.join(__dirname, '../../../../helper/', 'addInitRestrictedNotebook.js') });
|
||||
await page.goto('./', { waitUntil: 'domcontentloaded' });
|
||||
await page.goto('./', { waitUntil: 'networkidle' });
|
||||
|
||||
return createDomainObjectWithDefaults(page, { type: CUSTOM_NAME });
|
||||
}
|
||||
|
||||
@@ -80,7 +80,7 @@ async function createNotebookEntryAndTags(page, iterations = 1) {
|
||||
test.describe('Tagging in Notebooks @addInit', () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
//Go to baseURL
|
||||
await page.goto('./', { waitUntil: 'domcontentloaded' });
|
||||
await page.goto('./', { waitUntil: 'networkidle' });
|
||||
});
|
||||
test('Can load tags', async ({ page }) => {
|
||||
await createNotebookAndEntry(page);
|
||||
@@ -109,7 +109,7 @@ test.describe('Tagging in Notebooks @addInit', () => {
|
||||
await expect(page.locator('[aria-label="Autocomplete Options"]')).toContainText("Drilling");
|
||||
});
|
||||
test('Can add tags with blank entry', async ({ page }) => {
|
||||
await createDomainObjectWithDefaults(page, { type: 'Notebook' });
|
||||
createDomainObjectWithDefaults(page, { type: 'Notebook' });
|
||||
await selectInspectorTab(page, 'Annotations');
|
||||
|
||||
await nbUtils.enterTextEntry(page, '');
|
||||
@@ -214,7 +214,7 @@ test.describe('Tagging in Notebooks @addInit', () => {
|
||||
await page.locator('button[title="More options"]').click();
|
||||
await page.locator('li[title="Remove this object from its containing object."]').click();
|
||||
await page.locator('button:has-text("OK")').click();
|
||||
await page.goto('./', { waitUntil: 'domcontentloaded' });
|
||||
await page.goto('./', { waitUntil: 'networkidle' });
|
||||
|
||||
await page.locator('[aria-label="OpenMCT Search"] input[type="search"]').fill('Unnamed');
|
||||
await expect(page.locator('text=No results found')).toBeVisible();
|
||||
@@ -225,13 +225,37 @@ test.describe('Tagging in Notebooks @addInit', () => {
|
||||
});
|
||||
test('Tags persist across reload', async ({ page }) => {
|
||||
//Go to baseURL
|
||||
await page.goto('./', { waitUntil: 'domcontentloaded' });
|
||||
await page.goto('./', { waitUntil: 'networkidle' });
|
||||
|
||||
const clock = await createDomainObjectWithDefaults(page, { type: 'Clock' });
|
||||
|
||||
const ITERATIONS = 4;
|
||||
const notebook = await createNotebookEntryAndTags(page, ITERATIONS);
|
||||
await page.goto(notebook.url);
|
||||
|
||||
// Verify tags are present
|
||||
for (let iteration = 0; iteration < ITERATIONS; iteration++) {
|
||||
const entryLocator = `[aria-label="Notebook Entry"] >> nth = ${iteration}`;
|
||||
await expect(page.locator(entryLocator)).toContainText("Science");
|
||||
await expect(page.locator(entryLocator)).toContainText("Driving");
|
||||
}
|
||||
|
||||
await Promise.all([
|
||||
page.waitForNavigation(),
|
||||
page.goto('./#/browse/mine?hideTree=false'),
|
||||
page.click('.c-disclosure-triangle')
|
||||
]);
|
||||
|
||||
const treePane = page.getByRole('tree', {
|
||||
name: 'Main Tree'
|
||||
});
|
||||
// Click Clock
|
||||
await treePane.getByRole('treeitem', {
|
||||
name: clock.name
|
||||
}).click();
|
||||
// Click Notebook
|
||||
await page.getByRole('treeitem', {
|
||||
name: notebook.name
|
||||
}).click();
|
||||
|
||||
for (let iteration = 0; iteration < ITERATIONS; iteration++) {
|
||||
const entryLocator = `[aria-label="Notebook Entry"] >> nth = ${iteration}`;
|
||||
await expect(page.locator(entryLocator)).toContainText("Science");
|
||||
@@ -239,9 +263,14 @@ test.describe('Tagging in Notebooks @addInit', () => {
|
||||
}
|
||||
|
||||
//Reload Page
|
||||
await page.reload({ waitUntil: 'domcontentloaded' });
|
||||
await Promise.all([
|
||||
page.reload(),
|
||||
page.waitForLoadState('networkidle')
|
||||
]);
|
||||
|
||||
// Click Notebook
|
||||
await page.click(`text="${notebook.name}"`);
|
||||
|
||||
// Verify tags persist across reload
|
||||
for (let iteration = 0; iteration < ITERATIONS; iteration++) {
|
||||
const entryLocator = `[aria-label="Notebook Entry"] >> nth = ${iteration}`;
|
||||
await expect(page.locator(entryLocator)).toContainText("Science");
|
||||
|
||||
@@ -44,7 +44,7 @@ test.describe('Operator Status', () => {
|
||||
await page.addInitScript({ path: path.join(__dirname, '../../../../helper/', 'addInitExampleUser.js')});
|
||||
// eslint-disable-next-line no-undef
|
||||
await page.addInitScript({ path: path.join(__dirname, '../../../../helper/', 'addInitOperatorStatus.js')});
|
||||
await page.goto('./', { waitUntil: 'domcontentloaded' });
|
||||
await page.goto('./', { waitUntil: 'networkidle' });
|
||||
});
|
||||
|
||||
// verify that operator status is visible
|
||||
|
||||
@@ -40,7 +40,7 @@ test.describe('Autoscale', () => {
|
||||
//This is necessary due to the size of the test suite.
|
||||
test.slow();
|
||||
|
||||
await page.goto('./', { waitUntil: 'domcontentloaded' });
|
||||
await page.goto('./', { waitUntil: 'networkidle' });
|
||||
|
||||
await setTimeRange(page);
|
||||
|
||||
@@ -73,7 +73,6 @@ test.describe('Autoscale', () => {
|
||||
const canvas = page.locator('canvas').nth(1);
|
||||
|
||||
await canvas.hover({trial: true});
|
||||
await expect(page.locator('.js-series-data-loaded')).toBeVisible();
|
||||
|
||||
expect.soft(await canvas.screenshot()).toMatchSnapshot('autoscale-canvas-prepan.png', { animations: 'disabled' });
|
||||
|
||||
@@ -173,7 +172,7 @@ async function createSinewaveOverlayPlot(page, myItemsFolderName) {
|
||||
*/
|
||||
async function turnOffAutoscale(page) {
|
||||
// uncheck autoscale
|
||||
await page.getByRole('checkbox', { name: 'Auto scale' }).uncheck();
|
||||
await page.getByRole('listitem').filter({ hasText: 'Auto scale' }).getByRole('checkbox').uncheck();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -183,9 +182,14 @@ async function turnOffAutoscale(page) {
|
||||
*/
|
||||
async function setUserDefinedMinAndMax(page, min, max) {
|
||||
// set minimum value
|
||||
await page.getByRole('spinbutton').first().fill(min);
|
||||
const minRangeInput = page.getByRole('listitem').filter({ hasText: 'Minimum Value' }).locator('input[type="number"]');
|
||||
await minRangeInput.click();
|
||||
await minRangeInput.fill(min);
|
||||
|
||||
// set maximum value
|
||||
await page.getByRole('spinbutton').nth(1).fill(max);
|
||||
const maxRangeInput = page.getByRole('listitem').filter({ hasText: 'Maximum Value' }).locator('input[type="number"]');
|
||||
await maxRangeInput.click();
|
||||
await maxRangeInput.fill(max);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -76,7 +76,7 @@ test.describe('Log plot tests', () => {
|
||||
*/
|
||||
async function makeOverlayPlot(page, myItemsFolderName) {
|
||||
// fresh page with time range from 2022-03-29 22:00:00.000Z to 2022-03-29 22:00:30.000Z
|
||||
await page.goto('./', { waitUntil: 'domcontentloaded' });
|
||||
await page.goto('./', { waitUntil: 'networkidle' });
|
||||
|
||||
// Set a specific time range for consistency, otherwise it will change
|
||||
// on every test to a range based on the current time.
|
||||
@@ -147,7 +147,7 @@ async function makeOverlayPlot(page, myItemsFolderName) {
|
||||
* @param {import('@playwright/test').Page} page
|
||||
*/
|
||||
async function testRegularTicks(page) {
|
||||
const yTicks = page.locator('.gl-plot-y-tick-label');
|
||||
const yTicks = await page.locator('.gl-plot-y-tick-label');
|
||||
expect(await yTicks.count()).toBe(7);
|
||||
await expect(yTicks.nth(0)).toHaveText('-2');
|
||||
await expect(yTicks.nth(1)).toHaveText('0');
|
||||
@@ -162,7 +162,7 @@ async function testRegularTicks(page) {
|
||||
* @param {import('@playwright/test').Page} page
|
||||
*/
|
||||
async function testLogTicks(page) {
|
||||
const yTicks = page.locator('.gl-plot-y-tick-label');
|
||||
const yTicks = await page.locator('.gl-plot-y-tick-label');
|
||||
expect(await yTicks.count()).toBe(9);
|
||||
await expect(yTicks.nth(0)).toHaveText('-2.98');
|
||||
await expect(yTicks.nth(1)).toHaveText('-1.51');
|
||||
@@ -180,24 +180,27 @@ async function testLogTicks(page) {
|
||||
*/
|
||||
async function enableEditMode(page) {
|
||||
// turn on edit mode
|
||||
await page.getByRole('button', { name: 'Edit' }).click();
|
||||
await expect(page.getByRole('button', { name: 'Save' })).toBeVisible();
|
||||
await page.locator('text=Unnamed Overlay Plot Snapshot >> button').nth(3).click();
|
||||
await expect(await page.locator('text=Snapshot Save and Finish Editing Save and Continue Editing >> button').nth(1)).toBeVisible();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {import('@playwright/test').Page} page
|
||||
*/
|
||||
async function enableLogMode(page) {
|
||||
await expect(page.getByRole('checkbox', { name: 'Log mode' })).not.toBeChecked();
|
||||
await page.getByRole('checkbox', { name: 'Log mode' }).check();
|
||||
// turn on log mode
|
||||
await expect(page.getByRole('listitem').filter({ hasText: 'Log mode' }).getByRole('checkbox')).not.toBeChecked();
|
||||
await page.getByRole('listitem').filter({ hasText: 'Log mode' }).getByRole('checkbox').check();
|
||||
// await page.locator('text=Y Axis Label Log mode Auto scale Padding >> input[type="checkbox"]').first().check();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {import('@playwright/test').Page} page
|
||||
*/
|
||||
async function disableLogMode(page) {
|
||||
await expect(page.getByRole('checkbox', { name: 'Log mode' })).toBeChecked();
|
||||
await page.getByRole('checkbox', { name: 'Log mode' }).uncheck();
|
||||
// turn off log mode
|
||||
await expect(page.getByRole('listitem').filter({ hasText: 'Log mode' }).getByRole('checkbox')).toBeChecked();
|
||||
await page.getByRole('listitem').filter({ hasText: 'Log mode' }).getByRole('checkbox').uncheck();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -84,7 +84,7 @@ test.describe('Handle missing object for plots', () => {
|
||||
*/
|
||||
async function makeStackedPlot(page, myItemsFolderName) {
|
||||
// fresh page with time range from 2022-03-29 22:00:00.000Z to 2022-03-29 22:00:30.000Z
|
||||
await page.goto('./', { waitUntil: 'domcontentloaded' });
|
||||
await page.goto('./', { waitUntil: 'networkidle' });
|
||||
|
||||
// create stacked plot
|
||||
await page.locator('button.c-create-button').click();
|
||||
|
||||
@@ -30,7 +30,7 @@ const { createDomainObjectWithDefaults, selectInspectorTab } = require('../../..
|
||||
|
||||
test.describe('Overlay Plot', () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await page.goto('./', { waitUntil: 'domcontentloaded' });
|
||||
await page.goto('./', { waitUntil: 'networkidle' });
|
||||
});
|
||||
|
||||
test('Plot legend color is in sync with plot series color', async ({ page }) => {
|
||||
@@ -214,17 +214,12 @@ test.describe('Overlay Plot', () => {
|
||||
});
|
||||
|
||||
await page.goto(overlayPlot.url);
|
||||
// Wait for plot series data to load and be drawn
|
||||
await expect(page.locator('.js-series-data-loaded')).toBeVisible();
|
||||
await page.click('button[title="Edit"]');
|
||||
|
||||
await selectInspectorTab(page, 'Elements');
|
||||
|
||||
await page.locator(`#inspector-elements-tree >> text=${swgA.name}`).click();
|
||||
|
||||
// Wait for "View Large" plot series data to load and be drawn
|
||||
await expect(page.locator('.c-overlay .js-series-data-loaded')).toBeVisible();
|
||||
|
||||
const plotPixelSize = await getCanvasPixelsWithData(page);
|
||||
expect(plotPixelSize).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
@@ -33,7 +33,7 @@ test.describe('Plot Integrity Testing @unstable', () => {
|
||||
|
||||
test.beforeEach(async ({ page }) => {
|
||||
//Open a browser, navigate to the main page, and wait until all networkevents to resolve
|
||||
await page.goto('./', { waitUntil: 'domcontentloaded' });
|
||||
await page.goto('./', { waitUntil: 'networkidle' });
|
||||
sineWaveGeneratorObject = await createDomainObjectWithDefaults(page, { type: 'Sine Wave Generator' });
|
||||
});
|
||||
|
||||
|
||||
@@ -33,7 +33,7 @@ test.describe('Scatter Plot', () => {
|
||||
|
||||
test.beforeEach(async ({ page }) => {
|
||||
// Open a browser, navigate to the main page, and wait until all networkevents to resolve
|
||||
await page.goto('./', { waitUntil: 'domcontentloaded' });
|
||||
await page.goto('./', { waitUntil: 'networkidle' });
|
||||
|
||||
// Create the Scatter Plot
|
||||
scatterPlot = await createDomainObjectWithDefaults(page, { type: 'Scatter Plot' });
|
||||
|
||||
@@ -36,7 +36,7 @@ test.describe('Stacked Plot', () => {
|
||||
|
||||
test.beforeEach(async ({ page }) => {
|
||||
// Open a browser, navigate to the main page, and wait until all networkevents to resolve
|
||||
await page.goto('./', { waitUntil: 'domcontentloaded' });
|
||||
await page.goto('./', { waitUntil: 'networkidle' });
|
||||
|
||||
stackedPlot = await createDomainObjectWithDefaults(page, {
|
||||
type: "Stacked Plot"
|
||||
@@ -138,7 +138,7 @@ test.describe('Stacked Plot', () => {
|
||||
|
||||
// Assert that the inspector shows the Y Axis properties for swgA
|
||||
await expect(page.locator('[aria-label="Plot Series Properties"] >> h2')).toContainText("Plot Series");
|
||||
await expect(page.getByRole('heading', { name: "Y Axis" })).toBeVisible();
|
||||
await expect(page.getByRole('list', { name: "Y Axis Properties" }).locator("h2")).toContainText("Y Axis");
|
||||
await expect(page.locator('[aria-label="Plot Series Properties"] .c-object-label')).toContainText(swgA.name);
|
||||
|
||||
// Click on the 2nd plot
|
||||
@@ -146,7 +146,7 @@ test.describe('Stacked Plot', () => {
|
||||
|
||||
// Assert that the inspector shows the Y Axis properties for swgB
|
||||
await expect(page.locator('[aria-label="Plot Series Properties"] >> h2')).toContainText("Plot Series");
|
||||
await expect(page.getByRole('heading', { name: 'Y Axis' })).toBeVisible();
|
||||
await expect(page.getByRole('list', { name: "Y Axis Properties" }).locator("h2")).toContainText("Y Axis");
|
||||
await expect(page.locator('[aria-label="Plot Series Properties"] .c-object-label')).toContainText(swgB.name);
|
||||
|
||||
// Click on the 3rd plot
|
||||
@@ -154,7 +154,7 @@ test.describe('Stacked Plot', () => {
|
||||
|
||||
// Assert that the inspector shows the Y Axis properties for swgC
|
||||
await expect(page.locator('[aria-label="Plot Series Properties"] >> h2')).toContainText("Plot Series");
|
||||
await expect(page.getByRole('heading', { name: 'Y Axis' })).toBeVisible();
|
||||
await expect(page.getByRole('list', { name: "Y Axis Properties" }).locator("h2")).toContainText("Y Axis");
|
||||
await expect(page.locator('[aria-label="Plot Series Properties"] .c-object-label')).toContainText(swgC.name);
|
||||
|
||||
// Go into edit mode
|
||||
@@ -167,7 +167,7 @@ test.describe('Stacked Plot', () => {
|
||||
|
||||
// Assert that the inspector shows the Y Axis properties for swgA
|
||||
await expect(page.locator('[aria-label="Plot Series Properties"] >> h2')).toContainText("Plot Series");
|
||||
await expect(page.getByRole('heading', { name: 'Y Axis' })).toBeVisible();
|
||||
await expect(page.getByRole('list', { name: "Y Axis Properties" }).locator("h2")).toContainText("Y Axis");
|
||||
await expect(page.locator('[aria-label="Plot Series Properties"] .c-object-label')).toContainText(swgA.name);
|
||||
|
||||
//Click on canvas for the 2nd plot
|
||||
@@ -175,7 +175,7 @@ test.describe('Stacked Plot', () => {
|
||||
|
||||
// Assert that the inspector shows the Y Axis properties for swgB
|
||||
await expect(page.locator('[aria-label="Plot Series Properties"] >> h2')).toContainText("Plot Series");
|
||||
await expect(page.getByRole('heading', { name: 'Y Axis' })).toBeVisible();
|
||||
await expect(page.getByRole('list', { name: "Y Axis Properties" }).locator("h2")).toContainText("Y Axis");
|
||||
await expect(page.locator('[aria-label="Plot Series Properties"] .c-object-label')).toContainText(swgB.name);
|
||||
|
||||
//Click on canvas for the 3rd plot
|
||||
@@ -183,7 +183,7 @@ test.describe('Stacked Plot', () => {
|
||||
|
||||
// Assert that the inspector shows the Y Axis properties for swgC
|
||||
await expect(page.locator('[aria-label="Plot Series Properties"] >> h2')).toContainText("Plot Series");
|
||||
await expect(page.getByRole('heading', { name: 'Y Axis' })).toBeVisible();
|
||||
await expect(page.getByRole('list', { name: "Y Axis Properties" }).locator("h2")).toContainText("Y Axis");
|
||||
await expect(page.locator('[aria-label="Plot Series Properties"] .c-object-label')).toContainText(swgC.name);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -157,7 +157,7 @@ test.describe('Plot Tagging', () => {
|
||||
}
|
||||
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await page.goto('./', { waitUntil: 'domcontentloaded' });
|
||||
await page.goto('./', { waitUntil: 'networkidle' });
|
||||
});
|
||||
|
||||
test('Tags work with Overlay Plots', async ({ page }) => {
|
||||
|
||||
@@ -30,7 +30,7 @@ test.describe('Telemetry Table', () => {
|
||||
description: 'https://github.com/nasa/openmct/issues/5113'
|
||||
});
|
||||
|
||||
await page.goto('./', { waitUntil: 'domcontentloaded' });
|
||||
await page.goto('./', { waitUntil: 'networkidle' });
|
||||
|
||||
const table = await createDomainObjectWithDefaults(page, { type: 'Telemetry Table' });
|
||||
await createDomainObjectWithDefaults(page, {
|
||||
|
||||
@@ -26,7 +26,7 @@ const { setFixedTimeMode, setRealTimeMode, setStartOffset, setEndOffset } = requ
|
||||
test.describe('Time conductor operations', () => {
|
||||
test('validate start time does not exceeds end time', async ({ page }) => {
|
||||
// Go to baseURL
|
||||
await page.goto('./', { waitUntil: 'domcontentloaded' });
|
||||
await page.goto('./', { waitUntil: 'networkidle' });
|
||||
const year = new Date().getFullYear();
|
||||
|
||||
let startDate = 'xxxx-01-01 01:00:00.000Z';
|
||||
@@ -82,7 +82,7 @@ test.describe('Time conductor input fields real-time mode', () => {
|
||||
};
|
||||
|
||||
// Go to baseURL
|
||||
await page.goto('./', { waitUntil: 'domcontentloaded' });
|
||||
await page.goto('./', { waitUntil: 'networkidle' });
|
||||
|
||||
// Switch to real-time mode
|
||||
await setRealTimeMode(page);
|
||||
@@ -119,7 +119,7 @@ test.describe('Time conductor input fields real-time mode', () => {
|
||||
const endDelta = (1 * 1000);
|
||||
|
||||
// Go to baseURL
|
||||
await page.goto('./', { waitUntil: 'domcontentloaded' });
|
||||
await page.goto('./', { waitUntil: 'networkidle' });
|
||||
|
||||
// Switch to real-time mode
|
||||
await setRealTimeMode(page);
|
||||
|
||||
@@ -26,7 +26,7 @@ const { openObjectTreeContextMenu, createDomainObjectWithDefaults } = require('.
|
||||
test.describe('Timer', () => {
|
||||
let timer;
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await page.goto('./', { waitUntil: 'domcontentloaded' });
|
||||
await page.goto('./', { waitUntil: 'networkidle' });
|
||||
timer = await createDomainObjectWithDefaults(page, { type: 'timer' });
|
||||
});
|
||||
|
||||
|
||||
@@ -32,7 +32,7 @@ test.describe('Recent Objects', () => {
|
||||
/** @type {import('@playwright/test').Locator} */
|
||||
let folderA;
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await page.goto('./', { waitUntil: 'domcontentloaded' });
|
||||
await page.goto('./', { waitUntil: 'networkidle' });
|
||||
|
||||
// Set Recent Objects List locator for subsequent tests
|
||||
recentObjectsList = page.getByRole('list', {
|
||||
|
||||
@@ -38,7 +38,7 @@ const { test, expect } = require('../../pluginFixtures');
|
||||
test('Verify that the create button appears and that the Folder Domain Object is available for selection', async ({ page }) => {
|
||||
|
||||
//Go to baseURL
|
||||
await page.goto('./', { waitUntil: 'domcontentloaded' });
|
||||
await page.goto('./', { waitUntil: 'networkidle' });
|
||||
|
||||
//Click the Create button
|
||||
await page.click('button:has-text("Create")');
|
||||
|
||||
@@ -28,7 +28,7 @@ const {
|
||||
|
||||
test.describe('Main Tree', () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await page.goto('./', { waitUntil: 'domcontentloaded' });
|
||||
await page.goto('./', { waitUntil: 'networkidle' });
|
||||
});
|
||||
|
||||
test('Creating a child object within a folder and immediately opening it shows the created object in the tree @couchdb', async ({ page }) => {
|
||||
|
||||
@@ -200,8 +200,6 @@
|
||||
openmct.install(openmct.plugins.Timelist());
|
||||
openmct.install(openmct.plugins.BarChart());
|
||||
openmct.install(openmct.plugins.ScatterPlot());
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
openmct.start();
|
||||
});
|
||||
openmct.start();
|
||||
</script>
|
||||
</html>
|
||||
|
||||
12
package.json
12
package.json
@@ -1,14 +1,14 @@
|
||||
{
|
||||
"name": "openmct",
|
||||
"version": "2.2.3-SNAPSHOT",
|
||||
"version": "2.2.2",
|
||||
"description": "The Open MCT core platform",
|
||||
"devDependencies": {
|
||||
"@babel/eslint-parser": "7.18.9",
|
||||
"@braintree/sanitize-url": "6.0.2",
|
||||
"@deploysentinel/playwright": "0.3.4",
|
||||
"@deploysentinel/playwright": "0.3.3",
|
||||
"@percy/cli": "1.23.0",
|
||||
"@percy/playwright": "1.0.4",
|
||||
"@playwright/test": "1.32.3",
|
||||
"@playwright/test": "1.29.0",
|
||||
"@types/eventemitter3": "1.2.0",
|
||||
"@types/jasmine": "4.3.1",
|
||||
"@types/lodash": "4.14.192",
|
||||
@@ -24,7 +24,7 @@
|
||||
"eslint": "8.37.0",
|
||||
"eslint-plugin-compat": "4.1.4",
|
||||
"eslint-plugin-playwright": "0.12.0",
|
||||
"eslint-plugin-vue": "9.11.0",
|
||||
"eslint-plugin-vue": "9.10.0",
|
||||
"eslint-plugin-you-dont-need-lodash-underscore": "6.12.0",
|
||||
"eventemitter3": "1.2.0",
|
||||
"file-saver": "2.0.5",
|
||||
@@ -51,7 +51,7 @@
|
||||
"moment-timezone": "0.5.41",
|
||||
"nyc": "15.1.0",
|
||||
"painterro": "1.2.78",
|
||||
"playwright-core": "1.32.3",
|
||||
"playwright-core": "1.29.0",
|
||||
"plotly.js-basic-dist": "2.20.0",
|
||||
"plotly.js-gl2d-dist": "2.20.0",
|
||||
"printj": "1.3.1",
|
||||
@@ -69,7 +69,7 @@
|
||||
"vue-template-compiler": "2.6.14",
|
||||
"webpack": "5.79.0",
|
||||
"webpack-cli": "5.0.0",
|
||||
"webpack-dev-server": "4.13.3",
|
||||
"webpack-dev-server": "4.11.1",
|
||||
"webpack-merge": "5.8.0"
|
||||
},
|
||||
"scripts": {
|
||||
|
||||
@@ -134,11 +134,6 @@ class TimeAPI extends GlobalTimeContext {
|
||||
*/
|
||||
addIndependentContext(key, value, clockKey) {
|
||||
let timeContext = this.getIndependentContext(key);
|
||||
let upstreamClock;
|
||||
if (timeContext.upstreamTimeContext) {
|
||||
upstreamClock = timeContext.upstreamTimeContext.clock();
|
||||
}
|
||||
|
||||
//stop following upstream time context since the view has it's own
|
||||
timeContext.resetContext();
|
||||
|
||||
@@ -146,11 +141,6 @@ class TimeAPI extends GlobalTimeContext {
|
||||
timeContext.clock(clockKey, value);
|
||||
} else {
|
||||
timeContext.stopClock();
|
||||
//upstream clock was active, but now we don't have one
|
||||
if (upstreamClock) {
|
||||
timeContext.emit('clock', timeContext.activeClock);
|
||||
}
|
||||
|
||||
timeContext.bounds(value);
|
||||
}
|
||||
|
||||
|
||||
@@ -22,25 +22,12 @@
|
||||
|
||||
import EventEmitter from 'EventEmitter';
|
||||
|
||||
export const TIME_CONTEXT_EVENTS = {
|
||||
//old API events - to be deprecated
|
||||
bounds: 'bounds',
|
||||
clock: 'clock',
|
||||
timeSystem: 'timeSystem',
|
||||
clockOffsets: 'clockOffsets',
|
||||
//new API events
|
||||
tick: 'tick',
|
||||
modeChanged: 'modeChanged',
|
||||
boundsChanged: 'boundsChanged',
|
||||
clockChanged: 'clockChanged',
|
||||
timeSystemChanged: 'timeSystemChanged',
|
||||
clockOffsetsChanged: 'clockOffsetsChanged'
|
||||
};
|
||||
|
||||
export const MODES = {
|
||||
fixed: 'fixed',
|
||||
realtime: 'realtime'
|
||||
};
|
||||
export const TIME_CONTEXT_EVENTS = [
|
||||
'bounds',
|
||||
'clock',
|
||||
'timeSystem',
|
||||
'clockOffsets'
|
||||
];
|
||||
|
||||
class TimeContext extends EventEmitter {
|
||||
constructor() {
|
||||
@@ -60,7 +47,6 @@ class TimeContext extends EventEmitter {
|
||||
|
||||
this.activeClock = undefined;
|
||||
this.offsets = undefined;
|
||||
this.mode = undefined;
|
||||
|
||||
this.tick = this.tick.bind(this);
|
||||
}
|
||||
@@ -292,7 +278,7 @@ class TimeContext extends EventEmitter {
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop following the currently active clock. This will
|
||||
* Stop the currently active clock from ticking, and unset it. This will
|
||||
* revert all views to showing a static time frame defined by the current
|
||||
* bounds.
|
||||
*/
|
||||
@@ -375,7 +361,6 @@ class TimeContext extends EventEmitter {
|
||||
|
||||
this.boundsVal = newBounds;
|
||||
this.emit('bounds', this.boundsVal, true);
|
||||
this.emit(TIME_CONTEXT_EVENTS.boundsChanged, this.boundsVal, true);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -389,278 +374,6 @@ class TimeContext extends EventEmitter {
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the time system of the TimeAPI.
|
||||
* @returns {TimeSystem} The currently applied time system
|
||||
* @memberof module:openmct.TimeAPI#
|
||||
* @method getTimeSystem
|
||||
*/
|
||||
getTimeSystem() {
|
||||
return this.system;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the time system of the TimeAPI.
|
||||
* @param {TimeSystem | string} timeSystemOrKey
|
||||
* @param {module:openmct.TimeAPI~TimeConductorBounds} bounds
|
||||
* @fires module:openmct.TimeAPI~timeSystem
|
||||
* @returns {TimeSystem} The currently applied time system
|
||||
* @memberof module:openmct.TimeAPI#
|
||||
* @method setTimeSystem
|
||||
*/
|
||||
setTimeSystem(timeSystemOrKey, bounds) {
|
||||
if (!this.isRealTime() && !bounds) {
|
||||
throw new Error(
|
||||
"Must specify bounds when changing time system without "
|
||||
+ "an active clock."
|
||||
);
|
||||
}
|
||||
|
||||
if (timeSystemOrKey === undefined) {
|
||||
throw "Please provide a time system";
|
||||
}
|
||||
|
||||
let timeSystem;
|
||||
|
||||
if (typeof timeSystemOrKey === 'string') {
|
||||
timeSystem = this.timeSystems.get(timeSystemOrKey);
|
||||
|
||||
if (timeSystem === undefined) {
|
||||
throw "Unknown time system " + timeSystemOrKey + ". Has it been registered with 'addTimeSystem'?";
|
||||
}
|
||||
} else if (typeof timeSystemOrKey === 'object') {
|
||||
timeSystem = timeSystemOrKey;
|
||||
|
||||
if (!this.timeSystems.has(timeSystem.key)) {
|
||||
throw "Unknown time system " + timeSystem.key + ". Has it been registered with 'addTimeSystem'?";
|
||||
}
|
||||
} else {
|
||||
throw "Attempt to set invalid time system in Time API. Please provide a previously registered time system object or key";
|
||||
}
|
||||
|
||||
this.system = timeSystem;
|
||||
/**
|
||||
* The time system used by the time
|
||||
* conductor has changed. A change in Time System will always be
|
||||
* followed by a bounds event specifying new query bounds.
|
||||
*
|
||||
* @event module:openmct.TimeAPI~timeSystem
|
||||
* @property {TimeSystem} The value of the currently applied
|
||||
* Time System
|
||||
* */
|
||||
this.emit(TIME_CONTEXT_EVENTS.timeSystemChanged, this.system);
|
||||
|
||||
if (bounds) {
|
||||
this.setBounds(bounds);
|
||||
}
|
||||
|
||||
return this.system;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the start and end time of the time conductor. Basic validation
|
||||
* of bounds is performed.
|
||||
* @returns {module:openmct.TimeAPI~TimeConductorBounds}
|
||||
* @memberof module:openmct.TimeAPI#
|
||||
* @method bounds
|
||||
*/
|
||||
getBounds() {
|
||||
//Return a copy to prevent direct mutation of time conductor bounds.
|
||||
return JSON.parse(JSON.stringify(this.boundsVal));
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the start and end time of the time conductor. Basic validation
|
||||
* of bounds is performed.
|
||||
*
|
||||
* @param {module:openmct.TimeAPI~TimeConductorBounds} newBounds
|
||||
* @throws {Error} Validation error
|
||||
* @fires module:openmct.TimeAPI~bounds
|
||||
* @returns {module:openmct.TimeAPI~TimeConductorBounds}
|
||||
* @memberof module:openmct.TimeAPI#
|
||||
* @method bounds
|
||||
*/
|
||||
setBounds(newBounds) {
|
||||
const validationResult = this.validateBounds(newBounds);
|
||||
if (validationResult.valid !== true) {
|
||||
throw new Error(validationResult.message);
|
||||
}
|
||||
|
||||
//Create a copy to avoid direct mutation of conductor bounds
|
||||
this.boundsVal = JSON.parse(JSON.stringify(newBounds));
|
||||
/**
|
||||
* The start time, end time, or both have been updated.
|
||||
* @event bounds
|
||||
* @memberof module:openmct.TimeAPI~
|
||||
* @property {TimeConductorBounds} bounds The newly updated bounds
|
||||
* @property {boolean} [tick] `true` if the bounds update was due to
|
||||
* a "tick" event (i.e. was an automatic update), false otherwise.
|
||||
*/
|
||||
this.emit(TIME_CONTEXT_EVENTS.boundsChanged, this.boundsVal, false);
|
||||
|
||||
//Return a copy to prevent direct mutation of time conductor bounds.
|
||||
return JSON.parse(JSON.stringify(this.boundsVal));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the active clock.
|
||||
* @return {Clock} the currently active clock;
|
||||
*/
|
||||
getClock() {
|
||||
return this.activeClock;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the active clock. Tick source will be immediately subscribed to
|
||||
* and the currently ticking will begin.
|
||||
* Offsets from 'now', if provided, will be used to set realtime mode offsets
|
||||
*
|
||||
* @param {Clock || string} keyOrClock The clock to activate, or its key
|
||||
* @param {ClockOffsets} offsets on each tick these will be used to calculate
|
||||
* the start and end bounds when in realtime mode.
|
||||
* This maintains a sliding time window of a fixed width that automatically updates.
|
||||
* @fires module:openmct.TimeAPI~clock
|
||||
* @return {Clock} the currently active clock;
|
||||
*/
|
||||
setClock(keyOrClock, offsets) {
|
||||
let clock;
|
||||
|
||||
if (typeof keyOrClock === 'string') {
|
||||
clock = this.clocks.get(keyOrClock);
|
||||
if (clock === undefined) {
|
||||
throw "Unknown clock '" + keyOrClock + "'. Has it been registered with 'addClock'?";
|
||||
}
|
||||
} else if (typeof keyOrClock === 'object') {
|
||||
clock = keyOrClock;
|
||||
if (!this.clocks.has(clock.key)) {
|
||||
throw "Unknown clock '" + keyOrClock.key + "'. Has it been registered with 'addClock'?";
|
||||
}
|
||||
}
|
||||
|
||||
const isRealtimeMode = this.getMode() === MODES.realtime;
|
||||
const previousClock = this.activeClock;
|
||||
if (previousClock !== undefined && isRealtimeMode) {
|
||||
previousClock.off("tick", this.tick);
|
||||
}
|
||||
|
||||
this.activeClock = clock;
|
||||
|
||||
/**
|
||||
* The active clock has changed.
|
||||
* @event clock
|
||||
* @memberof module:openmct.TimeAPI~
|
||||
* @property {Clock} clock The newly activated clock, or undefined
|
||||
* if the system is no longer following a clock source
|
||||
*/
|
||||
this.emit(TIME_CONTEXT_EVENTS.clockChanged, this.activeClock);
|
||||
|
||||
if (isRealtimeMode) {
|
||||
if (this.activeClock !== undefined) {
|
||||
this.activeClock.on("tick", this.tick);
|
||||
}
|
||||
|
||||
if (offsets !== undefined) {
|
||||
this.clockOffsets(offsets);
|
||||
}
|
||||
}
|
||||
|
||||
return this.activeClock;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current mode.
|
||||
* @return {Mode} the current mode;
|
||||
*/
|
||||
getMode() {
|
||||
return this.mode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the mode to either fixed or realtime.
|
||||
*
|
||||
* @param {Mode} mode The mode to activate
|
||||
* @param {ClockOffsets | Bounds} offsets on each tick these will be used to calculate
|
||||
* the start and end bounds. In realtime mode, this maintains a sliding time window of a fixed
|
||||
* width that automatically updates.
|
||||
* @fires module:openmct.TimeAPI~clock
|
||||
* @return {Mode} the currently active mode;
|
||||
*/
|
||||
setMode(mode, offsets) {
|
||||
if (offsets === undefined) {
|
||||
throw "When setting the mode, offsets must also be provided";
|
||||
}
|
||||
|
||||
const previousMode = this.mode;
|
||||
|
||||
if (previousMode === MODES.realtime) {
|
||||
this.activeClock.off('tick', this.tick);
|
||||
}
|
||||
|
||||
this.mode = mode;
|
||||
|
||||
if (mode === MODES.realtime) {
|
||||
this.activeClock.on("tick", this.tick);
|
||||
this.setClockOffsets(offsets);
|
||||
} else if (mode === MODES.fixed) {
|
||||
this.activeClock.off("tick", this.tick);
|
||||
this.setBounds(offsets);
|
||||
}
|
||||
|
||||
/**
|
||||
* The active clock has changed. Clock can be unset by calling {@link stopClock}
|
||||
* @event clock
|
||||
* @memberof module:openmct.TimeAPI~
|
||||
* @property {Clock} clock The newly activated clock, or undefined
|
||||
* if the system is no longer following a clock source
|
||||
*/
|
||||
this.emit(TIME_CONTEXT_EVENTS.modeChanged, this.mode);
|
||||
|
||||
return this.mode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the currently applied clock offsets.
|
||||
* @returns {ClockOffsets}
|
||||
*/
|
||||
getClockOffsets() {
|
||||
return this.offsets;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the currently applied clock offsets. If no parameter is provided,
|
||||
* the current value will be returned. If provided, the new value will be
|
||||
* used as the new clock offsets.
|
||||
* @param {ClockOffsets} offsets
|
||||
* @returns {ClockOffsets}
|
||||
*/
|
||||
setClockOffsets(offsets) {
|
||||
const validationResult = this.validateOffsets(offsets);
|
||||
if (validationResult.valid !== true) {
|
||||
throw new Error(validationResult.message);
|
||||
}
|
||||
|
||||
this.offsets = offsets;
|
||||
|
||||
const currentValue = this.activeClock.currentValue();
|
||||
const newBounds = {
|
||||
start: currentValue + offsets.start,
|
||||
end: currentValue + offsets.end
|
||||
};
|
||||
|
||||
this.setBounds(newBounds);
|
||||
|
||||
/**
|
||||
* Event that is triggered when clock offsets change.
|
||||
* @event clockOffsets
|
||||
* @memberof module:openmct.TimeAPI~
|
||||
* @property {ClockOffsets} clockOffsets The newly activated clock
|
||||
* offsets.
|
||||
*/
|
||||
this.emit(TIME_CONTEXT_EVENTS.clockOffsetsChanged, offsets);
|
||||
|
||||
return this.offsets;
|
||||
}
|
||||
}
|
||||
|
||||
export default TimeContext;
|
||||
|
||||
@@ -19,7 +19,6 @@
|
||||
</li>
|
||||
<li class="grid-row">
|
||||
<div
|
||||
id="log-mode-checkbox"
|
||||
class="grid-cell label"
|
||||
title="Enable log mode."
|
||||
>
|
||||
@@ -30,7 +29,6 @@
|
||||
<input
|
||||
v-model="logMode"
|
||||
class="js-log-mode-input"
|
||||
aria-labelledby="log-mode-checkbox"
|
||||
type="checkbox"
|
||||
@change="updateForm('logMode')"
|
||||
/>
|
||||
@@ -38,14 +36,12 @@
|
||||
</li>
|
||||
<li class="grid-row">
|
||||
<div
|
||||
id="autoscale-checkbox"
|
||||
class="grid-cell label"
|
||||
title="Automatically scale the Y axis to keep all values in view."
|
||||
>Auto scale</div>
|
||||
<div class="grid-cell value"><input
|
||||
v-model="autoscale"
|
||||
type="checkbox"
|
||||
aria-labelledby="autoscale-checkbox"
|
||||
@change="updateForm('autoscale')"
|
||||
></div>
|
||||
</li>
|
||||
|
||||
@@ -21,8 +21,7 @@
|
||||
*****************************************************************************/
|
||||
<template>
|
||||
<div
|
||||
ref="timeConductorOptionsHolder"
|
||||
class="c-compact-tc is-expanded"
|
||||
class="c-conductor"
|
||||
:class="[
|
||||
{ 'is-zooming': isZooming },
|
||||
{ 'is-panning': isPanning },
|
||||
@@ -30,44 +29,52 @@
|
||||
isFixed ? 'is-fixed-mode' : 'is-realtime-mode'
|
||||
]"
|
||||
>
|
||||
<ConductorModeIcon class="c-conductor__mode-icon" />
|
||||
<!-- TODO - NEED TO ADD MODE, CLOCK AND TIMESYSTEM VIEW ONLY INFORMATION HERE -->
|
||||
<conductor-inputs-fixed
|
||||
v-if="isFixed"
|
||||
:input-bounds="viewBounds"
|
||||
:read-only="true"
|
||||
/>
|
||||
<conductor-inputs-realtime
|
||||
v-else
|
||||
:input-bounds="viewBounds"
|
||||
:read-only="true"
|
||||
/>
|
||||
<conductor-axis
|
||||
v-if="isFixed"
|
||||
class="c-conductor__ticks"
|
||||
:view-bounds="viewBounds"
|
||||
:is-fixed="isFixed"
|
||||
:alt-pressed="altPressed"
|
||||
@endPan="endPan"
|
||||
@endZoom="endZoom"
|
||||
@panAxis="pan"
|
||||
@zoomAxis="zoom"
|
||||
/>
|
||||
<div
|
||||
v-else
|
||||
class="u-flex-spreader"
|
||||
></div>
|
||||
<div class="c-not-button c-not-button--compact c-compact-tc__gear icon-gear"></div>
|
||||
<div class="c-conductor__time-bounds">
|
||||
<conductor-inputs-fixed
|
||||
v-if="isFixed"
|
||||
:input-bounds="viewBounds"
|
||||
@updated="saveFixedOffsets"
|
||||
/>
|
||||
<conductor-inputs-realtime
|
||||
v-else
|
||||
:input-bounds="viewBounds"
|
||||
@updated="saveClockOffsets"
|
||||
/>
|
||||
<ConductorModeIcon class="c-conductor__mode-icon" />
|
||||
<conductor-axis
|
||||
class="c-conductor__ticks"
|
||||
:view-bounds="viewBounds"
|
||||
:is-fixed="isFixed"
|
||||
:alt-pressed="altPressed"
|
||||
@endPan="endPan"
|
||||
@endZoom="endZoom"
|
||||
@panAxis="pan"
|
||||
@zoomAxis="zoom"
|
||||
/>
|
||||
</div>
|
||||
<div class="c-conductor__controls">
|
||||
<ConductorMode class="c-conductor__mode-select" />
|
||||
<ConductorTimeSystem class="c-conductor__time-system-select" />
|
||||
<ConductorHistory
|
||||
class="c-conductor__history-select"
|
||||
:offsets="openmct.time.clockOffsets()"
|
||||
:bounds="bounds"
|
||||
:time-system="timeSystem"
|
||||
:mode="timeMode"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import _ from 'lodash';
|
||||
import ConductorMode from './ConductorMode.vue';
|
||||
import ConductorTimeSystem from './ConductorTimeSystem.vue';
|
||||
import ConductorAxis from './ConductorAxis.vue';
|
||||
import ConductorModeIcon from './ConductorModeIcon.vue';
|
||||
import ConductorHistory from './ConductorHistory.vue';
|
||||
import ConductorInputsFixed from "./ConductorInputsFixed.vue";
|
||||
import ConductorInputsRealtime from "./ConductorInputsRealtime.vue";
|
||||
import conductorPopUpManager from "./conductorPopUpManager";
|
||||
|
||||
const DEFAULT_DURATION_FORMATTER = 'duration';
|
||||
|
||||
@@ -75,10 +82,12 @@ export default {
|
||||
components: {
|
||||
ConductorInputsRealtime,
|
||||
ConductorInputsFixed,
|
||||
ConductorMode,
|
||||
ConductorTimeSystem,
|
||||
ConductorAxis,
|
||||
ConductorModeIcon
|
||||
ConductorModeIcon,
|
||||
ConductorHistory
|
||||
},
|
||||
mixins: [conductorPopUpManager],
|
||||
inject: ['openmct', 'configuration'],
|
||||
data() {
|
||||
let bounds = this.openmct.time.bounds();
|
||||
@@ -112,9 +121,16 @@ export default {
|
||||
showDatePicker: false,
|
||||
altPressed: false,
|
||||
isPanning: false,
|
||||
isZooming: false
|
||||
isZooming: false,
|
||||
showTCInputStart: false,
|
||||
showTCInputEnd: false
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
timeMode() {
|
||||
return this.isFixed ? 'fixed' : 'realtime';
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
document.addEventListener('keydown', this.handleKeyDown);
|
||||
document.addEventListener('keyup', this.handleKeyUp);
|
||||
@@ -180,6 +196,7 @@ export default {
|
||||
this.isUTCBased = timeSystem.isUTCBased;
|
||||
},
|
||||
setViewFromClock(clock) {
|
||||
// this.clearAllValidation();
|
||||
this.isFixed = clock === undefined;
|
||||
},
|
||||
setViewFromBounds(bounds) {
|
||||
@@ -193,22 +210,11 @@ export default {
|
||||
format: key
|
||||
}).formatter;
|
||||
},
|
||||
saveFixedBounds(bounds) {
|
||||
this.openmct.time.bounds(bounds);
|
||||
},
|
||||
saveClockOffsets(offsets) {
|
||||
this.openmct.time.clockOffsets(offsets);
|
||||
},
|
||||
saveMode(option) {
|
||||
if (option.timeSystem) {
|
||||
this.openmct.time.timeSystem(option.timeSystem, option.bounds);
|
||||
}
|
||||
|
||||
if (option.clockKey === undefined) {
|
||||
this.openmct.time.stopClock();
|
||||
} else {
|
||||
this.openmct.time.clock(option.clockKey, option.offsets);
|
||||
}
|
||||
saveFixedOffsets(bounds) {
|
||||
this.openmct.time.bounds(bounds);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -98,10 +98,7 @@ export default {
|
||||
|
||||
//Respond to changes in conductor
|
||||
this.openmct.time.on("timeSystem", this.setViewFromTimeSystem);
|
||||
this.resizeTimer = setInterval(this.resize, RESIZE_POLL_INTERVAL);
|
||||
},
|
||||
beforeDestroy() {
|
||||
clearInterval(this.resizeTimer);
|
||||
setInterval(this.resize, RESIZE_POLL_INTERVAL);
|
||||
},
|
||||
methods: {
|
||||
setAxisDimensions() {
|
||||
|
||||
@@ -26,9 +26,8 @@
|
||||
>
|
||||
<div class="c-menu-button c-ctrl-wrapper c-ctrl-wrapper--menus-left">
|
||||
<button
|
||||
class="c-button--menu c-button--compact c-history-button icon-history"
|
||||
:class="buttonCssClass"
|
||||
aria-label="Time Conductor History"
|
||||
class="c-button--menu c-history-button icon-history"
|
||||
@click.prevent.stop="showHistoryMenu"
|
||||
>
|
||||
<span class="c-button__label">History</span>
|
||||
@@ -65,13 +64,6 @@ export default {
|
||||
mode: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
buttonCssClass: {
|
||||
type: String,
|
||||
required: false,
|
||||
default() {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
},
|
||||
data() {
|
||||
@@ -114,8 +106,8 @@ export default {
|
||||
bounds: {
|
||||
handler() {
|
||||
// only for fixed time since we track offsets for realtime
|
||||
this.updateMode();
|
||||
if (this.isFixed) {
|
||||
this.updateMode();
|
||||
this.addTimespan();
|
||||
}
|
||||
},
|
||||
@@ -124,9 +116,7 @@ export default {
|
||||
offsets: {
|
||||
handler() {
|
||||
this.updateMode();
|
||||
if (!this.isFixed) {
|
||||
this.addTimespan();
|
||||
}
|
||||
this.addTimespan();
|
||||
},
|
||||
deep: true
|
||||
},
|
||||
|
||||
@@ -1,34 +1,78 @@
|
||||
<template>
|
||||
<time-popup-fixed
|
||||
v-if="readOnly === false"
|
||||
:input-bounds="bounds"
|
||||
:input-time-system="timeSystem"
|
||||
@focus.native="$event.target.select()"
|
||||
@update="setBoundsFromView"
|
||||
@dismiss="dismiss"
|
||||
/>
|
||||
<div
|
||||
v-else
|
||||
class="c-compact-tc__bounds"
|
||||
<form
|
||||
ref="fixedDeltaInput"
|
||||
class="c-conductor__inputs"
|
||||
>
|
||||
<div class="c-compact-tc__bounds__value">{{ formattedBounds.start }}</div>
|
||||
<div class="c-compact-tc__bounds__start-end-sep icon-arrows-right-left"></div>
|
||||
<div class="c-compact-tc__bounds__value">{{ formattedBounds.end }}</div>
|
||||
</div>
|
||||
<div
|
||||
class="c-ctrl-wrapper c-conductor-input c-conductor__start-fixed"
|
||||
>
|
||||
<!-- Fixed start -->
|
||||
<div class="c-conductor__start-fixed__label">
|
||||
Start
|
||||
</div>
|
||||
<input
|
||||
ref="startDate"
|
||||
v-model="formattedBounds.start"
|
||||
class="c-input--datetime"
|
||||
type="text"
|
||||
autocorrect="off"
|
||||
spellcheck="false"
|
||||
@change="validateAllBounds('startDate'); submitForm()"
|
||||
>
|
||||
<date-picker
|
||||
v-if="isUTCBased"
|
||||
class="c-ctrl-wrapper--menus-left"
|
||||
:bottom="keyString !== undefined"
|
||||
:default-date-time="formattedBounds.start"
|
||||
:formatter="timeFormatter"
|
||||
@date-selected="startDateSelected"
|
||||
/>
|
||||
</div>
|
||||
<div class="c-ctrl-wrapper c-conductor-input c-conductor__end-fixed">
|
||||
<!-- Fixed end and RT 'last update' display -->
|
||||
<div class="c-conductor__end-fixed__label">
|
||||
End
|
||||
</div>
|
||||
<input
|
||||
ref="endDate"
|
||||
v-model="formattedBounds.end"
|
||||
class="c-input--datetime"
|
||||
type="text"
|
||||
autocorrect="off"
|
||||
spellcheck="false"
|
||||
@change="validateAllBounds('endDate'); submitForm()"
|
||||
>
|
||||
<date-picker
|
||||
v-if="isUTCBased"
|
||||
class="c-ctrl-wrapper--menus-left"
|
||||
:bottom="keyString !== undefined"
|
||||
:default-date-time="formattedBounds.end"
|
||||
:formatter="timeFormatter"
|
||||
@date-selected="endDateSelected"
|
||||
/>
|
||||
</div>
|
||||
</form>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import TimePopupFixed from "./timePopupFixed.vue";
|
||||
|
||||
import DatePicker from "./DatePicker.vue";
|
||||
import _ from "lodash";
|
||||
|
||||
// const DEFAULT_DURATION_FORMATTER = 'duration';
|
||||
const DEFAULT_DURATION_FORMATTER = 'duration';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
TimePopupFixed
|
||||
DatePicker
|
||||
},
|
||||
inject: ['openmct'],
|
||||
props: {
|
||||
keyString: {
|
||||
type: String,
|
||||
default() {
|
||||
return undefined;
|
||||
}
|
||||
},
|
||||
inputBounds: {
|
||||
type: Object,
|
||||
default() {
|
||||
@@ -40,29 +84,18 @@ export default {
|
||||
default() {
|
||||
return [];
|
||||
}
|
||||
},
|
||||
readOnly: {
|
||||
type: Boolean,
|
||||
default() {
|
||||
return false;
|
||||
}
|
||||
},
|
||||
compact: {
|
||||
type: Boolean,
|
||||
default() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
},
|
||||
data() {
|
||||
let timeSystem = this.openmct.time.timeSystem();
|
||||
// let durationFormatter = this.getFormatter(timeSystem.durationFormat || DEFAULT_DURATION_FORMATTER);
|
||||
let durationFormatter = this.getFormatter(timeSystem.durationFormat || DEFAULT_DURATION_FORMATTER);
|
||||
let timeFormatter = this.getFormatter(timeSystem.timeFormat);
|
||||
let bounds = this.bounds || this.openmct.time.bounds();
|
||||
|
||||
return {
|
||||
timeSystem: timeSystem,
|
||||
// durationFormatter,
|
||||
showTCInputStart: true,
|
||||
showTCInputEnd: true,
|
||||
durationFormatter,
|
||||
timeFormatter,
|
||||
bounds: {
|
||||
start: bounds.start,
|
||||
@@ -76,15 +109,8 @@ export default {
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
objectPath: {
|
||||
handler(newPath, oldPath) {
|
||||
if (newPath === oldPath) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.setTimeContext();
|
||||
},
|
||||
deep: true
|
||||
keyString() {
|
||||
this.setTimeContext();
|
||||
},
|
||||
inputBounds: {
|
||||
handler(newBounds) {
|
||||
@@ -100,26 +126,36 @@ export default {
|
||||
this.setTimeContext();
|
||||
},
|
||||
beforeDestroy() {
|
||||
this.clearAllValidation();
|
||||
this.openmct.time.off('timeSystem', this.setTimeSystem);
|
||||
this.stopFollowingTimeContext();
|
||||
},
|
||||
methods: {
|
||||
setTimeContext() {
|
||||
this.stopFollowingTimeContext();
|
||||
this.timeContext = this.openmct.time.getContextForView(this.objectPath);
|
||||
this.timeContext = this.openmct.time.getContextForView(this.keyString ? this.objectPath : []);
|
||||
|
||||
this.handleNewBounds(this.timeContext.bounds());
|
||||
this.timeContext.on('bounds', this.handleNewBounds);
|
||||
this.timeContext.on('clock', this.clearAllValidation);
|
||||
},
|
||||
stopFollowingTimeContext() {
|
||||
if (this.timeContext) {
|
||||
this.timeContext.off('bounds', this.handleNewBounds);
|
||||
this.timeContext.off('clock', this.clearAllValidation);
|
||||
}
|
||||
},
|
||||
handleNewBounds(bounds) {
|
||||
this.setBounds(bounds);
|
||||
this.setViewFromBounds(bounds);
|
||||
},
|
||||
clearAllValidation() {
|
||||
[this.$refs.startDate, this.$refs.endDate].forEach(this.clearValidationForInput);
|
||||
},
|
||||
clearValidationForInput(input) {
|
||||
input.setCustomValidity('');
|
||||
input.title = '';
|
||||
},
|
||||
setBounds(bounds) {
|
||||
this.bounds = bounds;
|
||||
},
|
||||
@@ -130,8 +166,8 @@ export default {
|
||||
setTimeSystem(timeSystem) {
|
||||
this.timeSystem = timeSystem;
|
||||
this.timeFormatter = this.getFormatter(timeSystem.timeFormat);
|
||||
// this.durationFormatter = this.getFormatter(
|
||||
// timeSystem.durationFormat || DEFAULT_DURATION_FORMATTER);
|
||||
this.durationFormatter = this.getFormatter(
|
||||
timeSystem.durationFormat || DEFAULT_DURATION_FORMATTER);
|
||||
this.isUTCBased = timeSystem.isUTCBased;
|
||||
},
|
||||
getFormatter(key) {
|
||||
@@ -139,14 +175,116 @@ export default {
|
||||
format: key
|
||||
}).formatter;
|
||||
},
|
||||
setBoundsFromView(bounds) {
|
||||
this.$emit('updated', {
|
||||
start: bounds.start,
|
||||
end: bounds.end
|
||||
setBoundsFromView($event) {
|
||||
if (this.$refs.fixedDeltaInput.checkValidity()) {
|
||||
let start = this.timeFormatter.parse(this.formattedBounds.start);
|
||||
let end = this.timeFormatter.parse(this.formattedBounds.end);
|
||||
|
||||
this.$emit('updated', {
|
||||
start: start,
|
||||
end: end
|
||||
});
|
||||
}
|
||||
|
||||
if ($event) {
|
||||
$event.preventDefault();
|
||||
|
||||
return false;
|
||||
}
|
||||
},
|
||||
submitForm() {
|
||||
// Allow Vue model to catch up to user input.
|
||||
// Submitting form will cause validation messages to display (but only if triggered by button click)
|
||||
this.$nextTick(() => this.setBoundsFromView());
|
||||
},
|
||||
validateAllBounds(ref) {
|
||||
if (!this.areBoundsFormatsValid()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
let validationResult = {
|
||||
valid: true
|
||||
};
|
||||
const currentInput = this.$refs[ref];
|
||||
|
||||
return [this.$refs.startDate, this.$refs.endDate].every((input) => {
|
||||
let boundsValues = {
|
||||
start: this.timeFormatter.parse(this.formattedBounds.start),
|
||||
end: this.timeFormatter.parse(this.formattedBounds.end)
|
||||
};
|
||||
//TODO: Do we need limits here? We have conductor limits disabled right now
|
||||
// const limit = this.getBoundsLimit();
|
||||
const limit = false;
|
||||
|
||||
if (this.timeSystem.isUTCBased && limit
|
||||
&& boundsValues.end - boundsValues.start > limit) {
|
||||
if (input === currentInput) {
|
||||
validationResult = {
|
||||
valid: false,
|
||||
message: "Start and end difference exceeds allowable limit"
|
||||
};
|
||||
}
|
||||
} else {
|
||||
if (input === currentInput) {
|
||||
validationResult = this.openmct.time.validateBounds(boundsValues);
|
||||
}
|
||||
}
|
||||
|
||||
return this.handleValidationResults(input, validationResult);
|
||||
});
|
||||
},
|
||||
dismiss() {
|
||||
this.$emit('dismiss');
|
||||
areBoundsFormatsValid() {
|
||||
let validationResult = {
|
||||
valid: true
|
||||
};
|
||||
|
||||
return [this.$refs.startDate, this.$refs.endDate].every((input) => {
|
||||
const formattedDate = input === this.$refs.startDate
|
||||
? this.formattedBounds.start
|
||||
: this.formattedBounds.end
|
||||
;
|
||||
|
||||
if (!this.timeFormatter.validate(formattedDate)) {
|
||||
validationResult = {
|
||||
valid: false,
|
||||
message: 'Invalid date'
|
||||
};
|
||||
}
|
||||
|
||||
return this.handleValidationResults(input, validationResult);
|
||||
});
|
||||
},
|
||||
getBoundsLimit() {
|
||||
const configuration = this.configuration.menuOptions
|
||||
.filter(option => option.timeSystem === this.timeSystem.key)
|
||||
.find(option => option.limit);
|
||||
|
||||
const limit = configuration ? configuration.limit : undefined;
|
||||
|
||||
return limit;
|
||||
},
|
||||
handleValidationResults(input, validationResult) {
|
||||
if (validationResult.valid !== true) {
|
||||
input.setCustomValidity(validationResult.message);
|
||||
input.title = validationResult.message;
|
||||
} else {
|
||||
input.setCustomValidity('');
|
||||
input.title = '';
|
||||
}
|
||||
|
||||
this.$refs.fixedDeltaInput.reportValidity();
|
||||
|
||||
return validationResult.valid;
|
||||
},
|
||||
startDateSelected(date) {
|
||||
this.formattedBounds.start = this.timeFormatter.format(date);
|
||||
this.validateAllBounds('startDate');
|
||||
this.submitForm();
|
||||
},
|
||||
endDateSelected(date) {
|
||||
this.formattedBounds.end = this.timeFormatter.format(date);
|
||||
this.validateAllBounds('endDate');
|
||||
this.submitForm();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,43 +1,95 @@
|
||||
<template>
|
||||
<time-popup-realtime
|
||||
v-if="readOnly === false"
|
||||
:offsets="offsets"
|
||||
@focus.native="$event.target.select()"
|
||||
@update="timePopUpdate"
|
||||
@dismiss="dismiss"
|
||||
/>
|
||||
<div
|
||||
v-else
|
||||
class="c-compact-tc__bounds"
|
||||
<form
|
||||
ref="deltaInput"
|
||||
class="c-conductor__inputs"
|
||||
>
|
||||
<div class="c-compact-tc__bounds__value icon-minus">{{ offsets.start }}</div>
|
||||
<div
|
||||
v-if="compact"
|
||||
class="c-compact-tc__bounds__start-end-sep icon-arrows-right-left"
|
||||
></div>
|
||||
<div
|
||||
v-else
|
||||
class="c-compact-tc__current-update"
|
||||
class="c-ctrl-wrapper c-conductor-input c-conductor__start-delta"
|
||||
>
|
||||
LAST UPDATE {{ formattedBounds.end }}
|
||||
<!-- RT start -->
|
||||
<div class="c-direction-indicator icon-minus"></div>
|
||||
<time-popup
|
||||
v-if="showTCInputStart"
|
||||
class="pr-tc-input-menu--start"
|
||||
:bottom="keyString !== undefined"
|
||||
:type="'start'"
|
||||
:offset="offsets.start"
|
||||
@focus.native="$event.target.select()"
|
||||
@hide="hideAllTimePopups"
|
||||
@update="timePopUpdate"
|
||||
/>
|
||||
<button
|
||||
ref="startOffset"
|
||||
class="c-button c-conductor__delta-button"
|
||||
title="Set the time offset after now"
|
||||
data-testid="conductor-start-offset-button"
|
||||
@click.prevent.stop="showTimePopupStart"
|
||||
>
|
||||
{{ offsets.start }}
|
||||
</button>
|
||||
</div>
|
||||
<div class="c-compact-tc__bounds__value icon-plus">{{ offsets.end }}</div>
|
||||
</div>
|
||||
<div class="c-ctrl-wrapper c-conductor-input c-conductor__end-fixed">
|
||||
<!-- RT 'last update' display -->
|
||||
<div class="c-conductor__end-fixed__label">
|
||||
Current
|
||||
</div>
|
||||
<input
|
||||
ref="endDate"
|
||||
v-model="formattedCurrentValue"
|
||||
class="c-input--datetime"
|
||||
type="text"
|
||||
autocorrect="off"
|
||||
spellcheck="false"
|
||||
:disabled="true"
|
||||
>
|
||||
</div>
|
||||
<div
|
||||
class="c-ctrl-wrapper c-conductor-input c-conductor__end-delta"
|
||||
>
|
||||
<!-- RT end -->
|
||||
<div class="c-direction-indicator icon-plus"></div>
|
||||
<time-popup
|
||||
v-if="showTCInputEnd"
|
||||
class="pr-tc-input-menu--end"
|
||||
:bottom="keyString !== undefined"
|
||||
:type="'end'"
|
||||
:offset="offsets.end"
|
||||
@focus.native="$event.target.select()"
|
||||
@hide="hideAllTimePopups"
|
||||
@update="timePopUpdate"
|
||||
/>
|
||||
<button
|
||||
ref="endOffset"
|
||||
class="c-button c-conductor__delta-button"
|
||||
title="Set the time offset preceding now"
|
||||
data-testid="conductor-end-offset-button"
|
||||
@click.prevent.stop="showTimePopupEnd"
|
||||
>
|
||||
{{ offsets.end }}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
import TimePopupRealtime from "./timePopupRealtime.vue";
|
||||
import timePopup from "./timePopup.vue";
|
||||
import _ from "lodash";
|
||||
|
||||
const DEFAULT_DURATION_FORMATTER = 'duration';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
TimePopupRealtime
|
||||
timePopup
|
||||
},
|
||||
inject: ['openmct'],
|
||||
props: {
|
||||
keyString: {
|
||||
type: String,
|
||||
default() {
|
||||
return undefined;
|
||||
}
|
||||
},
|
||||
objectPath: {
|
||||
type: Array,
|
||||
default() {
|
||||
@@ -49,18 +101,6 @@ export default {
|
||||
default() {
|
||||
return undefined;
|
||||
}
|
||||
},
|
||||
readOnly: {
|
||||
type: Boolean,
|
||||
default() {
|
||||
return false;
|
||||
}
|
||||
},
|
||||
compact: {
|
||||
type: Boolean,
|
||||
default() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
},
|
||||
data() {
|
||||
@@ -94,15 +134,8 @@ export default {
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
objectPath: {
|
||||
handler(newPath, oldPath) {
|
||||
if (newPath === oldPath) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.setTimeContext();
|
||||
},
|
||||
deep: true
|
||||
keyString() {
|
||||
this.setTimeContext();
|
||||
},
|
||||
inputBounds: {
|
||||
handler(newBounds) {
|
||||
@@ -126,17 +159,19 @@ export default {
|
||||
this.handleNewBounds(this.timeContext.bounds());
|
||||
this.setViewFromOffsets(this.timeContext.clockOffsets());
|
||||
this.timeContext.on('bounds', this.handleNewBounds);
|
||||
this.timeContext.on('clock', this.clearAllValidation);
|
||||
this.timeContext.on('clockOffsets', this.setViewFromOffsets);
|
||||
},
|
||||
stopFollowingTime() {
|
||||
if (this.timeContext) {
|
||||
this.timeContext.off('bounds', this.handleNewBounds);
|
||||
this.timeContext.off('clock', this.clearAllValidation);
|
||||
this.timeContext.off('clockOffsets', this.setViewFromOffsets);
|
||||
}
|
||||
},
|
||||
setTimeContext() {
|
||||
this.stopFollowingTime();
|
||||
this.timeContext = this.openmct.time.getContextForView(this.objectPath);
|
||||
this.timeContext = this.openmct.time.getContextForView(this.keyString ? this.objectPath : []);
|
||||
this.followTime();
|
||||
},
|
||||
handleNewBounds(bounds) {
|
||||
@@ -144,6 +179,13 @@ export default {
|
||||
this.setViewFromBounds(bounds);
|
||||
this.updateCurrentValue();
|
||||
},
|
||||
clearAllValidation() {
|
||||
[this.$refs.startOffset, this.$refs.endOffset].forEach(this.clearValidationForInput);
|
||||
},
|
||||
clearValidationForInput(input) {
|
||||
input.setCustomValidity('');
|
||||
input.title = '';
|
||||
},
|
||||
setViewFromOffsets(offsets) {
|
||||
if (offsets) {
|
||||
this.offsets.start = this.durationFormatter.format(Math.abs(offsets.start));
|
||||
@@ -180,22 +222,86 @@ export default {
|
||||
format: key
|
||||
}).formatter;
|
||||
},
|
||||
timePopUpdate({ start, end }) {
|
||||
this.offsets.start = [start.hours, start.minutes, start.seconds].join(':');
|
||||
this.offsets.end = [end.hours, end.minutes, end.seconds].join(':');
|
||||
this.setOffsetsFromView();
|
||||
hideAllTimePopups() {
|
||||
this.showTCInputStart = false;
|
||||
this.showTCInputEnd = false;
|
||||
},
|
||||
setOffsetsFromView() {
|
||||
let startOffset = 0 - this.durationFormatter.parse(this.offsets.start);
|
||||
let endOffset = this.durationFormatter.parse(this.offsets.end);
|
||||
showTimePopupStart() {
|
||||
this.hideAllTimePopups();
|
||||
this.showTCInputStart = !this.showTCInputStart;
|
||||
},
|
||||
showTimePopupEnd() {
|
||||
this.hideAllTimePopups();
|
||||
this.showTCInputEnd = !this.showTCInputEnd;
|
||||
},
|
||||
timePopUpdate({ type, hours, minutes, seconds }) {
|
||||
this.offsets[type] = [hours, minutes, seconds].join(':');
|
||||
this.setOffsetsFromView();
|
||||
this.hideAllTimePopups();
|
||||
},
|
||||
setOffsetsFromView($event) {
|
||||
if (this.$refs.deltaInput.checkValidity()) {
|
||||
let startOffset = 0 - this.durationFormatter.parse(this.offsets.start);
|
||||
let endOffset = this.durationFormatter.parse(this.offsets.end);
|
||||
|
||||
this.$emit('updated', {
|
||||
start: startOffset,
|
||||
end: endOffset
|
||||
this.$emit('updated', {
|
||||
start: startOffset,
|
||||
end: endOffset
|
||||
});
|
||||
}
|
||||
|
||||
if ($event) {
|
||||
$event.preventDefault();
|
||||
|
||||
return false;
|
||||
}
|
||||
},
|
||||
validateAllBounds(ref) {
|
||||
if (!this.areBoundsFormatsValid()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
let validationResult = {
|
||||
valid: true
|
||||
};
|
||||
const currentInput = this.$refs[ref];
|
||||
|
||||
return [this.$refs.startDate, this.$refs.endDate].every((input) => {
|
||||
let boundsValues = {
|
||||
start: this.timeFormatter.parse(this.formattedBounds.start),
|
||||
end: this.timeFormatter.parse(this.formattedBounds.end)
|
||||
};
|
||||
//TODO: Do we need limits here? We have conductor limits disabled right now
|
||||
// const limit = this.getBoundsLimit();
|
||||
const limit = false;
|
||||
|
||||
if (this.timeSystem.isUTCBased && limit
|
||||
&& boundsValues.end - boundsValues.start > limit) {
|
||||
if (input === currentInput) {
|
||||
validationResult = {
|
||||
valid: false,
|
||||
message: "Start and end difference exceeds allowable limit"
|
||||
};
|
||||
}
|
||||
} else {
|
||||
if (input === currentInput) {
|
||||
validationResult = this.openmct.time.validateBounds(boundsValues);
|
||||
}
|
||||
}
|
||||
|
||||
return this.handleValidationResults(input, validationResult);
|
||||
});
|
||||
},
|
||||
dismiss() {
|
||||
this.$emit('dismiss');
|
||||
handleValidationResults(input, validationResult) {
|
||||
if (validationResult.valid !== true) {
|
||||
input.setCustomValidity(validationResult.message);
|
||||
input.title = validationResult.message;
|
||||
} else {
|
||||
input.setCustomValidity('');
|
||||
input.title = '';
|
||||
}
|
||||
|
||||
return validationResult.valid;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -22,15 +22,11 @@
|
||||
<template>
|
||||
<div
|
||||
ref="modeButton"
|
||||
class="c-tc-input-popup__options"
|
||||
class="c-ctrl-wrapper c-ctrl-wrapper--menus-up"
|
||||
>
|
||||
<div class="c-menu-button c-ctrl-wrapper c-ctrl-wrapper--menus-left">
|
||||
<button
|
||||
class="c-button--menu c-button--compact js-mode-button"
|
||||
:class="[
|
||||
buttonCssClass,
|
||||
selectedMode.cssClass
|
||||
]"
|
||||
class="c-button--menu c-mode-button"
|
||||
@click.prevent.stop="showModesMenu"
|
||||
>
|
||||
<span class="c-button__label">{{ selectedMode.name }}</span>
|
||||
@@ -45,15 +41,6 @@ import toggleMixin from '../../ui/mixins/toggle-mixin';
|
||||
export default {
|
||||
mixins: [toggleMixin],
|
||||
inject: ['openmct', 'configuration'],
|
||||
props: {
|
||||
buttonCssClass: {
|
||||
type: String,
|
||||
required: false,
|
||||
default() {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
},
|
||||
data: function () {
|
||||
let activeClock = this.openmct.time.clock();
|
||||
if (activeClock !== undefined) {
|
||||
@@ -147,10 +134,6 @@ export default {
|
||||
clockKey = undefined;
|
||||
}
|
||||
|
||||
let option = {
|
||||
clockKey
|
||||
};
|
||||
|
||||
let configuration = this.getMatchingConfig({
|
||||
clock: clockKey,
|
||||
timeSystem: this.openmct.time.timeSystem().key
|
||||
@@ -160,19 +143,16 @@ export default {
|
||||
configuration = this.getMatchingConfig({
|
||||
clock: clockKey
|
||||
});
|
||||
option.timeSystem = configuration.timeSystem;
|
||||
option.bounds = configuration.bounds;
|
||||
|
||||
this.openmct.time.timeSystem(configuration.timeSystem, configuration.bounds);
|
||||
}
|
||||
|
||||
if (clockKey === undefined) {
|
||||
// this.openmct.time.stopClock();
|
||||
this.openmct.time.stopClock();
|
||||
} else {
|
||||
const offsets = this.openmct.time.clockOffsets() || configuration.clockOffsets;
|
||||
option.offsets = offsets;
|
||||
// this.openmct.time.clock(clockKey, offsets);
|
||||
this.openmct.time.clock(clockKey, offsets);
|
||||
}
|
||||
|
||||
this.$emit('updated', option);
|
||||
},
|
||||
|
||||
getMatchingConfig(options) {
|
||||
|
||||
@@ -21,17 +21,6 @@
|
||||
*****************************************************************************/
|
||||
<template>
|
||||
<div class="c-clock-symbol">
|
||||
<svg
|
||||
class="c-clock-symbol__outer"
|
||||
viewBox="0 0 16 16"
|
||||
>
|
||||
<path
|
||||
d="M6 0L3 0C1.34315 0 0 1.34315 0 3V13C0 14.6569 1.34315 16 3 16H6V13H3V3H6V0Z"
|
||||
/>
|
||||
<path
|
||||
d="M10 13H13V3H10V0H13C14.6569 0 16 1.34315 16 3V13C16 14.6569 14.6569 16 13 16H10V13Z"
|
||||
/>
|
||||
</svg>
|
||||
<div class="hand-little"></div>
|
||||
<div class="hand-big"></div>
|
||||
</div>
|
||||
|
||||
@@ -1,210 +0,0 @@
|
||||
<template>
|
||||
<div
|
||||
:style="position"
|
||||
class="c-tc-input-popup"
|
||||
:class="modeClass"
|
||||
@click.stop
|
||||
@keydown.enter.prevent
|
||||
@keyup.enter.prevent="submit"
|
||||
@keydown.esc.prevent
|
||||
@keyup.esc.prevent="hide"
|
||||
>
|
||||
<div
|
||||
class="c-tc-input-popup__options"
|
||||
>
|
||||
<Mode
|
||||
v-if="isIndependent"
|
||||
class="c-button--compact c-conductor__mode-select"
|
||||
:mode="timeOptionMode"
|
||||
:button-css-class="'c-button--compact'"
|
||||
@modeChanged="saveIndependentMode"
|
||||
/>
|
||||
<ConductorMode
|
||||
v-else
|
||||
class="c-conductor__mode-select"
|
||||
:button-css-class="'c-icon-button'"
|
||||
@updated="saveMode"
|
||||
/>
|
||||
<!-- TODO: Time system and history must work even with ITC later -->
|
||||
<ConductorTimeSystem
|
||||
v-if="!isIndependent"
|
||||
class="c-conductor__time-system-select"
|
||||
:button-css-class="'c-icon-button'"
|
||||
/>
|
||||
<ConductorHistory
|
||||
v-if="!isIndependent"
|
||||
class="c-conductor__history-select"
|
||||
:button-css-class="'c-icon-button'"
|
||||
:offsets="timeOffsets"
|
||||
:bounds="bounds"
|
||||
:time-system="timeSystem"
|
||||
:mode="timeMode"
|
||||
/>
|
||||
</div>
|
||||
<conductor-inputs-fixed
|
||||
v-if="isFixed"
|
||||
:input-bounds="bounds"
|
||||
:object-path="objectPath"
|
||||
@updated="saveFixedBounds"
|
||||
@dismiss="dismiss"
|
||||
/>
|
||||
<conductor-inputs-realtime
|
||||
v-else
|
||||
:input-bounds="bounds"
|
||||
:object-path="objectPath"
|
||||
@updated="saveClockOffsets"
|
||||
@dismiss="dismiss"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import ConductorMode from './ConductorMode.vue';
|
||||
import Mode from './independent/Mode.vue';
|
||||
import ConductorTimeSystem from "./ConductorTimeSystem.vue";
|
||||
import ConductorHistory from "./ConductorHistory.vue";
|
||||
import ConductorInputsFixed from "./ConductorInputsFixed.vue";
|
||||
import ConductorInputsRealtime from "./ConductorInputsRealtime.vue";
|
||||
|
||||
export default {
|
||||
|
||||
components: {
|
||||
ConductorMode,
|
||||
Mode,
|
||||
ConductorTimeSystem,
|
||||
ConductorHistory,
|
||||
ConductorInputsFixed,
|
||||
ConductorInputsRealtime
|
||||
},
|
||||
inject: ['openmct', 'configuration'],
|
||||
props: {
|
||||
positionX: {
|
||||
type: Number,
|
||||
required: true
|
||||
},
|
||||
// positionY: {
|
||||
// type: Number,
|
||||
// required: true
|
||||
// },
|
||||
isIndependent: {
|
||||
type: Boolean,
|
||||
default() {
|
||||
return false;
|
||||
}
|
||||
},
|
||||
timeOptions: {
|
||||
type: Object,
|
||||
default() {
|
||||
return undefined;
|
||||
}
|
||||
},
|
||||
bottom: {
|
||||
type: Boolean,
|
||||
default() {
|
||||
return false;
|
||||
}
|
||||
},
|
||||
objectPath: {
|
||||
type: Array,
|
||||
default() {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
},
|
||||
data() {
|
||||
let bounds = this.openmct.time.bounds();
|
||||
let timeSystem = this.openmct.time.timeSystem();
|
||||
|
||||
return {
|
||||
timeSystem: timeSystem,
|
||||
bounds: {
|
||||
start: bounds.start,
|
||||
end: bounds.end
|
||||
},
|
||||
isFixed: this.openmct.time.clock() === undefined
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
position() {
|
||||
return {
|
||||
left: `${this.positionX}px`
|
||||
// top: `${this.positionY}px`
|
||||
};
|
||||
},
|
||||
timeOffsets() {
|
||||
return this.isFixed || !this.timeContext ? undefined : this.timeContext.clockOffsets();
|
||||
},
|
||||
timeMode() {
|
||||
return this.isFixed ? 'fixed' : 'realtime';
|
||||
},
|
||||
modeClass() {
|
||||
const value = this.bottom ? 'c-tc-input-popup--bottom' : '';
|
||||
|
||||
return this.isFixed ? `${value} c-tc-input-popup--fixed-mode` : `${value} c-tc-input-popup--realtime-mode`;
|
||||
},
|
||||
timeOptionMode() {
|
||||
return this.timeOptions?.mode;
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
objectPath: {
|
||||
handler(newPath, oldPath) {
|
||||
//domain object or view has probably changed
|
||||
if (newPath === oldPath) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.setTimeContext();
|
||||
},
|
||||
deep: true
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.setTimeContext();
|
||||
},
|
||||
beforeDestroy() {
|
||||
this.stopFollowingTimeContext();
|
||||
},
|
||||
methods: {
|
||||
setTimeContext() {
|
||||
this.stopFollowingTimeContext();
|
||||
this.timeContext = this.openmct.time.getContextForView(this.objectPath);
|
||||
this.timeContext.on('clock', this.setViewFromClock);
|
||||
this.timeContext.on('bounds', this.setBounds);
|
||||
this.setViewFromClock(this.timeContext.clock());
|
||||
this.setBounds(this.timeContext.bounds());
|
||||
},
|
||||
stopFollowingTimeContext() {
|
||||
if (this.timeContext) {
|
||||
this.timeContext.off('clock', this.setViewFromClock);
|
||||
this.timeContext.off('bounds', this.setBounds);
|
||||
}
|
||||
},
|
||||
setViewFromClock(clock) {
|
||||
this.isFixed = clock === undefined;
|
||||
this.bounds = this.timeContext.bounds();
|
||||
},
|
||||
setBounds() {
|
||||
this.bounds = this.timeContext.bounds();
|
||||
},
|
||||
saveFixedBounds(bounds) {
|
||||
this.$emit('fixedBoundsUpdated', bounds);
|
||||
},
|
||||
saveClockOffsets(offsets) {
|
||||
this.$emit('clockOffsetsUpdated', offsets);
|
||||
},
|
||||
saveMode(option) {
|
||||
this.$emit('modeUpdated', option);
|
||||
},
|
||||
saveIndependentMode(mode) {
|
||||
this.$emit('independentModeUpdated', mode);
|
||||
},
|
||||
dismiss() {
|
||||
this.$emit('dismiss');
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
</script>
|
||||
@@ -26,13 +26,11 @@
|
||||
class="c-ctrl-wrapper c-ctrl-wrapper--menus-up"
|
||||
>
|
||||
<button
|
||||
class="c-button--menu c-button--compact c-time-system-button"
|
||||
:class="[
|
||||
buttonCssClass
|
||||
]"
|
||||
class="c-button--menu c-time-system-button"
|
||||
:class="selectedTimeSystem.cssClass"
|
||||
@click.prevent.stop="showTimeSystemMenu"
|
||||
>
|
||||
{{ selectedTimeSystem.name }}
|
||||
<span class="c-button__label">{{ selectedTimeSystem.name }}</span>
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
@@ -40,15 +38,6 @@
|
||||
<script>
|
||||
export default {
|
||||
inject: ['openmct', 'configuration'],
|
||||
props: {
|
||||
buttonCssClass: {
|
||||
type: String,
|
||||
required: false,
|
||||
default() {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
},
|
||||
data: function () {
|
||||
let activeClock = this.openmct.time.clock();
|
||||
|
||||
|
||||
@@ -57,11 +57,11 @@
|
||||
}
|
||||
|
||||
.is-realtime-mode & {
|
||||
$c: 1px solid rgba($colorTimeRealtime, 0.7);
|
||||
$c: 1px solid rgba($colorTime, 0.7);
|
||||
border-left: $c;
|
||||
border-right: $c;
|
||||
svg text {
|
||||
fill: $colorTimeRealtime;
|
||||
fill: $colorTime;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,17 +33,22 @@
|
||||
|
||||
|
||||
.c-clock-symbol {
|
||||
$c: rgba($colorBodyFg, 0.5);
|
||||
$d: 16px;
|
||||
$c: $colorBtnBg; //$colorObjHdrIc;
|
||||
$d: 18px;
|
||||
height: $d;
|
||||
width: $d;
|
||||
position: relative;
|
||||
|
||||
&__outer {
|
||||
// SVG brackets shape
|
||||
&:before {
|
||||
font-family: symbolsfont;
|
||||
color: $c;
|
||||
content: $glyph-icon-brackets;
|
||||
font-size: $d;
|
||||
line-height: normal;
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
fill: $c;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
// Clock hands
|
||||
@@ -88,15 +93,14 @@
|
||||
// Modes
|
||||
.is-realtime-mode &,
|
||||
.is-lad-mode & {
|
||||
$c: $colorTimeRealtimeFgSubtle;
|
||||
.c-clock-symbol__outer {
|
||||
&:before {
|
||||
// Brackets icon
|
||||
fill: $c;
|
||||
color: $colorTime;
|
||||
}
|
||||
div[class*="hand"] {
|
||||
animation-name: clock-hands;
|
||||
&:before {
|
||||
background: $c;
|
||||
background: $colorTime;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
.c-conductor__mode-menu {
|
||||
max-height: 80vh;
|
||||
max-width: 500px;
|
||||
min-height: 50px;
|
||||
//We don't need the z-index now that we're using the popup
|
||||
//z-index: 70;
|
||||
min-height: 250px;
|
||||
z-index: 70;
|
||||
|
||||
[class*="__icon"] {
|
||||
filter: $colorKeyFilter;
|
||||
|
||||
@@ -9,15 +9,10 @@
|
||||
/*********************************************** CONDUCTOR LAYOUT */
|
||||
.c-conductor {
|
||||
&__inputs {
|
||||
display: flex;
|
||||
flex: 0 0 auto;
|
||||
|
||||
> * + * {
|
||||
margin-left: $interiorMargin;
|
||||
}
|
||||
display: contents;
|
||||
}
|
||||
|
||||
/* &__time-bounds {
|
||||
&__time-bounds {
|
||||
display: grid;
|
||||
grid-column-gap: $interiorMargin;
|
||||
grid-row-gap: $interiorMargin;
|
||||
@@ -44,17 +39,16 @@
|
||||
grid-area: tc-end;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}*/
|
||||
}
|
||||
|
||||
&__ticks {
|
||||
flex: 1 1 auto;
|
||||
grid-area: tc-ticks;
|
||||
}
|
||||
|
||||
&__controls {
|
||||
grid-area: tc-controls;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
> * + * {
|
||||
margin-left: $interiorMargin;
|
||||
}
|
||||
@@ -113,8 +107,7 @@
|
||||
background: rgba($timeConductorActiveBg, 0.4);
|
||||
border-left-color: $timeConductorActiveBg;
|
||||
border-right-color: $timeConductorActiveBg;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
top: 0; bottom: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -130,7 +123,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
/* body.phone.portrait & {
|
||||
body.phone.portrait & {
|
||||
.c-conductor__time-bounds {
|
||||
grid-row-gap: $interiorMargin;
|
||||
grid-template-rows: auto auto;
|
||||
@@ -167,8 +160,8 @@
|
||||
grid-template-areas:
|
||||
"tc-mode-icon tc-start tc-start"
|
||||
"tc-mode-icon tc-end tc-end"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.is-realtime-mode {
|
||||
.c-conductor__time-bounds {
|
||||
@@ -181,20 +174,21 @@
|
||||
justify-content: flex-end;
|
||||
}
|
||||
}
|
||||
}*/
|
||||
}
|
||||
}
|
||||
|
||||
.c-conductor-holder--compact {
|
||||
//min-height: 22px;
|
||||
flex: 0 1 auto;
|
||||
overflow: hidden;
|
||||
min-height: 22px;
|
||||
|
||||
.c-conductor {
|
||||
&__inputs,
|
||||
&__time-bounds {
|
||||
display: flex;
|
||||
flex: 0 1 auto;
|
||||
overflow: hidden;
|
||||
|
||||
.c-toggle-switch {
|
||||
// Used in independent Time Conductor
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
}
|
||||
|
||||
&__inputs {
|
||||
@@ -224,32 +218,38 @@
|
||||
margin-right: $interiorMarginSm;
|
||||
}
|
||||
|
||||
.c-direction-indicator {
|
||||
// Holds realtime-mode + and - symbols
|
||||
font-size: 0.7em;
|
||||
}
|
||||
|
||||
input:invalid {
|
||||
background: rgba($colorFormInvalid, 0.5);
|
||||
}
|
||||
}
|
||||
|
||||
.is-realtime-mode {
|
||||
.c-conductor__controls button,
|
||||
.c-conductor__delta-button {
|
||||
//@include themedButton($colorTimeRealtimeBg);
|
||||
color: $colorTimeRealtimeFg;
|
||||
@include themedButton($colorTimeBg);
|
||||
color: $colorTimeFg;
|
||||
}
|
||||
|
||||
.c-conductor-input {
|
||||
&:before {
|
||||
color: $colorTimeRealtimeFgSubtle;
|
||||
color: $colorTime;
|
||||
}
|
||||
}
|
||||
|
||||
.c-conductor__end-fixed {
|
||||
// Displays last RT update
|
||||
color: $colorTimeRealtimeFgSubtle;
|
||||
// Displays last RT udpate
|
||||
color: $colorTime;
|
||||
|
||||
input {
|
||||
// Remove input look
|
||||
background: none;
|
||||
box-shadow: none;
|
||||
color: $colorTimeRealtimeFgSubtle;
|
||||
color: $colorTime;
|
||||
pointer-events: none;
|
||||
|
||||
&[disabled] {
|
||||
@@ -259,7 +259,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
//TODO: Do we need this?
|
||||
[class^='pr-tc-input-menu'] {
|
||||
// Uses ^= here to target both start and end menus
|
||||
background: $colorBodyBg;
|
||||
@@ -282,250 +281,30 @@
|
||||
}
|
||||
}
|
||||
|
||||
.l-shell__time-conductor .c-tc-input-popup--end {
|
||||
.l-shell__time-conductor .pr-tc-input-menu--end {
|
||||
left: auto;
|
||||
right: 0;
|
||||
}
|
||||
|
||||
.pr-time-label {
|
||||
font-size: 0.9em;
|
||||
text-transform: uppercase;
|
||||
|
||||
&:before {
|
||||
[class^='pr-time'] {
|
||||
&[class*='label'] {
|
||||
font-size: 0.8em;
|
||||
margin-right: $interiorMarginSm;
|
||||
}
|
||||
}
|
||||
|
||||
.pr-time-input {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
white-space: nowrap;
|
||||
|
||||
> * + * {
|
||||
margin-left: $interiorMarginSm;
|
||||
opacity: 0.6;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
input {
|
||||
height: 22px;
|
||||
line-height: 1em;
|
||||
font-size: 1.25em;
|
||||
}
|
||||
|
||||
&--date input {
|
||||
width: 120px;
|
||||
}
|
||||
|
||||
&--time input {
|
||||
width: 70px;
|
||||
}
|
||||
|
||||
&--buttons {
|
||||
> * + * {
|
||||
margin-left: $interiorMargin;
|
||||
}
|
||||
}
|
||||
|
||||
&__start-end-sep {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
&--input-and-button {
|
||||
@include wrappedInput();
|
||||
}
|
||||
}
|
||||
|
||||
/*********************************************** COMPACT TIME CONDUCTOR */
|
||||
.c-compact-tc,
|
||||
.c-tc-input-popup {
|
||||
[class*='start-end-sep'] {
|
||||
opacity: 0.5;
|
||||
}
|
||||
}
|
||||
|
||||
.c-compact-tc {
|
||||
border-radius: $controlCr;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex: 0 1 auto;
|
||||
overflow: hidden;
|
||||
align-items: center;
|
||||
padding: 2px $interiorMarginSm;
|
||||
|
||||
> * + * {
|
||||
margin-left: $interiorMargin;
|
||||
}
|
||||
|
||||
&__bounds,
|
||||
&__bounds__value {
|
||||
&[class*='controls'] {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
white-space: nowrap;
|
||||
|
||||
> * + * {
|
||||
margin-left: $interiorMargin;
|
||||
}
|
||||
}
|
||||
|
||||
&__bounds {
|
||||
cursor: pointer;
|
||||
flex: 0 1 auto;
|
||||
overflow: hidden;
|
||||
|
||||
> * + * {
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
}
|
||||
|
||||
&__bounds__value {
|
||||
@include ellipsize();
|
||||
color: $colorTimeRealtimeFg;
|
||||
flex: 0 1 auto;
|
||||
|
||||
&:before {
|
||||
font-size: 0.85em;
|
||||
input {
|
||||
height: 22px;
|
||||
line-height: 22px;
|
||||
margin-right: $interiorMarginSm;
|
||||
}
|
||||
}
|
||||
|
||||
&__current-update {
|
||||
@include ellipsize();
|
||||
flex: 0 1 auto;
|
||||
}
|
||||
|
||||
.c-direction-indicator {
|
||||
// Holds realtime-mode + and - symbols
|
||||
font-size: 0.7em;
|
||||
}
|
||||
|
||||
.c-toggle-switch,
|
||||
.c-clock-symbol {
|
||||
// Used in independent Time Conductor
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
.c-so-view & {
|
||||
// Time Conductor in a Layout frame
|
||||
.c-clock-symbol {
|
||||
$h: 14px;
|
||||
height: $h;
|
||||
width: $h;
|
||||
}
|
||||
|
||||
[class*='button'] {
|
||||
$p: 0px;
|
||||
padding: $p $p + 1;
|
||||
font-size: 1.25em;
|
||||
width: 42px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.is-fixed-mode.is-expanded {
|
||||
&.c-compact-tc,
|
||||
.c-tc-input-popup {
|
||||
background: $colorTimeFixedBg;
|
||||
color: $colorTimeFixedFgSubtle;
|
||||
|
||||
em,
|
||||
.pr-time-label:before {
|
||||
color: $colorTimeFixedFg;
|
||||
}
|
||||
|
||||
&__bounds__valuelue {
|
||||
color: $colorTimeFixedFg;
|
||||
}
|
||||
|
||||
&__time-value {
|
||||
color: $colorTimeFixedFg;
|
||||
}
|
||||
|
||||
[class*='c-button'] {
|
||||
background: $colorTimeFixedBtnBg;
|
||||
color: $colorTimeFixedBtnFg;
|
||||
|
||||
[class*='label'] {
|
||||
color: $colorTimeRealtimeFg;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.is-realtime-mode.is-expanded {
|
||||
&.c-compact-tc,
|
||||
.c-tc-input-popup {
|
||||
background: rgba($colorTimeRealtimeBg, 1);
|
||||
color: $colorTimeRealtimeFgSubtle;
|
||||
|
||||
em,
|
||||
.pr-time-label:before {
|
||||
color: $colorTimeRealtimeFg;
|
||||
}
|
||||
|
||||
&__bounds__valuelue {
|
||||
color: $colorTimeRealtimeFg;
|
||||
}
|
||||
|
||||
&__time-value {
|
||||
color: $colorTimeRealtimeFg;
|
||||
}
|
||||
|
||||
[class*='c-button'] {
|
||||
background: $colorTimeRealtimeBtnBg;
|
||||
color: $colorTimeRealtimeBtnFg;
|
||||
|
||||
[class*='label'] {
|
||||
color: $colorTimeRealtimeFg;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.c-compact-tc {
|
||||
&.l-shell__time-conductor {
|
||||
// Main view
|
||||
min-height: 24px;
|
||||
}
|
||||
}
|
||||
|
||||
/*********************************************** INPUTS POPUP DIALOG */
|
||||
.c-tc-input-popup {
|
||||
@include menuOuter();
|
||||
padding: $interiorMarginLg;
|
||||
position: absolute;
|
||||
width: min-content;
|
||||
bottom: 35px;
|
||||
|
||||
> * + * {
|
||||
margin-top: $interiorMarginLg;
|
||||
}
|
||||
|
||||
&[class*='--bottom'] {
|
||||
bottom: auto;
|
||||
top: 35px;
|
||||
}
|
||||
|
||||
&__options {
|
||||
display: flex;
|
||||
|
||||
> * + * {
|
||||
margin-left: $interiorMargin;
|
||||
}
|
||||
}
|
||||
|
||||
&--fixed-mode {
|
||||
.c-tc-input-popup__input-grid {
|
||||
grid-template-columns: 1fr 1fr 1fr 1fr 1fr 2fr;
|
||||
}
|
||||
}
|
||||
|
||||
&--realtime-mode {
|
||||
.c-tc-input-popup__input-grid {
|
||||
grid-template-columns: 1fr 1fr 1fr 1fr 1fr 1fr 1fr 2fr;
|
||||
}
|
||||
}
|
||||
|
||||
&__input-grid {
|
||||
display: grid;
|
||||
grid-column-gap: 3px;
|
||||
grid-row-gap: $interiorMargin;
|
||||
align-items: start;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,136 +0,0 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT, Copyright (c) 2014-2023, 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 raf from '@/utils/raf';
|
||||
import Vue from "vue";
|
||||
import ConductorPopUp from "./ConductorPopUp.vue";
|
||||
|
||||
export default {
|
||||
inject: ['openmct', 'configuration'],
|
||||
mounted() {
|
||||
this.showPopup = this.showPopup.bind(this);
|
||||
this.clearPopup = this.clearPopup.bind(this);
|
||||
this.positionBox = this.positionBox.bind(this);
|
||||
this.positionBox = raf(this.positionBox);
|
||||
this.timeConductorOptionsHolder = this.$refs.timeConductorOptionsHolder;
|
||||
this.registerPopUp();
|
||||
this.popupComponent = this.createPopupComponent();
|
||||
},
|
||||
beforeDestroy() {
|
||||
this.removePopup();
|
||||
},
|
||||
methods: {
|
||||
showPopup() {
|
||||
const popupElement = this.popupComponent;
|
||||
|
||||
document.body.appendChild(popupElement.$el);
|
||||
//Use capture, so we don't trigger immediately on the same iteration of the event loop
|
||||
document.addEventListener('click', this.clearPopup, {
|
||||
capture: true
|
||||
});
|
||||
|
||||
this.positionBox();
|
||||
|
||||
window.addEventListener('resize', this.positionBox);
|
||||
},
|
||||
|
||||
positionBox() {
|
||||
const popupElement = this.popupComponent;
|
||||
const timeConductorOptions = this.timeConductorOptionsHolder;
|
||||
|
||||
let timeConductorOptionsBox = timeConductorOptions.getBoundingClientRect();
|
||||
popupElement.positionX = timeConductorOptionsBox.left;
|
||||
//TODO: PositionY should be calculated to be top or bottom based on the location of the conductor options
|
||||
popupElement.positionY = timeConductorOptionsBox.top;
|
||||
const offsetTop = popupElement.$el.getBoundingClientRect().height;
|
||||
|
||||
const popupRight = popupElement.positionX + popupElement.$el.clientWidth;
|
||||
const offsetLeft = Math.min(window.innerWidth - popupRight, 0);
|
||||
|
||||
popupElement.positionX = popupElement.positionX + offsetLeft;
|
||||
popupElement.positionY = popupElement.positionY - offsetTop;
|
||||
},
|
||||
|
||||
clearPopup(clickAwayEvent) {
|
||||
if (this.canClose(clickAwayEvent)) {
|
||||
clickAwayEvent.stopPropagation();
|
||||
this.removePopup();
|
||||
}
|
||||
},
|
||||
canClose(clickAwayEvent) {
|
||||
const popupElement = this.popupComponent;
|
||||
|
||||
const isChildMenu = clickAwayEvent.target.closest('.c-menu') !== null;
|
||||
const isPopupElementItem = popupElement.$el.contains(clickAwayEvent.target);
|
||||
|
||||
return !isChildMenu && !isPopupElementItem;
|
||||
},
|
||||
removePopup() {
|
||||
const popupElement = this.popupComponent;
|
||||
popupElement.$el.remove();
|
||||
document.removeEventListener('click', this.clearPopup, {
|
||||
capture: true
|
||||
});
|
||||
window.removeEventListener('resize', this.positionBox);
|
||||
},
|
||||
|
||||
createPopupComponent() {
|
||||
const saveFixedBounds = this.saveFixedBounds;
|
||||
const saveClockOffsets = this.saveClockOffsets;
|
||||
const saveMode = this.saveMode;
|
||||
const removePopup = this.removePopup;
|
||||
|
||||
const popupElement = new Vue({
|
||||
components: {
|
||||
ConductorPopUp
|
||||
},
|
||||
provide: {
|
||||
openmct: this.openmct,
|
||||
configuration: this.configuration
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
positionX: 0,
|
||||
positionY: 0,
|
||||
saveClockOffsets,
|
||||
saveFixedBounds,
|
||||
saveMode,
|
||||
removePopup
|
||||
};
|
||||
},
|
||||
template: `<conductor-pop-up
|
||||
@dismiss="removePopup()"
|
||||
@modeUpdated="saveMode"
|
||||
@fixedBoundsUpdated="saveFixedBounds"
|
||||
@clockOffsetsUpdated="saveClockOffsets"
|
||||
:bottom="false"
|
||||
:positionX="positionX"
|
||||
:positionY="positionY" />`
|
||||
}).$mount();
|
||||
|
||||
return popupElement;
|
||||
},
|
||||
|
||||
registerPopUp() {
|
||||
this.timeConductorOptionsHolder.addEventListener('click', this.showPopup);
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -21,39 +21,49 @@
|
||||
*****************************************************************************/
|
||||
<template>
|
||||
<div
|
||||
ref="timeConductorOptionsHolder"
|
||||
class="c-compact-tc"
|
||||
class="c-conductor"
|
||||
:class="[
|
||||
isFixed ? 'is-fixed-mode' : independentTCEnabled ? 'is-realtime-mode' : 'is-fixed-mode',
|
||||
{ 'is-expanded' : independentTCEnabled }
|
||||
isFixed ? 'is-fixed-mode' : independentTCEnabled ? 'is-realtime-mode' : 'is-fixed-mode'
|
||||
]"
|
||||
>
|
||||
<toggle-switch
|
||||
id="independentTCToggle"
|
||||
class="c-toggle-switch--mini"
|
||||
:checked="independentTCEnabled"
|
||||
:title="`${independentTCEnabled ? 'Disable' : 'Enable'} independent Time Conductor`"
|
||||
@change="toggleIndependentTC"
|
||||
/>
|
||||
<div class="c-conductor__time-bounds">
|
||||
<toggle-switch
|
||||
id="independentTCToggle"
|
||||
:checked="independentTCEnabled"
|
||||
:title="`${independentTCEnabled ? 'Disable' : 'Enable'} independent Time Conductor`"
|
||||
@change="toggleIndependentTC"
|
||||
/>
|
||||
|
||||
<ConductorModeIcon v-if="independentTCEnabled" />
|
||||
<ConductorModeIcon />
|
||||
|
||||
<conductor-inputs-fixed
|
||||
v-if="isFixed && independentTCEnabled"
|
||||
class="c-compact-tc__bounds--fixed"
|
||||
:object-path="objectPath"
|
||||
:read-only="true"
|
||||
:compact="true"
|
||||
/>
|
||||
<div
|
||||
v-if="timeOptions && independentTCEnabled"
|
||||
class="c-conductor__controls"
|
||||
>
|
||||
<Mode
|
||||
v-if="mode"
|
||||
class="c-conductor__mode-select"
|
||||
:key-string="domainObject.identifier.key"
|
||||
:mode="timeOptions.mode"
|
||||
:enabled="independentTCEnabled"
|
||||
@modeChanged="saveMode"
|
||||
/>
|
||||
|
||||
<conductor-inputs-realtime
|
||||
v-if="!isFixed && independentTCEnabled"
|
||||
class="c-compact-tc__bounds--real-time"
|
||||
:object-path="objectPath"
|
||||
:read-only="true"
|
||||
:compact="true"
|
||||
/>
|
||||
<div class="c-not-button c-not-button--compact c-compact-tc__gear icon-gear"></div>
|
||||
<conductor-inputs-fixed
|
||||
v-if="isFixed"
|
||||
:key-string="domainObject.identifier.key"
|
||||
:object-path="objectPath"
|
||||
@updated="saveFixedOffsets"
|
||||
/>
|
||||
|
||||
<conductor-inputs-realtime
|
||||
v-else
|
||||
:key-string="domainObject.identifier.key"
|
||||
:object-path="objectPath"
|
||||
@updated="saveClockOffsets"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -62,16 +72,16 @@ import ConductorInputsFixed from "../ConductorInputsFixed.vue";
|
||||
import ConductorInputsRealtime from "../ConductorInputsRealtime.vue";
|
||||
import ConductorModeIcon from "@/plugins/timeConductor/ConductorModeIcon.vue";
|
||||
import ToggleSwitch from '../../../ui/components/ToggleSwitch.vue';
|
||||
import independentTimeConductorPopUpManager from "./independentTimeConductorPopUpManager";
|
||||
import Mode from "./Mode.vue";
|
||||
|
||||
export default {
|
||||
components: {
|
||||
Mode,
|
||||
ConductorModeIcon,
|
||||
ConductorInputsRealtime,
|
||||
ConductorInputsFixed,
|
||||
ToggleSwitch
|
||||
},
|
||||
mixins: [independentTimeConductorPopUpManager],
|
||||
inject: ['openmct'],
|
||||
props: {
|
||||
domainObject: {
|
||||
@@ -84,19 +94,13 @@ export default {
|
||||
}
|
||||
},
|
||||
data() {
|
||||
const bounds = this.openmct.time.bounds();
|
||||
|
||||
return {
|
||||
timeOptions: this.domainObject.configuration.timeOptions || {
|
||||
clockOffsets: this.openmct.time.clockOffsets(),
|
||||
fixedOffsets: this.openmct.time.bounds()
|
||||
},
|
||||
mode: undefined,
|
||||
independentTCEnabled: this.domainObject.configuration.useIndependentTime === true,
|
||||
viewBounds: {
|
||||
start: bounds.start,
|
||||
end: bounds.end
|
||||
}
|
||||
independentTCEnabled: this.domainObject.configuration.useIndependentTime === true
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
@@ -126,13 +130,6 @@ export default {
|
||||
}
|
||||
},
|
||||
deep: true
|
||||
},
|
||||
objectPath: {
|
||||
handler() {
|
||||
//domain object or view has probably changed
|
||||
this.setTimeContext();
|
||||
},
|
||||
deep: true
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
@@ -166,7 +163,6 @@ export default {
|
||||
if (this.independentTCEnabled) {
|
||||
this.registerIndependentTimeOffsets();
|
||||
} else {
|
||||
this.removePopup();
|
||||
this.destroyIndependentTime();
|
||||
}
|
||||
|
||||
@@ -191,10 +187,11 @@ export default {
|
||||
this.registerIndependentTimeOffsets();
|
||||
}
|
||||
},
|
||||
saveFixedBounds(bounds) {
|
||||
saveFixedOffsets(offsets) {
|
||||
const newOptions = Object.assign({}, this.timeOptions, {
|
||||
fixedOffsets: bounds
|
||||
fixedOffsets: offsets
|
||||
});
|
||||
|
||||
this.updateTimeOptions(newOptions);
|
||||
},
|
||||
saveClockOffsets(offsets) {
|
||||
|
||||
@@ -28,11 +28,7 @@
|
||||
<div class="c-menu-button c-ctrl-wrapper c-ctrl-wrapper--menus-left">
|
||||
<button
|
||||
v-if="selectedMode"
|
||||
class="c-icon-button c-button--menu js-mode-button"
|
||||
:class="[
|
||||
buttonCssClass,
|
||||
selectedMode.cssClass
|
||||
]"
|
||||
class="c-button--menu c-mode-button"
|
||||
@click.prevent.stop="showModesMenu"
|
||||
>
|
||||
<span class="c-button__label">{{ selectedMode.name }}</span>
|
||||
@@ -59,13 +55,6 @@ export default {
|
||||
default() {
|
||||
return false;
|
||||
}
|
||||
},
|
||||
buttonCssClass: {
|
||||
type: String,
|
||||
required: false,
|
||||
default() {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
},
|
||||
data: function () {
|
||||
|
||||
@@ -1,147 +0,0 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT, Copyright (c) 2014-2023, 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 raf from '@/utils/raf';
|
||||
import Vue from "vue";
|
||||
import ConductorPopUp from "../ConductorPopUp.vue";
|
||||
|
||||
export default {
|
||||
inject: ['openmct'],
|
||||
mounted() {
|
||||
this.showPopup = this.showPopup.bind(this);
|
||||
this.clearPopup = this.clearPopup.bind(this);
|
||||
this.positionBox = this.positionBox.bind(this);
|
||||
this.positionBox = raf(this.positionBox);
|
||||
this.timeConductorOptionsHolder = this.$refs.timeConductorOptionsHolder;
|
||||
this.registerPopUp();
|
||||
this.popupComponent = this.createPopupComponent();
|
||||
},
|
||||
beforeDestroy() {
|
||||
this.removePopup();
|
||||
},
|
||||
methods: {
|
||||
showPopup() {
|
||||
if (!this.independentTCEnabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
const popupElement = this.popupComponent;
|
||||
|
||||
document.body.appendChild(popupElement.$el);
|
||||
//Use capture, so we don't trigger immediately on the same iteration of the event loop
|
||||
document.addEventListener('click', this.clearPopup, {
|
||||
capture: true
|
||||
});
|
||||
|
||||
this.positionBox();
|
||||
|
||||
window.addEventListener('resize', this.positionBox);
|
||||
},
|
||||
|
||||
positionBox() {
|
||||
const popupElement = this.popupComponent;
|
||||
const timeConductorOptions = this.timeConductorOptionsHolder;
|
||||
|
||||
let timeConductorOptionsBox = timeConductorOptions.getBoundingClientRect();
|
||||
popupElement.positionX = timeConductorOptionsBox.left;
|
||||
//TODO: PositionY should be calculated to be top or bottom based on the location of the conductor options
|
||||
popupElement.positionY = timeConductorOptionsBox.top;
|
||||
const offsetTop = popupElement.$el.getBoundingClientRect().height;
|
||||
|
||||
const popupRight = popupElement.positionX + popupElement.$el.clientWidth;
|
||||
const offsetLeft = Math.min(window.innerWidth - popupRight, 0);
|
||||
|
||||
popupElement.positionX = popupElement.positionX + offsetLeft;
|
||||
popupElement.positionY = popupElement.positionY - offsetTop;
|
||||
},
|
||||
|
||||
clearPopup(clickAwayEvent) {
|
||||
if (this.canClose(clickAwayEvent)) {
|
||||
clickAwayEvent.stopPropagation();
|
||||
this.removePopup();
|
||||
}
|
||||
},
|
||||
canClose(clickAwayEvent) {
|
||||
const popupElement = this.popupComponent;
|
||||
|
||||
const isChildMenu = clickAwayEvent.target.closest('.c-menu') !== null;
|
||||
const isPopupElementItem = popupElement.$el.contains(clickAwayEvent.target);
|
||||
|
||||
return !isChildMenu && !isPopupElementItem;
|
||||
},
|
||||
removePopup() {
|
||||
const popupElement = this.popupComponent;
|
||||
document.removeEventListener('click', this.clearPopup, {
|
||||
capture: true
|
||||
});
|
||||
window.removeEventListener('resize', this.positionBox);
|
||||
popupElement.$el.remove();
|
||||
},
|
||||
|
||||
createPopupComponent() {
|
||||
const saveFixedBounds = this.saveFixedBounds;
|
||||
const saveClockOffsets = this.saveClockOffsets;
|
||||
const saveMode = this.saveMode;
|
||||
const removePopup = this.removePopup;
|
||||
const objectPath = this.objectPath;
|
||||
const timeOptions = this.timeOptions;
|
||||
|
||||
const popupElement = new Vue({
|
||||
components: {
|
||||
ConductorPopUp
|
||||
},
|
||||
provide: {
|
||||
openmct: this.openmct,
|
||||
configuration: undefined
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
positionX: 0,
|
||||
positionY: 0,
|
||||
saveClockOffsets,
|
||||
saveFixedBounds,
|
||||
saveMode,
|
||||
removePopup,
|
||||
timeOptions,
|
||||
objectPath
|
||||
};
|
||||
},
|
||||
template: `<conductor-pop-up
|
||||
@dismiss="removePopup()"
|
||||
@independentModeUpdated="saveMode"
|
||||
@fixedBoundsUpdated="saveFixedBounds"
|
||||
@clockOffsetsUpdated="saveClockOffsets"
|
||||
:object-path="objectPath"
|
||||
:is-independent="true"
|
||||
:time-options="timeOptions"
|
||||
:bottom="true"
|
||||
:positionX="positionX"
|
||||
:positionY="positionY" />`
|
||||
}).$mount();
|
||||
|
||||
return popupElement;
|
||||
},
|
||||
|
||||
registerPopUp() {
|
||||
this.timeConductorOptionsHolder.addEventListener('click', this.showPopup);
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -106,7 +106,7 @@ describe('time conductor', () => {
|
||||
|
||||
describe('in realtime mode', () => {
|
||||
beforeEach((done) => {
|
||||
const switcher = appHolder.querySelector('.js-mode-button');
|
||||
const switcher = appHolder.querySelector('.c-mode-button');
|
||||
const clickEvent = createMouseEvent("click");
|
||||
|
||||
switcher.dispatchEvent(clickEvent);
|
||||
|
||||
@@ -1,160 +1,96 @@
|
||||
<template>
|
||||
<div
|
||||
class="c-tc-input-popup"
|
||||
:class="{'c-tc-input-popup--bottom' : bottom === true}"
|
||||
class="pr-tc-input-menu"
|
||||
:class="{'pr-tc-input-menu--bottom' : bottom === true}"
|
||||
@keydown.enter.prevent
|
||||
@keyup.enter.prevent="submit"
|
||||
@keydown.esc.prevent
|
||||
@keyup.esc.prevent="hide"
|
||||
@click.stop
|
||||
>
|
||||
<slot></slot>
|
||||
<div
|
||||
v-if="false"
|
||||
>
|
||||
<div class="c-tc-input-popup__options c-tc-input-popup__options--real-time">
|
||||
Buttons here
|
||||
</div>
|
||||
<div class="pr-time-label__hrs">Hrs</div>
|
||||
<div class="pr-time-label__mins">Mins</div>
|
||||
<div class="pr-time-label__secs">Secs</div>
|
||||
|
||||
<div class="c-tc-input-popup__input-grid">
|
||||
<div class="pr-time-label icon-line-horz">Hrs</div>
|
||||
<div class="pr-time-label">Mins</div>
|
||||
<div class="pr-time-label">Secs</div>
|
||||
<div class="pr-time-label"></div>
|
||||
<div class="pr-time-label icon-plus">Hrs</div>
|
||||
<div class="pr-time-label">Mins</div>
|
||||
<div class="pr-time-label">Secs</div>
|
||||
<div class="pr-time-label"></div>
|
||||
|
||||
<div class="pr-time-input">
|
||||
<input
|
||||
ref="inputHrs"
|
||||
v-model="inputHrs"
|
||||
class="pr-time-input__hrs"
|
||||
step="1"
|
||||
type="number"
|
||||
min="0"
|
||||
max="23"
|
||||
title="Enter 0 - 23"
|
||||
@change="validate()"
|
||||
@keyup="validate()"
|
||||
@focusin="selectAll($event)"
|
||||
@focusout="format('inputHrs')"
|
||||
@wheel="increment($event, 'inputHrs')"
|
||||
>
|
||||
:
|
||||
</div>
|
||||
<div class="pr-time-input">
|
||||
<input
|
||||
ref="inputMins"
|
||||
v-model="inputMins"
|
||||
type="number"
|
||||
class="pr-time-input__mins"
|
||||
min="0"
|
||||
max="59"
|
||||
title="Enter 0 - 59"
|
||||
step="1"
|
||||
@change="validate()"
|
||||
@keyup="validate()"
|
||||
@focusin="selectAll($event)"
|
||||
@focusout="format('inputMins')"
|
||||
@wheel="increment($event, 'inputMins')"
|
||||
>
|
||||
:
|
||||
</div>
|
||||
<div class="pr-time-input">
|
||||
<input
|
||||
ref="inputSecs"
|
||||
v-model="inputSecs"
|
||||
type="number"
|
||||
class="pr-time-input__secs"
|
||||
min="0"
|
||||
max="59"
|
||||
title="Enter 0 - 59"
|
||||
step="1"
|
||||
@change="validate()"
|
||||
@keyup="validate()"
|
||||
@focusin="selectAll($event)"
|
||||
@focusout="format('inputSecs')"
|
||||
@wheel="increment($event, 'inputSecs')"
|
||||
>
|
||||
</div>
|
||||
|
||||
<div class="pr-time-input pr-time-input__start-end-sep icon-arrows-right-left"></div>
|
||||
|
||||
<div class="pr-time-input">
|
||||
<input
|
||||
ref="inputHrs"
|
||||
v-model="inputHrs"
|
||||
class="pr-time-input__hrs"
|
||||
step="1"
|
||||
type="number"
|
||||
min="0"
|
||||
max="23"
|
||||
title="Enter 0 - 23"
|
||||
@change="validate()"
|
||||
@keyup="validate()"
|
||||
@focusin="selectAll($event)"
|
||||
@focusout="format('inputHrs')"
|
||||
@wheel="increment($event, 'inputHrs')"
|
||||
>
|
||||
:
|
||||
</div>
|
||||
<div class="pr-time-input">
|
||||
<input
|
||||
ref="inputMins"
|
||||
v-model="inputMins"
|
||||
type="number"
|
||||
class="pr-time-input__mins"
|
||||
min="0"
|
||||
max="59"
|
||||
title="Enter 0 - 59"
|
||||
step="1"
|
||||
@change="validate()"
|
||||
@keyup="validate()"
|
||||
@focusin="selectAll($event)"
|
||||
@focusout="format('inputMins')"
|
||||
@wheel="increment($event, 'inputMins')"
|
||||
>
|
||||
:
|
||||
</div>
|
||||
<div class="pr-time-input pr-time-input--buttons">
|
||||
<input
|
||||
ref="inputSecs"
|
||||
v-model="inputSecs"
|
||||
type="number"
|
||||
class="pr-time-input__secs"
|
||||
min="0"
|
||||
max="59"
|
||||
title="Enter 0 - 59"
|
||||
step="1"
|
||||
@change="validate()"
|
||||
@keyup="validate()"
|
||||
@focusin="selectAll($event)"
|
||||
@focusout="format('inputSecs')"
|
||||
@wheel="increment($event, 'inputSecs')"
|
||||
>
|
||||
</div>
|
||||
|
||||
<div class="pr-time-input pr-time-input--buttons">
|
||||
<button
|
||||
class="c-button c-button--major icon-check"
|
||||
:disabled="isDisabled"
|
||||
@click.prevent="submit"
|
||||
></button>
|
||||
<button
|
||||
class="c-button icon-x"
|
||||
@click.prevent="hide"
|
||||
></button>
|
||||
</div>
|
||||
<div class="pr-time-controls">
|
||||
<input
|
||||
ref="inputHrs"
|
||||
v-model="inputHrs"
|
||||
class="pr-time-controls__hrs"
|
||||
step="1"
|
||||
type="number"
|
||||
min="0"
|
||||
max="23"
|
||||
title="Enter 0 - 23"
|
||||
@change="validate()"
|
||||
@keyup="validate()"
|
||||
@focusin="selectAll($event)"
|
||||
@focusout="format('inputHrs')"
|
||||
@wheel="increment($event, 'inputHrs')"
|
||||
>
|
||||
:
|
||||
</div>
|
||||
<div class="pr-time-controls">
|
||||
<input
|
||||
ref="inputMins"
|
||||
v-model="inputMins"
|
||||
type="number"
|
||||
class="pr-time-controls__mins"
|
||||
min="0"
|
||||
max="59"
|
||||
title="Enter 0 - 59"
|
||||
step="1"
|
||||
@change="validate()"
|
||||
@keyup="validate()"
|
||||
@focusin="selectAll($event)"
|
||||
@focusout="format('inputMins')"
|
||||
@wheel="increment($event, 'inputMins')"
|
||||
>
|
||||
:
|
||||
</div>
|
||||
<div class="pr-time-controls">
|
||||
<input
|
||||
ref="inputSecs"
|
||||
v-model="inputSecs"
|
||||
type="number"
|
||||
class="pr-time-controls__secs"
|
||||
min="0"
|
||||
max="59"
|
||||
title="Enter 0 - 59"
|
||||
step="1"
|
||||
@change="validate()"
|
||||
@keyup="validate()"
|
||||
@focusin="selectAll($event)"
|
||||
@focusout="format('inputSecs')"
|
||||
@wheel="increment($event, 'inputSecs')"
|
||||
>
|
||||
<div class="pr-time__buttons">
|
||||
<button
|
||||
class="c-button c-button--major icon-check"
|
||||
:disabled="isDisabled"
|
||||
@click.prevent="submit"
|
||||
></button>
|
||||
<button
|
||||
class="c-button icon-x"
|
||||
@click.prevent="hide"
|
||||
></button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
export default {
|
||||
props: {
|
||||
type: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
offset: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
bottom: {
|
||||
type: Boolean,
|
||||
default() {
|
||||
@@ -170,11 +106,6 @@ export default {
|
||||
isDisabled: false
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
isRealtime() {
|
||||
return this.mode.indexOf('realtime') !== -1;
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.setOffset();
|
||||
document.addEventListener('click', this.hide);
|
||||
|
||||
@@ -1,314 +0,0 @@
|
||||
<template>
|
||||
<form
|
||||
ref="fixedDeltaInput"
|
||||
class="c-tc-input-popup__input-grid"
|
||||
>
|
||||
<div class="pr-time-label"><em>Start</em> Date</div>
|
||||
<div class="pr-time-label">Time Z</div>
|
||||
<div class="pr-time-label"></div>
|
||||
<div class="pr-time-label"><em>End</em> Date</div>
|
||||
<div class="pr-time-label">Time Z</div>
|
||||
<div class="pr-time-label"></div>
|
||||
|
||||
<div class="pr-time-input pr-time-input--date pr-time-input--input-and-button">
|
||||
<input
|
||||
ref="startDate"
|
||||
v-model="formattedBounds.start"
|
||||
class="c-input--datetime"
|
||||
type="text"
|
||||
autocorrect="off"
|
||||
spellcheck="false"
|
||||
@change="validateAllBounds('startDate'); submitForm()"
|
||||
>
|
||||
<date-picker
|
||||
v-if="isUTCBased"
|
||||
class="c-ctrl-wrapper--menus-left"
|
||||
:default-date-time="formattedBounds.start"
|
||||
:formatter="timeFormatter"
|
||||
@date-selected="startDateSelected"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="pr-time-input pr-time-input--time">
|
||||
<input
|
||||
ref="startTime"
|
||||
v-model="formattedBounds.startTime"
|
||||
class="c-input--datetime"
|
||||
type="text"
|
||||
autocorrect="off"
|
||||
spellcheck="false"
|
||||
@change="validateAllBounds('startDate'); submitForm()"
|
||||
>
|
||||
</div>
|
||||
|
||||
<div class="pr-time-input pr-time-input__start-end-sep icon-arrows-right-left"></div>
|
||||
|
||||
<div class="pr-time-input pr-time-input--date pr-time-input--input-and-button">
|
||||
<input
|
||||
ref="endDate"
|
||||
v-model="formattedBounds.end"
|
||||
class="c-input--datetime"
|
||||
type="text"
|
||||
autocorrect="off"
|
||||
spellcheck="false"
|
||||
@change="validateAllBounds('endDate'); submitForm()"
|
||||
>
|
||||
<date-picker
|
||||
v-if="isUTCBased"
|
||||
class="c-ctrl-wrapper--menus-left"
|
||||
:default-date-time="formattedBounds.end"
|
||||
:formatter="timeFormatter"
|
||||
@date-selected="endDateSelected"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="pr-time-input pr-time-input--time">
|
||||
<input
|
||||
ref="endTime"
|
||||
v-model="formattedBounds.endTime"
|
||||
class="c-input--datetime"
|
||||
type="text"
|
||||
autocorrect="off"
|
||||
spellcheck="false"
|
||||
@change="validateAllBounds('endDate'); submitForm()"
|
||||
>
|
||||
</div>
|
||||
|
||||
<div class="pr-time-input pr-time-input--buttons">
|
||||
<button
|
||||
class="c-button c-button--major icon-check"
|
||||
:disabled="isDisabled"
|
||||
@click.prevent="submit"
|
||||
></button>
|
||||
<button
|
||||
class="c-button icon-x"
|
||||
@click.prevent="hide"
|
||||
></button>
|
||||
</div>
|
||||
</form>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import _ from "lodash";
|
||||
import DatePicker from "./DatePicker.vue";
|
||||
|
||||
const DEFAULT_DURATION_FORMATTER = 'duration';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
DatePicker
|
||||
},
|
||||
inject: ['openmct'],
|
||||
props: {
|
||||
inputBounds: {
|
||||
type: Object,
|
||||
required: true
|
||||
},
|
||||
inputTimeSystem: {
|
||||
type: Object,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
data() {
|
||||
let timeSystem = this.openmct.time.timeSystem();
|
||||
let durationFormatter = this.getFormatter(timeSystem.durationFormat || DEFAULT_DURATION_FORMATTER);
|
||||
let timeFormatter = this.getFormatter(timeSystem.timeFormat);
|
||||
let bounds = this.bounds || this.openmct.time.bounds();
|
||||
|
||||
return {
|
||||
timeFormatter,
|
||||
durationFormatter,
|
||||
bounds: {
|
||||
start: bounds.start,
|
||||
end: bounds.end
|
||||
},
|
||||
formattedBounds: {
|
||||
start: timeFormatter.format(bounds.start).split(' ')[0],
|
||||
end: timeFormatter.format(bounds.end).split(' ')[0],
|
||||
startTime: durationFormatter.format(Math.abs(bounds.start)),
|
||||
endTime: durationFormatter.format(Math.abs(bounds.end))
|
||||
},
|
||||
isUTCBased: timeSystem.isUTCBased,
|
||||
isDisabled: false
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
inputBounds: {
|
||||
handler(newBounds) {
|
||||
this.handleNewBounds(newBounds);
|
||||
},
|
||||
deep: true
|
||||
},
|
||||
inputTimeSystem: {
|
||||
handler(newTimeSystem) {
|
||||
this.setTimeSystem(newTimeSystem);
|
||||
},
|
||||
deep: true
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.handleNewBounds = _.throttle(this.handleNewBounds, 300);
|
||||
this.setTimeSystem(JSON.parse(JSON.stringify(this.openmct.time.timeSystem())));
|
||||
},
|
||||
beforeDestroy() {
|
||||
this.clearAllValidation();
|
||||
},
|
||||
methods: {
|
||||
handleNewBounds(bounds) {
|
||||
this.setBounds(bounds);
|
||||
this.setViewFromBounds(bounds);
|
||||
},
|
||||
clearAllValidation() {
|
||||
[this.$refs.startDate, this.$refs.endDate].forEach(this.clearValidationForInput);
|
||||
},
|
||||
clearValidationForInput(input) {
|
||||
input.setCustomValidity('');
|
||||
input.title = '';
|
||||
},
|
||||
setBounds(bounds) {
|
||||
this.bounds = bounds;
|
||||
},
|
||||
setViewFromBounds(bounds) {
|
||||
this.formattedBounds.start = this.timeFormatter.format(bounds.start).split(' ')[0];
|
||||
this.formattedBounds.end = this.timeFormatter.format(bounds.end).split(' ')[0];
|
||||
this.formattedBounds.startTime = this.durationFormatter.format(Math.abs(bounds.start));
|
||||
this.formattedBounds.endTime = this.durationFormatter.format(Math.abs(bounds.end));
|
||||
},
|
||||
setTimeSystem(timeSystem) {
|
||||
this.timeSystem = timeSystem;
|
||||
this.timeFormatter = this.getFormatter(timeSystem.timeFormat);
|
||||
this.durationFormatter = this.getFormatter(
|
||||
timeSystem.durationFormat || DEFAULT_DURATION_FORMATTER);
|
||||
this.isUTCBased = timeSystem.isUTCBased;
|
||||
},
|
||||
getFormatter(key) {
|
||||
return this.openmct.telemetry.getValueFormatter({
|
||||
format: key
|
||||
}).formatter;
|
||||
},
|
||||
setBoundsFromView(dismiss) {
|
||||
if (this.$refs.fixedDeltaInput.checkValidity()) {
|
||||
let start = this.timeFormatter.parse(`${this.formattedBounds.start} ${this.formattedBounds.startTime}`);
|
||||
let end = this.timeFormatter.parse(`${this.formattedBounds.end} ${this.formattedBounds.endTime}`);
|
||||
|
||||
this.$emit('update', {
|
||||
start: start,
|
||||
end: end
|
||||
});
|
||||
}
|
||||
|
||||
if (dismiss) {
|
||||
this.$emit('dismiss');
|
||||
|
||||
return false;
|
||||
}
|
||||
},
|
||||
submit() {
|
||||
this.validateAllBounds('startDate');
|
||||
this.validateAllBounds('endDate');
|
||||
this.submitForm(!this.isDisabled);
|
||||
},
|
||||
submitForm(dismiss) {
|
||||
// Allow Vue model to catch up to user input.
|
||||
// Submitting form will cause validation messages to display (but only if triggered by button click)
|
||||
this.$nextTick(() => this.setBoundsFromView(dismiss));
|
||||
},
|
||||
validateAllBounds(ref) {
|
||||
if (!this.areBoundsFormatsValid()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
let validationResult = {
|
||||
valid: true
|
||||
};
|
||||
const currentInput = this.$refs[ref];
|
||||
|
||||
return [this.$refs.startDate, this.$refs.endDate].every((input) => {
|
||||
let boundsValues = {
|
||||
start: this.timeFormatter.parse(`${this.formattedBounds.start} ${this.formattedBounds.startTime}`),
|
||||
end: this.timeFormatter.parse(`${this.formattedBounds.end} ${this.formattedBounds.endTime}`)
|
||||
};
|
||||
//TODO: Do we need limits here? We have conductor limits disabled right now
|
||||
// const limit = this.getBoundsLimit();
|
||||
const limit = false;
|
||||
|
||||
if (this.timeSystem.isUTCBased && limit
|
||||
&& boundsValues.end - boundsValues.start > limit) {
|
||||
if (input === currentInput) {
|
||||
validationResult = {
|
||||
valid: false,
|
||||
message: "Start and end difference exceeds allowable limit"
|
||||
};
|
||||
}
|
||||
} else {
|
||||
if (input === currentInput) {
|
||||
validationResult = this.openmct.time.validateBounds(boundsValues);
|
||||
}
|
||||
}
|
||||
|
||||
return this.handleValidationResults(input, validationResult);
|
||||
});
|
||||
},
|
||||
areBoundsFormatsValid() {
|
||||
let validationResult = {
|
||||
valid: true
|
||||
};
|
||||
|
||||
return [this.$refs.startDate, this.$refs.endDate].every((input) => {
|
||||
const formattedDate = input === this.$refs.startDate
|
||||
? `${this.formattedBounds.start} ${this.formattedBounds.startTime}`
|
||||
: `${this.formattedBounds.end} ${this.formattedBounds.endTime}`
|
||||
;
|
||||
|
||||
if (!this.timeFormatter.validate(formattedDate)) {
|
||||
validationResult = {
|
||||
valid: false,
|
||||
message: 'Invalid date'
|
||||
};
|
||||
}
|
||||
|
||||
return this.handleValidationResults(input, validationResult);
|
||||
});
|
||||
},
|
||||
getBoundsLimit() {
|
||||
const configuration = this.configuration.menuOptions
|
||||
.filter(option => option.timeSystem === this.timeSystem.key)
|
||||
.find(option => option.limit);
|
||||
|
||||
const limit = configuration ? configuration.limit : undefined;
|
||||
|
||||
return limit;
|
||||
},
|
||||
handleValidationResults(input, validationResult) {
|
||||
if (validationResult.valid !== true) {
|
||||
input.setCustomValidity(validationResult.message);
|
||||
input.title = validationResult.message;
|
||||
this.isDisabled = true;
|
||||
} else {
|
||||
input.setCustomValidity('');
|
||||
input.title = '';
|
||||
this.isDisabled = false;
|
||||
}
|
||||
|
||||
this.$refs.fixedDeltaInput.reportValidity();
|
||||
|
||||
return validationResult.valid;
|
||||
},
|
||||
startDateSelected(date) {
|
||||
this.formattedBounds.start = this.timeFormatter.format(date).split(' ')[0];
|
||||
this.validateAllBounds('startDate');
|
||||
this.submitForm();
|
||||
},
|
||||
endDateSelected(date) {
|
||||
this.formattedBounds.end = this.timeFormatter.format(date).split(' ')[0];
|
||||
this.validateAllBounds('endDate');
|
||||
this.submitForm();
|
||||
},
|
||||
hide($event) {
|
||||
if ($event.target.className.indexOf('c-button icon-x') > -1) {
|
||||
this.$emit('dismiss');
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
@@ -1,252 +0,0 @@
|
||||
<template>
|
||||
<form
|
||||
ref="deltaInput"
|
||||
class="c-tc-input-popup__input-grid"
|
||||
>
|
||||
<div class="pr-time-label icon-minus">Hrs</div>
|
||||
<div class="pr-time-label">Mins</div>
|
||||
<div class="pr-time-label">Secs</div>
|
||||
<div class="pr-time-label"></div>
|
||||
<div class="pr-time-label icon-plus">Hrs</div>
|
||||
<div class="pr-time-label">Mins</div>
|
||||
<div class="pr-time-label">Secs</div>
|
||||
<div class="pr-time-label"></div>
|
||||
|
||||
<div class="pr-time-input">
|
||||
<input
|
||||
ref="startInputHrs"
|
||||
v-model="startInputHrs"
|
||||
class="pr-time-input__hrs"
|
||||
step="1"
|
||||
type="number"
|
||||
min="0"
|
||||
max="23"
|
||||
title="Enter 0 - 23"
|
||||
@change="validate()"
|
||||
@keyup="validate()"
|
||||
@focusin="selectAll($event)"
|
||||
@focusout="format('startInputHrs')"
|
||||
@wheel="increment($event, 'startInputHrs')"
|
||||
>
|
||||
<b>:</b>
|
||||
</div>
|
||||
<div class="pr-time-input">
|
||||
<input
|
||||
ref="startInputMins"
|
||||
v-model="startInputMins"
|
||||
type="number"
|
||||
class="pr-time-input__mins"
|
||||
min="0"
|
||||
max="59"
|
||||
title="Enter 0 - 59"
|
||||
step="1"
|
||||
@change="validate()"
|
||||
@keyup="validate()"
|
||||
@focusin="selectAll($event)"
|
||||
@focusout="format('startInputMins')"
|
||||
@wheel="increment($event, 'startInputMins')"
|
||||
>
|
||||
<b>:</b>
|
||||
</div>
|
||||
<div class="pr-time-input">
|
||||
<input
|
||||
ref="startInputSecs"
|
||||
v-model="startInputSecs"
|
||||
type="number"
|
||||
class="pr-time-input__secs"
|
||||
min="0"
|
||||
max="59"
|
||||
title="Enter 0 - 59"
|
||||
step="1"
|
||||
@change="validate()"
|
||||
@keyup="validate()"
|
||||
@focusin="selectAll($event)"
|
||||
@focusout="format('startInputSecs')"
|
||||
@wheel="increment($event, 'startInputSecs')"
|
||||
>
|
||||
</div>
|
||||
|
||||
<div class="pr-time-input pr-time-input__start-end-sep icon-arrows-right-left"></div>
|
||||
|
||||
<div class="pr-time-input">
|
||||
<input
|
||||
ref="endInputHrs"
|
||||
v-model="endInputHrs"
|
||||
class="pr-time-input__hrs"
|
||||
step="1"
|
||||
type="number"
|
||||
min="0"
|
||||
max="23"
|
||||
title="Enter 0 - 23"
|
||||
@change="validate()"
|
||||
@keyup="validate()"
|
||||
@focusin="selectAll($event)"
|
||||
@focusout="format('endInputHrs')"
|
||||
@wheel="increment($event, 'endInputHrs')"
|
||||
>
|
||||
<b>:</b>
|
||||
</div>
|
||||
<div class="pr-time-input">
|
||||
<input
|
||||
ref="endInputMins"
|
||||
v-model="endInputMins"
|
||||
type="number"
|
||||
class="pr-time-input__mins"
|
||||
min="0"
|
||||
max="59"
|
||||
title="Enter 0 - 59"
|
||||
step="1"
|
||||
@change="validate()"
|
||||
@keyup="validate()"
|
||||
@focusin="selectAll($event)"
|
||||
@focusout="format('endInputMins')"
|
||||
@wheel="increment($event, 'endInputMins')"
|
||||
>
|
||||
<b>:</b>
|
||||
</div>
|
||||
<div class="pr-time-input">
|
||||
<input
|
||||
ref="endInputSecs"
|
||||
v-model="endInputSecs"
|
||||
type="number"
|
||||
class="pr-time-input__secs"
|
||||
min="0"
|
||||
max="59"
|
||||
title="Enter 0 - 59"
|
||||
step="1"
|
||||
@change="validate()"
|
||||
@keyup="validate()"
|
||||
@focusin="selectAll($event)"
|
||||
@focusout="format('endInputSecs')"
|
||||
@wheel="increment($event, 'endInputSecs')"
|
||||
>
|
||||
</div>
|
||||
|
||||
<div class="pr-time-input pr-time-input--buttons">
|
||||
<button
|
||||
class="c-button c-button--major icon-check"
|
||||
:disabled="isDisabled"
|
||||
@click.prevent="submit"
|
||||
></button>
|
||||
<button
|
||||
class="c-button icon-x"
|
||||
@click.prevent="hide"
|
||||
></button>
|
||||
</div>
|
||||
</form>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
offsets: {
|
||||
type: Object,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
startInputHrs: '00',
|
||||
startInputMins: '00',
|
||||
startInputSecs: '00',
|
||||
endInputHrs: '00',
|
||||
endInputMins: '00',
|
||||
endInputSecs: '00',
|
||||
isDisabled: false
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
offsets: {
|
||||
handler() {
|
||||
this.setOffsets();
|
||||
},
|
||||
deep: true
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.setOffsets();
|
||||
document.addEventListener('click', this.hide);
|
||||
},
|
||||
beforeDestroy() {
|
||||
document.removeEventListener('click', this.hide);
|
||||
},
|
||||
methods: {
|
||||
format(ref) {
|
||||
const curVal = this[ref];
|
||||
this[ref] = curVal.padStart(2, '0');
|
||||
},
|
||||
validate() {
|
||||
let disabled = false;
|
||||
let refs = ['startInputHrs', 'startInputMins', 'startInputSecs', 'endInputHrs', 'endInputMins', 'endInputSecs'];
|
||||
|
||||
for (let ref of refs) {
|
||||
let min = Number(this.$refs[ref].min);
|
||||
let max = Number(this.$refs[ref].max);
|
||||
let value = Number(this.$refs[ref].value);
|
||||
|
||||
if (value > max || value < min) {
|
||||
disabled = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
this.isDisabled = disabled;
|
||||
},
|
||||
submit() {
|
||||
this.$emit('update', {
|
||||
start: {
|
||||
hours: this.startInputHrs,
|
||||
minutes: this.startInputMins,
|
||||
seconds: this.startInputSecs
|
||||
},
|
||||
end: {
|
||||
hours: this.endInputHrs,
|
||||
minutes: this.endInputMins,
|
||||
seconds: this.endInputSecs
|
||||
}
|
||||
});
|
||||
this.$emit('dismiss');
|
||||
},
|
||||
hide($event) {
|
||||
if ($event.target.className.indexOf('c-button icon-x') > -1) {
|
||||
this.$emit('dismiss');
|
||||
}
|
||||
},
|
||||
increment($ev, ref) {
|
||||
$ev.preventDefault();
|
||||
const step = (ref === 'startInputHrs' || ref === 'endInputHrs') ? 1 : 5;
|
||||
const maxVal = (ref === 'startInputHrs' || ref === 'endInputHrs') ? 23 : 59;
|
||||
let cv = Math.round(parseInt(this[ref], 10) / step) * step;
|
||||
cv = Math.min(maxVal, Math.max(0, ($ev.deltaY < 0) ? cv + step : cv - step));
|
||||
this[ref] = cv.toString().padStart(2, '0');
|
||||
this.validate();
|
||||
},
|
||||
setOffsets() {
|
||||
[this.startInputHrs, this.startInputMins, this.startInputSecs] = this.offsets.start.split(':');
|
||||
[this.endInputHrs, this.endInputMins, this.endInputSecs] = this.offsets.end.split(':');
|
||||
this.numberSelect('startInputHrs');
|
||||
},
|
||||
numberSelect(input) {
|
||||
this.$refs[input].focus();
|
||||
|
||||
// change to text, select, then change back to number
|
||||
// number inputs do not support select()
|
||||
this.$nextTick(() => {
|
||||
if (this.$refs[input] === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.$refs[input].setAttribute('type', 'text');
|
||||
this.$refs[input].select();
|
||||
|
||||
this.$nextTick(() => {
|
||||
this.$refs[input].setAttribute('type', 'number');
|
||||
});
|
||||
});
|
||||
},
|
||||
selectAll($ev) {
|
||||
$ev.target.select();
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
@@ -81,7 +81,6 @@ $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%);
|
||||
|
||||
@@ -140,30 +139,13 @@ $colorBodyBgSubtleHov: pullForward($colorBodyBg, 10%);
|
||||
$colorKeySubtle: pushBack($colorKey, 10%);
|
||||
|
||||
// Time Colors
|
||||
$colorTimeFixed: #59554C;
|
||||
$colorTimeFixedBg: $colorTimeFixed;
|
||||
$colorTimeFixedFg: #eee;
|
||||
$colorTimeFixedFgSubtle: #B2AA98;
|
||||
$colorTimeFixedHov: pullForward($colorTimeFixed, 10%);
|
||||
$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, 10%);
|
||||
$colorTimeRealtimeSubtle: pushBack($colorTimeRealtime, 20%);
|
||||
$colorTimeRealtimeBtnBg: pullForward($colorTimeRealtime, 5%);
|
||||
$colorTimeRealtimeBtnFg: $colorTimeRealtimeFgSubtle;
|
||||
$colorTimeRealtimeBtnBgMajor: #588ffa;
|
||||
$colorTimeRealtimeBtnFgMajor: #fff;
|
||||
|
||||
$colorTime: #618cff;
|
||||
$colorTimeBg: $colorTime;
|
||||
$colorTimeFg: pullForward($colorTimeBg, 30%);
|
||||
$colorTimeHov: pullForward($colorTime, 10%);
|
||||
$colorTimeSubtle: pushBack($colorTime, 20%);
|
||||
$colorTOI: $colorBodyFg; // was $timeControllerToiLineColor
|
||||
$colorTOIHov: $colorTimeRealtime; // was $timeControllerToiLineColorHov
|
||||
$colorTOIHov: $colorTime; // was $timeControllerToiLineColorHov
|
||||
$timeConductorAxisHoverFilter: brightness(1.2);
|
||||
$timeConductorActiveBg: $colorKey;
|
||||
$timeConductorActivePanBg: #226074;
|
||||
@@ -259,7 +241,7 @@ $controlDisabledOpacity: 0.2;
|
||||
$colorMenuBg: $colorBodyBg;
|
||||
$colorMenuFg: $colorBodyFg;
|
||||
$colorMenuIc: $colorKey;
|
||||
$filterMenu: brightness(1.2);
|
||||
$filterMenu: brightness(1.4);
|
||||
$colorMenuHovBg: rgba($colorKey, 0.5);
|
||||
$colorMenuHovFg: $colorBodyFgEm;
|
||||
$colorMenuHovIc: $colorMenuHovFg;
|
||||
|
||||
@@ -85,7 +85,6 @@ $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%);
|
||||
|
||||
@@ -144,30 +143,13 @@ $colorBodyBgSubtleHov: pushBack($colorKey, 50%);
|
||||
$colorKeySubtle: pushBack($colorKey, 10%);
|
||||
|
||||
// Time Colors
|
||||
$colorTimeFixed: #59554C;
|
||||
$colorTimeFixedBg: $colorTimeFixed;
|
||||
$colorTimeFixedFg: #eee;
|
||||
$colorTimeFixedFgSubtle: #B2AA98;
|
||||
$colorTimeFixedHov: pullForward($colorTimeFixed, 10%);
|
||||
$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, 10%);
|
||||
$colorTimeRealtimeSubtle: pushBack($colorTimeRealtime, 20%);
|
||||
$colorTimeRealtimeBtnBg: pullForward($colorTimeRealtime, 5%);
|
||||
$colorTimeRealtimeBtnFg: $colorTimeRealtimeFgSubtle;
|
||||
$colorTimeRealtimeBtnBgMajor: #588ffa;
|
||||
$colorTimeRealtimeBtnFgMajor: #fff;
|
||||
|
||||
$colorTime: #618cff;
|
||||
$colorTimeBg: $colorTime;
|
||||
$colorTimeFg: pullForward($colorTimeBg, 30%);
|
||||
$colorTimeHov: pullForward($colorTime, 10%);
|
||||
$colorTimeSubtle: pushBack($colorTime, 20%);
|
||||
$colorTOI: $colorBodyFg; // was $timeControllerToiLineColor
|
||||
$colorTOIHov: $colorTimeRealtime; // was $timeControllerToiLineColorHov
|
||||
$colorTOIHov: $colorTime; // was $timeControllerToiLineColorHov
|
||||
$timeConductorAxisHoverFilter: brightness(1.2);
|
||||
$timeConductorActiveBg: $colorKey;
|
||||
$timeConductorActivePanBg: #226074;
|
||||
|
||||
@@ -81,7 +81,6 @@ $colorInteriorBorder: rgba($colorBodyFg, 0.2);
|
||||
$colorA: $colorBodyFg;
|
||||
$colorAHov: $colorKey;
|
||||
$filterHov: hue-rotate(-10deg) brightness(0.8) contrast(2); // Tree, location items
|
||||
$filterHovSubtle: hue-rotate(-8deg) brightness(0.5) contrast(1.2);
|
||||
$colorSelectedBg: pushBack($colorKey, 40%);
|
||||
$colorSelectedFg: pullForward($colorBodyFg, 10%);
|
||||
|
||||
@@ -140,30 +139,13 @@ $colorBodyBgSubtleHov: pullForward($colorBodyBg, 10%);
|
||||
$colorKeySubtle: pushBack($colorKey, 20%);
|
||||
|
||||
// Time Colors
|
||||
$colorTimeFixed: #59554C;
|
||||
$colorTimeFixedBg: $colorTimeFixed;
|
||||
$colorTimeFixedFg: #eee;
|
||||
$colorTimeFixedFgSubtle: #B2AA98;
|
||||
$colorTimeFixedHov: pullForward($colorTimeFixed, 10%);
|
||||
$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, 10%);
|
||||
$colorTimeRealtimeSubtle: pushBack($colorTimeRealtime, 20%);
|
||||
$colorTimeRealtimeBtnBg: pullForward($colorTimeRealtime, 5%);
|
||||
$colorTimeRealtimeBtnFg: $colorTimeRealtimeFgSubtle;
|
||||
$colorTimeRealtimeBtnBgMajor: #588ffa;
|
||||
$colorTimeRealtimeBtnFgMajor: #fff;
|
||||
|
||||
$colorTime: #618cff;
|
||||
$colorTimeBg: $colorTime;
|
||||
$colorTimeFg: $colorBodyBg;
|
||||
$colorTimeHov: pushBack($colorTime, 5%);
|
||||
$colorTimeSubtle: pushBack($colorTime, 20%);
|
||||
$colorTOI: $colorBodyFg; // was $timeControllerToiLineColor
|
||||
$colorTOIHov: $colorTimeRealtime; // was $timeControllerToiLineColorHov
|
||||
$colorTOIHov: $colorTime; // was $timeControllerToiLineColorHov
|
||||
$timeConductorAxisHoverFilter: brightness(0.8);
|
||||
$timeConductorActiveBg: $colorKey;
|
||||
$timeConductorActivePanBg: #A0CDE1;
|
||||
|
||||
@@ -244,13 +244,6 @@ button {
|
||||
}
|
||||
}
|
||||
|
||||
.c-not-button {
|
||||
// Use within a holder that's clickable; use to indicate interactability
|
||||
@include cButtonLayout();
|
||||
cursor: pointer;
|
||||
|
||||
}
|
||||
|
||||
/******************************************************** DISCLOSURE CONTROLS */
|
||||
/********* Disclosure Button */
|
||||
// Provides a downward arrow icon that when clicked displays additional options and/or info.
|
||||
|
||||
@@ -63,11 +63,6 @@ div {
|
||||
}
|
||||
}
|
||||
|
||||
.u-flex-spreader {
|
||||
// Pushes against elements in a flex layout to spread them out
|
||||
flex: 1 1 auto;
|
||||
}
|
||||
|
||||
/******************************************************** BROWSER ELEMENTS */
|
||||
body.desktop {
|
||||
::-webkit-scrollbar {
|
||||
|
||||
@@ -521,33 +521,24 @@
|
||||
}
|
||||
}
|
||||
|
||||
@mixin cButtonLayout() {
|
||||
$pad: $interiorMargin;
|
||||
padding: $pad floor($pad * 1.25);
|
||||
@mixin cButton() {
|
||||
@include cControl();
|
||||
@include cControlHov();
|
||||
@include themedButton();
|
||||
border-radius: $controlCr;
|
||||
color: $colorBtnFg;
|
||||
cursor: pointer;
|
||||
padding: $interiorMargin floor($interiorMargin * 1.25);
|
||||
|
||||
&:after,
|
||||
> * + * {
|
||||
margin-left: $interiorMarginSm;
|
||||
}
|
||||
|
||||
&[class*='--compact'] {
|
||||
padding: floor(math.div($pad, 1.5)) $pad;
|
||||
}
|
||||
}
|
||||
|
||||
@mixin cButton() {
|
||||
@include cControl();
|
||||
@include cControlHov();
|
||||
@include themedButton();
|
||||
@include cButtonLayout();
|
||||
border-radius: $controlCr;
|
||||
color: $colorBtnFg;
|
||||
cursor: pointer;
|
||||
|
||||
&[class*="--major"],
|
||||
&[class*='is-active']{
|
||||
background: $colorBtnMajorBg !important;
|
||||
color: $colorBtnMajorFg !important;
|
||||
background: $colorBtnMajorBg;
|
||||
color: $colorBtnMajorFg;
|
||||
}
|
||||
|
||||
&[class*='--caution'] {
|
||||
@@ -585,7 +576,7 @@
|
||||
*:before {
|
||||
// *:before handles any nested containers that may contain glyph elements
|
||||
// Needed for c-togglebutton.
|
||||
font-size: 1.15em;
|
||||
font-size: 1.25em;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -61,17 +61,6 @@
|
||||
'has-complex-content': complexContent
|
||||
}"
|
||||
>
|
||||
<div
|
||||
v-if="supportsIndependentTime"
|
||||
class="c-conductor-holder--compact"
|
||||
>
|
||||
<independent-time-conductor
|
||||
:domain-object="domainObject"
|
||||
:object-path="[domainObject]"
|
||||
@stateChanged="updateIndependentTimeState"
|
||||
@updated="saveTimeOptions"
|
||||
/>
|
||||
</div>
|
||||
<NotebookMenuSwitcher
|
||||
v-if="notebookEnabled"
|
||||
:domain-object="domainObject"
|
||||
@@ -116,7 +105,6 @@
|
||||
<script>
|
||||
import ObjectView from './ObjectView.vue';
|
||||
import NotebookMenuSwitcher from '@/plugins/notebook/components/NotebookMenuSwitcher.vue';
|
||||
import IndependentTimeConductor from '@/plugins/timeConductor/independent/IndependentTimeConductor.vue';
|
||||
|
||||
const SIMPLE_CONTENT_TYPES = [
|
||||
'clock',
|
||||
@@ -127,18 +115,10 @@ const SIMPLE_CONTENT_TYPES = [
|
||||
];
|
||||
const CSS_WIDTH_LESS_STR = '--width-less-than-';
|
||||
|
||||
const SupportedViewTypes = [
|
||||
'plot-stacked',
|
||||
'plot-overlay',
|
||||
'bar-graph.view',
|
||||
'time-strip.view'
|
||||
];
|
||||
|
||||
export default {
|
||||
components: {
|
||||
ObjectView,
|
||||
NotebookMenuSwitcher,
|
||||
IndependentTimeConductor
|
||||
NotebookMenuSwitcher
|
||||
},
|
||||
inject: ['openmct'],
|
||||
props: {
|
||||
@@ -183,11 +163,6 @@ export default {
|
||||
computed: {
|
||||
statusClass() {
|
||||
return (this.status) ? `is-status--${this.status}` : '';
|
||||
},
|
||||
supportsIndependentTime() {
|
||||
// const viewKey = this.getViewKey();
|
||||
|
||||
return true; //this.domainObject && SupportedViewTypes.includes(viewKey);
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
@@ -258,21 +233,6 @@ export default {
|
||||
}
|
||||
|
||||
this.widthClass = wClass.trimStart();
|
||||
},
|
||||
getViewKey() {
|
||||
let viewKey = this.this.$refs.objectView.viewKey;
|
||||
if (this.objectViewKey) {
|
||||
viewKey = this.objectViewKey;
|
||||
}
|
||||
|
||||
return viewKey;
|
||||
},
|
||||
//Should the domainObject be updated in the Independent Time conductor component itself?
|
||||
updateIndependentTimeState(useIndependentTime) {
|
||||
this.openmct.objects.mutate(this.domainObject, 'configuration.useIndependentTime', useIndependentTime);
|
||||
},
|
||||
saveTimeOptions(options) {
|
||||
this.openmct.objects.mutate(this.domainObject, 'configuration.timeOptions', options);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div>
|
||||
<div
|
||||
v-if="supportsIndependentTime && false"
|
||||
v-if="supportsIndependentTime"
|
||||
class="c-conductor-holder--compact l-shell__main-independent-time-conductor"
|
||||
>
|
||||
<independent-time-conductor
|
||||
|
||||
@@ -15,8 +15,6 @@
|
||||
|
||||
.c-object-label {
|
||||
font-size: 1.05em;
|
||||
min-width: 20%;
|
||||
|
||||
&__type-icon {
|
||||
opacity: $objectLabelTypeIconOpacity;
|
||||
}
|
||||
@@ -39,8 +37,7 @@
|
||||
/*************************** FRAME CONTROLS */
|
||||
&__frame-controls {
|
||||
display: flex;
|
||||
flex: 0 1 auto;
|
||||
overflow: hidden;
|
||||
flex: 0 0 auto;
|
||||
|
||||
&__btns,
|
||||
&__more {
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
.c-object-label {
|
||||
// <a> tag and draggable element that holds type icon and name.
|
||||
// Used mostly in trees and lists
|
||||
@include ellipsize();
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex: 0 1 auto;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
|
||||
> * + * { margin-left: $interiorMargin; }
|
||||
|
||||
|
||||
@@ -1,25 +1,9 @@
|
||||
@use 'sass:math';
|
||||
|
||||
@mixin toggleSwitch($d: 12px, $m: 2px, $bg: $colorBtnBg) {
|
||||
$br: math.div($d, 1.5);
|
||||
|
||||
.c-toggle-switch__slider {
|
||||
background: $bg;
|
||||
border-radius: $br;
|
||||
height: $d + ($m*2);
|
||||
width: $d*2 + $m*2;
|
||||
|
||||
&:before {
|
||||
// Knob
|
||||
border-radius: floor($br * 0.8);
|
||||
box-shadow: rgba(black, 0.4) 0 0 2px;
|
||||
height: $d; width: $d;
|
||||
top: $m; left: $m; right: auto;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.c-toggle-switch {
|
||||
$d: 12px;
|
||||
$m: 2px;
|
||||
$br: math.div($d, 1.5);
|
||||
cursor: pointer;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
@@ -36,26 +20,6 @@
|
||||
display: block;
|
||||
}
|
||||
|
||||
&__slider {
|
||||
// Sits within __switch
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
|
||||
&:before {
|
||||
// Knob
|
||||
background: $colorBtnFg; // TODO: make discrete theme constants for these colors
|
||||
content: '';
|
||||
display: block;
|
||||
position: absolute;
|
||||
transition: transform 100ms ease-in-out;
|
||||
}
|
||||
}
|
||||
|
||||
&__label {
|
||||
margin-left: $interiorMarginSm;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
input {
|
||||
opacity: 0;
|
||||
width: 0;
|
||||
@@ -71,9 +35,31 @@
|
||||
}
|
||||
}
|
||||
|
||||
@include toggleSwitch();
|
||||
}
|
||||
&__slider {
|
||||
// Sits within __switch
|
||||
background: $colorBtnBg; // TODO: make discrete theme constants for these colors
|
||||
border-radius: $br;
|
||||
display: inline-block;
|
||||
height: $d + ($m*2);
|
||||
position: relative;
|
||||
width: $d*2 + $m*2;
|
||||
|
||||
.c-toggle-switch--mini {
|
||||
@include toggleSwitch($d: 9px, $m: 0px);
|
||||
&:before {
|
||||
// Knob
|
||||
background: $colorBtnFg; // TODO: make discrete theme constants for these colors
|
||||
border-radius: floor($br * 0.8);
|
||||
box-shadow: rgba(black, 0.4) 0 0 2px;
|
||||
content: '';
|
||||
display: block;
|
||||
position: absolute;
|
||||
height: $d; width: $d;
|
||||
top: $m; left: $m; right: auto;
|
||||
transition: transform 100ms ease-in-out;
|
||||
}
|
||||
}
|
||||
|
||||
&__label {
|
||||
margin-left: $interiorMarginSm;
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,17 +34,6 @@
|
||||
</div>
|
||||
|
||||
<div class="l-browse-bar__end">
|
||||
<div
|
||||
v-if="supportsIndependentTime"
|
||||
class="c-conductor-holder--compact l-shell__main-independent-time-conductor"
|
||||
>
|
||||
<independent-time-conductor
|
||||
:domain-object="domainObject"
|
||||
:object-path="openmct.router.path"
|
||||
@stateChanged="updateIndependentTimeState"
|
||||
@updated="saveTimeOptions"
|
||||
/>
|
||||
</div>
|
||||
<ViewSwitcher
|
||||
v-if="!isEditing"
|
||||
:current-view="currentView"
|
||||
@@ -81,7 +70,6 @@
|
||||
v-if="isViewEditable && !isEditing && !domainObject.locked"
|
||||
class="l-browse-bar__actions__edit c-button c-button--major icon-pencil"
|
||||
title="Edit"
|
||||
aria-label="Edit"
|
||||
@click="edit()"
|
||||
></button>
|
||||
|
||||
@@ -92,7 +80,6 @@
|
||||
<button
|
||||
class="c-button--menu c-button--major icon-save"
|
||||
title="Save"
|
||||
aria-label="Save"
|
||||
@click.stop="toggleSaveMenu"
|
||||
></button>
|
||||
<div
|
||||
@@ -137,20 +124,11 @@
|
||||
<script>
|
||||
import ViewSwitcher from './ViewSwitcher.vue';
|
||||
import NotebookMenuSwitcher from '@/plugins/notebook/components/NotebookMenuSwitcher.vue';
|
||||
import IndependentTimeConductor from '@/plugins/timeConductor/independent/IndependentTimeConductor.vue';
|
||||
|
||||
const SupportedViewTypes = [
|
||||
'plot-stacked',
|
||||
'plot-overlay',
|
||||
'bar-graph.view',
|
||||
'time-strip.view'
|
||||
];
|
||||
|
||||
const PLACEHOLDER_OBJECT = {};
|
||||
|
||||
export default {
|
||||
components: {
|
||||
IndependentTimeConductor,
|
||||
NotebookMenuSwitcher,
|
||||
ViewSwitcher
|
||||
},
|
||||
@@ -240,11 +218,6 @@ export default {
|
||||
} else {
|
||||
return 'Unlocked for editing - click to lock.';
|
||||
}
|
||||
},
|
||||
supportsIndependentTime() {
|
||||
const viewKey = this.getViewKey();
|
||||
|
||||
return this.domainObject && SupportedViewTypes.includes(viewKey);
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
@@ -316,14 +289,6 @@ export default {
|
||||
edit() {
|
||||
this.openmct.editor.edit();
|
||||
},
|
||||
getViewKey() {
|
||||
let viewKey = this.viewKey;
|
||||
if (this.objectViewKey) {
|
||||
viewKey = this.objectViewKey;
|
||||
}
|
||||
|
||||
return viewKey;
|
||||
},
|
||||
promptUserandCancelEditing() {
|
||||
let dialog = this.openmct.overlays.dialog({
|
||||
iconClass: 'alert',
|
||||
@@ -400,13 +365,6 @@ export default {
|
||||
},
|
||||
setStatus(status) {
|
||||
this.status = status;
|
||||
},
|
||||
//Should the domainObject be updated in the Independent Time conductor component itself?
|
||||
updateIndependentTimeState(useIndependentTime) {
|
||||
this.openmct.objects.mutate(this.domainObject, 'configuration.useIndependentTime', useIndependentTime);
|
||||
},
|
||||
saveTimeOptions(options) {
|
||||
this.openmct.objects.mutate(this.domainObject, 'configuration.timeOptions', options);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -283,6 +283,17 @@
|
||||
flex: 1 1 auto !important;
|
||||
}
|
||||
|
||||
&__time-conductor {
|
||||
border-top: 1px solid $colorInteriorBorder;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding-top: $interiorMargin;
|
||||
|
||||
> * + * {
|
||||
margin-top: $interiorMargin;
|
||||
}
|
||||
}
|
||||
|
||||
&__main {
|
||||
> .l-pane {
|
||||
padding: nth($shellPanePad, 1) 0;
|
||||
@@ -366,10 +377,10 @@
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
|
||||
//[class*="__"] {
|
||||
// // Removes extraneous horizontal white space
|
||||
// display: inline-flex;
|
||||
//}
|
||||
[class*="__"] {
|
||||
// Removes extraneous horizontal white space
|
||||
display: inline-flex;
|
||||
}
|
||||
|
||||
> * + * {
|
||||
margin-left: $interiorMarginSm;
|
||||
|
||||
Reference in New Issue
Block a user