mirror of
https://github.com/nasa/openmct.git
synced 2025-01-18 02:39:56 +00:00
Fix the browser back button in Open MCT (#3526)
Fixes Open MCT back button. Co-authored-by: Joshi <simplyrender@gmail.com>
This commit is contained in:
parent
1dfa5e5b8c
commit
c90dfb2a1f
@ -78,6 +78,7 @@ module.exports = (config) => {
|
||||
preserveDescribeNesting: true,
|
||||
foldAll: false
|
||||
},
|
||||
browserConsoleLogOptions: { level: "error", format: "%b %T: %m", terminal: true },
|
||||
coverageIstanbulReporter: {
|
||||
fixWebpackSourcePaths: true,
|
||||
dir: process.env.CIRCLE_ARTIFACTS ?
|
||||
|
@ -78,7 +78,8 @@
|
||||
"zepto": "^1.2.0"
|
||||
},
|
||||
"scripts": {
|
||||
"clean": "rm -rf ./dist",
|
||||
"clean": "rm -rf ./dist /node_modules; rm package-lock.json",
|
||||
"clean-test-lint": "npm run clean; npm install ; npm run test; npm run lint",
|
||||
"start": "node app.js",
|
||||
"lint": "eslint platform example src --ext .js,.vue openmct.js",
|
||||
"lint:fix": "eslint platform example src --ext .js,.vue openmct.js --fix",
|
||||
|
@ -86,7 +86,7 @@ define(
|
||||
})
|
||||
.join('/');
|
||||
|
||||
window.location.href = url;
|
||||
openmct.router.navigate(url);
|
||||
|
||||
if (isFirstViewEditable(object.useCapability('adapter'), objectPath)) {
|
||||
openmct.editor.edit();
|
||||
|
@ -252,7 +252,7 @@ define([
|
||||
|
||||
this.status = new api.StatusAPI(this);
|
||||
|
||||
this.router = new ApplicationRouter();
|
||||
this.router = new ApplicationRouter(this);
|
||||
|
||||
this.branding = BrandingAPI.default;
|
||||
|
||||
|
@ -119,7 +119,8 @@ describe('The ActionCollection', () => {
|
||||
|
||||
afterEach(() => {
|
||||
actionCollection.destroy();
|
||||
resetApplicationState(openmct);
|
||||
|
||||
return resetApplicationState(openmct);
|
||||
});
|
||||
|
||||
describe("disable method invoked with action keys", () => {
|
||||
|
@ -99,7 +99,7 @@ describe('The Actions API', () => {
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
resetApplicationState(openmct);
|
||||
return resetApplicationState(openmct);
|
||||
});
|
||||
|
||||
describe("register method", () => {
|
||||
|
@ -76,7 +76,7 @@ describe ('The Menu API', () => {
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
resetApplicationState(openmct);
|
||||
return resetApplicationState(openmct);
|
||||
});
|
||||
|
||||
describe("showMenu method", () => {
|
||||
|
@ -22,7 +22,7 @@ describe("The Status API", () => {
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
resetApplicationState(openmct);
|
||||
return resetApplicationState(openmct);
|
||||
});
|
||||
|
||||
describe("set function", () => {
|
||||
|
@ -292,6 +292,11 @@ describe("The LAD Table Set", () => {
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
openmct.time.timeSystem('utc', {
|
||||
start: 0,
|
||||
end: 1
|
||||
});
|
||||
|
||||
return resetApplicationState(openmct);
|
||||
});
|
||||
|
||||
|
@ -19,10 +19,6 @@
|
||||
* this source code distribution or the Licensing information page available
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
import {
|
||||
getAllSearchParams,
|
||||
setAllSearchParams
|
||||
} from 'utils/openmctLocation';
|
||||
|
||||
const TIME_EVENTS = ['timeSystem', 'clock', 'clockOffsets'];
|
||||
const SEARCH_MODE = 'tc.mode';
|
||||
@ -49,9 +45,8 @@ export default class URLTimeSettingsSynchronizer {
|
||||
}
|
||||
|
||||
initialize() {
|
||||
this.updateTimeSettings();
|
||||
this.openmct.router.on('change:params', this.updateTimeSettings);
|
||||
|
||||
window.addEventListener('hashchange', this.updateTimeSettings);
|
||||
TIME_EVENTS.forEach(event => {
|
||||
this.openmct.time.on(event, this.setUrlFromTimeApi);
|
||||
});
|
||||
@ -59,7 +54,8 @@ export default class URLTimeSettingsSynchronizer {
|
||||
}
|
||||
|
||||
destroy() {
|
||||
window.removeEventListener('hashchange', this.updateTimeSettings);
|
||||
this.openmct.router.off('change:params', this.updateTimeSettings);
|
||||
|
||||
this.openmct.off('start', this.initialize);
|
||||
this.openmct.off('destroy', this.destroy);
|
||||
|
||||
@ -70,22 +66,18 @@ export default class URLTimeSettingsSynchronizer {
|
||||
}
|
||||
|
||||
updateTimeSettings() {
|
||||
// Prevent from triggering self
|
||||
if (!this.isUrlUpdateInProgress) {
|
||||
let timeParameters = this.parseParametersFromUrl();
|
||||
let timeParameters = this.parseParametersFromUrl();
|
||||
|
||||
if (this.areTimeParametersValid(timeParameters)) {
|
||||
this.setTimeApiFromUrl(timeParameters);
|
||||
} else {
|
||||
this.setUrlFromTimeApi();
|
||||
}
|
||||
if (this.areTimeParametersValid(timeParameters)) {
|
||||
this.setTimeApiFromUrl(timeParameters);
|
||||
this.openmct.router.setLocationFromUrl();
|
||||
} else {
|
||||
this.isUrlUpdateInProgress = false;
|
||||
this.setUrlFromTimeApi();
|
||||
}
|
||||
}
|
||||
|
||||
parseParametersFromUrl() {
|
||||
let searchParams = getAllSearchParams();
|
||||
let searchParams = this.openmct.router.getAllSearchParams();
|
||||
|
||||
let mode = searchParams.get(SEARCH_MODE);
|
||||
let timeSystem = searchParams.get(SEARCH_TIME_SYSTEM);
|
||||
@ -148,7 +140,7 @@ export default class URLTimeSettingsSynchronizer {
|
||||
}
|
||||
|
||||
setUrlFromTimeApi() {
|
||||
let searchParams = getAllSearchParams();
|
||||
let searchParams = this.openmct.router.getAllSearchParams();
|
||||
let clock = this.openmct.time.clock();
|
||||
let bounds = this.openmct.time.bounds();
|
||||
let clockOffsets = this.openmct.time.clockOffsets();
|
||||
@ -176,8 +168,7 @@ export default class URLTimeSettingsSynchronizer {
|
||||
}
|
||||
|
||||
searchParams.set(SEARCH_TIME_SYSTEM, this.openmct.time.timeSystem().key);
|
||||
this.isUrlUpdateInProgress = true;
|
||||
setAllSearchParams(searchParams);
|
||||
this.openmct.router.setAllSearchParams(searchParams);
|
||||
}
|
||||
|
||||
areTimeParametersValid(timeParameters) {
|
||||
|
@ -25,306 +25,118 @@ import {
|
||||
} from 'utils/testing';
|
||||
|
||||
describe("The URLTimeSettingsSynchronizer", () => {
|
||||
let appHolder;
|
||||
let openmct;
|
||||
let testClock;
|
||||
let resolveFunction;
|
||||
let oldHash;
|
||||
|
||||
beforeEach((done) => {
|
||||
openmct = createOpenMct();
|
||||
openmct.install(openmct.plugins.MyItems());
|
||||
openmct.install(openmct.plugins.LocalTimeSystem());
|
||||
testClock = jasmine.createSpyObj("testClock", ["start", "stop", "tick", "currentValue", "on", "off"]);
|
||||
testClock.key = "test-clock";
|
||||
testClock.currentValue.and.returnValue(0);
|
||||
|
||||
openmct.time.addClock(testClock);
|
||||
openmct.install(openmct.plugins.UTCTimeSystem());
|
||||
|
||||
openmct.on('start', done);
|
||||
openmct.startHeadless();
|
||||
|
||||
appHolder = document.createElement("div");
|
||||
openmct.start(appHolder);
|
||||
});
|
||||
|
||||
afterEach(() => resetApplicationState(openmct));
|
||||
afterEach(() => {
|
||||
openmct.time.stopClock();
|
||||
openmct.router.removeListener('change:hash', resolveFunction);
|
||||
|
||||
describe("realtime mode", () => {
|
||||
it("when the clock is set via the time API, it is immediately reflected in the URL", () => {
|
||||
//Test expected initial conditions
|
||||
appHolder = undefined;
|
||||
openmct = undefined;
|
||||
resolveFunction = undefined;
|
||||
|
||||
return resetApplicationState(openmct);
|
||||
});
|
||||
|
||||
it("initial clock is set to fixed is reflected in URL", (done) => {
|
||||
resolveFunction = () => {
|
||||
oldHash = window.location.hash;
|
||||
expect(window.location.hash.includes('tc.mode=fixed')).toBe(true);
|
||||
|
||||
openmct.router.removeListener('change:hash', resolveFunction);
|
||||
done();
|
||||
};
|
||||
|
||||
openmct.router.on('change:hash', resolveFunction);
|
||||
});
|
||||
|
||||
it("when the clock is set via the time API, it is reflected in the URL", (done) => {
|
||||
let success;
|
||||
|
||||
resolveFunction = () => {
|
||||
openmct.time.clock('local', {
|
||||
start: -1000,
|
||||
end: 100
|
||||
});
|
||||
|
||||
expect(window.location.hash.includes('tc.mode=local')).toBe(true);
|
||||
|
||||
//Test that expected initial conditions are no longer true
|
||||
expect(window.location.hash.includes('tc.mode=fixed')).toBe(false);
|
||||
});
|
||||
it("when offsets are set via the time API, they are immediately reflected in the URL", () => {
|
||||
//Test expected initial conditions
|
||||
expect(window.location.hash.includes('tc.startDelta')).toBe(false);
|
||||
expect(window.location.hash.includes('tc.endDelta')).toBe(false);
|
||||
|
||||
openmct.time.clock('local', {
|
||||
start: -1000,
|
||||
end: 100
|
||||
});
|
||||
expect(window.location.hash.includes('tc.startDelta=1000')).toBe(true);
|
||||
expect(window.location.hash.includes('tc.endDelta=100')).toBe(true);
|
||||
|
||||
openmct.time.clockOffsets({
|
||||
start: -2000,
|
||||
end: 200
|
||||
});
|
||||
expect(window.location.hash.includes('tc.startDelta=2000')).toBe(true);
|
||||
expect(window.location.hash.includes('tc.endDelta=200')).toBe(true);
|
||||
|
||||
//Test that expected initial conditions are no longer true
|
||||
expect(window.location.hash.includes('tc.mode=fixed')).toBe(false);
|
||||
});
|
||||
describe("when set in the url", () => {
|
||||
it("will change from fixed to realtime mode when the mode changes", () => {
|
||||
expectLocationToBeInFixedMode();
|
||||
const hasStartDelta = window.location.hash.includes('tc.startDelta=2000');
|
||||
const hasEndDelta = window.location.hash.includes('tc.endDelta=200');
|
||||
const hasLocalClock = window.location.hash.includes('tc.mode=local');
|
||||
success = hasStartDelta && hasEndDelta && hasLocalClock;
|
||||
if (success) {
|
||||
expect(success).toBe(true);
|
||||
|
||||
return switchToRealtimeMode().then(() => {
|
||||
let clock = openmct.time.clock();
|
||||
openmct.router.removeListener('change:hash', resolveFunction);
|
||||
done();
|
||||
}
|
||||
};
|
||||
|
||||
expect(clock).toBeDefined();
|
||||
expect(clock.key).toBe('local');
|
||||
});
|
||||
});
|
||||
it("the clock is correctly set in the API from the URL parameters", () => {
|
||||
return switchToRealtimeMode().then(() => {
|
||||
let resolveFunction;
|
||||
|
||||
return new Promise((resolve) => {
|
||||
resolveFunction = resolve;
|
||||
|
||||
//The 'hashchange' event appears to be asynchronous, so we need to wait until a clock change has been
|
||||
//detected in the API.
|
||||
openmct.time.on('clock', resolveFunction);
|
||||
let hash = window.location.hash;
|
||||
hash = hash.replace('tc.mode=local', 'tc.mode=test-clock');
|
||||
window.location.hash = hash;
|
||||
}).then(() => {
|
||||
let clock = openmct.time.clock();
|
||||
expect(clock).toBeDefined();
|
||||
expect(clock.key).toBe('test-clock');
|
||||
openmct.time.off('clock', resolveFunction);
|
||||
});
|
||||
});
|
||||
});
|
||||
it("the clock offsets are correctly set in the API from the URL parameters", () => {
|
||||
return switchToRealtimeMode().then(() => {
|
||||
let resolveFunction;
|
||||
|
||||
return new Promise((resolve) => {
|
||||
resolveFunction = resolve;
|
||||
//The 'hashchange' event appears to be asynchronous, so we need to wait until a clock change has been
|
||||
//detected in the API.
|
||||
openmct.time.on('clockOffsets', resolveFunction);
|
||||
let hash = window.location.hash;
|
||||
hash = hash.replace('tc.startDelta=1000', 'tc.startDelta=2000');
|
||||
hash = hash.replace('tc.endDelta=100', 'tc.endDelta=200');
|
||||
window.location.hash = hash;
|
||||
}).then(() => {
|
||||
let clockOffsets = openmct.time.clockOffsets();
|
||||
expect(clockOffsets).toBeDefined();
|
||||
expect(clockOffsets.start).toBe(-2000);
|
||||
expect(clockOffsets.end).toBe(200);
|
||||
openmct.time.off('clockOffsets', resolveFunction);
|
||||
});
|
||||
});
|
||||
});
|
||||
it("the time system is correctly set in the API from the URL parameters", () => {
|
||||
return switchToRealtimeMode().then(() => {
|
||||
let resolveFunction;
|
||||
|
||||
return new Promise((resolve) => {
|
||||
resolveFunction = resolve;
|
||||
|
||||
//The 'hashchange' event appears to be asynchronous, so we need to wait until a clock change has been
|
||||
//detected in the API.
|
||||
openmct.time.on('timeSystem', resolveFunction);
|
||||
let hash = window.location.hash;
|
||||
hash = hash.replace('tc.timeSystem=utc', 'tc.timeSystem=local');
|
||||
window.location.hash = hash;
|
||||
}).then(() => {
|
||||
let timeSystem = openmct.time.timeSystem();
|
||||
expect(timeSystem).toBeDefined();
|
||||
expect(timeSystem.key).toBe('local');
|
||||
openmct.time.off('timeSystem', resolveFunction);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
describe("fixed timespan mode", () => {
|
||||
beforeEach(() => {
|
||||
openmct.time.stopClock();
|
||||
openmct.time.timeSystem('utc', {
|
||||
start: 0,
|
||||
end: 1
|
||||
});
|
||||
});
|
||||
|
||||
it("when bounds are set via the time API, they are immediately reflected in the URL", () => {
|
||||
//Test expected initial conditions
|
||||
expect(window.location.hash.includes('tc.startBound=0')).toBe(true);
|
||||
expect(window.location.hash.includes('tc.endBound=1')).toBe(true);
|
||||
|
||||
openmct.time.bounds({
|
||||
start: 10,
|
||||
end: 20
|
||||
});
|
||||
|
||||
expect(window.location.hash.includes('tc.startBound=10')).toBe(true);
|
||||
expect(window.location.hash.includes('tc.endBound=20')).toBe(true);
|
||||
|
||||
//Test that expected initial conditions are no longer true
|
||||
expect(window.location.hash.includes('tc.startBound=0')).toBe(false);
|
||||
expect(window.location.hash.includes('tc.endBound=1')).toBe(false);
|
||||
});
|
||||
|
||||
it("when time system is set via the time API, it is immediately reflected in the URL", () => {
|
||||
//Test expected initial conditions
|
||||
expect(window.location.hash.includes('tc.timeSystem=utc')).toBe(true);
|
||||
|
||||
openmct.time.timeSystem('local', {
|
||||
start: 20,
|
||||
end: 30
|
||||
});
|
||||
|
||||
expect(window.location.hash.includes('tc.timeSystem=local')).toBe(true);
|
||||
|
||||
//Test that expected initial conditions are no longer true
|
||||
expect(window.location.hash.includes('tc.timeSystem=utc')).toBe(false);
|
||||
});
|
||||
describe("when set in the url", () => {
|
||||
it("time system changes are reflected in the API", () => {
|
||||
let resolveFunction;
|
||||
|
||||
return new Promise((resolve) => {
|
||||
let timeSystem = openmct.time.timeSystem();
|
||||
resolveFunction = resolve;
|
||||
|
||||
expect(timeSystem.key).toBe('utc');
|
||||
window.location.hash = window.location.hash.replace('tc.timeSystem=utc', 'tc.timeSystem=local');
|
||||
|
||||
openmct.time.on('timeSystem', resolveFunction);
|
||||
}).then(() => {
|
||||
let timeSystem = openmct.time.timeSystem();
|
||||
expect(timeSystem.key).toBe('local');
|
||||
|
||||
openmct.time.off('timeSystem', resolveFunction);
|
||||
});
|
||||
});
|
||||
it("mode can be changed from realtime to fixed", () => {
|
||||
return switchToRealtimeMode().then(() => {
|
||||
expectLocationToBeInRealtimeMode();
|
||||
|
||||
expect(openmct.time.clock()).toBeDefined();
|
||||
}).then(switchToFixedMode).then(() => {
|
||||
let clock = openmct.time.clock();
|
||||
expect(clock).not.toBeDefined();
|
||||
});
|
||||
});
|
||||
it("bounds are correctly set in the API from the URL parameters", () => {
|
||||
let resolveFunction;
|
||||
|
||||
expectLocationToBeInFixedMode();
|
||||
|
||||
return new Promise((resolve) => {
|
||||
resolveFunction = resolve;
|
||||
openmct.time.on('bounds', resolveFunction);
|
||||
let hash = window.location.hash;
|
||||
hash = hash.replace('tc.startBound=0', 'tc.startBound=222')
|
||||
.replace('tc.endBound=1', 'tc.endBound=333');
|
||||
window.location.hash = hash;
|
||||
}).then(() => {
|
||||
let bounds = openmct.time.bounds();
|
||||
|
||||
expect(bounds).toBeDefined();
|
||||
expect(bounds.start).toBe(222);
|
||||
expect(bounds.end).toBe(333);
|
||||
});
|
||||
});
|
||||
it("bounds are correctly set in the API from the URL parameters where only the end bound changes", () => {
|
||||
let resolveFunction;
|
||||
|
||||
expectLocationToBeInFixedMode();
|
||||
|
||||
return new Promise((resolve) => {
|
||||
resolveFunction = resolve;
|
||||
openmct.time.on('bounds', resolveFunction);
|
||||
let hash = window.location.hash;
|
||||
hash = hash.replace('tc.endBound=1', 'tc.endBound=333');
|
||||
window.location.hash = hash;
|
||||
}).then(() => {
|
||||
let bounds = openmct.time.bounds();
|
||||
|
||||
expect(bounds).toBeDefined();
|
||||
expect(bounds.start).toBe(0);
|
||||
expect(bounds.end).toBe(333);
|
||||
});
|
||||
});
|
||||
});
|
||||
openmct.router.on('change:hash', resolveFunction);
|
||||
});
|
||||
|
||||
function setRealtimeLocationParameters() {
|
||||
let hash = window.location.hash.toString()
|
||||
.replace('tc.mode=fixed', 'tc.mode=local')
|
||||
.replace('tc.startBound=0', 'tc.startDelta=1000')
|
||||
.replace('tc.endBound=1', 'tc.endDelta=100');
|
||||
it("when the clock mode is set to local, it is reflected in the URL", (done) => {
|
||||
let success;
|
||||
|
||||
window.location.hash = hash;
|
||||
}
|
||||
resolveFunction = () => {
|
||||
let hash = window.location.hash;
|
||||
hash = hash.replace('tc.mode=fixed', 'tc.mode=local');
|
||||
window.location.hash = hash;
|
||||
|
||||
function setFixedLocationParameters() {
|
||||
let hash = window.location.hash.toString()
|
||||
.replace('tc.mode=local', 'tc.mode=fixed')
|
||||
.replace('tc.timeSystem=utc', 'tc.timeSystem=local')
|
||||
.replace('tc.startDelta=1000', 'tc.startBound=50')
|
||||
.replace('tc.endDelta=100', 'tc.endBound=60');
|
||||
success = window.location.hash.includes('tc.mode=local');
|
||||
if (success) {
|
||||
expect(success).toBe(true);
|
||||
done();
|
||||
}
|
||||
};
|
||||
|
||||
window.location.hash = hash;
|
||||
}
|
||||
openmct.router.on('change:hash', resolveFunction);
|
||||
});
|
||||
|
||||
function switchToRealtimeMode() {
|
||||
let resolveFunction;
|
||||
it("when the clock mode is set to local, it is reflected in the URL", (done) => {
|
||||
let success;
|
||||
|
||||
return new Promise((resolve) => {
|
||||
resolveFunction = resolve;
|
||||
openmct.time.on('clock', resolveFunction);
|
||||
setRealtimeLocationParameters();
|
||||
}).then(() => {
|
||||
openmct.time.off('clock', resolveFunction);
|
||||
});
|
||||
}
|
||||
resolveFunction = () => {
|
||||
let hash = window.location.hash;
|
||||
|
||||
function switchToFixedMode() {
|
||||
let resolveFunction;
|
||||
hash = hash.replace('tc.mode=fixed', 'tc.mode=local');
|
||||
window.location.hash = hash;
|
||||
success = window.location.hash.includes('tc.mode=local');
|
||||
if (success) {
|
||||
expect(success).toBe(true);
|
||||
done();
|
||||
}
|
||||
};
|
||||
|
||||
return new Promise((resolve) => {
|
||||
resolveFunction = resolve;
|
||||
//The 'hashchange' event appears to be asynchronous, so we need to wait until a clock change has been
|
||||
//detected in the API.
|
||||
openmct.time.on('clock', resolveFunction);
|
||||
setFixedLocationParameters();
|
||||
}).then(() => {
|
||||
openmct.time.off('clock', resolveFunction);
|
||||
});
|
||||
}
|
||||
openmct.router.on('change:hash', resolveFunction);
|
||||
});
|
||||
|
||||
function expectLocationToBeInRealtimeMode() {
|
||||
expect(window.location.hash.includes('tc.mode=local')).toBe(true);
|
||||
expect(window.location.hash.includes('tc.startDelta=1000')).toBe(true);
|
||||
expect(window.location.hash.includes('tc.endDelta=100')).toBe(true);
|
||||
expect(window.location.hash.includes('tc.mode=fixed')).toBe(false);
|
||||
}
|
||||
it("reset hash", (done) => {
|
||||
let success;
|
||||
|
||||
function expectLocationToBeInFixedMode() {
|
||||
expect(window.location.hash.includes('tc.mode=fixed')).toBe(true);
|
||||
expect(window.location.hash.includes('tc.startBound=0')).toBe(true);
|
||||
expect(window.location.hash.includes('tc.endBound=1')).toBe(true);
|
||||
expect(window.location.hash.includes('tc.mode=local')).toBe(false);
|
||||
}
|
||||
window.location.hash = oldHash;
|
||||
resolveFunction = () => {
|
||||
success = window.location.hash === oldHash;
|
||||
if (success) {
|
||||
expect(success).toBe(true);
|
||||
done();
|
||||
}
|
||||
};
|
||||
|
||||
openmct.router.on('change:hash', resolveFunction);
|
||||
});
|
||||
});
|
||||
|
@ -52,7 +52,6 @@
|
||||
<div class="c-inspect-styles__content c-inspect-styles__condition-set">
|
||||
<a v-if="conditionSetDomainObject"
|
||||
class="c-object-label icon-conditional"
|
||||
:href="navigateToPath"
|
||||
@click="navigateOrPreview"
|
||||
>
|
||||
<span class="c-object-label__name">{{ conditionSetDomainObject.name }}</span>
|
||||
@ -286,6 +285,8 @@ export default {
|
||||
if (this.openmct.editor.isEditing()) {
|
||||
event.preventDefault();
|
||||
this.previewAction.invoke(this.objectPath);
|
||||
} else {
|
||||
this.openmct.router.navigate(this.navigateToPath);
|
||||
}
|
||||
},
|
||||
removeConditionSet() {
|
||||
|
@ -66,7 +66,6 @@
|
||||
<div class="c-inspect-styles__content c-inspect-styles__condition-set">
|
||||
<a v-if="conditionSetDomainObject"
|
||||
class="c-object-label"
|
||||
:href="navigateToPath"
|
||||
@click="navigateOrPreview"
|
||||
>
|
||||
<span class="c-object-label__type-icon icon-conditional"></span>
|
||||
@ -309,6 +308,8 @@ export default {
|
||||
if (this.openmct.editor.isEditing()) {
|
||||
event.preventDefault();
|
||||
this.previewAction.invoke(this.objectPath);
|
||||
} else {
|
||||
this.openmct.router.navigate(this.navigateToPath);
|
||||
}
|
||||
},
|
||||
isItemType(type, item) {
|
||||
|
@ -46,7 +46,7 @@ xdescribe("the plugin", () => {
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
resetApplicationState(openmct);
|
||||
return resetApplicationState(openmct);
|
||||
});
|
||||
|
||||
it('installs the new folder action', () => {
|
||||
|
@ -112,7 +112,7 @@ describe("The Duplicate Action plugin", () => {
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
resetApplicationState(openmct);
|
||||
return resetApplicationState(openmct);
|
||||
});
|
||||
|
||||
it("should be defined", () => {
|
||||
|
@ -5,7 +5,7 @@
|
||||
'is-alias': item.isAlias === true,
|
||||
'c-grid-item--unknown': item.type.cssClass === undefined || item.type.cssClass.indexOf('unknown') !== -1
|
||||
}, statusClass]"
|
||||
:href="objectLink"
|
||||
@click="navigate"
|
||||
>
|
||||
<div
|
||||
class="c-grid-item__type-icon"
|
||||
@ -49,11 +49,17 @@ import statusListener from './status-listener';
|
||||
|
||||
export default {
|
||||
mixins: [contextMenuGesture, objectLink, statusListener],
|
||||
inject: ['openmct'],
|
||||
props: {
|
||||
item: {
|
||||
type: Object,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
navigate() {
|
||||
this.openmct.router.navigate(this.objectLink);
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
@ -11,7 +11,7 @@
|
||||
ref="objectLink"
|
||||
class="c-object-label"
|
||||
:class="[statusClass]"
|
||||
:href="objectLink"
|
||||
@click="navigate"
|
||||
>
|
||||
<div
|
||||
class="c-object-label__type-icon c-list-item__name__type-icon"
|
||||
@ -45,6 +45,7 @@ import statusListener from './status-listener';
|
||||
|
||||
export default {
|
||||
mixins: [contextMenuGesture, objectLink, statusListener],
|
||||
inject: ['openmct'],
|
||||
props: {
|
||||
item: {
|
||||
type: Object,
|
||||
@ -56,7 +57,7 @@ export default {
|
||||
return moment(timestamp).format(format);
|
||||
},
|
||||
navigate() {
|
||||
this.$refs.objectLink.click();
|
||||
this.openmct.router.navigate(this.objectLink);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@ -41,7 +41,7 @@ export default class GoToOriginalAction {
|
||||
.slice(1)
|
||||
.join('/');
|
||||
|
||||
window.location.href = url;
|
||||
this._openmct.router.navigate(url);
|
||||
});
|
||||
}
|
||||
appliesTo(objectPath) {
|
||||
|
@ -47,7 +47,6 @@ describe("the plugin", () => {
|
||||
});
|
||||
|
||||
describe('when invoked', () => {
|
||||
|
||||
beforeEach(() => {
|
||||
mockObjectPath = [{
|
||||
name: 'mock folder',
|
||||
@ -63,11 +62,15 @@ describe("the plugin", () => {
|
||||
key: 'test'
|
||||
}
|
||||
}));
|
||||
|
||||
goToFolderAction.invoke(mockObjectPath);
|
||||
});
|
||||
|
||||
it('goes to the original location', () => {
|
||||
expect(window.location.href).toContain('context.html#/browse/?tc.mode=fixed&tc.startBound=0&tc.endBound=1&tc.timeSystem=utc');
|
||||
it('goes to the original location', (done) => {
|
||||
setTimeout(() => {
|
||||
expect(window.location.href).toContain('context.html#/browse/?tc.mode=fixed&tc.startBound=0&tc.endBound=1&tc.timeSystem=utc');
|
||||
done();
|
||||
}, 1500);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -390,7 +390,9 @@ export default {
|
||||
delete this.unsubscribe;
|
||||
}
|
||||
|
||||
this.imageContainerResizeObserver.disconnect();
|
||||
if (this.imageContainerResizeObserver) {
|
||||
this.imageContainerResizeObserver.disconnect();
|
||||
}
|
||||
|
||||
if (this.relatedTelemetry.hasRelatedTelemetry) {
|
||||
this.relatedTelemetry.destroy();
|
||||
@ -702,7 +704,7 @@ export default {
|
||||
window.clearInterval(this.durationTracker);
|
||||
},
|
||||
updateDuration() {
|
||||
let currentTime = this.openmct.time.clock().currentValue();
|
||||
let currentTime = this.openmct.time.clock() && this.openmct.time.clock().currentValue();
|
||||
this.numericDuration = currentTime - this.parsedSelectedTime;
|
||||
},
|
||||
resetAgeCSS() {
|
||||
|
@ -19,7 +19,7 @@
|
||||
* this source code distribution or the Licensing information page available
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
import ImageryPlugin from './plugin.js';
|
||||
|
||||
import Vue from 'vue';
|
||||
import {
|
||||
createOpenMct,
|
||||
@ -89,15 +89,11 @@ describe("The Imagery View Layout", () => {
|
||||
const START = Date.now();
|
||||
const COUNT = 10;
|
||||
|
||||
let resolveFunction;
|
||||
|
||||
let openmct;
|
||||
let imageryPlugin;
|
||||
let parent;
|
||||
let child;
|
||||
let timeFormat = 'utc';
|
||||
let bounds = {
|
||||
start: START - TEN_MINUTES,
|
||||
end: START
|
||||
};
|
||||
let imageTelemetry = generateTelemetry(START - TEN_MINUTES, COUNT);
|
||||
let imageryObject = {
|
||||
identifier: {
|
||||
@ -205,6 +201,10 @@ describe("The Imagery View Layout", () => {
|
||||
|
||||
openmct = createOpenMct();
|
||||
|
||||
openmct.install(openmct.plugins.MyItems());
|
||||
openmct.install(openmct.plugins.LocalTimeSystem());
|
||||
openmct.install(openmct.plugins.UTCTimeSystem());
|
||||
|
||||
parent = document.createElement('div');
|
||||
child = document.createElement('div');
|
||||
parent.appendChild(child);
|
||||
@ -215,22 +215,18 @@ describe("The Imagery View Layout", () => {
|
||||
});
|
||||
|
||||
spyOn(openmct.telemetry, 'request').and.returnValue(Promise.resolve([]));
|
||||
|
||||
imageryPlugin = new ImageryPlugin();
|
||||
openmct.install(imageryPlugin);
|
||||
|
||||
spyOn(openmct.objects, 'get').and.returnValue(Promise.resolve({}));
|
||||
|
||||
openmct.time.timeSystem(timeFormat, {
|
||||
start: 0,
|
||||
end: 4
|
||||
});
|
||||
|
||||
openmct.on('start', done);
|
||||
openmct.startHeadless(appHolder);
|
||||
openmct.start(appHolder);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
openmct.time.timeSystem('utc', {
|
||||
start: 0,
|
||||
end: 1
|
||||
});
|
||||
|
||||
return resetApplicationState(openmct);
|
||||
});
|
||||
|
||||
@ -248,7 +244,7 @@ describe("The Imagery View Layout", () => {
|
||||
let imageryViewProvider;
|
||||
let imageryView;
|
||||
|
||||
beforeEach(async (done) => {
|
||||
beforeEach(async () => {
|
||||
let telemetryRequestResolve;
|
||||
let telemetryRequestPromise = new Promise((resolve) => {
|
||||
telemetryRequestResolve = resolve;
|
||||
@ -260,23 +256,18 @@ describe("The Imagery View Layout", () => {
|
||||
return telemetryRequestPromise;
|
||||
});
|
||||
|
||||
openmct.time.clock('local', {
|
||||
start: bounds.start,
|
||||
end: bounds.end + 100
|
||||
});
|
||||
|
||||
applicableViews = openmct.objectViews.get(imageryObject, []);
|
||||
imageryViewProvider = applicableViews.find(viewProvider => viewProvider.key === imageryKey);
|
||||
imageryView = imageryViewProvider.view(imageryObject);
|
||||
imageryView.show(child);
|
||||
|
||||
await telemetryRequestPromise;
|
||||
await Vue.nextTick();
|
||||
|
||||
return done();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
openmct.time.stopClock();
|
||||
openmct.router.removeListener('change:hash', resolveFunction);
|
||||
|
||||
imageryView.destroy();
|
||||
});
|
||||
|
||||
@ -286,43 +277,44 @@ describe("The Imagery View Layout", () => {
|
||||
expect(imageInfo.url.indexOf(imageTelemetry[COUNT - 1].timeId)).not.toEqual(-1);
|
||||
});
|
||||
|
||||
it("should show the clicked thumbnail as the main image", async () => {
|
||||
it("should show the clicked thumbnail as the main image", (done) => {
|
||||
const target = imageTelemetry[5].url;
|
||||
parent.querySelectorAll(`img[src='${target}']`)[0].click();
|
||||
await Vue.nextTick();
|
||||
const imageInfo = getImageInfo(parent);
|
||||
Vue.nextTick(() => {
|
||||
const imageInfo = getImageInfo(parent);
|
||||
|
||||
expect(imageInfo.url.indexOf(imageTelemetry[5].timeId)).not.toEqual(-1);
|
||||
});
|
||||
|
||||
it("should show that an image is new", async (done) => {
|
||||
await Vue.nextTick();
|
||||
|
||||
// used in code, need to wait to the 500ms here too
|
||||
setTimeout(() => {
|
||||
const imageIsNew = isNew(parent);
|
||||
|
||||
expect(imageIsNew).toBeTrue();
|
||||
expect(imageInfo.url.indexOf(imageTelemetry[5].timeId)).not.toEqual(-1);
|
||||
done();
|
||||
}, REFRESH_CSS_MS);
|
||||
});
|
||||
});
|
||||
|
||||
it("should show that an image is not new", async (done) => {
|
||||
xit("should show that an image is new", (done) => {
|
||||
Vue.nextTick(() => {
|
||||
// used in code, need to wait to the 500ms here too
|
||||
setTimeout(() => {
|
||||
const imageIsNew = isNew(parent);
|
||||
expect(imageIsNew).toBeTrue();
|
||||
done();
|
||||
}, REFRESH_CSS_MS);
|
||||
});
|
||||
});
|
||||
|
||||
xit("should show that an image is not new", (done) => {
|
||||
const target = imageTelemetry[2].url;
|
||||
parent.querySelectorAll(`img[src='${target}']`)[0].click();
|
||||
|
||||
await Vue.nextTick();
|
||||
Vue.nextTick(() => {
|
||||
// used in code, need to wait to the 500ms here too
|
||||
setTimeout(() => {
|
||||
const imageIsNew = isNew(parent);
|
||||
|
||||
// used in code, need to wait to the 500ms here too
|
||||
setTimeout(() => {
|
||||
const imageIsNew = isNew(parent);
|
||||
|
||||
expect(imageIsNew).toBeFalse();
|
||||
done();
|
||||
}, REFRESH_CSS_MS);
|
||||
expect(imageIsNew).toBeFalse();
|
||||
done();
|
||||
}, REFRESH_CSS_MS);
|
||||
});
|
||||
});
|
||||
|
||||
it("should navigate via arrow keys", async () => {
|
||||
it("should navigate via arrow keys", (done) => {
|
||||
let keyOpts = {
|
||||
element: parent.querySelector('.c-imagery'),
|
||||
key: 'ArrowLeft',
|
||||
@ -332,14 +324,15 @@ describe("The Imagery View Layout", () => {
|
||||
|
||||
simulateKeyEvent(keyOpts);
|
||||
|
||||
await Vue.nextTick();
|
||||
Vue.nextTick(() => {
|
||||
const imageInfo = getImageInfo(parent);
|
||||
|
||||
const imageInfo = getImageInfo(parent);
|
||||
|
||||
expect(imageInfo.url.indexOf(imageTelemetry[COUNT - 2].timeId)).not.toEqual(-1);
|
||||
expect(imageInfo.url.indexOf(imageTelemetry[COUNT - 2].timeId)).not.toEqual(-1);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it("should navigate via numerous arrow keys", async () => {
|
||||
it("should navigate via numerous arrow keys", (done) => {
|
||||
let element = parent.querySelector('.c-imagery');
|
||||
let type = 'keyup';
|
||||
let leftKeyOpts = {
|
||||
@ -362,12 +355,12 @@ describe("The Imagery View Layout", () => {
|
||||
// right once
|
||||
simulateKeyEvent(rightKeyOpts);
|
||||
|
||||
await Vue.nextTick();
|
||||
Vue.nextTick(() => {
|
||||
const imageInfo = getImageInfo(parent);
|
||||
|
||||
const imageInfo = getImageInfo(parent);
|
||||
|
||||
expect(imageInfo.url.indexOf(imageTelemetry[COUNT - 3].timeId)).not.toEqual(-1);
|
||||
expect(imageInfo.url.indexOf(imageTelemetry[COUNT - 3].timeId)).not.toEqual(-1);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
|
@ -55,7 +55,7 @@ describe("The local time", () => {
|
||||
beforeEach(() => {
|
||||
localTimeSystem = openmct.time.timeSystem(LOCAL_SYSTEM_KEY, {
|
||||
start: 0,
|
||||
end: 4
|
||||
end: 1
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -81,7 +81,7 @@ describe("The Move Action plugin", () => {
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
resetApplicationState(openmct);
|
||||
return resetApplicationState(openmct);
|
||||
});
|
||||
|
||||
it("should be defined", () => {
|
||||
|
@ -135,6 +135,7 @@ import SearchResults from './SearchResults.vue';
|
||||
import Sidebar from './Sidebar.vue';
|
||||
import { clearDefaultNotebook, getDefaultNotebook, setDefaultNotebook, setDefaultNotebookSection, setDefaultNotebookPage } from '../utils/notebook-storage';
|
||||
import { addNotebookEntry, createNewEmbed, getEntryPosById, getNotebookEntries, mutateObject } from '../utils/notebook-entries';
|
||||
import { NOTEBOOK_VIEW_TYPE } from '../notebook-constants';
|
||||
import objectUtils from 'objectUtils';
|
||||
|
||||
import { debounce } from 'lodash';
|
||||
@ -189,14 +190,14 @@ export default {
|
||||
selectedPage() {
|
||||
const pages = this.getPages();
|
||||
if (!pages) {
|
||||
return null;
|
||||
return {};
|
||||
}
|
||||
|
||||
return pages.find(page => page.isSelected);
|
||||
},
|
||||
selectedSection() {
|
||||
if (!this.sections.length) {
|
||||
return null;
|
||||
return {};
|
||||
}
|
||||
|
||||
return this.sections.find(section => section.isSelected);
|
||||
@ -216,6 +217,7 @@ export default {
|
||||
|
||||
window.addEventListener('orientationchange', this.formatSidebar);
|
||||
window.addEventListener("hashchange", this.navigateToSectionPage, false);
|
||||
this.openmct.router.on('change:params', this.changeSectionPage);
|
||||
|
||||
this.navigateToSectionPage();
|
||||
},
|
||||
@ -226,6 +228,7 @@ export default {
|
||||
|
||||
window.removeEventListener('orientationchange', this.formatSidebar);
|
||||
window.removeEventListener("hashchange", this.navigateToSectionPage);
|
||||
this.openmct.router.off('change:params', this.changeSectionPage);
|
||||
},
|
||||
updated: function () {
|
||||
this.$nextTick(() => {
|
||||
@ -233,6 +236,28 @@ export default {
|
||||
});
|
||||
},
|
||||
methods: {
|
||||
changeSectionPage(newParams, oldParams, changedParams) {
|
||||
if (newParams.view !== NOTEBOOK_VIEW_TYPE) {
|
||||
return;
|
||||
}
|
||||
|
||||
let pageId = newParams.pageId;
|
||||
let sectionId = newParams.sectionId;
|
||||
|
||||
if (!pageId && !sectionId) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.sections.forEach(section => {
|
||||
section.isSelected = Boolean(section.id === sectionId);
|
||||
|
||||
if (section.isSelected) {
|
||||
section.pages.forEach(page => {
|
||||
page.isSelected = Boolean(page.id === pageId);
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
changeSelectedSection({ sectionId, pageId }) {
|
||||
const sections = this.sections.map(s => {
|
||||
s.isSelected = false;
|
||||
@ -518,9 +543,11 @@ export default {
|
||||
return this.sections.find(section => section.isSelected);
|
||||
},
|
||||
navigateToSectionPage() {
|
||||
const { pageId, sectionId } = this.openmct.router.getParams();
|
||||
let { pageId, sectionId } = this.openmct.router.getParams();
|
||||
|
||||
if (!pageId || !sectionId) {
|
||||
return;
|
||||
sectionId = this.selectedSection.id;
|
||||
pageId = this.selectedPage.id;
|
||||
}
|
||||
|
||||
const sections = this.sections.map(s => {
|
||||
|
@ -145,7 +145,7 @@ export default {
|
||||
|
||||
const relativeHash = hash.slice(hash.indexOf('#'));
|
||||
const url = new URL(relativeHash, `${location.protocol}//${location.host}${location.pathname}`);
|
||||
window.location.hash = url.hash;
|
||||
this.openmct.router.navigate(url.hash);
|
||||
},
|
||||
formatTime(unixTime, timeFormat) {
|
||||
return Moment.utc(unixTime).format(timeFormat);
|
||||
|
@ -111,10 +111,6 @@ export default {
|
||||
}
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
pages() {
|
||||
const selectedSection = this.sections.find(section => section.isSelected);
|
||||
|
@ -1,3 +1,4 @@
|
||||
export const EVENT_SNAPSHOTS_UPDATED = 'SNAPSHOTS_UPDATED';
|
||||
export const NOTEBOOK_DEFAULT = 'DEFAULT';
|
||||
export const NOTEBOOK_SNAPSHOT = 'SNAPSHOT';
|
||||
export const NOTEBOOK_VIEW_TYPE = 'notebook-vue';
|
||||
|
@ -65,7 +65,8 @@ describe("Notebook plugin:", () => {
|
||||
|
||||
afterAll(() => {
|
||||
appHolder.remove();
|
||||
resetApplicationState(openmct);
|
||||
|
||||
return resetApplicationState(openmct);
|
||||
});
|
||||
|
||||
it("has type as Notebook", () => {
|
||||
|
@ -140,7 +140,8 @@ describe('Notebook Entries:', () => {
|
||||
|
||||
afterEach(() => {
|
||||
notebookDomainObject.configuration.entries[selectedSection.id][selectedPage.id] = [];
|
||||
resetApplicationState(openmct);
|
||||
|
||||
return resetApplicationState(openmct);
|
||||
});
|
||||
|
||||
it('getNotebookEntries has no entries', () => {
|
||||
|
@ -83,7 +83,7 @@ describe('Notebook Storage:', () => {
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
resetApplicationState(openmct);
|
||||
return resetApplicationState(openmct);
|
||||
});
|
||||
|
||||
it('has empty local Storage', () => {
|
||||
|
@ -117,7 +117,7 @@ describe('the plugin', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('updates an object', () => {
|
||||
it('updates an object', (done) => {
|
||||
return openmct.objects.save(mockDomainObject).then((result) => {
|
||||
expect(result).toBeTrue();
|
||||
expect(provider.create).toHaveBeenCalled();
|
||||
@ -128,6 +128,7 @@ describe('the plugin', () => {
|
||||
return openmct.objects.save(mockDomainObject).then((updatedResult) => {
|
||||
expect(updatedResult).toBeTrue();
|
||||
expect(provider.update).toHaveBeenCalled();
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -228,15 +228,16 @@ export default {
|
||||
|
||||
doTickUpdate() {
|
||||
if (this.shouldCheckWidth) {
|
||||
const tickElements = this.$refs.tickContainer.querySelectorAll('.gl-plot-tick > span');
|
||||
const tickElements = this.$refs.tickContainer && this.$refs.tickContainer.querySelectorAll('.gl-plot-tick > span');
|
||||
if (tickElements) {
|
||||
const tickWidth = Number([].reduce.call(tickElements, function (memo, first) {
|
||||
return Math.max(memo, first.offsetWidth);
|
||||
}, 0));
|
||||
|
||||
const tickWidth = Number([].reduce.call(tickElements, function (memo, first) {
|
||||
return Math.max(memo, first.offsetWidth);
|
||||
}, 0));
|
||||
|
||||
this.tickWidth = tickWidth;
|
||||
this.$emit('plotTickWidth', tickWidth);
|
||||
this.shouldCheckWidth = false;
|
||||
this.tickWidth = tickWidth;
|
||||
this.$emit('plotTickWidth', tickWidth);
|
||||
this.shouldCheckWidth = false;
|
||||
}
|
||||
}
|
||||
|
||||
this.tickUpdate = false;
|
||||
|
@ -118,6 +118,11 @@ describe("the plugin", function () {
|
||||
});
|
||||
|
||||
afterEach((done) => {
|
||||
openmct.time.timeSystem('utc', {
|
||||
start: 0,
|
||||
end: 1
|
||||
});
|
||||
|
||||
// Needs to be in a timeout because plots use a bunch of setTimeouts, some of which can resolve during or after
|
||||
// teardown, which causes problems
|
||||
// This is hacky, we should find a better approach here.
|
||||
@ -129,7 +134,7 @@ describe("the plugin", function () {
|
||||
|
||||
configStore.deleteAll();
|
||||
|
||||
resetApplicationState(openmct).then(done);
|
||||
resetApplicationState(openmct).then(done).catch(done);
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -78,7 +78,7 @@ export default class RemoveAction {
|
||||
.map(object => this.openmct.objects.makeKeyString(object.identifier))
|
||||
.join("/");
|
||||
|
||||
window.location.href = '#/browse/' + urlPath;
|
||||
this.openmct.router.navigate('#/browse/' + urlPath);
|
||||
}
|
||||
|
||||
removeFromComposition(parent, child) {
|
||||
|
@ -72,7 +72,7 @@ describe("The Remove Action plugin", () => {
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
resetApplicationState(openmct);
|
||||
return resetApplicationState(openmct);
|
||||
});
|
||||
|
||||
it("should be defined", () => {
|
||||
|
@ -65,11 +65,6 @@
|
||||
<script>
|
||||
import ObjectView from '../../../ui/components/ObjectView.vue';
|
||||
import RemoveAction from '../../remove/RemoveAction.js';
|
||||
import {
|
||||
getSearchParam,
|
||||
setSearchParam,
|
||||
deleteSearchParam
|
||||
} from 'utils/openmctLocation';
|
||||
|
||||
const unknownObjectType = {
|
||||
definition: {
|
||||
@ -115,7 +110,7 @@ export default {
|
||||
this.composition.on('remove', this.removeItem);
|
||||
this.composition.on('reorder', this.onReorder);
|
||||
this.composition.load().then(() => {
|
||||
let currentTabIndexFromURL = getSearchParam(this.searchTabKey);
|
||||
let currentTabIndexFromURL = this.openmct.router.getSearchParam(this.searchTabKey);
|
||||
let currentTabIndexFromDomainObject = this.internalDomainObject.currentTabIndex;
|
||||
|
||||
if (currentTabIndexFromURL !== null) {
|
||||
@ -129,6 +124,8 @@ export default {
|
||||
|
||||
this.unsubscribe = this.openmct.objects.observe(this.internalDomainObject, '*', this.updateInternalDomainObject);
|
||||
|
||||
this.openmct.router.on('change:params', this.updateCurrentTab.bind(this));
|
||||
|
||||
this.RemoveAction = new RemoveAction(this.openmct);
|
||||
document.addEventListener('dragstart', this.dragstart);
|
||||
document.addEventListener('dragend', this.dragend);
|
||||
@ -148,6 +145,8 @@ export default {
|
||||
this.unsubscribe();
|
||||
this.clearCurrentTabIndexFromURL();
|
||||
|
||||
this.openmct.router.off('change:params', this.updateCurrentTab.bind(this));
|
||||
|
||||
document.removeEventListener('dragstart', this.dragstart);
|
||||
document.removeEventListener('dragend', this.dragend);
|
||||
},
|
||||
@ -285,15 +284,15 @@ export default {
|
||||
this.openmct.objects.mutate(this.internalDomainObject, 'currentTabIndex', index);
|
||||
},
|
||||
storeCurrentTabIndexInURL(index) {
|
||||
let currentTabIndexInURL = getSearchParam(this.searchTabKey);
|
||||
let currentTabIndexInURL = this.openmct.router.getSearchParam(this.searchTabKey);
|
||||
|
||||
if (index !== currentTabIndexInURL) {
|
||||
setSearchParam(this.searchTabKey, index);
|
||||
this.openmct.router.setSearchParam(this.searchTabKey, index);
|
||||
this.currentTabIndex = index;
|
||||
}
|
||||
},
|
||||
clearCurrentTabIndexFromURL() {
|
||||
deleteSearchParam(this.searchTabKey);
|
||||
this.openmct.router.deleteSearchParam(this.searchTabKey);
|
||||
},
|
||||
updateStatus(keyString, status) {
|
||||
let tabPos = this.tabsList.findIndex((tab) => {
|
||||
@ -309,6 +308,19 @@ export default {
|
||||
} else {
|
||||
return this.loadedTabs[tab.keyString];
|
||||
}
|
||||
},
|
||||
updateCurrentTab(newParams, oldParams, changedParams) {
|
||||
const tabIndex = changedParams[this.searchTabKey];
|
||||
if (!tabIndex) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.currentTabIndex === parseInt(tabIndex, 10)) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.currentTabIndex = tabIndex;
|
||||
this.currentTab = this.tabsList[tabIndex];
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@ -82,6 +82,11 @@ describe("the plugin", () => {
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
openmct.time.timeSystem('utc', {
|
||||
start: 0,
|
||||
end: 1
|
||||
});
|
||||
|
||||
return resetApplicationState(openmct);
|
||||
});
|
||||
|
||||
|
@ -59,7 +59,7 @@ describe("The UTC Time System", () => {
|
||||
it("can be set to be the main time system", () => {
|
||||
openmct.time.timeSystem(UTC_SYSTEM_AND_FORMAT_KEY, {
|
||||
start: 0,
|
||||
end: 4
|
||||
end: 1
|
||||
});
|
||||
|
||||
expect(openmct.time.timeSystem().key).toBe(UTC_SYSTEM_AND_FORMAT_KEY);
|
||||
|
@ -75,7 +75,7 @@ export default {
|
||||
event.preventDefault();
|
||||
this.preview();
|
||||
} else {
|
||||
window.location.assign(this.objectLink);
|
||||
this.openmct.router.navigate(this.objectLink);
|
||||
}
|
||||
},
|
||||
preview() {
|
||||
|
@ -180,16 +180,16 @@ export default {
|
||||
},
|
||||
hasParent() {
|
||||
return this.domainObject !== PLACEHOLDER_OBJECT
|
||||
&& this.parentUrl !== '#/browse';
|
||||
&& this.parentUrl !== '/browse';
|
||||
},
|
||||
parentUrl() {
|
||||
let objectKeyString = this.openmct.objects.makeKeyString(this.domainObject.identifier);
|
||||
let hash = window.location.hash;
|
||||
const objectKeyString = this.openmct.objects.makeKeyString(this.domainObject.identifier);
|
||||
const hash = this.openmct.router.getCurrentLocation().path;
|
||||
|
||||
return hash.slice(0, hash.lastIndexOf('/' + objectKeyString));
|
||||
},
|
||||
type() {
|
||||
let objectType = this.openmct.types.get(this.domainObject.type);
|
||||
const objectType = this.openmct.types.get(this.domainObject.type);
|
||||
if (!objectType) {
|
||||
return {};
|
||||
}
|
||||
@ -336,7 +336,7 @@ export default {
|
||||
});
|
||||
},
|
||||
goToParent() {
|
||||
window.location.hash = this.parentUrl;
|
||||
this.openmct.router.navigate(this.parentUrl);
|
||||
},
|
||||
updateActionItems(actionItems) {
|
||||
this.statusBarItems = this.actionCollection.getStatusBarActions();
|
||||
|
@ -23,23 +23,7 @@
|
||||
|
||||
const LocationBar = require('location-bar');
|
||||
const EventEmitter = require('EventEmitter');
|
||||
|
||||
function paramsToObject(searchParams) {
|
||||
let params = {};
|
||||
for (let [key, value] of searchParams.entries()) {
|
||||
if (params[key]) {
|
||||
if (!Array.isArray(params[key])) {
|
||||
params[key] = [params[key]];
|
||||
}
|
||||
|
||||
params[key].push(value);
|
||||
} else {
|
||||
params[key] = value;
|
||||
}
|
||||
}
|
||||
|
||||
return params;
|
||||
}
|
||||
const _ = require('lodash');
|
||||
|
||||
class ApplicationRouter extends EventEmitter {
|
||||
/**
|
||||
@ -57,11 +41,158 @@ class ApplicationRouter extends EventEmitter {
|
||||
* route(path, handler);
|
||||
* start(); Start routing.
|
||||
*/
|
||||
constructor() {
|
||||
constructor(openmct) {
|
||||
super();
|
||||
|
||||
this.locationBar = new LocationBar();
|
||||
this.openmct = openmct;
|
||||
this.routes = [];
|
||||
this.started = false;
|
||||
this.locationBar = new LocationBar();
|
||||
|
||||
this.setHash = _.debounce(this.setHash.bind(this), 300);
|
||||
}
|
||||
|
||||
// Public Methods
|
||||
|
||||
destroy() {
|
||||
this.locationBar.stop();
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a given query parameter from current url
|
||||
*
|
||||
* @param {string} paramName name of searchParam to delete from current url searchParams
|
||||
*/
|
||||
deleteSearchParam(paramName) {
|
||||
let url = this.getHashRelativeURL();
|
||||
|
||||
url.searchParams.delete(paramName);
|
||||
this.setLocationFromUrl();
|
||||
}
|
||||
|
||||
/**
|
||||
* object for accessing all current search parameters
|
||||
*
|
||||
* @returns {URLSearchParams} A {@link https://developer.mozilla.org/en-US/docs/Web/API/URLSearchParams/entries|URLSearchParams}
|
||||
*/
|
||||
getAllSearchParams() {
|
||||
return this.getHashRelativeURL().searchParams;
|
||||
}
|
||||
|
||||
/**
|
||||
* Uniquely identifies a domain object.
|
||||
*
|
||||
* @typedef CurrentLocation
|
||||
* @property {URL} url current url location
|
||||
* @property {string} path current url location pathname
|
||||
* @property {string} getQueryString a function which returns url search query
|
||||
* @property {object} params object representing url searchParams
|
||||
*/
|
||||
|
||||
/**
|
||||
* object for accessing current url location and search params
|
||||
*
|
||||
* @returns {CurrentLocation} A {@link CurrentLocation}
|
||||
*/
|
||||
getCurrentLocation() {
|
||||
return this.currentLocation;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get current location URL Object
|
||||
*
|
||||
* @returns {URL} current url location
|
||||
*/
|
||||
getHashRelativeURL() {
|
||||
return this.getCurrentLocation().url;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get current location URL Object searchParams
|
||||
*
|
||||
* @returns {object} object representing current url searchParams
|
||||
*/
|
||||
getParams() {
|
||||
return this.currentLocation.params;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a value of given param from current url searchParams
|
||||
*
|
||||
* @returns {string} value of paramName from current url searchParams
|
||||
*/
|
||||
getSearchParam(paramName) {
|
||||
return this.getAllSearchParams().get(paramName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Navgate to given hash and update current location object and notify listeners about location change
|
||||
*
|
||||
* @param {string} paramName name of searchParam to get from current url searchParams
|
||||
*
|
||||
* @returns {string} value of paramName from current url searchParams
|
||||
*/
|
||||
navigate(hash) {
|
||||
this.handleLocationChange(hash.substring(1));
|
||||
}
|
||||
|
||||
/**
|
||||
* Add routes listeners
|
||||
*
|
||||
* @param {string} matcher Regex to match value in url
|
||||
* @param {@function} callback function called when found match in url
|
||||
*/
|
||||
route(matcher, callback) {
|
||||
this.routes.push({
|
||||
matcher,
|
||||
callback
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Set url hash using path and queryString
|
||||
*
|
||||
* @param {string} path path for url
|
||||
* @param {string} queryString queryString for url
|
||||
*/
|
||||
set(path, queryString) {
|
||||
this.setHash(`${path}?${queryString}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Will replace all current search parameters with the ones defined in urlSearchParams
|
||||
*/
|
||||
setAllSearchParams() {
|
||||
this.setLocationFromUrl();
|
||||
}
|
||||
|
||||
/**
|
||||
* To force update url based on value in currentLocation object
|
||||
*/
|
||||
setLocationFromUrl() {
|
||||
this.updateTimeSettings();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set url hash using path
|
||||
*
|
||||
* @param {string} path path for url
|
||||
*/
|
||||
setPath(path) {
|
||||
this.handleLocationChange(path.substring(1));
|
||||
}
|
||||
|
||||
/**
|
||||
* Update param value from current url searchParams
|
||||
*
|
||||
* @param {string} paramName param name from current url searchParams
|
||||
* @param {string} paramValue param value from current url searchParams
|
||||
*/
|
||||
setSearchParam(paramName, paramValue) {
|
||||
let url = this.getHashRelativeURL();
|
||||
|
||||
url.searchParams.set(paramName, paramValue);
|
||||
this.setLocationFromUrl();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -74,105 +205,18 @@ class ApplicationRouter extends EventEmitter {
|
||||
|
||||
this.started = true;
|
||||
|
||||
this.locationBar.onChange(p => this.handleLocationChange(p));
|
||||
this.locationBar.onChange(p => this.hashChaged(p));
|
||||
this.locationBar.start({
|
||||
root: location.pathname
|
||||
});
|
||||
}
|
||||
|
||||
destroy() {
|
||||
this.locationBar.stop();
|
||||
this.removeAllListeners();
|
||||
}
|
||||
|
||||
handleLocationChange(pathString) {
|
||||
if (pathString[0] !== '/') {
|
||||
pathString = '/' + pathString;
|
||||
}
|
||||
|
||||
let url = new URL(
|
||||
pathString,
|
||||
`${location.protocol}//${location.host}${location.pathname}`
|
||||
);
|
||||
|
||||
let oldLocation = this.currentLocation;
|
||||
|
||||
let newLocation = {
|
||||
url: url,
|
||||
path: url.pathname,
|
||||
queryString: url.search.replace(/^\?/, ''),
|
||||
params: paramsToObject(url.searchParams)
|
||||
};
|
||||
|
||||
this.currentLocation = newLocation;
|
||||
|
||||
if (!oldLocation) {
|
||||
this.doPathChange(newLocation.path, null, newLocation);
|
||||
this.doParamsChange(newLocation.params, {}, newLocation);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (oldLocation.path !== newLocation.path) {
|
||||
this.doPathChange(
|
||||
newLocation.path,
|
||||
oldLocation.path,
|
||||
this
|
||||
);
|
||||
}
|
||||
|
||||
if (!_.isEqual(oldLocation.params, newLocation.params)) {
|
||||
this.doParamsChange(
|
||||
newLocation.params,
|
||||
oldLocation.params,
|
||||
newLocation
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
doPathChange(newPath, oldPath, newLocation) {
|
||||
let route = this.routes.filter(r => r.matcher.test(newPath))[0];
|
||||
if (route) {
|
||||
route.callback(newPath, route.matcher.exec(newPath), this.currentLocation.params);
|
||||
}
|
||||
|
||||
this.emit('change:path', newPath, oldPath);
|
||||
}
|
||||
|
||||
doParamsChange(newParams, oldParams, newLocation) {
|
||||
let changedParams = {};
|
||||
Object.entries(newParams).forEach(([key, value]) => {
|
||||
if (value !== oldParams[key]) {
|
||||
changedParams[key] = value;
|
||||
}
|
||||
});
|
||||
Object.keys(oldParams).forEach(key => {
|
||||
if (!Object.prototype.hasOwnProperty.call(newParams, key)) {
|
||||
changedParams[key] = undefined;
|
||||
}
|
||||
});
|
||||
this.emit('change:params', newParams, oldParams, changedParams);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update route params. Takes an object of updates. New parameters
|
||||
* Set url hash using path and searchParams object
|
||||
*
|
||||
* @param {string} path path for url
|
||||
* @param {string} params oject representing searchParams key/value
|
||||
*/
|
||||
updateParams(updateParams) {
|
||||
let searchParams = this.currentLocation.url.searchParams;
|
||||
Object.entries(updateParams).forEach(([key, value]) => {
|
||||
if (typeof value === 'undefined') {
|
||||
searchParams.delete(key);
|
||||
} else {
|
||||
searchParams.set(key, value);
|
||||
}
|
||||
});
|
||||
this.setQueryString(searchParams.toString());
|
||||
}
|
||||
|
||||
getParams() {
|
||||
return this.currentLocation.params;
|
||||
}
|
||||
|
||||
update(path, params) {
|
||||
let searchParams = this.currentLocation.url.searchParams;
|
||||
for (let [key, value] of Object.entries(params)) {
|
||||
@ -186,24 +230,190 @@ class ApplicationRouter extends EventEmitter {
|
||||
this.set(path, searchParams.toString());
|
||||
}
|
||||
|
||||
set(path, queryString) {
|
||||
location.hash = `${path}?${queryString}`;
|
||||
}
|
||||
|
||||
setQueryString(queryString) {
|
||||
this.set(this.currentLocation.path, queryString);
|
||||
}
|
||||
|
||||
setPath(path) {
|
||||
this.set(path, this.currentLocation.queryString);
|
||||
}
|
||||
|
||||
route(matcher, callback) {
|
||||
this.routes.push({
|
||||
matcher,
|
||||
callback
|
||||
/**
|
||||
* Update route params. Takes an object of updates. New parameters
|
||||
*/
|
||||
updateParams(updateParams) {
|
||||
let searchParams = this.currentLocation.url.searchParams;
|
||||
Object.entries(updateParams).forEach(([key, value]) => {
|
||||
if (typeof value === 'undefined') {
|
||||
searchParams.delete(key);
|
||||
} else {
|
||||
searchParams.set(key, value);
|
||||
}
|
||||
});
|
||||
|
||||
this.setQueryString(searchParams.toString());
|
||||
}
|
||||
|
||||
/**
|
||||
* To force update url based on value in currentLocation object
|
||||
*/
|
||||
updateTimeSettings() {
|
||||
const hash = `${this.currentLocation.path}?${this.currentLocation.getQueryString()}`;
|
||||
|
||||
this.setHash(hash);
|
||||
}
|
||||
|
||||
// Private Methods
|
||||
|
||||
/**
|
||||
* @private
|
||||
* Create currentLocation object
|
||||
*
|
||||
* @param {string} pathString USVString representing relative URL.
|
||||
*
|
||||
* @returns {CurrentLocation} A {@link CurrentLocation}
|
||||
*/
|
||||
createLocation(pathString) {
|
||||
if (pathString[0] !== '/') {
|
||||
pathString = '/' + pathString;
|
||||
}
|
||||
|
||||
let url = new URL(
|
||||
pathString,
|
||||
`${location.protocol}//${location.host}${location.pathname}`
|
||||
);
|
||||
|
||||
return {
|
||||
url: url,
|
||||
path: url.pathname,
|
||||
getQueryString: () => url.search.replace(/^\?/, ''),
|
||||
params: paramsToObject(url.searchParams)
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* Compare new and old path and on change emit event 'change:path'
|
||||
*
|
||||
* @param {string} newPath new path of url
|
||||
* @param {string} oldPath old path of url
|
||||
*/
|
||||
doPathChange(newPath, oldPath) {
|
||||
if (newPath === oldPath) {
|
||||
return;
|
||||
}
|
||||
|
||||
let route = this.routes.filter(r => r.matcher.test(newPath))[0];
|
||||
if (route) {
|
||||
route.callback(newPath, route.matcher.exec(newPath), this.currentLocation.params);
|
||||
}
|
||||
|
||||
this.emit('change:path', newPath, oldPath);
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* Compare new and old params and on change emit event 'change:params'
|
||||
*
|
||||
* @param {object} newParams new params of url
|
||||
* @param {object} oldParams old params of url
|
||||
*/
|
||||
doParamsChange(newParams, oldParams) {
|
||||
if (_.isEqual(newParams, oldParams)) {
|
||||
return;
|
||||
}
|
||||
|
||||
let changedParams = {};
|
||||
Object.entries(newParams).forEach(([key, value]) => {
|
||||
if (value !== oldParams[key]) {
|
||||
changedParams[key] = value;
|
||||
}
|
||||
});
|
||||
Object.keys(oldParams).forEach(key => {
|
||||
if (!Object.prototype.hasOwnProperty.call(newParams, key)) {
|
||||
changedParams[key] = undefined;
|
||||
}
|
||||
});
|
||||
|
||||
this.emit('change:params', newParams, oldParams, changedParams);
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* On location change, update currentLocation object and emit appropriate events
|
||||
*
|
||||
* @param {string} pathString USVString representing relative URL.
|
||||
*/
|
||||
handleLocationChange(pathString) {
|
||||
let oldLocation = this.currentLocation;
|
||||
let newLocation = this.createLocation(pathString);
|
||||
|
||||
this.currentLocation = newLocation;
|
||||
|
||||
if (!oldLocation) {
|
||||
this.doPathChange(newLocation.path, null);
|
||||
this.doParamsChange(newLocation.params, {});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
this.doPathChange(
|
||||
newLocation.path,
|
||||
oldLocation.path
|
||||
);
|
||||
|
||||
this.doParamsChange(
|
||||
newLocation.params,
|
||||
oldLocation.params
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* On hash changed, update currentLocation object and emit appropriate events
|
||||
*
|
||||
* @param {string} hash new hash for url
|
||||
*/
|
||||
hashChaged(hash) {
|
||||
this.emit('change:hash', hash);
|
||||
this.handleLocationChange(hash);
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* Set new hash for url
|
||||
*
|
||||
* @param {string} hash new hash for url
|
||||
*/
|
||||
setHash(hash) {
|
||||
location.hash = '#' + hash.replace(/#/g, '');
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* Set queryString part of current url
|
||||
*
|
||||
* @param {string} queryString queryString part of url
|
||||
*/
|
||||
setQueryString(queryString) {
|
||||
this.handleLocationChange(`${this.currentLocation.path}?${queryString}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert searchParams into Object
|
||||
*
|
||||
* @param {URLSearchParams} searchParams queryString part of url
|
||||
*
|
||||
* @returns {Object}
|
||||
*/
|
||||
function paramsToObject(searchParams) {
|
||||
let params = {};
|
||||
for (let [key, value] of searchParams.entries()) {
|
||||
if (params[key]) {
|
||||
if (!Array.isArray(params[key])) {
|
||||
params[key] = [params[key]];
|
||||
}
|
||||
|
||||
params[key].push(value);
|
||||
} else {
|
||||
params[key] = value;
|
||||
}
|
||||
}
|
||||
|
||||
return params;
|
||||
}
|
||||
|
||||
module.exports = ApplicationRouter;
|
||||
|
139
src/ui/router/ApplicationRouterSpec.js
Normal file
139
src/ui/router/ApplicationRouterSpec.js
Normal file
@ -0,0 +1,139 @@
|
||||
import { createOpenMct, resetApplicationState } from 'utils/testing';
|
||||
|
||||
let openmct;
|
||||
let element;
|
||||
let child;
|
||||
let appHolder;
|
||||
let resolveFunction;
|
||||
|
||||
let initialHash = '';
|
||||
|
||||
describe('Application router utility functions', () => {
|
||||
beforeAll(done => {
|
||||
appHolder = document.createElement('div');
|
||||
appHolder.style.width = '640px';
|
||||
appHolder.style.height = '480px';
|
||||
|
||||
openmct = createOpenMct();
|
||||
openmct.install(openmct.plugins.MyItems());
|
||||
openmct.install(openmct.plugins.LocalTimeSystem());
|
||||
openmct.install(openmct.plugins.UTCTimeSystem());
|
||||
|
||||
element = document.createElement('div');
|
||||
child = document.createElement('div');
|
||||
element.appendChild(child);
|
||||
|
||||
openmct.on('start', done);
|
||||
openmct.start(appHolder);
|
||||
|
||||
document.body.append(appHolder);
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
openmct.router.setHash(initialHash);
|
||||
appHolder.remove();
|
||||
|
||||
return resetApplicationState(openmct);
|
||||
});
|
||||
|
||||
it('has initial hash when loaded', (done) => {
|
||||
let success;
|
||||
resolveFunction = () => {
|
||||
openmct.router.setLocationFromUrl();
|
||||
success = window.location.hash !== null;
|
||||
if (success) {
|
||||
initialHash = window.location.hash;
|
||||
expect(success).toBe(true);
|
||||
|
||||
openmct.router.removeListener('change:hash', resolveFunction);
|
||||
done();
|
||||
}
|
||||
};
|
||||
|
||||
openmct.router.on('change:hash', resolveFunction);
|
||||
});
|
||||
|
||||
it('The setSearchParam function sets an individual search parameter in the window location hash', (done) => {
|
||||
let success;
|
||||
openmct.router.setSearchParam('testParam', 'testValue');
|
||||
resolveFunction = () => {
|
||||
success = window.location.hash.includes('testParam=testValue');
|
||||
if (success) {
|
||||
expect(success).toBe(true);
|
||||
|
||||
openmct.router.removeListener('change:hash', resolveFunction);
|
||||
done();
|
||||
}
|
||||
};
|
||||
|
||||
openmct.router.on('change:hash', resolveFunction);
|
||||
});
|
||||
|
||||
it('The getSearchParam function returns the value of an individual search paramater in the window location hash', () => {
|
||||
expect(openmct.router.getSearchParam('testParam')).toBe('testValue');
|
||||
});
|
||||
|
||||
it('The deleteSearchParam function deletes an individual search paramater in the window location hash', (done) => {
|
||||
let success;
|
||||
openmct.router.deleteSearchParam('testParam');
|
||||
resolveFunction = () => {
|
||||
success = window.location.hash.includes('testParam=testValue') === false;
|
||||
if (success) {
|
||||
expect(success).toBe(true);
|
||||
|
||||
openmct.router.removeListener('change:hash', resolveFunction);
|
||||
done();
|
||||
}
|
||||
};
|
||||
|
||||
openmct.router.on('change:hash', resolveFunction);
|
||||
});
|
||||
|
||||
it('The setSearchParam function sets an individual search parameters in the window location hash', (done) => {
|
||||
let success;
|
||||
openmct.router.setSearchParam('testParam1', 'testValue1');
|
||||
openmct.router.setSearchParam('testParam2', 'testValue2');
|
||||
|
||||
resolveFunction = () => {
|
||||
const hasTestParam1 = window.location.hash.includes('testParam1=testValue1');
|
||||
const hasTestParam2 = window.location.hash.includes('testParam2=testValue2');
|
||||
success = hasTestParam1 && hasTestParam2;
|
||||
if (success) {
|
||||
expect(success).toBe(true);
|
||||
|
||||
openmct.router.removeListener('change:hash', resolveFunction);
|
||||
done();
|
||||
}
|
||||
};
|
||||
|
||||
openmct.router.on('change:hash', resolveFunction);
|
||||
});
|
||||
|
||||
it('The setAllSearchParams function replaces all search paramaters in the window location hash', (done) => {
|
||||
let success;
|
||||
|
||||
openmct.router.setSearchParam('testParam2', 'updatedtestValue2');
|
||||
openmct.router.setSearchParam('newTestParam3', 'newTestValue3');
|
||||
|
||||
resolveFunction = () => {
|
||||
const hasupdatedValueForTestParam2 = window.location.hash.includes('testParam2=updatedtestValue2');
|
||||
const hasNewTestParam3 = window.location.hash.includes('newTestParam3=newTestValue3');
|
||||
success = hasupdatedValueForTestParam2 && hasNewTestParam3;
|
||||
if (success) {
|
||||
expect(success).toBe(true);
|
||||
|
||||
openmct.router.removeListener('change:hash', resolveFunction);
|
||||
done();
|
||||
}
|
||||
};
|
||||
|
||||
openmct.router.on('change:hash', resolveFunction);
|
||||
});
|
||||
|
||||
it('The getAllSearchParams function returns the values of all search paramaters in the window location hash', () => {
|
||||
let searchParams = openmct.router.getAllSearchParams();
|
||||
expect(searchParams.get('testParam1')).toBe('testValue1');
|
||||
expect(searchParams.get('testParam2')).toBe('updatedtestValue2');
|
||||
expect(searchParams.get('newTestParam3')).toBe('newTestValue3');
|
||||
});
|
||||
});
|
@ -13,13 +13,12 @@ define([
|
||||
let mutable;
|
||||
|
||||
openmct.router.route(/^\/browse\/?$/, navigateToFirstChildOfRoot);
|
||||
|
||||
openmct.router.route(/^\/browse\/(.*)$/, (path, results, params) => {
|
||||
isRoutingInProgress = true;
|
||||
let navigatePath = results[1];
|
||||
clearMutationListeners();
|
||||
|
||||
navigateToPath(navigatePath, params.view);
|
||||
onParamsChanged(null, null, params);
|
||||
});
|
||||
|
||||
openmct.router.on('change:params', onParamsChanged);
|
||||
@ -133,18 +132,21 @@ define([
|
||||
}
|
||||
|
||||
function navigateToFirstChildOfRoot() {
|
||||
openmct.objects.get('ROOT').then(rootObject => {
|
||||
openmct.composition.get(rootObject).load()
|
||||
.then(children => {
|
||||
let lastChild = children[children.length - 1];
|
||||
if (!lastChild) {
|
||||
console.error('Unable to navigate to anything. No root objects found.');
|
||||
} else {
|
||||
let lastChildId = openmct.objects.makeKeyString(lastChild.identifier);
|
||||
openmct.router.setPath(`#/browse/${lastChildId}`);
|
||||
}
|
||||
});
|
||||
});
|
||||
openmct.objects.get('ROOT')
|
||||
.then(rootObject => {
|
||||
openmct.composition.get(rootObject).load()
|
||||
.then(children => {
|
||||
let lastChild = children[children.length - 1];
|
||||
if (!lastChild) {
|
||||
console.error('Unable to navigate to anything. No root objects found.');
|
||||
} else {
|
||||
let lastChildId = openmct.objects.makeKeyString(lastChild.identifier);
|
||||
openmct.router.setPath(`#/browse/${lastChildId}`);
|
||||
}
|
||||
})
|
||||
.catch(e => console.error(e));
|
||||
})
|
||||
.catch(e => console.error(e));
|
||||
}
|
||||
|
||||
function clearMutationListeners() {
|
||||
|
@ -1,108 +0,0 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT, Copyright (c) 2014-2021, 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 objectUtils from '../api/objects/object-utils.js';
|
||||
|
||||
/**
|
||||
* Utility functions for getting and setting Open MCT search parameters and navigated object path.
|
||||
* Open MCT encodes application state into the "hash" of the url, making it awkward to use standard browser API such
|
||||
* as URL for modifying state in the URL. This wraps native API with some utility functions that operate only on the
|
||||
* hash section of the URL.
|
||||
*/
|
||||
|
||||
export function setSearchParam(paramName, paramValue) {
|
||||
let url = getHashRelativeURL();
|
||||
|
||||
url.searchParams.set(paramName, paramValue);
|
||||
setLocationFromUrl(url);
|
||||
}
|
||||
|
||||
export function deleteSearchParam(paramName) {
|
||||
let url = getHashRelativeURL();
|
||||
|
||||
url.searchParams.delete(paramName);
|
||||
setLocationFromUrl(url);
|
||||
}
|
||||
|
||||
/**
|
||||
* Will replace all current search parameters with the ones defined in urlSearchParams
|
||||
* @param {URLSearchParams} paramMap
|
||||
*/
|
||||
export function setAllSearchParams(newSearchParams) {
|
||||
let url = getHashRelativeURL();
|
||||
|
||||
Array.from(url.searchParams.keys()).forEach((key) => url.searchParams.delete(key));
|
||||
|
||||
Array.from(newSearchParams.keys()).forEach(key => {
|
||||
url.searchParams.set(key, newSearchParams.get(key));
|
||||
});
|
||||
|
||||
setLocationFromUrl(url);
|
||||
}
|
||||
|
||||
export function getSearchParam(paramName) {
|
||||
return getAllSearchParams().get(paramName);
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {URLSearchParams} A {@link https://developer.mozilla.org/en-US/docs/Web/API/URLSearchParams/entries|URLSearchParams}
|
||||
* object for accessing all current search parameters
|
||||
*/
|
||||
export function getAllSearchParams() {
|
||||
return getHashRelativeURL().searchParams;
|
||||
}
|
||||
|
||||
export function getObjectPath() {
|
||||
return getHashRelativeURL().pathname;
|
||||
}
|
||||
|
||||
export function setObjectPath(objectPath) {
|
||||
let objectPathString;
|
||||
let url = getHashRelativeURL();
|
||||
|
||||
if (objectPath instanceof Array) {
|
||||
if (objectPath.length > 0 && isDomainObject(objectPath[0])) {
|
||||
throw 'setObjectPath must be called with either a string, or an array of Domain Objects';
|
||||
}
|
||||
|
||||
objectPathString = objectPath.reduce((pathString, object) => {
|
||||
return `${pathString}/${objectUtils.makeKeyString(object.identifier)}`;
|
||||
}, '');
|
||||
} else {
|
||||
objectPathString = objectPath;
|
||||
}
|
||||
|
||||
url.pathname = objectPathString;
|
||||
setLocationFromUrl(url);
|
||||
}
|
||||
|
||||
function isDomainObject(potentialObject) {
|
||||
return potentialObject.identifier === undefined;
|
||||
}
|
||||
|
||||
function setLocationFromUrl(url) {
|
||||
window.location.hash = `${url.pathname}${url.search}`;
|
||||
}
|
||||
|
||||
function getHashRelativeURL() {
|
||||
return new URL(window.location.hash.substring(1), window.location.origin);
|
||||
}
|
@ -1,113 +0,0 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT, Copyright (c) 2014-2021, 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 {
|
||||
setSearchParam,
|
||||
deleteSearchParam,
|
||||
getAllSearchParams,
|
||||
getSearchParam,
|
||||
setAllSearchParams,
|
||||
getObjectPath,
|
||||
setObjectPath
|
||||
} from './openmctLocation';
|
||||
|
||||
import {resetApplicationState} from 'utils/testing';
|
||||
|
||||
describe('the openmct location utility functions', () => {
|
||||
afterEach(() => resetApplicationState());
|
||||
|
||||
it('The setSearchParam function sets an individual search parameters in the window location hash', () => {
|
||||
setSearchParam('testParam', 'testValue');
|
||||
expect(window.location.hash.includes('testParam=testValue')).toBe(true);
|
||||
});
|
||||
|
||||
it('The deleteSearchParam function deletes an individual search paramater in the window location hash', () => {
|
||||
window.location.hash = '#/?testParam=testValue';
|
||||
deleteSearchParam('testParam');
|
||||
expect(window.location.hash.includes('testParam=testValue')).toBe(false);
|
||||
});
|
||||
|
||||
it('The getSearchParam function returns the value of an individual search paramater in the window location hash', () => {
|
||||
window.location.hash = '#/?testParam=testValue';
|
||||
expect(getSearchParam('testParam')).toBe('testValue');
|
||||
});
|
||||
|
||||
it('The getAllSearchParams function returns the values of all search paramaters in the window location hash', () => {
|
||||
window.location.hash = '#/?testParam1=testValue1&testParam2=testValue2&testParam3=testValue3';
|
||||
let searchParams = getAllSearchParams();
|
||||
expect(searchParams.get('testParam1')).toBe('testValue1');
|
||||
expect(searchParams.get('testParam2')).toBe('testValue2');
|
||||
expect(searchParams.get('testParam3')).toBe('testValue3');
|
||||
});
|
||||
|
||||
it('The setAllSearchParams function replaces all search paramaters in the window location hash', () => {
|
||||
window.location.hash = '#/?testParam1=testValue1&testParam2=testValue2&testParam3=testValue3';
|
||||
let searchParams = getAllSearchParams();
|
||||
searchParams.delete('testParam3');
|
||||
searchParams.set('testParam1', 'updatedTestValue1');
|
||||
searchParams.set('newTestParam4', 'newTestValue4');
|
||||
setAllSearchParams(searchParams);
|
||||
expect(window.location.hash).toBe('#/?testParam1=updatedTestValue1&testParam2=testValue2&newTestParam4=newTestValue4');
|
||||
});
|
||||
|
||||
it('The getObjectPath function returns the current object path', () => {
|
||||
window.location.hash = '#/some/object/path?someParameter=someValue';
|
||||
expect(getObjectPath()).toBe('/some/object/path');
|
||||
});
|
||||
|
||||
it('The setObjectPath function allows the object path to be set to a given string', () => {
|
||||
window.location.hash = '#/some/object/path?someParameter=someValue';
|
||||
setObjectPath('/some/other/object/path');
|
||||
expect(window.location.hash).toBe('#/some/other/object/path?someParameter=someValue');
|
||||
});
|
||||
|
||||
it('The setObjectPath function allows the object path to be set from an array of domain objects', () => {
|
||||
const OBJECT_PATH = [
|
||||
{
|
||||
identifier: {
|
||||
namespace: 'namespace',
|
||||
key: 'objectKey1'
|
||||
}
|
||||
},
|
||||
{
|
||||
identifier: {
|
||||
namespace: 'namespace',
|
||||
key: 'objectKey2'
|
||||
}
|
||||
},
|
||||
{
|
||||
identifier: {
|
||||
namespace: 'namespace',
|
||||
key: 'objectKey3'
|
||||
}
|
||||
}
|
||||
];
|
||||
window.location.hash = '#/some/object/path?someParameter=someValue';
|
||||
setObjectPath(OBJECT_PATH);
|
||||
expect(window.location.hash).toBe('#/namespace:objectKey1/namespace:objectKey2/namespace:objectKey3?someParameter=someValue');
|
||||
});
|
||||
|
||||
it('The setObjectPath function throws an error if called with anything other than a string or an array of domain objects', () => {
|
||||
expect(() => setObjectPath(["array", "of", "strings"])).toThrow();
|
||||
expect(() => setObjectPath([{}, {someKey: 'someValue'}])).toThrow();
|
||||
});
|
||||
});
|
||||
|
Loading…
Reference in New Issue
Block a user