import { Table } from "antd";
|
import { inject, observer } from "mobx-react";
|
import { flow, getEnv, types } from "mobx-state-tree";
|
import Papa from "papaparse";
|
|
import { errorBuilder } from "../../core/DataValidator/ConfigValidator";
|
import Registry from "../../core/Registry";
|
import { AnnotationMixin } from "../../mixins/AnnotationMixin";
|
import ProcessAttrsMixin from "../../mixins/ProcessAttrs";
|
import Base from "./Base";
|
import { parseTypeAndOption, parseValue } from "../../utils/data";
|
|
/**
|
* The `Table` tag is used to display object keys and values in a table.
|
* @example
|
* <!-- Basic labeling configuration for text in a table -->
|
* <View>
|
* <Table name="text-1" value="$text"></Table>
|
* </View>
|
* @name Table
|
* @meta_title Table Tag to Display Keys & Values in Tables
|
* @meta_description Customize Label Studio by displaying key-value pairs in tasks for machine learning and data science projects.
|
* @param {string} value Data field value containing JSON type for Table
|
* @param {string} [valueType] Value to define the data type in Table
|
*/
|
const Model = types
|
.model({
|
type: "table",
|
value: types.maybeNull(types.string),
|
_value: types.frozen([]),
|
valuetype: types.optional(types.string, "json"),
|
})
|
.views((self) => ({
|
get isJsonArrayOfObjects() {
|
const value = self._value;
|
return (
|
Array.isArray(value) &&
|
value.length > 0 &&
|
typeof value[0] === "object" &&
|
value[0] !== null &&
|
!Array.isArray(value[0])
|
);
|
},
|
get dataSource() {
|
const { type } = parseTypeAndOption(self.valuetype);
|
|
if (type === "json") {
|
const value = self._value;
|
|
// If JSON is an array of objects, treat each object as a row for a normal table
|
if (self.isJsonArrayOfObjects) {
|
return value;
|
}
|
|
// Array of primitives or mixed values → key/value representation with index as key
|
if (Array.isArray(value)) {
|
return value.map((v, idx) => {
|
let val = v;
|
if (typeof val === "object") val = JSON.stringify(val);
|
return { type: String(idx), value: val };
|
});
|
}
|
|
// Object case → key/value representation
|
return Object.keys(value)
|
.sort((a, b) => {
|
return a.toLowerCase().localeCompare(b.toLowerCase());
|
})
|
.map((k) => {
|
let val = value[k];
|
|
if (typeof val === "object") val = JSON.stringify(val);
|
return { type: k, value: val };
|
});
|
}
|
return self._value;
|
},
|
get columns() {
|
const { type } = parseTypeAndOption(self.valuetype);
|
if (type === "json") {
|
const value = self._value;
|
// JSON array of objects → derive columns from union of keys across all rows
|
if (self.isJsonArrayOfObjects) {
|
const allKeys = Array.from(
|
new Set(
|
value.reduce((acc, row) => {
|
if (row && typeof row === "object" && !Array.isArray(row)) {
|
acc.push(...Object.keys(row));
|
}
|
return acc;
|
}, []),
|
),
|
);
|
|
return allKeys.map((key) => ({ title: key, dataIndex: key }));
|
}
|
// Otherwise, use generic Name/Value columns
|
return [
|
{ title: "Name", dataIndex: "type" },
|
{ title: "Value", dataIndex: "value" },
|
];
|
}
|
if (!self._value[0]) {
|
return [
|
{ title: "Name", dataIndex: "type" },
|
{ title: "Value", dataIndex: "value" },
|
];
|
}
|
return Object.keys(self._value[0]).map((value) => ({ title: value, dataIndex: value }));
|
},
|
}))
|
.actions((self) => ({
|
updateValue: flow(function* (store) {
|
const { type, options } = parseTypeAndOption(self.valuetype);
|
let originData = parseValue(self.value, store.task.dataObj);
|
|
if (options.url) {
|
try {
|
const response = yield fetch(originData);
|
const { ok, status, statusText } = response;
|
|
if (!ok) throw new Error(`${status} ${statusText}`);
|
|
originData = yield response.text();
|
} catch (error) {
|
const message = getEnv(self).messages.ERR_LOADING_HTTP({
|
attr: self.value,
|
error: String(error),
|
url: originData,
|
});
|
|
self.annotationStore.addErrors([errorBuilder.generalError(message)]);
|
}
|
}
|
|
switch (type) {
|
case "csv":
|
{
|
Papa.parse(originData, {
|
delimiter: options.separator,
|
header: !options.headless,
|
download: false,
|
complete: ({ data }) => {
|
self._value = data;
|
},
|
});
|
}
|
break;
|
default:
|
self._value = typeof originData === "string" ? JSON.parse(originData) : originData;
|
break;
|
}
|
}),
|
}));
|
|
const TableModel = types.compose("TableModel", Base, ProcessAttrsMixin, AnnotationMixin, Model);
|
|
const HtxTable = inject("store")(
|
observer(({ item }) => {
|
return (
|
<Table bordered dataSource={item.dataSource} columns={item.columns} pagination={{ hideOnSinglePage: true }} />
|
);
|
}),
|
);
|
|
Registry.addTag("table", TableModel, HtxTable);
|
Registry.addObjectType(TableModel);
|
|
export { HtxTable, TableModel };
|