mirror of
https://github.com/nasa/openmct.git
synced 2025-02-22 10:11:06 +00:00
feat: Recent Objects (#6103)
* clicking recent objects selects that object * clicking target navigates to but does not select that object * max 20 recent objects
This commit is contained in:
parent
22621aaaf8
commit
f98a2cdd6b
@ -49,11 +49,11 @@ test.describe('AppActions', () => {
|
|||||||
parent: e2eFolder.uuid
|
parent: e2eFolder.uuid
|
||||||
});
|
});
|
||||||
|
|
||||||
await page.goto(timer1.url, { waitUntil: 'networkidle' });
|
await page.goto(timer1.url);
|
||||||
await expect(page.locator('.l-browse-bar__object-name')).toHaveText(timer1.name);
|
await expect(page.locator('.l-browse-bar__object-name')).toHaveText(timer1.name);
|
||||||
await page.goto(timer2.url, { waitUntil: 'networkidle' });
|
await page.goto(timer2.url);
|
||||||
await expect(page.locator('.l-browse-bar__object-name')).toHaveText(timer2.name);
|
await expect(page.locator('.l-browse-bar__object-name')).toHaveText(timer2.name);
|
||||||
await page.goto(timer3.url, { waitUntil: 'networkidle' });
|
await page.goto(timer3.url);
|
||||||
await expect(page.locator('.l-browse-bar__object-name')).toHaveText(timer3.name);
|
await expect(page.locator('.l-browse-bar__object-name')).toHaveText(timer3.name);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -73,11 +73,11 @@ test.describe('AppActions', () => {
|
|||||||
name: 'Folder Baz',
|
name: 'Folder Baz',
|
||||||
parent: folder2.uuid
|
parent: folder2.uuid
|
||||||
});
|
});
|
||||||
await page.goto(folder1.url, { waitUntil: 'networkidle' });
|
await page.goto(folder1.url);
|
||||||
await expect(page.locator('.l-browse-bar__object-name')).toHaveText(folder1.name);
|
await expect(page.locator('.l-browse-bar__object-name')).toHaveText(folder1.name);
|
||||||
await page.goto(folder2.url, { waitUntil: 'networkidle' });
|
await page.goto(folder2.url);
|
||||||
await expect(page.locator('.l-browse-bar__object-name')).toHaveText(folder2.name);
|
await expect(page.locator('.l-browse-bar__object-name')).toHaveText(folder2.name);
|
||||||
await page.goto(folder3.url, { waitUntil: 'networkidle' });
|
await page.goto(folder3.url);
|
||||||
await expect(page.locator('.l-browse-bar__object-name')).toHaveText(folder3.name);
|
await expect(page.locator('.l-browse-bar__object-name')).toHaveText(folder3.name);
|
||||||
|
|
||||||
expect(folder1.url).toBe(`${e2eFolder.url}/${folder1.uuid}`);
|
expect(folder1.url).toBe(`${e2eFolder.url}/${folder1.uuid}`);
|
||||||
|
@ -43,48 +43,76 @@ test.describe('Move & link item tests', () => {
|
|||||||
name: 'Child Folder',
|
name: 'Child Folder',
|
||||||
parent: parentFolder.uuid
|
parent: parentFolder.uuid
|
||||||
});
|
});
|
||||||
await createDomainObjectWithDefaults(page, {
|
const grandchildFolder = await createDomainObjectWithDefaults(page, {
|
||||||
type: 'Folder',
|
type: 'Folder',
|
||||||
name: 'Grandchild Folder',
|
name: 'Grandchild Folder',
|
||||||
parent: childFolder.uuid
|
parent: childFolder.uuid
|
||||||
});
|
});
|
||||||
|
|
||||||
// Attempt to move parent to its own grandparent
|
// Attempt to move parent to its own grandparent
|
||||||
await page.locator(`text=Open MCT ${myItemsFolderName} >> span`).nth(3).click();
|
await page.locator('button[title="Show selected item in tree"]').click();
|
||||||
await page.locator('.c-disclosure-triangle >> nth=0').click();
|
|
||||||
|
|
||||||
await page.locator(`a:has-text("Parent Folder") >> nth=0`).click({
|
const treePane = page.locator('#tree-pane');
|
||||||
|
await treePane.getByRole('treeitem', {
|
||||||
|
name: 'Parent Folder'
|
||||||
|
}).click({
|
||||||
button: 'right'
|
button: 'right'
|
||||||
});
|
});
|
||||||
|
|
||||||
await page.locator('li.icon-move').click();
|
await page.getByRole('menuitem', {
|
||||||
await page.locator('form[name="mctForm"] >> .c-disclosure-triangle >> nth=0').click();
|
name: /Move/
|
||||||
await page.locator('form[name="mctForm"] >> text=Parent Folder').click();
|
}).click();
|
||||||
|
|
||||||
|
const locatorTree = page.locator('#locator-tree');
|
||||||
|
const myItemsLocatorTreeItem = locatorTree.getByRole('treeitem', {
|
||||||
|
name: myItemsFolderName
|
||||||
|
});
|
||||||
|
await myItemsLocatorTreeItem.locator('.c-disclosure-triangle').click();
|
||||||
|
await myItemsLocatorTreeItem.click();
|
||||||
|
|
||||||
|
const parentFolderLocatorTreeItem = locatorTree.getByRole('treeitem', {
|
||||||
|
name: parentFolder.name
|
||||||
|
});
|
||||||
|
await parentFolderLocatorTreeItem.locator('.c-disclosure-triangle').click();
|
||||||
|
await parentFolderLocatorTreeItem.click();
|
||||||
await expect(page.locator('[aria-label="Save"]')).toBeDisabled();
|
await expect(page.locator('[aria-label="Save"]')).toBeDisabled();
|
||||||
await page.locator('form[name="mctForm"] >> .c-disclosure-triangle >> nth=1').click();
|
|
||||||
await page.locator('form[name="mctForm"] >> text=Child Folder').click();
|
const childFolderLocatorTreeItem = locatorTree.getByRole('treeitem', {
|
||||||
|
name: new RegExp(childFolder.name)
|
||||||
|
});
|
||||||
|
await childFolderLocatorTreeItem.locator('.c-disclosure-triangle').click();
|
||||||
|
await childFolderLocatorTreeItem.click();
|
||||||
await expect(page.locator('[aria-label="Save"]')).toBeDisabled();
|
await expect(page.locator('[aria-label="Save"]')).toBeDisabled();
|
||||||
await page.locator('form[name="mctForm"] >> .c-disclosure-triangle >> nth=2').click();
|
|
||||||
await page.locator('form[name="mctForm"] >> text=Grandchild Folder').click();
|
const grandchildFolderLocatorTreeItem = locatorTree.getByRole('treeitem', {
|
||||||
|
name: grandchildFolder.name
|
||||||
|
});
|
||||||
|
await grandchildFolderLocatorTreeItem.locator('.c-disclosure-triangle').click();
|
||||||
|
await grandchildFolderLocatorTreeItem.click();
|
||||||
await expect(page.locator('[aria-label="Save"]')).toBeDisabled();
|
await expect(page.locator('[aria-label="Save"]')).toBeDisabled();
|
||||||
await page.locator('form[name="mctForm"] >> text=Parent Folder').click();
|
|
||||||
|
await parentFolderLocatorTreeItem.click();
|
||||||
await expect(page.locator('[aria-label="Save"]')).toBeDisabled();
|
await expect(page.locator('[aria-label="Save"]')).toBeDisabled();
|
||||||
await page.locator('[aria-label="Cancel"]').click();
|
await page.locator('[aria-label="Cancel"]').click();
|
||||||
|
|
||||||
// Move Child Folder from Parent Folder to My Items
|
// Move Child Folder from Parent Folder to My Items
|
||||||
await page.locator('.c-disclosure-triangle >> nth=0').click();
|
await treePane.getByRole('treeitem', {
|
||||||
await page.locator('.c-disclosure-triangle >> nth=1').click();
|
name: new RegExp(childFolder.name)
|
||||||
|
}).click({
|
||||||
await page.locator(`a:has-text("Child Folder") >> nth=0`).click({
|
|
||||||
button: 'right'
|
button: 'right'
|
||||||
});
|
});
|
||||||
await page.locator('li.icon-move').click();
|
await page.getByRole('menuitem', {
|
||||||
await page.locator(`form[name="mctForm"] >> text=${myItemsFolderName}`).click();
|
name: /Move/
|
||||||
|
}).click();
|
||||||
|
await myItemsLocatorTreeItem.click();
|
||||||
|
|
||||||
await page.locator('button:has-text("OK")').click();
|
await page.locator('[aria-label="Save"]').click();
|
||||||
|
const myItemsPaneTreeItem = treePane.getByRole('treeitem', {
|
||||||
|
name: myItemsFolderName
|
||||||
|
});
|
||||||
|
|
||||||
// Expect that Child Folder is in My Items, the root folder
|
// Expect that Child Folder is in My Items, the root folder
|
||||||
expect(page.locator(`text=${myItemsFolderName} >> nth=0:has(text=Child Folder)`)).toBeTruthy();
|
expect(myItemsPaneTreeItem.locator('nth=0:has(text=Child Folder)')).toBeTruthy();
|
||||||
});
|
});
|
||||||
test('Create a basic object and verify that it cannot be moved to telemetry object without Composition Provider', async ({ page, openmctConfig }) => {
|
test('Create a basic object and verify that it cannot be moved to telemetry object without Composition Provider', async ({ page, openmctConfig }) => {
|
||||||
const { myItemsFolderName } = openmctConfig;
|
const { myItemsFolderName } = openmctConfig;
|
||||||
@ -114,7 +142,7 @@ test.describe('Move & link item tests', () => {
|
|||||||
|
|
||||||
// See if it's possible to put the folder in the Telemetry object during creation (Soft Assert)
|
// See if it's possible to put the folder in the Telemetry object during creation (Soft Assert)
|
||||||
await page.locator(`form[name="mctForm"] >> text=${telemetryTable}`).click();
|
await page.locator(`form[name="mctForm"] >> text=${telemetryTable}`).click();
|
||||||
let okButton = await page.locator('button.c-button.c-button--major:has-text("OK")');
|
let okButton = page.locator('button.c-button.c-button--major:has-text("OK")');
|
||||||
let okButtonStateDisabled = await okButton.isDisabled();
|
let okButtonStateDisabled = await okButton.isDisabled();
|
||||||
expect.soft(okButtonStateDisabled).toBeTruthy();
|
expect.soft(okButtonStateDisabled).toBeTruthy();
|
||||||
|
|
||||||
@ -138,7 +166,7 @@ test.describe('Move & link item tests', () => {
|
|||||||
// See if it's possible to put the folder in the Telemetry object after creation
|
// See if it's possible to put the folder in the Telemetry object after creation
|
||||||
await page.locator(`text=Location Open MCT ${myItemsFolderName} >> span`).nth(3).click();
|
await page.locator(`text=Location Open MCT ${myItemsFolderName} >> span`).nth(3).click();
|
||||||
await page.locator(`form[name="mctForm"] >> text=${telemetryTable}`).click();
|
await page.locator(`form[name="mctForm"] >> text=${telemetryTable}`).click();
|
||||||
let okButton2 = await page.locator('button.c-button.c-button--major:has-text("OK")');
|
let okButton2 = page.locator('button.c-button.c-button--major:has-text("OK")');
|
||||||
let okButtonStateDisabled2 = await okButton2.isDisabled();
|
let okButtonStateDisabled2 = await okButton2.isDisabled();
|
||||||
expect(okButtonStateDisabled2).toBeTruthy();
|
expect(okButtonStateDisabled2).toBeTruthy();
|
||||||
});
|
});
|
||||||
@ -158,48 +186,76 @@ test.describe('Move & link item tests', () => {
|
|||||||
name: 'Child Folder',
|
name: 'Child Folder',
|
||||||
parent: parentFolder.uuid
|
parent: parentFolder.uuid
|
||||||
});
|
});
|
||||||
await createDomainObjectWithDefaults(page, {
|
const grandchildFolder = await createDomainObjectWithDefaults(page, {
|
||||||
type: 'Folder',
|
type: 'Folder',
|
||||||
name: 'Grandchild Folder',
|
name: 'Grandchild Folder',
|
||||||
parent: childFolder.uuid
|
parent: childFolder.uuid
|
||||||
});
|
});
|
||||||
|
|
||||||
// Attempt to link parent to its own grandparent
|
// Attempt to move parent to its own grandparent
|
||||||
await page.locator(`text=Open MCT ${myItemsFolderName} >> span`).nth(3).click();
|
await page.locator('button[title="Show selected item in tree"]').click();
|
||||||
await page.locator('.c-disclosure-triangle >> nth=0').click();
|
|
||||||
|
|
||||||
await page.locator(`a:has-text("Parent Folder") >> nth=0`).click({
|
const treePane = page.locator('#tree-pane');
|
||||||
|
await treePane.getByRole('treeitem', {
|
||||||
|
name: 'Parent Folder'
|
||||||
|
}).click({
|
||||||
button: 'right'
|
button: 'right'
|
||||||
});
|
});
|
||||||
|
|
||||||
await page.locator('li.icon-link').click();
|
await page.getByRole('menuitem', {
|
||||||
await page.locator('form[name="mctForm"] >> .c-disclosure-triangle >> nth=0').click();
|
name: /Move/
|
||||||
await page.locator('form[name="mctForm"] >> text=Parent Folder').click();
|
}).click();
|
||||||
|
|
||||||
|
const locatorTree = page.locator('#locator-tree');
|
||||||
|
const myItemsLocatorTreeItem = locatorTree.getByRole('treeitem', {
|
||||||
|
name: myItemsFolderName
|
||||||
|
});
|
||||||
|
await myItemsLocatorTreeItem.locator('.c-disclosure-triangle').click();
|
||||||
|
await myItemsLocatorTreeItem.click();
|
||||||
|
|
||||||
|
const parentFolderLocatorTreeItem = locatorTree.getByRole('treeitem', {
|
||||||
|
name: parentFolder.name
|
||||||
|
});
|
||||||
|
await parentFolderLocatorTreeItem.locator('.c-disclosure-triangle').click();
|
||||||
|
await parentFolderLocatorTreeItem.click();
|
||||||
await expect(page.locator('[aria-label="Save"]')).toBeDisabled();
|
await expect(page.locator('[aria-label="Save"]')).toBeDisabled();
|
||||||
await page.locator('form[name="mctForm"] >> .c-disclosure-triangle >> nth=1').click();
|
|
||||||
await page.locator('form[name="mctForm"] >> text=Child Folder').click();
|
const childFolderLocatorTreeItem = locatorTree.getByRole('treeitem', {
|
||||||
|
name: new RegExp(childFolder.name)
|
||||||
|
});
|
||||||
|
await childFolderLocatorTreeItem.locator('.c-disclosure-triangle').click();
|
||||||
|
await childFolderLocatorTreeItem.click();
|
||||||
await expect(page.locator('[aria-label="Save"]')).toBeDisabled();
|
await expect(page.locator('[aria-label="Save"]')).toBeDisabled();
|
||||||
await page.locator('form[name="mctForm"] >> .c-disclosure-triangle >> nth=2').click();
|
|
||||||
await page.locator('form[name="mctForm"] >> text=Grandchild Folder').click();
|
const grandchildFolderLocatorTreeItem = locatorTree.getByRole('treeitem', {
|
||||||
|
name: grandchildFolder.name
|
||||||
|
});
|
||||||
|
await grandchildFolderLocatorTreeItem.locator('.c-disclosure-triangle').click();
|
||||||
|
await grandchildFolderLocatorTreeItem.click();
|
||||||
await expect(page.locator('[aria-label="Save"]')).toBeDisabled();
|
await expect(page.locator('[aria-label="Save"]')).toBeDisabled();
|
||||||
await page.locator('form[name="mctForm"] >> text=Parent Folder').click();
|
|
||||||
|
await parentFolderLocatorTreeItem.click();
|
||||||
await expect(page.locator('[aria-label="Save"]')).toBeDisabled();
|
await expect(page.locator('[aria-label="Save"]')).toBeDisabled();
|
||||||
await page.locator('[aria-label="Cancel"]').click();
|
await page.locator('[aria-label="Cancel"]').click();
|
||||||
|
|
||||||
// Link Child Folder from Parent Folder to My Items
|
// Move Child Folder from Parent Folder to My Items
|
||||||
await page.locator('.c-disclosure-triangle >> nth=0').click();
|
await treePane.getByRole('treeitem', {
|
||||||
await page.locator('.c-disclosure-triangle >> nth=1').click();
|
name: new RegExp(childFolder.name)
|
||||||
|
}).click({
|
||||||
await page.locator(`a:has-text("Child Folder") >> nth=0`).click({
|
|
||||||
button: 'right'
|
button: 'right'
|
||||||
});
|
});
|
||||||
await page.locator('li.icon-link').click();
|
await page.getByRole('menuitem', {
|
||||||
await page.locator(`form[name="mctForm"] >> text=${myItemsFolderName}`).click();
|
name: /Link/
|
||||||
|
}).click();
|
||||||
|
await myItemsLocatorTreeItem.click();
|
||||||
|
|
||||||
await page.locator('button:has-text("OK")').click();
|
await page.locator('[aria-label="Save"]').click();
|
||||||
|
const myItemsPaneTreeItem = treePane.getByRole('treeitem', {
|
||||||
|
name: myItemsFolderName
|
||||||
|
});
|
||||||
|
|
||||||
// Expect that Child Folder is in My Items, the root folder
|
// Expect that Child Folder is in My Items, the root folder
|
||||||
expect(page.locator(`text=${myItemsFolderName} >> nth=0:has(text=Child Folder)`)).toBeTruthy();
|
expect(myItemsPaneTreeItem.locator('nth=0:has(text=Child Folder)')).toBeTruthy();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -98,8 +98,8 @@ test.describe.serial('Condition Set CRUD Operations on @localStorage', () => {
|
|||||||
await page.locator('text=Conditions View Snapshot >> button').nth(3).click();
|
await page.locator('text=Conditions View Snapshot >> button').nth(3).click();
|
||||||
|
|
||||||
//Edit Condition Set Name from main view
|
//Edit Condition Set Name from main view
|
||||||
await page.locator('text=Unnamed Condition Set').first().fill('Renamed Condition Set');
|
await page.locator('.l-browse-bar__object-name').filter({ hasText: 'Unnamed Condition Set' }).first().fill('Renamed Condition Set');
|
||||||
await page.locator('text=Renamed Condition Set').first().press('Enter');
|
await page.locator('.l-browse-bar__object-name').filter({ hasText: 'Renamed Condition Set' }).first().press('Enter');
|
||||||
// Click Save Button
|
// Click Save Button
|
||||||
await page.locator('text=Snapshot Save and Finish Editing Save and Continue Editing >> button').nth(1).click();
|
await page.locator('text=Snapshot Save and Finish Editing Save and Continue Editing >> button').nth(1).click();
|
||||||
// Click Save and Finish Editing Option
|
// Click Save and Finish Editing Option
|
||||||
|
@ -24,6 +24,7 @@ const { test, expect } = require('../../../../pluginFixtures');
|
|||||||
const { createDomainObjectWithDefaults, setStartOffset, setFixedTimeMode, setRealTimeMode } = require('../../../../appActions');
|
const { createDomainObjectWithDefaults, setStartOffset, setFixedTimeMode, setRealTimeMode } = require('../../../../appActions');
|
||||||
|
|
||||||
test.describe('Display Layout', () => {
|
test.describe('Display Layout', () => {
|
||||||
|
/** @type {import('../../../../appActions').CreatedObjectInfo} */
|
||||||
let sineWaveObject;
|
let sineWaveObject;
|
||||||
test.beforeEach(async ({ page }) => {
|
test.beforeEach(async ({ page }) => {
|
||||||
await page.goto('./', { waitUntil: 'networkidle' });
|
await page.goto('./', { waitUntil: 'networkidle' });
|
||||||
@ -47,7 +48,12 @@ test.describe('Display Layout', () => {
|
|||||||
// Expand the 'My Items' folder in the left tree
|
// Expand the 'My Items' folder in the left tree
|
||||||
await page.locator('.c-tree__item__view-control.c-disclosure-triangle').click();
|
await page.locator('.c-tree__item__view-control.c-disclosure-triangle').click();
|
||||||
// Add the Sine Wave Generator to the Display Layout and save changes
|
// Add the Sine Wave Generator to the Display Layout and save changes
|
||||||
await page.dragAndDrop('text=Test Sine Wave Generator', '.l-layout__grid-holder');
|
const treePane = page.locator('#tree-pane');
|
||||||
|
const sineWaveGeneratorTreeItem = treePane.getByRole('treeitem', {
|
||||||
|
name: new RegExp(sineWaveObject.name)
|
||||||
|
});
|
||||||
|
const layoutGridHolder = page.locator('.l-layout__grid-holder');
|
||||||
|
await sineWaveGeneratorTreeItem.dragTo(layoutGridHolder);
|
||||||
await page.locator('button[title="Save"]').click();
|
await page.locator('button[title="Save"]').click();
|
||||||
await page.locator('text=Save and Finish Editing').click();
|
await page.locator('text=Save and Finish Editing').click();
|
||||||
|
|
||||||
@ -74,7 +80,12 @@ test.describe('Display Layout', () => {
|
|||||||
// Expand the 'My Items' folder in the left tree
|
// Expand the 'My Items' folder in the left tree
|
||||||
await page.locator('.c-tree__item__view-control.c-disclosure-triangle').click();
|
await page.locator('.c-tree__item__view-control.c-disclosure-triangle').click();
|
||||||
// Add the Sine Wave Generator to the Display Layout and save changes
|
// Add the Sine Wave Generator to the Display Layout and save changes
|
||||||
await page.dragAndDrop('text=Test Sine Wave Generator', '.l-layout__grid-holder');
|
const treePane = page.locator('#tree-pane');
|
||||||
|
const sineWaveGeneratorTreeItem = treePane.getByRole('treeitem', {
|
||||||
|
name: new RegExp(sineWaveObject.name)
|
||||||
|
});
|
||||||
|
const layoutGridHolder = page.locator('.l-layout__grid-holder');
|
||||||
|
await sineWaveGeneratorTreeItem.dragTo(layoutGridHolder);
|
||||||
await page.locator('button[title="Save"]').click();
|
await page.locator('button[title="Save"]').click();
|
||||||
await page.locator('text=Save and Finish Editing').click();
|
await page.locator('text=Save and Finish Editing').click();
|
||||||
|
|
||||||
@ -105,7 +116,12 @@ test.describe('Display Layout', () => {
|
|||||||
// Expand the 'My Items' folder in the left tree
|
// Expand the 'My Items' folder in the left tree
|
||||||
await page.locator('.c-tree__item__view-control.c-disclosure-triangle').click();
|
await page.locator('.c-tree__item__view-control.c-disclosure-triangle').click();
|
||||||
// Add the Sine Wave Generator to the Display Layout and save changes
|
// Add the Sine Wave Generator to the Display Layout and save changes
|
||||||
await page.dragAndDrop('text=Test Sine Wave Generator', '.l-layout__grid-holder');
|
const treePane = page.locator('#tree-pane');
|
||||||
|
const sineWaveGeneratorTreeItem = treePane.getByRole('treeitem', {
|
||||||
|
name: new RegExp(sineWaveObject.name)
|
||||||
|
});
|
||||||
|
const layoutGridHolder = page.locator('.l-layout__grid-holder');
|
||||||
|
await sineWaveGeneratorTreeItem.dragTo(layoutGridHolder);
|
||||||
await page.locator('button[title="Save"]').click();
|
await page.locator('button[title="Save"]').click();
|
||||||
await page.locator('text=Save and Finish Editing').click();
|
await page.locator('text=Save and Finish Editing').click();
|
||||||
|
|
||||||
@ -139,7 +155,12 @@ test.describe('Display Layout', () => {
|
|||||||
// Expand the 'My Items' folder in the left tree
|
// Expand the 'My Items' folder in the left tree
|
||||||
await page.locator('.c-tree__item__view-control.c-disclosure-triangle').click();
|
await page.locator('.c-tree__item__view-control.c-disclosure-triangle').click();
|
||||||
// Add the Sine Wave Generator to the Display Layout and save changes
|
// Add the Sine Wave Generator to the Display Layout and save changes
|
||||||
await page.dragAndDrop('text=Test Sine Wave Generator', '.l-layout__grid-holder');
|
const treePane = page.locator('#tree-pane');
|
||||||
|
const sineWaveGeneratorTreeItem = treePane.getByRole('treeitem', {
|
||||||
|
name: new RegExp(sineWaveObject.name)
|
||||||
|
});
|
||||||
|
const layoutGridHolder = page.locator('.l-layout__grid-holder');
|
||||||
|
await sineWaveGeneratorTreeItem.dragTo(layoutGridHolder);
|
||||||
await page.locator('button[title="Save"]').click();
|
await page.locator('button[title="Save"]').click();
|
||||||
await page.locator('text=Save and Finish Editing').click();
|
await page.locator('text=Save and Finish Editing').click();
|
||||||
|
|
||||||
|
85
e2e/tests/functional/recentObjects.e2e.spec.js
Normal file
85
e2e/tests/functional/recentObjects.e2e.spec.js
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* 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.
|
||||||
|
*****************************************************************************/
|
||||||
|
|
||||||
|
const { test, expect } = require('../../pluginFixtures.js');
|
||||||
|
const { createDomainObjectWithDefaults } = require('../../appActions.js');
|
||||||
|
|
||||||
|
test.describe('Recent Objects', () => {
|
||||||
|
test('Recent Objects CRUD operations', async ({ page }) => {
|
||||||
|
await page.goto('./', { waitUntil: 'networkidle' });
|
||||||
|
|
||||||
|
// Create a folder and nest a Clock within it
|
||||||
|
const recentObjectsList = page.locator('[aria-label="Recent Objects"]');
|
||||||
|
const folderA = await createDomainObjectWithDefaults(page, {
|
||||||
|
type: 'Folder'
|
||||||
|
});
|
||||||
|
const clock = await createDomainObjectWithDefaults(page, {
|
||||||
|
type: 'Clock',
|
||||||
|
parent: folderA.uuid
|
||||||
|
});
|
||||||
|
|
||||||
|
// Drag the Recent Objects panel up a bit
|
||||||
|
await page.locator('div:nth-child(2) > .l-pane__handle').hover();
|
||||||
|
await page.mouse.down();
|
||||||
|
await page.mouse.move(0, 100);
|
||||||
|
await page.mouse.up();
|
||||||
|
|
||||||
|
// Verify that both created objects appear in the list and are in the correct order
|
||||||
|
expect(recentObjectsList.getByRole('listitem', { name: clock.name })).toBeTruthy();
|
||||||
|
expect(recentObjectsList.getByRole('listitem', { name: folderA.name })).toBeTruthy();
|
||||||
|
expect(recentObjectsList.getByRole('listitem', { name: clock.name }).locator('a').getByText(folderA.name)).toBeTruthy();
|
||||||
|
expect(recentObjectsList.getByRole('listitem').nth(0).getByText(clock.name)).toBeTruthy();
|
||||||
|
expect(recentObjectsList.getByRole('listitem', { name: clock.name }).locator('a').getByText(folderA.name)).toBeTruthy();
|
||||||
|
expect(recentObjectsList.getByRole('listitem').nth(1).getByText(folderA.name)).toBeTruthy();
|
||||||
|
|
||||||
|
// Navigate to the folder by clicking on the main object name in the recent objects list item
|
||||||
|
await recentObjectsList.getByRole('listitem', { name: folderA.name }).getByText(folderA.name).click();
|
||||||
|
await page.waitForURL(`**/${folderA.uuid}?*`);
|
||||||
|
expect(recentObjectsList.getByRole('listitem').nth(0).getByText(folderA.name)).toBeTruthy();
|
||||||
|
|
||||||
|
// Rename
|
||||||
|
folderA.name = `${folderA.name}-NEW!`;
|
||||||
|
await page.locator('.l-browse-bar__object-name').fill("");
|
||||||
|
await page.locator('.l-browse-bar__object-name').fill(folderA.name);
|
||||||
|
await page.keyboard.press('Enter');
|
||||||
|
|
||||||
|
// Verify rename has been applied in recent objects list item and objects paths
|
||||||
|
expect(page.getByRole('listitem', { name: clock.name }).locator('a').getByText(folderA.name)).toBeTruthy();
|
||||||
|
expect(recentObjectsList.getByRole('listitem', { name: folderA.name })).toBeTruthy();
|
||||||
|
|
||||||
|
// Delete
|
||||||
|
await page.click('button[title="Show selected item in tree"]');
|
||||||
|
// Delete the folder via the left tree pane treeitem context menu
|
||||||
|
await page.getByRole('treeitem', { name: new RegExp(folderA.name) }).locator('a').click({
|
||||||
|
button: 'right'
|
||||||
|
});
|
||||||
|
await page.getByRole('menuitem', { name: /Remove/ }).click();
|
||||||
|
await page.getByRole('button', { name: 'OK' }).click();
|
||||||
|
|
||||||
|
// Verify that the folder and clock are no longer in the recent objects list
|
||||||
|
await expect(recentObjectsList.getByRole('listitem', { name: folderA.name })).toBeHidden();
|
||||||
|
await expect(recentObjectsList.getByRole('listitem', { name: clock.name })).toBeHidden();
|
||||||
|
});
|
||||||
|
test.fixme("Clicking on the 'target button' scrolls the object into view in the tree and highlights it");
|
||||||
|
test.fixme("Clicking on an object in the path of a recent object navigates to the object");
|
||||||
|
test.fixme("Tests for context menu actions from recent objects");
|
||||||
|
});
|
@ -72,7 +72,7 @@ test.describe('Grand Search', () => {
|
|||||||
await page.locator('[aria-label="OpenMCT Search"] [aria-label="Search Input"]').fill('Cl');
|
await page.locator('[aria-label="OpenMCT Search"] [aria-label="Search Input"]').fill('Cl');
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
page.waitForNavigation(),
|
page.waitForNavigation(),
|
||||||
page.locator('text=Clock A').click()
|
page.locator('[aria-label="Clock A clock result"] >> text=Clock A').click()
|
||||||
]);
|
]);
|
||||||
await expect(page.locator('.is-object-type-clock')).toBeVisible();
|
await expect(page.locator('.is-object-type-clock')).toBeVisible();
|
||||||
|
|
||||||
|
@ -22,6 +22,7 @@
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<mct-tree
|
<mct-tree
|
||||||
|
id="locator-tree"
|
||||||
:is-selector-tree="true"
|
:is-selector-tree="true"
|
||||||
:initial-selection="model.parent"
|
:initial-selection="model.parent"
|
||||||
@tree-item-selection="handleItemSelection"
|
@tree-item-selection="handleItemSelection"
|
||||||
|
@ -189,13 +189,11 @@ export default class ObjectAPI {
|
|||||||
/**
|
/**
|
||||||
* Get a domain object.
|
* Get a domain object.
|
||||||
*
|
*
|
||||||
* @method get
|
|
||||||
* @memberof module:openmct.ObjectProvider#
|
|
||||||
* @param {string} key the key for the domain object to load
|
* @param {string} key the key for the domain object to load
|
||||||
* @param {AbortController.signal} abortSignal (optional) signal to abort fetch requests
|
* @param {AbortSignal} abortSignal (optional) signal to abort fetch requests
|
||||||
* @param {boolean} forceRemote defaults to false. If true, will skip cached and
|
* @param {boolean} [forceRemote=false] defaults to false. If true, will skip cached and
|
||||||
* dirty/in-transaction objects use and the provider.get method
|
* dirty/in-transaction objects use and the provider.get method
|
||||||
* @returns {Promise} a promise which will resolve when the domain object
|
* @returns {Promise<DomainObject>} a promise which will resolve when the domain object
|
||||||
* has been saved, or be rejected if it cannot be saved
|
* has been saved, or be rejected if it cannot be saved
|
||||||
*/
|
*/
|
||||||
get(identifier, abortSignal, forceRemote = false) {
|
get(identifier, abortSignal, forceRemote = false) {
|
||||||
@ -220,7 +218,7 @@ export default class ObjectAPI {
|
|||||||
const provider = this.getProvider(identifier);
|
const provider = this.getProvider(identifier);
|
||||||
|
|
||||||
if (!provider) {
|
if (!provider) {
|
||||||
throw new Error('No Provider Matched');
|
throw new Error(`No Provider Matched for keyString "${this.makeKeyString(identifier)}}"`);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!provider.get) {
|
if (!provider.get) {
|
||||||
@ -740,6 +738,46 @@ export default class ObjectAPI {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse and construct an `objectPath` from a `navigationPath`.
|
||||||
|
*
|
||||||
|
* A `navigationPath` is a string of the form `"/browse/<keyString>/<keyString>/..."` that is used
|
||||||
|
* by the Open MCT router to navigate to a specific object.
|
||||||
|
*
|
||||||
|
* Throws an error if the `navigationPath` is malformed.
|
||||||
|
*
|
||||||
|
* @param {string} navigationPath
|
||||||
|
* @returns {DomainObject[]} objectPath
|
||||||
|
*/
|
||||||
|
async getRelativeObjectPath(navigationPath) {
|
||||||
|
if (!navigationPath.startsWith('/browse/')) {
|
||||||
|
throw new Error(`Malformed navigation path: "${navigationPath}"`);
|
||||||
|
}
|
||||||
|
|
||||||
|
navigationPath = navigationPath.replace('/browse/', '');
|
||||||
|
|
||||||
|
if (!navigationPath || navigationPath === '/') {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove any query params and split on '/'
|
||||||
|
const keyStrings = navigationPath.split('?')?.[0].split('/');
|
||||||
|
|
||||||
|
if (keyStrings[0] !== 'ROOT') {
|
||||||
|
keyStrings.unshift('ROOT');
|
||||||
|
}
|
||||||
|
|
||||||
|
const objectPath = (await Promise.all(
|
||||||
|
keyStrings.map(
|
||||||
|
keyString => this.supportsMutation(keyString)
|
||||||
|
? this.getMutable(utils.parseKeyString(keyString))
|
||||||
|
: this.get(utils.parseKeyString(keyString))
|
||||||
|
)
|
||||||
|
)).reverse();
|
||||||
|
|
||||||
|
return objectPath;
|
||||||
|
}
|
||||||
|
|
||||||
isObjectPathToALink(domainObject, objectPath) {
|
isObjectPathToALink(domainObject, objectPath) {
|
||||||
return objectPath !== undefined
|
return objectPath !== undefined
|
||||||
&& objectPath.length > 1
|
&& objectPath.length > 1
|
||||||
|
@ -31,6 +31,7 @@ export default class DuplicateAction {
|
|||||||
this.priority = 7;
|
this.priority = 7;
|
||||||
|
|
||||||
this.openmct = openmct;
|
this.openmct = openmct;
|
||||||
|
this.transaction = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
invoke(objectPath) {
|
invoke(objectPath) {
|
||||||
@ -45,7 +46,9 @@ export default class DuplicateAction {
|
|||||||
.some(objectInPath => this.openmct.objects.areIdsEqual(objectInPath.identifier, this.object.identifier));
|
.some(objectInPath => this.openmct.objects.areIdsEqual(objectInPath.identifier, this.object.identifier));
|
||||||
}
|
}
|
||||||
|
|
||||||
onSave(changes) {
|
async onSave(changes) {
|
||||||
|
this.startTransaction();
|
||||||
|
|
||||||
let inNavigationPath = this.inNavigationPath();
|
let inNavigationPath = this.inNavigationPath();
|
||||||
if (inNavigationPath && this.openmct.editor.isEditing()) {
|
if (inNavigationPath && this.openmct.editor.isEditing()) {
|
||||||
this.openmct.editor.save();
|
this.openmct.editor.save();
|
||||||
@ -59,7 +62,9 @@ export default class DuplicateAction {
|
|||||||
const parentDomainObjectpath = changes.location || [this.parent];
|
const parentDomainObjectpath = changes.location || [this.parent];
|
||||||
const parent = parentDomainObjectpath[0];
|
const parent = parentDomainObjectpath[0];
|
||||||
|
|
||||||
return duplicationTask.duplicate(this.object, parent);
|
await duplicationTask.duplicate(this.object, parent);
|
||||||
|
|
||||||
|
return this.saveTransaction();
|
||||||
}
|
}
|
||||||
|
|
||||||
showForm(domainObject, parentDomainObject) {
|
showForm(domainObject, parentDomainObject) {
|
||||||
@ -142,4 +147,20 @@ export default class DuplicateAction {
|
|||||||
&& parentType.definition.creatable
|
&& parentType.definition.creatable
|
||||||
&& Array.isArray(parent.composition);
|
&& Array.isArray(parent.composition);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
startTransaction() {
|
||||||
|
if (!this.openmct.objects.isTransactionActive()) {
|
||||||
|
this.transaction = this.openmct.objects.startTransaction();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async saveTransaction() {
|
||||||
|
if (!this.transaction) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.transaction.commit();
|
||||||
|
this.openmct.objects.endTransaction();
|
||||||
|
this.transaction = null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -30,6 +30,7 @@ export default class LinkAction {
|
|||||||
this.priority = 7;
|
this.priority = 7;
|
||||||
|
|
||||||
this.openmct = openmct;
|
this.openmct = openmct;
|
||||||
|
this.transaction = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
appliesTo(objectPath) {
|
appliesTo(objectPath) {
|
||||||
@ -48,7 +49,9 @@ export default class LinkAction {
|
|||||||
}
|
}
|
||||||
|
|
||||||
onSave(changes) {
|
onSave(changes) {
|
||||||
let inNavigationPath = this.inNavigationPath();
|
this.startTransaction();
|
||||||
|
|
||||||
|
const inNavigationPath = this.inNavigationPath();
|
||||||
if (inNavigationPath && this.openmct.editor.isEditing()) {
|
if (inNavigationPath && this.openmct.editor.isEditing()) {
|
||||||
this.openmct.editor.save();
|
this.openmct.editor.save();
|
||||||
}
|
}
|
||||||
@ -57,6 +60,8 @@ export default class LinkAction {
|
|||||||
const parent = parentDomainObjectpath[0];
|
const parent = parentDomainObjectpath[0];
|
||||||
|
|
||||||
this.linkInNewParent(this.object, parent);
|
this.linkInNewParent(this.object, parent);
|
||||||
|
|
||||||
|
return this.saveTransaction();
|
||||||
}
|
}
|
||||||
|
|
||||||
linkInNewParent(child, newParent) {
|
linkInNewParent(child, newParent) {
|
||||||
@ -128,4 +133,19 @@ export default class LinkAction {
|
|||||||
return parentCandidate && this.openmct.composition.checkPolicy(parentCandidate, this.object);
|
return parentCandidate && this.openmct.composition.checkPolicy(parentCandidate, this.object);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
startTransaction() {
|
||||||
|
if (!this.openmct.objects.isTransactionActive()) {
|
||||||
|
this.transaction = this.openmct.objects.startTransaction();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async saveTransaction() {
|
||||||
|
if (!this.transaction) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.transaction.commit();
|
||||||
|
this.openmct.objects.endTransaction();
|
||||||
|
this.transaction = null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -29,6 +29,7 @@ export default class MoveAction {
|
|||||||
this.priority = 7;
|
this.priority = 7;
|
||||||
|
|
||||||
this.openmct = openmct;
|
this.openmct = openmct;
|
||||||
|
this.transaction = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
invoke(objectPath) {
|
invoke(objectPath) {
|
||||||
@ -60,6 +61,8 @@ export default class MoveAction {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async onSave(changes) {
|
async onSave(changes) {
|
||||||
|
this.startTransaction();
|
||||||
|
|
||||||
let inNavigationPath = this.inNavigationPath(this.object);
|
let inNavigationPath = this.inNavigationPath(this.object);
|
||||||
if (inNavigationPath && this.openmct.editor.isEditing()) {
|
if (inNavigationPath && this.openmct.editor.isEditing()) {
|
||||||
this.openmct.editor.save();
|
this.openmct.editor.save();
|
||||||
@ -99,6 +102,8 @@ export default class MoveAction {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await this.saveTransaction();
|
||||||
|
|
||||||
this.navigateTo(newObjectPath);
|
this.navigateTo(newObjectPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -189,4 +194,20 @@ export default class MoveAction {
|
|||||||
&& childType.definition.creatable
|
&& childType.definition.creatable
|
||||||
&& Array.isArray(parent.composition);
|
&& Array.isArray(parent.composition);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
startTransaction() {
|
||||||
|
if (!this.openmct.objects.isTransactionActive()) {
|
||||||
|
this.transaction = this.openmct.objects.startTransaction();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async saveTransaction() {
|
||||||
|
if (!this.transaction) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.transaction.commit();
|
||||||
|
this.openmct.objects.endTransaction();
|
||||||
|
this.transaction = null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -48,6 +48,7 @@
|
|||||||
@import "../ui/layout/mct-tree.scss";
|
@import "../ui/layout/mct-tree.scss";
|
||||||
@import "../ui/layout/search/search.scss";
|
@import "../ui/layout/search/search.scss";
|
||||||
@import "../ui/layout/pane.scss";
|
@import "../ui/layout/pane.scss";
|
||||||
|
@import "../ui/layout/recent-objects.scss";
|
||||||
@import "../ui/layout/status-bar/indicators.scss";
|
@import "../ui/layout/status-bar/indicators.scss";
|
||||||
@import "../ui/layout/status-bar/notification-banner.scss";
|
@import "../ui/layout/status-bar/notification-banner.scss";
|
||||||
@import "../ui/preview/preview.scss";
|
@import "../ui/preview/preview.scss";
|
||||||
|
@ -60,8 +60,15 @@ export function identifierToString(openmct, objectPath) {
|
|||||||
return '#/browse/' + openmct.objects.getRelativePath(objectPath);
|
return '#/browse/' + openmct.objects.getRelativePath(objectPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {import('../../openmct').OpenMCT} openmct
|
||||||
|
* @param {Array<import('../api/objects/ObjectAPI').DomainObject>} objectPath
|
||||||
|
* @param {any} customUrlParams
|
||||||
|
* @returns {string} url
|
||||||
|
*/
|
||||||
export default function objectPathToUrl(openmct, objectPath, customUrlParams = {}) {
|
export default function objectPathToUrl(openmct, objectPath, customUrlParams = {}) {
|
||||||
let url = identifierToString(openmct, objectPath);
|
let url = identifierToString(openmct, objectPath);
|
||||||
|
|
||||||
let urlParams = paramsToArray(openmct, customUrlParams);
|
let urlParams = paramsToArray(openmct, customUrlParams);
|
||||||
if (urlParams.length) {
|
if (urlParams.length) {
|
||||||
url += '?' + urlParams.join('&');
|
url += '?' + urlParams.join('&');
|
||||||
|
@ -26,7 +26,7 @@ describe('the url tool', function () {
|
|||||||
];
|
];
|
||||||
openmct = createOpenMct();
|
openmct = createOpenMct();
|
||||||
openmct.on('start', () => {
|
openmct.on('start', () => {
|
||||||
openmct.router.setPath(' http://localhost:8020/foobar?testParam1=testValue1');
|
openmct.router.setPath('/browse/mine?testParam1=testValue1');
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
openmct.startHeadless();
|
openmct.startHeadless();
|
||||||
|
@ -22,11 +22,11 @@
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<ul
|
<ul
|
||||||
v-if="orderedOriginalPath.length"
|
v-if="orderedPath.length"
|
||||||
class="c-location"
|
class="c-location"
|
||||||
>
|
>
|
||||||
<li
|
<li
|
||||||
v-for="pathObject in orderedOriginalPath"
|
v-for="pathObject in orderedPath"
|
||||||
:key="pathObject.key"
|
:key="pathObject.key"
|
||||||
class="c-location__item"
|
class="c-location__item"
|
||||||
>
|
>
|
||||||
@ -65,11 +65,17 @@ export default {
|
|||||||
default() {
|
default() {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
objectPath: {
|
||||||
|
type: Array,
|
||||||
|
default() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
orderedOriginalPath: []
|
orderedPath: []
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
async mounted() {
|
async mounted() {
|
||||||
@ -79,9 +85,14 @@ export default {
|
|||||||
this.keyString = keyString;
|
this.keyString = keyString;
|
||||||
this.originalPath = [];
|
this.originalPath = [];
|
||||||
|
|
||||||
const rawOriginalPath = await this.openmct.objects.getOriginalPath(keyString);
|
let rawPath = null;
|
||||||
|
if (this.objectPath === null) {
|
||||||
|
rawPath = await this.openmct.objects.getOriginalPath(keyString);
|
||||||
|
} else {
|
||||||
|
rawPath = this.objectPath;
|
||||||
|
}
|
||||||
|
|
||||||
const pathWithDomainObject = rawOriginalPath.map((domainObject, index, pathArray) => {
|
const pathWithDomainObject = rawPath.map((domainObject, index, pathArray) => {
|
||||||
let key = this.openmct.objects.makeKeyString(domainObject.identifier);
|
let key = this.openmct.objects.makeKeyString(domainObject.identifier);
|
||||||
const objectPath = pathArray.slice(index);
|
const objectPath = pathArray.slice(index);
|
||||||
|
|
||||||
@ -93,10 +104,10 @@ export default {
|
|||||||
});
|
});
|
||||||
if (this.showObjectItself) {
|
if (this.showObjectItself) {
|
||||||
// remove ROOT only
|
// remove ROOT only
|
||||||
this.orderedOriginalPath = pathWithDomainObject.slice(0, pathWithDomainObject.length - 1).reverse();
|
this.orderedPath = pathWithDomainObject.slice(0, pathWithDomainObject.length - 1).reverse();
|
||||||
} else {
|
} else {
|
||||||
// remove ROOT and object itself from path
|
// remove ROOT and object itself from path
|
||||||
this.orderedOriginalPath = pathWithDomainObject.slice(1, pathWithDomainObject.length - 1).reverse();
|
this.orderedPath = pathWithDomainObject.slice(1, pathWithDomainObject.length - 1).reverse();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -36,6 +36,7 @@
|
|||||||
&__type-icon {
|
&__type-icon {
|
||||||
width: auto;
|
width: auto;
|
||||||
font-size: 1em;
|
font-size: 1em;
|
||||||
|
min-width: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
|
@ -53,11 +53,12 @@
|
|||||||
type="horizontal"
|
type="horizontal"
|
||||||
>
|
>
|
||||||
<pane
|
<pane
|
||||||
id="tree-pane"
|
|
||||||
class="l-shell__pane-tree"
|
class="l-shell__pane-tree"
|
||||||
|
style="width: 300px;"
|
||||||
handle="after"
|
handle="after"
|
||||||
label="Browse"
|
label="Browse"
|
||||||
hide-param="hideTree"
|
hide-param="hideTree"
|
||||||
|
:persist-position="true"
|
||||||
@start-resizing="onStartResizing"
|
@start-resizing="onStartResizing"
|
||||||
@end-resizing="onEndResizing"
|
@end-resizing="onEndResizing"
|
||||||
>
|
>
|
||||||
@ -75,11 +76,30 @@
|
|||||||
@click="handleSyncTreeNavigation"
|
@click="handleSyncTreeNavigation"
|
||||||
>
|
>
|
||||||
</button>
|
</button>
|
||||||
<mct-tree
|
<multipane
|
||||||
:sync-tree-navigation="triggerSync"
|
type="vertical"
|
||||||
:reset-tree-navigation="triggerReset"
|
>
|
||||||
class="l-shell__tree"
|
<pane
|
||||||
/>
|
id="tree-pane"
|
||||||
|
>
|
||||||
|
<mct-tree
|
||||||
|
ref="mctTree"
|
||||||
|
:sync-tree-navigation="triggerSync"
|
||||||
|
:reset-tree-navigation="triggerReset"
|
||||||
|
class="l-shell__tree"
|
||||||
|
/>
|
||||||
|
</pane>
|
||||||
|
<pane
|
||||||
|
handle="before"
|
||||||
|
label="Recently Viewed"
|
||||||
|
:persist-position="true"
|
||||||
|
>
|
||||||
|
<RecentObjectsList
|
||||||
|
class="l-shell__tree"
|
||||||
|
@openAndScrollTo="openAndScrollTo($event)"
|
||||||
|
/>
|
||||||
|
</pane>
|
||||||
|
</multipane>
|
||||||
</pane>
|
</pane>
|
||||||
<pane class="l-shell__pane-main">
|
<pane class="l-shell__pane-main">
|
||||||
<browse-bar
|
<browse-bar
|
||||||
@ -109,6 +129,7 @@
|
|||||||
handle="before"
|
handle="before"
|
||||||
label="Inspect"
|
label="Inspect"
|
||||||
hide-param="hideInspector"
|
hide-param="hideInspector"
|
||||||
|
:persist-position="true"
|
||||||
@start-resizing="onStartResizing"
|
@start-resizing="onStartResizing"
|
||||||
@end-resizing="onEndResizing"
|
@end-resizing="onEndResizing"
|
||||||
>
|
>
|
||||||
@ -134,6 +155,7 @@ import Toolbar from '../toolbar/Toolbar.vue';
|
|||||||
import AppLogo from './AppLogo.vue';
|
import AppLogo from './AppLogo.vue';
|
||||||
import Indicators from './status-bar/Indicators.vue';
|
import Indicators from './status-bar/Indicators.vue';
|
||||||
import NotificationBanner from './status-bar/NotificationBanner.vue';
|
import NotificationBanner from './status-bar/NotificationBanner.vue';
|
||||||
|
import RecentObjectsList from './RecentObjectsList.vue';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
@ -148,7 +170,8 @@ export default {
|
|||||||
Toolbar,
|
Toolbar,
|
||||||
AppLogo,
|
AppLogo,
|
||||||
Indicators,
|
Indicators,
|
||||||
NotificationBanner
|
NotificationBanner,
|
||||||
|
RecentObjectsList
|
||||||
},
|
},
|
||||||
inject: ['openmct'],
|
inject: ['openmct'],
|
||||||
data: function () {
|
data: function () {
|
||||||
@ -245,6 +268,10 @@ export default {
|
|||||||
|
|
||||||
this.hasToolbar = structure.length > 0;
|
this.hasToolbar = structure.length > 0;
|
||||||
},
|
},
|
||||||
|
openAndScrollTo(navigationPath) {
|
||||||
|
this.$refs.mctTree.openAndScrollTo(navigationPath);
|
||||||
|
this.$refs.mctTree.targetedPath = navigationPath;
|
||||||
|
},
|
||||||
setActionCollection(actionCollection) {
|
setActionCollection(actionCollection) {
|
||||||
this.actionCollection = actionCollection;
|
this.actionCollection = actionCollection;
|
||||||
},
|
},
|
||||||
|
200
src/ui/layout/RecentObjectsList.vue
Normal file
200
src/ui/layout/RecentObjectsList.vue
Normal file
@ -0,0 +1,200 @@
|
|||||||
|
<template>
|
||||||
|
<div
|
||||||
|
class="c-tree-and-search l-shell__tree"
|
||||||
|
>
|
||||||
|
<ul
|
||||||
|
class="c-tree-and-search__tree c-tree c-tree__scrollable"
|
||||||
|
>
|
||||||
|
<recent-objects-list-item
|
||||||
|
v-for="(recentObject) in recentObjects"
|
||||||
|
:key="recentObject.navigationPath"
|
||||||
|
:object-path="recentObject.objectPath"
|
||||||
|
:navigation-path="recentObject.navigationPath"
|
||||||
|
:domain-object="recentObject.domainObject"
|
||||||
|
@openAndScrollTo="openAndScrollTo($event)"
|
||||||
|
/>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
const MAX_RECENT_ITEMS = 20;
|
||||||
|
const LOCAL_STORAGE_KEY__RECENT_OBJECTS = 'mct-recent-objects';
|
||||||
|
import RecentObjectsListItem from './RecentObjectsListItem.vue';
|
||||||
|
export default {
|
||||||
|
name: 'RecentObjectsList',
|
||||||
|
components: {
|
||||||
|
RecentObjectsListItem
|
||||||
|
},
|
||||||
|
inject: ['openmct'],
|
||||||
|
props: {
|
||||||
|
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
recents: []
|
||||||
|
};
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
recentObjects() {
|
||||||
|
return this.recents.filter((recentObject) => {
|
||||||
|
return recentObject.location !== null;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.compositionCollections = {};
|
||||||
|
this.openmct.router.on('change:path', this.onPathChange);
|
||||||
|
this.getSavedRecentItems();
|
||||||
|
},
|
||||||
|
destroyed() {
|
||||||
|
this.openmct.router.off('change:path', this.onPathChange);
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
/**
|
||||||
|
* Add a composition collection to the map and register its remove handler
|
||||||
|
* @param {string} navigationPath
|
||||||
|
*/
|
||||||
|
addCompositionListenerFor(navigationPath) {
|
||||||
|
this.compositionCollections[navigationPath].removeHandler = this.compositionRemoveHandler(navigationPath);
|
||||||
|
this.compositionCollections[navigationPath].collection.on('remove',
|
||||||
|
this.compositionCollections[navigationPath].removeHandler);
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* Handler for composition collection remove events.
|
||||||
|
* Removes the object and any of its children from the recents list.
|
||||||
|
* @param {string} navigationPath
|
||||||
|
*/
|
||||||
|
compositionRemoveHandler(navigationPath) {
|
||||||
|
/**
|
||||||
|
* @param {import('../../api/objects/ObjectAPI').Identifier | string} identifier
|
||||||
|
*/
|
||||||
|
return (identifier) => {
|
||||||
|
// Construct the navigationPath of the removed object itself
|
||||||
|
const removedNavigationPath = `${navigationPath}/${this.openmct.objects.makeKeyString(identifier)}`;
|
||||||
|
|
||||||
|
// Remove the object and any of its children from the recents list
|
||||||
|
this.recents = this.recents.filter((recentObject) => {
|
||||||
|
return !recentObject.navigationPath.includes(removedNavigationPath);
|
||||||
|
});
|
||||||
|
|
||||||
|
this.removeCompositionListenerFor(removedNavigationPath);
|
||||||
|
};
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* Restores the RecentObjects list from localStorage, retrieves composition collections,
|
||||||
|
* and registers composition listeners for composable objects.
|
||||||
|
*/
|
||||||
|
getSavedRecentItems() {
|
||||||
|
const savedRecentsString = localStorage.getItem(LOCAL_STORAGE_KEY__RECENT_OBJECTS);
|
||||||
|
const savedRecents = savedRecentsString ? JSON.parse(savedRecentsString) : [];
|
||||||
|
|
||||||
|
// Get composition collections and add composition listeners for composable objects
|
||||||
|
savedRecents.forEach((recentObject) => {
|
||||||
|
const { domainObject, navigationPath } = recentObject;
|
||||||
|
if (this.shouldTrackCompositionFor(domainObject)) {
|
||||||
|
this.compositionCollections[navigationPath] = {};
|
||||||
|
this.compositionCollections[navigationPath].collection = this.openmct.composition.get(domainObject);
|
||||||
|
this.addCompositionListenerFor(navigationPath);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
this.recents = savedRecents;
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* Handler for 'change:path' router events.
|
||||||
|
* Adds or moves to the top the object at the given path to the recents list.
|
||||||
|
* Registers compositionCollection listeners for composable objects.
|
||||||
|
* Enforces the MAX_RECENT_ITEMS limit.
|
||||||
|
* @param {string} navigationPath
|
||||||
|
*/
|
||||||
|
async onPathChange(navigationPath) {
|
||||||
|
// Short-circuit if the path is not a navigationPath
|
||||||
|
if (!navigationPath.startsWith('/browse/')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const objectPath = await this.openmct.objects.getRelativeObjectPath(navigationPath);
|
||||||
|
if (!objectPath.length) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const domainObject = objectPath[0];
|
||||||
|
|
||||||
|
// Get rid of '/ROOT' if it exists in the navigationPath.
|
||||||
|
// Handles for the case of navigating to "My Items" from a RecentObjectsListItem.
|
||||||
|
// Could lead to dupes of "My Items" in the RecentObjectsList if we don't drop the 'ROOT' here.
|
||||||
|
if (navigationPath.includes('/ROOT')) {
|
||||||
|
navigationPath = navigationPath.replace('/ROOT', '');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.shouldTrackCompositionFor(domainObject, navigationPath)) {
|
||||||
|
this.compositionCollections[navigationPath] = {};
|
||||||
|
this.compositionCollections[navigationPath].collection = this.openmct.composition.get(domainObject);
|
||||||
|
this.addCompositionListenerFor(navigationPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Don't add deleted objects to the recents list
|
||||||
|
if (domainObject?.location === null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Move the object to the top if its already existing in the recents list
|
||||||
|
const existingIndex = this.recents.findIndex((recentObject) => {
|
||||||
|
return navigationPath === recentObject.navigationPath;
|
||||||
|
});
|
||||||
|
if (existingIndex !== -1) {
|
||||||
|
this.recents.splice(existingIndex, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.recents.unshift({
|
||||||
|
objectPath,
|
||||||
|
navigationPath,
|
||||||
|
domainObject
|
||||||
|
});
|
||||||
|
|
||||||
|
// Enforce a max number of recent items
|
||||||
|
while (this.recents.length > MAX_RECENT_ITEMS) {
|
||||||
|
const poppedRecentItem = this.recents.pop();
|
||||||
|
this.removeCompositionListenerFor(poppedRecentItem.navigationPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.setSavedRecentItems();
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* Delete the composition collection and unregister its remove handler
|
||||||
|
* @param {string} navigationPath
|
||||||
|
*/
|
||||||
|
removeCompositionListenerFor(navigationPath) {
|
||||||
|
if (this.compositionCollections[navigationPath]) {
|
||||||
|
this.compositionCollections[navigationPath].collection.off('remove',
|
||||||
|
this.compositionCollections[navigationPath].removeHandler);
|
||||||
|
delete this.compositionCollections[navigationPath];
|
||||||
|
}
|
||||||
|
},
|
||||||
|
openAndScrollTo(navigationPath) {
|
||||||
|
this.$emit("openAndScrollTo", navigationPath);
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* Saves the Recent Objects list to localStorage.
|
||||||
|
*/
|
||||||
|
setSavedRecentItems() {
|
||||||
|
localStorage.setItem(LOCAL_STORAGE_KEY__RECENT_OBJECTS, JSON.stringify(this.recents));
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* Returns true if the `domainObject` supports composition and we are not already
|
||||||
|
* tracking its composition.
|
||||||
|
* @param {import('../../api/objects/ObjectAPI').DomainObject} domainObject
|
||||||
|
* @param {string} navigationPath
|
||||||
|
*/
|
||||||
|
shouldTrackCompositionFor(domainObject, navigationPath) {
|
||||||
|
return this.compositionCollections[navigationPath] === undefined
|
||||||
|
&& this.openmct.composition.supportsComposition(domainObject);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
|
||||||
|
</style>
|
134
src/ui/layout/RecentObjectsListItem.vue
Normal file
134
src/ui/layout/RecentObjectsListItem.vue
Normal file
@ -0,0 +1,134 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* Open MCT, Copyright (c) 2014-2022, United States Government
|
||||||
|
* as represented by the Administrator of the National Aeronautics and Space
|
||||||
|
* Administration. All rights reserved.
|
||||||
|
*
|
||||||
|
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*
|
||||||
|
* Open MCT includes source code licensed under additional open source
|
||||||
|
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||||
|
* this source code distribution or the Licensing information page available
|
||||||
|
* at runtime from the About dialog for additional information.
|
||||||
|
*****************************************************************************/
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<li
|
||||||
|
class="c-recentobjects-listitem c-recentobjects-listitem--object"
|
||||||
|
:class="isAlias"
|
||||||
|
:aria-label="`${domainObject.name}`"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="c-recentobjects-listitem__type-icon recent-object-icon"
|
||||||
|
:class="resultTypeIcon"
|
||||||
|
></div>
|
||||||
|
<div
|
||||||
|
class="c-recentobjects-listitem__body"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="c-recentobjects-listitem__title"
|
||||||
|
:name="domainObject.name"
|
||||||
|
draggable="true"
|
||||||
|
@dragstart="dragStart"
|
||||||
|
@click.prevent="clickedRecent"
|
||||||
|
>
|
||||||
|
{{ domainObject.name }}
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<ObjectPath
|
||||||
|
class="c-recentobjects-listitem__object-path"
|
||||||
|
:read-only="false"
|
||||||
|
:domain-object="domainObject"
|
||||||
|
:show-original-path="false"
|
||||||
|
:object-path="objectPath"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="c-recentobjects-listitem__target-button">
|
||||||
|
<button
|
||||||
|
class="c-icon-button icon-target"
|
||||||
|
@click="openAndScrollTo(navigationPath)"
|
||||||
|
></button>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import ObjectPath from '../components/ObjectPath.vue';
|
||||||
|
import PreviewAction from '../preview/PreviewAction';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'RecentObjectsListItem',
|
||||||
|
components: {
|
||||||
|
ObjectPath
|
||||||
|
},
|
||||||
|
inject: ['openmct'],
|
||||||
|
props: {
|
||||||
|
domainObject: {
|
||||||
|
type: Object,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
navigationPath: {
|
||||||
|
type: String,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
objectPath: {
|
||||||
|
type: Array,
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
isAlias() {
|
||||||
|
return this.openmct.objects.isObjectPathToALink(this.domainObject, this.objectPath) ? { 'is-alias': true } : undefined;
|
||||||
|
},
|
||||||
|
resultTypeIcon() {
|
||||||
|
return this.openmct.types.get(this.domainObject.type).definition.cssClass;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.previewAction = new PreviewAction(this.openmct);
|
||||||
|
this.previewAction.on('isVisible', this.togglePreviewState);
|
||||||
|
},
|
||||||
|
destroyed() {
|
||||||
|
this.previewAction.off('isVisible', this.togglePreviewState);
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
clickedRecent(_event) {
|
||||||
|
if (this.openmct.editor.isEditing()) {
|
||||||
|
this.preview();
|
||||||
|
} else {
|
||||||
|
this.openmct.router.navigate(`#${this.navigationPath}`);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
togglePreviewState(previewState) {
|
||||||
|
this.$emit('preview-changed', previewState);
|
||||||
|
},
|
||||||
|
preview() {
|
||||||
|
if (this.previewAction.appliesTo(this.objectPath)) {
|
||||||
|
this.previewAction.invoke(this.objectPath);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
dragStart(event) {
|
||||||
|
const navigatedObject = this.openmct.router.path[0];
|
||||||
|
const serializedPath = JSON.stringify(this.objectPath);
|
||||||
|
const keyString = this.openmct.objects.makeKeyString(this.domainObject.identifier);
|
||||||
|
if (this.openmct.composition.checkPolicy(navigatedObject, this.domainObject)) {
|
||||||
|
event.dataTransfer.setData("openmct/composable-domain-object", JSON.stringify(this.domainObject));
|
||||||
|
}
|
||||||
|
|
||||||
|
event.dataTransfer.setData("openmct/domain-object-path", serializedPath);
|
||||||
|
event.dataTransfer.setData(`openmct/domain-object/${keyString}`, this.domainObject);
|
||||||
|
},
|
||||||
|
openAndScrollTo(navigationPath) {
|
||||||
|
this.$emit('openAndScrollTo', navigationPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
@ -289,7 +289,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
&__pane-tree {
|
&__pane-tree {
|
||||||
width: 300px;
|
width: 100%;
|
||||||
padding-left: nth($shellPanePad, 2);
|
padding-left: nth($shellPanePad, 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -108,6 +108,10 @@
|
|||||||
color: $colorItemTreeSelectedFg;
|
color: $colorItemTreeSelectedFg;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
&.is-targeted-item {
|
||||||
|
$c: $colorBodyFg;
|
||||||
|
@include pulseProp($animName: flashTarget, $dur: 500ms, $iter: 8, $prop: background, $valStart: rgba($c, 0.4), $valEnd: rgba($c, 0));
|
||||||
|
}
|
||||||
|
|
||||||
&.is-new {
|
&.is-new {
|
||||||
animation-name: animTemporaryHighlight;
|
animation-name: animTemporaryHighlight;
|
||||||
|
@ -88,10 +88,12 @@
|
|||||||
:item-height="itemHeight"
|
:item-height="itemHeight"
|
||||||
:open-items="openTreeItems"
|
:open-items="openTreeItems"
|
||||||
:loading-items="treeItemLoading"
|
:loading-items="treeItemLoading"
|
||||||
|
:targeted-path="targetedPath"
|
||||||
@tree-item-mounted="scrollToCheck($event)"
|
@tree-item-mounted="scrollToCheck($event)"
|
||||||
@tree-item-destroyed="removeCompositionListenerFor($event)"
|
@tree-item-destroyed="removeCompositionListenerFor($event)"
|
||||||
@tree-item-action="treeItemAction(treeItem, $event)"
|
@tree-item-action="treeItemAction(treeItem, $event)"
|
||||||
@tree-item-selection="treeItemSelection(treeItem)"
|
@tree-item-selection="treeItemSelection(treeItem)"
|
||||||
|
@targeted-path-animation-end="targetedPathAnimationEnd()"
|
||||||
/>
|
/>
|
||||||
<!-- main loading -->
|
<!-- main loading -->
|
||||||
<div
|
<div
|
||||||
@ -174,19 +176,18 @@ export default {
|
|||||||
itemOffset: 0,
|
itemOffset: 0,
|
||||||
activeSearch: false,
|
activeSearch: false,
|
||||||
mainTreeTopMargin: undefined,
|
mainTreeTopMargin: undefined,
|
||||||
selectedItem: {}
|
selectedItem: {},
|
||||||
|
targetedPath: ''
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
childrenHeight() {
|
childrenHeight() {
|
||||||
let childrenCount = this.focusedItems.length || 1;
|
const childrenCount = this.focusedItems.length || 1;
|
||||||
|
|
||||||
return (this.itemHeight * childrenCount) - this.mainTreeTopMargin; // 5px margin
|
return (this.itemHeight * childrenCount) - this.mainTreeTopMargin; // 5px margin
|
||||||
},
|
},
|
||||||
childrenHeightStyles() {
|
childrenHeightStyles() {
|
||||||
let height = this.childrenHeight + 'px';
|
return { height: `${this.childrenHeight}px` };
|
||||||
|
|
||||||
return { height };
|
|
||||||
},
|
},
|
||||||
focusedItems() {
|
focusedItems() {
|
||||||
return this.activeSearch ? this.searchResultItems : this.treeItems;
|
return this.activeSearch ? this.searchResultItems : this.treeItems;
|
||||||
@ -195,9 +196,7 @@ export default {
|
|||||||
return Math.ceil(this.mainTreeHeight / this.itemHeight) + ITEM_BUFFER;
|
return Math.ceil(this.mainTreeHeight / this.itemHeight) + ITEM_BUFFER;
|
||||||
},
|
},
|
||||||
scrollableStyles() {
|
scrollableStyles() {
|
||||||
let height = this.mainTreeHeight + 'px';
|
return { height: `${this.mainTreeHeight}px` };
|
||||||
|
|
||||||
return { height };
|
|
||||||
},
|
},
|
||||||
showNoItems() {
|
showNoItems() {
|
||||||
return this.visibleItems.length === 0 && !this.activeSearch && this.searchValue === '' && !this.isLoading;
|
return this.visibleItems.length === 0 && !this.activeSearch && this.searchValue === '' && !this.isLoading;
|
||||||
@ -209,7 +208,7 @@ export default {
|
|||||||
if (!this.isSelectorTree) {
|
if (!this.isSelectorTree) {
|
||||||
return {};
|
return {};
|
||||||
} else {
|
} else {
|
||||||
return { 'min-height': this.itemHeight * LOCATOR_ITEM_COUNT_HEIGHT + 'px' };
|
return { minHeight: `${this.itemHeight * LOCATOR_ITEM_COUNT_HEIGHT}px`};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -311,6 +310,9 @@ export default {
|
|||||||
this.openTreeItem(parentItem);
|
this.openTreeItem(parentItem);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
targetedPathAnimationEnd() {
|
||||||
|
this.targetedPath = undefined;
|
||||||
|
},
|
||||||
treeItemSelection(item) {
|
treeItemSelection(item) {
|
||||||
this.selectedItem = item;
|
this.selectedItem = item;
|
||||||
this.$emit('tree-item-selection', item);
|
this.$emit('tree-item-selection', item);
|
||||||
@ -457,6 +459,9 @@ export default {
|
|||||||
|
|
||||||
this.treeItemSelection(item);
|
this.treeItemSelection(item);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.scrollToCheck(navigationPath);
|
||||||
|
this.scrollToPath = null;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
scrollToCheck(navigationPath) {
|
scrollToCheck(navigationPath) {
|
||||||
@ -480,9 +485,9 @@ export default {
|
|||||||
behavior: 'smooth'
|
behavior: 'smooth'
|
||||||
});
|
});
|
||||||
} else if (this.scrollToPath) {
|
} else if (this.scrollToPath) {
|
||||||
this.scrollToPath = undefined;
|
this.scrollToPath = null;
|
||||||
delete this.scrollToPath;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
},
|
},
|
||||||
scrollEndEvent() {
|
scrollEndEvent() {
|
||||||
if (!this.$refs.scrollable) {
|
if (!this.$refs.scrollable) {
|
||||||
@ -494,12 +499,14 @@ export default {
|
|||||||
if (!this.isItemInView(this.scrollToPath)) {
|
if (!this.isItemInView(this.scrollToPath)) {
|
||||||
this.scrollTo(this.scrollToPath);
|
this.scrollTo(this.scrollToPath);
|
||||||
} else {
|
} else {
|
||||||
this.scrollToPath = undefined;
|
this.scrollToPath = null;
|
||||||
delete this.scrollToPath;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
setTargetedItem(navigationPath) {
|
||||||
|
this.targetedItem = navigationPath;
|
||||||
|
},
|
||||||
isItemInView(navigationPath) {
|
isItemInView(navigationPath) {
|
||||||
const indexOfScroll = this.treeItems.findIndex(item => item.navigationPath === navigationPath);
|
const indexOfScroll = this.treeItems.findIndex(item => item.navigationPath === navigationPath);
|
||||||
const scrollTopAmount = indexOfScroll * this.itemHeight;
|
const scrollTopAmount = indexOfScroll * this.itemHeight;
|
||||||
|
@ -83,6 +83,8 @@
|
|||||||
&[class*="--vertical"] {
|
&[class*="--vertical"] {
|
||||||
padding-top: $interiorMargin;
|
padding-top: $interiorMargin;
|
||||||
padding-bottom: $interiorMargin;
|
padding-bottom: $interiorMargin;
|
||||||
|
min-height: 30px; // For Recents holder
|
||||||
|
|
||||||
&.l-pane--collapsed {
|
&.l-pane--collapsed {
|
||||||
padding-top: 0 !important;
|
padding-top: 0 !important;
|
||||||
padding-bottom: 0 !important;
|
padding-bottom: 0 !important;
|
||||||
|
@ -1,20 +1,12 @@
|
|||||||
<template>
|
<template>
|
||||||
<div
|
<div
|
||||||
class="l-pane"
|
class="l-pane"
|
||||||
:class="{
|
:class="paneClasses"
|
||||||
'l-pane--horizontal-handle-before': type === 'horizontal' && handle === 'before',
|
|
||||||
'l-pane--horizontal-handle-after': type === 'horizontal' && handle === 'after',
|
|
||||||
'l-pane--vertical-handle-before': type === 'vertical' && handle === 'before',
|
|
||||||
'l-pane--vertical-handle-after': type === 'vertical' && handle === 'after',
|
|
||||||
'l-pane--collapsed': collapsed,
|
|
||||||
'l-pane--reacts': !handle,
|
|
||||||
'l-pane--resizing': resizing === true
|
|
||||||
}"
|
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
v-if="handle"
|
v-if="handle"
|
||||||
class="l-pane__handle"
|
class="l-pane__handle"
|
||||||
@mousedown="start"
|
@mousedown.prevent="startResizing"
|
||||||
></div>
|
></div>
|
||||||
<div class="l-pane__header">
|
<div class="l-pane__header">
|
||||||
<span
|
<span
|
||||||
@ -42,6 +34,7 @@
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
const COLLAPSE_THRESHOLD_PX = 40;
|
const COLLAPSE_THRESHOLD_PX = 40;
|
||||||
|
const LOCAL_STORAGE_KEY__PANE_POSITIONS = 'mct-pane-positions';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
inject: ['openmct'],
|
inject: ['openmct'],
|
||||||
@ -60,6 +53,10 @@ export default {
|
|||||||
hideParam: {
|
hideParam: {
|
||||||
type: String,
|
type: String,
|
||||||
default: ''
|
default: ''
|
||||||
|
},
|
||||||
|
persistPosition: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
@ -70,7 +67,25 @@ export default {
|
|||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
isCollapsable() {
|
isCollapsable() {
|
||||||
return this.hideParam && this.hideParam.length > 0;
|
return this.hideParam?.length > 0;
|
||||||
|
},
|
||||||
|
localStorageKey() {
|
||||||
|
if (!this.label) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.label.toLowerCase().replace(/ /g, '-');
|
||||||
|
},
|
||||||
|
paneClasses() {
|
||||||
|
return {
|
||||||
|
'l-pane--horizontal-handle-before': this.type === 'horizontal' && this.handle === 'before',
|
||||||
|
'l-pane--horizontal-handle-after': this.type === 'horizontal' && this.handle === 'after',
|
||||||
|
'l-pane--vertical-handle-before': this.type === 'vertical' && this.handle === 'before',
|
||||||
|
'l-pane--vertical-handle-after': this.type === 'vertical' && this.handle === 'after',
|
||||||
|
'l-pane--collapsed': this.collapsed,
|
||||||
|
'l-pane--reacts': !this.handle,
|
||||||
|
'l-pane--resizing': this.resizing === true
|
||||||
|
};
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
beforeMount() {
|
beforeMount() {
|
||||||
@ -78,62 +93,33 @@ export default {
|
|||||||
this.styleProp = (this.type === 'horizontal') ? 'width' : 'height';
|
this.styleProp = (this.type === 'horizontal') ? 'width' : 'height';
|
||||||
},
|
},
|
||||||
async mounted() {
|
async mounted() {
|
||||||
|
if (this.persistPosition) {
|
||||||
|
const savedPosition = this.getSavedPosition();
|
||||||
|
if (savedPosition) {
|
||||||
|
this.$el.style[this.styleProp] = savedPosition;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
await this.$nextTick();
|
await this.$nextTick();
|
||||||
// Hide tree and/or inspector pane if specified in URL
|
// Hide tree and/or inspector pane if specified in URL
|
||||||
if (this.isCollapsable) {
|
if (this.isCollapsable) {
|
||||||
this.handleHideUrl();
|
this.handleHideUrl();
|
||||||
}
|
}
|
||||||
|
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
toggleCollapse: function (e) {
|
addHideParam(target) {
|
||||||
if (this.collapsed) {
|
|
||||||
this.handleExpand();
|
|
||||||
this.removeHideParam(this.hideParam);
|
|
||||||
} else {
|
|
||||||
this.handleCollapse();
|
|
||||||
this.addHideParam(this.hideParam);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
handleHideUrl: function () {
|
|
||||||
const hideParam = this.openmct.router.getSearchParam(this.hideParam);
|
|
||||||
|
|
||||||
if (hideParam === 'true') {
|
|
||||||
this.handleCollapse();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
addHideParam: function (target) {
|
|
||||||
this.openmct.router.setSearchParam(target, 'true');
|
this.openmct.router.setSearchParam(target, 'true');
|
||||||
},
|
},
|
||||||
removeHideParam: function (target) {
|
endResizing(_event) {
|
||||||
this.openmct.router.deleteSearchParam(target);
|
document.body.removeEventListener('mousemove', this.updatePosition);
|
||||||
|
document.body.removeEventListener('mouseup', this.endResizing);
|
||||||
|
this.resizing = false;
|
||||||
|
this.$emit('end-resizing');
|
||||||
|
this.trackSize();
|
||||||
},
|
},
|
||||||
handleCollapse: function () {
|
getNewSize(event) {
|
||||||
this.currentSize = (this.dragCollapse === true) ? this.initial : this.$el.style[this.styleProp];
|
const delta = this.startPosition - this.getPosition(event);
|
||||||
this.$el.style[this.styleProp] = '';
|
|
||||||
this.collapsed = true;
|
|
||||||
},
|
|
||||||
handleExpand: function () {
|
|
||||||
this.$el.style[this.styleProp] = this.currentSize;
|
|
||||||
delete this.currentSize;
|
|
||||||
delete this.dragCollapse;
|
|
||||||
this.collapsed = false;
|
|
||||||
},
|
|
||||||
trackSize: function () {
|
|
||||||
if (!this.dragCollapse === true) {
|
|
||||||
if (this.type === 'vertical') {
|
|
||||||
this.initial = this.$el.offsetHeight;
|
|
||||||
} else if (this.type === 'horizontal') {
|
|
||||||
this.initial = this.$el.offsetWidth;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
getPosition: function (event) {
|
|
||||||
return this.type === 'horizontal'
|
|
||||||
? event.pageX
|
|
||||||
: event.pageY;
|
|
||||||
},
|
|
||||||
getNewSize: function (event) {
|
|
||||||
let delta = this.startPosition - this.getPosition(event);
|
|
||||||
if (this.handle === "before") {
|
if (this.handle === "before") {
|
||||||
return `${this.initial + delta}px`;
|
return `${this.initial + delta}px`;
|
||||||
}
|
}
|
||||||
@ -142,33 +128,88 @@ export default {
|
|||||||
return `${this.initial - delta}px`;
|
return `${this.initial - delta}px`;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
updatePosition: function (event) {
|
getSavedPosition() {
|
||||||
let size = this.getNewSize(event);
|
if (!this.localStorageKey) {
|
||||||
let intSize = parseInt(size.substr(0, size.length - 2), 10);
|
return null;
|
||||||
if (intSize < COLLAPSE_THRESHOLD_PX && this.isCollapsable === true) {
|
}
|
||||||
this.dragCollapse = true;
|
|
||||||
this.end();
|
const savedPositionsString = localStorage.getItem(LOCAL_STORAGE_KEY__PANE_POSITIONS);
|
||||||
this.toggleCollapse();
|
const savedPositions = savedPositionsString ? JSON.parse(savedPositionsString) : {};
|
||||||
} else {
|
|
||||||
this.$el.style[this.styleProp] = size;
|
return savedPositions[this.localStorageKey];
|
||||||
|
},
|
||||||
|
getPosition(event) {
|
||||||
|
return this.type === 'horizontal'
|
||||||
|
? event.pageX
|
||||||
|
: event.pageY;
|
||||||
|
},
|
||||||
|
handleCollapse() {
|
||||||
|
this.currentSize = (this.dragCollapse === true) ? this.initial : this.$el.style[this.styleProp];
|
||||||
|
this.$el.style[this.styleProp] = '';
|
||||||
|
this.collapsed = true;
|
||||||
|
},
|
||||||
|
handleExpand() {
|
||||||
|
this.$el.style[this.styleProp] = this.currentSize;
|
||||||
|
delete this.currentSize;
|
||||||
|
delete this.dragCollapse;
|
||||||
|
this.collapsed = false;
|
||||||
|
},
|
||||||
|
handleHideUrl() {
|
||||||
|
const hideParam = this.openmct.router.getSearchParam(this.hideParam);
|
||||||
|
|
||||||
|
if (hideParam === 'true') {
|
||||||
|
this.handleCollapse();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
start: function (event) {
|
removeHideParam(target) {
|
||||||
event.preventDefault(); // stop from firing drag event
|
this.openmct.router.deleteSearchParam(target);
|
||||||
|
},
|
||||||
|
setSavedPosition(panePosition) {
|
||||||
|
const panePositionsString = localStorage.getItem(LOCAL_STORAGE_KEY__PANE_POSITIONS);
|
||||||
|
const panePositions = panePositionsString ? JSON.parse(panePositionsString) : {};
|
||||||
|
panePositions[this.localStorageKey] = panePosition;
|
||||||
|
localStorage.setItem(LOCAL_STORAGE_KEY__PANE_POSITIONS, JSON.stringify(panePositions));
|
||||||
|
},
|
||||||
|
startResizing(event) {
|
||||||
this.startPosition = this.getPosition(event);
|
this.startPosition = this.getPosition(event);
|
||||||
document.body.addEventListener('mousemove', this.updatePosition);
|
document.body.addEventListener('mousemove', this.updatePosition);
|
||||||
document.body.addEventListener('mouseup', this.end);
|
document.body.addEventListener('mouseup', this.endResizing);
|
||||||
this.resizing = true;
|
this.resizing = true;
|
||||||
this.$emit('start-resizing');
|
this.$emit('start-resizing');
|
||||||
this.trackSize();
|
this.trackSize();
|
||||||
},
|
},
|
||||||
end: function (event) {
|
toggleCollapse(_event) {
|
||||||
document.body.removeEventListener('mousemove', this.updatePosition);
|
if (this.collapsed) {
|
||||||
document.body.removeEventListener('mouseup', this.end);
|
this.handleExpand();
|
||||||
this.resizing = false;
|
this.removeHideParam(this.hideParam);
|
||||||
this.$emit('end-resizing');
|
} else {
|
||||||
this.trackSize();
|
this.handleCollapse();
|
||||||
|
this.addHideParam(this.hideParam);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
trackSize() {
|
||||||
|
if (!this.dragCollapse) {
|
||||||
|
if (this.type === 'vertical') {
|
||||||
|
this.initial = this.$el.offsetHeight;
|
||||||
|
} else if (this.type === 'horizontal') {
|
||||||
|
this.initial = this.$el.offsetWidth;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.persistPosition) {
|
||||||
|
this.setSavedPosition(`${this.initial}px`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
updatePosition(event) {
|
||||||
|
const size = this.getNewSize(event);
|
||||||
|
const intSize = parseInt(size.substr(0, size.length - 2), 10);
|
||||||
|
if (intSize < COLLAPSE_THRESHOLD_PX && this.isCollapsable === true) {
|
||||||
|
this.dragCollapse = true;
|
||||||
|
this.endResizing();
|
||||||
|
this.toggleCollapse();
|
||||||
|
} else {
|
||||||
|
this.$el.style[this.styleProp] = size;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
121
src/ui/layout/recent-objects.scss
Normal file
121
src/ui/layout/recent-objects.scss
Normal file
@ -0,0 +1,121 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* Open MCT, Copyright (c) 2014-2022, United States Government
|
||||||
|
* as represented by the Administrator of the National Aeronautics and Space
|
||||||
|
* Administration. All rights reserved.
|
||||||
|
*
|
||||||
|
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*
|
||||||
|
* Open MCT includes source code licensed under additional open source
|
||||||
|
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||||
|
* this source code distribution or the Licensing information page available
|
||||||
|
* at runtime from the About dialog for additional information.
|
||||||
|
*****************************************************************************/
|
||||||
|
|
||||||
|
.c-recentobjects-listitem {
|
||||||
|
display: flex;
|
||||||
|
padding: $interiorMargin $interiorMarginSm;
|
||||||
|
align-items: flex-start;
|
||||||
|
|
||||||
|
> * + * {
|
||||||
|
margin-left: $interiorMargin;
|
||||||
|
}
|
||||||
|
|
||||||
|
+ .c-recentobjects-listitem {
|
||||||
|
border-top: 1px solid $colorInteriorBorder;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.is-alias {
|
||||||
|
// Object is an alias to an original.
|
||||||
|
[class~='recent-object-icon'] {
|
||||||
|
@include isAlias();
|
||||||
|
&:after {
|
||||||
|
bottom: 20%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__object-path {
|
||||||
|
padding: 0 $interiorMarginSm;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__target-button{
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__type-icon,
|
||||||
|
&__more-options-button {
|
||||||
|
flex: 0 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__type-icon {
|
||||||
|
color: $colorItemTreeIcon;
|
||||||
|
font-size: 2.2em;
|
||||||
|
|
||||||
|
// TEMP: uses object-label component, hide label part
|
||||||
|
.c-object-label__name {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__more-options-button {
|
||||||
|
display: none; // TEMP until enabled
|
||||||
|
}
|
||||||
|
|
||||||
|
&__body {
|
||||||
|
flex: 1 1 auto;
|
||||||
|
|
||||||
|
> * + * {
|
||||||
|
margin-top: $interiorMarginSm;
|
||||||
|
}
|
||||||
|
|
||||||
|
.c-location {
|
||||||
|
font-size: 0.9em;
|
||||||
|
opacity: 0.8;
|
||||||
|
|
||||||
|
&__item {
|
||||||
|
|
||||||
|
> * + * {
|
||||||
|
background: blue !important;
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
&__tags {
|
||||||
|
display: flex;
|
||||||
|
|
||||||
|
> * + * {
|
||||||
|
margin-left: $interiorMargin;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__title {
|
||||||
|
border-radius: $basicCr;
|
||||||
|
color: pullForward($colorBodyFg, 30%);
|
||||||
|
cursor: pointer;
|
||||||
|
padding: $interiorMarginSm;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: $colorItemTreeHoverBg;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.c-tag {
|
||||||
|
font-size: 0.9em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.c-recentobjects-listitem:hover .c-recentobjects-listitem__target-button {
|
||||||
|
opacity: 100;
|
||||||
|
}
|
@ -67,7 +67,7 @@
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
import ObjectPath from '../../components/ObjectPath.vue';
|
import ObjectPath from '../../components/ObjectPath.vue';
|
||||||
import objectPathToUrl from '../../../tools/url';
|
import { identifierToString } from '../../../../src/tools/url';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'AnnotationSearchResult',
|
name: 'AnnotationSearchResult',
|
||||||
@ -128,11 +128,7 @@ export default {
|
|||||||
methods: {
|
methods: {
|
||||||
clickedResult() {
|
clickedResult() {
|
||||||
const objectPath = this.domainObject.originalPath;
|
const objectPath = this.domainObject.originalPath;
|
||||||
let resultUrl = objectPathToUrl(this.openmct, objectPath);
|
let resultUrl = identifierToString(this.openmct, objectPath);
|
||||||
// get rid of ROOT if extant
|
|
||||||
if (resultUrl.includes('/ROOT')) {
|
|
||||||
resultUrl = resultUrl.split('/ROOT').join('');
|
|
||||||
}
|
|
||||||
|
|
||||||
this.openmct.router.navigate(resultUrl);
|
this.openmct.router.navigate(resultUrl);
|
||||||
},
|
},
|
||||||
|
@ -104,7 +104,7 @@ export default {
|
|||||||
const originalPathObjects = await this.openmct.objects.getOriginalPath(keyStringForObject);
|
const originalPathObjects = await this.openmct.objects.getOriginalPath(keyStringForObject);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
originalPath: originalPathObjects,
|
objectPath: originalPathObjects,
|
||||||
...domainObject
|
...domainObject
|
||||||
};
|
};
|
||||||
}));
|
}));
|
||||||
@ -126,7 +126,7 @@ export default {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.openmct.objects.isReachable(result?.originalPath);
|
return this.openmct.objects.isReachable(result?.objectPath);
|
||||||
});
|
});
|
||||||
this.objectSearchResults = filterAnnotationsAndValidPaths;
|
this.objectSearchResults = filterAnnotationsAndValidPaths;
|
||||||
this.searchLoading = false;
|
this.searchLoading = false;
|
||||||
|
@ -58,7 +58,7 @@
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
import ObjectPath from '../../components/ObjectPath.vue';
|
import ObjectPath from '../../components/ObjectPath.vue';
|
||||||
import objectPathToUrl from '../../../tools/url';
|
import identifierToString from '../../../tools/url';
|
||||||
import PreviewAction from '../../preview/PreviewAction';
|
import PreviewAction from '../../preview/PreviewAction';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
@ -100,9 +100,10 @@ export default {
|
|||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
this.preview();
|
this.preview();
|
||||||
} else {
|
} else {
|
||||||
const objectPath = this.result.originalPath;
|
const { objectPath } = this.result;
|
||||||
let resultUrl = objectPathToUrl(this.openmct, objectPath);
|
let resultUrl = identifierToString(this.openmct, objectPath);
|
||||||
// get rid of ROOT if extant
|
|
||||||
|
// Remove the vestigial 'ROOT' identifier from url if it exists
|
||||||
if (resultUrl.includes('/ROOT')) {
|
if (resultUrl.includes('/ROOT')) {
|
||||||
resultUrl = resultUrl.split('/ROOT').join('');
|
resultUrl = resultUrl.split('/ROOT').join('');
|
||||||
}
|
}
|
||||||
@ -114,14 +115,14 @@ export default {
|
|||||||
this.$emit('preview-changed', previewState);
|
this.$emit('preview-changed', previewState);
|
||||||
},
|
},
|
||||||
preview() {
|
preview() {
|
||||||
const objectPath = this.result.originalPath;
|
const { objectPath } = this.result;
|
||||||
if (this.previewAction.appliesTo(objectPath)) {
|
if (this.previewAction.appliesTo(objectPath)) {
|
||||||
this.previewAction.invoke(objectPath);
|
this.previewAction.invoke(objectPath);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
dragStart(event) {
|
dragStart(event) {
|
||||||
const navigatedObject = this.openmct.router.path[0];
|
const navigatedObject = this.openmct.router.path[0];
|
||||||
const objectPath = this.result.originalPath;
|
const { objectPath } = this.result;
|
||||||
const serializedPath = JSON.stringify(objectPath);
|
const serializedPath = JSON.stringify(objectPath);
|
||||||
const keyString = this.openmct.objects.makeKeyString(this.result.identifier);
|
const keyString = this.openmct.objects.makeKeyString(this.result.identifier);
|
||||||
if (this.openmct.composition.checkPolicy(navigatedObject, this.result)) {
|
if (this.openmct.composition.checkPolicy(navigatedObject, this.result)) {
|
||||||
|
@ -9,10 +9,12 @@
|
|||||||
class="c-tree__item"
|
class="c-tree__item"
|
||||||
:class="{
|
:class="{
|
||||||
'is-alias': isAlias,
|
'is-alias': isAlias,
|
||||||
'is-navigated-object': shouldHightlight,
|
'is-navigated-object': shouldHighlight,
|
||||||
|
'is-targeted-item': isTargetedItem,
|
||||||
'is-context-clicked': contextClickActive,
|
'is-context-clicked': contextClickActive,
|
||||||
'is-new': isNewItem
|
'is-new': isNewItem
|
||||||
}"
|
}"
|
||||||
|
@animationend="targetedPathAnimationEnd($event)"
|
||||||
@click.capture="itemClick"
|
@click.capture="itemClick"
|
||||||
@contextmenu.capture="handleContextMenu"
|
@contextmenu.capture="handleContextMenu"
|
||||||
>
|
>
|
||||||
@ -58,6 +60,10 @@ export default {
|
|||||||
type: Boolean,
|
type: Boolean,
|
||||||
required: true
|
required: true
|
||||||
},
|
},
|
||||||
|
targetedPath: {
|
||||||
|
type: String,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
selectedItem: {
|
selectedItem: {
|
||||||
type: Object,
|
type: Object,
|
||||||
required: true
|
required: true
|
||||||
@ -122,6 +128,9 @@ export default {
|
|||||||
isSelectedItem() {
|
isSelectedItem() {
|
||||||
return this.selectedItem.objectPath === this.node.objectPath;
|
return this.selectedItem.objectPath === this.node.objectPath;
|
||||||
},
|
},
|
||||||
|
isTargetedItem() {
|
||||||
|
return this.targetedPath === this.navigationPath;
|
||||||
|
},
|
||||||
isNewItem() {
|
isNewItem() {
|
||||||
return this.isNew;
|
return this.isNew;
|
||||||
},
|
},
|
||||||
@ -131,7 +140,7 @@ export default {
|
|||||||
isOpen() {
|
isOpen() {
|
||||||
return this.openItems.includes(this.navigationPath);
|
return this.openItems.includes(this.navigationPath);
|
||||||
},
|
},
|
||||||
shouldHightlight() {
|
shouldHighlight() {
|
||||||
if (this.isSelectorTree) {
|
if (this.isSelectorTree) {
|
||||||
return this.isSelectedItem;
|
return this.isSelectedItem;
|
||||||
} else {
|
} else {
|
||||||
@ -164,6 +173,10 @@ export default {
|
|||||||
this.$emit('tree-item-destoyed', this.navigationPath);
|
this.$emit('tree-item-destoyed', this.navigationPath);
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
targetedPathAnimationEnd($event) {
|
||||||
|
$event.target.classList.remove('is-targeted-item');
|
||||||
|
this.$emit('targeted-path-animation-end');
|
||||||
|
},
|
||||||
itemAction() {
|
itemAction() {
|
||||||
this.$emit('tree-item-action', this.isOpen || this.isLoading ? 'close' : 'open');
|
this.$emit('tree-item-action', this.isOpen || this.isLoading ? 'close' : 'open');
|
||||||
},
|
},
|
||||||
|
@ -3,7 +3,7 @@ import objectPathToUrl from '../../tools/url';
|
|||||||
export default {
|
export default {
|
||||||
inject: ['openmct'],
|
inject: ['openmct'],
|
||||||
props: {
|
props: {
|
||||||
'objectPath': {
|
objectPath: {
|
||||||
type: Array,
|
type: Array,
|
||||||
default() {
|
default() {
|
||||||
return [];
|
return [];
|
||||||
@ -20,7 +20,7 @@ export default {
|
|||||||
return '#' + this.navigateToPath;
|
return '#' + this.navigateToPath;
|
||||||
}
|
}
|
||||||
|
|
||||||
let url = objectPathToUrl(this.openmct, this.objectPath);
|
const url = objectPathToUrl(this.openmct, this.objectPath);
|
||||||
|
|
||||||
return url;
|
return url;
|
||||||
}
|
}
|
||||||
|
@ -130,11 +130,10 @@ class ApplicationRouter extends EventEmitter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Navigate to given hash and update current location object and notify listeners about location change
|
* Navigate to given hash, update current location object, and notify listeners about location change
|
||||||
*
|
*
|
||||||
* @param {string} paramName name of searchParam to get from current url searchParams
|
* @param {string} hash The URL hash to navigate to in the form of "#/browse/mine/{keyString}/{keyString}".
|
||||||
*
|
* Should not include any params.
|
||||||
* @returns {string} value of paramName from current url searchParams
|
|
||||||
*/
|
*/
|
||||||
navigate(hash) {
|
navigate(hash) {
|
||||||
this.handleLocationChange(hash.substring(1));
|
this.handleLocationChange(hash.substring(1));
|
||||||
@ -227,7 +226,7 @@ class ApplicationRouter extends EventEmitter {
|
|||||||
|
|
||||||
this.started = true;
|
this.started = true;
|
||||||
|
|
||||||
this.locationBar.onChange(p => this.hashChaged(p));
|
this.locationBar.onChange(p => this.hashChanged(p));
|
||||||
this.locationBar.start({
|
this.locationBar.start({
|
||||||
root: location.pathname
|
root: location.pathname
|
||||||
});
|
});
|
||||||
@ -390,7 +389,7 @@ class ApplicationRouter extends EventEmitter {
|
|||||||
*
|
*
|
||||||
* @param {string} hash new hash for url
|
* @param {string} hash new hash for url
|
||||||
*/
|
*/
|
||||||
hashChaged(hash) {
|
hashChanged(hash) {
|
||||||
this.emit('change:hash', hash);
|
this.emit('change:hash', hash);
|
||||||
this.handleLocationChange(hash);
|
this.handleLocationChange(hash);
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user