@rokkit/data 1.0.0-next.94
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/LICENSE +21 -0
- package/README.md +50 -0
- package/package.json +56 -0
- package/src/aggregators.js +24 -0
- package/src/constants.js +72 -0
- package/src/convert.js +36 -0
- package/src/dataset.js +442 -0
- package/src/filter.js +57 -0
- package/src/formatter.js +56 -0
- package/src/hierarchy.js +136 -0
- package/src/index.js +6 -0
- package/src/infer.js +263 -0
- package/src/list.js +232 -0
- package/src/metadata.js +106 -0
- package/src/model.js +100 -0
- package/src/parser.js +110 -0
- package/src/renamer.js +98 -0
- package/src/rollup.js +138 -0
- package/src/types.js +208 -0
- package/src/utils.js +31 -0
- package/src/view.js +187 -0
package/src/index.js
ADDED
package/src/infer.js
ADDED
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
import { ascending, descending } from 'd3-array'
|
|
2
|
+
import { compact } from '@rokkit/core'
|
|
3
|
+
import { omit } from 'ramda'
|
|
4
|
+
import { createFormatter } from './formatter'
|
|
5
|
+
import { defaultViewOptions, defaultActionOrder } from './constants'
|
|
6
|
+
import { getType } from './utils'
|
|
7
|
+
import { deriveColumnProperties } from './metadata'
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Derives the sorting information for a column based on the type of the input value.
|
|
11
|
+
*
|
|
12
|
+
* @param {import('./types').SortableColumn} value - The value that determines the sorting behavior of the column.
|
|
13
|
+
* @returns {import('./types').ColumnSorter} An object containing the 'name' of the column and a 'sorter' function.
|
|
14
|
+
* @throws {Error} Throws an error if the value type is not supported or the value format is invalid.
|
|
15
|
+
*/
|
|
16
|
+
export function deriveSortableColumn(value) {
|
|
17
|
+
const type = getType(value)
|
|
18
|
+
|
|
19
|
+
const configGenerators = {
|
|
20
|
+
array: arrayConfigGenerator,
|
|
21
|
+
object: objectConfigGenerator,
|
|
22
|
+
string: stringConfigGenerator
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const configGenerator = configGenerators[type]
|
|
26
|
+
if (!configGenerator) {
|
|
27
|
+
throw new Error(`Invalid value type: ${type}`)
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
return configGenerator(value)
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Generates a sorting configuration for an array value.
|
|
35
|
+
*
|
|
36
|
+
* @param {Array} value - The array from which to derive a sorting configuration.
|
|
37
|
+
* @returns {Object} A validated sorting configuration based on the array.
|
|
38
|
+
*/
|
|
39
|
+
function arrayConfigGenerator(value) {
|
|
40
|
+
arrayValidator(value)
|
|
41
|
+
return { name: value[0], sorter: value[1] ? ascending : descending }
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Generates a sorting configuration for an object value.
|
|
46
|
+
*
|
|
47
|
+
* @param {Object} value - The object from which to derive a sorting configuration.
|
|
48
|
+
* @returns {Object} A validated sorting configuration based on the object.
|
|
49
|
+
*/
|
|
50
|
+
function objectConfigGenerator(value) {
|
|
51
|
+
objectValidator(value)
|
|
52
|
+
return { name: value.name, sorter: value.sorter ?? ascending }
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Generates a sorting configuration for a string value.
|
|
57
|
+
*
|
|
58
|
+
* @param {string} value - The string from which to derive a sorting configuration.
|
|
59
|
+
* @returns {Object} A sorting configuration using the string name and ascending sort function.
|
|
60
|
+
*/
|
|
61
|
+
function stringConfigGenerator(value) {
|
|
62
|
+
return { name: value, sorter: ascending }
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Validates an array to ensure it can be used to create a sorting configuration.
|
|
67
|
+
*
|
|
68
|
+
* @param {Array} value - The array to validate.
|
|
69
|
+
* @throws {Error} Throws an error if the array does not correspond to a [string, boolean] pair.
|
|
70
|
+
*/
|
|
71
|
+
function arrayValidator(value) {
|
|
72
|
+
if (value.length !== 2 || typeof value[0] !== 'string' || typeof value[1] !== 'boolean') {
|
|
73
|
+
throw new Error('Array should be a pair of [string, boolean]')
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Validates an object to ensure it has the necessary properties for creating a sorting configuration.
|
|
79
|
+
*
|
|
80
|
+
* @param {Object} value - The object to validate.
|
|
81
|
+
* @throws {Error} Throws an error if the object does not have a 'name' property or has an invalid 'sorter'.
|
|
82
|
+
*/
|
|
83
|
+
function objectValidator(value) {
|
|
84
|
+
if (typeof value.name !== 'string') {
|
|
85
|
+
throw new Error('The property "name" is required and must be a string')
|
|
86
|
+
}
|
|
87
|
+
if (value.sorter !== undefined && typeof value.sorter !== 'function') {
|
|
88
|
+
throw new Error('Sorter should be a function')
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Creates a deep scan sample object that contains a union of all keys from all
|
|
94
|
+
* objects in the array, pulling the values from the last item that contains the key.
|
|
95
|
+
*
|
|
96
|
+
* @param {Array<Object>} data - An array of objects to deep scan and collect sample values.
|
|
97
|
+
* @returns {Object} A sample object consolidating all unique keys with their latest non-null values.
|
|
98
|
+
*/
|
|
99
|
+
export function getDeepScanSample(data) {
|
|
100
|
+
return data.reduce(
|
|
101
|
+
(acc, cur) => ({
|
|
102
|
+
...acc,
|
|
103
|
+
...compact(omit(Object.keys(acc), cur))
|
|
104
|
+
}),
|
|
105
|
+
{}
|
|
106
|
+
)
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Derives a unique list of column names from an array of data objects.
|
|
110
|
+
* Each data object represents a row and this function compiles a list of all unique property keys
|
|
111
|
+
* present across these objects.
|
|
112
|
+
*
|
|
113
|
+
* @param {Object[]} data - The array of data objects to analyze for column names.
|
|
114
|
+
* @returns {string[]} - An array of strings representing the unique column names found across all data objects.
|
|
115
|
+
*/
|
|
116
|
+
export function deriveColumns(data, options) {
|
|
117
|
+
if (data.length === 0) return []
|
|
118
|
+
|
|
119
|
+
const { scanMode } = { ...defaultViewOptions, ...options }
|
|
120
|
+
const sample = scanMode === 'fast' ? data[0] : getDeepScanSample(data)
|
|
121
|
+
|
|
122
|
+
return deriveColumnProperties(sample, options)
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Derives the data types of columns based on the data provided in an array of objects.
|
|
127
|
+
* The function assumes that all values of a single field (column) are of the same type. It uses
|
|
128
|
+
* the first object in the data array as a reference for the field names. Each field is categorized
|
|
129
|
+
* as either 'string' or 'number' based on whether any of the field values in the data set are non-numeric.
|
|
130
|
+
*
|
|
131
|
+
* @param {Object[]} data - The array of data objects to assess for column data types.
|
|
132
|
+
* @returns {Object} - An object with keys being the data types ('string' & 'number') and values
|
|
133
|
+
* being arrays of field names that correspond to that data type.
|
|
134
|
+
*/
|
|
135
|
+
export function deriveDataTypes(data) {
|
|
136
|
+
const dataTypes = Object.keys(data[0])
|
|
137
|
+
.map((field) => ({
|
|
138
|
+
field,
|
|
139
|
+
type: data.map((d) => d[field]).some(isNaN) ? 'string' : 'number'
|
|
140
|
+
}))
|
|
141
|
+
.reduce((acc, cur) => ({ ...acc, [cur.type]: [...acc[cur.type], cur.field] }), {
|
|
142
|
+
string: [],
|
|
143
|
+
number: []
|
|
144
|
+
})
|
|
145
|
+
return dataTypes
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Infers the data type of a set of values by examining the values in the array.
|
|
150
|
+
* Assumes all values in the array should have the same type, unless they are 'null'.
|
|
151
|
+
* If multiple types are detected (excluding 'null'), the function will return 'mixed'.
|
|
152
|
+
* If all values are 'null', it returns 'null', and it returns 'undefined' for an empty array.
|
|
153
|
+
*
|
|
154
|
+
* @param {Array} values - An array of values for which the data type is to be inferred.
|
|
155
|
+
* @returns {string} - A string representing the inferred data type. Possible values are
|
|
156
|
+
* 'undefined', 'null', 'mixed', or any data type returned by the getType function.
|
|
157
|
+
*/
|
|
158
|
+
export function inferDataType(values) {
|
|
159
|
+
if (values.length === 0) {
|
|
160
|
+
return 'undefined'
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
const nonNullValues = values.filter((value) => value !== null)
|
|
164
|
+
|
|
165
|
+
if (nonNullValues.length === 0) {
|
|
166
|
+
return 'null'
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
let type = getType(nonNullValues[0])
|
|
170
|
+
|
|
171
|
+
for (let i = 1; i < nonNullValues.length; i++) {
|
|
172
|
+
if (getType(nonNullValues[i]) !== type) {
|
|
173
|
+
type = 'mixed'
|
|
174
|
+
break
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
return type
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Derives column metadata from the data to be used in a tabular component.
|
|
183
|
+
*
|
|
184
|
+
* @param {Array} data - The data to derive column metadata from.
|
|
185
|
+
* @returns {Array<import('./types').ColumnMetadata>} - The derived column metadata.
|
|
186
|
+
*/
|
|
187
|
+
export function deriveMetadata(dataArray, options = {}) {
|
|
188
|
+
let { columns = [] } = options
|
|
189
|
+
if (dataArray.length === 0) return columns
|
|
190
|
+
const { actions, language } = { ...defaultViewOptions, ...options }
|
|
191
|
+
|
|
192
|
+
if (columns.length === 0) {
|
|
193
|
+
columns = deriveColumns(dataArray, options)
|
|
194
|
+
} else {
|
|
195
|
+
columns.forEach((col) => {
|
|
196
|
+
// if (col.sortable && !col.order) col.order = 'none'
|
|
197
|
+
if (!col.fields) col.fields = { text: col.name }
|
|
198
|
+
})
|
|
199
|
+
}
|
|
200
|
+
columns = addFormatters(columns, language)
|
|
201
|
+
if (actions.length > 0) columns = deriveActions(columns, actions)
|
|
202
|
+
|
|
203
|
+
return columns
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* Adds a formatter function to each column in the array based on the column type. If a formatter
|
|
208
|
+
* function is already present, it will not be overwritten. The formatter is created based on the
|
|
209
|
+
* override formatter if present, or the column type and language.
|
|
210
|
+
*
|
|
211
|
+
* @param {Array<import('./types').ColumnMetadata>} columns - The array of column metadata to add formatters to.
|
|
212
|
+
* @param {string} language - The language to use for formatting.
|
|
213
|
+
* @returns {Array<import('./types').ColumnMetadata>} - The array of column metadata with formatters added.
|
|
214
|
+
*/
|
|
215
|
+
export function addFormatters(columns, language) {
|
|
216
|
+
return columns.map((column) => {
|
|
217
|
+
const formatter = column.formatter ?? column.type
|
|
218
|
+
if (typeof formatter !== 'function') {
|
|
219
|
+
const digits = column.digits ?? (['number', 'currency'].includes(column.type) ? 2 : 0)
|
|
220
|
+
column.formatter = createFormatter(formatter, language, digits)
|
|
221
|
+
}
|
|
222
|
+
return column
|
|
223
|
+
})
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* Converts an array of action names to an array of action objects.
|
|
228
|
+
*
|
|
229
|
+
* @param {Array<string|import('./types').Action>} input - The input to convert to actions.
|
|
230
|
+
* @returns {Array<import('./types').Action>} - The converted actions.
|
|
231
|
+
*/
|
|
232
|
+
export function convertToActions(input) {
|
|
233
|
+
if (!Array.isArray(input)) throw new Error('Actions must be an array')
|
|
234
|
+
const actions = input
|
|
235
|
+
.map((action) => {
|
|
236
|
+
if (typeof action === 'string') {
|
|
237
|
+
return { name: action, label: action, order: defaultActionOrder[action] }
|
|
238
|
+
}
|
|
239
|
+
return { order: defaultActionOrder[action.name], ...action }
|
|
240
|
+
})
|
|
241
|
+
.sort((a, b) => a.order - b.order)
|
|
242
|
+
return actions
|
|
243
|
+
}
|
|
244
|
+
/**
|
|
245
|
+
* Adds actions to the column metadata. This function updates the columns array in place.
|
|
246
|
+
*
|
|
247
|
+
* @param {Array<import('./types').ColumnMetadata>} columns - The column metadata to update.
|
|
248
|
+
* @param {Array<string|import('./types').Action>} input - The actions to add to the column metadata.
|
|
249
|
+
* @returns {Array<import('./types').ColumnMetadata>} - The updated column metadata.
|
|
250
|
+
*/
|
|
251
|
+
export function deriveActions(columns, input) {
|
|
252
|
+
// convert array of strings to array of objects
|
|
253
|
+
const actions = convertToActions(input)
|
|
254
|
+
const existing = columns.filter(({ action }) => action).map(({ action }) => action)
|
|
255
|
+
|
|
256
|
+
const actionColumns = actions
|
|
257
|
+
.filter(({ name }) => !existing.includes(name))
|
|
258
|
+
.map(({ name, label }) => ({
|
|
259
|
+
label,
|
|
260
|
+
action: name
|
|
261
|
+
}))
|
|
262
|
+
return [...actionColumns, ...columns]
|
|
263
|
+
}
|
package/src/list.js
ADDED
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
import { writable, get } from 'svelte/store'
|
|
2
|
+
import { ascending } from 'd3-array'
|
|
3
|
+
import { nest } from 'd3-collection'
|
|
4
|
+
import { v4 as uuid } from '@lukeed/uuid'
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Compares two items for sorting. When grouped, uses group comparison before item comparison
|
|
8
|
+
*
|
|
9
|
+
* @param {*} a first item
|
|
10
|
+
* @param {*} b second item
|
|
11
|
+
* @param {*} data
|
|
12
|
+
* @returns
|
|
13
|
+
*/
|
|
14
|
+
export function compare(a, b, data) {
|
|
15
|
+
let result = 0
|
|
16
|
+
|
|
17
|
+
if (data.groupKey) {
|
|
18
|
+
result = ascending(data.lookup[a[data.groupKey]], data.lookup[b[data.groupKey]])
|
|
19
|
+
}
|
|
20
|
+
if (result === 0) {
|
|
21
|
+
result = ascending(a[data.sortKey], b[data.sortKey])
|
|
22
|
+
}
|
|
23
|
+
return result
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Search the data for existence of string in the data
|
|
28
|
+
*
|
|
29
|
+
* @param {List} data
|
|
30
|
+
* @param {string} value
|
|
31
|
+
* @returns
|
|
32
|
+
*/
|
|
33
|
+
export function quickSearch(data, value) {
|
|
34
|
+
return data.data.filter((d) => d[data.filterKey].toLowerCase().includes(value.toLowerCase()))
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Implements a list containing data and functionality for sorting, filtering, and grouping.
|
|
39
|
+
*/
|
|
40
|
+
export class List {
|
|
41
|
+
/**
|
|
42
|
+
* Creates a List instance with initial data and default settings for keys and functions.
|
|
43
|
+
* @param {Array<object>} data - The initial array of objects to populate the list.
|
|
44
|
+
*/
|
|
45
|
+
constructor(data) {
|
|
46
|
+
this.data = data
|
|
47
|
+
|
|
48
|
+
this.primaryKey = 'id'
|
|
49
|
+
this.filterKey = undefined
|
|
50
|
+
this.sortKey = undefined
|
|
51
|
+
this.groupKey = undefined
|
|
52
|
+
this.lookup = {}
|
|
53
|
+
this.searchText = ''
|
|
54
|
+
|
|
55
|
+
this.filterUsing = undefined
|
|
56
|
+
this.sortUsing = undefined
|
|
57
|
+
this.filtered = writable(data)
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Sets a primary key attribute for list items and assigns a unique identifier if not present.
|
|
62
|
+
*
|
|
63
|
+
* @param {string} value - The property name to be set as the primary key.
|
|
64
|
+
* @returns {List} This list instance for method chaining.
|
|
65
|
+
*/
|
|
66
|
+
key(value) {
|
|
67
|
+
this.primaryKey = value
|
|
68
|
+
this.data = this.data.map((item) => (value in item ? item : { ...item, [value]: uuid() }))
|
|
69
|
+
|
|
70
|
+
return this
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Configures sorting by a given key and sorting function.
|
|
75
|
+
*
|
|
76
|
+
* @param {string} key - The property name to sort by.
|
|
77
|
+
* @param {Function} [using=compare] - The function used to sort the list items.
|
|
78
|
+
* @returns {List} This list instance for method chaining.
|
|
79
|
+
*/
|
|
80
|
+
sortBy(key, using = compare) {
|
|
81
|
+
this.sortKey = key
|
|
82
|
+
this.sortUsing = using
|
|
83
|
+
|
|
84
|
+
return this.sort().refresh()
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Groups list items by a given key and maps group keys for display using an optional lookup object.
|
|
89
|
+
*
|
|
90
|
+
* @param {string} key - The property name to group by.
|
|
91
|
+
* @param {object|null} [lookup=null] - An object mapping group keys to display names.
|
|
92
|
+
* @returns {List} This list instance for method chaining.
|
|
93
|
+
*/
|
|
94
|
+
groupBy(key, lookup = null) {
|
|
95
|
+
this.groupKey = key
|
|
96
|
+
|
|
97
|
+
if (lookup) this.lookup = lookup
|
|
98
|
+
else
|
|
99
|
+
this.lookup = this.data
|
|
100
|
+
.map((item) => item[key])
|
|
101
|
+
.reduce((obj, value) => ({ ...obj, [value]: value }), {})
|
|
102
|
+
|
|
103
|
+
return this.sort().refresh()
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Sets the key and function for filtering list items.
|
|
108
|
+
*
|
|
109
|
+
* @param {string} key - The property name to filter by.
|
|
110
|
+
* @param {Function} [using=quickSearch] - The function used to filter the list items.
|
|
111
|
+
* @returns {List} This list instance for method chaining.
|
|
112
|
+
*/
|
|
113
|
+
filterBy(key, using = quickSearch) {
|
|
114
|
+
this.filterKey = key
|
|
115
|
+
this.filterUsing = using
|
|
116
|
+
return this
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Sorts the list based on the configured sorting function.
|
|
121
|
+
*
|
|
122
|
+
* @returns {List} This list instance for method chaining.
|
|
123
|
+
*/
|
|
124
|
+
sort() {
|
|
125
|
+
if (this.sortUsing) this.data.sort((a, b) => this.sortUsing(a, b, this))
|
|
126
|
+
return this
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Adds a new item to the list, assigning a primary key if necessary, and then sorts and refreshes the list.
|
|
131
|
+
*
|
|
132
|
+
* @param {object} item - The item to add to the list.
|
|
133
|
+
* @returns {List} This list instance for method chaining.
|
|
134
|
+
*/
|
|
135
|
+
add(item) {
|
|
136
|
+
const index = this.data.findIndex((d) => d[this.primaryKey] === item[this.primaryKey])
|
|
137
|
+
if (index === -1) {
|
|
138
|
+
if (!(this.primaryKey in item)) {
|
|
139
|
+
item[this.primaryKey] = uuid()
|
|
140
|
+
}
|
|
141
|
+
this.data.push(item)
|
|
142
|
+
this.sort().refresh()
|
|
143
|
+
}
|
|
144
|
+
return this
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Removes an item from the list by primary key and refreshes the list.
|
|
149
|
+
*
|
|
150
|
+
* @param {object} item - The item to remove from the list.
|
|
151
|
+
* @returns {List} This list instance for method chaining.
|
|
152
|
+
*/
|
|
153
|
+
remove(item) {
|
|
154
|
+
const index = this.data.findIndex((d) => d[this.primaryKey] === item[this.primaryKey])
|
|
155
|
+
if (index > -1) {
|
|
156
|
+
this.data = [...this.data.slice(0, index), ...this.data.slice(index + 1)]
|
|
157
|
+
this.refresh()
|
|
158
|
+
}
|
|
159
|
+
return this
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Modifies an existing item in the list, identified by primary key, and then sorts and refreshes the list.
|
|
164
|
+
*
|
|
165
|
+
* @param {object} item - The item to modify in the list.
|
|
166
|
+
* @returns {List} This list instance for method chaining.
|
|
167
|
+
*/
|
|
168
|
+
modify(item) {
|
|
169
|
+
const index = this.data.findIndex((d) => d[this.primaryKey] === item[this.primaryKey])
|
|
170
|
+
if (index > -1) {
|
|
171
|
+
this.data[index] = { ...item }
|
|
172
|
+
this.sort().refresh()
|
|
173
|
+
}
|
|
174
|
+
return this
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Sets the text to search for in the list's items and then refreshes the list.
|
|
179
|
+
*
|
|
180
|
+
* @param {string} text - The text to search for.
|
|
181
|
+
* @returns {List} This list instance for method chaining.
|
|
182
|
+
*/
|
|
183
|
+
search(text) {
|
|
184
|
+
this.searchText = text
|
|
185
|
+
return this.refresh()
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Refreshes the list based on current sorting, grouping, and filtering configuration.
|
|
190
|
+
*
|
|
191
|
+
* @returns {List} This list instance for method chaining.
|
|
192
|
+
*/
|
|
193
|
+
refresh() {
|
|
194
|
+
let data = [...this.data]
|
|
195
|
+
|
|
196
|
+
if (this.filterKey && this.searchText) {
|
|
197
|
+
data = this.filterUsing(this, this.searchText)
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
if (this.groupKey) {
|
|
201
|
+
data = nest()
|
|
202
|
+
.key((d) => d[this.groupKey])
|
|
203
|
+
.entries(data)
|
|
204
|
+
.map(({ key, values }) => ({
|
|
205
|
+
key,
|
|
206
|
+
name: this.lookup[key],
|
|
207
|
+
data: values
|
|
208
|
+
}))
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
this.filtered.set(data)
|
|
212
|
+
return this
|
|
213
|
+
}
|
|
214
|
+
/**
|
|
215
|
+
* Gets the current state of the filtered list data.
|
|
216
|
+
*
|
|
217
|
+
* @returns {Array<object>} The current filtered data of the list.
|
|
218
|
+
*/
|
|
219
|
+
current() {
|
|
220
|
+
return get(this.filtered)
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* Create a new list
|
|
226
|
+
*
|
|
227
|
+
* @param {Array<object>} data
|
|
228
|
+
* @returns
|
|
229
|
+
*/
|
|
230
|
+
export function list(data) {
|
|
231
|
+
return new List(data)
|
|
232
|
+
}
|
package/src/metadata.js
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { defaultPathOptions } from './constants'
|
|
2
|
+
import { getDeepScanSample } from './infer'
|
|
3
|
+
import { getType } from './utils'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Merges currency-related attributes into the corresponding base columns.
|
|
7
|
+
*
|
|
8
|
+
* @param {Array<Object>} input - An array of column definitions.
|
|
9
|
+
* @param {string} currencySuffix - The suffix used to identify currency columns.
|
|
10
|
+
* @returns {Array<Object>} A new array of merged column definitions.
|
|
11
|
+
*/
|
|
12
|
+
function mergeCurrencyAttributes(input, currencySuffix) {
|
|
13
|
+
let currencyColumns = input.filter((col) => col.name.endsWith(currencySuffix))
|
|
14
|
+
const columns = input.filter((col) => !col.name.endsWith(currencySuffix))
|
|
15
|
+
|
|
16
|
+
currencyColumns = currencyColumns
|
|
17
|
+
.map((col) => {
|
|
18
|
+
const matched = columns.find((column) => column.name === col.name.replace(currencySuffix, ''))
|
|
19
|
+
if (matched) {
|
|
20
|
+
matched.fields.currency = col.name
|
|
21
|
+
matched.type = 'currency'
|
|
22
|
+
matched.digits = 2
|
|
23
|
+
return null
|
|
24
|
+
}
|
|
25
|
+
return col
|
|
26
|
+
})
|
|
27
|
+
.filter((col) => col !== null)
|
|
28
|
+
|
|
29
|
+
return [...columns, ...currencyColumns]
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Adds a path modifier to column definitions that match a specific path.
|
|
34
|
+
*
|
|
35
|
+
* @param {Array<Object>} columns - The original array of column definitions.
|
|
36
|
+
* @param {string|null} path - The path used to identify columns that require path modifiers.
|
|
37
|
+
* @param {string} separator - The separator used in conjunction with the path.
|
|
38
|
+
* @returns {Array<Object>} The array of column definitions with path modifiers added as needed.
|
|
39
|
+
*/
|
|
40
|
+
export function addPathModifier(columns, path, separator) {
|
|
41
|
+
if (!path) return columns
|
|
42
|
+
|
|
43
|
+
const index = columns.findIndex((col) => col.name === path)
|
|
44
|
+
if (index > -1) {
|
|
45
|
+
return [
|
|
46
|
+
{ ...columns[index], path: true, separator },
|
|
47
|
+
...columns.slice(0, index),
|
|
48
|
+
...columns.slice(index + 1)
|
|
49
|
+
]
|
|
50
|
+
}
|
|
51
|
+
return columns
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Derives column properties for a given sample and options.
|
|
56
|
+
*
|
|
57
|
+
* @param {Object} sample - The sample object used to derive column properties.
|
|
58
|
+
* @param {Object} options - The options used to modify column properties.
|
|
59
|
+
* It can specify path, separator, and currencySuffix for column customization.
|
|
60
|
+
* @returns {Array<Object>} An array of derived column definitions.
|
|
61
|
+
*/
|
|
62
|
+
export function deriveColumnProperties(sample, options) {
|
|
63
|
+
const { path, separator, currencySuffix } = { ...defaultPathOptions, ...options }
|
|
64
|
+
|
|
65
|
+
let columns = Object.keys(sample).map((key) => ({
|
|
66
|
+
name: key,
|
|
67
|
+
type: getType(sample[key]),
|
|
68
|
+
sortable: true,
|
|
69
|
+
filterable: true,
|
|
70
|
+
sorted: 'none',
|
|
71
|
+
fields: { text: key }
|
|
72
|
+
}))
|
|
73
|
+
|
|
74
|
+
columns = addPathModifier(columns, path, separator)
|
|
75
|
+
return mergeCurrencyAttributes(columns, currencySuffix)
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Derives the column metadata from the provided data and options.
|
|
80
|
+
*
|
|
81
|
+
* @param {Array} data - The data to derive the column metadata from.
|
|
82
|
+
* @param {Object} [options] - Optional parameters to control the metadata derivation.
|
|
83
|
+
* @param {Array} [options.metadata] - The metadata to use instead of deriving it from the data.
|
|
84
|
+
* @param {boolean} [options.deepScan=false] - Determines if the metadata derivation should perform a deep scan of the data.
|
|
85
|
+
*
|
|
86
|
+
* @returns {import('./types').Metadata} The derived column metadata.
|
|
87
|
+
*/
|
|
88
|
+
export function deriveColumnMetadata(data, options = {}) {
|
|
89
|
+
const { metadata, deepScan = false } = options
|
|
90
|
+
if (Array.isArray(metadata) && metadata.length > 0) return metadata
|
|
91
|
+
if (data.length === 0) return []
|
|
92
|
+
const sample = deepScan ? getDeepScanSample(data) : data[0]
|
|
93
|
+
return Object.entries(sample).map(([name, value]) => ({ name, type: getType(value) }))
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Derive column index from metadata.
|
|
98
|
+
* @param {Array} metadata - The metadata array.
|
|
99
|
+
* @returns {Object} - The column index object.
|
|
100
|
+
*/
|
|
101
|
+
// export function deriveColumnIndex(metadata) {
|
|
102
|
+
// return metadata.reduce((acc, col, index) => {
|
|
103
|
+
// acc[col.name] = index
|
|
104
|
+
// return acc
|
|
105
|
+
// }, {})
|
|
106
|
+
// }
|
package/src/model.js
ADDED
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { identity, clone as copy } from 'ramda'
|
|
2
|
+
import { getDeepScanSample } from './infer'
|
|
3
|
+
import { getType } from './utils'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* A model is a representation of a dataset
|
|
7
|
+
* @returns an object with methods to manipulate the model
|
|
8
|
+
*/
|
|
9
|
+
export function model() {
|
|
10
|
+
let data = []
|
|
11
|
+
|
|
12
|
+
const actions = {
|
|
13
|
+
get: () => data,
|
|
14
|
+
/*
|
|
15
|
+
* Creates a clone from another model
|
|
16
|
+
* @param {Object} other - the other model object to clone from
|
|
17
|
+
*/
|
|
18
|
+
clone: (other) => {
|
|
19
|
+
data = copy(other.get())
|
|
20
|
+
return actions
|
|
21
|
+
},
|
|
22
|
+
/**
|
|
23
|
+
* Renames the reference names using a renamer
|
|
24
|
+
* @param {Function} rename - the renamer function
|
|
25
|
+
*/
|
|
26
|
+
renameUsing: (rename = identity) => {
|
|
27
|
+
if (rename !== identity) {
|
|
28
|
+
data = data.map((x) => ({ ...x, name: rename(x.name) }))
|
|
29
|
+
}
|
|
30
|
+
return actions
|
|
31
|
+
},
|
|
32
|
+
/**
|
|
33
|
+
* Analyzes the input data and derives a model from it
|
|
34
|
+
*
|
|
35
|
+
* @param {Array|Object} value - the data to derive the model from
|
|
36
|
+
* @param {boolean} sparseData - indicates that rows may have missing attributes
|
|
37
|
+
* @returns {Object} this
|
|
38
|
+
*/
|
|
39
|
+
from: (value, sparseData) => {
|
|
40
|
+
data = deriveModel(value, sparseData)
|
|
41
|
+
return actions
|
|
42
|
+
},
|
|
43
|
+
/**
|
|
44
|
+
* Merges the model with another model
|
|
45
|
+
*
|
|
46
|
+
* @param {Array} other - the model to merge with
|
|
47
|
+
* @param {Boolean} override - whether to override the type of the first model
|
|
48
|
+
* @returns {Object} this
|
|
49
|
+
*/
|
|
50
|
+
merge: (other, override = false) => {
|
|
51
|
+
data = mergeModels(data, other.get(), override)
|
|
52
|
+
return actions
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
return actions
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Derives a model from the input data
|
|
60
|
+
*
|
|
61
|
+
* @param {Array|Object} data - the data to derive the model from
|
|
62
|
+
* @param {Boolean} sparse - whether the data set contains sparse data
|
|
63
|
+
* @returns {Array} - the derived model
|
|
64
|
+
*/
|
|
65
|
+
function deriveModel(data, sparse = false) {
|
|
66
|
+
const model = []
|
|
67
|
+
let item = data
|
|
68
|
+
|
|
69
|
+
if (Array.isArray(data)) item = sparse ? getDeepScanSample(data) : data[0]
|
|
70
|
+
|
|
71
|
+
const kv = Object.entries(item)
|
|
72
|
+
kv.forEach(([key, value]) => {
|
|
73
|
+
model.push({
|
|
74
|
+
name: key,
|
|
75
|
+
type: getType(value)
|
|
76
|
+
})
|
|
77
|
+
})
|
|
78
|
+
return model
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Merges two models, returning a new model
|
|
83
|
+
*
|
|
84
|
+
* @param {Array} first - the first model
|
|
85
|
+
* @param {Array} second - the second model
|
|
86
|
+
* @param {Boolean} override - whether to override the type of the first model
|
|
87
|
+
* @returns {Array} - the merged model
|
|
88
|
+
*/
|
|
89
|
+
function mergeModels(first, second, override = false) {
|
|
90
|
+
second.forEach(({ name, type }) => {
|
|
91
|
+
const existing = first.find((x) => x.name === name)
|
|
92
|
+
if (existing) {
|
|
93
|
+
if (existing.type !== type) existing.mixedTypes = true
|
|
94
|
+
if (override) existing.type = type
|
|
95
|
+
} else {
|
|
96
|
+
first.push({ name, type })
|
|
97
|
+
}
|
|
98
|
+
})
|
|
99
|
+
return first
|
|
100
|
+
}
|