@xh/hoist 69.0.0-SNAPSHOT.1726689665759 → 69.0.0-SNAPSHOT.1727461290353

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -2,6 +2,14 @@
2
2
 
3
3
  ## 69.0.0-SNAPSHOT - unreleased
4
4
 
5
+ ### 🎁 New Features
6
+
7
+ * `Markdown` now supports a `reactMarkdownOptions` prop to allow passing React Markdown
8
+ props to the underlying `reactMarkdown` instance.
9
+
10
+ ### ⚙️ Technical
11
+ * Misc. Improvements to Cluster Tab in Admin Panel.
12
+
5
13
  ## 68.0.0 - 2024-09-18
6
14
 
7
15
  ### 💥 Breaking Changes (upgrade difficulty: 🟢 LOW - Hoist Core update only)
@@ -11,12 +11,15 @@ import {BaseInstanceModel} from '@xh/hoist/admin/tabs/cluster/BaseInstanceModel'
11
11
  import {GridModel} from '@xh/hoist/cmp/grid';
12
12
  import * as Col from '@xh/hoist/cmp/grid/columns';
13
13
  import {br, fragment} from '@xh/hoist/cmp/layout';
14
- import {LoadSpec, managed, XH} from '@xh/hoist/core';
14
+ import {LoadSpec, managed, PlainObject, XH} from '@xh/hoist/core';
15
15
  import {RecordActionSpec} from '@xh/hoist/data';
16
16
  import {Icon} from '@xh/hoist/icon';
17
- import {isEmpty} from 'lodash';
17
+ import {bindable, makeObservable} from '@xh/hoist/mobx';
18
+ import {first, isEmpty, last} from 'lodash';
18
19
 
19
20
  export class HzObjectModel extends BaseInstanceModel {
21
+ @bindable groupBy: 'type' | 'owner' = 'owner';
22
+
20
23
  clearAction: RecordActionSpec = {
21
24
  text: 'Clear Objects',
22
25
  icon: Icon.reset(),
@@ -35,21 +38,25 @@ export class HzObjectModel extends BaseInstanceModel {
35
38
  selModel: 'multiple',
36
39
  enableExport: true,
37
40
  exportOptions: {filename: exportFilenameWithDate('distributed-objects'), columns: 'ALL'},
38
- sortBy: 'name',
39
- groupBy: 'type',
41
+ sortBy: 'displayName',
42
+ groupBy: this.groupBy,
40
43
  store: {
41
44
  fields: [
42
45
  {name: 'name', type: 'string'},
46
+ {name: 'displayName', type: 'string'},
47
+ {name: 'owner', type: 'string'},
43
48
  {name: 'type', type: 'string', displayName: 'Type'},
44
49
  {name: 'size', type: 'int'},
45
50
  {name: 'lastUpdateTime', type: 'date'},
46
51
  {name: 'lastAccessTime', type: 'date'}
47
52
  ],
48
- idSpec: 'name'
53
+ idSpec: 'name',
54
+ processRawData: o => this.processRawData(o)
49
55
  },
50
56
  columns: [
51
- {field: 'type', hidden: true},
52
- {field: 'name', flex: 1},
57
+ {field: 'displayName', flex: 1},
58
+ {field: 'owner'},
59
+ {field: 'type'},
53
60
  {field: 'size', displayName: 'Entry Count', ...Col.number, width: 130},
54
61
  {
55
62
  ...timestampNoYear,
@@ -65,6 +72,15 @@ export class HzObjectModel extends BaseInstanceModel {
65
72
  contextMenu: [this.clearAction, '-', ...GridModel.defaultContextMenu]
66
73
  });
67
74
 
75
+ constructor() {
76
+ super();
77
+ makeObservable(this);
78
+ this.addReaction({
79
+ track: () => this.groupBy,
80
+ run: v => this.gridModel.setGroupBy(v)
81
+ });
82
+ }
83
+
68
84
  async clearAsync() {
69
85
  const {gridModel} = this;
70
86
  if (
@@ -151,4 +167,26 @@ export class HzObjectModel extends BaseInstanceModel {
151
167
  this.handleLoadException(e, loadSpec);
152
168
  }
153
169
  }
170
+
171
+ //----------------------
172
+ // Implementation
173
+ //----------------------
174
+ private processRawData(obj: PlainObject): PlainObject {
175
+ const tail: string = last(obj.name.split('.')),
176
+ className = first(tail.split('['));
177
+
178
+ const owner = className.endsWith('Service')
179
+ ? className
180
+ : className.startsWith('xh')
181
+ ? 'Hoist'
182
+ : obj.type == 'Cache'
183
+ ? 'Hibernate'
184
+ : 'Other';
185
+
186
+ return {
187
+ displayName: tail,
188
+ owner: owner,
189
+ ...obj
190
+ };
191
+ }
154
192
  }
@@ -9,8 +9,9 @@ import {filler, hframe, placeholder} from '@xh/hoist/cmp/layout';
9
9
  import {storeFilterField} from '@xh/hoist/cmp/store';
10
10
  import {creates, hoistCmp, uses} from '@xh/hoist/core';
11
11
  import {button, exportButton} from '@xh/hoist/desktop/cmp/button';
12
- import {jsonInput} from '@xh/hoist/desktop/cmp/input';
12
+ import {jsonInput, select} from '@xh/hoist/desktop/cmp/input';
13
13
  import {panel} from '@xh/hoist/desktop/cmp/panel';
14
+ import {toolbar} from '@xh/hoist/desktop/cmp/toolbar';
14
15
  import {recordActionBar} from '@xh/hoist/desktop/cmp/record';
15
16
  import {Icon} from '@xh/hoist/icon';
16
17
  import {HzObjectModel} from './HzObjectModel';
@@ -20,26 +21,13 @@ export const hzObjectPanel = hoistCmp.factory({
20
21
 
21
22
  render({model}) {
22
23
  return panel({
23
- bbar: [
24
- recordActionBar({
25
- selModel: model.gridModel.selModel,
26
- actions: [model.clearAction]
24
+ item: hframe(
25
+ panel({
26
+ item: grid({agOptions: {groupDefaultExpanded: 0}}),
27
+ bbar: bbar()
27
28
  }),
28
- '-',
29
- button({
30
- text: 'Clear Hibernate Caches',
31
- icon: Icon.reset(),
32
- intent: 'warning',
33
- tooltip: 'Clear the Hibernate caches using the native Hibernate API',
34
- onClick: () => model.clearHibernateCachesAsync()
35
- }),
36
- filler(),
37
- gridCountLabel({unit: 'objects'}),
38
- '-',
39
- storeFilterField({matchMode: 'any'}),
40
- exportButton()
41
- ],
42
- item: hframe(grid(), detailsPanel()),
29
+ detailsPanel()
30
+ ),
43
31
  mask: 'onLoad',
44
32
  ref: model.viewRef
45
33
  });
@@ -49,16 +37,16 @@ export const hzObjectPanel = hoistCmp.factory({
49
37
  const detailsPanel = hoistCmp.factory({
50
38
  model: uses(HzObjectModel),
51
39
  render({model}) {
52
- const data = model.gridModel.selectedRecord?.raw;
40
+ const record = model.gridModel.selectedRecord;
53
41
  return panel({
54
- title: data ? `Stats: ${data.name}` : 'Stats',
42
+ title: record ? `Stats: ${record.data.displayName}` : 'Stats',
55
43
  icon: Icon.info(),
56
44
  compactHeader: true,
57
45
  modelConfig: {
58
46
  side: 'right',
59
47
  defaultSize: 450
60
48
  },
61
- item: data
49
+ item: record
62
50
  ? panel({
63
51
  item: jsonInput({
64
52
  readonly: true,
@@ -66,10 +54,45 @@ const detailsPanel = hoistCmp.factory({
66
54
  height: '100%',
67
55
  showFullscreenButton: false,
68
56
  editorProps: {lineNumbers: false},
69
- value: model.fmtStats(data)
57
+ value: model.fmtStats(record.raw)
70
58
  })
71
59
  })
72
60
  : placeholder(Icon.grip(), 'Select an object')
73
61
  });
74
62
  }
75
63
  });
64
+
65
+ const bbar = hoistCmp.factory({
66
+ model: uses(HzObjectModel),
67
+ render({model}) {
68
+ return toolbar(
69
+ recordActionBar({
70
+ selModel: model.gridModel.selModel,
71
+ actions: [model.clearAction]
72
+ }),
73
+ '-',
74
+ button({
75
+ text: 'Clear Hibernate Caches',
76
+ icon: Icon.reset(),
77
+ intent: 'warning',
78
+ tooltip: 'Clear the Hibernate caches using the native Hibernate API',
79
+ onClick: () => model.clearHibernateCachesAsync()
80
+ }),
81
+ filler(),
82
+ gridCountLabel({unit: 'objects'}),
83
+ '-',
84
+ select({
85
+ options: [
86
+ {label: 'By Owner', value: 'owner'},
87
+ {label: 'By Type', value: 'type'},
88
+ {label: 'Ungrouped', value: null}
89
+ ],
90
+ width: 125,
91
+ bind: 'groupBy',
92
+ hideDropdownIndicator: true
93
+ }),
94
+ storeFilterField({matchMode: 'any'}),
95
+ exportButton()
96
+ );
97
+ }
98
+ });
@@ -11,12 +11,19 @@ import {BaseInstanceModel} from '@xh/hoist/admin/tabs/cluster/BaseInstanceModel'
11
11
  import {GridModel} from '@xh/hoist/cmp/grid';
12
12
  import {br, fragment} from '@xh/hoist/cmp/layout';
13
13
  import {LoadSpec, managed, XH} from '@xh/hoist/core';
14
- import {RecordActionSpec} from '@xh/hoist/data';
14
+ import {FilterLike, FilterTestFn, RecordActionSpec} from '@xh/hoist/data';
15
15
  import {Icon} from '@xh/hoist/icon';
16
+ import {bindable, makeObservable} from '@xh/hoist/mobx';
16
17
  import {pluralize} from '@xh/hoist/utils/js';
17
- import {isEmpty, lowerFirst} from 'lodash';
18
+ import {capitalize, isEmpty, lowerFirst} from 'lodash';
18
19
 
19
20
  export class ServiceModel extends BaseInstanceModel {
21
+ @bindable
22
+ typeFilter: 'hoist' | 'app' | 'all' = 'all';
23
+
24
+ @bindable.ref
25
+ textFilter: FilterTestFn = null;
26
+
20
27
  clearCachesAction: RecordActionSpec = {
21
28
  text: 'Clear Caches',
22
29
  icon: Icon.reset(),
@@ -57,10 +64,9 @@ export class ServiceModel extends BaseInstanceModel {
57
64
  ]
58
65
  },
59
66
  sortBy: 'displayName',
60
- groupBy: 'provider',
61
67
  columns: [
62
- {field: 'provider', hidden: true},
63
68
  {field: 'displayName', flex: 1},
69
+ {field: 'provider'},
64
70
  {...timestampNoYear, field: 'lastCachesCleared'},
65
71
  {...timestampNoYear, field: 'initializedDate'}
66
72
  ],
@@ -72,6 +78,16 @@ export class ServiceModel extends BaseInstanceModel {
72
78
  ]
73
79
  });
74
80
 
81
+ constructor() {
82
+ super();
83
+ makeObservable(this);
84
+ this.addReaction({
85
+ track: () => [this.textFilter, this.typeFilter],
86
+ run: this.applyFilters,
87
+ fireImmediately: true
88
+ });
89
+ }
90
+
75
91
  async clearCachesAsync(entireCluster: boolean) {
76
92
  const {gridModel, instanceName, loadModel} = this,
77
93
  {selectedRecords} = gridModel;
@@ -132,4 +148,14 @@ export class ServiceModel extends BaseInstanceModel {
132
148
  const displayName = lowerFirst(r.name.replace('hoistCore', ''));
133
149
  return {provider, displayName, ...r};
134
150
  }
151
+
152
+ private applyFilters() {
153
+ const {typeFilter, textFilter} = this;
154
+ const filters: FilterLike[] = [textFilter];
155
+
156
+ if (typeFilter == 'hoist' || typeFilter == 'app') {
157
+ filters.push({field: 'provider', op: '=', value: capitalize(typeFilter)});
158
+ }
159
+ this.gridModel.store.setFilter(filters);
160
+ }
135
161
  }
@@ -8,10 +8,12 @@ import {detailsPanel} from '@xh/hoist/admin/tabs/cluster/services/DetailsPanel';
8
8
  import {grid, gridCountLabel} from '@xh/hoist/cmp/grid';
9
9
  import {filler, hframe} from '@xh/hoist/cmp/layout';
10
10
  import {storeFilterField} from '@xh/hoist/cmp/store';
11
- import {creates, hoistCmp} from '@xh/hoist/core';
11
+ import {creates, hoistCmp, uses} from '@xh/hoist/core';
12
12
  import {exportButton} from '@xh/hoist/desktop/cmp/button';
13
13
  import {panel} from '@xh/hoist/desktop/cmp/panel';
14
14
  import {recordActionBar} from '@xh/hoist/desktop/cmp/record';
15
+ import {toolbar} from '@xh/hoist/desktop/cmp/toolbar';
16
+ import {select} from '@xh/hoist/desktop/cmp/input';
15
17
  import {ServiceModel} from './ServiceModel';
16
18
 
17
19
  export const servicePanel = hoistCmp.factory({
@@ -19,25 +21,17 @@ export const servicePanel = hoistCmp.factory({
19
21
 
20
22
  render({model}) {
21
23
  return panel({
22
- bbar: [
23
- recordActionBar({
24
- selModel: model.gridModel.selModel,
25
- actions: [model.clearCachesAction, model.clearClusterCachesAction]
26
- }),
27
- filler(),
28
- gridCountLabel({unit: 'service'}),
29
- '-',
30
- storeFilterField({matchMode: 'any'}),
31
- exportButton()
32
- ],
33
24
  item: hframe(
34
- grid({
35
- flex: 1,
36
- agOptions: {
37
- groupRowRendererParams: {
38
- innerRenderer: params => params.value + ' Services'
25
+ panel({
26
+ item: grid({
27
+ flex: 1,
28
+ agOptions: {
29
+ groupRowRendererParams: {
30
+ innerRenderer: params => params.value + ' Services'
31
+ }
39
32
  }
40
- }
33
+ }),
34
+ bbar: bbar()
41
35
  }),
42
36
  detailsPanel()
43
37
  ),
@@ -46,3 +40,34 @@ export const servicePanel = hoistCmp.factory({
46
40
  });
47
41
  }
48
42
  });
43
+
44
+ const bbar = hoistCmp.factory({
45
+ model: uses(ServiceModel),
46
+ render({model}) {
47
+ return toolbar(
48
+ recordActionBar({
49
+ selModel: model.gridModel.selModel,
50
+ actions: [model.clearCachesAction, model.clearClusterCachesAction]
51
+ }),
52
+ filler(),
53
+ gridCountLabel({unit: 'service'}),
54
+ '-',
55
+ select({
56
+ options: [
57
+ {value: 'all', label: 'All'},
58
+ {value: 'app', label: 'App Only'},
59
+ {value: 'hoist', label: 'Hoist Only'}
60
+ ],
61
+ width: 125,
62
+ bind: 'typeFilter',
63
+ hideDropdownIndicator: true
64
+ }),
65
+ storeFilterField({
66
+ matchMode: 'any',
67
+ autoApply: false,
68
+ onFilterChange: f => (model.textFilter = f)
69
+ }),
70
+ exportButton()
71
+ );
72
+ }
73
+ });
@@ -3,9 +3,12 @@ import { GridModel } from '@xh/hoist/cmp/grid';
3
3
  import { LoadSpec } from '@xh/hoist/core';
4
4
  import { RecordActionSpec } from '@xh/hoist/data';
5
5
  export declare class HzObjectModel extends BaseInstanceModel {
6
+ groupBy: 'type' | 'owner';
6
7
  clearAction: RecordActionSpec;
7
8
  gridModel: GridModel;
9
+ constructor();
8
10
  clearAsync(): Promise<void>;
9
11
  clearHibernateCachesAsync(): Promise<void>;
10
12
  doLoadAsync(loadSpec: LoadSpec): Promise<void>;
13
+ private processRawData;
11
14
  }
@@ -1,12 +1,16 @@
1
1
  import { BaseInstanceModel } from '@xh/hoist/admin/tabs/cluster/BaseInstanceModel';
2
2
  import { GridModel } from '@xh/hoist/cmp/grid';
3
3
  import { LoadSpec } from '@xh/hoist/core';
4
- import { RecordActionSpec } from '@xh/hoist/data';
4
+ import { FilterTestFn, RecordActionSpec } from '@xh/hoist/data';
5
5
  export declare class ServiceModel extends BaseInstanceModel {
6
+ typeFilter: 'hoist' | 'app' | 'all';
7
+ textFilter: FilterTestFn;
6
8
  clearCachesAction: RecordActionSpec;
7
9
  clearClusterCachesAction: RecordActionSpec;
8
10
  gridModel: GridModel;
11
+ constructor();
9
12
  clearCachesAsync(entireCluster: boolean): Promise<void>;
10
13
  doLoadAsync(loadSpec: LoadSpec): Promise<void>;
11
14
  private processRawData;
15
+ private applyFilters;
12
16
  }
@@ -1,17 +1,13 @@
1
1
  /// <reference types="react" />
2
2
  import { HoistProps } from '@xh/hoist/core';
3
- import { Components } from 'react-markdown';
3
+ import { Options } from 'react-markdown';
4
4
  interface MarkdownProps extends HoistProps {
5
5
  /** Markdown formatted string to render. */
6
6
  content: string;
7
- /**
8
- * Map of html tag to tag or functional component to control rendering of standard html
9
- * elements. See https://www.npmjs.com/package/react-markdown/v/8.0.6#appendix-b-components
10
- * for details.
11
- */
12
- components?: Components;
13
7
  /** True (default) to render new lines with <br/> tags. */
14
8
  lineBreaks?: boolean;
9
+ /** Escape hatch to provide additional options to the React Markdown implementation */
10
+ reactMarkdownOptions?: Partial<Options>;
15
11
  }
16
12
  /**
17
13
  * Render Markdown formatted strings as HTML (e.g. **foo** becomes <strong>foo</strong>).
@@ -6,7 +6,7 @@
6
6
  */
7
7
  import {hoistCmp, HoistProps} from '@xh/hoist/core';
8
8
  import {reactMarkdown} from '@xh/hoist/kit/react-markdown';
9
- import {Components} from 'react-markdown';
9
+ import {Options} from 'react-markdown';
10
10
  import remarkBreaks from 'remark-breaks';
11
11
  import remarkGfm from 'remark-gfm';
12
12
  import {PluggableList} from 'unified/lib';
@@ -15,15 +15,11 @@ interface MarkdownProps extends HoistProps {
15
15
  /** Markdown formatted string to render. */
16
16
  content: string;
17
17
 
18
- /**
19
- * Map of html tag to tag or functional component to control rendering of standard html
20
- * elements. See https://www.npmjs.com/package/react-markdown/v/8.0.6#appendix-b-components
21
- * for details.
22
- */
23
- components?: Components;
24
-
25
18
  /** True (default) to render new lines with <br/> tags. */
26
19
  lineBreaks?: boolean;
20
+
21
+ /** Escape hatch to provide additional options to the React Markdown implementation */
22
+ reactMarkdownOptions?: Partial<Options>;
27
23
  }
28
24
 
29
25
  /**
@@ -34,13 +30,18 @@ interface MarkdownProps extends HoistProps {
34
30
  */
35
31
  export const [Markdown, markdown] = hoistCmp.withFactory<MarkdownProps>({
36
32
  displayName: 'Markdown',
37
- render({content, lineBreaks = true, components = {}}) {
38
- const remarkPlugins: PluggableList = [remarkGfm];
33
+ render({content, lineBreaks = true, reactMarkdownOptions = {}}) {
34
+ // add default remark plugins, ensure app provided takes precedence
35
+ const remarkPlugins: PluggableList = [],
36
+ appRemarkPlugins = reactMarkdownOptions.remarkPlugins;
37
+ if (appRemarkPlugins) remarkPlugins.push(...appRemarkPlugins);
39
38
  if (lineBreaks) remarkPlugins.push(remarkBreaks);
39
+ remarkPlugins.push(remarkGfm);
40
+
40
41
  return reactMarkdown({
41
42
  item: content,
42
- remarkPlugins,
43
- components
43
+ ...reactMarkdownOptions,
44
+ remarkPlugins
44
45
  });
45
46
  }
46
47
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xh/hoist",
3
- "version": "69.0.0-SNAPSHOT.1726689665759",
3
+ "version": "69.0.0-SNAPSHOT.1727461290353",
4
4
  "description": "Hoist add-on for building and deploying React Applications.",
5
5
  "repository": "github:xh/hoist-react",
6
6
  "homepage": "https://xh.io",