From 8f533b3f2af5fd9fcb1d3039bf0d788a9c27d762 Mon Sep 17 00:00:00 2001 From: Markus Edenhauser <1720843+maxmarkus@users.noreply.github.com> Date: Fri, 24 Jan 2020 12:22:25 +0100 Subject: [PATCH 01/18] working half way, wrong viewUrl and non-updating on navigation --- core/src/navigation/services/navigation.js | 80 ++++++++++++++++++++-- 1 file changed, 76 insertions(+), 4 deletions(-) diff --git a/core/src/navigation/services/navigation.js b/core/src/navigation/services/navigation.js index c8669e6a08..9cbb969ad2 100644 --- a/core/src/navigation/services/navigation.js +++ b/core/src/navigation/services/navigation.js @@ -61,7 +61,7 @@ class NavigationClass { this.rootNode = { children: topNavNodes }; } - await this.getChildren(this.rootNode); // keep it, mutates and filters children + await this.getChildren(this.rootNode, null, activePath); // keep it, mutates and filters children } const nodeNamesInCurrentPath = activePath.split('/'); @@ -171,7 +171,8 @@ class NavigationClass { nodesInCurrentPath, childrenOfCurrentNode, context, - pathParams = {} + pathParams = {}, + virtualPathNames ) { if (!context.parentNavigationContexts) { context.parentNavigationContexts = []; @@ -205,14 +206,22 @@ class NavigationClass { pathParams ); try { - let children = await this.getChildren(node, newContext); + /** + * If its a virtual tree, + * build static children + */ + virtualPathNames = this.buildVirtualTree(node, nodeNamesInCurrentPath, virtualPathNames); + + // STANDARD PROCEDURE + let children = await this.getChildren(node, newContext, nodeNamesInCurrentPath); const newNodeNamesInCurrentPath = nodeNamesInCurrentPath.slice(1); result = this.buildNode( newNodeNamesInCurrentPath, nodesInCurrentPath, children, newContext, - pathParams + pathParams, + virtualPathNames ); } catch (err) { console.error('Error getting nodes children', err); @@ -222,6 +231,69 @@ class NavigationClass { return result; } + cleanObj(input, keys) { + const res = {}; + for (const key in input) { + if (input.hasOwnProperty(key)) { + const noFullMatch = keys.filter(k => key.includes(k)).length === 0; + const noPartialMatch = keys.filter(k => k.endsWith('*')) + .map(k => k.slice(0, -1)) + .filter(k => key.startsWith(k)).length === 0; + if (noFullMatch && noPartialMatch) { + res[key] = input[key]; + } + } + } + return res; + } + + + buildVirtualTree(node, nodeNamesInCurrentPath) { + if ((node.isVirtualTree || node._isVirtualTree) && nodeNamesInCurrentPath[0]) { + const isVirtualTreeRoot = node.isVirtualTree; + // Temporary store values that will be cleaned up when creating a copy + let _virtualPathNames = node._virtualPathNames; + let _virtualPathIndex = node._virtualPathIndex; + if (!_virtualPathNames) { + _virtualPathNames = nodeNamesInCurrentPath.slice(); // take without first segment, which is the parent one. + _virtualPathIndex = 0; + if(!node.context) { + node.context = {}; + } + } + // In case of defined virtualTree, when it got directly accessed + // Or when someone tries to target a to long url + const maxPathDepth = 50; + if(!_virtualPathNames.length || _virtualPathIndex > maxPathDepth) { + return; + } + + // console.log('== buildVirtualTree', this.isVirtualTree, _virtualPathIndex, _virtualPathNames.join('/'), 'nniCP', nodeNamesInCurrentPath.join('/')) + + const vPath = _virtualPathNames.slice(0, _virtualPathIndex).join('/'); + _virtualPathIndex++; + // TODO: VIEWURL IS NOT WORKING, HAVE CHANGED INDEX + VPATH ORDER + + const keysToClean = ['_*', 'parent', 'isVirtualTree', 'viewUrl', 'children']; + const newChild = this.cleanObj(node, keysToClean); + + Object.assign(newChild, { + // _prevSegment: nodeNamesInCurrentPath[0], // just for debugging + pathSegment: ':virtualSegment', + label: ':virtualSegment', + viewUrl: node.virtualViewUrl.replace(':virtualPath', vPath), + _isVirtualTree: true, + _virtualPath: vPath, + _virtualPathNames, + _virtualPathIndex + }); + + // override .children with a represence of the current node + node.children = [newChild]; + return _virtualPathNames; + } + } + findMatchingNode(urlPathElement, nodes) { let result = null; const segmentsLength = nodes.filter(n => !!n.pathSegment).length; From f1c4023d7a2960b1b565c5f72155d441a7b1b017 Mon Sep 17 00:00:00 2001 From: Markus Edenhauser <1720843+maxmarkus@users.noreply.github.com> Date: Mon, 27 Jan 2020 12:28:14 +0100 Subject: [PATCH 02/18] working simplified solution --- core/src/navigation/services/navigation.js | 79 ++++++++----------- core/src/utilities/helpers/generic-helpers.js | 27 +++++++ 2 files changed, 61 insertions(+), 45 deletions(-) diff --git a/core/src/navigation/services/navigation.js b/core/src/navigation/services/navigation.js index 9cbb969ad2..ce6ac5e86f 100644 --- a/core/src/navigation/services/navigation.js +++ b/core/src/navigation/services/navigation.js @@ -171,8 +171,7 @@ class NavigationClass { nodesInCurrentPath, childrenOfCurrentNode, context, - pathParams = {}, - virtualPathNames + pathParams = {} ) { if (!context.parentNavigationContexts) { context.parentNavigationContexts = []; @@ -210,18 +209,21 @@ class NavigationClass { * If its a virtual tree, * build static children */ - virtualPathNames = this.buildVirtualTree(node, nodeNamesInCurrentPath, virtualPathNames); + this.buildVirtualTree(node, nodeNamesInCurrentPath, pathParams); // STANDARD PROCEDURE - let children = await this.getChildren(node, newContext, nodeNamesInCurrentPath); + let children = await this.getChildren( + node, + newContext, + nodeNamesInCurrentPath + ); const newNodeNamesInCurrentPath = nodeNamesInCurrentPath.slice(1); result = this.buildNode( newNodeNamesInCurrentPath, nodesInCurrentPath, children, newContext, - pathParams, - virtualPathNames + pathParams ); } catch (err) { console.error('Error getting nodes children', err); @@ -231,66 +233,53 @@ class NavigationClass { return result; } - cleanObj(input, keys) { - const res = {}; - for (const key in input) { - if (input.hasOwnProperty(key)) { - const noFullMatch = keys.filter(k => key.includes(k)).length === 0; - const noPartialMatch = keys.filter(k => k.endsWith('*')) - .map(k => k.slice(0, -1)) - .filter(k => key.startsWith(k)).length === 0; - if (noFullMatch && noPartialMatch) { - res[key] = input[key]; - } + buildVirtualViewUrl(str, pathParams, _virtualPathIndex) { + let newStr = '/'; + for (const key in pathParams) { + if (key.startsWith('virtualSegment')) { + newStr += ':' + key + '/'; } } - return res; + newStr += ':virtualSegment_' + _virtualPathIndex + '/'; + return str.replace(':virtualPath', newStr); } - - buildVirtualTree(node, nodeNamesInCurrentPath) { - if ((node.isVirtualTree || node._isVirtualTree) && nodeNamesInCurrentPath[0]) { - const isVirtualTreeRoot = node.isVirtualTree; + buildVirtualTree(node, nodeNamesInCurrentPath, pathParams) { + const isVirtualTreeRoot = node.isVirtualTree; + const isVirtualTreeChild = node._isVirtualTree; + if ( + (isVirtualTreeRoot || isVirtualTreeChild) && + nodeNamesInCurrentPath[0] + ) { // Temporary store values that will be cleaned up when creating a copy - let _virtualPathNames = node._virtualPathNames; let _virtualPathIndex = node._virtualPathIndex; - if (!_virtualPathNames) { - _virtualPathNames = nodeNamesInCurrentPath.slice(); // take without first segment, which is the parent one. + if (isVirtualTreeRoot) { _virtualPathIndex = 0; - if(!node.context) { - node.context = {}; - } } // In case of defined virtualTree, when it got directly accessed // Or when someone tries to target a to long url + // Or if end of indexes reached const maxPathDepth = 50; - if(!_virtualPathNames.length || _virtualPathIndex > maxPathDepth) { + if (_virtualPathIndex > maxPathDepth) { return; } - // console.log('== buildVirtualTree', this.isVirtualTree, _virtualPathIndex, _virtualPathNames.join('/'), 'nniCP', nodeNamesInCurrentPath.join('/')) - - const vPath = _virtualPathNames.slice(0, _virtualPathIndex).join('/'); _virtualPathIndex++; - // TODO: VIEWURL IS NOT WORKING, HAVE CHANGED INDEX + VPATH ORDER - - const keysToClean = ['_*', 'parent', 'isVirtualTree', 'viewUrl', 'children']; - const newChild = this.cleanObj(node, keysToClean); - + const keysToClean = ['_*', 'parent', 'isVirtualTree', 'children']; + const newChild = GenericHelpers.cleanObject(node, keysToClean); Object.assign(newChild, { - // _prevSegment: nodeNamesInCurrentPath[0], // just for debugging - pathSegment: ':virtualSegment', - label: ':virtualSegment', - viewUrl: node.virtualViewUrl.replace(':virtualPath', vPath), + pathSegment: ':virtualSegment_' + _virtualPathIndex, + label: ':virtualSegment_' + _virtualPathIndex, + viewUrl: this.buildVirtualViewUrl( + node.virtualViewUrl, + pathParams, + _virtualPathIndex + ), _isVirtualTree: true, - _virtualPath: vPath, - _virtualPathNames, _virtualPathIndex }); - // override .children with a represence of the current node node.children = [newChild]; - return _virtualPathNames; } } diff --git a/core/src/utilities/helpers/generic-helpers.js b/core/src/utilities/helpers/generic-helpers.js index 4da695492c..9b89a432a6 100644 --- a/core/src/utilities/helpers/generic-helpers.js +++ b/core/src/utilities/helpers/generic-helpers.js @@ -262,6 +262,33 @@ class GenericHelpersClass { input ); } + + /** + * Returns a new Object with the same object, + * without the keys that were given. + * References still stay. + * Allows wildcard ending keys + * + * @param {Object} input any given object + * @param {Array} of keys, allows also wildcards at the end, like: _* + */ + cleanObject(input, keys) { + const res = {}; + for (const key in input) { + if (input.hasOwnProperty(key)) { + const noFullMatch = keys.filter(k => key.includes(k)).length === 0; + const noPartialMatch = + keys + .filter(k => k.endsWith('*')) + .map(k => k.slice(0, -1)) + .filter(k => key.startsWith(k)).length === 0; + if (noFullMatch && noPartialMatch) { + res[key] = input[key]; + } + } + } + return res; + } } export const GenericHelpers = new GenericHelpersClass(); From a7245923b214d2a5d6835ee73a65d58cabc1a29d Mon Sep 17 00:00:00 2001 From: Markus Edenhauser <1720843+maxmarkus@users.noreply.github.com> Date: Mon, 27 Jan 2020 14:17:24 +0100 Subject: [PATCH 03/18] added unit tets --- core/src/navigation/services/navigation.js | 27 +++- core/src/utilities/helpers/generic-helpers.js | 8 +- core/test/services/navigation.spec.js | 146 +++++++++++++++++- .../utilities/helpers/generic-helpers.spec.js | 15 ++ 4 files changed, 187 insertions(+), 9 deletions(-) diff --git a/core/src/navigation/services/navigation.js b/core/src/navigation/services/navigation.js index ce6ac5e86f..7a69fe007f 100644 --- a/core/src/navigation/services/navigation.js +++ b/core/src/navigation/services/navigation.js @@ -233,8 +233,17 @@ class NavigationClass { return result; } + /** + * Requires str to include :virtualPath + * and pathParams consist of :virtualSegment_N + * for deep nested virtual tree building + * + * @param {string} str + * @param {Object} pathParams + * @param {number} _virtualPathIndex + */ buildVirtualViewUrl(str, pathParams, _virtualPathIndex) { - let newStr = '/'; + let newStr = ''; for (const key in pathParams) { if (key.startsWith('virtualSegment')) { newStr += ':' + key + '/'; @@ -251,14 +260,22 @@ class NavigationClass { (isVirtualTreeRoot || isVirtualTreeChild) && nodeNamesInCurrentPath[0] ) { + // Check requirements + if (isVirtualTreeRoot && !node.virtualViewUrl) { + console.error( + '[ERROR] node is declared as virtual tree, but no virtualViewUrl parameter found in node.', + node + ); + return; + } + // Temporary store values that will be cleaned up when creating a copy let _virtualPathIndex = node._virtualPathIndex; if (isVirtualTreeRoot) { _virtualPathIndex = 0; } - // In case of defined virtualTree, when it got directly accessed - // Or when someone tries to target a to long url - // Or if end of indexes reached + + // Allowing maximum of 50 path segments to avoid memory issues const maxPathDepth = 50; if (_virtualPathIndex > maxPathDepth) { return; @@ -266,7 +283,7 @@ class NavigationClass { _virtualPathIndex++; const keysToClean = ['_*', 'parent', 'isVirtualTree', 'children']; - const newChild = GenericHelpers.cleanObject(node, keysToClean); + const newChild = GenericHelpers.removeProperties(node, keysToClean); Object.assign(newChild, { pathSegment: ':virtualSegment_' + _virtualPathIndex, label: ':virtualSegment_' + _virtualPathIndex, diff --git a/core/src/utilities/helpers/generic-helpers.js b/core/src/utilities/helpers/generic-helpers.js index 9b89a432a6..95c72b08ea 100644 --- a/core/src/utilities/helpers/generic-helpers.js +++ b/core/src/utilities/helpers/generic-helpers.js @@ -272,8 +272,14 @@ class GenericHelpersClass { * @param {Object} input any given object * @param {Array} of keys, allows also wildcards at the end, like: _* */ - cleanObject(input, keys) { + removeProperties(input, keys) { const res = {}; + if (!keys instanceof Array || !keys.length) { + console.error( + '[ERROR] removeProperties requires second parameter: array of keys to remove from object.' + ); + return input; + } for (const key in input) { if (input.hasOwnProperty(key)) { const noFullMatch = keys.filter(k => key.includes(k)).length === 0; diff --git a/core/test/services/navigation.spec.js b/core/test/services/navigation.spec.js index 9367d04be7..8b9a0c911e 100644 --- a/core/test/services/navigation.spec.js +++ b/core/test/services/navigation.spec.js @@ -71,11 +71,16 @@ describe('Navigation', function() { beforeEach(() => { Navigation._rootNodeProviderUsed = undefined; Navigation.rootNode = undefined; + console.warn = sinon.spy(); + console.error = sinon.spy(); + console.warn.resetHistory(); + console.error.resetHistory(); }); afterEach(() => { // reset LuigiConfig.config = {}; sinon.restore(); + sinon.reset(); }); describe('getNavigationPath', function() { it('should not fail for undefined arguments', () => { @@ -434,9 +439,6 @@ describe('Navigation', function() { ] }); - console.warn = sinon.spy(); - console.error = sinon.spy(); - // truthy tests // when const resStaticOk = Navigation.findMatchingNode('other', [staticNode()]); @@ -914,4 +916,142 @@ describe('Navigation', function() { assert.deepEqual(result, expected); }); }); + describe('buildVirtualViewUrl', () => { + it('returns same if :virtualPath is not defined', () => { + const given = 'https://mf.luigi-project.io'; + assert.equal(Navigation.buildVirtualViewUrl(given), given); + }); + it('returns valid substituted string without proper pathParams', () => { + const mock = { + url: 'https://mf.luigi-project.io#!/:virtualPath', + pathParams: { + otherParam: 'foo' + }, + index: 1 + }; + const expected = 'https://mf.luigi-project.io#!/:virtualSegment_1/'; + + assert.equal( + Navigation.buildVirtualViewUrl(mock.url, mock.pathParams, mock.index), + expected + ); + }); + it('returns valid substituted string with pathParams', () => { + const mock = { + url: 'https://mf.luigi-project.io#!/:virtualPath', + pathParams: { + otherParam: 'foo', + virtualSegment_1: 'one', + virtualSegment_2: 'two' + }, + index: 3 + }; + const expected = + 'https://mf.luigi-project.io#!/:virtualSegment_1/:virtualSegment_2/:virtualSegment_3/'; + + assert.equal( + Navigation.buildVirtualViewUrl(mock.url, mock.pathParams, mock.index), + expected + ); + }); + }); + describe('buildVirtualTree', () => { + it('unchanged node if not a virtual tree root', () => { + const given = { + label: 'Luigi' + }; + const expected = Object.assign({}, given); + + Navigation.buildVirtualTree(given); + + assert.deepEqual(given, expected); + }); + it('unchanged if directly accessing a node which is defined as virtual tree root', () => { + const mockNode = { + label: 'Luigi', + isVirtualTree: true, + virtualViewUrl: 'foo' + }; + const mockNodeNames = []; // no further child segments + + const expected = Object.assign({}, mockNode); + + Navigation.buildVirtualTree(mockNode, mockNodeNames); + + assert.deepEqual(mockNode, expected); + }); + it('throws and error if virtualViewUrl is undefined', () => { + const mockNode = { + label: 'Luigi', + isVirtualTree: true + }; + const expected = Object.assign({}, mockNode); + + Navigation.buildVirtualTree(mockNode, ['one']); + + sinon.assert.calledOnce(console.error); + assert.deepEqual(mockNode, expected); + }); + it('with first virtual tree segment', () => { + const mockNode = { + label: 'Luigi', + isVirtualTree: true, + virtualViewUrl: 'http://mf.luigi-project.io/:virtualPath' + }; + const mockNodeNames = ['foo']; + + const expected = Object.assign({}, mockNode, { + children: [ + { + _isVirtualTree: true, + _virtualPathIndex: 1, + label: ':virtualSegment_1', + pathSegment: ':virtualSegment_1', + viewUrl: 'http://mf.luigi-project.io/:virtualSegment_1/', + virtualViewUrl: 'http://mf.luigi-project.io/:virtualPath' + } + ] + }); + + Navigation.buildVirtualTree(mockNode, mockNodeNames); + + assert.deepEqual(mockNode, expected); + }); + it('with a deep nested virtual tree segment', () => { + const mockNode = { + _isVirtualTree: true, + _virtualPathIndex: 3, + label: ':virtualSegment_3', + pathSegment: ':virtualSegment_3', + viewUrl: + 'http://mf.luigi-project.io/:virtualSegment_2/:virtualSegment_3/', + virtualViewUrl: 'http://mf.luigi-project.io/:virtualPath' + }; + const mockNodeNames = ['foo']; + const pathParams = { + otherParam: 'foo', + virtualSegment_1: 'one', + virtualSegment_2: 'two', + virtualSegment_3: 'three' + }; + + const expected = Object.assign({}, mockNode, { + children: [ + { + _isVirtualTree: true, + _virtualPathIndex: 4, + label: ':virtualSegment_4', + pathSegment: ':virtualSegment_4', + viewUrl: + 'http://mf.luigi-project.io/:virtualSegment_1/:virtualSegment_2/:virtualSegment_3/:virtualSegment_4/', + virtualViewUrl: 'http://mf.luigi-project.io/:virtualPath' + } + ] + }); + + Navigation.buildVirtualTree(mockNode, mockNodeNames, pathParams); + + assert.deepEqual(mockNode, expected); + }); + }); }); diff --git a/core/test/utilities/helpers/generic-helpers.spec.js b/core/test/utilities/helpers/generic-helpers.spec.js index f3a70cd762..ff984aab2e 100644 --- a/core/test/utilities/helpers/generic-helpers.spec.js +++ b/core/test/utilities/helpers/generic-helpers.spec.js @@ -77,4 +77,19 @@ describe('Generic-helpers', () => { }; assert.deepEqual(GenericHelpers.removeInternalProperties(input), expected); }); + it('removeProperties', () => { + const input = { + some: true, + value: true, + _internal: true, + _somefn: () => true, + internalOne: true, + internalTwo: true + }; + const keys = ['_*', 'value', 'internal*']; + const expected = { + some: true + }; + assert.deepEqual(GenericHelpers.removeProperties(input, keys), expected); + }); }); From 18f21a0b5c801908f669494b0a977a43a48968fd Mon Sep 17 00:00:00 2001 From: Markus Edenhauser <1720843+maxmarkus@users.noreply.github.com> Date: Thu, 30 Jan 2020 18:22:51 +0100 Subject: [PATCH 04/18] refactored to simple virtualTree setting, docu updates --- core/src/navigation/services/navigation.js | 35 +++++++++++++--------- docs/navigation-parameters-reference.md | 4 +++ 2 files changed, 25 insertions(+), 14 deletions(-) diff --git a/core/src/navigation/services/navigation.js b/core/src/navigation/services/navigation.js index 7a69fe007f..acd8fc38f7 100644 --- a/core/src/navigation/services/navigation.js +++ b/core/src/navigation/services/navigation.js @@ -250,20 +250,18 @@ class NavigationClass { } } newStr += ':virtualSegment_' + _virtualPathIndex + '/'; - return str.replace(':virtualPath', newStr); + return str + '/' + newStr; } buildVirtualTree(node, nodeNamesInCurrentPath, pathParams) { - const isVirtualTreeRoot = node.isVirtualTree; - const isVirtualTreeChild = node._isVirtualTree; - if ( - (isVirtualTreeRoot || isVirtualTreeChild) && - nodeNamesInCurrentPath[0] - ) { + const virtualTreeRoot = node.virtualTree; + const virtualTreeChild = node._virtualTree; + const _virtualViewUrl = node._virtualViewUrl || node.viewUrl; + if ((virtualTreeRoot || virtualTreeChild) && nodeNamesInCurrentPath[0]) { // Check requirements - if (isVirtualTreeRoot && !node.virtualViewUrl) { + if (virtualTreeRoot && !_virtualViewUrl) { console.error( - '[ERROR] node is declared as virtual tree, but no virtualViewUrl parameter found in node.', + '[ERROR] node is declared as virtual tree, but no _virtualViewUrl parameter found in node.', node ); return; @@ -271,8 +269,9 @@ class NavigationClass { // Temporary store values that will be cleaned up when creating a copy let _virtualPathIndex = node._virtualPathIndex; - if (isVirtualTreeRoot) { + if (virtualTreeRoot) { _virtualPathIndex = 0; + node.keepSelectedForChildren = true; } // Allowing maximum of 50 path segments to avoid memory issues @@ -282,18 +281,26 @@ class NavigationClass { } _virtualPathIndex++; - const keysToClean = ['_*', 'parent', 'isVirtualTree', 'children']; + const keysToClean = [ + '_*', + 'virtualTree', + 'parent', + 'children', + 'keepSelectedForChildren', + 'navigationContext' + ]; const newChild = GenericHelpers.removeProperties(node, keysToClean); Object.assign(newChild, { pathSegment: ':virtualSegment_' + _virtualPathIndex, label: ':virtualSegment_' + _virtualPathIndex, viewUrl: this.buildVirtualViewUrl( - node.virtualViewUrl, + _virtualViewUrl, pathParams, _virtualPathIndex ), - _isVirtualTree: true, - _virtualPathIndex + _virtualTree: true, + _virtualPathIndex, + _virtualViewUrl }); node.children = [newChild]; diff --git a/docs/navigation-parameters-reference.md b/docs/navigation-parameters-reference.md index 279cac6486..939b2e58b9 100644 --- a/docs/navigation-parameters-reference.md +++ b/docs/navigation-parameters-reference.md @@ -243,6 +243,10 @@ settings: { - **type**: boolean or "exclusive" - **description**: when set to `true`, the node is always accessible. When set to `exclusive`, the node is only visible in logged-out state. Requires **auth.disableAutoLogin** to be set to `true`. **anonymousAccess** needs to be defined both on parent and child nodes. +### virtualTree +- **type**: boolean +- **description**: marks the node as the beginning of a virtual tree. Allows navigation to any of its child path without the need of specifying nested children. **keepSelectedForChildren** is automatically applied. + ## Context switcher The context switcher is a drop-down list available in the top navigation bar. It allows you to switch between a curated list of navigation elements such as Environments. To do so, add the **contextSwitcher** parameter to the **navigation** object using the following optional parameters: From c000e35f6c07b9165553a588a9f2c22d63b89ab9 Mon Sep 17 00:00:00 2001 From: Markus Edenhauser <1720843+maxmarkus@users.noreply.github.com> Date: Thu, 13 Feb 2020 16:48:09 +0100 Subject: [PATCH 05/18] semi-working intermediate state --- client/public/luigi-client.d.ts | 11 +++ client/src/lifecycleManager.js | 119 +++++++++++++++++++++++++- client/src/linkManager.js | 16 +++- core/src/App.html | 45 +++++++--- core/test/services/navigation.spec.js | 40 +++------ docs/luigi-client-api.md | 31 +++++++ website/docs/package-lock.json | 41 ++++++--- 7 files changed, 252 insertions(+), 51 deletions(-) diff --git a/client/public/luigi-client.d.ts b/client/public/luigi-client.d.ts index 011c81236e..bd93d06cf8 100644 --- a/client/public/luigi-client.d.ts +++ b/client/public/luigi-client.d.ts @@ -461,3 +461,14 @@ export type linkManager = () => LinkManager; /** @name uxManager */ export function uxManager(): UxManager; export type uxManager = () => UxManager; + +/** + * Use the LifeCycle Manager to manage the appearance features in Luigi. + */ +/** @name lifecycleManager */ +export function lifecycleManager(): LifecycleManager; +export type lifecycleManager = () => LifecycleManager; + +export declare interface LifecycleManager { + setNavigationSync: (config: object) => void; +} diff --git a/client/src/lifecycleManager.js b/client/src/lifecycleManager.js index 2e9e511e4c..1313bb0367 100644 --- a/client/src/lifecycleManager.js +++ b/client/src/lifecycleManager.js @@ -1,6 +1,6 @@ import { LuigiClientBase } from './baseClass'; import { helpers } from './helpers'; - +import { linkManager } from './linkManager'; /** * Use the functions and parameters to define the Lifecycle of listeners, navigation nodes, and Event data. * @name Lifecycle @@ -27,7 +27,19 @@ class LifecycleManager extends LuigiClientBase { this._onInactiveFns = {}; this._onInitFns = {}; this.authData = {}; - + + /** + * Virtual Tree Nav related vars + * @private + */ + this._navigationSyncDefaults = { + active: true, + useHashRouting: false, + useClosestContext: false, + localBasePath: null + } + this.navigationSync = {}; + this.originalHistory = {}; /** * Adds event listener for communication with Luigi Core and starts communication * @private @@ -80,7 +92,7 @@ class LifecycleManager extends LuigiClientBase { helpers.addEventListener('luigi.navigate', e => { setContext(e.data); - if (!this.currentContext.internal.isNavigateBack) { + if (!this.currentContext.internal.isNavigateBack && !this.navigationSync.active) { history.replaceState(null, '', e.data.viewUrl); window.dispatchEvent( new PopStateEvent('popstate', { state: 'luiginavigation' }) @@ -101,6 +113,7 @@ class LifecycleManager extends LuigiClientBase { '*' ); this._tpcCheck(); + this._initNavigationSync(); }; luigiClientInit(); @@ -396,5 +409,105 @@ class LifecycleManager extends LuigiClientBase { ); helpers.sendPostMessageToLuigiCore(customMessageInternal); } + + /** + * Configures automatic routing synchronization + * Allows the use of a micro frontend router as main navigation strategy, which implicitely updates the Luigi Core URL. + * Mostly used in combination with **virtualTree** node configuration, which allows to simply drop-in a micro-frontend under a specified navigation tree. + * // TODO: skipEvaluation: true + * @param {Object} config Configuration object + * @param {string} [config.active=true] enables or disables routing synchronization + * @param {string} [config.useHashRouting=false] defines the configured routing strategy of the micro frontend. If not set, path routing is assumed. + * @param {string} [config.useClosestContext=false] when set to true, **fromClosestContext()** will be used. Set **navigationContext** at the node where virtualTree is defined and enable this value. + * @param {string} [config.localBasePath] defines + * @returns {Promise} gets resolved when congigVirtualTreeNav got applied and can be used. This is required since the configuration needs to wait for the successful client initialization + * @example + * import LuigiClient from '@kyma-project/luigi-client'; + * LuigiClient.lifecycleManager().setNavigationSync({ useHashRouting: false, useClosestContext: true }); + * LuigiClient.lifecycleManager().setNavigationSync({ active: true, useHashRouting: false, useClosestContext: true }); + * Disable: + * LuigiClient.lifecycleManager().setNavigationSync({active: false}); + * @memberof Lifecycle + * @since 0.7.4 + */ + setNavigationSync(config) { + this.navigationSync = Object.assign({}, this._navigationSyncDefaults, config); + } + + /** + * @private + * @since 0.7.4 + */ + _initNavigationSync() { + if(this._navigationSyncInitalized) { + return; + } + + this._navigationSyncInitalized = true; + this.addInitListener(() => { + const linkManagerInstance = new linkManager({ + currentContext: this.currentContext + }); + const navigateTo = function(rawPath) { + let path = rawPath.startsWith('#') ? rawPath.slice(1) : rawPath; + + if (this.navigationSync.localBasePath && path.startsWith(this.navigationSync.localBasePath)) { + path = path.replace(this.navigationSync.localBasePath, ''); + } + + if(this.navigationSync.useClosestContext) { + linkManagerInstance.withoutSync().fromClosestContext().navigate(path); + } else { + linkManagerInstance.withoutSync().navigate(path); + } + }.bind(this); + + if (this.navigationSync.listenerId) { + helpers.removeEventListener(this.navigationSync.listenerId); + } + const isNavigationSyncActive = () => { + return this.navigationSync.active; + } + + if (this.navigationSync.active) { + const routingEventName = this.navigationSync.useHashRouting ? 'hashchange' : 'popstate'; + const isHashRouting = routingEventName === 'hashchange'; + this.navigationSync.listenerId = helpers.addEventListener(routingEventName, (e) => { + console.log('url change', e); + }); + this.navigationSync.listenerId = helpers.addEventListener('popstate', (e) => { + console.log('url popstate', e); + }); + + // monkeypatch history api to listen to router changes + ["pushState", "replaceState"].map(type => { + // keep a "real" original history api in the reference, it is required if + // setNavigationSync is called multiple times + const original = this.originalHistory[type] || history[type]; + this.originalHistory[type] = original; + + history[type] = function() { + const result = original.apply(this, arguments); + const event = new Event(type); + event.arguments = arguments; + + if(isNavigationSyncActive()) { + console.log('isNavigationSyncActive'); + if(isHashRouting) { + const hash = arguments[2]; + console.log('update prarent hash route to', hash, event.arguments); + navigateTo(hash); + } else { + navigateTo(arguments[1]); // TODO: Check validity + } + } + + dispatchEvent(event); + return result; + }; + }); + } + }) + } } export const lifecycleManager = new LifecycleManager(); diff --git a/client/src/linkManager.js b/client/src/linkManager.js index 24a67c962d..7115a82e53 100644 --- a/client/src/linkManager.js +++ b/client/src/linkManager.js @@ -14,7 +14,6 @@ export class linkManager extends LuigiClientBase { * @private */ constructor(values) { - // @param {object} values TODO: is it necessary at all, where is it used? super(); Object.assign(this, values); @@ -117,6 +116,7 @@ export class linkManager extends LuigiClientBase { */ fromContext(navigationContext) { const navigationContextInParent = + this.currentContext.context.parentNavigationContexts && this.currentContext.context.parentNavigationContexts.indexOf( navigationContext ) !== -1; @@ -142,6 +142,7 @@ export class linkManager extends LuigiClientBase { */ fromClosestContext() { const hasParentNavigationContext = + this.currentContext && this.currentContext.context.parentNavigationContexts.length > 0; if (hasParentNavigationContext) { this.options.fromContext = null; @@ -251,4 +252,17 @@ export class linkManager extends LuigiClientBase { goBackContext: goBackValue && JSON.stringify(goBackValue) }); } + + /** + * Disables the navigation handling for this specific request + * Used in **lifecycleManager().setNavigationSync** to prevent + * Luigi Core from handling url change after navigate. + * @private + * @example + * LuigiClient.linkManager().withoutSync().navigate('/projects/xy/foobar'); + */ + withoutSync() { + this.options.withoutSync = true; + return this; + } } diff --git a/core/src/App.html b/core/src/App.html index ab8ba6bc6b..66cf17dc7c 100644 --- a/core/src/App.html +++ b/core/src/App.html @@ -131,6 +131,7 @@ let viewGroup; let isolateView; let previousNodeValues; + let isVirtualTree = false; let isNavigateBack = false; let goBackContext; let navigationPath; @@ -147,19 +148,24 @@ let thirdPartyCookiesCheck; const prepareInternalData = config => { + const iframeConf = config.iframe.luigi; const internalData = { - isNavigateBack: isNavigateBack, + isVirtualTree, + isNavigateBack, viewStackSize: preservedViews.length, currentLocale: LuigiI18N.getCurrentLocale(), - clientPermissions: config.iframe.luigi.nextViewUrl - ? config.iframe.luigi.nextClientPermissions - : config.iframe.luigi.clientPermissions + clientPermissions: iframeConf.nextViewUrl + ? iframeConf.nextClientPermissions + : iframeConf.clientPermissions }; + IframeHelpers.specialIframeTypes .map(o => o.iframeConfigKey) .forEach(key => { internalData[key] = config[key] || false; }); + + console.log(JSON.stringify(internalData, null, 2)); return internalData; }; @@ -168,6 +174,7 @@ console.info('iframe does not exist, not able to send context.'); return; } + const message = { msg: 'luigi.init', context: JSON.stringify( @@ -182,6 +189,16 @@ internal: JSON.stringify(prepareInternalData(config)), authData: AuthHelpers.getStoredAuthData() }; + console.log( + 'currentNode', + currentNode.isVirtualTree, + currentNode._isVirtualTree, + currentNode, + (config.iframe.luigi.currentNode || {}).isVirtualTree, + (config.iframe.luigi.currentNode || {})._isVirtualTree, + 'int', + prepareInternalData(config) + ); IframeHelpers.sendMessageToIframe(config.iframe, message); }; @@ -213,7 +230,10 @@ path = GenericHelpers.addLeadingSlash(path); addPreserveView(data, config); - Routing.navigateTo(path); //navigate to the raw path. Any errors/alerts are handled later + + if (!data.params.skipRouting) { + Routing.navigateTo(path); //navigate to the raw path. Any errors/alerts are handled later + } }; const removeQueryParams = str => str.split('?')[0]; @@ -279,7 +299,8 @@ showLoadingIndicator, tabNav, isNavigateBack, - goBackContext + goBackContext, + isVirtualTree }; }, set: obj => { @@ -323,6 +344,8 @@ isNavigateBack = obj.isNavigateBack; } else if (prop === 'goBackContext') { goBackContext = obj.goBackContext; + } else if (prop === 'isVirtualTree') { + isVirtualTree = obj.isVirtualTree; } }); } @@ -506,9 +529,7 @@ getAlertWithId(currentAlerts, settings.id) ) { console.error( - `The alert with id '${ - settings.id - }' already exists in a queue, therefore it won't be displayed ` + `The alert with id '${settings.id}' already exists in a queue, therefore it won't be displayed ` ); return Promise.reject(); } @@ -835,7 +856,11 @@ openSplitView(path, e.data.params.splitView); } else { getUnsavedChangesModalPromise().then(() => { - handleNavigation(e.data, config); + const isNavigationSyncDisabled = !e.data.params.withoutSync; + if (isNavigationSyncDisabled) { + console.log('handleNavigation', e.data); + handleNavigation(e.data, config); + } closeModal(); closeSplitView(); }); diff --git a/core/test/services/navigation.spec.js b/core/test/services/navigation.spec.js index 8b9a0c911e..29cdd24e7c 100644 --- a/core/test/services/navigation.spec.js +++ b/core/test/services/navigation.spec.js @@ -917,13 +917,13 @@ describe('Navigation', function() { }); }); describe('buildVirtualViewUrl', () => { - it('returns same if :virtualPath is not defined', () => { + it('returns same if virtualTree is not defined', () => { const given = 'https://mf.luigi-project.io'; assert.equal(Navigation.buildVirtualViewUrl(given), given); }); it('returns valid substituted string without proper pathParams', () => { const mock = { - url: 'https://mf.luigi-project.io#!/:virtualPath', + url: 'https://mf.luigi-project.io#!', pathParams: { otherParam: 'foo' }, @@ -938,7 +938,7 @@ describe('Navigation', function() { }); it('returns valid substituted string with pathParams', () => { const mock = { - url: 'https://mf.luigi-project.io#!/:virtualPath', + url: 'https://mf.luigi-project.io#!/x', pathParams: { otherParam: 'foo', virtualSegment_1: 'one', @@ -947,7 +947,7 @@ describe('Navigation', function() { index: 3 }; const expected = - 'https://mf.luigi-project.io#!/:virtualSegment_1/:virtualSegment_2/:virtualSegment_3/'; + 'https://mf.luigi-project.io#!/x/:virtualSegment_1/:virtualSegment_2/:virtualSegment_3/'; assert.equal( Navigation.buildVirtualViewUrl(mock.url, mock.pathParams, mock.index), @@ -969,8 +969,8 @@ describe('Navigation', function() { it('unchanged if directly accessing a node which is defined as virtual tree root', () => { const mockNode = { label: 'Luigi', - isVirtualTree: true, - virtualViewUrl: 'foo' + virtualTree: true, + viewUrl: 'foo' }; const mockNodeNames = []; // no further child segments @@ -980,35 +980,23 @@ describe('Navigation', function() { assert.deepEqual(mockNode, expected); }); - it('throws and error if virtualViewUrl is undefined', () => { - const mockNode = { - label: 'Luigi', - isVirtualTree: true - }; - const expected = Object.assign({}, mockNode); - - Navigation.buildVirtualTree(mockNode, ['one']); - - sinon.assert.calledOnce(console.error); - assert.deepEqual(mockNode, expected); - }); it('with first virtual tree segment', () => { const mockNode = { label: 'Luigi', - isVirtualTree: true, - virtualViewUrl: 'http://mf.luigi-project.io/:virtualPath' + virtualTree: true, + _virtualViewUrl: 'http://mf.luigi-project.io/:virtualPath' }; const mockNodeNames = ['foo']; const expected = Object.assign({}, mockNode, { children: [ { - _isVirtualTree: true, + _virtualTree: true, _virtualPathIndex: 1, label: ':virtualSegment_1', pathSegment: ':virtualSegment_1', viewUrl: 'http://mf.luigi-project.io/:virtualSegment_1/', - virtualViewUrl: 'http://mf.luigi-project.io/:virtualPath' + _virtualViewUrl: 'http://mf.luigi-project.io' } ] }); @@ -1019,13 +1007,13 @@ describe('Navigation', function() { }); it('with a deep nested virtual tree segment', () => { const mockNode = { - _isVirtualTree: true, + _virtualTree: true, _virtualPathIndex: 3, label: ':virtualSegment_3', pathSegment: ':virtualSegment_3', viewUrl: 'http://mf.luigi-project.io/:virtualSegment_2/:virtualSegment_3/', - virtualViewUrl: 'http://mf.luigi-project.io/:virtualPath' + _virtualViewUrl: 'http://mf.luigi-project.io' }; const mockNodeNames = ['foo']; const pathParams = { @@ -1038,13 +1026,13 @@ describe('Navigation', function() { const expected = Object.assign({}, mockNode, { children: [ { - _isVirtualTree: true, + _virtualTree: true, _virtualPathIndex: 4, label: ':virtualSegment_4', pathSegment: ':virtualSegment_4', viewUrl: 'http://mf.luigi-project.io/:virtualSegment_1/:virtualSegment_2/:virtualSegment_3/:virtualSegment_4/', - virtualViewUrl: 'http://mf.luigi-project.io/:virtualPath' + _virtualViewUrl: 'http://mf.luigi-project.io' } ] }); diff --git a/docs/luigi-client-api.md b/docs/luigi-client-api.md index 4c218615fc..f1ff2d6bf7 100644 --- a/docs/luigi-client-api.md +++ b/docs/luigi-client-api.md @@ -180,6 +180,37 @@ LuigiClient.sendCustomMessage({id: 'environment.created', production: false}) - **since**: 0.6.2 +### setNavigationSync + +Configures automatic routing synchronization +Allows the use of a micro frontend router as main navigation strategy, which implicitely updates the Luigi Core URL. +Mostly used in combination with **virtualTree** node configuration, which allows to simply drop-in a micro-frontend under a specified navigation tree. +// TODO: skipEvaluation: true + +#### Parameters + +- `config` **[Object](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object)** Configuration object + - `config.active` **[string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)** enables or disables routing synchronization (optional, default `true`) + - `config.useHashRouting` **[string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)** defines the configured routing strategy of the micro frontend. If not set, path routing is assumed. (optional, default `false`) + - `config.useClosestContext` **[string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)** when set to true, **fromClosestContext()** will be used. Set **navigationContext** at the node where virtualTree is defined and enable this value. (optional, default `false`) + - `config.localBasePath` **[string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)?** defines + +#### Examples + +```javascript +import LuigiClient from '@kyma-project/luigi-client'; +LuigiClient.lifecycleManager().setNavigationSync({ useHashRouting: false, useClosestContext: true }); +LuigiClient.lifecycleManager().setNavigationSync({ active: true, useHashRouting: false, useClosestContext: true }); +Disable: +LuigiClient.lifecycleManager().setNavigationSync({active: false}); +``` + +Returns **[Promise](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Promise)** gets resolved when congigVirtualTreeNav got applied and can be used. This is required since the configuration needs to wait for the successful client initialization + +**Meta** + +- **since**: 0.7.4 + ## Lifecycle~initListenerCallback Callback of the addInitListener diff --git a/website/docs/package-lock.json b/website/docs/package-lock.json index bbf8ba1c2e..7a163d64a0 100644 --- a/website/docs/package-lock.json +++ b/website/docs/package-lock.json @@ -3203,7 +3203,8 @@ "ansi-regex": { "version": "2.1.1", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "aproba": { "version": "1.2.0", @@ -3224,12 +3225,14 @@ "balanced-match": { "version": "1.0.0", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "brace-expansion": { "version": "1.1.11", "bundled": true, "dev": true, + "optional": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -3244,17 +3247,20 @@ "code-point-at": { "version": "1.1.0", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "concat-map": { "version": "0.0.1", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "console-control-strings": { "version": "1.1.0", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "core-util-is": { "version": "1.0.2", @@ -3371,7 +3377,8 @@ "inherits": { "version": "2.0.3", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "ini": { "version": "1.3.5", @@ -3383,6 +3390,7 @@ "version": "1.0.0", "bundled": true, "dev": true, + "optional": true, "requires": { "number-is-nan": "^1.0.0" } @@ -3397,6 +3405,7 @@ "version": "3.0.4", "bundled": true, "dev": true, + "optional": true, "requires": { "brace-expansion": "^1.1.7" } @@ -3404,12 +3413,14 @@ "minimist": { "version": "0.0.8", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "minipass": { "version": "2.3.5", "bundled": true, "dev": true, + "optional": true, "requires": { "safe-buffer": "^5.1.2", "yallist": "^3.0.0" @@ -3428,6 +3439,7 @@ "version": "0.5.1", "bundled": true, "dev": true, + "optional": true, "requires": { "minimist": "0.0.8" } @@ -3508,7 +3520,8 @@ "number-is-nan": { "version": "1.0.1", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "object-assign": { "version": "4.1.1", @@ -3520,6 +3533,7 @@ "version": "1.4.0", "bundled": true, "dev": true, + "optional": true, "requires": { "wrappy": "1" } @@ -3605,7 +3619,8 @@ "safe-buffer": { "version": "5.1.2", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "safer-buffer": { "version": "2.1.2", @@ -3641,6 +3656,7 @@ "version": "1.0.2", "bundled": true, "dev": true, + "optional": true, "requires": { "code-point-at": "^1.0.0", "is-fullwidth-code-point": "^1.0.0", @@ -3660,6 +3676,7 @@ "version": "3.0.1", "bundled": true, "dev": true, + "optional": true, "requires": { "ansi-regex": "^2.0.0" } @@ -3703,12 +3720,14 @@ "wrappy": { "version": "1.0.2", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "yallist": { "version": "3.0.3", "bundled": true, - "dev": true + "dev": true, + "optional": true } } }, From 984fcf4581814744c4e925e9e9dc99e4f6eec78f Mon Sep 17 00:00:00 2001 From: Markus Edenhauser <1720843+maxmarkus@users.noreply.github.com> Date: Fri, 14 Feb 2020 10:55:19 +0100 Subject: [PATCH 06/18] skipping navigation on core side --- client/src/lifecycleManager.js | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/client/src/lifecycleManager.js b/client/src/lifecycleManager.js index 1313bb0367..38894a6440 100644 --- a/client/src/lifecycleManager.js +++ b/client/src/lifecycleManager.js @@ -40,6 +40,19 @@ class LifecycleManager extends LuigiClientBase { } this.navigationSync = {}; this.originalHistory = {}; + /** + * Virtual Tree Nav related vars + * @memberof Lifecycle + * @private + */ + this._navigationSyncDefaults = { + active: true, + useHashRouting: false, + useClosestContext: false, + localBasePath: null + }; + this.navigationSync = {}; + this.originalHistory = {}; /** * Adds event listener for communication with Luigi Core and starts communication * @private @@ -436,6 +449,7 @@ class LifecycleManager extends LuigiClientBase { /** * @private + * @memberof Lifecycle * @since 0.7.4 */ _initNavigationSync() { From 4ee8502773770c710c0758f305c3808a8b8daa02 Mon Sep 17 00:00:00 2001 From: Markus Edenhauser <1720843+maxmarkus@users.noreply.github.com> Date: Fri, 14 Feb 2020 13:36:55 +0100 Subject: [PATCH 07/18] example half way prepared --- client/package-lock.json | 91 +++++++++-- client/package.json | 3 + client/src/lifecycleManager.js | 93 ++++++----- client/src/linkManager.js | 2 +- .../src/app/app-routing.module.ts | 22 ++- .../app/project/dynamic/dynamic.component.css | 3 + .../project/dynamic/dynamic.component.html | 17 +- .../app/project/dynamic/dynamic.component.ts | 59 +++++-- .../src/assets/sampleexternal.html | 18 ++- .../src/luigi-config/extended/navigation.js | 145 ++---------------- .../luigi-config/extended/projectDetailNav.js | 11 ++ core/src/App.html | 36 ++--- 12 files changed, 274 insertions(+), 226 deletions(-) diff --git a/client/package-lock.json b/client/package-lock.json index f57d43dad1..d8308c6127 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -736,6 +736,14 @@ "semver": "^5.5.0" } }, + "@babel/runtime": { + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.8.4.tgz", + "integrity": "sha512-neAp3zt80trRVBI1x0azq6c57aNBqYZH8KhMm3TaB7wEI5Q4A2SHfBHE8w9gOhI/lrqxtEbXZgQIrHP+wvSGwQ==", + "requires": { + "regenerator-runtime": "^0.13.2" + } + }, "@babel/template": { "version": "7.4.4", "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.4.4.tgz", @@ -2573,7 +2581,8 @@ "ansi-regex": { "version": "2.1.1", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "aproba": { "version": "1.2.0", @@ -2594,12 +2603,14 @@ "balanced-match": { "version": "1.0.0", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "brace-expansion": { "version": "1.1.11", "bundled": true, "dev": true, + "optional": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -2614,17 +2625,20 @@ "code-point-at": { "version": "1.1.0", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "concat-map": { "version": "0.0.1", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "console-control-strings": { "version": "1.1.0", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "core-util-is": { "version": "1.0.2", @@ -2741,7 +2755,8 @@ "inherits": { "version": "2.0.3", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "ini": { "version": "1.3.5", @@ -2753,6 +2768,7 @@ "version": "1.0.0", "bundled": true, "dev": true, + "optional": true, "requires": { "number-is-nan": "^1.0.0" } @@ -2767,6 +2783,7 @@ "version": "3.0.4", "bundled": true, "dev": true, + "optional": true, "requires": { "brace-expansion": "^1.1.7" } @@ -2774,12 +2791,14 @@ "minimist": { "version": "0.0.8", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "minipass": { "version": "2.3.5", "bundled": true, "dev": true, + "optional": true, "requires": { "safe-buffer": "^5.1.2", "yallist": "^3.0.0" @@ -2798,6 +2817,7 @@ "version": "0.5.1", "bundled": true, "dev": true, + "optional": true, "requires": { "minimist": "0.0.8" } @@ -2878,7 +2898,8 @@ "number-is-nan": { "version": "1.0.1", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "object-assign": { "version": "4.1.1", @@ -2890,6 +2911,7 @@ "version": "1.4.0", "bundled": true, "dev": true, + "optional": true, "requires": { "wrappy": "1" } @@ -2975,7 +2997,8 @@ "safe-buffer": { "version": "5.1.2", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "safer-buffer": { "version": "2.1.2", @@ -3011,6 +3034,7 @@ "version": "1.0.2", "bundled": true, "dev": true, + "optional": true, "requires": { "code-point-at": "^1.0.0", "is-fullwidth-code-point": "^1.0.0", @@ -3030,6 +3054,7 @@ "version": "3.0.1", "bundled": true, "dev": true, + "optional": true, "requires": { "ansi-regex": "^2.0.0" } @@ -3073,12 +3098,14 @@ "wrappy": { "version": "1.0.2", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "yallist": { "version": "3.0.3", "bundled": true, - "dev": true + "dev": true, + "optional": true } } }, @@ -3354,6 +3381,19 @@ "minimalistic-assert": "^1.0.1" } }, + "history": { + "version": "4.10.1", + "resolved": "https://registry.npmjs.org/history/-/history-4.10.1.tgz", + "integrity": "sha512-36nwAD620w12kuzPAsyINPWJqlNbij+hpK1k9XRloDtym8mxzGYl2c17LnV6IAGB2Dmg4tEa7G7DlawS0+qjew==", + "requires": { + "@babel/runtime": "^7.1.2", + "loose-envify": "^1.2.0", + "resolve-pathname": "^3.0.0", + "tiny-invariant": "^1.0.2", + "tiny-warning": "^1.0.0", + "value-equal": "^1.0.1" + } + }, "hmac-drbg": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", @@ -3657,8 +3697,7 @@ "js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "dev": true + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" }, "jsesc": { "version": "2.5.2", @@ -3770,7 +3809,6 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", - "dev": true, "requires": { "js-tokens": "^3.0.0 || ^4.0.0" } @@ -8137,6 +8175,11 @@ "regenerate": "^1.4.0" } }, + "regenerator-runtime": { + "version": "0.13.3", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.3.tgz", + "integrity": "sha512-naKIZz2GQ8JWh///G7L3X6LaQUAMp2lvb1rvwwsURe/VXwD6VMfr+/1NuNw3ag8v2kY1aQ/go5SNn79O9JU7yw==" + }, "regenerator-transform": { "version": "0.13.4", "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.13.4.tgz", @@ -8263,6 +8306,11 @@ "integrity": "sha1-six699nWiBvItuZTM17rywoYh0g=", "dev": true }, + "resolve-pathname": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-pathname/-/resolve-pathname-3.0.0.tgz", + "integrity": "sha512-C7rARubxI8bXFNB/hqcp/4iUeIXJhJZvFPFPiSPRnhU5UPxzMFIl+2E6yY6c4k9giDJAhtV+enfA+G89N6Csng==" + }, "resolve-url": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz", @@ -8860,6 +8908,16 @@ "setimmediate": "^1.0.4" } }, + "tiny-invariant": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.1.0.tgz", + "integrity": "sha512-ytxQvrb1cPc9WBEI/HSeYYoGD0kWnGEOR8RY6KomWLBVhqz0RgTwVO9dLrGz7dC+nN9llyI7OKAgRq8Vq4ZBSw==" + }, + "tiny-warning": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/tiny-warning/-/tiny-warning-1.0.3.tgz", + "integrity": "sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==" + }, "to-arraybuffer": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/to-arraybuffer/-/to-arraybuffer-1.0.1.tgz", @@ -9150,6 +9208,11 @@ "spdx-expression-parse": "^3.0.0" } }, + "value-equal": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/value-equal/-/value-equal-1.0.1.tgz", + "integrity": "sha512-NOJ6JZCAWr0zlxZt+xqCHNTEKOsrks2HQd4MqhP1qy4z1SkbEP467eNx6TgDKXMvUOb+OENfJCZwM+16n7fRfw==" + }, "vm-browserify": { "version": "0.0.4", "resolved": "https://registry.npmjs.org/vm-browserify/-/vm-browserify-0.0.4.tgz", diff --git a/client/package.json b/client/package.json index 87d7b76722..ba9a76af7a 100644 --- a/client/package.json +++ b/client/package.json @@ -47,5 +47,8 @@ "bundlesize": "^0.17.0", "acorn": "^6.0.5", "serialize-javascript": ">=2.1.1" + }, + "dependencies": { + "history": "^4.10.1" } } diff --git a/client/src/lifecycleManager.js b/client/src/lifecycleManager.js index 38894a6440..7eb46c39a8 100644 --- a/client/src/lifecycleManager.js +++ b/client/src/lifecycleManager.js @@ -1,6 +1,9 @@ import { LuigiClientBase } from './baseClass'; import { helpers } from './helpers'; + import { linkManager } from './linkManager'; +// import { createBrowserHistory } from 'history'; + /** * Use the functions and parameters to define the Lifecycle of listeners, navigation nodes, and Event data. * @name Lifecycle @@ -27,19 +30,7 @@ class LifecycleManager extends LuigiClientBase { this._onInactiveFns = {}; this._onInitFns = {}; this.authData = {}; - - /** - * Virtual Tree Nav related vars - * @private - */ - this._navigationSyncDefaults = { - active: true, - useHashRouting: false, - useClosestContext: false, - localBasePath: null - } - this.navigationSync = {}; - this.originalHistory = {}; + /** * Virtual Tree Nav related vars * @memberof Lifecycle @@ -53,6 +44,7 @@ class LifecycleManager extends LuigiClientBase { }; this.navigationSync = {}; this.originalHistory = {}; + /** * Adds event listener for communication with Luigi Core and starts communication * @private @@ -105,7 +97,10 @@ class LifecycleManager extends LuigiClientBase { helpers.addEventListener('luigi.navigate', e => { setContext(e.data); - if (!this.currentContext.internal.isNavigateBack && !this.navigationSync.active) { + if ( + !this.currentContext.internal.isNavigateBack && + !this.navigationSync.active + ) { history.replaceState(null, '', e.data.viewUrl); window.dispatchEvent( new PopStateEvent('popstate', { state: 'luiginavigation' }) @@ -422,17 +417,17 @@ class LifecycleManager extends LuigiClientBase { ); helpers.sendPostMessageToLuigiCore(customMessageInternal); } - + /** * Configures automatic routing synchronization * Allows the use of a micro frontend router as main navigation strategy, which implicitely updates the Luigi Core URL. * Mostly used in combination with **virtualTree** node configuration, which allows to simply drop-in a micro-frontend under a specified navigation tree. - * // TODO: skipEvaluation: true + * // TODO: skipEvaluation: true * @param {Object} config Configuration object * @param {string} [config.active=true] enables or disables routing synchronization * @param {string} [config.useHashRouting=false] defines the configured routing strategy of the micro frontend. If not set, path routing is assumed. * @param {string} [config.useClosestContext=false] when set to true, **fromClosestContext()** will be used. Set **navigationContext** at the node where virtualTree is defined and enable this value. - * @param {string} [config.localBasePath] defines + * @param {string} [config.localBasePath] defines * @returns {Promise} gets resolved when congigVirtualTreeNav got applied and can be used. This is required since the configuration needs to wait for the successful client initialization * @example * import LuigiClient from '@kyma-project/luigi-client'; @@ -444,7 +439,11 @@ class LifecycleManager extends LuigiClientBase { * @since 0.7.4 */ setNavigationSync(config) { - this.navigationSync = Object.assign({}, this._navigationSyncDefaults, config); + this.navigationSync = Object.assign( + {}, + this._navigationSyncDefaults, + config + ); } /** @@ -453,9 +452,13 @@ class LifecycleManager extends LuigiClientBase { * @since 0.7.4 */ _initNavigationSync() { - if(this._navigationSyncInitalized) { + if (this._navigationSyncInitalized) { return; } + // const history = createBrowserHistory(); + // history.listen((location, action) => { + // console.log('history.listen', action, location.pathname, location.state, `The current URL is ${location.pathname}${location.search}${location.hash}`) + // }) this._navigationSyncInitalized = true; this.addInitListener(() => { @@ -465,14 +468,20 @@ class LifecycleManager extends LuigiClientBase { const navigateTo = function(rawPath) { let path = rawPath.startsWith('#') ? rawPath.slice(1) : rawPath; - if (this.navigationSync.localBasePath && path.startsWith(this.navigationSync.localBasePath)) { + if ( + this.navigationSync.localBasePath && + path.startsWith(this.navigationSync.localBasePath) + ) { path = path.replace(this.navigationSync.localBasePath, ''); } - if(this.navigationSync.useClosestContext) { - linkManagerInstance.withoutSync().fromClosestContext().navigate(path); + if (this.navigationSync.useClosestContext) { + linkManagerInstance + .withoutSync() + .fromClosestContext() + .navigate(path); } else { - linkManagerInstance.withoutSync().navigate(path); + linkManagerInstance.withoutSync().navigate(path); } }.bind(this); @@ -481,21 +490,29 @@ class LifecycleManager extends LuigiClientBase { } const isNavigationSyncActive = () => { return this.navigationSync.active; - } + }; if (this.navigationSync.active) { - const routingEventName = this.navigationSync.useHashRouting ? 'hashchange' : 'popstate'; + const routingEventName = this.navigationSync.useHashRouting + ? 'hashchange' + : 'popstate'; const isHashRouting = routingEventName === 'hashchange'; - this.navigationSync.listenerId = helpers.addEventListener(routingEventName, (e) => { - console.log('url change', e); - }); - this.navigationSync.listenerId = helpers.addEventListener('popstate', (e) => { - console.log('url popstate', e); - }); + this.navigationSync.listenerId = helpers.addEventListener( + routingEventName, + e => { + console.log('url change', e); + } + ); + this.navigationSync.listenerId = helpers.addEventListener( + 'popstate', + e => { + console.log('url popstate', e); + } + ); // monkeypatch history api to listen to router changes - ["pushState", "replaceState"].map(type => { - // keep a "real" original history api in the reference, it is required if + ['pushState', 'replaceState'].map(type => { + // keep a "real" original history api in the reference, it is required if // setNavigationSync is called multiple times const original = this.originalHistory[type] || history[type]; this.originalHistory[type] = original; @@ -504,14 +521,12 @@ class LifecycleManager extends LuigiClientBase { const result = original.apply(this, arguments); const event = new Event(type); event.arguments = arguments; - - if(isNavigationSyncActive()) { - console.log('isNavigationSyncActive'); - if(isHashRouting) { + if (isNavigationSyncActive()) { + if (isHashRouting) { const hash = arguments[2]; - console.log('update prarent hash route to', hash, event.arguments); navigateTo(hash); } else { + console.log('update prarent path route to', event.arguments); navigateTo(arguments[1]); // TODO: Check validity } } @@ -521,7 +536,7 @@ class LifecycleManager extends LuigiClientBase { }; }); } - }) + }); } } export const lifecycleManager = new LifecycleManager(); diff --git a/client/src/linkManager.js b/client/src/linkManager.js index 7115a82e53..0495261f59 100644 --- a/client/src/linkManager.js +++ b/client/src/linkManager.js @@ -69,7 +69,7 @@ export class linkManager extends LuigiClientBase { splitView: splitViewSettings }) }; - + console.log('navigationOpenMsg', navigationOpenMsg); helpers.sendPostMessageToLuigiCore(navigationOpenMsg); } diff --git a/core/examples/luigi-sample-angular/src/app/app-routing.module.ts b/core/examples/luigi-sample-angular/src/app/app-routing.module.ts index 69fdcec130..4dcdd6a4ef 100644 --- a/core/examples/luigi-sample-angular/src/app/app-routing.module.ts +++ b/core/examples/luigi-sample-angular/src/app/app-routing.module.ts @@ -70,10 +70,30 @@ const routes: Routes = [ path: 'projects/:projectId/dps/dps2', component: ChildNode2Component }, + { + path: 'projects/:projectId/dynamic', + component: DynamicComponent + }, { path: 'projects/:projectId/dynamic/:dynamicValue', component: DynamicComponent }, + { + path: 'projects/:projectId/dynamic/:dyn1/:dynamicValue', + component: DynamicComponent + }, + { + path: 'projects/:projectId/dynamic/:dyn1/:dyn2/:dynamicValue', + component: DynamicComponent + }, + { + path: 'projects/:projectId/dynamic/:dyn1/:dyn2/:dyn3/:dynamicValue', + component: DynamicComponent + }, + { + path: 'projects/:projectId/dynamic/:dyn1/:dyn2/:dyn3/:dyn4/:dynamicValue', + component: DynamicComponent + }, { path: 'create/:dynamicValue', component: DynamicComponent @@ -87,7 +107,7 @@ const routes: Routes = [ component: DynamicComponent }, { - path: 'environments/:ignoredDynamicValue/:dynamicValue', + path: 'environments/:dyn1/:dynamicValue', component: DynamicComponent } ]; diff --git a/core/examples/luigi-sample-angular/src/app/project/dynamic/dynamic.component.css b/core/examples/luigi-sample-angular/src/app/project/dynamic/dynamic.component.css index fdbbd03fad..fa896aa8a7 100644 --- a/core/examples/luigi-sample-angular/src/app/project/dynamic/dynamic.component.css +++ b/core/examples/luigi-sample-angular/src/app/project/dynamic/dynamic.component.css @@ -5,3 +5,6 @@ .sap-icon--navigation-left-arrow::before { font-size: 11px; } +.dynamic-component .fd-panel + .fd-panel { + margin-top: 20px; +} diff --git a/core/examples/luigi-sample-angular/src/app/project/dynamic/dynamic.component.html b/core/examples/luigi-sample-angular/src/app/project/dynamic/dynamic.component.html index 46accc1af2..d6843fcd31 100644 --- a/core/examples/luigi-sample-angular/src/app/project/dynamic/dynamic.component.html +++ b/core/examples/luigi-sample-angular/src/app/project/dynamic/dynamic.component.html @@ -1,7 +1,22 @@ -
+

{{ nodeLabel }}

+ +
+

Playground for Virtual Tree and navigation sync

+ + +
    +
  • +   + +
  • +
+
+
{{ nodeLabel }} content.
    diff --git a/core/examples/luigi-sample-angular/src/app/project/dynamic/dynamic.component.ts b/core/examples/luigi-sample-angular/src/app/project/dynamic/dynamic.component.ts index af3dcc920e..fb8ac81af2 100644 --- a/core/examples/luigi-sample-angular/src/app/project/dynamic/dynamic.component.ts +++ b/core/examples/luigi-sample-angular/src/app/project/dynamic/dynamic.component.ts @@ -1,17 +1,22 @@ +import { Router, ActivatedRoute } from '@angular/router'; import { Component, OnInit, ChangeDetectorRef, OnDestroy } from '@angular/core'; import { Subscription } from 'rxjs'; + import { getPathParams, getNodeParams, linkManager, + lifecycleManager, PathParams, NodeParams } from '@kyma-project/luigi-client'; + import { LuigiContextService, IContextMessage } from '../../services/luigi-context.service'; import { toTitleCase } from '../../services/helpers'; +import { ThrowStmt } from '@angular/compiler'; @Component({ selector: 'app-dynamic', @@ -26,17 +31,27 @@ export class DynamicComponent implements OnInit, OnDestroy { public hasBack: boolean; public nodeParams: NodeParams = null; public callbackValue = 'default value'; - private lcSubscription: Subscription; + + // routing playground + public routeUrl: string; + public mfBasePath = ''; + public showRouting = false; + + private lcSubscription: Subscription = new Subscription(); constructor( private luigiService: LuigiContextService, - private cdr: ChangeDetectorRef + private cdr: ChangeDetectorRef, + private router: Router, + private route: ActivatedRoute ) {} ngOnInit() { - this.lcSubscription = this.luigiService - .getContext() - .subscribe((ctx: IContextMessage) => { + this.route.url.subscribe(url => + console.log('url', url.map(u => u.path).join('/')) + ); + this.lcSubscription.add( + this.luigiService.getContext().subscribe((ctx: IContextMessage) => { if (!ctx.context) { console.warn( `To use this component properly, node configuration requires context.label to be defined. @@ -46,10 +61,23 @@ export class DynamicComponent implements OnInit, OnDestroy { } const lastPathParam = Object.values(getPathParams() || {}).pop(); + this.routeUrl = Object.values(getPathParams() || {}).join('/'); // We can directly access our specified context values here - this.nodeLabel = toTitleCase(ctx.context.label || lastPathParam); + this.nodeLabel = toTitleCase( + ctx.context.label || lastPathParam || 'hello' + ); this.links = ctx.context.links; + this.mfBasePath = ctx.context.mfBasePath; + this.showRouting = ctx.context.showRouting; + if (this.showRouting) { + lifecycleManager().setNavigationSync({ + active: true, + useHashRouting: true, + useClosestContext: true, + localBasePath: this.mfBasePath + }); + } // preserveView and node params this.hasBack = linkManager().hasBack(); @@ -59,13 +87,22 @@ export class DynamicComponent implements OnInit, OnDestroy { if (!this.cdr['destroyed']) { this.cdr.detectChanges(); } - }); + }) + ); + } + goToRoute(route) { + console.log('goToRoute', this.mfBasePath + route); + this.router.navigateByUrl(this.mfBasePath + route).then(e => { + if (e) { + console.log('Navigation is successful!'); + } else { + console.log('Navigation has failed!'); + } + }); } - ngOnDestroy() { - if (this.lcSubscription) { - this.lcSubscription.unsubscribe(); - } + this.lcSubscription.unsubscribe(); + lifecycleManager().setNavigationSync({ active: false }); } public slugify(str: string): string { diff --git a/core/examples/luigi-sample-angular/src/assets/sampleexternal.html b/core/examples/luigi-sample-angular/src/assets/sampleexternal.html index 2fdbd94f2e..ccd7d37bc4 100644 --- a/core/examples/luigi-sample-angular/src/assets/sampleexternal.html +++ b/core/examples/luigi-sample-angular/src/assets/sampleexternal.html @@ -41,6 +41,8 @@

    Cross Domain Example

    LuigiClient.uxManager().showLoadingIndicator();
     LuigiClient.uxManager().hideLoadingIndicator();

    +

    Navigate deep +

@@ -76,10 +78,20 @@

Cross Domain Example

LuigiClient.uxManager().hideLoadingIndicator(); }, 3000); } -hideLoadingIndicator(); - - - + + hideLoadingIndicator(); + + - + + \ No newline at end of file From 3e11633c6f1fadbe5081b182a8cd6d49685c8c44 Mon Sep 17 00:00:00 2001 From: Markus Edenhauser <1720843+maxmarkus@users.noreply.github.com> Date: Thu, 5 Mar 2020 17:06:30 +0100 Subject: [PATCH 17/18] skip navigation event after re-rendering navigation. required for updating of side nav --- .../src/luigi-config/extended/projectDetailNav.js | 2 -- core/src/App.html | 3 --- core/src/services/iframe.js | 11 ++++++++++- 3 files changed, 10 insertions(+), 6 deletions(-) diff --git a/core/examples/luigi-sample-angular/src/luigi-config/extended/projectDetailNav.js b/core/examples/luigi-sample-angular/src/luigi-config/extended/projectDetailNav.js index a6697bd94f..edd486f24a 100644 --- a/core/examples/luigi-sample-angular/src/luigi-config/extended/projectDetailNav.js +++ b/core/examples/luigi-sample-angular/src/luigi-config/extended/projectDetailNav.js @@ -254,9 +254,7 @@ export const projectDetailNavStructure = projectId => [ { pathSegment: 'nav-sync', label: 'Nav Sync', - // viewUrl: '/sampleapp.html#/nav-sync-example/one', icon: 'synchronize', - keepSelectedForChildren: true, navigationContext: 'navSync', children: ['one', 'two', 'three', 'four'].map(seg => ({ label: seg, diff --git a/core/src/App.html b/core/src/App.html index c8ae6fe2f6..e6d713c953 100644 --- a/core/src/App.html +++ b/core/src/App.html @@ -376,10 +376,7 @@ closeModal(); closeSplitView(); - if (isNavigationSyncDisabled) { Routing.handleRouteChange(path, getComponentWrapper(), node, config); - } - isNavigationSyncDisabled = true; // always reset it after use }); }; diff --git a/core/src/services/iframe.js b/core/src/services/iframe.js index a63281a205..96116b0d62 100644 --- a/core/src/services/iframe.js +++ b/core/src/services/iframe.js @@ -285,7 +285,16 @@ class IframeClass { pathParams: JSON.stringify(Object.assign({}, componentData.pathParams)), internal: JSON.stringify(component.prepareInternalData(config)) }; - IframeHelpers.sendMessageToIframe(config.iframe, message); + + const withSync = !componentData.isNavigationSyncDisabled; + if (withSync) { + // default, send navigation event to client + IframeHelpers.sendMessageToIframe(config.iframe, message); + } else { + // `withoutSync()` used. client navigation was skipped, reset after one-time use. + component.set({ isNavigationSyncDisabled: true }); + } + // clear goBackContext and reset navigateBack after sending it to the client component.set({ goBackContext: undefined, isNavigateBack: false }); From 651c86747963f7479d3f9d501587d86674756b82 Mon Sep 17 00:00:00 2001 From: Markus Edenhauser <1720843+maxmarkus@users.noreply.github.com> Date: Fri, 6 Mar 2020 14:01:49 +0100 Subject: [PATCH 18/18] fix small issue --- core/src/App.html | 12 ++++++------ core/src/services/iframe.js | 4 ++-- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/core/src/App.html b/core/src/App.html index e6d713c953..348a8e7cba 100644 --- a/core/src/App.html +++ b/core/src/App.html @@ -133,7 +133,7 @@ let viewGroup; let isolateView; let previousNodeValues; - let isNavigationSyncDisabled = true; + let isNavigationSyncEnabled = true; let isNavigateBack = false; let goBackContext; let navigationPath; @@ -289,7 +289,7 @@ tabNav, isNavigateBack, goBackContext, - isNavigationSyncDisabled + isNavigationSyncEnabled }; }, set: obj => { @@ -333,8 +333,8 @@ isNavigateBack = obj.isNavigateBack; } else if (prop === 'goBackContext') { goBackContext = obj.goBackContext; - } else if (prop === 'isNavigationSyncDisabled') { - isNavigationSyncDisabled = obj.isNavigationSyncDisabled; + } else if (prop === 'isNavigationSyncEnabled') { + isNavigationSyncEnabled = obj.isNavigationSyncEnabled; } }); } @@ -376,7 +376,7 @@ closeModal(); closeSplitView(); - Routing.handleRouteChange(path, getComponentWrapper(), node, config); + Routing.handleRouteChange(path, getComponentWrapper(), node, config); }); }; @@ -876,7 +876,7 @@ openSplitView(path, e.data.params.splitView); } else { getUnsavedChangesModalPromise().then(() => { - isNavigationSyncDisabled = !e.data.params.withoutSync; + isNavigationSyncEnabled = !e.data.params.withoutSync; handleNavigation(e.data, config); closeModal(); closeSplitView(); diff --git a/core/src/services/iframe.js b/core/src/services/iframe.js index 96116b0d62..c43745bd20 100644 --- a/core/src/services/iframe.js +++ b/core/src/services/iframe.js @@ -286,13 +286,13 @@ class IframeClass { internal: JSON.stringify(component.prepareInternalData(config)) }; - const withSync = !componentData.isNavigationSyncDisabled; + const withSync = componentData.isNavigationSyncEnabled; if (withSync) { // default, send navigation event to client IframeHelpers.sendMessageToIframe(config.iframe, message); } else { // `withoutSync()` used. client navigation was skipped, reset after one-time use. - component.set({ isNavigationSyncDisabled: true }); + component.set({ isNavigationSyncEnabled: true }); } // clear goBackContext and reset navigateBack after sending it to the client