From 26188c08c15b1379da27cadbe38ee3bf06ee6606 Mon Sep 17 00:00:00 2001 From: Jacob Haddad Date: Fri, 15 Mar 2024 12:11:12 -0700 Subject: [PATCH 01/11] feat(other): advanced routing panel in other tab --- client-app/src/desktop/AppModel.ts | 5 + client-app/src/desktop/tabs/other/OtherTab.ts | 4 +- .../other/routing/AdvancedRoutingPanel.tsx | 171 ++++++++++++++++++ 3 files changed, 179 insertions(+), 1 deletion(-) create mode 100644 client-app/src/desktop/tabs/other/routing/AdvancedRoutingPanel.tsx diff --git a/client-app/src/desktop/AppModel.ts b/client-app/src/desktop/AppModel.ts index c6f3add49..2579f8b1a 100755 --- a/client-app/src/desktop/AppModel.ts +++ b/client-app/src/desktop/AppModel.ts @@ -184,6 +184,11 @@ export class AppModel extends BaseAppModel { name: 'simpleRouting', path: '/simpleRouting', children: [{name: 'recordId', path: '/:recordId'}] + }, + { + name: 'advancedRouting', + path: '/advancedRouting', + children: [{name: 'routeParam', path: '/:routeParam'}] } ] }, diff --git a/client-app/src/desktop/tabs/other/OtherTab.ts b/client-app/src/desktop/tabs/other/OtherTab.ts index 873c5a7c0..108c07ed9 100644 --- a/client-app/src/desktop/tabs/other/OtherTab.ts +++ b/client-app/src/desktop/tabs/other/OtherTab.ts @@ -18,6 +18,7 @@ import {placeholderPanel} from './PlaceholderPanel'; import {popupsPanel} from './PopupsPanel'; import {relativeTimestampPanel} from './relativetimestamp/RelativeTimestampPanel'; import {simpleRoutingPanel} from './routing/SimpleRoutingPanel'; +import {advancedRoutingPanel} from './routing/AdvancedRoutingPanel'; export const otherTab = hoistCmp.factory(() => tabContainer({ @@ -46,7 +47,8 @@ export const otherTab = hoistCmp.factory(() => {id: 'placeholder', title: 'Placeholder', content: placeholderPanel}, {id: 'popups', content: popupsPanel}, {id: 'timestamp', content: relativeTimestampPanel}, - {id: 'simpleRouting', content: simpleRoutingPanel} + {id: 'simpleRouting', content: simpleRoutingPanel}, + {id: 'advancedRouting', content: advancedRoutingPanel} ] }, className: 'toolbox-tab' diff --git a/client-app/src/desktop/tabs/other/routing/AdvancedRoutingPanel.tsx b/client-app/src/desktop/tabs/other/routing/AdvancedRoutingPanel.tsx new file mode 100644 index 000000000..e068e7133 --- /dev/null +++ b/client-app/src/desktop/tabs/other/routing/AdvancedRoutingPanel.tsx @@ -0,0 +1,171 @@ +import {HoistModel, hoistCmp, creates, XH} from '@xh/hoist/core'; +import {grid, gridCountLabel, GridModel} from '@xh/hoist/cmp/grid'; +import {action, observable, makeObservable} from '@xh/hoist/mobx'; +import {panel} from '@xh/hoist/desktop/cmp/panel'; +import {wrapper} from '../../../common'; +import React from 'react'; +import {Icon} from '@xh/hoist/icon'; +import {filler, hbox, hframe, span, vbox, vframe} from '@xh/hoist/cmp/layout'; +import {colAutosizeButton, refreshButton} from '@xh/hoist/desktop/cmp/button'; +import {select} from '@xh/hoist/desktop/cmp/input'; + +export const advancedRoutingPanel = hoistCmp.factory({ + displayName: 'AdvancedRoutingPanel', + model: creates(() => new AdvancedRoutingPanelModel()), + + render({model}) { + return wrapper({ + description: [ +

+ This example demonstrates how to use URL route parameters to store and restore + the state of a component. The state of the grid (grouping, sorting, and selected + record) is stored in the URL, and the state is restored when the URL is + revisited. +

, +

+ The state is encoded in the URL as a base64 string, which is then + decoded and parsed to restore the state. +

, +

+ The current state encoding is:
+ {'{'} +
+ groupBy: {model.groupBy || 'None'} +
+ sortBy: {model.sortBy || 'None'} +
+ selectedId: {model.gridModel.selectedRecord?.id || 'None'} +
+ {'}'} +

, +

+ ], + item: panel({ + ref: model.panelRef, + mask: 'onLoad', + item: hframe( + vframe( + grid(), + hbox({ + items: [Icon.info()], + className: 'tb-sample-grid__selbar' + }) + ) + ), + tbar: [ + refreshButton(), + colAutosizeButton(), + span('Group by:'), + select({ + bind: 'groupBy', + options: [ + {value: 'city', label: 'City'}, + {value: 'trade_date', label: 'Trade Date'}, + {value: 'city,trade_date', label: 'City › Trade Date'}, + {value: null, label: 'None'} + ], + width: 160 + }), + span('Sort by:'), + select({ + bind: 'sortBy', + options: [ + {value: 'id|desc', label: 'Company ID (Desc)'}, + {value: 'id|asc', label: 'Company ID (Asc)'}, + {value: 'company|desc', label: 'Company Name (Desc)'}, + {value: 'company|asc', label: 'Company Name (Asc)'}, + {value: 'city|desc', label: 'City (Desc)'}, + {value: 'city|asc', label: 'City (Asc)'}, + {value: 'trade_date|desc', label: 'Trade Date (Desc)'}, + {value: 'trade_date|asc', label: 'Trade Date (Asc)'}, + {value: null, label: 'None'} + ] + }), + filler(), + gridCountLabel({unit: 'companies'}), + vbox({}) + ] + }) + }); + } +}); + +class AdvancedRoutingPanelModel extends HoistModel { + @observable groupBy = null; + @observable sortBy = null; + + constructor() { + super(); + makeObservable(this); + + this.addReaction({ + track: () => XH.routerState.params, + run: () => this.parseRouteParams(), + fireImmediately: true + }); + + this.addReaction({ + track: () => [this.groupBy, this.sortBy, this.gridModel.selectedRecord?.id], + run: () => this.updateRoute(), + fireImmediately: true + }); + } + + gridModel = new GridModel({ + columns: [ + {field: 'id', flex: 0}, + {field: 'company', flex: 1}, + {field: 'city', flex: 1}, + {field: 'trade_date', flex: 1} + ] + }); + + @action + private setGroupBy(groupBy: string) { + this.groupBy = groupBy; + + // Always select first when regrouping. + const groupByArr = groupBy ? groupBy.split(',') : []; + this.gridModel.setGroupBy(groupByArr); + } + + @action + private setSortBy(sortBy: string) { + this.sortBy = sortBy; + + // Always select first when resorting. + const sortByArr = sortBy ? sortBy.split(',') : []; + this.gridModel.setSortBy(sortByArr); + } + + @action + private async setSelected(recordId: string | number) { + await this.gridModel.selectAsync(Number(recordId)); + } + + @action + private async parseRouteParams() { + const advParam = XH.routerState.params.routeParam; + if (!advParam) return; + const decodedParam = atob(advParam); + const {groupBy, sortBy, selectedId} = JSON.parse(decodedParam); + if (groupBy) this.setGroupBy(groupBy); + if (sortBy) this.setSortBy(sortBy); + if (selectedId) await this.setSelected(selectedId); + } + + @action + private updateRoute() { + if (!XH.routerState.name.startsWith('default.other.advancedRouting')) return; + const {groupBy, sortBy} = this; + const selectedId = this.gridModel.selectedRecord?.id; + const routeParam = btoa(JSON.stringify({groupBy, sortBy, selectedId})); + XH.navigate('default.other.advancedRouting.routeParam', {routeParam}); + } + + override async doLoadAsync(loadSpec) { + const {trades} = await XH.fetchJson({url: 'trade'}); + this.gridModel.loadData(trades); + await this.parseRouteParams(); + } +} From b4bc2644d07ebc40d201ca2873fbfb291dc5c986 Mon Sep 17 00:00:00 2001 From: Jacob Haddad Date: Tue, 19 Mar 2024 10:18:48 -0700 Subject: [PATCH 02/11] feat(other-advanced): add automatic encode/decode to AppModel --- client-app/src/desktop/AppModel.ts | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/client-app/src/desktop/AppModel.ts b/client-app/src/desktop/AppModel.ts index 2579f8b1a..331fbe193 100755 --- a/client-app/src/desktop/AppModel.ts +++ b/client-app/src/desktop/AppModel.ts @@ -21,7 +21,19 @@ import {panelsTab} from './tabs/panels/PanelsTab'; import {fmtDateTimeSec} from '@xh/hoist/format'; import {span} from '@xh/hoist/cmp/layout'; import {BaseAppModel} from '../BaseAppModel'; +import {isEmpty} from 'lodash'; +// Encoding of json route params as base64 +export const routeParamEncoders = { + encodeParams: params => { + if (isEmpty(params)) return {}; + return {q: window.btoa(JSON.stringify(params))}; + }, + decodeParams: params => { + if (!params.q) return {}; + return JSON.parse(window.atob(params.q)); + } +}; export class AppModel extends BaseAppModel { /** Singleton instance reference - installed by XH upon init. */ static instance: AppModel; @@ -47,6 +59,7 @@ export class AppModel extends BaseAppModel { override async initAsync() { await super.initAsync(); await XH.installServicesAsync(GitHubService, PortfolioService); + XH.router.setOption('queryParamsMode', 'loose'); // Demo app-specific handling of EnvironmentService.serverVersion observable. this.addReaction({ @@ -188,7 +201,7 @@ export class AppModel extends BaseAppModel { { name: 'advancedRouting', path: '/advancedRouting', - children: [{name: 'routeParam', path: '/:routeParam'}] + ...routeParamEncoders } ] }, From 4a7852fb8cbaed7e88bd87f5c5bacc7c9d5b0a3a Mon Sep 17 00:00:00 2001 From: Jacob Haddad Date: Tue, 19 Mar 2024 13:41:21 -0700 Subject: [PATCH 03/11] fix(other-advanced): implement selective route replacement --- .../other/routing/AdvancedRoutingPanel.tsx | 26 ++++++++++++++----- 1 file changed, 19 insertions(+), 7 deletions(-) diff --git a/client-app/src/desktop/tabs/other/routing/AdvancedRoutingPanel.tsx b/client-app/src/desktop/tabs/other/routing/AdvancedRoutingPanel.tsx index e068e7133..8df21ccd0 100644 --- a/client-app/src/desktop/tabs/other/routing/AdvancedRoutingPanel.tsx +++ b/client-app/src/desktop/tabs/other/routing/AdvancedRoutingPanel.tsx @@ -22,6 +22,12 @@ export const advancedRoutingPanel = hoistCmp.factory({ record) is stored in the URL, and the state is restored when the URL is revisited.

, +

+ Hoist applications are able to navigate to a specific URL and specify whether or + not to push onto the route history. In this example, selecting individual + records in the grid will not save the URL to the route history, but changing the{' '} + groupBy or sortBy fields will. +

,

The state is encoded in the URL as a base64 string, which is then decoded and parsed to restore the state. @@ -145,10 +151,8 @@ class AdvancedRoutingPanelModel extends HoistModel { @action private async parseRouteParams() { - const advParam = XH.routerState.params.routeParam; - if (!advParam) return; - const decodedParam = atob(advParam); - const {groupBy, sortBy, selectedId} = JSON.parse(decodedParam); + if (this.gridModel.empty) return; + const {groupBy, sortBy, selectedId} = XH.routerState.params; if (groupBy) this.setGroupBy(groupBy); if (sortBy) this.setSortBy(sortBy); if (selectedId) await this.setSelected(selectedId); @@ -156,11 +160,19 @@ class AdvancedRoutingPanelModel extends HoistModel { @action private updateRoute() { - if (!XH.routerState.name.startsWith('default.other.advancedRouting')) return; + if ( + !XH.routerState.name.startsWith('default.other.advancedRouting') || + this.gridModel.empty + ) + return; const {groupBy, sortBy} = this; const selectedId = this.gridModel.selectedRecord?.id; - const routeParam = btoa(JSON.stringify({groupBy, sortBy, selectedId})); - XH.navigate('default.other.advancedRouting.routeParam', {routeParam}); + XH.navigate( + 'default.other.advancedRouting', + {groupBy, sortBy, selectedId}, + // Only push URL to route history if groupBy or sortBy changes. + {replace: selectedId != XH.routerState.params.selectedId} + ); } override async doLoadAsync(loadSpec) { From d3f88644a19c24453f9703ad42e61323322115a8 Mon Sep 17 00:00:00 2001 From: Jacob Haddad Date: Tue, 19 Mar 2024 14:21:57 -0700 Subject: [PATCH 04/11] feat(other-adv): provide switch to enable route deactivation prevention --- .../other/routing/AdvancedRoutingPanel.tsx | 27 ++++++++++++++----- 1 file changed, 21 insertions(+), 6 deletions(-) diff --git a/client-app/src/desktop/tabs/other/routing/AdvancedRoutingPanel.tsx b/client-app/src/desktop/tabs/other/routing/AdvancedRoutingPanel.tsx index 8df21ccd0..90b69caa4 100644 --- a/client-app/src/desktop/tabs/other/routing/AdvancedRoutingPanel.tsx +++ b/client-app/src/desktop/tabs/other/routing/AdvancedRoutingPanel.tsx @@ -1,5 +1,5 @@ import {HoistModel, hoistCmp, creates, XH} from '@xh/hoist/core'; -import {grid, gridCountLabel, GridModel} from '@xh/hoist/cmp/grid'; +import {grid, GridModel} from '@xh/hoist/cmp/grid'; import {action, observable, makeObservable} from '@xh/hoist/mobx'; import {panel} from '@xh/hoist/desktop/cmp/panel'; import {wrapper} from '../../../common'; @@ -7,7 +7,7 @@ import React from 'react'; import {Icon} from '@xh/hoist/icon'; import {filler, hbox, hframe, span, vbox, vframe} from '@xh/hoist/cmp/layout'; import {colAutosizeButton, refreshButton} from '@xh/hoist/desktop/cmp/button'; -import {select} from '@xh/hoist/desktop/cmp/input'; +import {select, switchInput} from '@xh/hoist/desktop/cmp/input'; export const advancedRoutingPanel = hoistCmp.factory({ displayName: 'AdvancedRoutingPanel', @@ -26,7 +26,9 @@ export const advancedRoutingPanel = hoistCmp.factory({ Hoist applications are able to navigate to a specific URL and specify whether or not to push onto the route history. In this example, selecting individual records in the grid will not save the URL to the route history, but changing the{' '} - groupBy or sortBy fields will. + groupBy or sortBy fields will. Hoist also provides the + ability to prevent route deactivation, allowing the developer to present the + user with a pop-up before navigating away from the current route.

,

The state is encoded in the URL as a base64 string, which is then @@ -34,7 +36,6 @@ export const advancedRoutingPanel = hoistCmp.factory({

,

The current state encoding is:
- {'{'}
groupBy: {model.groupBy || 'None'}
@@ -42,7 +43,6 @@ export const advancedRoutingPanel = hoistCmp.factory({
selectedId: {model.gridModel.selectedRecord?.id || 'None'}
- {'}'}

,

], @@ -87,8 +87,11 @@ export const advancedRoutingPanel = hoistCmp.factory({ {value: null, label: 'None'} ] }), + switchInput({ + label: 'Prevent Route Deactivation', + onChange: () => (model.preventDeactivate = !model.preventDeactivate) + }), filler(), - gridCountLabel({unit: 'companies'}), vbox({}) ] }) @@ -99,6 +102,7 @@ export const advancedRoutingPanel = hoistCmp.factory({ class AdvancedRoutingPanelModel extends HoistModel { @observable groupBy = null; @observable sortBy = null; + @observable preventDeactivate = false; constructor() { super(); @@ -115,6 +119,14 @@ class AdvancedRoutingPanelModel extends HoistModel { run: () => this.updateRoute(), fireImmediately: true }); + + window.addEventListener('beforeunload', e => { + if (!XH.routerState.name.startsWith('default.other.advancedRouting')) { + delete e.returnValue; + return; + } + if (this.preventDeactivate) e.preventDefault(); + }); } gridModel = new GridModel({ @@ -147,6 +159,9 @@ class AdvancedRoutingPanelModel extends HoistModel { @action private async setSelected(recordId: string | number) { await this.gridModel.selectAsync(Number(recordId)); + if (!this.gridModel.selectedId) { + XH.dangerToast(`Record ${recordId} not found`); + } } @action From a6422e36fa94f909ab6522248ba21939dade1556 Mon Sep 17 00:00:00 2001 From: Jacob Haddad Date: Tue, 26 Mar 2024 11:06:48 -0700 Subject: [PATCH 05/11] fix(advanced-routing): relocate code in AppModel, remove extraneous comments in AdvancedRouting, inject description values from router state --- client-app/src/desktop/AppModel.ts | 24 ++++---- .../other/routing/AdvancedRoutingPanel.tsx | 57 ++++++++----------- 2 files changed, 36 insertions(+), 45 deletions(-) diff --git a/client-app/src/desktop/AppModel.ts b/client-app/src/desktop/AppModel.ts index 331fbe193..c036c6b7a 100755 --- a/client-app/src/desktop/AppModel.ts +++ b/client-app/src/desktop/AppModel.ts @@ -23,17 +23,6 @@ import {span} from '@xh/hoist/cmp/layout'; import {BaseAppModel} from '../BaseAppModel'; import {isEmpty} from 'lodash'; -// Encoding of json route params as base64 -export const routeParamEncoders = { - encodeParams: params => { - if (isEmpty(params)) return {}; - return {q: window.btoa(JSON.stringify(params))}; - }, - decodeParams: params => { - if (!params.q) return {}; - return JSON.parse(window.atob(params.q)); - } -}; export class AppModel extends BaseAppModel { /** Singleton instance reference - installed by XH upon init. */ static instance: AppModel; @@ -59,6 +48,7 @@ export class AppModel extends BaseAppModel { override async initAsync() { await super.initAsync(); await XH.installServicesAsync(GitHubService, PortfolioService); + // Set the queryParamsMode to 'loose' to allow for more flexible URL query parameters. XH.router.setOption('queryParamsMode', 'loose'); // Demo app-specific handling of EnvironmentService.serverVersion observable. @@ -228,3 +218,15 @@ export class AppModel extends BaseAppModel { ]; } } + +// Encoding of json route params as base64 +export const routeParamEncoders = { + encodeParams: params => { + if (isEmpty(params)) return {}; + return {q: window.btoa(JSON.stringify(params))}; + }, + decodeParams: params => { + if (!params.q) return {}; + return JSON.parse(window.atob(params.q)); + } +}; diff --git a/client-app/src/desktop/tabs/other/routing/AdvancedRoutingPanel.tsx b/client-app/src/desktop/tabs/other/routing/AdvancedRoutingPanel.tsx index 90b69caa4..ffdacd1ed 100644 --- a/client-app/src/desktop/tabs/other/routing/AdvancedRoutingPanel.tsx +++ b/client-app/src/desktop/tabs/other/routing/AdvancedRoutingPanel.tsx @@ -4,8 +4,7 @@ import {action, observable, makeObservable} from '@xh/hoist/mobx'; import {panel} from '@xh/hoist/desktop/cmp/panel'; import {wrapper} from '../../../common'; import React from 'react'; -import {Icon} from '@xh/hoist/icon'; -import {filler, hbox, hframe, span, vbox, vframe} from '@xh/hoist/cmp/layout'; +import {filler, span, vbox} from '@xh/hoist/cmp/layout'; import {colAutosizeButton, refreshButton} from '@xh/hoist/desktop/cmp/button'; import {select, switchInput} from '@xh/hoist/desktop/cmp/input'; @@ -37,11 +36,11 @@ export const advancedRoutingPanel = hoistCmp.factory({

The current state encoding is:

- groupBy: {model.groupBy || 'None'} + groupBy: {XH.routerState.params.groupBy || 'None'}
- sortBy: {model.sortBy || 'None'} + sortBy: {XH.routerState.params.sortBy || 'None'}
- selectedId: {model.gridModel.selectedRecord?.id || 'None'} + selectedId: {XH.routerState.params.selectedId || 'None'}

,

@@ -49,15 +48,7 @@ export const advancedRoutingPanel = hoistCmp.factory({ item: panel({ ref: model.panelRef, mask: 'onLoad', - item: hframe( - vframe( - grid(), - hbox({ - items: [Icon.info()], - className: 'tb-sample-grid__selbar' - }) - ) - ), + item: grid(), tbar: [ refreshButton(), colAutosizeButton(), @@ -103,22 +94,31 @@ class AdvancedRoutingPanelModel extends HoistModel { @observable groupBy = null; @observable sortBy = null; @observable preventDeactivate = false; + gridModel: GridModel = null; constructor() { super(); makeObservable(this); - this.addReaction({ - track: () => XH.routerState.params, - run: () => this.parseRouteParams(), - fireImmediately: true + this.gridModel = new GridModel({ + columns: [ + {field: 'id'}, + {field: 'company', flex: 1}, + {field: 'city', flex: 1}, + {field: 'trade_date', flex: 1} + ] }); - this.addReaction({ - track: () => [this.groupBy, this.sortBy, this.gridModel.selectedRecord?.id], - run: () => this.updateRoute(), - fireImmediately: true - }); + this.addReaction( + { + track: () => XH.routerState.params, + run: () => this.parseRouteParams() + }, + { + track: () => [this.groupBy, this.sortBy, this.gridModel.selectedRecord?.id], + run: () => this.updateRoute() + } + ); window.addEventListener('beforeunload', e => { if (!XH.routerState.name.startsWith('default.other.advancedRouting')) { @@ -129,20 +129,10 @@ class AdvancedRoutingPanelModel extends HoistModel { }); } - gridModel = new GridModel({ - columns: [ - {field: 'id', flex: 0}, - {field: 'company', flex: 1}, - {field: 'city', flex: 1}, - {field: 'trade_date', flex: 1} - ] - }); - @action private setGroupBy(groupBy: string) { this.groupBy = groupBy; - // Always select first when regrouping. const groupByArr = groupBy ? groupBy.split(',') : []; this.gridModel.setGroupBy(groupByArr); } @@ -151,7 +141,6 @@ class AdvancedRoutingPanelModel extends HoistModel { private setSortBy(sortBy: string) { this.sortBy = sortBy; - // Always select first when resorting. const sortByArr = sortBy ? sortBy.split(',') : []; this.gridModel.setSortBy(sortByArr); } From c521f59e80916a6f6ead8689585316e7712b3e48 Mon Sep 17 00:00:00 2001 From: Jacob Haddad Date: Tue, 26 Mar 2024 11:10:49 -0700 Subject: [PATCH 06/11] fix(advanced-routing): remove extraneous condition on parseRouteParams --- .../src/desktop/tabs/other/routing/AdvancedRoutingPanel.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/client-app/src/desktop/tabs/other/routing/AdvancedRoutingPanel.tsx b/client-app/src/desktop/tabs/other/routing/AdvancedRoutingPanel.tsx index ffdacd1ed..8039d3ff4 100644 --- a/client-app/src/desktop/tabs/other/routing/AdvancedRoutingPanel.tsx +++ b/client-app/src/desktop/tabs/other/routing/AdvancedRoutingPanel.tsx @@ -155,7 +155,6 @@ class AdvancedRoutingPanelModel extends HoistModel { @action private async parseRouteParams() { - if (this.gridModel.empty) return; const {groupBy, sortBy, selectedId} = XH.routerState.params; if (groupBy) this.setGroupBy(groupBy); if (sortBy) this.setSortBy(sortBy); From d1af1a6cc635381c1171f2cb79e821fec0ab394f Mon Sep 17 00:00:00 2001 From: Jacob Haddad Date: Thu, 28 Mar 2024 09:19:16 -0700 Subject: [PATCH 07/11] fix(advanced-routing): clear query parameters when navigating to other panels in the same tab --- .../tabs/other/routing/AdvancedRoutingPanel.tsx | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/client-app/src/desktop/tabs/other/routing/AdvancedRoutingPanel.tsx b/client-app/src/desktop/tabs/other/routing/AdvancedRoutingPanel.tsx index 8039d3ff4..1cd258db6 100644 --- a/client-app/src/desktop/tabs/other/routing/AdvancedRoutingPanel.tsx +++ b/client-app/src/desktop/tabs/other/routing/AdvancedRoutingPanel.tsx @@ -111,8 +111,8 @@ class AdvancedRoutingPanelModel extends HoistModel { this.addReaction( { - track: () => XH.routerState.params, - run: () => this.parseRouteParams() + track: () => XH.routerState, + run: () => this.processRouterState() }, { track: () => [this.groupBy, this.sortBy, this.gridModel.selectedRecord?.id], @@ -152,7 +152,6 @@ class AdvancedRoutingPanelModel extends HoistModel { XH.dangerToast(`Record ${recordId} not found`); } } - @action private async parseRouteParams() { const {groupBy, sortBy, selectedId} = XH.routerState.params; @@ -161,13 +160,23 @@ class AdvancedRoutingPanelModel extends HoistModel { if (selectedId) await this.setSelected(selectedId); } + @action + private async processRouterState() { + if (!XH.routerState.name.startsWith('default.other.advancedRouting')) { + // Clear query parameters when navigating to other panels in the same tab. + XH.navigate(XH.routerState.name, null, {replace: true}); + return; + } + await this.parseRouteParams(); + } + @action private updateRoute() { if ( !XH.routerState.name.startsWith('default.other.advancedRouting') || this.gridModel.empty ) - return; + return XH.navigate(XH.routerState.name, null, {replace: true}); const {groupBy, sortBy} = this; const selectedId = this.gridModel.selectedRecord?.id; XH.navigate( From bd1e1a01afbd29a3ff69444382e2b35baf2e9981 Mon Sep 17 00:00:00 2001 From: Jacob Haddad Date: Thu, 28 Mar 2024 13:07:15 -0700 Subject: [PATCH 08/11] fix(advanced-routing): only clear route parameters when navigating away from advancedRouting panel --- .../other/routing/AdvancedRoutingPanel.tsx | 53 +++++++++++-------- 1 file changed, 31 insertions(+), 22 deletions(-) diff --git a/client-app/src/desktop/tabs/other/routing/AdvancedRoutingPanel.tsx b/client-app/src/desktop/tabs/other/routing/AdvancedRoutingPanel.tsx index 1cd258db6..5fe2c6028 100644 --- a/client-app/src/desktop/tabs/other/routing/AdvancedRoutingPanel.tsx +++ b/client-app/src/desktop/tabs/other/routing/AdvancedRoutingPanel.tsx @@ -7,6 +7,7 @@ import React from 'react'; import {filler, span, vbox} from '@xh/hoist/cmp/layout'; import {colAutosizeButton, refreshButton} from '@xh/hoist/desktop/cmp/button'; import {select, switchInput} from '@xh/hoist/desktop/cmp/input'; +import {State} from 'router5'; export const advancedRoutingPanel = hoistCmp.factory({ displayName: 'AdvancedRoutingPanel', @@ -79,8 +80,8 @@ export const advancedRoutingPanel = hoistCmp.factory({ ] }), switchInput({ - label: 'Prevent Route Deactivation', - onChange: () => (model.preventDeactivate = !model.preventDeactivate) + bind: 'preventDeactivate', + label: 'Prevent Route Deactivation' }), filler(), vbox({}) @@ -112,7 +113,7 @@ class AdvancedRoutingPanelModel extends HoistModel { this.addReaction( { track: () => XH.routerState, - run: () => this.processRouterState() + run: (newState, oldState) => this.processRouterState(newState, oldState) }, { track: () => [this.groupBy, this.sortBy, this.gridModel.selectedRecord?.id], @@ -152,6 +153,12 @@ class AdvancedRoutingPanelModel extends HoistModel { XH.dangerToast(`Record ${recordId} not found`); } } + + @action + private setPreventDeactivate(preventDeactivate: boolean) { + this.preventDeactivate = preventDeactivate; + } + @action private async parseRouteParams() { const {groupBy, sortBy, selectedId} = XH.routerState.params; @@ -161,30 +168,32 @@ class AdvancedRoutingPanelModel extends HoistModel { } @action - private async processRouterState() { - if (!XH.routerState.name.startsWith('default.other.advancedRouting')) { - // Clear query parameters when navigating to other panels in the same tab. - XH.navigate(XH.routerState.name, null, {replace: true}); - return; - } - await this.parseRouteParams(); + private async processRouterState(newState?: State, oldState?: State) { + if ( + !newState.name.startsWith('default.other.advancedRouting') && + oldState.name.startsWith('default.other.advancedRouting') + ) + return XH.navigate(newState.name, null, {replace: true}); + + if (newState.name.startsWith('default.other.advancedRouting')) + await this.parseRouteParams(); } @action private updateRoute() { if ( - !XH.routerState.name.startsWith('default.other.advancedRouting') || - this.gridModel.empty - ) - return XH.navigate(XH.routerState.name, null, {replace: true}); - const {groupBy, sortBy} = this; - const selectedId = this.gridModel.selectedRecord?.id; - XH.navigate( - 'default.other.advancedRouting', - {groupBy, sortBy, selectedId}, - // Only push URL to route history if groupBy or sortBy changes. - {replace: selectedId != XH.routerState.params.selectedId} - ); + XH.routerState.name.startsWith('default.other.advancedRouting') && + !this.gridModel.empty + ) { + const {groupBy, sortBy} = this; + const selectedId = this.gridModel.selectedRecord?.id; + XH.navigate( + 'default.other.advancedRouting', + {groupBy, sortBy, selectedId}, + // Only push URL to route history if groupBy or sortBy changes. + {replace: selectedId != XH.routerState.params.selectedId} + ); + } } override async doLoadAsync(loadSpec) { From 864f023b4ff79a6342355386e3334f32d15cb433 Mon Sep 17 00:00:00 2001 From: Jacob Haddad Date: Thu, 28 Mar 2024 13:13:03 -0700 Subject: [PATCH 09/11] fix(advanced-routing): preserve state encoding when returning from other panel in Other tab --- .../desktop/tabs/other/routing/AdvancedRoutingPanel.tsx | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/client-app/src/desktop/tabs/other/routing/AdvancedRoutingPanel.tsx b/client-app/src/desktop/tabs/other/routing/AdvancedRoutingPanel.tsx index 5fe2c6028..ec1db7f58 100644 --- a/client-app/src/desktop/tabs/other/routing/AdvancedRoutingPanel.tsx +++ b/client-app/src/desktop/tabs/other/routing/AdvancedRoutingPanel.tsx @@ -174,8 +174,12 @@ class AdvancedRoutingPanelModel extends HoistModel { oldState.name.startsWith('default.other.advancedRouting') ) return XH.navigate(newState.name, null, {replace: true}); - - if (newState.name.startsWith('default.other.advancedRouting')) + else if ( + newState.name.startsWith('default.other.advancedRouting') && + !oldState.name.startsWith('default.other.advancedRouting') + ) + this.updateRoute(); + else if (newState.name.startsWith('default.other.advancedRouting')) await this.parseRouteParams(); } From efa220a1e5e7fb8cd1ec230dcb38823e84f30879 Mon Sep 17 00:00:00 2001 From: Anselm McClain Date: Fri, 26 Apr 2024 10:11:42 -0700 Subject: [PATCH 10/11] Tweaks --- client-app/src/desktop/tabs/other/OtherTab.ts | 6 +++--- .../tabs/other/routing/AdvancedRoutingPanel.tsx | 13 +++++-------- 2 files changed, 8 insertions(+), 11 deletions(-) diff --git a/client-app/src/desktop/tabs/other/OtherTab.ts b/client-app/src/desktop/tabs/other/OtherTab.ts index 108c07ed9..bf1cac3e2 100644 --- a/client-app/src/desktop/tabs/other/OtherTab.ts +++ b/client-app/src/desktop/tabs/other/OtherTab.ts @@ -46,9 +46,9 @@ export const otherTab = hoistCmp.factory(() => {id: 'pinPad', title: 'PIN Pad', content: pinPadPanel}, {id: 'placeholder', title: 'Placeholder', content: placeholderPanel}, {id: 'popups', content: popupsPanel}, - {id: 'timestamp', content: relativeTimestampPanel}, - {id: 'simpleRouting', content: simpleRoutingPanel}, - {id: 'advancedRouting', content: advancedRoutingPanel} + {id: 'simpleRouting', title: 'Routing (Simple)', content: simpleRoutingPanel}, + {id: 'advancedRouting', title: 'Routing (Advanced)', content: advancedRoutingPanel}, + {id: 'timestamp', content: relativeTimestampPanel} ] }, className: 'toolbox-tab' diff --git a/client-app/src/desktop/tabs/other/routing/AdvancedRoutingPanel.tsx b/client-app/src/desktop/tabs/other/routing/AdvancedRoutingPanel.tsx index ec1db7f58..91d46e0f6 100644 --- a/client-app/src/desktop/tabs/other/routing/AdvancedRoutingPanel.tsx +++ b/client-app/src/desktop/tabs/other/routing/AdvancedRoutingPanel.tsx @@ -1,13 +1,12 @@ -import {HoistModel, hoistCmp, creates, XH} from '@xh/hoist/core'; import {grid, GridModel} from '@xh/hoist/cmp/grid'; -import {action, observable, makeObservable} from '@xh/hoist/mobx'; -import {panel} from '@xh/hoist/desktop/cmp/panel'; -import {wrapper} from '../../../common'; -import React from 'react'; import {filler, span, vbox} from '@xh/hoist/cmp/layout'; -import {colAutosizeButton, refreshButton} from '@xh/hoist/desktop/cmp/button'; +import {creates, hoistCmp, HoistModel, XH} from '@xh/hoist/core'; import {select, switchInput} from '@xh/hoist/desktop/cmp/input'; +import {panel} from '@xh/hoist/desktop/cmp/panel'; +import {action, makeObservable, observable} from '@xh/hoist/mobx'; +import React from 'react'; import {State} from 'router5'; +import {wrapper} from '../../../common'; export const advancedRoutingPanel = hoistCmp.factory({ displayName: 'AdvancedRoutingPanel', @@ -51,8 +50,6 @@ export const advancedRoutingPanel = hoistCmp.factory({ mask: 'onLoad', item: grid(), tbar: [ - refreshButton(), - colAutosizeButton(), span('Group by:'), select({ bind: 'groupBy', From 935dce6492997a0a5323eeabc823a7d1308a1acd Mon Sep 17 00:00:00 2001 From: Anselm McClain Date: Fri, 26 Apr 2024 10:14:13 -0700 Subject: [PATCH 11/11] Tweaks --- .../src/desktop/tabs/other/routing/AdvancedRoutingPanel.tsx | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/client-app/src/desktop/tabs/other/routing/AdvancedRoutingPanel.tsx b/client-app/src/desktop/tabs/other/routing/AdvancedRoutingPanel.tsx index 91d46e0f6..1afa6c186 100644 --- a/client-app/src/desktop/tabs/other/routing/AdvancedRoutingPanel.tsx +++ b/client-app/src/desktop/tabs/other/routing/AdvancedRoutingPanel.tsx @@ -1,5 +1,5 @@ import {grid, GridModel} from '@xh/hoist/cmp/grid'; -import {filler, span, vbox} from '@xh/hoist/cmp/layout'; +import {span} from '@xh/hoist/cmp/layout'; import {creates, hoistCmp, HoistModel, XH} from '@xh/hoist/core'; import {select, switchInput} from '@xh/hoist/desktop/cmp/input'; import {panel} from '@xh/hoist/desktop/cmp/panel'; @@ -79,9 +79,7 @@ export const advancedRoutingPanel = hoistCmp.factory({ switchInput({ bind: 'preventDeactivate', label: 'Prevent Route Deactivation' - }), - filler(), - vbox({}) + }) ] }) });