Skip to content

Commit b2c8e84

Browse files
committed
Merge branch 'develop' into advanced-routing
2 parents eaba6a6 + 047c9c6 commit b2c8e84

File tree

18 files changed

+1024
-835
lines changed

18 files changed

+1024
-835
lines changed

.github/workflows/gradle.yml

+67
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
# This workflow uses actions that are not certified by GitHub.
2+
# They are provided by a third-party and are governed by
3+
# separate terms of service, privacy policy, and support
4+
# documentation.
5+
# This workflow will build a Java project with Gradle and cache/restore any dependencies to improve the workflow execution time
6+
# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-java-with-gradle
7+
8+
name: Java CI with Gradle
9+
10+
on:
11+
push:
12+
branches: [ "develop" ]
13+
pull_request:
14+
branches: [ "develop" ]
15+
16+
jobs:
17+
build:
18+
19+
runs-on: ubuntu-latest
20+
permissions:
21+
contents: read
22+
23+
steps:
24+
- uses: actions/checkout@v4
25+
- name: Set up JDK 17
26+
uses: actions/setup-java@v4
27+
with:
28+
java-version: '17'
29+
distribution: 'zulu'
30+
31+
# Configure Gradle for optimal use in GiHub Actions, including caching of downloaded dependencies.
32+
# See: https://github.com/gradle/actions/blob/main/setup-gradle/README.md
33+
- name: Setup Gradle
34+
uses: gradle/actions/setup-gradle@417ae3ccd767c252f5661f1ace9f835f9654f2b5 # v3.1.0
35+
36+
- name: Build with Gradle Wrapper
37+
run: ./gradlew build
38+
39+
# NOTE: The Gradle Wrapper is the default and recommended way to run Gradle (https://docs.gradle.org/current/userguide/gradle_wrapper.html).
40+
# If your project does not have the Gradle Wrapper configured, you can use the following configuration to run Gradle with a specified version.
41+
#
42+
# - name: Setup Gradle
43+
# uses: gradle/actions/setup-gradle@417ae3ccd767c252f5661f1ace9f835f9654f2b5 # v3.1.0
44+
# with:
45+
# gradle-version: '8.5'
46+
#
47+
# - name: Build with Gradle 8.5
48+
# run: gradle build
49+
50+
dependency-submission:
51+
52+
runs-on: ubuntu-latest
53+
permissions:
54+
contents: write
55+
56+
steps:
57+
- uses: actions/checkout@v4
58+
- name: Set up JDK 17
59+
uses: actions/setup-java@v4
60+
with:
61+
java-version: '17'
62+
distribution: 'zulu'
63+
64+
# Generates and submits a dependency graph, enabling Dependabot Alerts for all project dependencies.
65+
# See: https://github.com/gradle/actions/blob/main/dependency-submission/README.md
66+
- name: Generate and submit dependency graph
67+
uses: gradle/actions/dependency-submission@417ae3ccd767c252f5661f1ace9f835f9654f2b5 # v3.1.0

build.gradle

+8-2
Original file line numberDiff line numberDiff line change
@@ -49,10 +49,16 @@ if (parseBoolean(runHoistInline)) {
4949
}
5050

5151
dependencies {
52-
implementation "org.bitbucket.b_c:jose4j:0.7.12"
52+
// For server-side JWT validation.
53+
implementation "org.bitbucket.b_c:jose4j:0.9.6"
54+
55+
// Database drivers - H2 for temporary in-memory DB with `useH2: true` in instanceConfig YAML
56+
// MySQL for deployed instances, or more stateful local workstation setups.
57+
runtimeOnly "com.h2database:h2:2.2.224"
5358
runtimeOnly "mysql:mysql-connector-java:8.0.33"
54-
developmentOnly "io.methvin:directory-watcher:0.15.0"
5559

60+
// For hot reloading
61+
developmentOnly "io.methvin:directory-watcher:0.15.0"
5662
if (parseBoolean(enableHotSwap)) {
5763
developmentOnly "io.xh:groovyReset:1.0"
5864
}

client-app/package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@
4343
"@ag-grid-enterprise/sparklines": "~30.2.1",
4444
"@auth0/auth0-spa-js": "~1.22.0",
4545
"@fortawesome/free-brands-svg-icons": "^6.4.0",
46-
"@xh/hoist": "^62.0.0-SNAPSHOT",
46+
"@xh/hoist": "^63.0.0-SNAPSHOT",
4747
"@xh/package-template": "~2.0.0",
4848
"core-js": "3.x",
4949
"highcharts": "11.x",

client-app/src/desktop/AppModel.ts

+5
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,11 @@ export class AppModel extends BaseAppModel {
183183
{name: 'placeholder', path: '/placeholder'},
184184
{name: 'popups', path: '/popups'},
185185
{name: 'timestamp', path: '/timestamp'},
186+
{
187+
name: 'simpleRouting',
188+
path: '/simpleRouting',
189+
children: [{name: 'recordId', path: '/:recordId'}]
190+
},
186191
{
187192
name: 'advancedRouting',
188193
path: '/advancedRouting',

client-app/src/desktop/tabs/home/widgets/roadmap/RoadmapViewItem.ts

+4-4
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ export const roadmapViewItem = hoistCmp.factory({
3131
? popover({
3232
popoverClassName: 'tb-roadmap__popover',
3333
minimal: true,
34-
target: truncate(name, {length: 55, omission: ' ...'}),
34+
item: truncate(name, {length: 55, omission: ' ...'}),
3535
content: name
3636
})
3737
: name
@@ -44,7 +44,7 @@ export const roadmapViewItem = hoistCmp.factory({
4444
minimal: true,
4545
interactionKind: 'hover',
4646
position: 'top',
47-
target: div({
47+
item: div({
4848
className: 'tb-roadmap-item__statusIcon',
4949
item: getStatusIcon(status)
5050
}),
@@ -62,7 +62,7 @@ export const roadmapViewItem = hoistCmp.factory({
6262
minimal: true,
6363
interactionKind: 'hover',
6464
position: 'left-top',
65-
target: span(' ...'),
65+
item: span(' ...'),
6666
content: div({
6767
items: breakUpDescription(description)
6868
})
@@ -120,7 +120,7 @@ function getGitIcon(gitLinks) {
120120
} else {
121121
return popover({
122122
minimal: true,
123-
target: button({icon: gitIcon}),
123+
item: button({icon: gitIcon}),
124124
content: menu({items: getGitMenuItems(gitLinks)})
125125
});
126126
}

client-app/src/desktop/tabs/home/widgets/roadmap/RoadmapWidget.scss

+1-1
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@
6060
background-color: var(--xh-bg-alt);
6161
padding: var(--xh-pad-half-px);
6262

63-
.bp4-popover-content {
63+
.bp5-popover-content {
6464
background-color: var(--xh-bg-alt) !important;
6565
}
6666

Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
.xh-app .tb-chart-widget__buttons .xh-button.bp4-button.xh-button--enabled:focus,
2-
.xh-app.xh-dark .tb-chart-widget__buttons .xh-button.bp4-button.xh-button--enabled:focus,
3-
.xh-app.xh-dark.bp4-dark .tb-chart-widget__buttons .xh-button.bp4-button.xh-button--enabled:focus {
1+
.xh-app .tb-chart-widget__buttons .xh-button.bp5-button.xh-button--enabled:focus,
2+
.xh-app.xh-dark .tb-chart-widget__buttons .xh-button.bp5-button.xh-button--enabled:focus,
3+
.xh-app.xh-dark.bp5-dark .tb-chart-widget__buttons .xh-button.bp5-button.xh-button--enabled:focus {
44
outline: none !important;
55
}

client-app/src/desktop/tabs/other/OtherTab.ts

+2
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import {placeholderPanel} from './PlaceholderPanel';
1818
import {popupsPanel} from './PopupsPanel';
1919
import {relativeTimestampPanel} from './relativetimestamp/RelativeTimestampPanel';
2020
import {advancedRoutingPanel} from './routing/AdvancedRoutingPanel';
21+
import {simpleRoutingPanel} from './routing/SimpleRoutingPanel';
2122

2223
export const otherTab = hoistCmp.factory(() =>
2324
tabContainer({
@@ -46,6 +47,7 @@ export const otherTab = hoistCmp.factory(() =>
4647
{id: 'placeholder', title: 'Placeholder', content: placeholderPanel},
4748
{id: 'popups', content: popupsPanel},
4849
{id: 'timestamp', content: relativeTimestampPanel},
50+
{id: 'simpleRouting', content: simpleRoutingPanel},
4951
{id: 'advancedRouting', content: advancedRoutingPanel}
5052
]
5153
},

client-app/src/desktop/tabs/other/exceptions/ExceptionHandlerPanel.tsx

+2-2
Original file line numberDiff line numberDiff line change
@@ -144,8 +144,8 @@ const displayOptions = hoistCmp.factory<ExceptionHandlerModel>(({model}) =>
144144
alignItems: 'center',
145145
items: [
146146
label({
147-
className: `bp4-control bp4-switch bp4-inline bp4-align-right xh-input xh-switch-input${
148-
!model.showAlert ? ' bp4-disabled xh-input-disabled' : ''
147+
className: `bp5-control bp5-switch bp5-inline bp5-align-right xh-input xh-switch-input${
148+
!model.showAlert ? ' bp5-disabled xh-input-disabled' : ''
149149
}`,
150150
item: 'Alert Type'
151151
}),

client-app/src/desktop/tabs/other/formats/Styles.scss

+5-5
Original file line numberDiff line numberDiff line change
@@ -38,19 +38,19 @@
3838
}
3939
}
4040

41-
.bp4-form-content {
41+
.bp5-form-content {
4242
display: flex;
4343
}
4444

45-
.bp4-form-group {
45+
.bp5-form-group {
4646
margin: 0;
4747
padding: var(--xh-pad-half-px);
4848
float: right;
4949

50-
.bp4-label {
50+
.bp5-label {
5151
margin-bottom: 2px;
5252

53-
.bp4-text-muted {
53+
.bp5-text-muted {
5454
visibility: hidden;
5555
font-family: var(--xh-font-family-mono);
5656
background-color: var(--xh-bg-alt);
@@ -64,7 +64,7 @@
6464
}
6565
}
6666

67-
.bp4-form-helper-text {
67+
.bp5-form-helper-text {
6868
margin-left: 20px;
6969
}
7070
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
import {grid, GridModel} from '@xh/hoist/cmp/grid';
2+
import {creates, hoistCmp, HoistModel, LoadSpec, managed, XH} from '@xh/hoist/core';
3+
import {panel} from '@xh/hoist/desktop/cmp/panel';
4+
import {Icon} from '@xh/hoist/icon';
5+
import React from 'react';
6+
import {wrapper} from '../../../common';
7+
8+
export const simpleRoutingPanel = hoistCmp.factory({
9+
displayName: 'SimpleRoutingPanel',
10+
model: creates(() => new SimpleRoutingPanelModel()),
11+
12+
render({model}) {
13+
const routedUrl = `${window.location.origin}/app/other/simpleRouting/123`;
14+
return wrapper({
15+
description: [
16+
<p>
17+
Hoist provides functionality for route parameters to interact with UI
18+
components. The grid below has its selected record synced with a routable URL.
19+
</p>,
20+
<p>
21+
Given a URL such as <a href={routedUrl}>{routedUrl}</a>, where <code>123</code>{' '}
22+
is a record ID, we can auto-select the matching record in the grid. Updates to
23+
application state can be pushed back to the URL - try selecting a different
24+
record in the grid and observe the URL change.
25+
</p>,
26+
<p>
27+
Note that this routing relies on an appropriate route path being defined in the
28+
config returned by <code>AppModel.getRoutes()</code>.
29+
</p>
30+
],
31+
item: panel({
32+
title: 'Simple Routing',
33+
icon: Icon.gridPanel(),
34+
item: grid(),
35+
height: 500,
36+
width: 700
37+
})
38+
});
39+
}
40+
});
41+
42+
@managed
43+
class SimpleRoutingPanelModel extends HoistModel {
44+
private readonly BASE_ROUTE = 'default.other.simpleRouting';
45+
46+
@managed gridModel = new GridModel({
47+
columns: [{field: 'id'}, {field: 'company', flex: 1}]
48+
});
49+
50+
constructor() {
51+
super();
52+
this.addReaction(
53+
{
54+
// Track lastLoadCompleted to sync route -> grid after initial load.
55+
track: () => [XH.routerState.params, this.lastLoadCompleted],
56+
run: () => this.updateGridFromRoute()
57+
},
58+
{
59+
track: () => this.gridModel.selectedId,
60+
run: () => this.updateRouteFromGrid()
61+
}
62+
);
63+
}
64+
65+
async updateGridFromRoute() {
66+
const {gridModel, BASE_ROUTE} = this,
67+
{name: currRouteName, params} = XH.routerState,
68+
{recordId} = params;
69+
70+
// No-op if not on the current base route.
71+
if (!currRouteName.startsWith(BASE_ROUTE)) return;
72+
73+
if (recordId) {
74+
await gridModel.selectAsync(Number(recordId));
75+
76+
// Check and alert if requested record not found, and clean up route to match.
77+
if (!gridModel.selectedRecord) {
78+
XH.dangerToast(`Record ${recordId} not found.`);
79+
XH.navigate(BASE_ROUTE, {replace: true});
80+
}
81+
} else {
82+
gridModel.clearSelection();
83+
}
84+
}
85+
86+
updateRouteFromGrid() {
87+
const {gridModel, BASE_ROUTE} = this,
88+
{name: currRouteName, params} = XH.routerState,
89+
{selectedId} = gridModel,
90+
{recordId} = params;
91+
92+
// No-op if not on the current base route, or if route and selection already match.
93+
if (!currRouteName.startsWith(BASE_ROUTE) || recordId === selectedId) return;
94+
95+
if (selectedId) {
96+
XH.navigate(
97+
'default.other.simpleRouting.recordId',
98+
{recordId: selectedId},
99+
{replace: true} // avoids adding steps to browser history
100+
);
101+
} else {
102+
XH.navigate('default.other.simpleRouting', {replace: true});
103+
}
104+
}
105+
106+
override async doLoadAsync(loadSpec: LoadSpec) {
107+
const {trades} = await XH.fetchJson({url: 'trade', loadSpec});
108+
this.gridModel.loadData(trades);
109+
}
110+
}

client-app/src/desktop/tabs/panels/BasicPanel.tsx

+20-3
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,16 @@ import React from 'react';
22
import {creates, hoistCmp, XH} from '@xh/hoist/core';
33
import {div, filler, p} from '@xh/hoist/cmp/layout';
44
import {menu, menuItem, popover} from '@xh/hoist/kit/blueprint';
5-
import {wrapper} from '../../common';
65
import {panel} from '@xh/hoist/desktop/cmp/panel';
76
import {select, switchInput} from '@xh/hoist/desktop/cmp/input';
87
import {toolbarSep} from '@xh/hoist/desktop/cmp/toolbar';
98
import {button} from '@xh/hoist/desktop/cmp/button';
109
import {Icon} from '@xh/hoist/icon';
10+
import {wait} from '@xh/hoist/promise';
11+
import {clipboardMenuItem} from '@xh/hoist/desktop/cmp/clipboard';
12+
import {wrapper} from '../../common';
1113
import {usStates} from '../../../core/data';
1214
import {BasicPanelModel} from './BasicPanelModel';
13-
import {wait} from '@xh/hoist/promise';
1415

1516
export const basicPanel = hoistCmp.factory({
1617
model: creates(BasicPanelModel),
@@ -69,11 +70,27 @@ export const basicPanel = hoistCmp.factory({
6970
})
7071
})
7172
],
73+
contextMenu: [
74+
clipboardMenuItem({
75+
text: 'Copy Text',
76+
getCopyText: () => model.demoText.join('\n')
77+
}),
78+
{
79+
text: 'Increase Text Size',
80+
icon: Icon.plusCircle(),
81+
actionFn: () => model.changeTextSize(true)
82+
},
83+
{
84+
text: 'Decrease Text Size',
85+
icon: Icon.minusCircle(),
86+
actionFn: () => model.changeTextSize(false)
87+
}
88+
],
7289
tbar: [
7390
popover({
7491
position: 'bottom-left',
7592
minimal: true,
76-
target: button({
93+
item: button({
7794
icon: Icon.chevronDown(),
7895
text: 'Menu Button'
7996
}),

client-app/src/desktop/tabs/panels/BasicPanelModel.ts

+8
Original file line numberDiff line numberDiff line change
@@ -22,4 +22,12 @@ export class BasicPanelModel extends HoistModel {
2222

2323
'Morbi eget tincidunt ex. Mauris eget egestas nulla. Pellentesque egestas sapien blandit nisi pellentesque varius. Cras dignissim consectetur mauris, eu faucibus quam mattis vel. Curabitur in libero purus. Duis nulla turpis, faucibus sed tristique eget, pretium id nibh. Phasellus sit amet egestas lectus. Donec in aliquet tortor.'
2424
];
25+
26+
changeTextSize(up: boolean) {
27+
const el = document.querySelector('.toolbox-panel-text-reader') as HTMLElement,
28+
fontSize = window.getComputedStyle(el).fontSize,
29+
currentSize = parseFloat(fontSize);
30+
31+
el.style.fontSize = `${currentSize + (up ? 1 : -1)}px`;
32+
}
2533
}

0 commit comments

Comments
 (0)