juxscript 1.1.166 → 1.1.167
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/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
|
});
|
|
@@ -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
|
|
|
7
8
|
const TRIGGER_EVENTS = [] as const;
|
|
8
9
|
const CALLBACK_EVENTS = ['load', 'error', 'transform'] as const;
|
|
@@ -16,6 +17,8 @@ export interface DataFrameOptions {
|
|
|
16
17
|
filterable?: boolean;
|
|
17
18
|
paginated?: boolean;
|
|
18
19
|
rowsPerPage?: number;
|
|
20
|
+
showStatus?: boolean;
|
|
21
|
+
icon?: string;
|
|
19
22
|
style?: string;
|
|
20
23
|
class?: string;
|
|
21
24
|
}
|
|
@@ -42,7 +45,9 @@ export class DataFrameComponent extends BaseComponent<DataFrameState> {
|
|
|
42
45
|
private _uploadRef: FileUpload | null = null;
|
|
43
46
|
private _storageKey: string | null = null;
|
|
44
47
|
private _pendingSource: (() => Promise<void>) | null = null;
|
|
45
|
-
private _inlineUpload: { label: string; accept: string } | null = null;
|
|
48
|
+
private _inlineUpload: { label: string; accept: string; icon: string } | null = null;
|
|
49
|
+
private _showStatus: boolean = true;
|
|
50
|
+
private _icon: string = '';
|
|
46
51
|
|
|
47
52
|
constructor(id: string, options: DataFrameOptions = {}) {
|
|
48
53
|
super(id, {
|
|
@@ -62,11 +67,14 @@ export class DataFrameComponent extends BaseComponent<DataFrameState> {
|
|
|
62
67
|
options.storeName ?? 'frames'
|
|
63
68
|
);
|
|
64
69
|
|
|
70
|
+
this._showStatus = options.showStatus ?? true;
|
|
71
|
+
this._icon = options.icon ?? '';
|
|
72
|
+
|
|
65
73
|
this._tableOptions = {
|
|
66
74
|
striped: options.striped ?? true,
|
|
67
75
|
hoverable: options.hoverable ?? true,
|
|
68
76
|
sortable: options.sortable ?? true,
|
|
69
|
-
filterable: options.filterable ??
|
|
77
|
+
filterable: options.filterable ?? false, // defer until data loaded
|
|
70
78
|
paginated: options.paginated ?? true,
|
|
71
79
|
rowsPerPage: options.rowsPerPage ?? 25
|
|
72
80
|
};
|
|
@@ -79,91 +87,98 @@ export class DataFrameComponent extends BaseComponent<DataFrameState> {
|
|
|
79
87
|
* DATA SOURCES
|
|
80
88
|
* ═══════════════════════════════════════════════════ */
|
|
81
89
|
|
|
82
|
-
/**
|
|
83
|
-
* Load from IndexedDB by storage key
|
|
84
|
-
*/
|
|
85
90
|
fromStorage(key: string): this {
|
|
86
91
|
this._storageKey = key;
|
|
87
|
-
|
|
92
|
+
const loadFn = async () => {
|
|
88
93
|
this.state.loading = true;
|
|
94
|
+
this._updateStatus('⏳ Loading...', 'loading');
|
|
89
95
|
try {
|
|
90
|
-
|
|
96
|
+
const df = await this._driver.loadByName(key);
|
|
91
97
|
if (!df) {
|
|
92
98
|
this._triggerCallback('error', 'No table found with key: ' + key, null, this);
|
|
93
99
|
this.state.loading = false;
|
|
100
|
+
this._updateStatus('No table found: ' + key, 'empty');
|
|
94
101
|
return;
|
|
95
102
|
}
|
|
96
103
|
this._setDataFrame(df, 'storage: ' + key);
|
|
97
104
|
} catch (err: any) {
|
|
98
105
|
this._triggerCallback('error', err.message, null, this);
|
|
99
106
|
this.state.loading = false;
|
|
107
|
+
this._updateStatus('❌ ' + err.message, 'error');
|
|
100
108
|
}
|
|
101
109
|
};
|
|
110
|
+
if (this._table) { loadFn(); } else { this._pendingSource = loadFn; }
|
|
102
111
|
return this;
|
|
103
112
|
}
|
|
104
113
|
|
|
105
|
-
/**
|
|
106
|
-
* Load from a FileUpload component — auto-wires change event
|
|
107
|
-
*/
|
|
108
114
|
fromUpload(upload: FileUpload): this {
|
|
109
115
|
this._uploadRef = upload;
|
|
110
116
|
this._pendingSource = async () => {
|
|
111
|
-
// Wire upload's change event to parse incoming files
|
|
112
117
|
upload.bind('change', async (files: File[]) => {
|
|
113
118
|
if (!files || files.length === 0) return;
|
|
114
119
|
const file = files[0];
|
|
115
120
|
this.state.loading = true;
|
|
116
|
-
|
|
121
|
+
this._updateStatus('⏳ Parsing ' + file.name + '...', 'loading');
|
|
117
122
|
try {
|
|
118
123
|
const df = await this._driver.streamFile(file);
|
|
119
|
-
// Auto-persist to IndexedDB
|
|
120
124
|
await this._driver.store(file.name, df, { source: file.name });
|
|
121
|
-
this._setDataFrame(df,
|
|
125
|
+
this._setDataFrame(df, file.name);
|
|
122
126
|
} catch (err: any) {
|
|
123
127
|
this._triggerCallback('error', err.message, null, this);
|
|
124
128
|
this.state.loading = false;
|
|
129
|
+
this._updateStatus('❌ ' + err.message, 'error');
|
|
125
130
|
}
|
|
126
131
|
});
|
|
127
132
|
};
|
|
128
133
|
return this;
|
|
129
134
|
}
|
|
130
135
|
|
|
131
|
-
/**
|
|
132
|
-
* Load from raw data — array of objects or Record<string, any[]>
|
|
133
|
-
*/
|
|
134
136
|
fromData(data: Record<string, any>[] | Record<string, any[]>): this {
|
|
135
|
-
|
|
137
|
+
const loadFn = async () => {
|
|
136
138
|
this.state.loading = true;
|
|
139
|
+
this._updateStatus('⏳ Loading data...', 'loading');
|
|
137
140
|
try {
|
|
138
141
|
const df = new DataFrame(data);
|
|
139
142
|
this._setDataFrame(df, 'inline data');
|
|
140
143
|
} catch (err: any) {
|
|
141
144
|
this._triggerCallback('error', err.message, null, this);
|
|
142
145
|
this.state.loading = false;
|
|
146
|
+
this._updateStatus('❌ ' + err.message, 'error');
|
|
143
147
|
}
|
|
144
148
|
};
|
|
149
|
+
if (this._table) { loadFn(); } else { this._pendingSource = loadFn; }
|
|
145
150
|
return this;
|
|
146
151
|
}
|
|
147
152
|
|
|
148
153
|
/**
|
|
149
|
-
* Add an inline file upload control
|
|
150
|
-
*
|
|
151
|
-
*
|
|
152
|
-
* @param label - Button/label text (default: 'Upload File')
|
|
154
|
+
* Add an inline file upload control.
|
|
155
|
+
* @param label - Button label (default: 'Upload File')
|
|
153
156
|
* @param accept - File types (default: '.csv,.tsv,.txt,.xlsx,.xls')
|
|
157
|
+
* @param icon - Upload icon name (default: 'upload'). Pass '' to hide icon.
|
|
154
158
|
*/
|
|
155
|
-
withUpload(
|
|
156
|
-
|
|
159
|
+
withUpload(
|
|
160
|
+
label: string = 'Upload File',
|
|
161
|
+
accept: string = '.csv,.tsv,.txt,.xlsx,.xls',
|
|
162
|
+
icon: string = 'upload'
|
|
163
|
+
): this {
|
|
164
|
+
this._inlineUpload = { label, accept, icon };
|
|
157
165
|
return this;
|
|
158
166
|
}
|
|
159
167
|
|
|
160
168
|
/* ═══════════════════════════════════════════════════
|
|
161
|
-
*
|
|
169
|
+
* UI TOGGLES
|
|
170
|
+
* ═══════════════════════════════════════════════════ */
|
|
171
|
+
|
|
172
|
+
/** Show/hide the status bar */
|
|
173
|
+
showStatus(v: boolean): this { this._showStatus = v; return this; }
|
|
174
|
+
|
|
175
|
+
/** Set a custom icon for the status bar */
|
|
176
|
+
statusIcon(v: string): this { this._icon = v; return this; }
|
|
177
|
+
|
|
178
|
+
/* ═══════════════════════════════════════════════════
|
|
179
|
+
* TRANSFORM API
|
|
162
180
|
* ═══════════════════════════════════════════════════ */
|
|
163
181
|
|
|
164
|
-
/**
|
|
165
|
-
* Apply a transform to the current DataFrame and update the table
|
|
166
|
-
*/
|
|
167
182
|
apply(fn: (df: DataFrame) => DataFrame): this {
|
|
168
183
|
if (!this._df) return this;
|
|
169
184
|
const result = fn(this._df);
|
|
@@ -172,51 +187,30 @@ export class DataFrameComponent extends BaseComponent<DataFrameState> {
|
|
|
172
187
|
return this;
|
|
173
188
|
}
|
|
174
189
|
|
|
175
|
-
/**
|
|
176
|
-
* Filter rows
|
|
177
|
-
*/
|
|
178
190
|
filter(predicate: (row: Record<string, any>, index: number) => boolean): this {
|
|
179
191
|
return this.apply(df => df.filter(predicate));
|
|
180
192
|
}
|
|
181
193
|
|
|
182
|
-
/**
|
|
183
|
-
* Select columns
|
|
184
|
-
*/
|
|
185
194
|
select(...cols: string[]): this {
|
|
186
195
|
return this.apply(df => df.select(...cols));
|
|
187
196
|
}
|
|
188
197
|
|
|
189
|
-
/**
|
|
190
|
-
* Sort by column
|
|
191
|
-
*/
|
|
192
198
|
sort(col: string, descending?: boolean): this {
|
|
193
199
|
return this.apply(df => df.sort(col, descending));
|
|
194
200
|
}
|
|
195
201
|
|
|
196
|
-
/**
|
|
197
|
-
* Show first N rows
|
|
198
|
-
*/
|
|
199
202
|
head(n: number = 5): this {
|
|
200
203
|
return this.apply(df => df.head(n));
|
|
201
204
|
}
|
|
202
205
|
|
|
203
|
-
/**
|
|
204
|
-
* Show last N rows
|
|
205
|
-
*/
|
|
206
206
|
tail(n: number = 5): this {
|
|
207
207
|
return this.apply(df => df.tail(n));
|
|
208
208
|
}
|
|
209
209
|
|
|
210
|
-
/**
|
|
211
|
-
* Add a computed column
|
|
212
|
-
*/
|
|
213
210
|
withColumn(name: string, fn: (row: Record<string, any>, index: number) => any): this {
|
|
214
211
|
return this.apply(df => df.withColumn(name, fn));
|
|
215
212
|
}
|
|
216
213
|
|
|
217
|
-
/**
|
|
218
|
-
* Where clause
|
|
219
|
-
*/
|
|
220
214
|
where(col: string, op: '==' | '!=' | '>' | '<' | '>=' | '<=' | 'contains' | 'startsWith' | 'endsWith', value: any): this {
|
|
221
215
|
return this.apply(df => df.where(col, op, value));
|
|
222
216
|
}
|
|
@@ -225,41 +219,15 @@ export class DataFrameComponent extends BaseComponent<DataFrameState> {
|
|
|
225
219
|
* ACCESSORS
|
|
226
220
|
* ═══════════════════════════════════════════════════ */
|
|
227
221
|
|
|
228
|
-
/** Get the underlying DataFrame */
|
|
229
222
|
get df(): DataFrame | null { return this._df; }
|
|
230
|
-
|
|
231
|
-
/** Get the underlying TabularDriver */
|
|
232
223
|
get driver(): TabularDriver { return this._driver; }
|
|
233
|
-
|
|
234
|
-
/** Get the internal Table component */
|
|
235
224
|
get table(): Table | null { return this._table; }
|
|
225
|
+
describe(): Record<string, any> | null { return this._df?.describe() ?? null; }
|
|
226
|
+
toCSV(delimiter?: string): string { return this._df?.toCSV(delimiter) ?? ''; }
|
|
227
|
+
toRows(): Record<string, any>[] { return this._df?.toRows() ?? []; }
|
|
228
|
+
get shape(): [number, number] { return this._df?.shape ?? [0, 0]; }
|
|
229
|
+
get columns(): string[] { return this._df?.columns ?? []; }
|
|
236
230
|
|
|
237
|
-
/** Get describe() stats */
|
|
238
|
-
describe(): Record<string, any> | null {
|
|
239
|
-
return this._df?.describe() ?? null;
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
/** Export to CSV string */
|
|
243
|
-
toCSV(delimiter?: string): string {
|
|
244
|
-
return this._df?.toCSV(delimiter) ?? '';
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
/** Export to row objects */
|
|
248
|
-
toRows(): Record<string, any>[] {
|
|
249
|
-
return this._df?.toRows() ?? [];
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
/** Get shape */
|
|
253
|
-
get shape(): [number, number] {
|
|
254
|
-
return this._df?.shape ?? [0, 0];
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
/** Get column names */
|
|
258
|
-
get columns(): string[] {
|
|
259
|
-
return this._df?.columns ?? [];
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
/** Save current DataFrame to IndexedDB */
|
|
263
231
|
async save(key?: string): Promise<string | null> {
|
|
264
232
|
if (!this._df) return null;
|
|
265
233
|
const name = key ?? this._storageKey ?? this._id;
|
|
@@ -281,9 +249,28 @@ export class DataFrameComponent extends BaseComponent<DataFrameState> {
|
|
|
281
249
|
* INTERNAL
|
|
282
250
|
* ═══════════════════════════════════════════════════ */
|
|
283
251
|
|
|
284
|
-
private _updateStatus(text: string): void {
|
|
252
|
+
private _updateStatus(text: string, type: 'loading' | 'success' | 'error' | 'empty' = 'empty'): void {
|
|
285
253
|
const el = document.getElementById(`${this._id}-status`);
|
|
286
|
-
if (el)
|
|
254
|
+
if (!el) return;
|
|
255
|
+
|
|
256
|
+
el.className = 'jux-dataframe-status';
|
|
257
|
+
if (type) el.classList.add(`jux-dataframe-status-${type}`);
|
|
258
|
+
|
|
259
|
+
// Clear and rebuild
|
|
260
|
+
el.innerHTML = '';
|
|
261
|
+
|
|
262
|
+
if (this._icon && type === 'success') {
|
|
263
|
+
const iconEl = renderIcon(this._icon);
|
|
264
|
+
iconEl.style.width = '16px';
|
|
265
|
+
iconEl.style.height = '16px';
|
|
266
|
+
iconEl.style.marginRight = '6px';
|
|
267
|
+
iconEl.style.verticalAlign = 'middle';
|
|
268
|
+
el.appendChild(iconEl);
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
const span = document.createElement('span');
|
|
272
|
+
span.textContent = text;
|
|
273
|
+
el.appendChild(span);
|
|
287
274
|
}
|
|
288
275
|
|
|
289
276
|
private _setDataFrame(df: DataFrame, sourceName: string): void {
|
|
@@ -300,7 +287,16 @@ export class DataFrameComponent extends BaseComponent<DataFrameState> {
|
|
|
300
287
|
}
|
|
301
288
|
|
|
302
289
|
this._updateTable();
|
|
303
|
-
this._updateStatus(
|
|
290
|
+
this._updateStatus(
|
|
291
|
+
`${sourceName} — ${this._df!.height} rows × ${this._df!.width} cols`,
|
|
292
|
+
'success'
|
|
293
|
+
);
|
|
294
|
+
|
|
295
|
+
// Enable filter now that data exists
|
|
296
|
+
if (this._tableOptions.filterable) {
|
|
297
|
+
this._showFilterInput();
|
|
298
|
+
}
|
|
299
|
+
|
|
304
300
|
this._triggerCallback('load', this._df, null, this);
|
|
305
301
|
}
|
|
306
302
|
|
|
@@ -309,6 +305,55 @@ export class DataFrameComponent extends BaseComponent<DataFrameState> {
|
|
|
309
305
|
this._table.columns(this._df.columns).rows(this._df.toRows());
|
|
310
306
|
}
|
|
311
307
|
|
|
308
|
+
private _showFilterInput(): void {
|
|
309
|
+
const wrapper = document.getElementById(this._id);
|
|
310
|
+
if (!wrapper) return;
|
|
311
|
+
// Only add once
|
|
312
|
+
if (wrapper.querySelector('.jux-dataframe-filter')) return;
|
|
313
|
+
|
|
314
|
+
const filterContainer = document.createElement('div');
|
|
315
|
+
filterContainer.className = 'jux-dataframe-filter';
|
|
316
|
+
|
|
317
|
+
const input = document.createElement('input');
|
|
318
|
+
input.type = 'text';
|
|
319
|
+
input.placeholder = 'Filter rows...';
|
|
320
|
+
input.className = 'jux-input-element jux-dataframe-filter-input';
|
|
321
|
+
|
|
322
|
+
const iconEl = renderIcon('search');
|
|
323
|
+
iconEl.style.width = '16px';
|
|
324
|
+
iconEl.style.height = '16px';
|
|
325
|
+
|
|
326
|
+
const iconWrap = document.createElement('span');
|
|
327
|
+
iconWrap.className = 'jux-dataframe-filter-icon';
|
|
328
|
+
iconWrap.appendChild(iconEl);
|
|
329
|
+
|
|
330
|
+
filterContainer.appendChild(iconWrap);
|
|
331
|
+
filterContainer.appendChild(input);
|
|
332
|
+
|
|
333
|
+
// Insert before the table
|
|
334
|
+
const tableWrapper = wrapper.querySelector('.jux-table-wrapper');
|
|
335
|
+
if (tableWrapper) {
|
|
336
|
+
wrapper.insertBefore(filterContainer, tableWrapper);
|
|
337
|
+
} else {
|
|
338
|
+
wrapper.appendChild(filterContainer);
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
input.addEventListener('input', () => {
|
|
342
|
+
if (!this._df) return;
|
|
343
|
+
const text = input.value.toLowerCase();
|
|
344
|
+
if (!text) {
|
|
345
|
+
this._table?.rows(this._df.toRows());
|
|
346
|
+
return;
|
|
347
|
+
}
|
|
348
|
+
const filtered = this._df.filter((row) => {
|
|
349
|
+
return Object.values(row).some(v =>
|
|
350
|
+
v !== null && v !== undefined && String(v).toLowerCase().includes(text)
|
|
351
|
+
);
|
|
352
|
+
});
|
|
353
|
+
this._table?.rows(filtered.toRows());
|
|
354
|
+
});
|
|
355
|
+
}
|
|
356
|
+
|
|
312
357
|
update(prop: string, value: any): void { }
|
|
313
358
|
|
|
314
359
|
/* ═══════════════════════════════════════════════════
|
|
@@ -325,22 +370,25 @@ export class DataFrameComponent extends BaseComponent<DataFrameState> {
|
|
|
325
370
|
if (className) wrapper.className += ` ${className}`;
|
|
326
371
|
if (style) wrapper.setAttribute('style', style);
|
|
327
372
|
|
|
328
|
-
// Inline upload
|
|
373
|
+
// Inline upload
|
|
329
374
|
if (this._inlineUpload) {
|
|
330
|
-
const
|
|
375
|
+
const uploadOpts: any = {
|
|
331
376
|
label: this._inlineUpload.label,
|
|
332
|
-
accept: this._inlineUpload.accept
|
|
333
|
-
}
|
|
377
|
+
accept: this._inlineUpload.accept,
|
|
378
|
+
};
|
|
379
|
+
if (this._inlineUpload.icon) {
|
|
380
|
+
uploadOpts.icon = this._inlineUpload.icon;
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
const upload = new FileUpload(`${this._id}-upload`, uploadOpts);
|
|
334
384
|
|
|
335
|
-
// Wire it as a fromUpload source
|
|
336
385
|
this._uploadRef = upload;
|
|
337
386
|
this._pendingSource = async () => {
|
|
338
387
|
upload.bind('change', async (files: File[]) => {
|
|
339
388
|
if (!files || files.length === 0) return;
|
|
340
389
|
const file = files[0];
|
|
341
390
|
this.state.loading = true;
|
|
342
|
-
this._updateStatus('⏳ Parsing ' + file.name + '...');
|
|
343
|
-
|
|
391
|
+
this._updateStatus('⏳ Parsing ' + file.name + '...', 'loading');
|
|
344
392
|
try {
|
|
345
393
|
const df = await this._driver.streamFile(file);
|
|
346
394
|
await this._driver.store(file.name, df, { source: file.name });
|
|
@@ -348,12 +396,11 @@ export class DataFrameComponent extends BaseComponent<DataFrameState> {
|
|
|
348
396
|
} catch (err: any) {
|
|
349
397
|
this._triggerCallback('error', err.message, null, this);
|
|
350
398
|
this.state.loading = false;
|
|
351
|
-
this._updateStatus('❌ ' + err.message);
|
|
399
|
+
this._updateStatus('❌ ' + err.message, 'error');
|
|
352
400
|
}
|
|
353
401
|
});
|
|
354
402
|
};
|
|
355
403
|
|
|
356
|
-
// Render upload into wrapper before status/table
|
|
357
404
|
const uploadContainer = document.createElement('div');
|
|
358
405
|
uploadContainer.className = 'jux-dataframe-upload';
|
|
359
406
|
uploadContainer.id = `${this._id}-upload-container`;
|
|
@@ -364,20 +411,21 @@ export class DataFrameComponent extends BaseComponent<DataFrameState> {
|
|
|
364
411
|
container.appendChild(wrapper);
|
|
365
412
|
}
|
|
366
413
|
|
|
367
|
-
// Status bar
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
414
|
+
// Status bar (conditional)
|
|
415
|
+
if (this._showStatus) {
|
|
416
|
+
const statusBar = document.createElement('div');
|
|
417
|
+
statusBar.className = 'jux-dataframe-status jux-dataframe-status-empty';
|
|
418
|
+
statusBar.id = `${this._id}-status`;
|
|
419
|
+
statusBar.textContent = 'No data loaded.';
|
|
420
|
+
wrapper.appendChild(statusBar);
|
|
421
|
+
}
|
|
374
422
|
|
|
375
|
-
//
|
|
423
|
+
// Table — filterable is false initially; we enable it after data loads
|
|
376
424
|
const tbl = new Table(`${this._id}-table`, {
|
|
377
425
|
striped: this._tableOptions.striped,
|
|
378
426
|
hoverable: this._tableOptions.hoverable,
|
|
379
427
|
sortable: this._tableOptions.sortable,
|
|
380
|
-
filterable:
|
|
428
|
+
filterable: false, // we handle filtering ourselves
|
|
381
429
|
paginated: this._tableOptions.paginated,
|
|
382
430
|
rowsPerPage: this._tableOptions.rowsPerPage
|
|
383
431
|
});
|
|
@@ -392,7 +440,6 @@ export class DataFrameComponent extends BaseComponent<DataFrameState> {
|
|
|
392
440
|
}
|
|
393
441
|
|
|
394
442
|
this._wireStandardEvents(wrapper);
|
|
395
|
-
|
|
396
443
|
return this;
|
|
397
444
|
}
|
|
398
445
|
}
|
package/lib/styles/shadcn.css
CHANGED
|
@@ -1103,4 +1103,358 @@ button.jux-button:active,
|
|
|
1103
1103
|
|
|
1104
1104
|
.dark .jux-menu-chevron {
|
|
1105
1105
|
color: #a1a1aa;
|
|
1106
|
+
}
|
|
1107
|
+
|
|
1108
|
+
/* ═══════════════════════════════════════════════════════════════════
|
|
1109
|
+
* DATAFRAME COMPONENT
|
|
1110
|
+
* ═══════════════════════════════════════════════════════════════════ */
|
|
1111
|
+
|
|
1112
|
+
.jux-dataframe {
|
|
1113
|
+
display: flex;
|
|
1114
|
+
flex-direction: column;
|
|
1115
|
+
gap: 0.75rem;
|
|
1116
|
+
width: 100%;
|
|
1117
|
+
}
|
|
1118
|
+
|
|
1119
|
+
.jux-dataframe-upload {
|
|
1120
|
+
margin-bottom: 0.25rem;
|
|
1121
|
+
}
|
|
1122
|
+
|
|
1123
|
+
.jux-dataframe-status {
|
|
1124
|
+
display: flex;
|
|
1125
|
+
align-items: center;
|
|
1126
|
+
padding: 0.5rem 0.75rem;
|
|
1127
|
+
font-size: 0.8125rem;
|
|
1128
|
+
font-weight: 500;
|
|
1129
|
+
border-radius: 0.375rem;
|
|
1130
|
+
border: 1px solid #e4e4e7;
|
|
1131
|
+
background: #fafafa;
|
|
1132
|
+
color: #71717a;
|
|
1133
|
+
transition: all 0.2s ease;
|
|
1134
|
+
}
|
|
1135
|
+
|
|
1136
|
+
.jux-dataframe-status-success {
|
|
1137
|
+
background: #f0fdf4;
|
|
1138
|
+
border-color: #bbf7d0;
|
|
1139
|
+
color: #15803d;
|
|
1140
|
+
}
|
|
1141
|
+
|
|
1142
|
+
.jux-dataframe-status-loading {
|
|
1143
|
+
background: #fefce8;
|
|
1144
|
+
border-color: #fde68a;
|
|
1145
|
+
color: #a16207;
|
|
1146
|
+
}
|
|
1147
|
+
|
|
1148
|
+
.jux-dataframe-status-error {
|
|
1149
|
+
background: #fef2f2;
|
|
1150
|
+
border-color: #fecaca;
|
|
1151
|
+
color: #dc2626;
|
|
1152
|
+
}
|
|
1153
|
+
|
|
1154
|
+
.jux-dataframe-status-empty {
|
|
1155
|
+
background: #fafafa;
|
|
1156
|
+
border-color: #e4e4e7;
|
|
1157
|
+
color: #a1a1aa;
|
|
1158
|
+
}
|
|
1159
|
+
|
|
1160
|
+
/* DataFrame Filter */
|
|
1161
|
+
.jux-dataframe-filter {
|
|
1162
|
+
position: relative;
|
|
1163
|
+
display: flex;
|
|
1164
|
+
align-items: center;
|
|
1165
|
+
width: 100%;
|
|
1166
|
+
}
|
|
1167
|
+
|
|
1168
|
+
.jux-dataframe-filter-icon {
|
|
1169
|
+
position: absolute;
|
|
1170
|
+
left: 0.75rem;
|
|
1171
|
+
top: 50%;
|
|
1172
|
+
transform: translateY(-50%);
|
|
1173
|
+
color: #a1a1aa;
|
|
1174
|
+
pointer-events: none;
|
|
1175
|
+
display: flex;
|
|
1176
|
+
align-items: center;
|
|
1177
|
+
}
|
|
1178
|
+
|
|
1179
|
+
.jux-dataframe-filter-input {
|
|
1180
|
+
padding-left: 2.25rem !important;
|
|
1181
|
+
}
|
|
1182
|
+
|
|
1183
|
+
/* ═══════════════════════════════════════════════════════════════════
|
|
1184
|
+
* FILE UPLOAD (Shadcn styled)
|
|
1185
|
+
* ═══════════════════════════════════════════════════════════════════ */
|
|
1186
|
+
|
|
1187
|
+
.jux-fileupload {
|
|
1188
|
+
gap: 0.5rem;
|
|
1189
|
+
}
|
|
1190
|
+
|
|
1191
|
+
.jux-fileupload-button-container {
|
|
1192
|
+
display: flex;
|
|
1193
|
+
align-items: center;
|
|
1194
|
+
gap: 0.5rem;
|
|
1195
|
+
}
|
|
1196
|
+
|
|
1197
|
+
.jux-fileupload-button {
|
|
1198
|
+
display: inline-flex;
|
|
1199
|
+
align-items: center;
|
|
1200
|
+
justify-content: center;
|
|
1201
|
+
white-space: nowrap;
|
|
1202
|
+
border-radius: 0.375rem;
|
|
1203
|
+
font-size: 0.875rem;
|
|
1204
|
+
font-weight: 500;
|
|
1205
|
+
height: 2.5rem;
|
|
1206
|
+
padding: 0 1rem;
|
|
1207
|
+
cursor: pointer;
|
|
1208
|
+
transition: all 0.15s ease;
|
|
1209
|
+
border: 1px solid #e4e4e7;
|
|
1210
|
+
background: #ffffff;
|
|
1211
|
+
color: #09090b;
|
|
1212
|
+
}
|
|
1213
|
+
|
|
1214
|
+
.jux-fileupload-button:hover {
|
|
1215
|
+
background: #f4f4f5;
|
|
1216
|
+
color: #18181b;
|
|
1217
|
+
}
|
|
1218
|
+
|
|
1219
|
+
.jux-fileupload-button:disabled {
|
|
1220
|
+
pointer-events: none;
|
|
1221
|
+
opacity: 0.5;
|
|
1222
|
+
}
|
|
1223
|
+
|
|
1224
|
+
.jux-fileupload-icon {
|
|
1225
|
+
display: flex;
|
|
1226
|
+
align-items: center;
|
|
1227
|
+
color: #71717a;
|
|
1228
|
+
}
|
|
1229
|
+
|
|
1230
|
+
.jux-fileupload-list {
|
|
1231
|
+
display: flex;
|
|
1232
|
+
flex-direction: column;
|
|
1233
|
+
gap: 0.25rem;
|
|
1234
|
+
}
|
|
1235
|
+
|
|
1236
|
+
.jux-fileupload-item {
|
|
1237
|
+
display: flex;
|
|
1238
|
+
align-items: center;
|
|
1239
|
+
gap: 0.5rem;
|
|
1240
|
+
padding: 0.375rem 0.75rem;
|
|
1241
|
+
background: #f4f4f5;
|
|
1242
|
+
border: 1px solid #e4e4e7;
|
|
1243
|
+
border-radius: 0.375rem;
|
|
1244
|
+
font-size: 0.8125rem;
|
|
1245
|
+
color: #09090b;
|
|
1246
|
+
}
|
|
1247
|
+
|
|
1248
|
+
.jux-fileupload-filename {
|
|
1249
|
+
flex: 1;
|
|
1250
|
+
font-weight: 500;
|
|
1251
|
+
overflow: hidden;
|
|
1252
|
+
text-overflow: ellipsis;
|
|
1253
|
+
white-space: nowrap;
|
|
1254
|
+
}
|
|
1255
|
+
|
|
1256
|
+
.jux-fileupload-filesize {
|
|
1257
|
+
color: #a1a1aa;
|
|
1258
|
+
font-size: 0.75rem;
|
|
1259
|
+
flex-shrink: 0;
|
|
1260
|
+
}
|
|
1261
|
+
|
|
1262
|
+
.jux-fileupload-remove {
|
|
1263
|
+
background: transparent;
|
|
1264
|
+
border: none;
|
|
1265
|
+
color: #a1a1aa;
|
|
1266
|
+
cursor: pointer;
|
|
1267
|
+
font-size: 1rem;
|
|
1268
|
+
padding: 0 0.25rem;
|
|
1269
|
+
line-height: 1;
|
|
1270
|
+
border-radius: 0.25rem;
|
|
1271
|
+
transition: all 0.15s;
|
|
1272
|
+
}
|
|
1273
|
+
|
|
1274
|
+
.jux-fileupload-remove:hover {
|
|
1275
|
+
color: #ef4444;
|
|
1276
|
+
background: #fef2f2;
|
|
1277
|
+
}
|
|
1278
|
+
|
|
1279
|
+
/* ═══════════════════════════════════════════════════════════════════
|
|
1280
|
+
* TABLE ENHANCEMENTS
|
|
1281
|
+
* ═══════════════════════════════════════════════════════════════════ */
|
|
1282
|
+
|
|
1283
|
+
.jux-table-wrapper {
|
|
1284
|
+
width: 100%;
|
|
1285
|
+
overflow-x: auto;
|
|
1286
|
+
}
|
|
1287
|
+
|
|
1288
|
+
.jux-table-filter {
|
|
1289
|
+
display: flex;
|
|
1290
|
+
height: 2.5rem;
|
|
1291
|
+
width: 100%;
|
|
1292
|
+
border-radius: 0.375rem;
|
|
1293
|
+
border: 1px solid #e4e4e7;
|
|
1294
|
+
background: #ffffff;
|
|
1295
|
+
padding: 0.5rem 0.75rem;
|
|
1296
|
+
font-size: 0.875rem;
|
|
1297
|
+
color: #09090b;
|
|
1298
|
+
outline: none;
|
|
1299
|
+
transition: all 0.15s ease;
|
|
1300
|
+
box-sizing: border-box;
|
|
1301
|
+
margin-bottom: 0.75rem;
|
|
1302
|
+
}
|
|
1303
|
+
|
|
1304
|
+
.jux-table-filter::placeholder {
|
|
1305
|
+
color: #a1a1aa;
|
|
1306
|
+
}
|
|
1307
|
+
|
|
1308
|
+
.jux-table-filter:focus {
|
|
1309
|
+
border-color: #18181b;
|
|
1310
|
+
box-shadow: 0 0 0 2px #18181b, 0 0 0 4px rgba(24, 24, 27, 0.1);
|
|
1311
|
+
}
|
|
1312
|
+
|
|
1313
|
+
.jux-table-pagination {
|
|
1314
|
+
margin-top: 0.75rem;
|
|
1315
|
+
display: flex;
|
|
1316
|
+
gap: 0.375rem;
|
|
1317
|
+
justify-content: center;
|
|
1318
|
+
align-items: center;
|
|
1319
|
+
padding: 0.5rem 0;
|
|
1320
|
+
}
|
|
1321
|
+
|
|
1322
|
+
.jux-table-pagination button {
|
|
1323
|
+
display: inline-flex;
|
|
1324
|
+
align-items: center;
|
|
1325
|
+
justify-content: center;
|
|
1326
|
+
white-space: nowrap;
|
|
1327
|
+
border-radius: 0.375rem;
|
|
1328
|
+
font-size: 0.875rem;
|
|
1329
|
+
font-weight: 500;
|
|
1330
|
+
height: 2.25rem;
|
|
1331
|
+
padding: 0 0.875rem;
|
|
1332
|
+
cursor: pointer;
|
|
1333
|
+
transition: all 0.15s ease;
|
|
1334
|
+
border: 1px solid #e4e4e7;
|
|
1335
|
+
background: #ffffff;
|
|
1336
|
+
color: #09090b;
|
|
1337
|
+
}
|
|
1338
|
+
|
|
1339
|
+
.jux-table-pagination button:hover:not(:disabled) {
|
|
1340
|
+
background: #f4f4f5;
|
|
1341
|
+
color: #18181b;
|
|
1342
|
+
}
|
|
1343
|
+
|
|
1344
|
+
.jux-table-pagination button:disabled {
|
|
1345
|
+
pointer-events: none;
|
|
1346
|
+
opacity: 0.5;
|
|
1347
|
+
}
|
|
1348
|
+
|
|
1349
|
+
.jux-table-pagination span {
|
|
1350
|
+
font-size: 0.875rem;
|
|
1351
|
+
color: #71717a;
|
|
1352
|
+
padding: 0 0.75rem;
|
|
1353
|
+
}
|
|
1354
|
+
|
|
1355
|
+
/* Table hoverable rows */
|
|
1356
|
+
.jux-table-hoverable tbody tr {
|
|
1357
|
+
transition: background-color 0.1s ease;
|
|
1358
|
+
}
|
|
1359
|
+
|
|
1360
|
+
.jux-table-hoverable tbody tr:hover {
|
|
1361
|
+
background-color: #f4f4f5 !important;
|
|
1362
|
+
}
|
|
1363
|
+
|
|
1364
|
+
/* Selection highlight override for shadcn */
|
|
1365
|
+
.jux-table-row-selected {
|
|
1366
|
+
background-color: #eff6ff !important;
|
|
1367
|
+
}
|
|
1368
|
+
|
|
1369
|
+
.jux-table-row-selected:hover {
|
|
1370
|
+
background-color: #dbeafe !important;
|
|
1371
|
+
}
|
|
1372
|
+
|
|
1373
|
+
/* ═══════════════════════════════════════════════════════════════════
|
|
1374
|
+
* DARK MODE - DATAFRAME / FILEUPLOAD / TABLE
|
|
1375
|
+
* ═══════════════════════════════════════════════════════════════════ */
|
|
1376
|
+
|
|
1377
|
+
.dark .jux-dataframe-status {
|
|
1378
|
+
background: #18181b;
|
|
1379
|
+
border-color: #27272a;
|
|
1380
|
+
color: #a1a1aa;
|
|
1381
|
+
}
|
|
1382
|
+
|
|
1383
|
+
.dark .jux-dataframe-status-success {
|
|
1384
|
+
background: #052e16;
|
|
1385
|
+
border-color: #166534;
|
|
1386
|
+
color: #4ade80;
|
|
1387
|
+
}
|
|
1388
|
+
|
|
1389
|
+
.dark .jux-dataframe-status-loading {
|
|
1390
|
+
background: #422006;
|
|
1391
|
+
border-color: #a16207;
|
|
1392
|
+
color: #facc15;
|
|
1393
|
+
}
|
|
1394
|
+
|
|
1395
|
+
.dark .jux-dataframe-status-error {
|
|
1396
|
+
background: #450a0a;
|
|
1397
|
+
border-color: #991b1b;
|
|
1398
|
+
color: #f87171;
|
|
1399
|
+
}
|
|
1400
|
+
|
|
1401
|
+
.dark .jux-fileupload-button {
|
|
1402
|
+
border-color: #27272a;
|
|
1403
|
+
background: #09090b;
|
|
1404
|
+
color: #fafafa;
|
|
1405
|
+
}
|
|
1406
|
+
|
|
1407
|
+
.dark .jux-fileupload-button:hover {
|
|
1408
|
+
background: #27272a;
|
|
1409
|
+
}
|
|
1410
|
+
|
|
1411
|
+
.dark .jux-fileupload-item {
|
|
1412
|
+
background: #18181b;
|
|
1413
|
+
border-color: #27272a;
|
|
1414
|
+
color: #fafafa;
|
|
1415
|
+
}
|
|
1416
|
+
|
|
1417
|
+
.dark .jux-fileupload-filesize {
|
|
1418
|
+
color: #71717a;
|
|
1419
|
+
}
|
|
1420
|
+
|
|
1421
|
+
.dark .jux-table-filter {
|
|
1422
|
+
border-color: #27272a;
|
|
1423
|
+
background: #09090b;
|
|
1424
|
+
color: #fafafa;
|
|
1425
|
+
}
|
|
1426
|
+
|
|
1427
|
+
.dark .jux-table-filter::placeholder {
|
|
1428
|
+
color: #52525b;
|
|
1429
|
+
}
|
|
1430
|
+
|
|
1431
|
+
.dark .jux-table-filter:focus {
|
|
1432
|
+
border-color: #fafafa;
|
|
1433
|
+
box-shadow: 0 0 0 2px #fafafa, 0 0 0 4px rgba(250, 250, 250, 0.1);
|
|
1434
|
+
}
|
|
1435
|
+
|
|
1436
|
+
.dark .jux-table-pagination button {
|
|
1437
|
+
border-color: #27272a;
|
|
1438
|
+
background: #09090b;
|
|
1439
|
+
color: #fafafa;
|
|
1440
|
+
}
|
|
1441
|
+
|
|
1442
|
+
.dark .jux-table-pagination button:hover:not(:disabled) {
|
|
1443
|
+
background: #27272a;
|
|
1444
|
+
}
|
|
1445
|
+
|
|
1446
|
+
.dark .jux-table-pagination span {
|
|
1447
|
+
color: #71717a;
|
|
1448
|
+
}
|
|
1449
|
+
|
|
1450
|
+
.dark .jux-table-hoverable tbody tr:hover {
|
|
1451
|
+
background-color: #27272a !important;
|
|
1452
|
+
}
|
|
1453
|
+
|
|
1454
|
+
.dark .jux-table-row-selected {
|
|
1455
|
+
background-color: #172554 !important;
|
|
1456
|
+
}
|
|
1457
|
+
|
|
1458
|
+
.dark .jux-table-row-selected:hover {
|
|
1459
|
+
background-color: #1e3a5f !important;
|
|
1106
1460
|
}
|