dazscript-framework 0.1.15 → 0.1.16

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.
Files changed (82) hide show
  1. package/README.md +365 -93
  2. package/babel.config.js +33 -33
  3. package/dist/babel/trace-babel-plugin.js +243 -243
  4. package/dist/babel/trace-log-babel-plugin.js +164 -164
  5. package/dist/scripts/install-generator.js +185 -185
  6. package/package.json +72 -70
  7. package/src/common/dz-dump.ts +120 -120
  8. package/src/common/log.ts +56 -56
  9. package/src/common/trace.ts +26 -26
  10. package/src/core/action-decorator.ts +15 -15
  11. package/src/core/base-script.ts +30 -30
  12. package/src/core/custom-action.ts +11 -11
  13. package/src/core/global.ts +4 -4
  14. package/src/dialog/basic-dialog.ts +40 -32
  15. package/src/dialog/builders/button-builder.ts +52 -52
  16. package/src/dialog/builders/checkbox-builder.ts +29 -29
  17. package/src/dialog/builders/color-picker-builder.ts +35 -35
  18. package/src/dialog/builders/combo-box-builder.ts +38 -35
  19. package/src/dialog/builders/combo-edit-builder.ts +35 -35
  20. package/src/dialog/builders/dialog-builder.ts +49 -49
  21. package/src/dialog/builders/groupbox-builder.ts +121 -82
  22. package/src/dialog/builders/label-builder.ts +33 -28
  23. package/src/dialog/builders/layout-builder.ts +59 -59
  24. package/src/dialog/builders/line-edit-builder.ts +124 -119
  25. package/src/dialog/builders/list-box-builder.ts +103 -0
  26. package/src/dialog/builders/list-view-builder.ts +396 -328
  27. package/src/dialog/builders/node-selection-builder.ts +55 -55
  28. package/src/dialog/builders/path-combo-box-builder.ts +24 -24
  29. package/src/dialog/builders/popup-menu-builder.ts +46 -46
  30. package/src/dialog/builders/radio-builder.ts +42 -42
  31. package/src/dialog/builders/slider-builder.ts +39 -22
  32. package/src/dialog/builders/splitter-builder.ts +88 -88
  33. package/src/dialog/builders/tab-builder.ts +120 -106
  34. package/src/dialog/builders/widget-builder.ts +136 -124
  35. package/src/dialog/builders/widgets-builder.ts +140 -135
  36. package/src/dialog/input-dialog.ts +30 -27
  37. package/src/dialog/input-validator.ts +8 -8
  38. package/src/dialog/selection-dialog.ts +47 -0
  39. package/src/dialog/shared.ts +8 -8
  40. package/src/helpers/action-helper.ts +214 -81
  41. package/src/helpers/array-helper.ts +170 -145
  42. package/src/helpers/camera-helper.ts +12 -12
  43. package/src/helpers/custom-action-helper.ts +176 -176
  44. package/src/helpers/directory-helper.ts +14 -0
  45. package/src/helpers/file-helper.ts +90 -90
  46. package/src/helpers/http-helper.ts +186 -0
  47. package/src/helpers/input-helper.ts +18 -18
  48. package/src/helpers/list-view-helper.ts +105 -89
  49. package/src/helpers/menu-helper.ts +28 -28
  50. package/src/helpers/message-box-helper.ts +25 -15
  51. package/src/helpers/node-helper.ts +461 -236
  52. package/src/helpers/number-helper.ts +10 -10
  53. package/src/helpers/numeric-property-helper.ts +91 -91
  54. package/src/helpers/object-helper.ts +2 -2
  55. package/src/helpers/pane-helper.ts +28 -20
  56. package/src/helpers/progress-helper.ts +51 -33
  57. package/src/helpers/property-helper.ts +61 -52
  58. package/src/helpers/record-helper.ts +16 -16
  59. package/src/helpers/scene-helper.ts +144 -94
  60. package/src/helpers/script-helper.ts +15 -15
  61. package/src/helpers/skeleton-helper.ts +26 -26
  62. package/src/helpers/splitter-helper.ts +8 -8
  63. package/src/helpers/string-helper.ts +39 -31
  64. package/src/helpers/surface-helper.ts +5 -5
  65. package/src/helpers/undo-helper.ts +8 -8
  66. package/src/helpers/viewport-helper.ts +21 -8
  67. package/src/lib/delayed.ts +38 -38
  68. package/src/lib/dictionary.ts +3 -0
  69. package/src/lib/frame-keys.ts +67 -67
  70. package/src/lib/guid.ts +2 -2
  71. package/src/lib/menu-item.ts +10 -9
  72. package/src/lib/observable.ts +147 -94
  73. package/src/lib/set.ts +50 -25
  74. package/src/lib/settings.ts +112 -104
  75. package/src/lib/tree-node.ts +149 -145
  76. package/src/samples/config.ts +2 -2
  77. package/src/samples/hello-world.dsa.ts +12 -12
  78. package/src/samples/sample-dialog.dsa.ts +51 -51
  79. package/src/samples/sample-dialog.ts +48 -48
  80. package/src/shared/set-keyboard-shortcut.ts +146 -102
  81. package/tsconfig.json +116 -116
  82. package/webpack.config.js +70 -70
@@ -1,328 +1,396 @@
1
- import { warn } from '@dsf/common/log';
2
- import { clearColumns, filter, getDataItem, setDataItem } from '@dsf/helpers/list-view-helper';
3
- import { Delayed } from '@dsf/lib/delayed';
4
- import { Observable } from '@dsf/lib/observable';
5
- import { TreeNode } from '@dsf/lib/tree-node';
6
- import { IWidgetBuilder, createWidget } from './widget-builder';
7
- import { WidgetBuilderContext } from './widgets-builder';
8
-
9
- type ListViewFilterOptions = {
10
- keywords: Observable<string>,
11
- field: (listItem: DzListViewItem) => string,
12
- selectOnFilter?: boolean,
13
- filters?: (viewItem: DzListViewItem) => boolean,
14
- delay?: { min: number, max: number }
15
- }
16
-
17
- export enum ListViewRefreshOptions {
18
- All,
19
- Filters
20
- }
21
-
22
- export class ListViewBuilder<TItem, TData> implements IWidgetBuilder<DzListView> {
23
-
24
- private readonly context: ListViewBuilderContext<TItem, TData>
25
-
26
- constructor(dialogContext: WidgetBuilderContext) {
27
- this.context = new ListViewBuilderContext(dialogContext)
28
- }
29
-
30
- visible(value: boolean | Observable<boolean>): this {
31
- this.context.visible = typeof value === 'boolean'
32
- ? new Observable(value)
33
- : value
34
- return this
35
- }
36
-
37
- /**
38
- * Binds the list rows to a collection of items
39
- * @param items
40
- * @returns
41
- */
42
- items(items: Observable<TreeNode<TItem>[]>): ListViewBindBuilder<TItem, TData> {
43
- this.context.items = items
44
- return new ListViewBindBuilder(this.context)
45
- }
46
-
47
- /**
48
- * Build an item row with the provided function
49
- * @param build the function to use for building a row
50
- * @returns returns a ListViewItem or DzCheckListItem
51
- */
52
- row(build: (item: TreeNode<TItem>, parent: DzListView | DzListViewItem, id?: number) => DzListViewItem): this {
53
- this.context.rowBuilder = build
54
- return this
55
- }
56
-
57
- sorting(sort: boolean | number): this {
58
- if (typeof sort === 'boolean')
59
- this.context.sorting = sort ? 0 : -1
60
- else
61
- this.context.sorting = sort
62
- return this
63
- }
64
-
65
- expanded(onOff: boolean | Observable<boolean>): this {
66
- if (typeof onOff === 'boolean')
67
- this.context.expanded = new Observable(onOff)
68
- else
69
- this.context.expanded = onOff
70
- return this
71
- }
72
-
73
- flat(onOff: boolean | Observable<boolean>): this {
74
- this.context.flat = typeof onOff === 'boolean'
75
- ? new Observable(onOff)
76
- : onOff
77
- return this
78
- }
79
-
80
- doubleClicked(bind: Observable<TData>): this {
81
- this.context.doubleClicked = bind
82
- return this
83
- }
84
-
85
- refresh(when: Observable<any>, what: ListViewRefreshOptions = ListViewRefreshOptions.All): this {
86
- this.context.refreshWhen = when
87
- this.context.refreshWhat = what
88
- return this
89
- }
90
-
91
- contextMenu(fn: (listView: DzListView, item: DzListViewItem, pos: Point) => DzPopupMenu): this {
92
- this.context.contextMenu = fn
93
- return this
94
- }
95
-
96
- build(then?: (listView: DzListView) => void): DzListView {
97
- let listView = build(this.context)
98
- then?.(listView)
99
- return listView
100
- }
101
- }
102
-
103
- class ListViewBuilderContext<TItem, TData> {
104
- columns: Observable<string[]> = new Observable([])
105
- columnsWidth: (index: number, width: number) => number
106
- items: Observable<TreeNode<TItem>[]> = new Observable([])
107
- text: (item: TreeNode<TItem>) => string[]
108
- data: (item: TreeNode<TItem>) => TData
109
- sorting: number = 0
110
- selected: Observable<TData>
111
- filter: ListViewFilterOptions
112
- doubleClicked: Observable<TData>
113
- rowBuilder: (item: TreeNode<TItem>, parent: DzListView | DzListViewItem, id?: number) => DzListViewItem
114
- contextMenu: (listView: DzListView, item: DzListViewItem, pos: Point) => DzPopupMenu
115
- refreshWhen: Observable<void>
116
- expanded: Observable<boolean>
117
- visible: Observable<boolean> = new Observable(true)
118
- decorated: boolean
119
- flat: Observable<boolean>
120
- refreshWhat: ListViewRefreshOptions = ListViewRefreshOptions.All
121
- constructor(public readonly dialogContext: WidgetBuilderContext) { }
122
- }
123
-
124
- class ListViewBindBuilder<TItem, TData> {
125
- constructor(private context: ListViewBuilderContext<TItem, TData>) { }
126
-
127
- /**
128
- * Sepecify the text to display on each column
129
- * @param columns
130
- * @returns
131
- */
132
- columns(columns: string[] | Observable<string[]>, width?: (index: number, width: number) => number): this {
133
- this.context.columns = columns instanceof Observable
134
- ? columns
135
- : new Observable(columns)
136
- this.context.columnsWidth = width
137
- return this
138
- }
139
-
140
- /**
141
- * Callback for extracting the text to display on each column from every item in the list
142
- * @param from the callback function to extract the text for each item
143
- * @returns a string array for every column text to be display for a given item
144
- */
145
- text(from: (item: TreeNode<TItem>) => string[]): this {
146
- this.context.text = from
147
- return this
148
- }
149
-
150
- /**
151
- * Callback to specify which part of the item will be stored in each row as data. It can be
152
- * the whole item or a property of the item
153
- * @param from the callback function to specify what to store as data
154
- * @returns the data to be stored in each row for a given item
155
- */
156
- data(from: (item: TreeNode<TItem>) => TData): this {
157
- this.context.data = from
158
- return this
159
- }
160
-
161
- /**
162
- * Bind the data of a selected row in the list to an object
163
- * @param bind
164
- * @returns
165
- */
166
- selected(bind: Observable<TData>): this {
167
- this.context.selected = bind
168
- return this
169
- }
170
-
171
- filter(options: ListViewFilterOptions): this {
172
- this.context.filter = options
173
- return this
174
- }
175
-
176
- build(then?: (listView: DzListView) => void): DzListView {
177
- let listView = build(this.context)
178
- then?.(listView)
179
- return listView
180
- }
181
- }
182
-
183
- const build = <TItem, TData>(context: ListViewBuilderContext<TItem, TData>): DzListView => {
184
- const listView = createWidget(context.dialogContext).build(DzListView)
185
- let rowId = -1
186
-
187
- if (context.visible) {
188
- if (context.visible.value === false)
189
- listView.hide()
190
- context.visible.connect((visible) => {
191
- if (visible)
192
- listView.show()
193
- else
194
- listView.hide()
195
- })
196
- }
197
-
198
- const buildColumns = (columns: string[]) => {
199
- clearColumns(listView)
200
- columns.forEach(column => {
201
- listView.addColumn(column)
202
- })
203
- }
204
-
205
- const buildItem = (item: TreeNode<TItem>, parent: DzListView | DzListViewItem) => {
206
- rowId++
207
- parent = context.flat?.value === true ? listView : parent
208
-
209
- let listItem = context.rowBuilder ? context.rowBuilder(item, parent, rowId) : new DzListViewItem(parent, rowId)
210
- if (!listItem) return
211
-
212
- listItem.open = context.expanded?.value === true
213
-
214
- context.text(item).forEach((text, idx) => {
215
- let data = context.data?.(item)
216
- if (data) {
217
- setDataItem(listItem, data)
218
- }
219
- if (!text || idx >= context.columns.value.length)
220
- return;
221
- listItem.setText(idx, text)
222
- })
223
-
224
- item.children.forEach((child) => {
225
- context.decorated = true
226
- buildItem(child, listItem)
227
- })
228
- }
229
-
230
- const filterList = (keywords?: string) => {
231
- filter(listView, context.filter.field, keywords ?? context.filter?.keywords?.value, { selectOnFilter: context.filter.selectOnFilter ?? true, filters: context.filter.filters })
232
- }
233
-
234
- const buildList = (items: TreeNode<TItem>[], selectedId?: number) => {
235
- rowId = -1
236
- if (!context.text)
237
- return warn('No text function provided for list builder')
238
-
239
- listView.clear()
240
- context.columns?.value.forEach((_, index) => {
241
- listView.setColumnWidth(index, 0)
242
- })
243
- listView.allColumnsShowFocus = true
244
-
245
- if (context.visible.value === false)
246
- return
247
-
248
- if (!items) return
249
-
250
- items.forEach((item) => {
251
- buildItem(item, listView)
252
- })
253
-
254
- if (context.columnsWidth) {
255
- context.columns?.value.forEach((_, index) => {
256
- listView.setColumnWidth(index, context.columnsWidth(index, listView.columnWidth(index)))
257
- })
258
- }
259
-
260
- listView.rootIsDecorated = context.decorated
261
- listView.setSorting(context.sorting)
262
- if (context.sorting >= 0) listView.sort()
263
-
264
- if (context.filter?.keywords.value || context.filter?.filters)
265
- filterList()
266
-
267
- if (selectedId) {
268
- listView.getItems(DzListView.All).forEach(item => {
269
- if (item.id === selectedId) {
270
- listView.setSelected(item, true)
271
- listView.setCurrentItem(item)
272
- listView.ensureItemVisible(item)
273
- }
274
- })
275
- }
276
- }
277
-
278
- const onSelectionChanged = (callback: (item: DzListViewItem, data: TData) => void) => {
279
- (listView as any)["selectionChanged()"].scriptConnect(() => {
280
- callback(listView.selectedItem(), getDataItem(listView.selectedItem()))
281
- })
282
- }
283
-
284
- buildColumns(context.columns.value)
285
- context.columns.connect((columns) => {
286
- buildColumns(columns)
287
- })
288
-
289
- buildList(context.items?.value)
290
- context.items.connect((items) => {
291
- buildList(items, listView.selectedItem()?.id)
292
- })
293
-
294
- context.refreshWhen?.connect(() => {
295
- if (context.refreshWhat === ListViewRefreshOptions.All)
296
- buildList(context.items?.value, listView.selectedItem()?.id)
297
- else {
298
- filterList()
299
- }
300
-
301
- })
302
-
303
- if (context.selected) {
304
- onSelectionChanged((_, data) => {
305
- context.selected.value = data
306
- })
307
- }
308
-
309
- if (context.filter) {
310
- context.filter.keywords.connect((keywords) => {
311
- new Delayed(() => {
312
- filterList(keywords)
313
- }, context.filter.delay?.min ?? 100, context.filter.delay?.max ?? 400).trigger()
314
- })
315
- }
316
-
317
- if (context.doubleClicked) {
318
- listView.doubleClicked.scriptConnect((listItem) => {
319
- context.doubleClicked.value = listItem?.getDataItem('data')
320
- })
321
- }
322
-
323
- listView.contextMenuRequested.scriptConnect((item: DzListViewItem, pos: Point) => {
324
- context.contextMenu?.(listView, item, pos).exec(pos.cursorPos(), 0)
325
- })
326
-
327
- return listView
328
- }
1
+ import { warn } from '@dsf/common/log';
2
+ import { clearColumns, filter, getDataItem, setDataItem } from '@dsf/helpers/list-view-helper';
3
+ import { Delayed } from '@dsf/lib/delayed';
4
+ import { Observable } from '@dsf/lib/observable';
5
+ import { TreeNode } from '@dsf/lib/tree-node';
6
+ import { IWidgetBuilder, createWidget } from './widget-builder';
7
+ import { WidgetBuilderContext } from './widgets-builder';
8
+
9
+ type ListViewFilterOptions = {
10
+ keywords: Observable<string>,
11
+ field: (listItem: DzListViewItem) => string,
12
+ selectOnFilter?: boolean,
13
+ filters?: (viewItem: DzListViewItem) => boolean,
14
+ delay?: { min: number, max: number }
15
+ }
16
+
17
+ export enum ListViewRefreshOptions {
18
+ All,
19
+ Filters
20
+ }
21
+
22
+ export class ListViewBuilder<TItem, TData> implements IWidgetBuilder<DzListView> {
23
+
24
+ private readonly context: ListViewBuilderContext<TItem, TData>
25
+
26
+ constructor(dialogContext: WidgetBuilderContext) {
27
+ this.context = new ListViewBuilderContext(dialogContext)
28
+ }
29
+
30
+ visible(value: boolean | Observable<boolean>): this {
31
+ this.context.visible = typeof value === 'boolean'
32
+ ? new Observable(value)
33
+ : value
34
+ return this
35
+ }
36
+
37
+ height(value: number): this {
38
+ this.context.height = value
39
+ return this
40
+ }
41
+
42
+ minHeight(value: number): this {
43
+ this.context.minHeight = value
44
+ return this
45
+ }
46
+
47
+ /**
48
+ * Binds the list rows to a collection of items
49
+ * @param items
50
+ * @returns
51
+ */
52
+ items(items: Observable<TreeNode<TItem>[]>): ListViewBindBuilder<TItem, TData> {
53
+ this.context.items = items
54
+ return new ListViewBindBuilder(this.context)
55
+ }
56
+
57
+ /**
58
+ * Build an item row with the provided function
59
+ * @param build the function to use for building a row
60
+ * @returns returns a ListViewItem or DzCheckListItem
61
+ */
62
+ row(build: (item: TreeNode<TItem>, parent: DzListView | DzListViewItem, id?: number) => DzListViewItem): this {
63
+ this.context.rowBuilder = build
64
+ return this
65
+ }
66
+
67
+ sorting(sort: boolean | number, order: 'asc' | 'desc' = 'asc'): this {
68
+ if (typeof sort === 'boolean')
69
+ this.context.sorting = sort ? 0 : -1
70
+ else
71
+ this.context.sorting = sort
72
+ this.context.sortAscending = order === 'asc'
73
+ return this
74
+ }
75
+
76
+ sortOnBuild(onOff: boolean = true): this {
77
+ this.context.sortOnBuild = onOff
78
+ return this
79
+ }
80
+
81
+ expanded(onOff: boolean | Observable<boolean>): this {
82
+ if (typeof onOff === 'boolean')
83
+ this.context.expanded = new Observable(onOff)
84
+ else
85
+ this.context.expanded = onOff
86
+ return this
87
+ }
88
+
89
+ flat(onOff: boolean | Observable<boolean>): this {
90
+ this.context.flat = typeof onOff === 'boolean'
91
+ ? new Observable(onOff)
92
+ : onOff
93
+ return this
94
+ }
95
+
96
+ doubleClicked(bind: Observable<TData>): this {
97
+ this.context.doubleClicked = bind
98
+ return this
99
+ }
100
+
101
+ refresh(when: Observable<any>, what: ListViewRefreshOptions = ListViewRefreshOptions.All): this {
102
+ this.context.refreshWhen = when
103
+ this.context.refreshWhat = what
104
+ return this
105
+ }
106
+
107
+ contextMenu(fn: (listView: DzListView, item: DzListViewItem, pos: Point) => DzPopupMenu): this {
108
+ this.context.contextMenu = fn
109
+ return this
110
+ }
111
+
112
+ build(then?: (listView: DzListView) => void): DzListView {
113
+ let listView = build(this.context)
114
+ then?.(listView)
115
+ return listView
116
+ }
117
+ }
118
+
119
+ class ListViewBuilderContext<TItem, TData> {
120
+ columns: Observable<string[]> = new Observable([])
121
+ columnsWidth?: (index: number, width: number, listView: any) => number
122
+ items: Observable<TreeNode<TItem>[]> = new Observable([])
123
+ text: (item: TreeNode<TItem>) => string[]
124
+ data: (item: TreeNode<TItem>) => TData
125
+ sorting: number = 0
126
+ sortAscending: boolean = true
127
+ sortOnBuild: boolean = true
128
+ selected: Observable<TData>
129
+ filter: ListViewFilterOptions
130
+ doubleClicked: Observable<TData>
131
+ rowBuilder: (item: TreeNode<TItem>, parent: DzListView | DzListViewItem, id?: number) => DzListViewItem
132
+ contextMenu: (listView: DzListView, item: DzListViewItem, pos: Point) => DzPopupMenu
133
+ refreshWhen: Observable<void>
134
+ expanded: Observable<boolean>
135
+ visible: Observable<boolean> = new Observable(true)
136
+ height: number | null = null
137
+ minHeight: number | null = null
138
+ decorated: boolean
139
+ flat: Observable<boolean>
140
+ refreshWhat: ListViewRefreshOptions = ListViewRefreshOptions.All
141
+ constructor(public readonly dialogContext: WidgetBuilderContext) { }
142
+ }
143
+
144
+ class ListViewBindBuilder<TItem, TData> {
145
+ constructor(private context: ListViewBuilderContext<TItem, TData>) { }
146
+
147
+ /**
148
+ * Sepecify the text to display on each column
149
+ * @param columns
150
+ * @returns
151
+ */
152
+ columns(columns: string[] | Observable<string[]>, width?: (index: number, width: number, listView: any) => number): this {
153
+ this.context.columns = columns instanceof Observable
154
+ ? columns
155
+ : new Observable(columns)
156
+ this.context.columnsWidth = width
157
+ return this
158
+ }
159
+
160
+ /**
161
+ * Callback for extracting the text to display on each column from every item in the list
162
+ * @param from the callback function to extract the text for each item
163
+ * @returns a string array for every column text to be display for a given item
164
+ */
165
+ text(from: (item: TreeNode<TItem>) => string[]): this {
166
+ this.context.text = from
167
+ return this
168
+ }
169
+
170
+ /**
171
+ * Callback to specify which part of the item will be stored in each row as data. It can be
172
+ * the whole item or a property of the item
173
+ * @param from the callback function to specify what to store as data
174
+ * @returns the data to be stored in each row for a given item
175
+ */
176
+ data(from: (item: TreeNode<TItem>) => TData): this {
177
+ this.context.data = from
178
+ return this
179
+ }
180
+
181
+ /**
182
+ * Bind the data of a selected row in the list to an object
183
+ * @param bind
184
+ * @returns
185
+ */
186
+ selected(bind: Observable<TData>): this {
187
+ this.context.selected = bind
188
+ return this
189
+ }
190
+
191
+ filter(options: ListViewFilterOptions): this {
192
+ this.context.filter = options
193
+ return this
194
+ }
195
+
196
+ build(then?: (listView: DzListView) => void): DzListView {
197
+ let listView = build(this.context)
198
+ then?.(listView)
199
+ return listView
200
+ }
201
+ }
202
+
203
+ const build = <TItem, TData>(context: ListViewBuilderContext<TItem, TData>): DzListView => {
204
+ const listView = createWidget(context.dialogContext).build(DzListView)
205
+ let rowId = -1
206
+
207
+ if (context.visible) {
208
+ if (context.visible.value === false)
209
+ listView.hide()
210
+ context.visible.connect((visible) => {
211
+ if (visible)
212
+ listView.show()
213
+ else
214
+ listView.hide()
215
+ })
216
+ }
217
+
218
+ if (context.height !== null) {
219
+ listView.height = context.height
220
+ }
221
+
222
+ if (context.minHeight !== null) {
223
+ listView.minHeight = context.minHeight
224
+ }
225
+
226
+ const buildColumns = (columns: string[]) => {
227
+ clearColumns(listView)
228
+ columns.forEach(column => {
229
+ listView.addColumn(column)
230
+ })
231
+ }
232
+
233
+ const buildItem = (item: TreeNode<TItem>, parent: DzListView | DzListViewItem) => {
234
+ rowId++
235
+ parent = context.flat?.value === true ? listView : parent
236
+
237
+ let listItem = context.rowBuilder ? context.rowBuilder(item, parent, rowId) : new DzListViewItem(parent, rowId)
238
+ if (!listItem) return
239
+
240
+ listItem.open = context.expanded?.value === true
241
+ let data = context.data ? context.data(item) : item.value
242
+ if (data != null) {
243
+ setDataItem(listItem, data)
244
+ }
245
+
246
+ context.text(item).forEach((text, idx) => {
247
+ if (!text || idx >= context.columns.value.length)
248
+ return;
249
+ listItem.setText(idx, text)
250
+ })
251
+
252
+ item.children.forEach((child) => {
253
+ context.decorated = true
254
+ buildItem(child, listItem)
255
+ })
256
+ }
257
+
258
+ const filterList = (keywords?: string) => {
259
+ filter(listView, context.filter.field, keywords ?? context.filter?.keywords?.value, { selectOnFilter: context.filter.selectOnFilter ?? true, filters: context.filter.filters })
260
+ }
261
+ let delayedFilter: Delayed | null = null
262
+
263
+ const buildList = (items: TreeNode<TItem>[], selectedId?: number) => {
264
+ rowId = -1
265
+ context.decorated = false
266
+ if (!context.text)
267
+ return warn('No text function provided for list builder')
268
+
269
+ listView.clear()
270
+ context.columns?.value.forEach((_, index) => {
271
+ listView.setColumnWidth(index, 0)
272
+ })
273
+ listView.allColumnsShowFocus = true
274
+
275
+ if (context.visible.value === false)
276
+ return
277
+
278
+ if (!items) return
279
+
280
+ items.forEach((item) => {
281
+ buildItem(item, listView)
282
+ })
283
+
284
+ if (context.columnsWidth) {
285
+ context.columns?.value.forEach((_, index) => {
286
+ listView.setColumnWidth(index, context.columnsWidth!(index, listView.columnWidth(index), listView))
287
+ })
288
+ }
289
+
290
+ listView.rootIsDecorated = context.decorated
291
+ if (context.sorting >= 0 && context.sortOnBuild) listView.sort()
292
+
293
+ if (context.filter?.keywords.value || context.filter?.filters)
294
+ filterList()
295
+
296
+ if (selectedId) {
297
+ listView.getItems(DzListView.All).forEach(item => {
298
+ if (item.id === selectedId) {
299
+ listView.setSelected(item, true)
300
+ listView.setCurrentItem(item)
301
+ listView.ensureItemVisible(item)
302
+ }
303
+ })
304
+ }
305
+ }
306
+
307
+ const updateList = (items: TreeNode<TItem>[]) => {
308
+ if (!context.text) return
309
+ if (!items) return
310
+
311
+ const existingByPath: Record<string, DzListViewItem> = {}
312
+ listView.getItems(DzListView.All).forEach((li) => {
313
+ const data = getDataItem<any>(li)
314
+ const path = data?.id ?? String(li.id)
315
+ existingByPath[path] = li
316
+ })
317
+
318
+ const incomingPaths: Record<string, boolean> = {}
319
+ items.forEach((item) => {
320
+ incomingPaths[item.path] = true
321
+ const existing = existingByPath[item.path]
322
+ if (existing) {
323
+ context.text(item).forEach((text, idx) => {
324
+ existing.setText(idx, text ?? '')
325
+ })
326
+ const data = context.data?.(item)
327
+ if (data) setDataItem(existing, data)
328
+ } else {
329
+ buildItem(item, listView)
330
+ }
331
+ })
332
+
333
+ listView.getItems(DzListView.All).forEach((li) => {
334
+ const data = getDataItem<any>(li)
335
+ const path = data?.id ?? String(li.id)
336
+ if (!incomingPaths[path]) listView.deleteItem(li)
337
+ })
338
+
339
+ listView.sort()
340
+ }
341
+
342
+ const onSelectionChanged = (callback: (item: DzListViewItem, data: TData) => void) => {
343
+ (listView as any)["selectionChanged()"].scriptConnect(() => {
344
+ callback(listView.selectedItem(), getDataItem(listView.selectedItem()))
345
+ })
346
+ }
347
+
348
+ buildColumns(context.columns.value)
349
+ context.columns.connect((columns) => {
350
+ buildColumns(columns)
351
+ })
352
+
353
+ listView.setSorting(context.sorting, context.sortAscending)
354
+ buildList(context.items?.value)
355
+ context.items.connect((items) => {
356
+ updateList(items)
357
+ })
358
+
359
+ context.refreshWhen?.connect(() => {
360
+ if (context.refreshWhat === ListViewRefreshOptions.All)
361
+ buildList(context.items?.value, listView.selectedItem()?.id)
362
+ else {
363
+ filterList()
364
+ }
365
+
366
+ })
367
+
368
+ if (context.selected) {
369
+ onSelectionChanged((_, data) => {
370
+ context.selected.value = data
371
+ })
372
+ }
373
+
374
+ if (context.filter) {
375
+ context.filter.keywords.connect((keywords) => {
376
+ if (!delayedFilter) {
377
+ delayedFilter = new Delayed(() => {
378
+ filterList(context.filter?.keywords?.value)
379
+ }, context.filter.delay?.min ?? 100, context.filter.delay?.max ?? 400)
380
+ }
381
+ delayedFilter.trigger()
382
+ })
383
+ }
384
+
385
+ if (context.doubleClicked) {
386
+ listView.doubleClicked.scriptConnect((listItem) => {
387
+ context.doubleClicked.value = listItem?.getDataItem('data')
388
+ })
389
+ }
390
+
391
+ listView.contextMenuRequested.scriptConnect((item: DzListViewItem, pos: Point) => {
392
+ context.contextMenu?.(listView, item, pos).exec(pos.cursorPos(), 0)
393
+ })
394
+
395
+ return listView
396
+ }