2022-03-03 00:42:43 +08:00
|
|
|
<script lang="ts">
|
|
|
|
import { createEventDispatcher, tick } from "svelte";
|
2022-05-09 12:55:02 +08:00
|
|
|
import { dsvFormat } from "d3-dsv";
|
|
|
|
import { dequal } from "dequal/lite";
|
|
|
|
|
|
|
|
import { Upload } from "@gradio/upload";
|
2023-01-18 04:47:40 +08:00
|
|
|
import { Button } from "@gradio/button";
|
2022-05-09 12:55:02 +08:00
|
|
|
import EditableCell from "./EditableCell.svelte";
|
2023-03-14 08:12:41 +08:00
|
|
|
import type { SelectData } from "@gradio/utils";
|
2022-03-03 00:42:43 +08:00
|
|
|
|
2022-07-04 17:27:48 +08:00
|
|
|
type Datatype = "str" | "markdown" | "html" | "number" | "bool" | "date";
|
|
|
|
|
|
|
|
export let datatype: Datatype | Array<Datatype>;
|
2022-05-19 06:14:12 +08:00
|
|
|
export let label: string | null = null;
|
2022-03-03 00:42:43 +08:00
|
|
|
export let headers: Array<string> = [];
|
2022-07-22 02:12:46 +08:00
|
|
|
export let values:
|
|
|
|
| Array<Array<string | number>>
|
|
|
|
| { data: Array<Array<string | number>>; headers: Array<string> } = [[]];
|
2022-05-09 12:55:02 +08:00
|
|
|
export let col_count: [number, "fixed" | "dynamic"];
|
|
|
|
export let row_count: [number, "fixed" | "dynamic"];
|
2022-03-03 00:42:43 +08:00
|
|
|
|
|
|
|
export let editable = true;
|
2022-06-17 08:02:01 +08:00
|
|
|
export let wrap: boolean = false;
|
2022-03-03 00:42:43 +08:00
|
|
|
|
2023-04-20 22:27:52 +08:00
|
|
|
let selected: false | string = false;
|
|
|
|
|
2022-07-12 18:35:20 +08:00
|
|
|
$: {
|
|
|
|
if (values && !Array.isArray(values)) {
|
2022-07-22 02:12:46 +08:00
|
|
|
headers = values.headers;
|
2022-07-12 18:35:20 +08:00
|
|
|
values =
|
|
|
|
values.data.length === 0
|
|
|
|
? [Array(headers.length).fill("")]
|
|
|
|
: values.data;
|
2023-04-20 22:27:52 +08:00
|
|
|
selected = false;
|
2022-07-12 18:35:20 +08:00
|
|
|
} else if (values === null) {
|
|
|
|
values = [Array(headers.length).fill("")];
|
2023-04-20 22:27:52 +08:00
|
|
|
selected = false;
|
2022-07-12 18:35:20 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
const dispatch = createEventDispatcher<{
|
2022-07-22 02:12:46 +08:00
|
|
|
change: { data: Array<Array<string | number>>; headers: Array<string> };
|
2023-03-14 08:12:41 +08:00
|
|
|
select: SelectData;
|
2022-07-12 18:35:20 +08:00
|
|
|
}>();
|
2022-03-03 00:42:43 +08:00
|
|
|
|
2023-03-14 08:12:41 +08:00
|
|
|
let editing: false | string = false;
|
|
|
|
$: {
|
|
|
|
if (selected !== false) {
|
|
|
|
const loc = selected.split("-");
|
|
|
|
const row = parseInt(loc[0]);
|
|
|
|
const col = parseInt(loc[1]);
|
|
|
|
dispatch("select", { index: [row, col], value: data[row][col].value });
|
|
|
|
}
|
|
|
|
}
|
2022-03-03 00:42:43 +08:00
|
|
|
let els: Record<
|
|
|
|
string,
|
|
|
|
{ cell: null | HTMLTableCellElement; input: null | HTMLInputElement }
|
|
|
|
> = {};
|
|
|
|
|
2022-04-14 04:53:35 +08:00
|
|
|
type Headers = Array<{ value: string; id: string }>;
|
2022-03-03 00:42:43 +08:00
|
|
|
|
2022-05-09 12:55:02 +08:00
|
|
|
function make_headers(_head: Array<string>): Headers {
|
|
|
|
let _h = _head || [];
|
|
|
|
if (col_count[1] === "fixed" && _h.length < col_count[0]) {
|
|
|
|
const fill = Array(col_count[0] - _h.length)
|
|
|
|
.fill("")
|
2022-07-22 02:12:46 +08:00
|
|
|
.map((_, i) => `${i + _h.length}`);
|
2022-05-09 12:55:02 +08:00
|
|
|
_h = _h.concat(fill);
|
|
|
|
}
|
|
|
|
|
2022-02-18 22:06:24 +08:00
|
|
|
if (!_h || _h.length === 0) {
|
2022-05-09 12:55:02 +08:00
|
|
|
return Array(col_count[0])
|
|
|
|
.fill(0)
|
|
|
|
.map((_, i) => {
|
|
|
|
const _id = `h-${i}`;
|
|
|
|
els[_id] = { cell: null, input: null };
|
|
|
|
return { id: _id, value: JSON.stringify(i + 1) };
|
|
|
|
});
|
2022-03-03 00:42:43 +08:00
|
|
|
} else {
|
2022-04-14 04:53:35 +08:00
|
|
|
return _h.map((h, i) => {
|
|
|
|
const _id = `h-${i}`;
|
2022-03-03 00:42:43 +08:00
|
|
|
els[_id] = { cell: null, input: null };
|
2022-05-17 01:22:09 +08:00
|
|
|
return { id: _id, value: h ?? "" };
|
2022-03-03 00:42:43 +08:00
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-04-14 04:53:35 +08:00
|
|
|
function process_data(_values: Array<Array<string | number>>) {
|
2022-05-09 12:55:02 +08:00
|
|
|
const data_row_length = _values.length > 0 ? _values.length : row_count[0];
|
|
|
|
|
|
|
|
return Array(
|
|
|
|
row_count[1] === "fixed"
|
|
|
|
? row_count[0]
|
|
|
|
: data_row_length < row_count[0]
|
|
|
|
? row_count[0]
|
|
|
|
: data_row_length
|
|
|
|
)
|
|
|
|
.fill(0)
|
2022-07-22 02:12:46 +08:00
|
|
|
.map((_, i) =>
|
2022-05-09 12:55:02 +08:00
|
|
|
Array(col_count[1] === "fixed" ? col_count[0] : _values[0].length)
|
2022-04-14 04:53:35 +08:00
|
|
|
.fill(0)
|
2022-07-22 02:12:46 +08:00
|
|
|
.map((_, j) => {
|
2022-05-09 12:55:02 +08:00
|
|
|
const id = `${i}-${j}`;
|
2022-04-14 04:53:35 +08:00
|
|
|
els[id] = { input: null, cell: null };
|
2022-05-17 01:22:09 +08:00
|
|
|
return { value: _values?.[i]?.[j] ?? "", id };
|
2022-04-14 04:53:35 +08:00
|
|
|
})
|
2022-05-09 12:55:02 +08:00
|
|
|
);
|
2022-04-14 04:53:35 +08:00
|
|
|
}
|
|
|
|
|
2022-03-03 00:42:43 +08:00
|
|
|
let _headers = make_headers(headers);
|
2022-04-14 04:53:35 +08:00
|
|
|
let old_headers: Array<string> | undefined;
|
2022-05-09 12:55:02 +08:00
|
|
|
|
2022-04-14 04:53:35 +08:00
|
|
|
$: {
|
2022-05-09 12:55:02 +08:00
|
|
|
if (!dequal(headers, old_headers)) {
|
2022-04-14 04:53:35 +08:00
|
|
|
_headers = make_headers(headers);
|
2022-05-09 12:55:02 +08:00
|
|
|
|
2022-04-14 04:53:35 +08:00
|
|
|
old_headers = headers;
|
|
|
|
refresh_focus();
|
|
|
|
}
|
|
|
|
}
|
2022-05-09 12:55:02 +08:00
|
|
|
$: if (!dequal(values, old_val)) {
|
2022-07-22 02:12:46 +08:00
|
|
|
data = process_data(values as Array<Array<string | number>>);
|
|
|
|
old_val = values as Array<Array<string | number>>;
|
2022-04-14 04:53:35 +08:00
|
|
|
|
|
|
|
refresh_focus();
|
|
|
|
}
|
|
|
|
|
|
|
|
async function refresh_focus() {
|
|
|
|
if (typeof editing === "string") {
|
|
|
|
await tick();
|
|
|
|
els[editing as string]?.input?.focus();
|
|
|
|
} else if (typeof selected === "string") {
|
|
|
|
await tick();
|
|
|
|
els[selected as string]?.input?.focus();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
let data: Array<Array<{ id: string; value: string | number }>> = [[]];
|
|
|
|
|
|
|
|
let old_val: undefined | Array<Array<string | number>> = undefined;
|
|
|
|
|
2022-05-09 12:55:02 +08:00
|
|
|
$: _headers &&
|
2022-07-12 18:35:20 +08:00
|
|
|
dispatch("change", {
|
|
|
|
data: data.map((r) => r.map(({ value }) => value)),
|
|
|
|
headers: _headers.map((h) => h.value)
|
|
|
|
});
|
2022-03-03 00:42:43 +08:00
|
|
|
|
|
|
|
function get_sort_status(
|
|
|
|
name: string,
|
|
|
|
sort: number,
|
|
|
|
direction?: SortDirection
|
|
|
|
) {
|
|
|
|
if (!sort) return "none";
|
|
|
|
if (headers[sort] === name) {
|
|
|
|
if (direction === "asc") return "ascending";
|
|
|
|
if (direction === "des") return "descending";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-05-09 12:55:02 +08:00
|
|
|
function get_current_indices(id: string) {
|
|
|
|
return data.reduce(
|
|
|
|
(acc, arr, i) => {
|
|
|
|
const j = arr.reduce((acc, data, j) => (id === data.id ? j : acc), -1);
|
|
|
|
|
|
|
|
return j === -1 ? acc : [i, j];
|
|
|
|
},
|
|
|
|
[-1, -1]
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
async function start_edit(id: string, clear?: boolean) {
|
|
|
|
if (!editable || editing === id) return;
|
|
|
|
|
|
|
|
if (clear) {
|
|
|
|
const [i, j] = get_current_indices(id);
|
|
|
|
|
|
|
|
data[i][j].value = "";
|
|
|
|
}
|
2022-03-03 00:42:43 +08:00
|
|
|
editing = id;
|
|
|
|
await tick();
|
|
|
|
const { input } = els[id];
|
|
|
|
input?.focus();
|
|
|
|
}
|
|
|
|
|
2022-05-09 12:55:02 +08:00
|
|
|
async function handle_keydown(
|
2022-03-03 00:42:43 +08:00
|
|
|
event: KeyboardEvent,
|
|
|
|
i: number,
|
|
|
|
j: number,
|
2022-04-14 04:53:35 +08:00
|
|
|
id: string
|
2022-03-03 00:42:43 +08:00
|
|
|
) {
|
|
|
|
let is_data;
|
2022-05-09 12:55:02 +08:00
|
|
|
|
2022-03-03 00:42:43 +08:00
|
|
|
switch (event.key) {
|
|
|
|
case "ArrowRight":
|
|
|
|
if (editing) break;
|
|
|
|
event.preventDefault();
|
|
|
|
is_data = data[i][j + 1];
|
|
|
|
selected = is_data ? is_data.id : selected;
|
|
|
|
break;
|
|
|
|
case "ArrowLeft":
|
|
|
|
if (editing) break;
|
|
|
|
event.preventDefault();
|
|
|
|
is_data = data[i][j - 1];
|
|
|
|
selected = is_data ? is_data.id : selected;
|
|
|
|
break;
|
|
|
|
case "ArrowDown":
|
|
|
|
if (editing) break;
|
|
|
|
event.preventDefault();
|
|
|
|
is_data = data[i + 1];
|
|
|
|
selected = is_data ? is_data[j].id : selected;
|
|
|
|
break;
|
|
|
|
case "ArrowUp":
|
|
|
|
if (editing) break;
|
|
|
|
event.preventDefault();
|
|
|
|
is_data = data[i - 1];
|
|
|
|
selected = is_data ? is_data[j].id : selected;
|
|
|
|
break;
|
|
|
|
case "Escape":
|
|
|
|
if (!editable) break;
|
|
|
|
event.preventDefault();
|
2022-05-09 12:55:02 +08:00
|
|
|
selected = editing;
|
2022-03-03 00:42:43 +08:00
|
|
|
editing = false;
|
|
|
|
break;
|
|
|
|
case "Enter":
|
|
|
|
if (!editable) break;
|
|
|
|
event.preventDefault();
|
2022-05-09 12:55:02 +08:00
|
|
|
|
|
|
|
if (event.shiftKey) {
|
|
|
|
add_row(i);
|
|
|
|
await tick();
|
|
|
|
const [pos] = get_current_indices(id);
|
|
|
|
selected = data[pos + 1][j].id;
|
2022-03-03 00:42:43 +08:00
|
|
|
} else {
|
2022-05-09 12:55:02 +08:00
|
|
|
if (editing === id) {
|
|
|
|
editing = false;
|
|
|
|
} else {
|
|
|
|
start_edit(id);
|
|
|
|
}
|
2022-03-03 00:42:43 +08:00
|
|
|
}
|
2022-05-09 12:55:02 +08:00
|
|
|
|
2022-03-03 00:42:43 +08:00
|
|
|
break;
|
|
|
|
case "Backspace":
|
|
|
|
if (!editable) break;
|
|
|
|
if (!editing) {
|
|
|
|
event.preventDefault();
|
|
|
|
data[i][j].value = "";
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case "Delete":
|
|
|
|
if (!editable) break;
|
|
|
|
if (!editing) {
|
|
|
|
event.preventDefault();
|
|
|
|
data[i][j].value = "";
|
|
|
|
}
|
2022-05-09 12:55:02 +08:00
|
|
|
break;
|
|
|
|
case "Tab":
|
|
|
|
let direction = event.shiftKey ? -1 : 1;
|
|
|
|
|
|
|
|
let is_data_x = data[i][j + direction];
|
|
|
|
let is_data_y =
|
|
|
|
data?.[i + direction]?.[direction > 0 ? 0 : _headers.length - 1];
|
|
|
|
let _selected = is_data_x || is_data_y;
|
|
|
|
if (_selected) {
|
|
|
|
event.preventDefault();
|
|
|
|
selected = _selected ? _selected.id : selected;
|
|
|
|
}
|
|
|
|
editing = false;
|
|
|
|
|
2022-03-03 00:42:43 +08:00
|
|
|
break;
|
|
|
|
default:
|
2022-05-09 12:55:02 +08:00
|
|
|
if (
|
|
|
|
(!editing || (editing && editing !== id)) &&
|
|
|
|
event.key.length === 1
|
|
|
|
) {
|
|
|
|
start_edit(id, true);
|
|
|
|
}
|
|
|
|
|
2022-03-03 00:42:43 +08:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-04-14 04:53:35 +08:00
|
|
|
async function handle_cell_click(id: string) {
|
|
|
|
if (editing === id) return;
|
|
|
|
if (selected === id) return;
|
2022-03-03 00:42:43 +08:00
|
|
|
editing = false;
|
|
|
|
selected = id;
|
|
|
|
}
|
|
|
|
|
2022-04-14 04:53:35 +08:00
|
|
|
async function set_focus(id: string | boolean, type: "edit" | "select") {
|
2022-05-09 12:55:02 +08:00
|
|
|
if (type === "edit" && typeof id == "string") {
|
2022-03-03 00:42:43 +08:00
|
|
|
await tick();
|
|
|
|
els[id].input?.focus();
|
|
|
|
}
|
|
|
|
|
|
|
|
if (
|
|
|
|
type === "edit" &&
|
|
|
|
typeof id == "boolean" &&
|
2022-05-09 12:55:02 +08:00
|
|
|
typeof selected === "string"
|
2022-03-03 00:42:43 +08:00
|
|
|
) {
|
|
|
|
let cell = els[selected]?.cell;
|
|
|
|
await tick();
|
|
|
|
cell?.focus();
|
|
|
|
}
|
|
|
|
|
2022-05-09 12:55:02 +08:00
|
|
|
if (type === "select" && typeof id == "string") {
|
2022-03-03 00:42:43 +08:00
|
|
|
const { cell } = els[id];
|
2022-05-09 12:55:02 +08:00
|
|
|
// cell?.setAttribute("tabindex", "0");
|
2022-03-03 00:42:43 +08:00
|
|
|
await tick();
|
2022-05-09 12:55:02 +08:00
|
|
|
cell?.focus();
|
2022-03-03 00:42:43 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
$: set_focus(editing, "edit");
|
|
|
|
$: set_focus(selected, "select");
|
|
|
|
|
|
|
|
type SortDirection = "asc" | "des";
|
|
|
|
let sort_direction: SortDirection;
|
|
|
|
let sort_by: number;
|
|
|
|
|
|
|
|
function sort(col: number, dir: SortDirection) {
|
|
|
|
if (dir === "asc") {
|
|
|
|
data = data.sort((a, b) => (a[col].value < b[col].value ? -1 : 1));
|
|
|
|
} else if (dir === "des") {
|
|
|
|
data = data.sort((a, b) => (a[col].value > b[col].value ? -1 : 1));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function handle_sort(col: number) {
|
|
|
|
if (typeof sort_by !== "number" || sort_by !== col) {
|
|
|
|
sort_direction = "asc";
|
|
|
|
sort_by = col;
|
|
|
|
} else {
|
|
|
|
if (sort_direction === "asc") {
|
|
|
|
sort_direction = "des";
|
|
|
|
} else if (sort_direction === "des") {
|
|
|
|
sort_direction = "asc";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
sort(col, sort_direction);
|
|
|
|
}
|
|
|
|
|
2023-03-14 08:12:41 +08:00
|
|
|
let header_edit: string | false;
|
2022-03-03 00:42:43 +08:00
|
|
|
|
2022-09-24 01:50:08 +08:00
|
|
|
function update_headers_data() {
|
|
|
|
if (typeof selected === "string") {
|
|
|
|
const new_header = els[selected].input?.value;
|
|
|
|
if (_headers.find((i) => i.id === selected)) {
|
|
|
|
let obj = _headers.find((i) => i.id === selected);
|
|
|
|
if (new_header) obj!["value"] = new_header;
|
|
|
|
} else {
|
|
|
|
if (new_header) _headers.push({ id: selected, value: new_header });
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-04-14 04:53:35 +08:00
|
|
|
async function edit_header(_id: string, select?: boolean) {
|
2022-05-09 12:55:02 +08:00
|
|
|
if (!editable || col_count[1] !== "dynamic" || editing === _id) return;
|
2022-03-03 00:42:43 +08:00
|
|
|
header_edit = _id;
|
|
|
|
await tick();
|
|
|
|
els[_id].input?.focus();
|
|
|
|
if (select) els[_id].input?.select();
|
|
|
|
}
|
|
|
|
|
|
|
|
function end_header_edit(event: KeyboardEvent) {
|
|
|
|
if (!editable) return;
|
|
|
|
|
|
|
|
switch (event.key) {
|
|
|
|
case "Escape":
|
|
|
|
case "Enter":
|
2022-09-24 01:50:08 +08:00
|
|
|
case "Tab":
|
2022-03-03 00:42:43 +08:00
|
|
|
event.preventDefault();
|
2022-05-09 12:55:02 +08:00
|
|
|
selected = header_edit;
|
2022-03-03 00:42:43 +08:00
|
|
|
header_edit = false;
|
2022-09-24 01:50:08 +08:00
|
|
|
update_headers_data();
|
|
|
|
break;
|
2022-03-03 00:42:43 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-05-09 12:55:02 +08:00
|
|
|
function add_row(index?: number) {
|
|
|
|
if (row_count[1] !== "dynamic") return;
|
|
|
|
data.splice(
|
|
|
|
index ? index + 1 : data.length,
|
|
|
|
0,
|
2022-05-19 06:14:12 +08:00
|
|
|
Array(data[0].length)
|
2022-05-09 12:55:02 +08:00
|
|
|
.fill(0)
|
|
|
|
.map((_, i) => {
|
|
|
|
const _id = `${data.length}-${i}`;
|
|
|
|
els[_id] = { cell: null, input: null };
|
|
|
|
return { id: _id, value: "" };
|
|
|
|
})
|
2022-03-03 00:42:43 +08:00
|
|
|
);
|
2022-05-09 12:55:02 +08:00
|
|
|
|
2022-03-03 00:42:43 +08:00
|
|
|
data = data;
|
|
|
|
}
|
|
|
|
|
|
|
|
async function add_col() {
|
2022-05-09 12:55:02 +08:00
|
|
|
if (col_count[1] !== "dynamic") return;
|
2022-03-03 00:42:43 +08:00
|
|
|
for (let i = 0; i < data.length; i++) {
|
2022-04-14 04:53:35 +08:00
|
|
|
const _id = `${i}-${data[i].length}`;
|
2022-03-03 00:42:43 +08:00
|
|
|
els[_id] = { cell: null, input: null };
|
|
|
|
data[i].push({ id: _id, value: "" });
|
|
|
|
}
|
|
|
|
|
2022-05-09 23:18:13 +08:00
|
|
|
const _id = `h-${_headers.length}`;
|
2022-03-03 00:42:43 +08:00
|
|
|
els[_id] = { cell: null, input: null };
|
|
|
|
_headers.push({ id: _id, value: `Header ${_headers.length + 1}` });
|
|
|
|
|
|
|
|
data = data;
|
|
|
|
_headers = _headers;
|
|
|
|
|
|
|
|
await tick();
|
|
|
|
|
|
|
|
edit_header(_id, true);
|
|
|
|
}
|
|
|
|
|
2022-05-09 12:55:02 +08:00
|
|
|
function handle_click_outside(event: Event) {
|
|
|
|
if (typeof editing === "string" && els[editing]) {
|
|
|
|
if (
|
|
|
|
els[editing].cell !== event.target &&
|
|
|
|
!els[editing].cell?.contains(event?.target as Node | null)
|
|
|
|
) {
|
2022-05-17 02:55:24 +08:00
|
|
|
editing = false;
|
2022-03-03 00:42:43 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-05-09 12:55:02 +08:00
|
|
|
if (typeof header_edit === "string" && els[header_edit]) {
|
|
|
|
if (
|
|
|
|
els[header_edit].cell !== event.target &&
|
|
|
|
!els[header_edit].cell?.contains(event.target as Node | null)
|
|
|
|
) {
|
2022-09-24 01:50:08 +08:00
|
|
|
selected = header_edit;
|
|
|
|
header_edit = false;
|
|
|
|
update_headers_data();
|
2022-05-09 12:55:02 +08:00
|
|
|
header_edit = false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function guess_delimitaor(text: string, possibleDelimiters: Array<string>) {
|
|
|
|
return possibleDelimiters.filter(weedOut);
|
|
|
|
|
|
|
|
function weedOut(delimiter: string) {
|
|
|
|
var cache = -1;
|
|
|
|
return text.split("\n").every(checkLength);
|
|
|
|
|
|
|
|
function checkLength(line: string) {
|
|
|
|
if (!line) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
var length = line.split(delimiter).length;
|
|
|
|
if (cache < 0) {
|
|
|
|
cache = length;
|
|
|
|
}
|
|
|
|
return cache === length && length > 1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function data_uri_to_blob(data_uri: string) {
|
|
|
|
const byte_str = atob(data_uri.split(",")[1]);
|
|
|
|
const mime_str = data_uri.split(",")[0].split(":")[1].split(";")[0];
|
|
|
|
|
|
|
|
const ab = new ArrayBuffer(byte_str.length);
|
|
|
|
const ia = new Uint8Array(ab);
|
|
|
|
|
|
|
|
for (let i = 0; i < byte_str.length; i++) {
|
|
|
|
ia[i] = byte_str.charCodeAt(i);
|
|
|
|
}
|
|
|
|
|
|
|
|
return new Blob([ab], { type: mime_str });
|
|
|
|
}
|
|
|
|
|
|
|
|
function blob_to_string(blob: Blob) {
|
|
|
|
const reader = new FileReader();
|
|
|
|
|
2022-05-09 23:18:13 +08:00
|
|
|
function handle_read(e: ProgressEvent<FileReader>) {
|
|
|
|
if (!e?.target?.result || typeof e.target.result !== "string") return;
|
2022-05-09 12:55:02 +08:00
|
|
|
|
2022-05-09 23:18:13 +08:00
|
|
|
const [delimiter] = guess_delimitaor(e.target.result, [",", "\t"]);
|
|
|
|
|
|
|
|
const [head, ...rest] = dsvFormat(delimiter).parseRows(e.target.result);
|
2022-05-09 12:55:02 +08:00
|
|
|
|
|
|
|
_headers = make_headers(
|
|
|
|
col_count[1] === "fixed" ? head.slice(0, col_count[0]) : head
|
|
|
|
);
|
|
|
|
|
|
|
|
values = rest;
|
|
|
|
reader.removeEventListener("loadend", handle_read);
|
|
|
|
}
|
2022-03-03 00:42:43 +08:00
|
|
|
|
2022-05-09 12:55:02 +08:00
|
|
|
reader.addEventListener("loadend", handle_read);
|
|
|
|
|
|
|
|
reader.readAsText(blob);
|
|
|
|
}
|
|
|
|
|
|
|
|
let dragging = false;
|
2022-03-03 00:42:43 +08:00
|
|
|
</script>
|
|
|
|
|
2022-05-17 02:55:24 +08:00
|
|
|
<svelte:window
|
|
|
|
on:click={handle_click_outside}
|
|
|
|
on:touchstart={handle_click_outside}
|
|
|
|
/>
|
2022-05-09 12:55:02 +08:00
|
|
|
|
2023-01-18 04:47:40 +08:00
|
|
|
<div class:label={label && label.length !== 0}>
|
2022-05-19 06:14:12 +08:00
|
|
|
{#if label && label.length !== 0}
|
2023-01-18 04:47:40 +08:00
|
|
|
<p>
|
2022-05-19 06:14:12 +08:00
|
|
|
{label}
|
|
|
|
</p>
|
|
|
|
{/if}
|
2023-01-18 04:47:40 +08:00
|
|
|
<div class="table-wrap scroll-hide" class:dragging class:no-wrap={!wrap}>
|
2022-05-19 06:14:12 +08:00
|
|
|
<Upload
|
|
|
|
flex={false}
|
|
|
|
center={false}
|
|
|
|
boundedheight={false}
|
2022-11-03 07:28:38 +08:00
|
|
|
disable_click={true}
|
2022-05-19 06:14:12 +08:00
|
|
|
on:load={(e) => blob_to_string(data_uri_to_blob(e.detail.data))}
|
|
|
|
bind:dragging
|
2022-05-09 12:55:02 +08:00
|
|
|
>
|
2023-01-18 04:47:40 +08:00
|
|
|
<table class:dragging>
|
2022-05-19 06:14:12 +08:00
|
|
|
{#if label && label.length !== 0}
|
|
|
|
<caption class="sr-only">{label}</caption>
|
|
|
|
{/if}
|
2023-01-18 04:47:40 +08:00
|
|
|
<thead>
|
|
|
|
<tr>
|
2022-05-19 06:14:12 +08:00
|
|
|
{#each _headers as { value, id }, i (id)}
|
|
|
|
<th
|
2022-05-09 12:55:02 +08:00
|
|
|
bind:this={els[id].cell}
|
2023-01-18 04:47:40 +08:00
|
|
|
class:editing={header_edit === id}
|
2022-05-19 06:14:12 +08:00
|
|
|
aria-sort={get_sort_status(value, sort_by, sort_direction)}
|
2022-03-03 00:42:43 +08:00
|
|
|
>
|
2023-01-18 04:47:40 +08:00
|
|
|
<div class="cell-wrap">
|
2022-05-09 12:55:02 +08:00
|
|
|
<EditableCell
|
2022-05-19 06:14:12 +08:00
|
|
|
{value}
|
2022-05-09 12:55:02 +08:00
|
|
|
bind:el={els[id].input}
|
2022-05-19 06:14:12 +08:00
|
|
|
edit={header_edit === id}
|
|
|
|
on:keydown={end_header_edit}
|
|
|
|
on:dblclick={() => edit_header(id)}
|
|
|
|
header
|
2022-03-03 00:42:43 +08:00
|
|
|
/>
|
2022-05-19 06:14:12 +08:00
|
|
|
|
|
|
|
<div
|
2023-01-18 04:47:40 +08:00
|
|
|
class:sorted={sort_by === i}
|
|
|
|
class:des={sort_by === i && sort_direction === "des"}
|
|
|
|
class="sort-button {sort_direction} "
|
2022-05-19 06:14:12 +08:00
|
|
|
on:click={() => handle_sort(i)}
|
|
|
|
>
|
|
|
|
<svg
|
|
|
|
width="1em"
|
|
|
|
height="1em"
|
|
|
|
viewBox="0 0 9 7"
|
|
|
|
fill="none"
|
|
|
|
xmlns="http://www.w3.org/2000/svg"
|
|
|
|
>
|
|
|
|
<path d="M4.49999 0L8.3971 6.75H0.602875L4.49999 0Z" />
|
|
|
|
</svg>
|
|
|
|
</div>
|
2022-05-09 12:55:02 +08:00
|
|
|
</div>
|
2022-05-19 06:14:12 +08:00
|
|
|
</th>
|
2022-05-09 12:55:02 +08:00
|
|
|
{/each}
|
|
|
|
</tr>
|
2022-05-19 06:14:12 +08:00
|
|
|
</thead>
|
|
|
|
|
2023-01-18 04:47:40 +08:00
|
|
|
<tbody>
|
2022-05-19 06:14:12 +08:00
|
|
|
{#each data as row, i (row)}
|
2023-01-18 04:47:40 +08:00
|
|
|
<tr>
|
2022-05-19 06:14:12 +08:00
|
|
|
{#each row as { value, id }, j (id)}
|
|
|
|
<td
|
|
|
|
tabindex="0"
|
|
|
|
bind:this={els[id].cell}
|
|
|
|
on:touchstart={() => start_edit(id)}
|
|
|
|
on:click={() => handle_cell_click(id)}
|
|
|
|
on:dblclick={() => start_edit(id)}
|
|
|
|
on:keydown={(e) => handle_keydown(e, i, j, id)}
|
|
|
|
>
|
|
|
|
<div
|
|
|
|
class:border-transparent={selected !== id}
|
2023-01-18 04:47:40 +08:00
|
|
|
class="cell-wrap"
|
2022-05-19 06:14:12 +08:00
|
|
|
>
|
|
|
|
<EditableCell
|
|
|
|
bind:value
|
|
|
|
bind:el={els[id].input}
|
|
|
|
edit={editing === id}
|
2022-07-04 17:27:48 +08:00
|
|
|
datatype={Array.isArray(datatype)
|
|
|
|
? datatype[j]
|
|
|
|
: datatype}
|
2022-05-19 06:14:12 +08:00
|
|
|
/>
|
|
|
|
</div>
|
|
|
|
</td>
|
|
|
|
{/each}
|
|
|
|
</tr>
|
|
|
|
{/each}
|
|
|
|
</tbody>
|
|
|
|
</table>
|
|
|
|
</Upload>
|
2022-03-03 00:42:43 +08:00
|
|
|
</div>
|
2022-05-19 06:14:12 +08:00
|
|
|
{#if editable}
|
2023-01-18 04:47:40 +08:00
|
|
|
<div class="controls-wrap">
|
2022-05-19 06:14:12 +08:00
|
|
|
{#if row_count[1] === "dynamic"}
|
2023-01-18 04:47:40 +08:00
|
|
|
<span class="button-wrap">
|
2023-03-07 04:52:31 +08:00
|
|
|
<Button variant="secondary" size="sm" on:click={() => add_row()}>
|
2023-01-18 04:47:40 +08:00
|
|
|
<svg
|
|
|
|
xmlns="http://www.w3.org/2000/svg"
|
|
|
|
xmlns:xlink="http://www.w3.org/1999/xlink"
|
|
|
|
aria-hidden="true"
|
|
|
|
role="img"
|
|
|
|
width="1em"
|
|
|
|
height="1em"
|
|
|
|
preserveAspectRatio="xMidYMid meet"
|
|
|
|
viewBox="0 0 32 32"
|
|
|
|
>
|
|
|
|
<path
|
|
|
|
fill="currentColor"
|
|
|
|
d="M24.59 16.59L17 24.17V4h-2v20.17l-7.59-7.58L6 18l10 10l10-10l-1.41-1.41z"
|
|
|
|
/>
|
|
|
|
</svg>
|
|
|
|
New row
|
|
|
|
</Button>
|
|
|
|
</span>
|
2022-05-19 06:14:12 +08:00
|
|
|
{/if}
|
|
|
|
{#if col_count[1] === "dynamic"}
|
2023-01-18 04:47:40 +08:00
|
|
|
<span class="button-wrap">
|
2023-03-07 04:52:31 +08:00
|
|
|
<Button variant="secondary" size="sm" on:click={add_col}>
|
2023-01-18 04:47:40 +08:00
|
|
|
<svg
|
|
|
|
xmlns="http://www.w3.org/2000/svg"
|
|
|
|
xmlns:xlink="http://www.w3.org/1999/xlink"
|
|
|
|
aria-hidden="true"
|
|
|
|
role="img"
|
|
|
|
width="1em"
|
|
|
|
height="1em"
|
|
|
|
preserveAspectRatio="xMidYMid meet"
|
|
|
|
viewBox="0 0 32 32"
|
|
|
|
>
|
|
|
|
<path
|
|
|
|
fill="currentColor"
|
|
|
|
d="m18 6l-1.43 1.393L24.15 15H4v2h20.15l-7.58 7.573L18 26l10-10L18 6z"
|
|
|
|
/>
|
|
|
|
</svg>
|
|
|
|
New column
|
|
|
|
</Button>
|
|
|
|
</span>
|
|
|
|
{/if}
|
2022-05-19 06:14:12 +08:00
|
|
|
</div>
|
|
|
|
{/if}
|
|
|
|
</div>
|
2023-01-18 04:47:40 +08:00
|
|
|
|
|
|
|
<style>
|
|
|
|
.button-wrap:hover svg {
|
2023-03-18 08:20:55 +08:00
|
|
|
color: var(--color-accent);
|
2023-01-18 04:47:40 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
.button-wrap svg {
|
|
|
|
margin-right: var(--size-1);
|
|
|
|
margin-left: -5px;
|
|
|
|
}
|
|
|
|
.label {
|
|
|
|
margin-top: var(--size-6);
|
|
|
|
}
|
|
|
|
|
|
|
|
.label p {
|
|
|
|
position: relative;
|
|
|
|
z-index: var(--layer-4);
|
|
|
|
margin-bottom: var(--size-2);
|
2023-03-17 22:41:53 +08:00
|
|
|
color: var(--block-label-text-color);
|
2023-03-07 04:52:31 +08:00
|
|
|
font-size: var(--block-label-text-size);
|
2023-01-18 04:47:40 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
.table-wrap {
|
|
|
|
position: relative;
|
|
|
|
transition: 150ms;
|
2023-03-17 22:41:53 +08:00
|
|
|
border: 1px solid var(--border-color-primary);
|
2023-03-07 04:52:31 +08:00
|
|
|
border-radius: var(--table-radius);
|
2023-01-18 04:47:40 +08:00
|
|
|
overflow-x: scroll;
|
|
|
|
overflow-y: hidden;
|
|
|
|
}
|
|
|
|
|
|
|
|
.dragging {
|
2023-03-18 08:20:55 +08:00
|
|
|
border-color: var(--color-accent);
|
2023-01-18 04:47:40 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
.no-wrap {
|
|
|
|
white-space: nowrap;
|
|
|
|
}
|
|
|
|
|
|
|
|
table {
|
|
|
|
transition: 150ms;
|
|
|
|
width: var(--size-full);
|
|
|
|
table-layout: auto;
|
|
|
|
overflow: hidden;
|
2023-03-07 04:52:31 +08:00
|
|
|
color: var(--body-text-color);
|
|
|
|
font-size: var(--input-text-size);
|
2023-01-18 04:47:40 +08:00
|
|
|
line-height: var(--line-md);
|
|
|
|
font-family: var(--font-mono);
|
|
|
|
}
|
|
|
|
|
|
|
|
table.dragging {
|
|
|
|
opacity: 0.4;
|
|
|
|
}
|
|
|
|
|
|
|
|
thead {
|
|
|
|
position: sticky;
|
|
|
|
top: 0;
|
|
|
|
left: 0;
|
|
|
|
z-index: var(--layer-1);
|
|
|
|
box-shadow: var(--shadow-drop);
|
|
|
|
}
|
|
|
|
|
|
|
|
tr {
|
2023-03-17 22:41:53 +08:00
|
|
|
border-bottom: 1px solid var(--border-color-primary);
|
2023-01-18 04:47:40 +08:00
|
|
|
text-align: left;
|
|
|
|
}
|
|
|
|
|
|
|
|
tr > * + * {
|
|
|
|
border-right-width: 0px;
|
|
|
|
border-left-width: 1px;
|
|
|
|
border-style: solid;
|
2023-03-17 22:41:53 +08:00
|
|
|
border-color: var(--border-color-primary);
|
2023-01-18 04:47:40 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
th,
|
|
|
|
td {
|
|
|
|
--ring-color: transparent;
|
|
|
|
position: relative;
|
|
|
|
outline: none;
|
|
|
|
box-shadow: inset 0 0 0 1px var(--ring-color);
|
|
|
|
padding: 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
th:first-child {
|
2023-03-07 04:52:31 +08:00
|
|
|
border-top-left-radius: var(--table-radius);
|
2023-01-18 04:47:40 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
th:last-child {
|
2023-03-07 04:52:31 +08:00
|
|
|
border-top-right-radius: var(--table-radius);
|
2023-01-18 04:47:40 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
th:focus-within,
|
|
|
|
td:focus-within {
|
2023-03-18 08:20:55 +08:00
|
|
|
--ring-color: var(--color-accent);
|
2023-01-18 04:47:40 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
tr:last-child td:first-child {
|
2023-03-07 04:52:31 +08:00
|
|
|
border-bottom-left-radius: var(--table-radius);
|
2023-01-18 04:47:40 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
tr:last-child td:last-child {
|
2023-03-07 04:52:31 +08:00
|
|
|
border-bottom-right-radius: var(--table-radius);
|
2023-01-18 04:47:40 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
tr th {
|
2023-03-18 08:20:55 +08:00
|
|
|
background: var(--table-even-background-fill);
|
2023-01-18 04:47:40 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
th svg {
|
|
|
|
fill: currentColor;
|
|
|
|
font-size: 10px;
|
|
|
|
}
|
|
|
|
|
|
|
|
.sort-button {
|
|
|
|
display: flex;
|
|
|
|
flex: none;
|
|
|
|
justify-content: center;
|
|
|
|
align-items: center;
|
|
|
|
transition: 150ms;
|
|
|
|
cursor: pointer;
|
|
|
|
padding: var(--size-2);
|
2023-03-17 22:41:53 +08:00
|
|
|
color: var(--body-text-color-subdued);
|
2023-03-07 04:52:31 +08:00
|
|
|
line-height: var(--text-sm);
|
2023-01-18 04:47:40 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
.sort-button:hover {
|
2023-03-07 04:52:31 +08:00
|
|
|
color: var(--body-text-color);
|
2023-01-18 04:47:40 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
.des {
|
|
|
|
transform: scaleY(-1);
|
|
|
|
}
|
|
|
|
|
|
|
|
.sort-button.sorted {
|
2023-03-18 08:20:55 +08:00
|
|
|
color: var(--color-accent);
|
2023-01-18 04:47:40 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
tbody {
|
|
|
|
overflow-y: scroll;
|
|
|
|
}
|
|
|
|
|
|
|
|
tbody > tr:last-child {
|
|
|
|
border: none;
|
|
|
|
}
|
|
|
|
|
|
|
|
tbody > tr:nth-child(even) {
|
2023-03-18 08:20:55 +08:00
|
|
|
background: var(--table-even-background-fill);
|
2023-01-18 04:47:40 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
tbody > tr:nth-child(odd) {
|
2023-03-18 08:20:55 +08:00
|
|
|
background: var(--table-odd-background-fill);
|
2023-01-18 04:47:40 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
tbody > tr:nth-child(odd):focus {
|
2023-03-18 08:20:55 +08:00
|
|
|
background: var(--background-fill-primary);
|
2023-01-18 04:47:40 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
.editing {
|
2023-03-07 04:52:31 +08:00
|
|
|
background: var(--table-editing);
|
2023-01-18 04:47:40 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
.cell-wrap {
|
|
|
|
display: flex;
|
|
|
|
align-items: center;
|
|
|
|
outline: none;
|
|
|
|
height: var(--size-full);
|
|
|
|
min-height: var(--size-9);
|
|
|
|
}
|
|
|
|
|
|
|
|
.controls-wrap {
|
|
|
|
display: flex;
|
|
|
|
justify-content: flex-end;
|
|
|
|
padding-top: var(--size-2);
|
|
|
|
}
|
|
|
|
|
|
|
|
.controls-wrap > * + * {
|
|
|
|
margin-left: var(--size-1);
|
|
|
|
}
|
|
|
|
</style>
|