x4js 2.0.20 → 2.0.22

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 (173) hide show
  1. package/lib/cjs/x4.css +1 -1
  2. package/lib/cjs/x4.js +2 -2
  3. package/lib/esm/x4.css +1 -1
  4. package/lib/esm/x4.mjs +2 -2
  5. package/lib/styles/x4.css +1 -1
  6. package/lib/types/x4js.d.ts +93 -77
  7. package/package.json +1 -1
  8. package/src/components/boxes/boxes.ts +33 -1
  9. package/src/components/canvas/canvas.ts +9 -3
  10. package/src/components/combobox/combobox.module.scss +13 -0
  11. package/src/components/combobox/combobox.ts +7 -0
  12. package/src/components/form/form.ts +4 -4
  13. package/src/components/gridview/gridview.ts +12 -9
  14. package/src/components/header/header.module.scss +2 -1
  15. package/src/components/header/header.ts +17 -5
  16. package/src/components/icon/icon.module.scss +1 -0
  17. package/src/components/label/label.module.scss +6 -1
  18. package/src/components/listbox/listbox.module.scss +35 -31
  19. package/src/components/listbox/listbox.ts +50 -12
  20. package/src/components/messages/messages.ts +14 -0
  21. package/src/components/notification/notification.ts +1 -1
  22. package/src/components/popup/popup.ts +13 -2
  23. package/src/components/propgrid/propgrid.ts +4 -2
  24. package/src/components/sizers/sizer.ts +4 -4
  25. package/src/components/tabs/tabs.module.scss +2 -2
  26. package/src/components/tabs/tabs.ts +28 -4
  27. package/src/components/textedit/textedit.ts +1 -1
  28. package/src/components/treeview/treeview.module.scss +9 -1
  29. package/src/components/treeview/treeview.ts +52 -13
  30. package/src/core/component.ts +46 -9
  31. package/src/core/core_data.ts +8 -8
  32. package/src/core/core_state.ts +1 -1
  33. package/src/core/core_tools.ts +2 -0
  34. package/lib/src/components/base.scss +0 -25
  35. package/lib/src/components/boxes/boxes.module.scss +0 -54
  36. package/lib/src/components/boxes/boxes.ts +0 -370
  37. package/lib/src/components/breadcrumb/breadcrumb.scss +0 -56
  38. package/lib/src/components/breadcrumb/breadcrumb.ts +0 -93
  39. package/lib/src/components/breadcrumb/chevron-right.svg +0 -1
  40. package/lib/src/components/btngroup/btngroup.module.scss +0 -41
  41. package/lib/src/components/btngroup/btngroup.ts +0 -153
  42. package/lib/src/components/button/button.module.scss +0 -173
  43. package/lib/src/components/button/button.ts +0 -185
  44. package/lib/src/components/calendar/calendar-check-sharp-light.svg +0 -1
  45. package/lib/src/components/calendar/calendar.module.scss +0 -163
  46. package/lib/src/components/calendar/calendar.ts +0 -327
  47. package/lib/src/components/calendar/chevron-left-sharp-light.svg +0 -1
  48. package/lib/src/components/calendar/chevron-right-sharp-light.svg +0 -1
  49. package/lib/src/components/canvas/canvas.module.scss +0 -25
  50. package/lib/src/components/canvas/canvas.ts +0 -189
  51. package/lib/src/components/canvas/canvas_ex.ts +0 -276
  52. package/lib/src/components/checkbox/check.svg +0 -4
  53. package/lib/src/components/checkbox/checkbox.module.scss +0 -142
  54. package/lib/src/components/checkbox/checkbox.ts +0 -140
  55. package/lib/src/components/colorinput/colorinput.module.scss +0 -65
  56. package/lib/src/components/colorinput/colorinput.ts +0 -91
  57. package/lib/src/components/colorinput/crosshairs-simple-sharp-light.svg +0 -1
  58. package/lib/src/components/colorpicker/colorpicker.module.scss +0 -133
  59. package/lib/src/components/colorpicker/colorpicker.ts +0 -482
  60. package/lib/src/components/combobox/combobox.module.scss +0 -133
  61. package/lib/src/components/combobox/combobox.ts +0 -275
  62. package/lib/src/components/combobox/updown.svg +0 -4
  63. package/lib/src/components/components.ts +0 -42
  64. package/lib/src/components/dialog/dialog.module.scss +0 -104
  65. package/lib/src/components/dialog/dialog.ts +0 -229
  66. package/lib/src/components/dialog/xmark-sharp-light.svg +0 -1
  67. package/lib/src/components/filedrop/cloud-arrow-up.svg +0 -1
  68. package/lib/src/components/filedrop/filedrop.module.scss +0 -70
  69. package/lib/src/components/filedrop/filedrop.ts +0 -131
  70. package/lib/src/components/form/form.module.scss +0 -38
  71. package/lib/src/components/form/form.ts +0 -172
  72. package/lib/src/components/gridview/arrow-down-light.svg +0 -1
  73. package/lib/src/components/gridview/arrow-up-light.svg +0 -1
  74. package/lib/src/components/gridview/gridview.module.scss +0 -324
  75. package/lib/src/components/gridview/gridview.ts +0 -1175
  76. package/lib/src/components/header/header.module.scss +0 -40
  77. package/lib/src/components/header/header.ts +0 -130
  78. package/lib/src/components/icon/icon.module.scss +0 -31
  79. package/lib/src/components/icon/icon.ts +0 -137
  80. package/lib/src/components/image/image.module.scss +0 -28
  81. package/lib/src/components/image/image.ts +0 -168
  82. package/lib/src/components/input/input.module.scss +0 -74
  83. package/lib/src/components/input/input.ts +0 -422
  84. package/lib/src/components/keyboard/arrow-up.svg +0 -1
  85. package/lib/src/components/keyboard/delete-left.svg +0 -1
  86. package/lib/src/components/keyboard/eye-slash.svg +0 -1
  87. package/lib/src/components/keyboard/keyboard.module.scss +0 -134
  88. package/lib/src/components/keyboard/keyboard.ts +0 -526
  89. package/lib/src/components/label/label.module.scss +0 -76
  90. package/lib/src/components/label/label.ts +0 -97
  91. package/lib/src/components/link/link.ts +0 -81
  92. package/lib/src/components/listbox/listbox.module.scss +0 -161
  93. package/lib/src/components/listbox/listbox.ts +0 -539
  94. package/lib/src/components/menu/caret-right-solid.svg +0 -1
  95. package/lib/src/components/menu/menu.module.scss +0 -117
  96. package/lib/src/components/menu/menu.ts +0 -174
  97. package/lib/src/components/messages/circle-exclamation.svg +0 -1
  98. package/lib/src/components/messages/messages.module.scss +0 -92
  99. package/lib/src/components/messages/messages.ts +0 -215
  100. package/lib/src/components/messages/pen-field.svg +0 -1
  101. package/lib/src/components/normalize.scss +0 -391
  102. package/lib/src/components/notification/circle-check-solid.svg +0 -1
  103. package/lib/src/components/notification/circle-exclamation-solid.svg +0 -1
  104. package/lib/src/components/notification/circle-notch-light.svg +0 -1
  105. package/lib/src/components/notification/notification.module.scss +0 -84
  106. package/lib/src/components/notification/notification.ts +0 -107
  107. package/lib/src/components/notification/xmark-sharp-light.svg +0 -1
  108. package/lib/src/components/panel/panel.module.scss +0 -60
  109. package/lib/src/components/panel/panel.ts +0 -58
  110. package/lib/src/components/popup/popup.module.scss +0 -51
  111. package/lib/src/components/popup/popup.ts +0 -442
  112. package/lib/src/components/progress/progress.module.scss +0 -57
  113. package/lib/src/components/progress/progress.ts +0 -44
  114. package/lib/src/components/propgrid/folder-closed.svg +0 -1
  115. package/lib/src/components/propgrid/folder-open.svg +0 -1
  116. package/lib/src/components/propgrid/progrid.module.scss +0 -112
  117. package/lib/src/components/propgrid/propgrid.ts +0 -288
  118. package/lib/src/components/propgrid/updown.svg +0 -4
  119. package/lib/src/components/radio/radio.module.scss +0 -147
  120. package/lib/src/components/radio/radio.svg +0 -4
  121. package/lib/src/components/radio/radio.ts +0 -142
  122. package/lib/src/components/rating/rating.module.scss +0 -23
  123. package/lib/src/components/rating/rating.ts +0 -131
  124. package/lib/src/components/rating/star-sharp-light.svg +0 -1
  125. package/lib/src/components/rating/star-sharp-solid.svg +0 -1
  126. package/lib/src/components/select/select.module.scss +0 -9
  127. package/lib/src/components/select/select.ts +0 -134
  128. package/lib/src/components/shared.scss +0 -137
  129. package/lib/src/components/sizers/sizer.module.scss +0 -90
  130. package/lib/src/components/sizers/sizer.ts +0 -132
  131. package/lib/src/components/slider/slider.module.scss +0 -118
  132. package/lib/src/components/slider/slider.ts +0 -198
  133. package/lib/src/components/switch/switch.module.scss +0 -127
  134. package/lib/src/components/switch/switch.ts +0 -62
  135. package/lib/src/components/tabs/tabs.module.scss +0 -45
  136. package/lib/src/components/tabs/tabs.ts +0 -205
  137. package/lib/src/components/textarea/textarea.module.scss +0 -63
  138. package/lib/src/components/textarea/textarea.ts +0 -125
  139. package/lib/src/components/textedit/textedit.module.scss +0 -116
  140. package/lib/src/components/textedit/textedit.ts +0 -115
  141. package/lib/src/components/themes.scss +0 -88
  142. package/lib/src/components/tickline/tickline.module.scss +0 -26
  143. package/lib/src/components/tickline/tickline.ts +0 -82
  144. package/lib/src/components/tooltips/circle-info-sharp-light.svg +0 -1
  145. package/lib/src/components/tooltips/comments-question.svg +0 -1
  146. package/lib/src/components/tooltips/tooltips.scss +0 -72
  147. package/lib/src/components/tooltips/tooltips.ts +0 -109
  148. package/lib/src/components/treeview/chevron-down-light.svg +0 -1
  149. package/lib/src/components/treeview/treeview.module.scss +0 -185
  150. package/lib/src/components/treeview/treeview.ts +0 -445
  151. package/lib/src/components/viewport/viewport.module.scss +0 -32
  152. package/lib/src/components/viewport/viewport.ts +0 -41
  153. package/lib/src/core/component.ts +0 -1066
  154. package/lib/src/core/core_application.ts +0 -265
  155. package/lib/src/core/core_colors.ts +0 -250
  156. package/lib/src/core/core_data.ts +0 -1310
  157. package/lib/src/core/core_dom.ts +0 -471
  158. package/lib/src/core/core_dragdrop.ts +0 -201
  159. package/lib/src/core/core_element.ts +0 -115
  160. package/lib/src/core/core_events.ts +0 -177
  161. package/lib/src/core/core_i18n.ts +0 -393
  162. package/lib/src/core/core_react.ts +0 -79
  163. package/lib/src/core/core_router.ts +0 -237
  164. package/lib/src/core/core_state.ts +0 -62
  165. package/lib/src/core/core_styles.ts +0 -214
  166. package/lib/src/core/core_svg.ts +0 -712
  167. package/lib/src/core/core_tools.ts +0 -906
  168. package/lib/src/types/scss.d.ts +0 -4
  169. package/lib/src/types/svg.d.ts +0 -1
  170. package/lib/src/types/x4react.d.ts +0 -9
  171. package/lib/src/x4.scss +0 -19
  172. package/lib/src/x4.ts +0 -35
  173. package/lib/src/x4tsx.d.ts +0 -25
@@ -1,1310 +0,0 @@
1
- /**
2
- * ___ ___ __
3
- * \ \/ / / _
4
- * \ / /_| |_
5
- * / \____ _|
6
- * /__/\__\ |_|
7
- *
8
- * @file core_data.ts
9
- * @author Etienne Cochard
10
- *
11
- * @copyright (c) 2024 R-libre ingenierie
12
- *
13
- * Use of this source code is governed by an MIT-style license
14
- * that can be found in the LICENSE file or at https://opensource.org/licenses/MIT.
15
- **/
16
-
17
-
18
- import { EvChange } from './component';
19
- import { CoreElement } from './core_element';
20
- import { CoreEvent, EventMap, EventSource } from './core_events';
21
- import { isArray, isString } from './core_tools';
22
-
23
- export type DataRecordID = any;
24
- export type DataFieldValue = string | Date | number | boolean;
25
-
26
- export type ChangeCallback = (type: string, id?: DataRecordID) => void;
27
- export type CalcCallback = () => string;
28
-
29
- export type FieldType = 'string' | 'int' | 'float' | 'date' | 'bool' | 'array' | 'object' | 'any' | 'calc';
30
- export type DataIndex = Uint32Array;
31
-
32
- export interface EvDataChange extends CoreEvent {
33
- change_type: 'create' | 'update' | 'delete' | 'data' | 'change';
34
- id?: DataRecordID;
35
- }
36
-
37
-
38
-
39
-
40
-
41
-
42
- /**
43
- * fields definition
44
- * field with index=0 is record id
45
- */
46
-
47
- export interface MetaData {
48
- type?: FieldType;
49
- prec?: number;
50
- required?: boolean;
51
- calc?: (rec: DataRecord) => any;
52
- model?: DataModel; // in case of array of subtypes, the model
53
- }
54
-
55
- export interface FieldInfo extends MetaData {
56
- name: string;
57
- }
58
-
59
- /**
60
- *
61
- */
62
-
63
- class MetaInfos {
64
- name: string;
65
- id: string; // field name holding 'id' record info
66
- fields: FieldInfo[]; // field list
67
-
68
- constructor( name: string ) {
69
- this.name = name;
70
- this.id = undefined;
71
- this.fields = [];
72
- }
73
- }
74
-
75
- const metaFields = Symbol( 'metaField' );
76
-
77
- function _getMetas( obj: object, create = true ) : MetaInfos {
78
-
79
- let ctor = obj.constructor as any;
80
- let mfld = Object.prototype.hasOwnProperty.call(ctor,metaFields) ? ctor[metaFields] : undefined;
81
-
82
- if( mfld===undefined ) {
83
- if( !create ) {
84
- console.assert( mfld!==undefined );
85
- }
86
-
87
- // construct our metas
88
- mfld = new MetaInfos( ctor.name );
89
-
90
- // merge with parent class metas
91
- let pctor = Object.getPrototypeOf(ctor);
92
- if( pctor!=DataModel ) {
93
- let pmetas = pctor[metaFields];
94
- mfld.fields = [...pmetas.fields, ...mfld.fields ]
95
-
96
- console.assert( mfld.id===undefined, 'cannot define mutiple record id' );
97
- if( !mfld.id ) {
98
- mfld.id = pmetas.id;
99
- }
100
- }
101
-
102
- (obj.constructor as any)[metaFields] = mfld;
103
- }
104
-
105
- return mfld;
106
- }
107
-
108
- // eslint-disable-next-line @typescript-eslint/no-namespace
109
- export namespace data {
110
-
111
- /**
112
- * define a model id
113
- * @example
114
- * \@data_id()
115
- * id: string; // this field is the record id
116
- **/
117
-
118
- export function id( ) {
119
- return ( ownerCls: any, fldName: string ) => {
120
- let metas = _getMetas( ownerCls );
121
- metas.fields.push( {
122
- name: fldName,
123
- type: 'any',
124
- required: true,
125
- });
126
-
127
- metas.id = fldName;
128
- }
129
- }
130
-
131
- /**
132
- * @ignore
133
- */
134
-
135
- export function field( data: MetaData ) {
136
-
137
- return ( ownerCls: any, fldName: string ) => {
138
- let metas = _getMetas( ownerCls );
139
- metas.fields.push( {
140
- name: fldName,
141
- ...data
142
- } );
143
- }
144
- }
145
-
146
- /**
147
- * following member is a string field
148
- * @example
149
- * \@data_string()
150
- * my_field: string; // this field will be seen as a string
151
- */
152
-
153
- export function string( props?: MetaData ) {
154
- return field( { ...props, type: 'string' } );
155
- }
156
-
157
- /**
158
- * following member is an integer field
159
- * @example
160
- * \@data_string()
161
- * my_field: number; // this field will be seen as an integer
162
- */
163
-
164
- export function int( props?: MetaData ) {
165
- return field( { ...props, type: 'int' } );
166
- }
167
-
168
- /**
169
- * following member is a float field
170
- * @example
171
- * \@data_float()
172
- * my_field: number; // this field will be seen as a float
173
- */
174
-
175
- export function float( props?: MetaData ) {
176
- return field( { ...props, type: 'float' } );
177
- }
178
-
179
- /**
180
- * following member is a boolean field
181
- * @example
182
- * \@data_bool()
183
- * my_field: boolean; // this field will be seen as a boolean
184
- */
185
-
186
- export function bool( props?: MetaData ) {
187
- return field( { ...props, type: 'bool' } );
188
- }
189
-
190
- /**
191
- * following member is a date field
192
- * @example
193
- * \@data_date()
194
- * my_field: date; // this field will be seen as a date
195
- */
196
-
197
- export function date( props?: MetaData ) {
198
- return field( { ...props, type: 'date' } );
199
- }
200
-
201
- /**
202
- * following member is a calculated field
203
- * @example
204
- * \@data_calc( )
205
- * get my_field(): string => {
206
- * return 'hello';
207
- * };
208
- */
209
-
210
- export function calc( props?: MetaData ) {
211
- return field( { ...props, type: 'calc'} )
212
- }
213
-
214
- /**
215
- *
216
- */
217
-
218
- interface ModelConstructor {
219
- new ( data?: any, id?: any ): DataModel;
220
- }
221
-
222
- /**
223
- * following member is a record array
224
- * @example
225
- * \@data_array( )
226
- * my_field(): TypedRecord[];
227
- */
228
-
229
- export function array( ctor: ModelConstructor, props?: MetaData ) {
230
- return data.field( { ...props, type: 'array', model: ctor ? new ctor() : null } )
231
- }
232
-
233
- /**
234
- * following member is unknown
235
- * @example
236
- * \@data.any( )
237
- * my_field: TypedRecord[];
238
- */
239
-
240
- export function any( props?: MetaData ) {
241
- return field( { ...props, type: 'any' } );
242
- }
243
- }
244
-
245
-
246
-
247
-
248
- /**
249
- * record model
250
- */
251
-
252
- export class DataModel {
253
-
254
- /**
255
- * MUST IMPLEMENT
256
- * @returns fields descriptors
257
- */
258
-
259
- getFields(): FieldInfo[] {
260
- let metas = _getMetas( this, false );
261
- return metas.fields;
262
- }
263
-
264
- /**
265
- *
266
- */
267
-
268
- validate( record: DataRecord ) : Error[] {
269
-
270
- let errs: Error[] = null;
271
-
272
- let fields = this.getFields( );
273
-
274
- fields.forEach( (fi) => {
275
- if( fi.required && !this.getField(fi.name,record) ) {
276
- if( errs ) {
277
- errs = [];
278
- }
279
-
280
- errs.push( new Error( `field ${fi.name} is required.` ) );
281
- }
282
- })
283
-
284
- return errs;
285
- }
286
-
287
- /**
288
- * return the field index by name
289
- */
290
-
291
- getFieldIndex( name: string ) : number {
292
- let fields = this.getFields( );
293
- return fields.findIndex( (fd) => fd.name == name );
294
- }
295
-
296
- /**
297
- * default serializer
298
- * @returns an object with known record values
299
- */
300
-
301
- serialize<T = any>( input: DataRecord ): T {
302
- let rec: any = {};
303
-
304
- this.getFields().forEach((f) => {
305
- if( f.calc === undefined ) {
306
- rec[f.name] = input[f.name];
307
- }
308
- });
309
-
310
- return rec as T;
311
- }
312
-
313
-
314
-
315
- /**
316
- * default unserializer
317
- * @param data - input data
318
- * @returns a new Record
319
- */
320
-
321
- unSerialize(data: any, id?: DataRecordID ) : DataRecord {
322
-
323
- const fields = this.getFields();
324
- const rec = new DataRecord( );
325
-
326
- fields.forEach( (sf) => {
327
- let value = data[sf.name];
328
- if (value !== undefined) {
329
- rec[sf.name] = this._convertField( sf, value );
330
- }
331
- });
332
-
333
- if( id!==undefined ) {
334
- rec[fields[0].name] = id;
335
- }
336
- else {
337
- console.assert( this.getID(rec)!==undefined ); // store do not have ID field
338
- }
339
-
340
- return rec;
341
- }
342
-
343
- /**
344
- * field conversion
345
- * @param field - field descriptor
346
- * @param input - value to convert
347
- * @returns the field value in it's original form
348
- */
349
-
350
- protected _convertField( field: FieldInfo, input: any ) : any {
351
-
352
- //TODO: boolean
353
-
354
- switch( field.type ) {
355
- case 'float': {
356
- let ffv: number = typeof (input) === 'number' ? input : parseFloat(input);
357
-
358
- if (field.prec !== undefined) {
359
- let mul = Math.pow(10, field.prec);
360
- ffv = Math.round(ffv * mul) / mul;
361
- }
362
-
363
- return ffv;
364
- }
365
-
366
- case 'int': {
367
- return typeof (input) === 'number' ? input : parseInt(input);
368
- }
369
-
370
- case 'date': {
371
- return isString(input) ? new Date(input) : input;
372
- }
373
-
374
- case 'array': {
375
- debugger;
376
- /*
377
- let result: any[] = [];
378
-
379
- if( field.model ) {
380
- input.forEach( ( v: any ) => {
381
- result.push( field.model.clone( v ) );
382
- })
383
-
384
- return result;
385
- }
386
- */
387
- break;
388
- }
389
- }
390
-
391
- return input;
392
- }
393
-
394
- /**
395
- * get the record unique identifier
396
- * by default the return value is the first field
397
- * @return unique identifier
398
- */
399
-
400
- getID( rec: DataRecord ): any {
401
- if( !rec ) return null;
402
- let metas = _getMetas( this, false );
403
- return rec[metas.id];
404
- }
405
-
406
- /**
407
- * get raw value of a field
408
- * @param name - field name or field index
409
- */
410
-
411
- getRaw( name: string | number, rec: DataRecord ) : any {
412
-
413
- let idx;
414
- let fields = this.getFields( );
415
-
416
- if( typeof(name) === 'string' ) {
417
- idx = fields.findIndex( ( fi: FieldInfo) => fi.name == name );
418
- if( idx < 0 ) {
419
- console.assert( false, 'unknown field: '+name);
420
- return undefined;
421
- }
422
- }
423
- else if( name<fields.length ) {
424
- if( name<0 ) {
425
- return undefined
426
- }
427
-
428
- idx = name;
429
- }
430
- else {
431
- console.assert( false, 'bad field name: '+name);
432
- return undefined;
433
- }
434
-
435
- let fld = fields[idx];
436
- if( fld.calc!==undefined ) {
437
- return fld.calc( rec );
438
- }
439
-
440
- return rec[fld.name];
441
- }
442
-
443
- /**
444
- * get field value (as string)
445
- * @param name - field name
446
- * @example
447
- * let value = record.get('field1');
448
- */
449
-
450
- getField( name: string, rec: DataRecord ): string {
451
- let v = this.getRaw( name, rec );
452
- return (v===undefined || v===null) ? '' : ''+v;
453
- }
454
- }
455
-
456
- /**
457
- *
458
- */
459
-
460
- export class DataRecord {
461
- [ key: string ]: DataFieldValue;
462
-
463
- /*
464
- / **
465
- * @returns fields descriptors
466
- * /
467
-
468
- getFields(): FieldInfo[] {
469
- let metas = _getMetas( this, false );
470
- return metas.fields;
471
- }
472
-
473
-
474
-
475
- / **
476
- *
477
- * @param name
478
- * @param data
479
- * /
480
-
481
- setRaw( name: string, data: string ) {
482
- this[name] = data;
483
- }
484
-
485
-
486
-
487
- / **
488
- * set field value
489
- * @param name - field name
490
- * @param value - value to set
491
- * @example
492
- * record.set( 'field1', 7 );
493
- * /
494
-
495
- setField(name: string, value: any) {
496
- let fields = this.getFields( );
497
- let idx = fields.findIndex( fi => fi.name == name );
498
-
499
- if( idx < 0 ) {
500
- console.assert( false, 'unknown field: '+name);
501
- return;
502
- }
503
-
504
- let fld = fields[idx];
505
- if( fld.calc!==undefined ) {
506
- console.assert( false, 'cannot set calc field: '+name);
507
- return;
508
- }
509
-
510
- this.setRaw( fld.name, value );
511
- }
512
- */
513
-
514
-
515
- }
516
-
517
-
518
- /**
519
- *
520
- */
521
-
522
- interface DataEventMap extends EventMap {
523
- change?: EvChange;
524
- }
525
-
526
- type DataSolver = ( data: any ) => DataRecord[];
527
-
528
- export interface DataProxyProps {
529
- url: string;
530
- params?: string[];
531
- solver?: DataSolver;
532
- }
533
-
534
- export class DataProxy extends CoreElement<DataEventMap> {
535
-
536
- protected m_props: DataProxyProps;
537
-
538
- constructor( props: DataProxyProps ) {
539
- super( );
540
-
541
- this.m_props = props;
542
- }
543
-
544
- async load( url?: string ) {
545
- if( url ) {
546
- this.m_props.url = url;
547
- }
548
- else {
549
- url = this.m_props.url;
550
- }
551
-
552
- if( this.m_props.params ) {
553
- url += '?' + this.m_props.params.join( '&' );
554
- }
555
-
556
- const r = await fetch( url );
557
- if( r.ok ) {
558
- const raw = await r.json( );
559
-
560
- let json = raw;
561
- if( this.m_props.solver ) {
562
- json = this.m_props.solver( json );
563
- }
564
-
565
- this.fire( 'change', {value:json,context:raw} );
566
- }
567
- }
568
- }
569
-
570
-
571
- /**
572
- *
573
- */
574
-
575
- interface DataStoreProps {
576
- model: DataModel;
577
- data?: any[];
578
- url?: string;
579
- autoload?: false;
580
- solver?: DataSolver;
581
- }
582
-
583
-
584
- interface DataStoreEventMap extends EventMap {
585
- data_change: EvDataChange;
586
- }
587
-
588
-
589
-
590
- /**
591
- *
592
- */
593
-
594
- export class DataStore extends EventSource<DataStoreEventMap> {
595
-
596
- protected m_model: DataModel;
597
- protected m_fields: FieldInfo[];
598
- protected m_records: DataRecord[];
599
-
600
- protected m_proxy: DataProxy;
601
- protected m_rec_index: DataIndex;
602
-
603
- constructor(props: DataStoreProps ) {
604
- super( );
605
-
606
- this.m_fields = undefined;
607
- this.m_records = [];
608
- this.m_rec_index = null;
609
- this.m_model = props.model;
610
- this.m_fields = props.model.getFields();
611
-
612
- if (props.data) {
613
- this.setRawData( props.data );
614
- }
615
- else if( props.url ) {
616
- this.m_proxy = new DataProxy( {
617
- url: props.url,
618
- solver: props.solver,
619
- });
620
-
621
- this.m_proxy.on( 'change', ( ev: EvChange) => {
622
- this.setData( ev.value );
623
- });
624
-
625
- if( props.autoload!=false ) {
626
- this.m_proxy.load( );
627
- }
628
- }
629
- }
630
-
631
- /**
632
- *
633
- * @param records
634
- */
635
-
636
- async load( url?: string ) {
637
- return this.m_proxy.load( url );
638
- }
639
-
640
- async reload( ) {
641
- return this.m_proxy.load( );
642
- }
643
-
644
- /**
645
- * convert raw objects to real records from model
646
- * @param records
647
- */
648
-
649
- public setData( records: any[] ) {
650
-
651
- const realRecords: DataRecord[] = new Array( records.length );
652
-
653
- records.forEach( (rec,idx) => {
654
- realRecords[idx] = this.m_model.unSerialize(rec);
655
- });
656
-
657
- this.setRawData( realRecords );
658
- }
659
-
660
- /**
661
- * just set the records
662
- * @param records - must be of the same type as model
663
- */
664
-
665
- public setRawData(records: DataRecord[]) {
666
-
667
- this.m_records = records;
668
- this._rebuildIndex( );
669
- this.fire( 'data_change', { change_type: 'change'} );
670
- }
671
-
672
- private _rebuildIndex( ) {
673
- this.m_rec_index = null; // null to signal that we have to run on records instead of index
674
- this.m_rec_index = this.createIndex( null ); // prepare index (remove deleted)
675
- this.m_rec_index = this.sortIndex( this.m_rec_index, null ); // sort by id
676
- }
677
-
678
- /**
679
- *
680
- */
681
-
682
- public update( rec: DataRecord ) {
683
-
684
- let id = this.m_model.getID( rec );
685
- let index = this.indexOfId(id);
686
- if (index < 0) {
687
- return false;
688
- }
689
-
690
- this.m_records[this.m_rec_index[index]] = rec;
691
- this.fire( 'data_change', {change_type: 'update', id } );
692
- return true;
693
- }
694
-
695
- /**
696
- *
697
- * @param data
698
- */
699
-
700
- public append( rec: DataRecord | any ) {
701
-
702
- if( !(rec instanceof DataRecord) ) {
703
- rec = this.m_model.unSerialize( rec );
704
- }
705
-
706
- const id = this.m_model.getID(rec);
707
- console.assert( id!==undefined );
708
-
709
- this.m_records.push( rec );
710
- this._rebuildIndex( );
711
- this.fire( 'data_change', {change_type: 'create', id } );
712
- }
713
-
714
- /**
715
- *
716
- */
717
-
718
- getMaxId( ) {
719
- let maxID: number = undefined;
720
- const m = this.m_model;
721
-
722
- this.m_records.forEach( (r) => {
723
- let rid = m.getID( r );
724
- if( maxID===undefined || maxID<rid ) {
725
- maxID = rid;
726
- }
727
- });
728
-
729
- return maxID;
730
- }
731
-
732
- /**
733
- *
734
- * @param id
735
- */
736
-
737
- public delete(id: DataRecordID ): boolean {
738
-
739
- let idx = this.indexOfId( id );
740
- if( idx<0 ) {
741
- return false;
742
- }
743
-
744
- idx = this.m_rec_index[idx];
745
-
746
- // mark as deleted
747
- this.m_records.splice( idx, 1 );
748
- this._rebuildIndex( );
749
- this.fire( 'data_change', { change_type: 'delete', id } );
750
- return true;
751
- }
752
-
753
- /**
754
- * return the number of records
755
- */
756
-
757
- get count( ) : number {
758
- return this.m_rec_index ? this.m_rec_index.length : this.m_records.length;
759
- }
760
-
761
- /**
762
- * return the fields
763
- */
764
-
765
- get fields( ) : FieldInfo [] {
766
- return this.m_fields;
767
- }
768
-
769
- /**
770
- * find the index of the element with the given id
771
- */
772
-
773
- public indexOfId(id: DataRecordID ): number {
774
-
775
- //if( this.count<10 ) {
776
- // this.forEach( (rec) => rec.getID() == id );
777
- //}
778
-
779
- const m = this.m_model;
780
-
781
- for( let lim = this.count, base = 0; lim != 0; lim >>= 1 ) {
782
-
783
- const p = base + (lim >> 1); // int conversion
784
- const idx = this.m_rec_index[p];
785
- const rid = m.getID( this.m_records[idx] );
786
-
787
- if( rid==id ) {
788
- return p;
789
- }
790
-
791
- if( rid<id ) {
792
- base = p+1;
793
- lim--;
794
- }
795
- }
796
-
797
- return -1;
798
- }
799
-
800
- /**
801
- * return the record by it's id
802
- * @returns record or null
803
- */
804
-
805
- public getById(id: DataRecordID): DataRecord {
806
- let idx = this.indexOfId( id );
807
- if( idx<0 ) {
808
- return null;
809
- }
810
-
811
- idx = this.m_rec_index[idx];
812
- return this.m_records[idx];
813
- }
814
-
815
- /**
816
- * return a record by it's index
817
- * @returns record or null
818
- */
819
-
820
- public getByIndex( index: number ): DataRecord {
821
- let idx = this.m_rec_index[index];
822
- return this._getRecord( idx );
823
- }
824
-
825
- private _getRecord( index: number ) : DataRecord {
826
- return this.m_records[index] ?? null;
827
- }
828
-
829
- public moveTo( other: DataStore ) {
830
- other.setRawData( this.m_records );
831
- }
832
-
833
- /**
834
- * create a new view on the DataStore
835
- * @param opts
836
- */
837
-
838
- createView( opts?: DataViewProps ) : DataView {
839
- let eopts = { ...opts, store: this };
840
- return new DataView( eopts );
841
- }
842
-
843
- /**
844
- *
845
- */
846
-
847
- createIndex( filter: FilterInfo ) : DataIndex {
848
-
849
- if( filter && filter.op==='empty-result' ) {
850
- return new Uint32Array(0);
851
- }
852
-
853
- let index = new Uint32Array( this.m_records.length );
854
- let iidx = 0;
855
-
856
- if( !filter ) {
857
- // reset filter
858
- this.forEach( (rec, idx) => {
859
- index[iidx++] = idx;
860
- } );
861
- }
862
- else {
863
- if( typeof(filter.op)==='function' ) {
864
-
865
- let fn = filter.op as FilterFunc;
866
-
867
- // scan all records and append only interesting ones
868
- this.forEach( (rec, idx) => {
869
- // skip deleted
870
- if( !rec ) {
871
- return;
872
- }
873
-
874
- if( fn(rec) ) {
875
- index[iidx++] = idx;
876
- }
877
- } );
878
- }
879
- else {
880
- let filterFld = this.m_model.getFieldIndex( filter.field ); // field index to filter on
881
- if( filterFld<0 ) {
882
- // unknown filter field, nothing inside
883
- console.assert( false, 'unknown field name in filter' )
884
- return new Uint32Array(0);
885
- }
886
-
887
- let filterValue = filter.value;
888
- if( isString(filterValue) && !filter.caseSensitive ) {
889
- filterValue = filterValue.toUpperCase( );
890
- }
891
-
892
- function _lt( recval: string ) : boolean {
893
- return recval < filterValue;
894
- }
895
-
896
- function _le( recval: string ) : boolean {
897
- return recval <= filterValue;
898
- }
899
-
900
- function _eq( recval: string ) : boolean {
901
- return recval == filterValue;
902
- }
903
-
904
- function _neq( recval: string ) : boolean {
905
- return recval != filterValue;
906
- }
907
-
908
- function _ge( recval: string ) : boolean {
909
- return recval >= filterValue;
910
- }
911
-
912
- function _gt( recval: string ) : boolean {
913
- return recval > filterValue;
914
- }
915
-
916
- function _re( recval: string ) : boolean {
917
- filterRe.lastIndex = -1;
918
- return filterRe.test( recval );
919
- }
920
-
921
- let filterFn: ( rec: string ) => boolean; // filter fn
922
- let filterRe: RegExp; // if fielter is regexp
923
- if( filterValue instanceof RegExp ) {
924
- filterRe = filterValue;
925
- filterFn = _re;
926
- }
927
- else {
928
- switch( filter.op ) {
929
- case '<': { filterFn = _lt; break; }
930
- case '<=': { filterFn = _le; break; }
931
- case '=': { filterFn = _eq; break; }
932
- case '>=': { filterFn = _ge; break; }
933
- case '>': { filterFn = _gt; break; }
934
- case '<>': { filterFn = _neq; break; }
935
- }
936
- }
937
-
938
- // scan all records and append only interesting ones
939
- const m = this.m_model;
940
-
941
- this.forEach( (rec, idx) => {
942
-
943
- // skip deleted
944
- if( !rec ) {
945
- return;
946
- }
947
-
948
- let field = m.getRaw( filterFld, rec );
949
- if( field===null || field===undefined ) {
950
- field = '';
951
- }
952
- else {
953
- field = ''+field;
954
- if( !filter.caseSensitive ) {
955
- field = field.toUpperCase( );
956
- }
957
- }
958
-
959
- let keep = filterFn( field );
960
- if( keep ) {
961
- index[iidx++] = idx;
962
- };
963
- });
964
- }
965
- }
966
-
967
- return index.slice( 0, iidx );
968
- }
969
-
970
- sortIndex( index: DataIndex, sort: SortProp[] ) {
971
-
972
- interface sort_info {
973
- fidx: number,
974
- asc: boolean
975
- }
976
-
977
- let bads = 0; // unknown fields
978
- let fidxs: sort_info[] = []; // fields indexes
979
-
980
- // if no fields are given, reset sort by id
981
- if ( sort===null ) {
982
- fidxs.push( { fidx: 0, asc: true } );
983
- }
984
- else {
985
- fidxs = sort.map( (si) => {
986
-
987
- let fi = this.m_model.getFieldIndex( si.field );
988
- if (fi == -1) {
989
- console.assert( false, 'unknown field name in sort' )
990
- bads++;
991
- }
992
-
993
- return { fidx: fi, asc: si.ascending };
994
- });
995
- }
996
-
997
- // unknown field or nothing to sort on ??
998
- if( bads || fidxs.length==0 ) {
999
- return index;
1000
- }
1001
-
1002
- // sort only by one field : optimize it
1003
- const m = this.m_model;
1004
-
1005
- if( fidxs.length==1 ) {
1006
-
1007
- const field = fidxs[0].fidx;
1008
-
1009
- index.sort( ( ia, ib ) => {
1010
-
1011
- let va = m.getRaw( field, this.getByIndex(ia) ) ?? '';
1012
- let vb = m.getRaw( field, this.getByIndex(ib) ) ?? '';
1013
- if (va > vb) { return 1; }
1014
- if (va < vb) { return -1; }
1015
- return 0;
1016
- } );
1017
-
1018
- // just reverse if
1019
- if( !fidxs[0].asc ) {
1020
- index.reverse( );
1021
- }
1022
- }
1023
- else {
1024
- index.sort( ( ia, ib ) => {
1025
-
1026
- for( let fi=0; fi<fidxs.length; fi++ ) {
1027
-
1028
- let fidx = fidxs[fi].fidx;
1029
- let mul = fidxs[fi].asc ? 1 : -1;
1030
-
1031
- let va = m.getRaw( fidx, this.getByIndex(ia) ) ?? '';
1032
- let vb = m.getRaw( fidx, this.getByIndex(ib) ) ?? '';
1033
- if (va > vb) { return mul; }
1034
- if (va < vb) { return -mul; }
1035
- }
1036
-
1037
- return 0;
1038
- } );
1039
- }
1040
-
1041
- return index
1042
- }
1043
-
1044
- /**
1045
- *
1046
- */
1047
-
1048
- forEach( cb: ( rec: DataRecord, index: number ) => any ) {
1049
-
1050
- if( this.m_rec_index ) {
1051
- this.m_rec_index.some( (ri,index) => {
1052
- if( cb( this.m_records[ri], index ) ) {
1053
- return index;
1054
- }
1055
- });
1056
- }
1057
- else {
1058
- this.m_records.some( ( rec, index ) => {
1059
- if( rec ) {
1060
- if( cb( rec, index ) ) {
1061
- return index;
1062
- }
1063
- }
1064
- } );
1065
- }
1066
- }
1067
-
1068
- export( ) {
1069
- return this.m_records;
1070
- }
1071
-
1072
- changed( ) {
1073
- this.fire( 'data_change', { change_type: 'change'} );
1074
- }
1075
-
1076
- getModel( ) {
1077
- return this.m_model;
1078
- }
1079
- }
1080
-
1081
-
1082
- // :: VIEWS ::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
1083
-
1084
- export interface EvViewChange extends CoreEvent {
1085
- change_type: "change" | "filter" | "sort";
1086
- }
1087
-
1088
- interface DataViewEventMap extends EventMap {
1089
- view_change: EvViewChange;
1090
- }
1091
-
1092
- interface DataViewProps {
1093
- store?: DataStore;
1094
- filter?: FilterInfo;
1095
- order?: string | SortProp[] | SortProp;
1096
- }
1097
-
1098
- export type FilterFunc = ( rec: DataRecord ) => boolean;
1099
-
1100
- export interface FilterInfo {
1101
- op: '<' | '<=' | '=' | '>=' | '>' | '<>' | 'empty-result' | FilterFunc, // emptydb mean return an empty result always
1102
- field?: string;
1103
- value?: string | RegExp; // if regexp then operator is =
1104
- caseSensitive?: boolean;
1105
- }
1106
-
1107
-
1108
- export interface SortProp {
1109
- field: string; //
1110
- ascending: boolean; //
1111
- numeric?: boolean; // numeric sort
1112
- }
1113
-
1114
-
1115
-
1116
- /**
1117
- * Dataview allow different views of the DataStore.
1118
- * You can sort the columns & filter data
1119
- * You can have multiple views for a single DataStore
1120
- */
1121
-
1122
- export class DataView extends CoreElement<DataViewEventMap>
1123
- {
1124
- protected m_index: DataIndex;
1125
- protected m_store: DataStore;
1126
- protected m_model: DataModel;
1127
-
1128
- protected m_sort: SortProp[];
1129
- protected m_filter: FilterInfo;
1130
-
1131
- protected m_props: DataViewProps;
1132
-
1133
- constructor( props: DataViewProps ) {
1134
- super( );
1135
-
1136
- this.m_props = props;
1137
- this.m_store = props.store;
1138
- this.m_index = null;
1139
- this.m_filter = null;
1140
- this.m_sort = null;
1141
- this.m_model = this.m_store.getModel();
1142
-
1143
- this.filter( props.filter );
1144
-
1145
- if( props.order ) {
1146
- if( isString(props.order) ) {
1147
- this.sort( [ { field: props.order, ascending: true } ] );
1148
- }
1149
- else if( isArray(props.order) ) {
1150
- this.sort( props.order );
1151
- }
1152
- else {
1153
- this.sort( [props.order] );
1154
- }
1155
- }
1156
- else {
1157
- this.sort( null );
1158
- }
1159
-
1160
- this.m_store.addListener( 'data_change', ( e ) => this._storeChange(e) );
1161
- }
1162
-
1163
- private _storeChange( ev: EvDataChange ) {
1164
-
1165
- this._filter( this.m_filter, ev.type!='change' );
1166
- this._sort( this.m_sort, ev.type!='change' );
1167
-
1168
- this.fire( 'view_change', { change_type: 'change' } );
1169
- }
1170
-
1171
- /**
1172
- *
1173
- * @param filter
1174
- */
1175
-
1176
- public filter( filter?: FilterInfo ) : number {
1177
-
1178
- this.m_index = null; // null to signal that we have to run on records instead of index
1179
- return this._filter( filter, true );
1180
- }
1181
-
1182
- private _filter( filter: FilterInfo, notify: boolean) : number {
1183
-
1184
- this.m_index = this.m_store.createIndex( filter );
1185
- this.m_filter = filter;
1186
-
1187
- // need to sort again:
1188
- if( this.m_sort ) {
1189
- this.sort( this.m_sort );
1190
- }
1191
-
1192
- if( notify ) {
1193
- this.fire( 'view_change', { change_type: 'filter' } );
1194
- }
1195
-
1196
- return this.m_index.length;
1197
- }
1198
-
1199
- /**
1200
- *
1201
- * @param columns
1202
- * @param ascending
1203
- */
1204
-
1205
- public sort( props: SortProp[] ) {
1206
- this._sort( props, true );
1207
- }
1208
-
1209
- private _sort( props: SortProp[], notify: boolean ) {
1210
- this.m_index = this.m_store.sortIndex( this.m_index, props );
1211
- this.m_sort = props;
1212
-
1213
- if( notify ) {
1214
- this.fire( 'view_change', { change_type: 'sort' } );
1215
- }
1216
- }
1217
-
1218
- /**
1219
- *
1220
- */
1221
-
1222
- getStore ( ) {
1223
- return this.m_store;
1224
- }
1225
-
1226
- /**
1227
- *
1228
- */
1229
-
1230
- public getCount() {
1231
- return this.m_index.length;
1232
- }
1233
-
1234
- /**
1235
- *
1236
- * @param id
1237
- */
1238
-
1239
- public indexOfId(id: DataRecordID): number {
1240
- let ridx = this.m_store.indexOfId( id );
1241
- return this.m_index.findIndex( (rid) => rid === ridx );
1242
- }
1243
-
1244
- /**
1245
- *
1246
- * @param index
1247
- */
1248
-
1249
- public getByIndex(index: number): DataRecord {
1250
-
1251
- if (index >= 0 && index < this.m_index.length) {
1252
- let rid = this.m_index[index];
1253
- return this.m_store.getByIndex( rid );
1254
- }
1255
-
1256
- return null;
1257
- }
1258
-
1259
- public getIdByIndex( index: number ) : DataRecordID {
1260
- const rec = this.getByIndex( index );
1261
- return this.m_model.getID( rec );
1262
- }
1263
-
1264
- public getRecId( rec: DataRecord ): DataRecordID {
1265
- return this.m_model.getID( rec );
1266
- }
1267
-
1268
- /**
1269
- *
1270
- * @param id
1271
- */
1272
-
1273
- public getById( id: DataRecordID): DataRecord {
1274
- return this.m_store.getById( id );
1275
- }
1276
-
1277
- /**
1278
- *
1279
- */
1280
-
1281
- getModel( ) {
1282
- return this.m_model;
1283
- }
1284
-
1285
- /**
1286
- *
1287
- */
1288
-
1289
- changed( ) {
1290
- this.fire( 'view_change', {change_type:'change'} );
1291
- }
1292
-
1293
- /**
1294
- *
1295
- */
1296
-
1297
- forEach( cb: ( rec: DataRecord, index: number ) => any ) {
1298
- this.m_index.some( ( index ) => {
1299
- let rec = this.m_store.getByIndex( index );
1300
- if( rec ) {
1301
- if( cb( rec, index ) ) {
1302
- return index;
1303
- }
1304
- }
1305
- } );
1306
- }
1307
- }
1308
-
1309
-
1310
-