Support table line item in the prebuilt page. (#889)

* Extract regional table.

* Support tootip in tableView component.

* Support table item in prebuilt page.

* update comment.

* Fix lint error.

* Fix lint error.

* Remove console.log and tune style a bit.

Co-authored-by: Buddha Wang <scwang0103@gmail.com>
This commit is contained in:
SimoTw 2021-03-10 12:57:33 +08:00 коммит произвёл GitHub
Родитель 60dcc3b57f
Коммит 19e00bff6d
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
4 изменённых файлов: 388 добавлений и 246 удалений

Просмотреть файл

@ -0,0 +1,176 @@
import React from "react";
export interface IRegionalTableState { }
export interface IRegionalTableProps {
regionalTableToView?: any;
tableTagColor: string;
onMouseEnter: (rowName: string, columnName: string) => void;
onMouseLeave: () => void;
}
export default class RegionalTable extends React.Component<IRegionalTableProps, IRegionalTableState> {
makeOnMouseEnter = (rowName, columnName) => () => {
this.props.onMouseEnter(rowName, columnName);
}
onMouseLeave = () => {
this.props.onMouseLeave();
}
private displayRegionalTable = (regionalTableToView) => {
const tableBody = [];
if (regionalTableToView?.type === "array") {
const columnHeaderRow = [];
const colKeys = Object.keys(regionalTableToView?.valueArray?.[0]?.valueObject || {});
if (colKeys.length === 0) {
return (
<div>
<h5 className="mb-4 ml-2 mt-2 pb-1">
<span style={{ borderBottom: `4px solid ${this.props.tableTagColor}` }}>Table name: {regionalTableToView.fieldName}</span>
</h5>
<div className="table-view-container">
<table>
<tbody>
Empty table
</tbody>
</table>
</div>
</div>
);
}
for (let i = 0; i < colKeys.length + 1; i++) {
if (i === 0) {
columnHeaderRow.push(
<th key={i} className={"empty_header hidden"} />
);
} else {
columnHeaderRow.push(
<th key={i} className={"column_header"}>
{colKeys[i - 1]}
</th>
);
}
}
tableBody.push(<tr key={0}>{columnHeaderRow}</tr>);
regionalTableToView?.valueArray?.forEach((row, rowIndex) => {
const rowName = `#${rowIndex}`;
const tableRow = [];
tableRow.push(
<th key={0} className={"row_header hidden"}>
{rowName}
</th>
);
Object.keys(row?.valueObject).forEach((columnName, columnIndex) => {
const tableCell = row?.valueObject?.[columnName];
tableRow.push(
<td
className={"table-cell"}
key={columnIndex + 1}
onMouseEnter={this.makeOnMouseEnter(rowName, columnName)}
onMouseLeave={this.onMouseLeave}
>
{tableCell ? tableCell.text : null}
</td>
);
})
tableBody.push(<tr key={(rowIndex + 1)}>{tableRow}</tr>);
})
} else {
const columnHeaderRow = [];
const colKeys = Object.keys(regionalTableToView?.valueObject?.[Object.keys(regionalTableToView?.valueObject)?.[0]]?.valueObject || {});
if (colKeys.length === 0) {
return (
<div>
<h5 className="mb-4 ml-2 mt-2 pb-1">
<span style={{ borderBottom: `4px solid ${this.props.tableTagColor}` }}>Table name: {regionalTableToView.fieldName}</span>
</h5>
<div className="table-view-container">
<table>
<tbody>
Empty table
</tbody>
</table>
</div>
</div>
);
}
for (let i = 0; i < colKeys.length + 1; i++) {
if (i === 0) {
columnHeaderRow.push(
<th key={i} className={"empty_header hidden"} />
);
} else {
columnHeaderRow.push(
<th key={i} className={"column_header"}>
{colKeys[i - 1]}
</th>
);
}
}
tableBody.push(<tr key={0}>{columnHeaderRow}</tr>);
Object.keys(regionalTableToView?.valueObject).forEach((rowName, index) => {
const tableRow = [];
tableRow.push(
<th key={0} className={"row_header"}>
{rowName}
</th>
);
if (regionalTableToView?.valueObject?.[rowName]) {
Object.keys(regionalTableToView?.valueObject?.[rowName]?.valueObject)?.forEach((columnName, index) => {
const tableCell = regionalTableToView?.valueObject?.[rowName]?.valueObject?.[columnName];
tableRow.push(
<td
className={"table-cell"}
key={index + 1}
onMouseEnter={() => {
this.setState({ highlightedTableCellRowKey: rowName, highlightedTableCellColumnKey: columnName })
}}
onMouseLeave={() => {
this.setState({ highlightedTableCellRowKey: null, highlightedTableCellColumnKey: null })
}}
>
{tableCell ? tableCell.text : null}
</td>
);
});
} else {
colKeys.forEach((columnName, index) => {
tableRow.push(
<td
className={"table-cell"}
key={index + 1}
>
{null}
</td>
);
})
}
tableBody.push(<tr key={index + 1}>{tableRow}</tr>);
});
}
return (
<div>
<h5 className="mb-4 ml-2 mt-2 pb-1">
<span style={{ borderBottom: `4px solid ${this.props.tableTagColor}` }}>Table name: {regionalTableToView.fieldName}</span>
</h5>
<div className="table-view-container">
<table>
<tbody>
{tableBody}
</tbody>
</table>
</div>
</div>
);
}
render() {
return (
<>
{this.displayRegionalTable(this.props.regionalTableToView)}
</>
);
}
}

Просмотреть файл

@ -2,13 +2,40 @@ import * as React from "react";
import { ICustomizations, Customizer, ContextualMenu, IDragOptions, Modal, FontIcon } from "@fluentui/react"; import { ICustomizations, Customizer, ContextualMenu, IDragOptions, Modal, FontIcon } from "@fluentui/react";
import { getDarkGreyTheme } from "../../../../common/themes"; import { getDarkGreyTheme } from "../../../../common/themes";
import "./tableView.scss"; import "./tableView.scss";
import { TooltipHost, TooltipDelay, DirectionalHint, ITooltipProps, ITooltipHostStyles } from "@fluentui/react";
import { useId } from '@uifabric/react-hooks';
function Tooltip({ children, content }) {
const makeTooltipProps = (content: object): ITooltipProps => ({
onRenderContent: () => (
<ul style={{ margin: 10, padding: 0 }}>
{Object.keys(content).map((key, index) => content[key] ? <li key={index}>{`${key}: ${content[key]}`}</li> : null)}
</ul>
),
});
const hostStyles: Partial<ITooltipHostStyles> = { root: { display: 'inline-block' } };
const tooltipId = useId('tooltip');
const tooltipProps = makeTooltipProps(content);
return (
<TooltipHost
delay={TooltipDelay.zero}
directionalHint={DirectionalHint.topCenter}
id={tooltipId}
tooltipProps={tooltipProps}
styles={hostStyles}
>
{children}
</TooltipHost>
)
}
interface ITableViewProps { interface ITableViewProps {
handleTableViewClose: () => any; handleTableViewClose: () => any;
tableToView: object; tableToView: object;
showToolTips?: boolean;
} }
export const TableView: React.FunctionComponent<ITableViewProps> = (props) => { export const TableView: React.FunctionComponent<ITableViewProps> = ({ handleTableViewClose, tableToView, showToolTips = false }) => {
const dark: ICustomizations = { const dark: ICustomizations = {
settings: { settings: {
theme: getDarkGreyTheme(), theme: getDarkGreyTheme(),
@ -20,49 +47,52 @@ export const TableView: React.FunctionComponent<ITableViewProps> = (props) => {
moveMenuItemText: "Move", moveMenuItemText: "Move",
closeMenuItemText: "Close", closeMenuItemText: "Close",
menu: ContextualMenu, menu: ContextualMenu,
}; };
function getTableBody() { function getTableBody() {
const table = props.tableToView; const table = tableToView;
let tableBody = null; let tableBody = null;
if (table !== null) { if (table !== null) {
tableBody = []; tableBody = [];
const rows = table["rows"]; const rows = table["rows"];
// const columns = table["columns"];
for (let i = 0; i < rows; i++) { for (let i = 0; i < rows; i++) {
const tableRow = []; const tableRow = [];
tableBody.push(<tr key={i}>{tableRow}</tr>); tableBody.push(<tr key={i}>{tableRow}</tr>);
} }
table["cells"].forEach((cell) => { table["cells"].forEach(({ rowIndex, columnIndex, rowSpan, colSpan, text, confidence }) => {
const rowIndex = cell["rowIndex"]; const content = { confidence: confidence || null };
const columnIndex = cell["columnIndex"]; const hasContentValue = Object.values(content).reduce((hasValue, value) => value || hasValue, false);
const rowSpan = cell["rowSpan"]; tableBody[rowIndex]["props"]["children"][columnIndex] = (
const colSpan = cell["columnSpan"];
tableBody[rowIndex]["props"]["children"][columnIndex] =
<td key={columnIndex} colSpan={colSpan} rowSpan={rowSpan}> <td key={columnIndex} colSpan={colSpan} rowSpan={rowSpan}>
{cell["text"]} {showToolTips && hasContentValue ? (
</td>; <Tooltip content={content}>
{text}
</Tooltip>
) : (
<React.Fragment>{text}</React.Fragment>
)}
</td>
)
}); });
} }
return tableBody; return tableBody;
} }
return ( return (
<Customizer {...dark}> <Customizer {...dark}>
<Modal <Modal
titleAriaId={"Table view"} titleAriaId={"Table view"}
isOpen={props.tableToView !== null} isOpen={tableToView !== null}
isModeless={false} isModeless={false}
isDarkOverlay={true} isDarkOverlay={true}
dragOptions={dragOptions} dragOptions={dragOptions}
onDismiss={props.handleTableViewClose} onDismiss={handleTableViewClose}
scrollableContentClassName={"table-view-scrollable-content"} scrollableContentClassName={"table-view-scrollable-content"}
> >
<FontIcon <FontIcon
className="close-modal" className="close-modal"
role="button" role="button"
iconName="Cancel" iconName="Cancel"
onClick={props.handleTableViewClose} onClick={handleTableViewClose}
/> />
<div className="table-view-container"> <div className="table-view-container">
<table className="viewed-table"> <table className="viewed-table">

Просмотреть файл

@ -38,7 +38,7 @@ import PreventLeaving from "../../common/preventLeaving/preventLeaving";
import { CanvasCommandBar } from "../editorPage/canvasCommandBar"; import { CanvasCommandBar } from "../editorPage/canvasCommandBar";
import { TableView } from "../editorPage/tableView"; import { TableView } from "../editorPage/tableView";
import "../predict/predictPage.scss"; import "../predict/predictPage.scss";
import PredictResult from "../predict/predictResult"; import PredictResult, { ITableResultItem } from "../predict/predictResult";
import { ILoadFileHelper, ILoadFileResult, LoadFileHelper } from "./LoadFileHelper"; import { ILoadFileHelper, ILoadFileResult, LoadFileHelper } from "./LoadFileHelper";
import "./prebuiltPredictPage.scss"; import "./prebuiltPredictPage.scss";
import { ITableHelper, ITableState, TableHelper } from "./tableHelper"; import { ITableHelper, ITableState, TableHelper } from "./tableHelper";
@ -83,6 +83,10 @@ export interface IPrebuiltPredictPageState extends ILoadFileResult, ITableState
predictionEndpointUrl: string; predictionEndpointUrl: string;
liveMode: boolean; liveMode: boolean;
viewRegionalTable?: boolean;
regionalTableToView?: any;
tableTagColor?: string;
} }
function mapStateToProps(state: IApplicationState) { function mapStateToProps(state: IApplicationState) {
@ -158,6 +162,10 @@ export class PrebuiltPredictPage extends React.Component<IPrebuiltPredictPagePro
predictionEndpointUrl: "", predictionEndpointUrl: "",
liveMode: true, liveMode: true,
viewRegionalTable: false,
regionalTableToView: null,
tableTagColor: null,
}; };
private analyzeResults: any; private analyzeResults: any;
@ -203,7 +211,7 @@ export class PrebuiltPredictPage extends React.Component<IPrebuiltPredictPagePro
if (prevState.highlightedField !== this.state.highlightedField) { if (prevState.highlightedField !== this.state.highlightedField) {
this.setPredictedFieldHighlightStatus(this.state.highlightedField); this.setPredictedFieldHighlightStatus(this.state.highlightedField);
} }
} }
if (_prevProps.prebuiltSettings !== this.props.prebuiltSettings) { if (_prevProps.prebuiltSettings !== this.props.prebuiltSettings) {
this.handleUpdateRequestURI(); this.handleUpdateRequestURI();
@ -229,7 +237,7 @@ export class PrebuiltPredictPage extends React.Component<IPrebuiltPredictPagePro
!this.state.fileLoaded || !this.state.fileLoaded ||
(this.state.withPageRange && !this.state.pageRangeIsValid) || (this.state.withPageRange && !this.state.pageRangeIsValid) ||
(needEndPoint && (!this.props.prebuiltSettings?.apiKey || (needEndPoint && (!this.props.prebuiltSettings?.apiKey ||
!this.props.prebuiltSettings?.serviceURI)); !this.props.prebuiltSettings?.serviceURI));
} }
public render() { public render() {
@ -372,12 +380,20 @@ export class PrebuiltPredictPage extends React.Component<IPrebuiltPredictPagePro
onPredictionClick={this.onPredictionClick} onPredictionClick={this.onPredictionClick}
onPredictionMouseEnter={this.onPredictionMouseEnter} onPredictionMouseEnter={this.onPredictionMouseEnter}
onPredictionMouseLeave={this.onPredictionMouseLeave} onPredictionMouseLeave={this.onPredictionMouseLeave}
onTablePredictionClick={this.onTablePredictionClick}
/> />
} }
{ {
(Object.keys(predictions).length === 0 && this.state.predictionLoaded) && (Object.keys(predictions).length === 0 && this.state.predictionLoaded) &&
<div>{strings.prebuiltPredict.noFieldCanBeExtracted}</div> <div>{strings.prebuiltPredict.noFieldCanBeExtracted}</div>
} }
{this.state.viewRegionalTable &&
<TableView
handleTableViewClose={this.onTablePredictionClose}
tableToView={this.state.regionalTableToView}
showToolTips={true}
/>
}
</div> </div>
</div> </div>
<Alert <Alert
@ -462,7 +478,7 @@ export class PrebuiltPredictPage extends React.Component<IPrebuiltPredictPagePro
} }
if (data.file) { if (data.file) {
HtmlFileReader.readAsText(data.file) HtmlFileReader.readAsText(data.file)
.then(({ content }) => JSON.parse(content as string)) .then(({ content }) => JSON.parse(content as string))
.then(result => this.setState({ .then(result => this.setState({
currentPage: 1, currentPage: 1,
analyzeResult: null, analyzeResult: null,
@ -470,7 +486,7 @@ export class PrebuiltPredictPage extends React.Component<IPrebuiltPredictPagePro
fileLoaded: false, fileLoaded: false,
}, () => new Promise(() => this.handlePredictionResult(result)) }, () => new Promise(() => this.handlePredictionResult(result))
.catch(this.handlePredictionError))) .catch(this.handlePredictionError)))
} }
} }
handleLiveModeToggleChange = (event, checked: boolean) => { handleLiveModeToggleChange = (event, checked: boolean) => {
@ -689,22 +705,22 @@ export class PrebuiltPredictPage extends React.Component<IPrebuiltPredictPagePro
} }
private handlePredictionError = (error) => { private handlePredictionError = (error) => {
let alertMessage = ""; let alertMessage = "";
if (error.response) { if (error.response) {
alertMessage = error.response.data; alertMessage = error.response.data;
} else if (error.errorCode === ErrorCode.PredictWithoutTrainForbidden) { } else if (error.errorCode === ErrorCode.PredictWithoutTrainForbidden) {
alertMessage = strings.errors.predictWithoutTrainForbidden.message; alertMessage = strings.errors.predictWithoutTrainForbidden.message;
} else if (error.errorCode === ErrorCode.ModelNotFound) { } else if (error.errorCode === ErrorCode.ModelNotFound) {
alertMessage = error.message; alertMessage = error.message;
} else { } else {
alertMessage = interpolate(strings.errors.endpointConnectionError.message, { endpoint: "form recognizer backend URL" }); alertMessage = interpolate(strings.errors.endpointConnectionError.message, { endpoint: "form recognizer backend URL" });
} }
this.setState({ this.setState({
shouldShowAlert: true, shouldShowAlert: true,
alertTitle: "Prediction Failed", alertTitle: "Prediction Failed",
alertMessage, alertMessage,
isPredicting: false, isPredicting: false,
}); });
} }
private handleClick = () => { private handleClick = () => {
@ -810,72 +826,89 @@ export class PrebuiltPredictPage extends React.Component<IPrebuiltPredictPagePro
private drawPredictionResult = (): void => { private drawPredictionResult = (): void => {
// Comment this line to prevent clear OCR boundary boxes. // Comment this line to prevent clear OCR boundary boxes.
// this.imageMap.removeAllFeatures(); // this.imageMap.removeAllFeatures();
const createFeature = (fieldName, field) => {
if (Array.isArray(field)) {
field.forEach(field => createFeature(fieldName, field))
} else {
if (_.get(field, "page", null) === this.state.currentPage) {
const text = fieldName;
const boundingbox = _.get(field, "boundingBox", []);
const feature = this.createBoundingBoxVectorFeature(text, boundingbox, imageExtent, ocrExtent);
features.push(feature);
}
}
}
const features = []; const features = [];
const imageExtent = [0, 0, this.state.imageWidth, this.state.imageHeight]; const imageExtent = [0, 0, this.state.imageWidth, this.state.imageHeight];
const ocrForCurrentPage: any = this.getOcrFromAnalyzeResult(this.state.analyzeResult)[this.state.currentPage - 1]; const ocrForCurrentPage: any = this.getOcrFromAnalyzeResult(this.state.analyzeResult)[this.state.currentPage - 1];
const ocrExtent = [0, 0, ocrForCurrentPage.width, ocrForCurrentPage.height]; const ocrExtent = [0, 0, ocrForCurrentPage.width, ocrForCurrentPage.height];
const predictions = this.getPredictionsFromAnalyzeResult(this.state.analyzeResult); const predictions = this.flatFields(this.getPredictionsFromAnalyzeResult(this.state.analyzeResult));
for (const fieldName of Object.keys(predictions)) { for (const [fieldName, field] of Object.entries(predictions)) {
const field = predictions[fieldName]; createFeature(fieldName, field);
if (_.get(field, "page", null) === this.state.currentPage) {
const text = fieldName;
const boundingbox = _.get(field, "boundingBox", []);
const feature = this.createBoundingBoxVectorFeature(text, boundingbox, imageExtent, ocrExtent);
features.push(feature);
}
} }
this.imageMap.addFeatures(features); this.imageMap.addFeatures(features);
this.tableHelper.drawTables(this.state.currentPage); this.tableHelper.drawTables(this.state.currentPage);
} }
private getPredictionsFromAnalyzeResult(analyzeResult: any) { private flatFields = (fields: object = {}): { [key: string]: (object[] | object) } => {
if (analyzeResult) { /**
const documentResults = _.get(analyzeResult, "documentResults", []); * @param fields: primitive types, object types likes array, object, and root level field
const isSupportField = fieldName => { * @return flattenfields, a field props or an array of field props
// Define list of unsupported field names. */
const blockedFieldNames = ["ReceiptType"]; const flattedFields = {};
return blockedFieldNames.indexOf(fieldName) === -1; const isSupportField = fieldName => {
} // Define list of unsupported field names.
const isRootItemObject = obj => obj.hasOwnProperty("text"); const blockedFieldNames = ["ReceiptType"];
// flat fieldProps of type "array" and "object", and extract root level field props in "object" type return blockedFieldNames.indexOf(fieldName) === -1;
const flattedFields = {}; }
const flatFields = (fields = {}) => { const isRootItemObject = obj => obj.hasOwnProperty("text");
const flatFieldProps = (displayName, fieldProps) => { // flat fieldProps of type "array" and "object", and extract root level field props in "object" type
if (isSupportField(displayName)) { const flatFieldProps = (fieldName, fieldProps) => {
switch (_.get(fieldProps, "type", "")) { if (isSupportField(fieldName)) {
case "array": { switch (_.get(fieldProps, "type", "")) {
const valueArray = _.get(fieldProps, "valueArray", []); case "array": {
for (const [index, valueArrayItem] of valueArray.entries()) { const valueArray = _.get(fieldProps, "valueArray", []);
flatFieldProps(`${displayName} ${index + 1}`, valueArrayItem); for (const arrayItem of valueArray) {
} flatFieldProps(fieldName, arrayItem);
break; }
} break;
case "object": { }
// root level field props case "object": {
const { type, valueObject, ...restProps } = fieldProps; // root level field props
if (isRootItemObject(restProps)) { const { type, valueObject, ...restProps } = fieldProps;
flatFieldProps(displayName, restProps); if (isRootItemObject(restProps)) {
} flatFieldProps(fieldName, restProps);
for (const [fieldName, objFieldProps] of Object.entries(fieldProps.valueObject)) { }
flatFieldProps(`${displayName}: ${fieldName}`, objFieldProps); for (const objFieldProps of Object.values(fieldProps.valueObject)) {
} if (objFieldProps) {
break; flatFieldProps(fieldName, objFieldProps);
}
default: {
flattedFields[displayName] = fieldProps;
} }
} }
break;
}
default: {
if (flattedFields[fieldName] == null) {
flattedFields[fieldName] = fieldProps;
}
else if (Array.isArray(flattedFields[fieldName])) {
flattedFields[fieldName].push(fieldProps)
} else {
flattedFields[fieldName] = [flattedFields[fieldName], fieldProps];
}
} }
} }
for (const [fieldName, fieldProps] of Object.entries(fields)) {
flatFieldProps(fieldName, fieldProps);
}
} }
for (const documentResult of documentResults) { }
const fields = documentResult["fields"]; for (const [fieldName, fieldProps] of Object.entries(fields)) {
flatFields(fields); flatFieldProps(fieldName, fieldProps);
} }
return flattedFields; return flattedFields;
}
private getPredictionsFromAnalyzeResult(analyzeResult: any) {
if (analyzeResult) {
const documentResults = _.get(analyzeResult, "documentResults", []);
return documentResults.reduce((accFields, documentResult) => ({ ...accFields, ...documentResult.fields }), {});
} else { } else {
return {}; return {};
} }
@ -997,4 +1030,45 @@ export class PrebuiltPredictPage extends React.Component<IPrebuiltPredictPagePro
predictionEndpointUrl: newValue predictionEndpointUrl: newValue
}); });
} }
private onTablePredictionClick = (predictedItem: ITableResultItem, tagColor: string) => {
const makeTable = (clickedFieldName) => {
function Cell(rowIndex, columnIndex, text = null, confidence = null) {
this.rowIndex = rowIndex;
this.columnIndex = columnIndex;
this.text = text;
this.confidence = confidence;
}
const valueArray = clickedFieldName.valueArray || [];
const columnNames = Object.keys(valueArray[0].valueObject);
const columnHeaders = function makeColumnHeaders() {
const indexColumn = new Cell(0, 0, "");
const contentColumns = columnNames.map((columnName, columnIndex) => new Cell(0, columnIndex + 1, columnName));
return [indexColumn, ...contentColumns];
}()
const matrix: any[] = [columnHeaders];
for (let i = 0; i < valueArray.length; i++) {
const valueObject = valueArray[i].valueObject || {};
const indexColumn = new Cell(i + 1, 0, `#${i + 1}`);
const contentColumns = columnNames.map((columnName, columnIndex) => {
const { text, confidence } = valueObject[columnName] || {};
return new Cell(i + 1, columnIndex + 1, text, confidence);
});
matrix.push([indexColumn, ...contentColumns]);
}
const flattenCells = matrix.reduce((cells, row) => cells = [...cells, ...row], []);
return { cells: flattenCells, columns: matrix[0].length, rows: matrix.length, fieldName: clickedField, tagColor };
}
const predictions = this.getPredictionsFromAnalyzeResult(this.state.analyzeResult)
const clickedFieldName = predictedItem?.fieldName;
const clickedField = predictions[clickedFieldName];
const regionalTableToView = makeTable(clickedField);
this.setState({ viewRegionalTable: true, regionalTableToView });
}
private onTablePredictionClose = () => {
this.setState({ viewRegionalTable: false, regionalTableToView: null })
}
} }

Просмотреть файл

@ -44,6 +44,7 @@ import "./predictPage.scss";
import PredictResult, { IAnalyzeModelInfo, ITableResultItem } from "./predictResult"; import PredictResult, { IAnalyzeModelInfo, ITableResultItem } from "./predictResult";
import RecentModelsView from "./recentModelsView"; import RecentModelsView from "./recentModelsView";
import { UploadToTrainingSetView } from "./uploadToTrainingSetView"; import { UploadToTrainingSetView } from "./uploadToTrainingSetView";
import RegionalTable from "../../common/regionalTable/regionalTable";
pdfjsLib.GlobalWorkerOptions.workerSrc = constants.pdfjsWorkerSrc(pdfjsLib.version); pdfjsLib.GlobalWorkerOptions.workerSrc = constants.pdfjsWorkerSrc(pdfjsLib.version);
@ -441,7 +442,12 @@ export default class PredictPage extends React.Component<IPredictPageProps, IPre
{this.state.viewRegionalTable && {this.state.viewRegionalTable &&
<div className="m-2"> <div className="m-2">
<h4 className="ml-1 mb-4">View analyzed Table</h4> <h4 className="ml-1 mb-4">View analyzed Table</h4>
{this.displayRegionalTable(this.state.regionalTableToView)} <RegionalTable
regionalTableToView={this.state.regionalTableToView}
tableTagColor={this.state.tableTagColor}
onMouseEnter={this.onMouseEnter}
onMouseLeave={this.onMouseLeave}
/>
<PrimaryButton <PrimaryButton
className="mt-4 ml-2" className="mt-4 ml-2"
theme={getPrimaryGreyTheme()} theme={getPrimaryGreyTheme()}
@ -1072,158 +1078,6 @@ export default class PredictPage extends React.Component<IPredictPageProps, IPre
this.setState({ viewRegionalTable: true, regionalTableToView: predictedItem, tableTagColor: tagColor }); this.setState({ viewRegionalTable: true, regionalTableToView: predictedItem, tableTagColor: tagColor });
} }
private displayRegionalTable = (regionalTableToView) => {
const tableBody = [];
if (regionalTableToView?.type === "array") {
const columnHeaderRow = [];
const colKeys = Object.keys(regionalTableToView?.valueArray?.[0]?.valueObject || {});
if (colKeys.length === 0) {
return (
<div>
<h5 className="mb-4 ml-2 mt-2 pb-1">
<span style={{ borderBottom: `4px solid ${this.state.tableTagColor}` }}>Table name: {regionalTableToView.fieldName}</span>
</h5>
<div className="table-view-container">
<table>
<tbody>
Empty table
</tbody>
</table>
</div>
</div>
);
}
for (let i = 0; i < colKeys.length + 1; i++) {
if (i === 0) {
columnHeaderRow.push(
<th key={i} className={"empty_header hidden"} />
);
} else {
columnHeaderRow.push(
<th key={i} className={"column_header"}>
{colKeys[i - 1]}
</th>
);
}
}
tableBody.push(<tr key={0}>{columnHeaderRow}</tr>);
regionalTableToView?.valueArray?.forEach((row, rowIndex) => {
const tableRow = [];
tableRow.push(
<th key={0} className={"row_header hidden"}>
{"#" + rowIndex}
</th>
);
Object.keys(row?.valueObject).forEach((columnName, columnIndex) => {
const tableCell = row?.valueObject?.[columnName];
tableRow.push(
<td
className={"table-cell"}
key={columnIndex + 1}
onMouseEnter={() => {
this.setState({ highlightedTableCellRowKey: "#" + rowIndex, highlightedTableCellColumnKey: columnName })
}}
onMouseLeave={() => {
this.setState({ highlightedTableCellRowKey: null, highlightedTableCellColumnKey: null })
}}
>
{tableCell ? tableCell.text : null}
</td>
);
})
tableBody.push(<tr key={(rowIndex + 1)}>{tableRow}</tr>);
})
} else {
const columnHeaderRow = [];
const colKeys = Object.keys(regionalTableToView?.valueObject?.[Object.keys(regionalTableToView?.valueObject)?.[0]]?.valueObject || {});
if (colKeys.length === 0) {
return (
<div>
<h5 className="mb-4 ml-2 mt-2 pb-1">
<span style={{ borderBottom: `4px solid ${this.state.tableTagColor}` }}>Table name: {regionalTableToView.fieldName}</span>
</h5>
<div className="table-view-container">
<table>
<tbody>
Empty table
</tbody>
</table>
</div>
</div>
);
}
for (let i = 0; i < colKeys.length + 1; i++) {
if (i === 0) {
columnHeaderRow.push(
<th key={i} className={"empty_header hidden"} />
);
} else {
columnHeaderRow.push(
<th key={i} className={"column_header"}>
{colKeys[i - 1]}
</th>
);
}
}
tableBody.push(<tr key={0}>{columnHeaderRow}</tr>);
Object.keys(regionalTableToView?.valueObject).forEach((rowName, index) => {
const tableRow = [];
tableRow.push(
<th key={0} className={"row_header"}>
{rowName}
</th>
);
if (regionalTableToView?.valueObject?.[rowName]) {
Object.keys(regionalTableToView?.valueObject?.[rowName]?.valueObject)?.forEach((columnName, index) => {
const tableCell = regionalTableToView?.valueObject?.[rowName]?.valueObject?.[columnName];
tableRow.push(
<td
className={"table-cell"}
key={index + 1}
onMouseEnter={() => {
this.setState({ highlightedTableCellRowKey: rowName, highlightedTableCellColumnKey: columnName })
}}
onMouseLeave={() => {
this.setState({ highlightedTableCellRowKey: null, highlightedTableCellColumnKey: null })
}}
>
{tableCell ? tableCell.text : null}
</td>
);
});
} else {
colKeys.forEach((columnName, index) => {
tableRow.push(
<td
className={"table-cell"}
key={index + 1}
>
{null}
</td>
);
})
}
tableBody.push(<tr key={index + 1}>{tableRow}</tr>);
});
}
return (
<div>
<h5 className="mb-4 ml-2 mt-2 pb-1">
<span style={{ borderBottom: `4px solid ${this.state.tableTagColor}` }}>Table name: {regionalTableToView.fieldName}</span>
</h5>
<div className="table-view-container">
<table>
<tbody>
{tableBody}
</tbody>
</table>
</div>
</div>
);
}
private onPredictionMouseEnter = (predictedItem: any) => { private onPredictionMouseEnter = (predictedItem: any) => {
this.setState({ this.setState({
highlightedField: predictedItem.fieldName ?? "", highlightedField: predictedItem.fieldName ?? "",
@ -1376,4 +1230,12 @@ export default class PredictPage extends React.Component<IPredictPageProps, IPre
this.props.appTitleActions.setTitle(project.name); this.props.appTitleActions.setTitle(project.name);
} }
} }
private onMouseEnter = (rowName: string, columnName: string) => {
this.setState({ highlightedTableCellRowKey: rowName, highlightedTableCellColumnKey: columnName });
}
private onMouseLeave = () => {
this.setState({ highlightedTableCellRowKey: null, highlightedTableCellColumnKey: null });
}
} }