juxscript 1.1.166 → 1.1.168
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/lib/components/dataframe.d.ts +13 -47
- package/lib/components/dataframe.d.ts.map +1 -1
- package/lib/components/dataframe.js +134 -92
- package/lib/components/dataframe.ts +149 -102
- package/lib/components/fileupload.d.ts.map +1 -1
- package/lib/components/fileupload.js +14 -8
- package/lib/components/fileupload.ts +15 -9
- package/lib/styles/shadcn.css +354 -0
- package/package.json +1 -1
|
@@ -12,6 +12,8 @@ export interface DataFrameOptions {
|
|
|
12
12
|
filterable?: boolean;
|
|
13
13
|
paginated?: boolean;
|
|
14
14
|
rowsPerPage?: number;
|
|
15
|
+
showStatus?: boolean;
|
|
16
|
+
icon?: string;
|
|
15
17
|
style?: string;
|
|
16
18
|
class?: string;
|
|
17
19
|
}
|
|
@@ -30,78 +32,41 @@ export declare class DataFrameComponent extends BaseComponent<DataFrameState> {
|
|
|
30
32
|
private _storageKey;
|
|
31
33
|
private _pendingSource;
|
|
32
34
|
private _inlineUpload;
|
|
35
|
+
private _showStatus;
|
|
36
|
+
private _icon;
|
|
33
37
|
constructor(id: string, options?: DataFrameOptions);
|
|
34
38
|
protected getTriggerEvents(): readonly string[];
|
|
35
39
|
protected getCallbackEvents(): readonly string[];
|
|
36
|
-
/**
|
|
37
|
-
* Load from IndexedDB by storage key
|
|
38
|
-
*/
|
|
39
40
|
fromStorage(key: string): this;
|
|
40
|
-
/**
|
|
41
|
-
* Load from a FileUpload component — auto-wires change event
|
|
42
|
-
*/
|
|
43
41
|
fromUpload(upload: FileUpload): this;
|
|
44
|
-
/**
|
|
45
|
-
* Load from raw data — array of objects or Record<string, any[]>
|
|
46
|
-
*/
|
|
47
42
|
fromData(data: Record<string, any>[] | Record<string, any[]>): this;
|
|
48
43
|
/**
|
|
49
|
-
* Add an inline file upload control
|
|
50
|
-
*
|
|
51
|
-
*
|
|
52
|
-
* @param label - Button/label text (default: 'Upload File')
|
|
44
|
+
* Add an inline file upload control.
|
|
45
|
+
* @param label - Button label (default: 'Upload File')
|
|
53
46
|
* @param accept - File types (default: '.csv,.tsv,.txt,.xlsx,.xls')
|
|
47
|
+
* @param icon - Upload icon name (default: 'upload'). Pass '' to hide icon.
|
|
54
48
|
*/
|
|
55
|
-
withUpload(label?: string, accept?: string): this;
|
|
56
|
-
/**
|
|
57
|
-
|
|
58
|
-
|
|
49
|
+
withUpload(label?: string, accept?: string, icon?: string): this;
|
|
50
|
+
/** Show/hide the status bar */
|
|
51
|
+
showStatus(v: boolean): this;
|
|
52
|
+
/** Set a custom icon for the status bar */
|
|
53
|
+
statusIcon(v: string): this;
|
|
59
54
|
apply(fn: (df: DataFrame) => DataFrame): this;
|
|
60
|
-
/**
|
|
61
|
-
* Filter rows
|
|
62
|
-
*/
|
|
63
55
|
filter(predicate: (row: Record<string, any>, index: number) => boolean): this;
|
|
64
|
-
/**
|
|
65
|
-
* Select columns
|
|
66
|
-
*/
|
|
67
56
|
select(...cols: string[]): this;
|
|
68
|
-
/**
|
|
69
|
-
* Sort by column
|
|
70
|
-
*/
|
|
71
57
|
sort(col: string, descending?: boolean): this;
|
|
72
|
-
/**
|
|
73
|
-
* Show first N rows
|
|
74
|
-
*/
|
|
75
58
|
head(n?: number): this;
|
|
76
|
-
/**
|
|
77
|
-
* Show last N rows
|
|
78
|
-
*/
|
|
79
59
|
tail(n?: number): this;
|
|
80
|
-
/**
|
|
81
|
-
* Add a computed column
|
|
82
|
-
*/
|
|
83
60
|
withColumn(name: string, fn: (row: Record<string, any>, index: number) => any): this;
|
|
84
|
-
/**
|
|
85
|
-
* Where clause
|
|
86
|
-
*/
|
|
87
61
|
where(col: string, op: '==' | '!=' | '>' | '<' | '>=' | '<=' | 'contains' | 'startsWith' | 'endsWith', value: any): this;
|
|
88
|
-
/** Get the underlying DataFrame */
|
|
89
62
|
get df(): DataFrame | null;
|
|
90
|
-
/** Get the underlying TabularDriver */
|
|
91
63
|
get driver(): TabularDriver;
|
|
92
|
-
/** Get the internal Table component */
|
|
93
64
|
get table(): Table | null;
|
|
94
|
-
/** Get describe() stats */
|
|
95
65
|
describe(): Record<string, any> | null;
|
|
96
|
-
/** Export to CSV string */
|
|
97
66
|
toCSV(delimiter?: string): string;
|
|
98
|
-
/** Export to row objects */
|
|
99
67
|
toRows(): Record<string, any>[];
|
|
100
|
-
/** Get shape */
|
|
101
68
|
get shape(): [number, number];
|
|
102
|
-
/** Get column names */
|
|
103
69
|
get columns(): string[];
|
|
104
|
-
/** Save current DataFrame to IndexedDB */
|
|
105
70
|
save(key?: string): Promise<string | null>;
|
|
106
71
|
striped(v: boolean): this;
|
|
107
72
|
hoverable(v: boolean): this;
|
|
@@ -112,6 +77,7 @@ export declare class DataFrameComponent extends BaseComponent<DataFrameState> {
|
|
|
112
77
|
private _updateStatus;
|
|
113
78
|
private _setDataFrame;
|
|
114
79
|
private _updateTable;
|
|
80
|
+
private _showFilterInput;
|
|
115
81
|
update(prop: string, value: any): void;
|
|
116
82
|
render(targetId?: string | HTMLElement | BaseComponent<any>): this;
|
|
117
83
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dataframe.d.ts","sourceRoot":"","sources":["dataframe.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM,yBAAyB,CAAC;AACnE,OAAO,EAAE,SAAS,EAAE,MAAM,yBAAyB,CAAC;AACpD,OAAO,EAAE,aAAa,EAAE,MAAM,6BAA6B,CAAC;AAC5D,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAC7C,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"dataframe.d.ts","sourceRoot":"","sources":["dataframe.ts"],"names":[],"mappings":"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"}
|
|
@@ -3,6 +3,7 @@ import { DataFrame } from '../storage/DataFrame.js';
|
|
|
3
3
|
import { TabularDriver } from '../storage/TabularDriver.js';
|
|
4
4
|
import { FileUpload } from './fileupload.js';
|
|
5
5
|
import { Table } from './table.js';
|
|
6
|
+
import { renderIcon } from './icons.js';
|
|
6
7
|
const TRIGGER_EVENTS = [];
|
|
7
8
|
const CALLBACK_EVENTS = ['load', 'error', 'transform'];
|
|
8
9
|
export class DataFrameComponent extends BaseComponent {
|
|
@@ -24,12 +25,16 @@ export class DataFrameComponent extends BaseComponent {
|
|
|
24
25
|
this._storageKey = null;
|
|
25
26
|
this._pendingSource = null;
|
|
26
27
|
this._inlineUpload = null;
|
|
28
|
+
this._showStatus = true;
|
|
29
|
+
this._icon = '';
|
|
27
30
|
this._driver = new TabularDriver(options.dbName ?? 'jux-dataframes', options.storeName ?? 'frames');
|
|
31
|
+
this._showStatus = options.showStatus ?? true;
|
|
32
|
+
this._icon = options.icon ?? '';
|
|
28
33
|
this._tableOptions = {
|
|
29
34
|
striped: options.striped ?? true,
|
|
30
35
|
hoverable: options.hoverable ?? true,
|
|
31
36
|
sortable: options.sortable ?? true,
|
|
32
|
-
filterable: options.filterable ??
|
|
37
|
+
filterable: options.filterable ?? false, // defer until data loaded
|
|
33
38
|
paginated: options.paginated ?? true,
|
|
34
39
|
rowsPerPage: options.rowsPerPage ?? 25
|
|
35
40
|
};
|
|
@@ -39,18 +44,17 @@ export class DataFrameComponent extends BaseComponent {
|
|
|
39
44
|
/* ═══════════════════════════════════════════════════
|
|
40
45
|
* DATA SOURCES
|
|
41
46
|
* ═══════════════════════════════════════════════════ */
|
|
42
|
-
/**
|
|
43
|
-
* Load from IndexedDB by storage key
|
|
44
|
-
*/
|
|
45
47
|
fromStorage(key) {
|
|
46
48
|
this._storageKey = key;
|
|
47
|
-
|
|
49
|
+
const loadFn = async () => {
|
|
48
50
|
this.state.loading = true;
|
|
51
|
+
this._updateStatus('⏳ Loading...', 'loading');
|
|
49
52
|
try {
|
|
50
|
-
|
|
53
|
+
const df = await this._driver.loadByName(key);
|
|
51
54
|
if (!df) {
|
|
52
55
|
this._triggerCallback('error', 'No table found with key: ' + key, null, this);
|
|
53
56
|
this.state.loading = false;
|
|
57
|
+
this._updateStatus('No table found: ' + key, 'empty');
|
|
54
58
|
return;
|
|
55
59
|
}
|
|
56
60
|
this._setDataFrame(df, 'storage: ' + key);
|
|
@@ -58,42 +62,44 @@ export class DataFrameComponent extends BaseComponent {
|
|
|
58
62
|
catch (err) {
|
|
59
63
|
this._triggerCallback('error', err.message, null, this);
|
|
60
64
|
this.state.loading = false;
|
|
65
|
+
this._updateStatus('❌ ' + err.message, 'error');
|
|
61
66
|
}
|
|
62
67
|
};
|
|
68
|
+
if (this._table) {
|
|
69
|
+
loadFn();
|
|
70
|
+
}
|
|
71
|
+
else {
|
|
72
|
+
this._pendingSource = loadFn;
|
|
73
|
+
}
|
|
63
74
|
return this;
|
|
64
75
|
}
|
|
65
|
-
/**
|
|
66
|
-
* Load from a FileUpload component — auto-wires change event
|
|
67
|
-
*/
|
|
68
76
|
fromUpload(upload) {
|
|
69
77
|
this._uploadRef = upload;
|
|
70
78
|
this._pendingSource = async () => {
|
|
71
|
-
// Wire upload's change event to parse incoming files
|
|
72
79
|
upload.bind('change', async (files) => {
|
|
73
80
|
if (!files || files.length === 0)
|
|
74
81
|
return;
|
|
75
82
|
const file = files[0];
|
|
76
83
|
this.state.loading = true;
|
|
84
|
+
this._updateStatus('⏳ Parsing ' + file.name + '...', 'loading');
|
|
77
85
|
try {
|
|
78
86
|
const df = await this._driver.streamFile(file);
|
|
79
|
-
// Auto-persist to IndexedDB
|
|
80
87
|
await this._driver.store(file.name, df, { source: file.name });
|
|
81
|
-
this._setDataFrame(df,
|
|
88
|
+
this._setDataFrame(df, file.name);
|
|
82
89
|
}
|
|
83
90
|
catch (err) {
|
|
84
91
|
this._triggerCallback('error', err.message, null, this);
|
|
85
92
|
this.state.loading = false;
|
|
93
|
+
this._updateStatus('❌ ' + err.message, 'error');
|
|
86
94
|
}
|
|
87
95
|
});
|
|
88
96
|
};
|
|
89
97
|
return this;
|
|
90
98
|
}
|
|
91
|
-
/**
|
|
92
|
-
* Load from raw data — array of objects or Record<string, any[]>
|
|
93
|
-
*/
|
|
94
99
|
fromData(data) {
|
|
95
|
-
|
|
100
|
+
const loadFn = async () => {
|
|
96
101
|
this.state.loading = true;
|
|
102
|
+
this._updateStatus('⏳ Loading data...', 'loading');
|
|
97
103
|
try {
|
|
98
104
|
const df = new DataFrame(data);
|
|
99
105
|
this._setDataFrame(df, 'inline data');
|
|
@@ -101,27 +107,37 @@ export class DataFrameComponent extends BaseComponent {
|
|
|
101
107
|
catch (err) {
|
|
102
108
|
this._triggerCallback('error', err.message, null, this);
|
|
103
109
|
this.state.loading = false;
|
|
110
|
+
this._updateStatus('❌ ' + err.message, 'error');
|
|
104
111
|
}
|
|
105
112
|
};
|
|
113
|
+
if (this._table) {
|
|
114
|
+
loadFn();
|
|
115
|
+
}
|
|
116
|
+
else {
|
|
117
|
+
this._pendingSource = loadFn;
|
|
118
|
+
}
|
|
106
119
|
return this;
|
|
107
120
|
}
|
|
108
121
|
/**
|
|
109
|
-
* Add an inline file upload control
|
|
110
|
-
*
|
|
111
|
-
*
|
|
112
|
-
* @param label - Button/label text (default: 'Upload File')
|
|
122
|
+
* Add an inline file upload control.
|
|
123
|
+
* @param label - Button label (default: 'Upload File')
|
|
113
124
|
* @param accept - File types (default: '.csv,.tsv,.txt,.xlsx,.xls')
|
|
125
|
+
* @param icon - Upload icon name (default: 'upload'). Pass '' to hide icon.
|
|
114
126
|
*/
|
|
115
|
-
withUpload(label = 'Upload File', accept = '.csv,.tsv,.txt,.xlsx,.xls') {
|
|
116
|
-
this._inlineUpload = { label, accept };
|
|
127
|
+
withUpload(label = 'Upload File', accept = '.csv,.tsv,.txt,.xlsx,.xls', icon = 'upload') {
|
|
128
|
+
this._inlineUpload = { label, accept, icon };
|
|
117
129
|
return this;
|
|
118
130
|
}
|
|
119
131
|
/* ═══════════════════════════════════════════════════
|
|
120
|
-
*
|
|
132
|
+
* UI TOGGLES
|
|
133
|
+
* ═══════════════════════════════════════════════════ */
|
|
134
|
+
/** Show/hide the status bar */
|
|
135
|
+
showStatus(v) { this._showStatus = v; return this; }
|
|
136
|
+
/** Set a custom icon for the status bar */
|
|
137
|
+
statusIcon(v) { this._icon = v; return this; }
|
|
138
|
+
/* ═══════════════════════════════════════════════════
|
|
139
|
+
* TRANSFORM API
|
|
121
140
|
* ═══════════════════════════════════════════════════ */
|
|
122
|
-
/**
|
|
123
|
-
* Apply a transform to the current DataFrame and update the table
|
|
124
|
-
*/
|
|
125
141
|
apply(fn) {
|
|
126
142
|
if (!this._df)
|
|
127
143
|
return this;
|
|
@@ -130,78 +146,38 @@ export class DataFrameComponent extends BaseComponent {
|
|
|
130
146
|
this._triggerCallback('transform', result, null, this);
|
|
131
147
|
return this;
|
|
132
148
|
}
|
|
133
|
-
/**
|
|
134
|
-
* Filter rows
|
|
135
|
-
*/
|
|
136
149
|
filter(predicate) {
|
|
137
150
|
return this.apply(df => df.filter(predicate));
|
|
138
151
|
}
|
|
139
|
-
/**
|
|
140
|
-
* Select columns
|
|
141
|
-
*/
|
|
142
152
|
select(...cols) {
|
|
143
153
|
return this.apply(df => df.select(...cols));
|
|
144
154
|
}
|
|
145
|
-
/**
|
|
146
|
-
* Sort by column
|
|
147
|
-
*/
|
|
148
155
|
sort(col, descending) {
|
|
149
156
|
return this.apply(df => df.sort(col, descending));
|
|
150
157
|
}
|
|
151
|
-
/**
|
|
152
|
-
* Show first N rows
|
|
153
|
-
*/
|
|
154
158
|
head(n = 5) {
|
|
155
159
|
return this.apply(df => df.head(n));
|
|
156
160
|
}
|
|
157
|
-
/**
|
|
158
|
-
* Show last N rows
|
|
159
|
-
*/
|
|
160
161
|
tail(n = 5) {
|
|
161
162
|
return this.apply(df => df.tail(n));
|
|
162
163
|
}
|
|
163
|
-
/**
|
|
164
|
-
* Add a computed column
|
|
165
|
-
*/
|
|
166
164
|
withColumn(name, fn) {
|
|
167
165
|
return this.apply(df => df.withColumn(name, fn));
|
|
168
166
|
}
|
|
169
|
-
/**
|
|
170
|
-
* Where clause
|
|
171
|
-
*/
|
|
172
167
|
where(col, op, value) {
|
|
173
168
|
return this.apply(df => df.where(col, op, value));
|
|
174
169
|
}
|
|
175
170
|
/* ═══════════════════════════════════════════════════
|
|
176
171
|
* ACCESSORS
|
|
177
172
|
* ═══════════════════════════════════════════════════ */
|
|
178
|
-
/** Get the underlying DataFrame */
|
|
179
173
|
get df() { return this._df; }
|
|
180
|
-
/** Get the underlying TabularDriver */
|
|
181
174
|
get driver() { return this._driver; }
|
|
182
|
-
/** Get the internal Table component */
|
|
183
175
|
get table() { return this._table; }
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
toCSV(delimiter) {
|
|
190
|
-
return this._df?.toCSV(delimiter) ?? '';
|
|
191
|
-
}
|
|
192
|
-
/** Export to row objects */
|
|
193
|
-
toRows() {
|
|
194
|
-
return this._df?.toRows() ?? [];
|
|
195
|
-
}
|
|
196
|
-
/** Get shape */
|
|
197
|
-
get shape() {
|
|
198
|
-
return this._df?.shape ?? [0, 0];
|
|
199
|
-
}
|
|
200
|
-
/** Get column names */
|
|
201
|
-
get columns() {
|
|
202
|
-
return this._df?.columns ?? [];
|
|
203
|
-
}
|
|
204
|
-
/** Save current DataFrame to IndexedDB */
|
|
176
|
+
describe() { return this._df?.describe() ?? null; }
|
|
177
|
+
toCSV(delimiter) { return this._df?.toCSV(delimiter) ?? ''; }
|
|
178
|
+
toRows() { return this._df?.toRows() ?? []; }
|
|
179
|
+
get shape() { return this._df?.shape ?? [0, 0]; }
|
|
180
|
+
get columns() { return this._df?.columns ?? []; }
|
|
205
181
|
async save(key) {
|
|
206
182
|
if (!this._df)
|
|
207
183
|
return null;
|
|
@@ -220,10 +196,26 @@ export class DataFrameComponent extends BaseComponent {
|
|
|
220
196
|
/* ═══════════════════════════════════════════════════
|
|
221
197
|
* INTERNAL
|
|
222
198
|
* ═══════════════════════════════════════════════════ */
|
|
223
|
-
_updateStatus(text) {
|
|
199
|
+
_updateStatus(text, type = 'empty') {
|
|
224
200
|
const el = document.getElementById(`${this._id}-status`);
|
|
225
|
-
if (el)
|
|
226
|
-
|
|
201
|
+
if (!el)
|
|
202
|
+
return;
|
|
203
|
+
el.className = 'jux-dataframe-status';
|
|
204
|
+
if (type)
|
|
205
|
+
el.classList.add(`jux-dataframe-status-${type}`);
|
|
206
|
+
// Clear and rebuild
|
|
207
|
+
el.innerHTML = '';
|
|
208
|
+
if (this._icon && type === 'success') {
|
|
209
|
+
const iconEl = renderIcon(this._icon);
|
|
210
|
+
iconEl.style.width = '16px';
|
|
211
|
+
iconEl.style.height = '16px';
|
|
212
|
+
iconEl.style.marginRight = '6px';
|
|
213
|
+
iconEl.style.verticalAlign = 'middle';
|
|
214
|
+
el.appendChild(iconEl);
|
|
215
|
+
}
|
|
216
|
+
const span = document.createElement('span');
|
|
217
|
+
span.textContent = text;
|
|
218
|
+
el.appendChild(span);
|
|
227
219
|
}
|
|
228
220
|
_setDataFrame(df, sourceName) {
|
|
229
221
|
this._df = df;
|
|
@@ -237,7 +229,11 @@ export class DataFrameComponent extends BaseComponent {
|
|
|
237
229
|
this._df = df.select(...cleanCols);
|
|
238
230
|
}
|
|
239
231
|
this._updateTable();
|
|
240
|
-
this._updateStatus(
|
|
232
|
+
this._updateStatus(`${sourceName} — ${this._df.height} rows × ${this._df.width} cols`, 'success');
|
|
233
|
+
// Enable filter now that data exists
|
|
234
|
+
if (this._tableOptions.filterable) {
|
|
235
|
+
this._showFilterInput();
|
|
236
|
+
}
|
|
241
237
|
this._triggerCallback('load', this._df, null, this);
|
|
242
238
|
}
|
|
243
239
|
_updateTable() {
|
|
@@ -245,6 +241,49 @@ export class DataFrameComponent extends BaseComponent {
|
|
|
245
241
|
return;
|
|
246
242
|
this._table.columns(this._df.columns).rows(this._df.toRows());
|
|
247
243
|
}
|
|
244
|
+
_showFilterInput() {
|
|
245
|
+
const wrapper = document.getElementById(this._id);
|
|
246
|
+
if (!wrapper)
|
|
247
|
+
return;
|
|
248
|
+
// Only add once
|
|
249
|
+
if (wrapper.querySelector('.jux-dataframe-filter'))
|
|
250
|
+
return;
|
|
251
|
+
const filterContainer = document.createElement('div');
|
|
252
|
+
filterContainer.className = 'jux-dataframe-filter';
|
|
253
|
+
const input = document.createElement('input');
|
|
254
|
+
input.type = 'text';
|
|
255
|
+
input.placeholder = 'Filter rows...';
|
|
256
|
+
input.className = 'jux-input-element jux-dataframe-filter-input';
|
|
257
|
+
const iconEl = renderIcon('search');
|
|
258
|
+
iconEl.style.width = '16px';
|
|
259
|
+
iconEl.style.height = '16px';
|
|
260
|
+
const iconWrap = document.createElement('span');
|
|
261
|
+
iconWrap.className = 'jux-dataframe-filter-icon';
|
|
262
|
+
iconWrap.appendChild(iconEl);
|
|
263
|
+
filterContainer.appendChild(iconWrap);
|
|
264
|
+
filterContainer.appendChild(input);
|
|
265
|
+
// Insert before the table
|
|
266
|
+
const tableWrapper = wrapper.querySelector('.jux-table-wrapper');
|
|
267
|
+
if (tableWrapper) {
|
|
268
|
+
wrapper.insertBefore(filterContainer, tableWrapper);
|
|
269
|
+
}
|
|
270
|
+
else {
|
|
271
|
+
wrapper.appendChild(filterContainer);
|
|
272
|
+
}
|
|
273
|
+
input.addEventListener('input', () => {
|
|
274
|
+
if (!this._df)
|
|
275
|
+
return;
|
|
276
|
+
const text = input.value.toLowerCase();
|
|
277
|
+
if (!text) {
|
|
278
|
+
this._table?.rows(this._df.toRows());
|
|
279
|
+
return;
|
|
280
|
+
}
|
|
281
|
+
const filtered = this._df.filter((row) => {
|
|
282
|
+
return Object.values(row).some(v => v !== null && v !== undefined && String(v).toLowerCase().includes(text));
|
|
283
|
+
});
|
|
284
|
+
this._table?.rows(filtered.toRows());
|
|
285
|
+
});
|
|
286
|
+
}
|
|
248
287
|
update(prop, value) { }
|
|
249
288
|
/* ═══════════════════════════════════════════════════
|
|
250
289
|
* RENDER
|
|
@@ -259,13 +298,16 @@ export class DataFrameComponent extends BaseComponent {
|
|
|
259
298
|
wrapper.className += ` ${className}`;
|
|
260
299
|
if (style)
|
|
261
300
|
wrapper.setAttribute('style', style);
|
|
262
|
-
// Inline upload
|
|
301
|
+
// Inline upload
|
|
263
302
|
if (this._inlineUpload) {
|
|
264
|
-
const
|
|
303
|
+
const uploadOpts = {
|
|
265
304
|
label: this._inlineUpload.label,
|
|
266
|
-
accept: this._inlineUpload.accept
|
|
267
|
-
}
|
|
268
|
-
|
|
305
|
+
accept: this._inlineUpload.accept,
|
|
306
|
+
};
|
|
307
|
+
if (this._inlineUpload.icon) {
|
|
308
|
+
uploadOpts.icon = this._inlineUpload.icon;
|
|
309
|
+
}
|
|
310
|
+
const upload = new FileUpload(`${this._id}-upload`, uploadOpts);
|
|
269
311
|
this._uploadRef = upload;
|
|
270
312
|
this._pendingSource = async () => {
|
|
271
313
|
upload.bind('change', async (files) => {
|
|
@@ -273,7 +315,7 @@ export class DataFrameComponent extends BaseComponent {
|
|
|
273
315
|
return;
|
|
274
316
|
const file = files[0];
|
|
275
317
|
this.state.loading = true;
|
|
276
|
-
this._updateStatus('⏳ Parsing ' + file.name + '...');
|
|
318
|
+
this._updateStatus('⏳ Parsing ' + file.name + '...', 'loading');
|
|
277
319
|
try {
|
|
278
320
|
const df = await this._driver.streamFile(file);
|
|
279
321
|
await this._driver.store(file.name, df, { source: file.name });
|
|
@@ -282,11 +324,10 @@ export class DataFrameComponent extends BaseComponent {
|
|
|
282
324
|
catch (err) {
|
|
283
325
|
this._triggerCallback('error', err.message, null, this);
|
|
284
326
|
this.state.loading = false;
|
|
285
|
-
this._updateStatus('❌ ' + err.message);
|
|
327
|
+
this._updateStatus('❌ ' + err.message, 'error');
|
|
286
328
|
}
|
|
287
329
|
});
|
|
288
330
|
};
|
|
289
|
-
// Render upload into wrapper before status/table
|
|
290
331
|
const uploadContainer = document.createElement('div');
|
|
291
332
|
uploadContainer.className = 'jux-dataframe-upload';
|
|
292
333
|
uploadContainer.id = `${this._id}-upload-container`;
|
|
@@ -297,19 +338,20 @@ export class DataFrameComponent extends BaseComponent {
|
|
|
297
338
|
else {
|
|
298
339
|
container.appendChild(wrapper);
|
|
299
340
|
}
|
|
300
|
-
// Status bar
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
341
|
+
// Status bar (conditional)
|
|
342
|
+
if (this._showStatus) {
|
|
343
|
+
const statusBar = document.createElement('div');
|
|
344
|
+
statusBar.className = 'jux-dataframe-status jux-dataframe-status-empty';
|
|
345
|
+
statusBar.id = `${this._id}-status`;
|
|
346
|
+
statusBar.textContent = 'No data loaded.';
|
|
347
|
+
wrapper.appendChild(statusBar);
|
|
348
|
+
}
|
|
349
|
+
// Table — filterable is false initially; we enable it after data loads
|
|
308
350
|
const tbl = new Table(`${this._id}-table`, {
|
|
309
351
|
striped: this._tableOptions.striped,
|
|
310
352
|
hoverable: this._tableOptions.hoverable,
|
|
311
353
|
sortable: this._tableOptions.sortable,
|
|
312
|
-
filterable:
|
|
354
|
+
filterable: false, // we handle filtering ourselves
|
|
313
355
|
paginated: this._tableOptions.paginated,
|
|
314
356
|
rowsPerPage: this._tableOptions.rowsPerPage
|
|
315
357
|
});
|