@gradio/dataframe 0.15.0 → 0.16.1
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/CHANGELOG.md +36 -0
- package/Dataframe.stories.svelte +183 -2
- package/Example.svelte +7 -0
- package/Index.svelte +20 -3
- package/dist/Example.svelte +7 -0
- package/dist/Index.svelte +16 -4
- package/dist/Index.svelte.d.ts +12 -0
- package/dist/shared/CellMenu.svelte +1 -1
- package/dist/shared/EditableCell.svelte +1 -6
- package/dist/shared/Table.svelte +620 -319
- package/dist/shared/Table.svelte.d.ts +3 -0
- package/dist/shared/Toolbar.svelte +122 -30
- package/dist/shared/Toolbar.svelte.d.ts +4 -0
- package/dist/shared/VirtualTable.svelte +70 -26
- package/dist/shared/VirtualTable.svelte.d.ts +1 -0
- package/dist/shared/icons/FilterIcon.svelte +11 -0
- package/dist/shared/icons/FilterIcon.svelte.d.ts +16 -0
- package/dist/shared/icons/SortIcon.svelte +90 -0
- package/dist/shared/icons/SortIcon.svelte.d.ts +20 -0
- package/dist/shared/selection_utils.d.ts +12 -2
- package/dist/shared/selection_utils.js +33 -5
- package/dist/shared/types.d.ts +16 -0
- package/dist/shared/types.js +1 -0
- package/dist/shared/utils/menu_utils.d.ts +42 -0
- package/dist/shared/utils/menu_utils.js +58 -0
- package/dist/shared/utils/sort_utils.d.ts +7 -0
- package/dist/shared/utils/sort_utils.js +39 -0
- package/dist/shared/utils/table_utils.d.ts +12 -0
- package/dist/shared/utils/table_utils.js +148 -0
- package/package.json +8 -8
- package/shared/CellMenu.svelte +1 -1
- package/shared/EditableCell.svelte +1 -6
- package/shared/Table.svelte +649 -322
- package/shared/Toolbar.svelte +125 -30
- package/shared/VirtualTable.svelte +73 -26
- package/shared/icons/FilterIcon.svelte +12 -0
- package/shared/icons/SortIcon.svelte +95 -0
- package/shared/selection_utils.ts +51 -9
- package/shared/types.ts +27 -0
- package/shared/utils/menu_utils.ts +115 -0
- package/shared/utils/sort_utils.test.ts +71 -0
- package/shared/utils/sort_utils.ts +55 -0
- package/shared/utils/table_utils.test.ts +114 -0
- package/shared/utils/table_utils.ts +206 -0
- package/dist/shared/table_utils.d.ts +0 -12
- package/dist/shared/table_utils.js +0 -113
- package/shared/table_utils.ts +0 -148
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { describe, test, expect } from "vitest";
|
|
2
|
+
import { get_sort_status, sort_data } from "./sort_utils";
|
|
3
|
+
|
|
4
|
+
describe("sort_utils", () => {
|
|
5
|
+
describe("get_sort_status", () => {
|
|
6
|
+
const headers = ["A", "B", "C"];
|
|
7
|
+
|
|
8
|
+
test("returns none when no sort is active", () => {
|
|
9
|
+
expect(get_sort_status("A", undefined, undefined, headers)).toBe("none");
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
test("returns ascending when column is sorted ascending", () => {
|
|
13
|
+
expect(get_sort_status("A", 0, "asc", headers)).toBe("ascending");
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
test("returns descending when column is sorted descending", () => {
|
|
17
|
+
expect(get_sort_status("B", 1, "des", headers)).toBe("descending");
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
test("returns none for non-matching column", () => {
|
|
21
|
+
expect(get_sort_status("A", 1, "asc", headers)).toBe("none");
|
|
22
|
+
});
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
describe("sort_data", () => {
|
|
26
|
+
const data = [
|
|
27
|
+
[
|
|
28
|
+
{ id: "1", value: "B" },
|
|
29
|
+
{ id: "2", value: 2 }
|
|
30
|
+
],
|
|
31
|
+
[
|
|
32
|
+
{ id: "3", value: "A" },
|
|
33
|
+
{ id: "4", value: 1 }
|
|
34
|
+
],
|
|
35
|
+
[
|
|
36
|
+
{ id: "5", value: "C" },
|
|
37
|
+
{ id: "6", value: 3 }
|
|
38
|
+
]
|
|
39
|
+
];
|
|
40
|
+
|
|
41
|
+
test("sorts strings ascending", () => {
|
|
42
|
+
const indices = sort_data(data, 0, "asc");
|
|
43
|
+
expect(indices).toEqual([1, 0, 2]); // A, B, C
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
test("sorts numbers ascending", () => {
|
|
47
|
+
const indices = sort_data(data, 1, "asc");
|
|
48
|
+
expect(indices).toEqual([1, 0, 2]); // 1, 2, 3
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
test("sorts strings descending", () => {
|
|
52
|
+
const indices = sort_data(data, 0, "des");
|
|
53
|
+
expect(indices).toEqual([2, 0, 1]); // C, B, A
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
test("returns original order when sort params are invalid", () => {
|
|
57
|
+
const indices = sort_data(data, undefined, undefined);
|
|
58
|
+
expect(indices).toEqual([0, 1, 2]);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
test("handles empty data", () => {
|
|
62
|
+
const indices = sort_data([], 0, "asc");
|
|
63
|
+
expect(indices).toEqual([]);
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
test("handles invalid column index", () => {
|
|
67
|
+
const indices = sort_data(data, 999, "asc");
|
|
68
|
+
expect(indices).toEqual([0, 1, 2]);
|
|
69
|
+
});
|
|
70
|
+
});
|
|
71
|
+
});
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import type { Headers } from "../types";
|
|
2
|
+
|
|
3
|
+
export type SortDirection = "asc" | "des";
|
|
4
|
+
|
|
5
|
+
export function get_sort_status(
|
|
6
|
+
name: string,
|
|
7
|
+
sort_by: number | undefined,
|
|
8
|
+
direction: SortDirection | undefined,
|
|
9
|
+
headers: Headers
|
|
10
|
+
): "none" | "ascending" | "descending" {
|
|
11
|
+
if (typeof sort_by !== "number") return "none";
|
|
12
|
+
if (sort_by < 0 || sort_by >= headers.length) return "none";
|
|
13
|
+
if (headers[sort_by] === name) {
|
|
14
|
+
if (direction === "asc") return "ascending";
|
|
15
|
+
if (direction === "des") return "descending";
|
|
16
|
+
}
|
|
17
|
+
return "none";
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function sort_data(
|
|
21
|
+
data: { id: string; value: string | number }[][],
|
|
22
|
+
sort_by: number | undefined,
|
|
23
|
+
sort_direction: SortDirection | undefined
|
|
24
|
+
): number[] {
|
|
25
|
+
if (!data || !data.length || !data[0]) {
|
|
26
|
+
return [];
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
if (
|
|
30
|
+
typeof sort_by === "number" &&
|
|
31
|
+
sort_direction &&
|
|
32
|
+
sort_by >= 0 &&
|
|
33
|
+
sort_by < data[0].length
|
|
34
|
+
) {
|
|
35
|
+
const row_indices = [...Array(data.length)].map((_, i) => i);
|
|
36
|
+
row_indices.sort((row_a_idx, row_b_idx) => {
|
|
37
|
+
const row_a = data[row_a_idx];
|
|
38
|
+
const row_b = data[row_b_idx];
|
|
39
|
+
if (
|
|
40
|
+
!row_a ||
|
|
41
|
+
!row_b ||
|
|
42
|
+
sort_by >= row_a.length ||
|
|
43
|
+
sort_by >= row_b.length
|
|
44
|
+
)
|
|
45
|
+
return 0;
|
|
46
|
+
|
|
47
|
+
const val_a = row_a[sort_by].value;
|
|
48
|
+
const val_b = row_b[sort_by].value;
|
|
49
|
+
const comparison = val_a < val_b ? -1 : val_a > val_b ? 1 : 0;
|
|
50
|
+
return sort_direction === "asc" ? comparison : -comparison;
|
|
51
|
+
});
|
|
52
|
+
return row_indices;
|
|
53
|
+
}
|
|
54
|
+
return [...Array(data.length)].map((_, i) => i);
|
|
55
|
+
}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { describe, test, expect } from "vitest";
|
|
2
|
+
import {
|
|
3
|
+
make_cell_id,
|
|
4
|
+
make_header_id,
|
|
5
|
+
process_data,
|
|
6
|
+
make_headers
|
|
7
|
+
} from "./table_utils";
|
|
8
|
+
|
|
9
|
+
describe("table_utils", () => {
|
|
10
|
+
describe("id generation", () => {
|
|
11
|
+
test("generates unique cell ids", () => {
|
|
12
|
+
const id_set = new Set();
|
|
13
|
+
for (let row = 0; row < 10; row++) {
|
|
14
|
+
for (let col = 0; col < 10; col++) {
|
|
15
|
+
id_set.add(make_cell_id(row, col));
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
expect(id_set.size).toBe(100);
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
test("generates unique header ids", () => {
|
|
22
|
+
const id_set = new Set();
|
|
23
|
+
for (let col = 0; col < 10; col++) {
|
|
24
|
+
id_set.add(make_header_id(col));
|
|
25
|
+
}
|
|
26
|
+
expect(id_set.size).toBe(10);
|
|
27
|
+
});
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
describe("process_data", () => {
|
|
31
|
+
const element_refs: Record<string, { cell: null; input: null }> = {};
|
|
32
|
+
const data_binding: Record<string, { value: string | number; id: string }> =
|
|
33
|
+
{};
|
|
34
|
+
|
|
35
|
+
test("processes data with row numbers", () => {
|
|
36
|
+
const test_result = process_data(
|
|
37
|
+
[
|
|
38
|
+
["1", "2"],
|
|
39
|
+
["3", "4"]
|
|
40
|
+
],
|
|
41
|
+
[2, "fixed"],
|
|
42
|
+
[2, "fixed"],
|
|
43
|
+
["A", "B"],
|
|
44
|
+
true,
|
|
45
|
+
element_refs,
|
|
46
|
+
data_binding
|
|
47
|
+
);
|
|
48
|
+
|
|
49
|
+
expect(test_result[0].map((item) => item.value)).toEqual(["1", "2"]);
|
|
50
|
+
expect(test_result).toHaveLength(2);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
test("processes data without row numbers", () => {
|
|
54
|
+
const test_result = process_data(
|
|
55
|
+
[
|
|
56
|
+
["1", "2"],
|
|
57
|
+
["3", "4"]
|
|
58
|
+
],
|
|
59
|
+
[2, "fixed"],
|
|
60
|
+
[2, "fixed"],
|
|
61
|
+
["A", "B"],
|
|
62
|
+
false,
|
|
63
|
+
element_refs,
|
|
64
|
+
data_binding
|
|
65
|
+
);
|
|
66
|
+
|
|
67
|
+
expect(test_result.length).toBe(2);
|
|
68
|
+
expect(test_result[0].length).toBe(2);
|
|
69
|
+
expect(test_result[0][0].value).toBe("1");
|
|
70
|
+
expect(test_result[0][1].value).toBe("2");
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
test("handles empty data", () => {
|
|
74
|
+
const test_result = process_data(
|
|
75
|
+
[],
|
|
76
|
+
[0, "dynamic"],
|
|
77
|
+
[0, "dynamic"],
|
|
78
|
+
[],
|
|
79
|
+
false,
|
|
80
|
+
element_refs,
|
|
81
|
+
data_binding
|
|
82
|
+
);
|
|
83
|
+
|
|
84
|
+
expect(test_result.length).toBe(0);
|
|
85
|
+
});
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
describe("make_headers", () => {
|
|
89
|
+
const element_refs: Record<string, { cell: null; input: null }> = {};
|
|
90
|
+
|
|
91
|
+
test("adds empty column for row numbers", () => {
|
|
92
|
+
const test_result = make_headers(
|
|
93
|
+
["A", "B"],
|
|
94
|
+
true,
|
|
95
|
+
[2, "fixed"],
|
|
96
|
+
element_refs
|
|
97
|
+
);
|
|
98
|
+
|
|
99
|
+
expect(test_result.length).toBe(3);
|
|
100
|
+
expect(test_result[0].value).toBe("");
|
|
101
|
+
expect(test_result[1].value).toBe("A");
|
|
102
|
+
expect(test_result[2].value).toBe("B");
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
test("handles empty headers with fixed columns", () => {
|
|
106
|
+
const test_result = make_headers([], false, [3, "fixed"], element_refs);
|
|
107
|
+
|
|
108
|
+
expect(test_result.length).toBe(3);
|
|
109
|
+
expect(
|
|
110
|
+
test_result.every((header) => typeof header.value === "string")
|
|
111
|
+
).toBe(true);
|
|
112
|
+
});
|
|
113
|
+
});
|
|
114
|
+
});
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
Headers,
|
|
3
|
+
HeadersWithIDs,
|
|
4
|
+
TableCell,
|
|
5
|
+
TableData,
|
|
6
|
+
CountConfig,
|
|
7
|
+
ElementRefs,
|
|
8
|
+
DataBinding
|
|
9
|
+
} from "../types";
|
|
10
|
+
import { sort_data } from "./sort_utils";
|
|
11
|
+
import type { SortDirection } from "./sort_utils";
|
|
12
|
+
import { dsvFormat } from "d3-dsv";
|
|
13
|
+
|
|
14
|
+
export function make_cell_id(row: number, col: number): string {
|
|
15
|
+
return `cell-${row}-${col}`;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function make_header_id(col: number): string {
|
|
19
|
+
return `header-${col}`;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function process_data(
|
|
23
|
+
input_values: (string | number)[][],
|
|
24
|
+
row_count: CountConfig,
|
|
25
|
+
col_count: CountConfig,
|
|
26
|
+
headers: Headers,
|
|
27
|
+
show_row_numbers: boolean,
|
|
28
|
+
element_refs: ElementRefs,
|
|
29
|
+
data_binding: DataBinding
|
|
30
|
+
): TableData {
|
|
31
|
+
const data_row_length = input_values.length;
|
|
32
|
+
return Array(row_count[1] === "fixed" ? row_count[0] : data_row_length)
|
|
33
|
+
.fill(0)
|
|
34
|
+
.map((_, row) => {
|
|
35
|
+
return Array(
|
|
36
|
+
col_count[1] === "fixed"
|
|
37
|
+
? col_count[0]
|
|
38
|
+
: data_row_length > 0
|
|
39
|
+
? input_values[0].length
|
|
40
|
+
: headers.length
|
|
41
|
+
)
|
|
42
|
+
.fill(0)
|
|
43
|
+
.map((_, col) => {
|
|
44
|
+
const cell_id = make_cell_id(row, col);
|
|
45
|
+
element_refs[cell_id] = element_refs[cell_id] || {
|
|
46
|
+
input: null,
|
|
47
|
+
cell: null
|
|
48
|
+
};
|
|
49
|
+
const cell_obj: TableCell = {
|
|
50
|
+
value: input_values?.[row]?.[col] ?? "",
|
|
51
|
+
id: cell_id
|
|
52
|
+
};
|
|
53
|
+
data_binding[cell_id] = cell_obj;
|
|
54
|
+
return cell_obj;
|
|
55
|
+
});
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function make_headers(
|
|
60
|
+
input_headers: Headers,
|
|
61
|
+
show_row_numbers: boolean,
|
|
62
|
+
col_count: CountConfig,
|
|
63
|
+
element_refs: ElementRefs
|
|
64
|
+
): HeadersWithIDs[] {
|
|
65
|
+
let header_list = input_headers || [];
|
|
66
|
+
if (show_row_numbers) {
|
|
67
|
+
header_list = ["", ...header_list];
|
|
68
|
+
}
|
|
69
|
+
if (col_count[1] === "fixed" && header_list.length < col_count[0]) {
|
|
70
|
+
const fill_headers = Array(col_count[0] - header_list.length)
|
|
71
|
+
.fill("")
|
|
72
|
+
.map((_, i) => `${i + header_list.length}`);
|
|
73
|
+
header_list = header_list.concat(fill_headers);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
if (!header_list || header_list.length === 0) {
|
|
77
|
+
return Array(col_count[0])
|
|
78
|
+
.fill(0)
|
|
79
|
+
.map((_, col) => {
|
|
80
|
+
const header_id = make_header_id(col);
|
|
81
|
+
element_refs[header_id] = { cell: null, input: null };
|
|
82
|
+
return { id: header_id, value: JSON.stringify(col + 1) };
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return header_list.map((header: string | null, col: number) => {
|
|
87
|
+
const header_id = make_header_id(col);
|
|
88
|
+
element_refs[header_id] = { cell: null, input: null };
|
|
89
|
+
return { id: header_id, value: header ?? "" };
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export function get_max(data: TableData): TableCell[] {
|
|
94
|
+
if (!data || !data.length) return [];
|
|
95
|
+
let max = data[0].slice();
|
|
96
|
+
for (let i = 0; i < data.length; i++) {
|
|
97
|
+
for (let j = 0; j < data[i].length; j++) {
|
|
98
|
+
if (`${max[j].value}`.length < `${data[i][j].value}`.length) {
|
|
99
|
+
max[j] = data[i][j];
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
return max;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export function sort_table_data(
|
|
107
|
+
data: TableData,
|
|
108
|
+
display_value: string[][] | null,
|
|
109
|
+
styling: string[][] | null,
|
|
110
|
+
col: number,
|
|
111
|
+
dir: SortDirection
|
|
112
|
+
): void {
|
|
113
|
+
const indices = sort_data(data, col, dir);
|
|
114
|
+
|
|
115
|
+
const new_data = indices.map((i: number) => data[i]);
|
|
116
|
+
data.splice(0, data.length, ...new_data);
|
|
117
|
+
|
|
118
|
+
if (display_value) {
|
|
119
|
+
const new_display = indices.map((i: number) => display_value[i]);
|
|
120
|
+
display_value.splice(0, display_value.length, ...new_display);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
if (styling) {
|
|
124
|
+
const new_styling = indices.map((i: number) => styling[i]);
|
|
125
|
+
styling.splice(0, styling.length, ...new_styling);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export async function copy_table_data(
|
|
130
|
+
data: TableData,
|
|
131
|
+
selected_cells: [number, number][]
|
|
132
|
+
): Promise<void> {
|
|
133
|
+
const csv = selected_cells.reduce(
|
|
134
|
+
(acc: { [key: string]: { [key: string]: string } }, [row, col]) => {
|
|
135
|
+
acc[row] = acc[row] || {};
|
|
136
|
+
const value = String(data[row][col].value);
|
|
137
|
+
acc[row][col] =
|
|
138
|
+
value.includes(",") || value.includes('"') || value.includes("\n")
|
|
139
|
+
? `"${value.replace(/"/g, '""')}"`
|
|
140
|
+
: value;
|
|
141
|
+
return acc;
|
|
142
|
+
},
|
|
143
|
+
{}
|
|
144
|
+
);
|
|
145
|
+
|
|
146
|
+
const rows = Object.keys(csv).sort((a, b) => +a - +b);
|
|
147
|
+
const cols = Object.keys(csv[rows[0]]).sort((a, b) => +a - +b);
|
|
148
|
+
const text = rows
|
|
149
|
+
.map((r) => cols.map((c) => csv[r][c] || "").join(","))
|
|
150
|
+
.join("\n");
|
|
151
|
+
|
|
152
|
+
try {
|
|
153
|
+
await navigator.clipboard.writeText(text);
|
|
154
|
+
} catch (err) {
|
|
155
|
+
console.error("Copy failed:", err);
|
|
156
|
+
throw new Error("Failed to copy to clipboard: " + (err as Error).message);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// File Import/Export
|
|
161
|
+
export function guess_delimiter(
|
|
162
|
+
text: string,
|
|
163
|
+
possibleDelimiters: string[]
|
|
164
|
+
): string[] {
|
|
165
|
+
return possibleDelimiters.filter(weedOut);
|
|
166
|
+
|
|
167
|
+
function weedOut(delimiter: string): boolean {
|
|
168
|
+
var cache = -1;
|
|
169
|
+
return text.split("\n").every(checkLength);
|
|
170
|
+
|
|
171
|
+
function checkLength(line: string): boolean {
|
|
172
|
+
if (!line) return true;
|
|
173
|
+
var length = line.split(delimiter).length;
|
|
174
|
+
if (cache < 0) cache = length;
|
|
175
|
+
return cache === length && length > 1;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
export function data_uri_to_blob(data_uri: string): Blob {
|
|
181
|
+
const byte_str = atob(data_uri.split(",")[1]);
|
|
182
|
+
const mime_str = data_uri.split(",")[0].split(":")[1].split(";")[0];
|
|
183
|
+
const ab = new ArrayBuffer(byte_str.length);
|
|
184
|
+
const ia = new Uint8Array(ab);
|
|
185
|
+
for (let i = 0; i < byte_str.length; i++) {
|
|
186
|
+
ia[i] = byte_str.charCodeAt(i);
|
|
187
|
+
}
|
|
188
|
+
return new Blob([ab], { type: mime_str });
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
export function handle_file_upload(
|
|
192
|
+
data_uri: string,
|
|
193
|
+
update_headers: (headers: Headers) => HeadersWithIDs[],
|
|
194
|
+
update_values: (values: (string | number)[][]) => void
|
|
195
|
+
): void {
|
|
196
|
+
const blob = data_uri_to_blob(data_uri);
|
|
197
|
+
const reader = new FileReader();
|
|
198
|
+
reader.addEventListener("loadend", (e) => {
|
|
199
|
+
if (!e?.target?.result || typeof e.target.result !== "string") return;
|
|
200
|
+
const [delimiter] = guess_delimiter(e.target.result, [",", "\t"]);
|
|
201
|
+
const [head, ...rest] = dsvFormat(delimiter).parseRows(e.target.result);
|
|
202
|
+
update_headers(head);
|
|
203
|
+
update_values(rest);
|
|
204
|
+
});
|
|
205
|
+
reader.readAsText(blob);
|
|
206
|
+
}
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
import type { HeadersWithIDs } from "./utils";
|
|
2
|
-
import type { CellCoordinate } from "./selection_utils";
|
|
3
|
-
export type TableData = {
|
|
4
|
-
value: string | number;
|
|
5
|
-
id: string;
|
|
6
|
-
}[][];
|
|
7
|
-
export declare function get_max(_d: TableData): TableData[0];
|
|
8
|
-
export declare function guess_delimiter(text: string, possibleDelimiters: string[]): string[];
|
|
9
|
-
export declare function data_uri_to_blob(data_uri: string): Blob;
|
|
10
|
-
export declare function copy_table_data(data: TableData, headers?: HeadersWithIDs, selected_cells?: CellCoordinate[]): Promise<void>;
|
|
11
|
-
export declare function blob_to_string(blob: Blob, col_count: [number, "fixed" | "dynamic"], make_headers: (head: string[]) => HeadersWithIDs, set_values: (values: (string | number)[][]) => void): void;
|
|
12
|
-
export declare function handle_file_upload(data_uri: string, col_count: [number, "fixed" | "dynamic"], make_headers: (head: string[]) => HeadersWithIDs, set_values: (values: (string | number)[][]) => void): void;
|
|
@@ -1,113 +0,0 @@
|
|
|
1
|
-
import { dsvFormat } from "d3-dsv";
|
|
2
|
-
export function get_max(_d) {
|
|
3
|
-
if (!_d || _d.length === 0 || !_d[0])
|
|
4
|
-
return [];
|
|
5
|
-
let max = _d[0].slice();
|
|
6
|
-
for (let i = 0; i < _d.length; i++) {
|
|
7
|
-
for (let j = 0; j < _d[i].length; j++) {
|
|
8
|
-
if (`${max[j].value}`.length < `${_d[i][j].value}`.length) {
|
|
9
|
-
max[j] = _d[i][j];
|
|
10
|
-
}
|
|
11
|
-
}
|
|
12
|
-
}
|
|
13
|
-
return max;
|
|
14
|
-
}
|
|
15
|
-
export function guess_delimiter(text, possibleDelimiters) {
|
|
16
|
-
return possibleDelimiters.filter(weedOut);
|
|
17
|
-
function weedOut(delimiter) {
|
|
18
|
-
var cache = -1;
|
|
19
|
-
return text.split("\n").every(checkLength);
|
|
20
|
-
function checkLength(line) {
|
|
21
|
-
if (!line)
|
|
22
|
-
return true;
|
|
23
|
-
var length = line.split(delimiter).length;
|
|
24
|
-
if (cache < 0)
|
|
25
|
-
cache = length;
|
|
26
|
-
return cache === length && length > 1;
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
export function data_uri_to_blob(data_uri) {
|
|
31
|
-
const byte_str = atob(data_uri.split(",")[1]);
|
|
32
|
-
const mime_str = data_uri.split(",")[0].split(":")[1].split(";")[0];
|
|
33
|
-
const ab = new ArrayBuffer(byte_str.length);
|
|
34
|
-
const ia = new Uint8Array(ab);
|
|
35
|
-
for (let i = 0; i < byte_str.length; i++) {
|
|
36
|
-
ia[i] = byte_str.charCodeAt(i);
|
|
37
|
-
}
|
|
38
|
-
return new Blob([ab], { type: mime_str });
|
|
39
|
-
}
|
|
40
|
-
export async function copy_table_data(data, headers, selected_cells) {
|
|
41
|
-
if (!selected_cells || selected_cells.length === 0) {
|
|
42
|
-
const header_row = headers
|
|
43
|
-
? headers.map((h) => String(h.value)).join(",")
|
|
44
|
-
: "";
|
|
45
|
-
const table_data = data
|
|
46
|
-
.map((row) => row.map((cell) => String(cell.value)).join(","))
|
|
47
|
-
.join("\n");
|
|
48
|
-
const all_data = header_row ? `${header_row}\n${table_data}` : table_data;
|
|
49
|
-
await write_to_clipboard(all_data);
|
|
50
|
-
return;
|
|
51
|
-
}
|
|
52
|
-
const min_row = Math.min(...selected_cells.map(([r]) => r));
|
|
53
|
-
const max_row = Math.max(...selected_cells.map(([r]) => r));
|
|
54
|
-
const min_col = Math.min(...selected_cells.map(([_, c]) => c));
|
|
55
|
-
const max_col = Math.max(...selected_cells.map(([_, c]) => c));
|
|
56
|
-
const selected_data = [];
|
|
57
|
-
for (let i = min_row; i <= max_row; i++) {
|
|
58
|
-
const row = [];
|
|
59
|
-
for (let j = min_col; j <= max_col; j++) {
|
|
60
|
-
const is_selected = selected_cells.some(([r, c]) => r === i && c === j);
|
|
61
|
-
row.push(is_selected ? String(data[i][j].value) : "");
|
|
62
|
-
}
|
|
63
|
-
selected_data.push(row.join(","));
|
|
64
|
-
}
|
|
65
|
-
await write_to_clipboard(selected_data.join("\n"));
|
|
66
|
-
}
|
|
67
|
-
async function write_to_clipboard(csv_data) {
|
|
68
|
-
try {
|
|
69
|
-
if ("clipboard" in navigator) {
|
|
70
|
-
await navigator.clipboard.writeText(csv_data);
|
|
71
|
-
}
|
|
72
|
-
else {
|
|
73
|
-
const textArea = document.createElement("textarea");
|
|
74
|
-
textArea.value = csv_data;
|
|
75
|
-
textArea.style.position = "absolute";
|
|
76
|
-
textArea.style.left = "-999999px";
|
|
77
|
-
document.body.prepend(textArea);
|
|
78
|
-
textArea.select();
|
|
79
|
-
document.execCommand("copy");
|
|
80
|
-
textArea.remove();
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
catch (error) {
|
|
84
|
-
console.error("Failed to copy table data:", error);
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
export function blob_to_string(blob, col_count, make_headers, set_values) {
|
|
88
|
-
const reader = new FileReader();
|
|
89
|
-
function handle_read(e) {
|
|
90
|
-
if (!e?.target?.result || typeof e.target.result !== "string")
|
|
91
|
-
return;
|
|
92
|
-
const [delimiter] = guess_delimiter(e.target.result, [",", "\t"]);
|
|
93
|
-
const [head, ...rest] = dsvFormat(delimiter).parseRows(e.target.result);
|
|
94
|
-
make_headers(col_count[1] === "fixed" ? head.slice(0, col_count[0]) : head);
|
|
95
|
-
set_values(rest);
|
|
96
|
-
reader.removeEventListener("loadend", handle_read);
|
|
97
|
-
}
|
|
98
|
-
reader.addEventListener("loadend", handle_read);
|
|
99
|
-
reader.readAsText(blob);
|
|
100
|
-
}
|
|
101
|
-
export function handle_file_upload(data_uri, col_count, make_headers, set_values) {
|
|
102
|
-
const blob = data_uri_to_blob(data_uri);
|
|
103
|
-
const reader = new FileReader();
|
|
104
|
-
reader.addEventListener("loadend", (e) => {
|
|
105
|
-
if (!e?.target?.result || typeof e.target.result !== "string")
|
|
106
|
-
return;
|
|
107
|
-
const [delimiter] = guess_delimiter(e.target.result, [",", "\t"]);
|
|
108
|
-
const [head, ...rest] = dsvFormat(delimiter).parseRows(e.target.result);
|
|
109
|
-
make_headers(col_count[1] === "fixed" ? head.slice(0, col_count[0]) : head);
|
|
110
|
-
set_values(rest);
|
|
111
|
-
});
|
|
112
|
-
reader.readAsText(blob);
|
|
113
|
-
}
|