Merge branch 'master' into alex-krasn/fix-onprem-network-error-message
This commit is contained in:
Коммит
921541e7cb
|
@ -38,3 +38,4 @@ secrets.sh
|
|||
es6-src/
|
||||
report/
|
||||
debug.log
|
||||
src/git-commit-info.txt
|
||||
|
|
|
@ -173,6 +173,15 @@ Click on the Analyze icon on the left pane to open the Analyze page. Upload a fo
|
|||
|
||||
Tip: You can also run the Analyze API with a REST call. To learn how to do this, see [Train with labels using Python](https://docs.microsoft.com/en-us/azure/cognitive-services/form-recognizer/quickstarts/python-labeled-data).
|
||||
|
||||
#### Compose a model ####
|
||||
Click the Compose icon on the left pane to open the Compose page. FoTT will display the first page of your models—by decending order of Model ID—in a list. Select multiple models you want to compose into one model and click the **Compose** button. Once the new model has been composed, it's ready to analyze with.
|
||||
|
||||
![alt text](docs/images/compose.png "Compose")
|
||||
|
||||
To load more of your models, click the **Load next page** button at the bottom of the list. This will load the next page of your models by decending order of model ID.
|
||||
|
||||
You can sort the currently loaded models by clicking the column headers at the top of the list. Only the currently loaded models will be sorted. You will need to load all pages of your models first and then sort to view the complete sorted list of your models.
|
||||
|
||||
#### Save a project and resume later ####
|
||||
|
||||
To resume your project at another time or in another browser, you need to save your project's security token and reenter it later.
|
||||
|
|
Двоичный файл не отображается.
После Ширина: | Высота: | Размер: 354 KiB |
|
@ -43,7 +43,7 @@
|
|||
"redux": "^4.0.4",
|
||||
"redux-thunk": "^2.3.0",
|
||||
"rimraf": "^3.0.2",
|
||||
"serialize-javascript": "^3.0.0",
|
||||
"serialize-javascript": "^5.0.1",
|
||||
"shortid": "^2.2.15",
|
||||
"utif": "^3.1.0",
|
||||
"vott-react": "^0.2.12",
|
||||
|
@ -52,8 +52,8 @@
|
|||
"scripts": {
|
||||
"start": "env-cmd -f .env.electron nf start -p 3000",
|
||||
"compile": "tsc",
|
||||
"build": "react-scripts build",
|
||||
"react-start": "react-scripts start",
|
||||
"build": "node ./scripts/dump_git_info.js && react-scripts build",
|
||||
"react-start": "node ./scripts/dump_git_info.js && react-scripts start",
|
||||
"test": "react-scripts test --env=jsdom --silent",
|
||||
"eject": "react-scripts eject",
|
||||
"webpack:dev": "webpack --config ./config/webpack.dev.js",
|
||||
|
|
|
@ -0,0 +1,21 @@
|
|||
spawn = require('child_process').spawn,
|
||||
fs = require('fs');
|
||||
|
||||
git = spawn('git', ['log', '-1']),
|
||||
buf = Buffer.alloc(0);
|
||||
|
||||
git.stdout.on('data', (data) => {
|
||||
buf = Buffer.concat([buf, data])
|
||||
});
|
||||
|
||||
git.stderr.on('data', (data) => {
|
||||
console.log(data.toString());
|
||||
});
|
||||
|
||||
git.on('close', (code) => {
|
||||
fs.writeFile("src/git-commit-info.txt", buf.toString(), (err, data) => {
|
||||
if (err) {
|
||||
console.log(err);
|
||||
}
|
||||
});
|
||||
});
|
|
@ -0,0 +1,4 @@
|
|||
#!/bin/bash
|
||||
|
||||
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"
|
||||
git status && git log -1 > $DIR/../src/git-commit-info.txt || echo 'Not a Git repo. Continue...'
|
|
@ -148,6 +148,9 @@
|
|||
.ms-Icon-18px {
|
||||
font-size: 18px;
|
||||
}
|
||||
.ms-Icon-25px{
|
||||
font-size: 25px;
|
||||
}
|
||||
|
||||
.ms-Spinner-label {
|
||||
color: inherit;
|
||||
|
|
Различия файлов скрыты, потому что одна или несколько строк слишком длинны
|
@ -37,7 +37,9 @@ export const constants = {
|
|||
convertedThumbnailQuality: 0.2,
|
||||
recentModelRecordsCount: 5,
|
||||
apiModelsPath: `/formrecognizer/${apiVersion}/custom/models`,
|
||||
autoLabelBatchSize: 10,
|
||||
autoLabelBatchSizeMax: 10,
|
||||
autoLabelBatchSizeMin: 3,
|
||||
showOriginLabelsByDefault: true,
|
||||
|
||||
pdfjsWorkerSrc(version: string) {
|
||||
return `https://fotts.azureedge.net/npm/pdfjs-dist/${version}/pdf.worker.js`;
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
import { IAppStrings } from "../strings";
|
||||
import {IAppStrings} from "../strings";
|
||||
|
||||
/*eslint-disable no-template-curly-in-string, no-multi-str*/
|
||||
|
||||
|
@ -41,8 +41,8 @@ export const english: IAppStrings = {
|
|||
},
|
||||
openCloudProject: {
|
||||
title: "Open Cloud Project",
|
||||
selectConnection: "Select a Connection",
|
||||
pasteSharedUri: "Please paste shared project string here",
|
||||
selectConnection: "Open cloud project",
|
||||
pasteSharedUri: "Paste shared project token here",
|
||||
},
|
||||
recentProjects: "Recent Projects",
|
||||
deleteProject: {
|
||||
|
@ -131,7 +131,7 @@ export const english: IAppStrings = {
|
|||
downloadJson: "Download JSON file",
|
||||
trainConfirm: {
|
||||
title: "Labels not revised yet",
|
||||
message: "You have label files not yet revised, do you want to train with those files?"
|
||||
message: "There are newly auto-labeled files not yet revised by you, do you want to train with those files?"
|
||||
},
|
||||
errors: {
|
||||
electron: {
|
||||
|
@ -272,6 +272,10 @@ export const english: IAppStrings = {
|
|||
},
|
||||
toolbar: {
|
||||
add: "Add new tag",
|
||||
onlyShowCurrentPageTags: "Only show tags used in current page",
|
||||
showAllTags: "Show all tags",
|
||||
showOriginLabels:"Show origin labels",
|
||||
hideOriginLabels:"Hide origin labels",
|
||||
contextualMenu: "Contextual Menu",
|
||||
delete: "Delete tag",
|
||||
edit: "Edit tag",
|
||||
|
@ -449,10 +453,10 @@ export const english: IAppStrings = {
|
|||
additionalActions: {
|
||||
text: "Additional actions",
|
||||
subIMenuItems: {
|
||||
runOcrOnCurrentDocument: "Run OCR on current document",
|
||||
runOcrOnAllDocuments: "Run OCR on all documents",
|
||||
runOcrOnCurrentDocument: "Run Layout on current document",
|
||||
runOcrOnAllDocuments: "Run Layout on all documents",
|
||||
runAutoLabelingCurrentDocument: "Auto-label the current document",
|
||||
runAutoLabelingOnNotLabelingDocuments: "Auto-label next ${batchSize} unlabeled documents",
|
||||
runAutoLabelingOnMultipleUnlabeledDocuments: "Auto-label multiple unlabeled documents",
|
||||
noPredictModelOnProject: "Predict model not avaliable, please train the model first.",
|
||||
}
|
||||
}
|
||||
|
@ -529,7 +533,7 @@ export const english: IAppStrings = {
|
|||
},
|
||||
tips: {
|
||||
quickLabeling: {
|
||||
name: "Lable with hot keys",
|
||||
name: "Label with hot keys",
|
||||
description: "Hotkeys 1 through 0 and all letters are assigned to first 36 tags. After selecting one or multiple words, press tag's assigned hotkey.",
|
||||
},
|
||||
renameTag: {
|
||||
|
@ -700,13 +704,13 @@ export const english: IAppStrings = {
|
|||
shareProject: {
|
||||
name: "Share Project",
|
||||
errors: {
|
||||
cannotDecodeString: "Cannot decode shared string! Please, check if your string has been modified.",
|
||||
cannotDecodeString: "Cannot decode shared token. Check if shared token has been modified.",
|
||||
connectionNotFound: "Connection not found. Add shared project's connection to your connections.",
|
||||
noConnections: "Connection is required for project sharing",
|
||||
connectionRequirement: "Shared project's connection must be added before opening it",
|
||||
tokenNameExist: "Warning! You already have token with same name as in shared project. Please create a new token, and update the existing project which uses ''${sharedTokenName}'' with new token name."
|
||||
},
|
||||
copy: {
|
||||
success: "String for sharing your project has been saved to clipboard. In order to use it, paste it in appropriate section of the 'Open Cloud Project' popup.",
|
||||
success: "Project token copied to clipboard and ready to share. Reciever of project token can click 'Open Cloud Project' from the Home page to use shared token.",
|
||||
}
|
||||
},
|
||||
};
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
import { IAppStrings } from "../strings";
|
||||
import {IAppStrings} from "../strings";
|
||||
|
||||
/*eslint-disable no-template-curly-in-string, no-multi-str*/
|
||||
|
||||
|
@ -42,7 +42,7 @@ export const spanish: IAppStrings = {
|
|||
},
|
||||
openCloudProject: {
|
||||
title: "Abrir Proyecto de la Nube",
|
||||
selectConnection: "Select a Connection",
|
||||
selectConnection: "Proyecto de nube abierta",
|
||||
pasteSharedUri: "Pegue la cadena de proyecto compartida aquí",
|
||||
},
|
||||
deleteProject: {
|
||||
|
@ -132,7 +132,7 @@ export const spanish: IAppStrings = {
|
|||
downloadJson: "Descargar archivo JSON",
|
||||
trainConfirm: {
|
||||
title: "Etiquetas no revisadas todavía",
|
||||
message: "Tiene archivos de etiquetas que aún no han sido revisados, ¿desea entrenar con esos archivos?"
|
||||
message: "Hay archivos recientemente etiquetados automáticamente que aún no ha revisado, ¿desea entrenar con esos archivos?"
|
||||
},
|
||||
errors: {
|
||||
electron: {
|
||||
|
@ -271,6 +271,10 @@ export const spanish: IAppStrings = {
|
|||
},
|
||||
toolbar: {
|
||||
add: "Agregar nueva etiqueta",
|
||||
onlyShowCurrentPageTags: "Mostrar solo las etiquetas utilizadas en la página actual",
|
||||
showAllTags: "Mostrar todas las etiquetas",
|
||||
showOriginLabels: "Mostrar etiquetas de origen",
|
||||
hideOriginLabels: "Ocultar etiquetas de origen",
|
||||
contextualMenu: "Menú contextual",
|
||||
delete: "Borrar etiqueta",
|
||||
edit: "Editar etiqueta",
|
||||
|
@ -450,10 +454,10 @@ export const spanish: IAppStrings = {
|
|||
additionalActions: {
|
||||
text: "Acciones adicionales",
|
||||
subIMenuItems: {
|
||||
runOcrOnCurrentDocument: "Ejecutar OCR en el documento actual",
|
||||
runOcrOnAllDocuments: "Ejecute OCR en todos los documentos",
|
||||
runOcrOnCurrentDocument: "Ejecutar Layout en el documento actual",
|
||||
runOcrOnAllDocuments: "Ejecute Layout en todos los documentos",
|
||||
runAutoLabelingCurrentDocument: "Etiquetar automáticamente el documento actual",
|
||||
runAutoLabelingOnNotLabelingDocuments: "Etiquetar automáticamente los siguientes ${batchSize} documentos sin etiquetar",
|
||||
runAutoLabelingOnMultipleUnlabeledDocuments: "Etiquetar automáticamente varios documentos sin etiquetar",
|
||||
noPredictModelOnProject: "Predecir modelo no disponible, entrene el modelo primero.",
|
||||
}
|
||||
}
|
||||
|
@ -701,13 +705,13 @@ export const spanish: IAppStrings = {
|
|||
shareProject: {
|
||||
name: "Compartir proyecto",
|
||||
errors: {
|
||||
cannotDecodeString: "¡No se puede decodificar la cadena compartida! Por favor, verifique si su cadena ha sido modificada.",
|
||||
cannotDecodeString: "No se puede decodificar el token compartido. Compruebe si se ha modificado el token compartido.",
|
||||
connectionNotFound: "Conexión no encontrada. Agregue la conexión del proyecto compartido a sus conexiones.",
|
||||
noConnections: "Se requiere conexión para compartir proyectos",
|
||||
connectionRequirement: "La conexión del proyecto compartido debe agregarse antes de abrirlo",
|
||||
tokenNameExist: "¡Advertencia! Ya tiene token con el mismo nombre que en el proyecto compartido. Cree un nuevo token y actualice el proyecto existente que usa ''${sharedTokenName}'' con el nuevo nombre del token.",
|
||||
},
|
||||
copy: {
|
||||
success: "La cadena para compartir su proyecto se ha guardado en el portapapeles. Para usarlo, péguelo en la sección correspondiente de la ventana emergente 'Open Cloud Project'.",
|
||||
success: "Token de proyecto copiado al portapapeles y listo para compartir. El receptor del token del proyecto puede hacer clic en 'Abrir proyecto en la nube' desde la página de inicio para usar el token compartido.",
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
@ -246,6 +246,10 @@ export interface IAppStrings {
|
|||
}
|
||||
toolbar: {
|
||||
add: string,
|
||||
onlyShowCurrentPageTags:string,
|
||||
showAllTags:string,
|
||||
showOriginLabels: string
|
||||
hideOriginLabels: string,
|
||||
contextualMenu: string,
|
||||
delete: string,
|
||||
edit: string,
|
||||
|
@ -447,7 +451,7 @@ export interface IAppStrings {
|
|||
runOcrOnCurrentDocument: string,
|
||||
runOcrOnAllDocuments: string,
|
||||
runAutoLabelingCurrentDocument: string,
|
||||
runAutoLabelingOnNotLabelingDocuments: string,
|
||||
runAutoLabelingOnMultipleUnlabeledDocuments: string,
|
||||
noPredictModelOnProject: string,
|
||||
}
|
||||
}
|
||||
|
@ -589,7 +593,7 @@ export interface IAppStrings {
|
|||
errors: {
|
||||
cannotDecodeString: string,
|
||||
connectionNotFound: string,
|
||||
noConnections: string,
|
||||
connectionRequirement: string,
|
||||
tokenNameExist: string,
|
||||
},
|
||||
copy: {
|
||||
|
|
|
@ -74,6 +74,10 @@
|
|||
"name": "CircleRing",
|
||||
"unicode": "EA3A"
|
||||
},
|
||||
{
|
||||
"name": "ClearFilter",
|
||||
"unicode": "EF8F"
|
||||
},
|
||||
{
|
||||
"name": "Cloud",
|
||||
"unicode": "E753"
|
||||
|
@ -110,6 +114,14 @@
|
|||
"name": "Filter",
|
||||
"unicode": "E71C"
|
||||
},
|
||||
{
|
||||
"name": "GroupedList",
|
||||
"unicode": "EF74"
|
||||
},
|
||||
{
|
||||
"name": "GroupList",
|
||||
"unicode": "F168"
|
||||
},
|
||||
{
|
||||
"name": "Help",
|
||||
"unicode": "E897"
|
||||
|
|
|
@ -6,7 +6,7 @@ import path from "path";
|
|||
import rimraf from "rimraf";
|
||||
import { BrowserWindow, dialog } from "electron";
|
||||
import { IStorageProvider } from "../../../providers/storage/storageProviderFactory";
|
||||
import { IAsset, AssetState, AssetType, StorageType } from "../../../models/applicationState";
|
||||
import { IAsset, AssetState, AssetType, StorageType, ILabelData, AssetLabelingState } from "../../../models/applicationState";
|
||||
import { AssetService } from "../../../services/assetService";
|
||||
import { constants } from "../../../common/constants";
|
||||
import { strings } from "../../../common/strings";
|
||||
|
@ -180,6 +180,11 @@ export default class LocalFileSystem implements IStorageProvider {
|
|||
const ocrFileName = decodeURIComponent(`${file}${constants.ocrFileExtension}`);
|
||||
if (files.find((str) => str === labelFileName)) {
|
||||
asset.state = AssetState.Tagged;
|
||||
const json = await this.readText(labelFileName);
|
||||
const labelData = JSON.parse(json) as ILabelData;
|
||||
if (labelData) {
|
||||
asset.labelingState = labelData.labelingState || AssetLabelingState.ManuallyLabeled;
|
||||
}
|
||||
} else if (files.find((str) => str === ocrFileName)) {
|
||||
asset.state = AssetState.Visited;
|
||||
} else {
|
||||
|
|
|
@ -232,8 +232,10 @@ export interface ILabel {
|
|||
label: string,
|
||||
key?: IFormRegion[],
|
||||
value: IFormRegion[],
|
||||
originValue?: IFormRegion[],
|
||||
labelType?: string,
|
||||
confidence?: number,
|
||||
revised?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -110,7 +110,7 @@ export class AssetPreview extends React.Component<IAssetPreviewProps, IAssetPrev
|
|||
<div className="asset-loading">
|
||||
<div className="asset-loading-ocr-spinner">
|
||||
<Label className="p-0" ></Label>
|
||||
<Spinner size={SpinnerSize.small} label="Running OCR..." ariaLive="off" labelPosition="right" />
|
||||
<Spinner size={SpinnerSize.small} label="Running Layout..." ariaLive="off" labelPosition="right" />
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
|
|
@ -1,11 +1,15 @@
|
|||
.shared-string-input-container {
|
||||
padding: 1rem;
|
||||
.input-uri {
|
||||
padding: 1rem;
|
||||
padding: 1rem 1rem 0 1rem;
|
||||
.form-control{
|
||||
font-size: 90%;
|
||||
}
|
||||
}
|
||||
.error-message {
|
||||
color: #db7272;
|
||||
padding: 4px 1rem 1rem 1rem;
|
||||
}
|
||||
}
|
||||
|
||||
.separator {
|
||||
|
|
|
@ -3,13 +3,14 @@
|
|||
|
||||
import React from "react";
|
||||
import { toast } from "react-toastify";
|
||||
import { Button, Modal, ModalBody, ModalFooter, ModalHeader, InputGroup, Input } from "reactstrap";
|
||||
import { Modal, ModalBody, ModalFooter, ModalHeader, InputGroup, Input } from "reactstrap";
|
||||
import { strings, interpolate } from "../../../../common/strings";
|
||||
import { IConnection, StorageType, ErrorCode, AppError, ISecurityToken } from "../../../../models/applicationState";
|
||||
import { StorageProviderFactory } from "../../../../providers/storage/storageProviderFactory";
|
||||
import CondensedList, { ListItem } from "../condensedList/condensedList";
|
||||
import "./cloudFilePicker.scss"
|
||||
import { Separator } from "@fluentui/react";
|
||||
import { PrimaryButton, Separator } from "@fluentui/react";
|
||||
import { getPrimaryGreenTheme, getPrimaryGreyTheme } from "../../../../common/themes";
|
||||
|
||||
/**
|
||||
* Properties for Cloud File Picker
|
||||
|
@ -52,7 +53,6 @@ export interface ICloudFilePickerState {
|
|||
pastedUri: string;
|
||||
pasting: boolean;
|
||||
sharedStringData: ISharedStringData;
|
||||
haveCloudConnections: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -82,7 +82,6 @@ export class CloudFilePicker extends React.Component<ICloudFilePickerProps, IClo
|
|||
|
||||
public render() {
|
||||
const closeBtn = <button className="close" onClick={this.close}>×</button>;
|
||||
|
||||
return (
|
||||
<Modal isOpen={this.state.isOpen} centered={true}>
|
||||
<ModalHeader toggle={this.close} close={closeBtn}>
|
||||
|
@ -91,10 +90,10 @@ export class CloudFilePicker extends React.Component<ICloudFilePickerProps, IClo
|
|||
{!this.state.selectedConnection &&
|
||||
<>
|
||||
<div className={"shared-string-input-container"}>
|
||||
<div className="condensed-list-header bg-darker-2 shared-uri-header">Shared Project String</div>
|
||||
{!this.state.haveCloudConnections &&
|
||||
<div className="p-3 text-center">{strings.shareProject.errors.noConnections}</div>
|
||||
}
|
||||
<div className="condensed-list-header bg-darker-2 shared-uri-header">Shared project token</div>
|
||||
<div className="input-uri">
|
||||
{strings.shareProject.errors.connectionRequirement}
|
||||
</div>
|
||||
<InputGroup className="input-uri">
|
||||
<Input placeholder={strings.homePage.openCloudProject.pasteSharedUri}
|
||||
id="sharedURI"
|
||||
|
@ -102,7 +101,6 @@ export class CloudFilePicker extends React.Component<ICloudFilePickerProps, IClo
|
|||
value={this.state.pastedUri}
|
||||
onChange={this.handleChangeUri}
|
||||
onPaste={this.handlePasteUri}
|
||||
disabled={!this.state.haveCloudConnections}
|
||||
/>
|
||||
</InputGroup>
|
||||
</div>
|
||||
|
@ -115,13 +113,28 @@ export class CloudFilePicker extends React.Component<ICloudFilePickerProps, IClo
|
|||
}
|
||||
<ModalFooter>
|
||||
{this.state.selectedFile || ""}
|
||||
<Button
|
||||
className="btn btn-success mr-1"
|
||||
{!this.state.okDisabled &&
|
||||
<PrimaryButton
|
||||
theme={getPrimaryGreenTheme()}
|
||||
className="mr-1 ml-2"
|
||||
onClick={this.ok}
|
||||
disabled={this.state.okDisabled}>Ok</Button>
|
||||
>
|
||||
Open
|
||||
</PrimaryButton>
|
||||
}
|
||||
{this.state.backDisabled && !this.state.pastedUri ?
|
||||
<Button onClick={this.close}>Close</Button> :
|
||||
<Button onClick={this.back}>Go Back</Button>
|
||||
<PrimaryButton
|
||||
onClick={this.close}
|
||||
theme={getPrimaryGreyTheme()}
|
||||
>
|
||||
Cancel
|
||||
</PrimaryButton> :
|
||||
<PrimaryButton
|
||||
onClick={this.back}
|
||||
theme={getPrimaryGreyTheme()}
|
||||
>
|
||||
Go Back
|
||||
</PrimaryButton>
|
||||
}
|
||||
</ModalFooter>
|
||||
</Modal>
|
||||
|
@ -161,7 +174,6 @@ export class CloudFilePicker extends React.Component<ICloudFilePickerProps, IClo
|
|||
pastedUri: "",
|
||||
pasting: false,
|
||||
sharedStringData: null,
|
||||
haveCloudConnections: cloudConnectionList.props.items.length > 0,
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -192,7 +204,7 @@ export class CloudFilePicker extends React.Component<ICloudFilePickerProps, IClo
|
|||
}
|
||||
|
||||
private getSharedConnection(connections: IConnection[], sasFolder: string) {
|
||||
const connection: IConnection = connections.find(({ providerOptions }) => providerOptions["sas"].includes(sasFolder));
|
||||
const connection: IConnection = connections?.find(({ providerOptions }) => providerOptions["sas"]?.includes(sasFolder));
|
||||
if (connection) {
|
||||
return connection;
|
||||
}
|
||||
|
@ -263,7 +275,7 @@ export class CloudFilePicker extends React.Component<ICloudFilePickerProps, IClo
|
|||
const fileList = await this.fileList(connection);
|
||||
this.setState({
|
||||
selectedConnection: connection,
|
||||
modalHeader: `Select a file from "${connection.name}"`,
|
||||
modalHeader: `Select a project from "${connection.name}"`,
|
||||
condensedList: fileList,
|
||||
backDisabled: false,
|
||||
});
|
||||
|
|
|
@ -33,8 +33,7 @@
|
|||
display: flex;
|
||||
flex-direction: column;
|
||||
position: relative;
|
||||
|
||||
&-container {
|
||||
&-container{
|
||||
overflow-x: visible;
|
||||
overflow-y: auto;
|
||||
padding: 0 0 0 100px;
|
||||
|
@ -43,12 +42,13 @@
|
|||
content: " ";
|
||||
display: inline-block;
|
||||
position: absolute;
|
||||
width: 80px;
|
||||
height: 100%;
|
||||
left: -80px;
|
||||
background: linear-gradient(to right, #00000000 0%,#000000 100%);
|
||||
top: 44px;
|
||||
width: 50px;
|
||||
height: calc(100% - 44px);
|
||||
left: -55px;
|
||||
margin-right: 4px;
|
||||
background: linear-gradient(to right, #00000000 10%,#00000080 80%);
|
||||
}
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -93,14 +93,20 @@
|
|||
&-2 {
|
||||
width: 100%;
|
||||
}
|
||||
.tag-item-confidence{
|
||||
position: absolute;
|
||||
line-height: 2em;
|
||||
left: -70PX;
|
||||
z-index: 900;
|
||||
text-align: right;
|
||||
width:50px;
|
||||
text-shadow: 1px 1px 1px #333;
|
||||
.tag-item-label-container {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
border: 1px solid $lighter-5;
|
||||
&-item1 {
|
||||
justify-content: center;
|
||||
align-self: center;
|
||||
width: 50px;
|
||||
text-align: center;
|
||||
}
|
||||
&-item2 {
|
||||
border-left: 1px solid $lighter-5;
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -128,14 +134,13 @@
|
|||
&-highlight {
|
||||
.tag-content {
|
||||
background-color: $lighter-5 !important;
|
||||
box-shadow:4px 4px 5px $lighter-5;
|
||||
box-shadow: 4px 4px 5px $lighter-5;
|
||||
}
|
||||
}
|
||||
&-label {
|
||||
min-height: 1em;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
margin-bottom: 5px;
|
||||
padding-left: 3px;
|
||||
font-size: 0.95em;
|
||||
}
|
||||
|
@ -203,13 +208,18 @@
|
|||
|
||||
&-item-label {
|
||||
color: #a0a0a0;
|
||||
}
|
||||
|
||||
&-item-label:hover {
|
||||
&:hover {
|
||||
color: #fff;
|
||||
background: $lighter-4;
|
||||
cursor: pointer;
|
||||
}
|
||||
&-origin {
|
||||
color: #a0a0a0;
|
||||
background-color: $darker-4;
|
||||
border-bottom: 1px solid $lighter-5;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
&-color {
|
||||
width: $tagColorWidth;
|
||||
|
|
|
@ -19,6 +19,7 @@ describe("Tag Input Component", () => {
|
|||
function createProps(tags?: ITag[], onChange?): ITagInputProps {
|
||||
return {
|
||||
tagsLoaded: true,
|
||||
pageNumber: 1,
|
||||
tags: tags || MockFactory.createTestTags(),
|
||||
lockedTags: [],
|
||||
selectedRegions: [MockFactory.createTestRegion()],
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
import React, { KeyboardEvent } from "react";
|
||||
import {
|
||||
ContextualMenu,
|
||||
ContextualMenuItemType,
|
||||
|
@ -10,20 +9,22 @@ import {
|
|||
IContextualMenuItem,
|
||||
ICustomizations,
|
||||
Spinner,
|
||||
SpinnerSize,
|
||||
SpinnerSize
|
||||
} from "@fluentui/react";
|
||||
import { strings, interpolate } from "../../../../common/strings";
|
||||
import { getDarkTheme } from "../../../../common/themes";
|
||||
import { AlignPortal } from "../align/alignPortal";
|
||||
import { getNextColor } from "../../../../common/utils";
|
||||
import { IRegion, ITag, ILabel, FieldType, FieldFormat, FeatureCategory } from "../../../../models/applicationState";
|
||||
import { ColorPicker } from "../colorPicker";
|
||||
import "./tagInput.scss";
|
||||
import "../condensedList/condensedList.scss";
|
||||
import TagInputItem, { ITagInputItemProps, ITagClickProps } from "./tagInputItem";
|
||||
import TagInputToolbar from "./tagInputToolbar";
|
||||
import { toast } from "react-toastify";
|
||||
import debounce from 'lodash/debounce';
|
||||
import React, {KeyboardEvent} from "react";
|
||||
import {toast} from "react-toastify";
|
||||
import {constants} from "../../../../common/constants";
|
||||
import {interpolate, strings} from "../../../../common/strings";
|
||||
import {getDarkTheme} from "../../../../common/themes";
|
||||
import {getNextColor} from "../../../../common/utils";
|
||||
import {FeatureCategory, FieldFormat, FieldType, ILabel, IRegion, ITag} from "../../../../models/applicationState";
|
||||
import {AlignPortal} from "../align/alignPortal";
|
||||
import {ColorPicker} from "../colorPicker";
|
||||
import "../condensedList/condensedList.scss";
|
||||
import "./tagInput.scss";
|
||||
import TagInputItem, {ITagClickProps, ITagInputItemProps} from "./tagInputItem";
|
||||
import TagInputToolbar from "./tagInputToolbar";
|
||||
// tslint:disable-next-line:no-var-requires
|
||||
const tagColors = require("../../common/tagColors.json");
|
||||
|
||||
|
@ -50,6 +51,8 @@ export interface ITagInputProps {
|
|||
selectedRegions?: IRegion[];
|
||||
/** The labels in the canvas */
|
||||
labels: ILabel[];
|
||||
/** The doc current page number */
|
||||
pageNumber: number;
|
||||
/** Tags that are currently locked for editing experience */
|
||||
lockedTags?: string[];
|
||||
/** Updates to locked tags */
|
||||
|
@ -83,6 +86,8 @@ export interface ITagInputState {
|
|||
tags: ITag[];
|
||||
tagOperation: TagOperationMode;
|
||||
addTags: boolean;
|
||||
onlyCurrentPageTags: boolean;
|
||||
showOriginLabels: boolean;
|
||||
searchTags: boolean;
|
||||
searchQuery: string;
|
||||
selectedTag: ITag;
|
||||
|
@ -109,7 +114,7 @@ function filterFormat(type: FieldType): FieldFormat[] {
|
|||
FieldFormat.YMD,
|
||||
];
|
||||
default:
|
||||
return [FieldFormat.NotSpecified];
|
||||
return [ FieldFormat.NotSpecified ];
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -131,6 +136,8 @@ export class TagInput extends React.Component<ITagInputProps, ITagInputState> {
|
|||
searchTags: this.props.showSearchBox,
|
||||
searchQuery: "",
|
||||
selectedTag: null,
|
||||
onlyCurrentPageTags: false,
|
||||
showOriginLabels: constants.showOriginLabelsByDefault,
|
||||
};
|
||||
|
||||
private tagItemRefs: Map<string, TagInputItem> = new Map<string, TagInputItem>();
|
||||
|
@ -156,7 +163,6 @@ export class TagInput extends React.Component<ITagInputProps, ITagInputState> {
|
|||
});
|
||||
}
|
||||
}
|
||||
|
||||
public render() {
|
||||
const dark: ICustomizations = {
|
||||
settings: {
|
||||
|
@ -165,7 +171,7 @@ export class TagInput extends React.Component<ITagInputProps, ITagInputState> {
|
|||
scopedSettings: {},
|
||||
};
|
||||
|
||||
const { selectedTag, tagOperation } = this.state;
|
||||
const {selectedTag, tagOperation} = this.state;
|
||||
const selectedTagRef = selectedTag ? this.tagItemRefs.get(selectedTag.name)?.getTagNameRef() : null;
|
||||
|
||||
return (
|
||||
|
@ -174,7 +180,9 @@ export class TagInput extends React.Component<ITagInputProps, ITagInputState> {
|
|||
<span className="tag-input-title">{strings.tags.title}</span>
|
||||
<TagInputToolbar
|
||||
selectedTag={this.state.selectedTag}
|
||||
onAddTags={() => this.setState({ addTags: !this.state.addTags })}
|
||||
onAddTags={() => this.setState({addTags: !this.state.addTags})}
|
||||
onOnlyCurrentPageTags={() => this.setState({onlyCurrentPageTags: !this.state.onlyCurrentPageTags})}
|
||||
onShowOriginLabels = {(showOriginLabels: boolean) => this.setState({showOriginLabels})}
|
||||
onSearchTags={() => this.setState({
|
||||
searchTags: !this.state.searchTags,
|
||||
searchQuery: "",
|
||||
|
@ -186,7 +194,7 @@ export class TagInput extends React.Component<ITagInputProps, ITagInputState> {
|
|||
onReorder={this.onReOrder}
|
||||
/>
|
||||
</div>
|
||||
{ this.props.tagsLoaded ?
|
||||
{this.props.tagsLoaded ?
|
||||
<div className="tag-input-body-container">
|
||||
<div className="tag-input-body">
|
||||
{
|
||||
|
@ -196,10 +204,10 @@ export class TagInput extends React.Component<ITagInputProps, ITagInputState> {
|
|||
className="tag-search-box"
|
||||
type="text"
|
||||
onKeyDown={this.onSearchKeyDown}
|
||||
onChange={(e) => this.setState({ searchQuery: e.target.value })}
|
||||
onChange={(e) => this.setState({searchQuery: e.target.value})}
|
||||
placeholder="Search tags"
|
||||
autoFocus={true}
|
||||
onFocus={() => this.setState({ selectedTag: null, tagOperation: TagOperationMode.Rename })}
|
||||
onFocus={() => this.setState({selectedTag: null, tagOperation: TagOperationMode.Rename})}
|
||||
/>
|
||||
<FontIcon iconName="Search" />
|
||||
</div>
|
||||
|
@ -243,13 +251,11 @@ export class TagInput extends React.Component<ITagInputProps, ITagInputState> {
|
|||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
public triggerNewTagBlur() {
|
||||
if (this.inputRef.current) {
|
||||
this.inputRef.current.blur();
|
||||
}
|
||||
}
|
||||
|
||||
private onRenameTag = (tag: ITag) => {
|
||||
const tagOperation = this.state.tagOperation === TagOperationMode.Rename
|
||||
? TagOperationMode.None : TagOperationMode.Rename;
|
||||
|
@ -262,7 +268,7 @@ export class TagInput extends React.Component<ITagInputProps, ITagInputState> {
|
|||
if (!tag) {
|
||||
return;
|
||||
}
|
||||
let lockedTags = [...this.props.lockedTags];
|
||||
let lockedTags = [ ...this.props.lockedTags ];
|
||||
if (lockedTags.find((str) => isNameEqual(tag.name, str))) {
|
||||
lockedTags = lockedTags.filter((str) => !isNameEqual(tag.name, str));
|
||||
} else {
|
||||
|
@ -279,7 +285,7 @@ export class TagInput extends React.Component<ITagInputProps, ITagInputState> {
|
|||
if (!tag) {
|
||||
return;
|
||||
}
|
||||
const tags = [...this.state.tags];
|
||||
const tags = [ ...this.state.tags ];
|
||||
const currentIndex = tags.indexOf(tag);
|
||||
const newIndex = currentIndex + displacement;
|
||||
if (newIndex < 0 || newIndex >= tags.length) {
|
||||
|
@ -315,7 +321,7 @@ export class TagInput extends React.Component<ITagInputProps, ITagInputState> {
|
|||
return;
|
||||
}
|
||||
|
||||
const tags = [...this.state.tags, tag];
|
||||
const tags = [ ...this.state.tags, tag ];
|
||||
this.setState({
|
||||
tags,
|
||||
}, () => this.props.onChange(tags));
|
||||
|
@ -362,10 +368,10 @@ export class TagInput extends React.Component<ITagInputProps, ITagInputState> {
|
|||
}
|
||||
|
||||
private getColorPickerPortal = () => {
|
||||
const { selectedTag } = this.state;
|
||||
const {selectedTag} = this.state;
|
||||
const showColorPicker = this.state.tagOperation === TagOperationMode.ColorPicker;
|
||||
return (
|
||||
<AlignPortal align={{ points: ["tr", "tl"] }} target={() => this.headerRef.current}>
|
||||
<AlignPortal align={{points: [ "tr", "tl" ]}} target={() => this.headerRef.current}>
|
||||
<div className="tag-input-colorpicker-container">
|
||||
{
|
||||
showColorPicker &&
|
||||
|
@ -395,6 +401,7 @@ export class TagInput extends React.Component<ITagInputProps, ITagInputState> {
|
|||
{...prop}
|
||||
key={prop.tag.name}
|
||||
labels={this.setTagLabels(prop.tag.name)}
|
||||
showOriginLabels={this.state.showOriginLabels}
|
||||
ref={(item) => this.setTagItemRef(item, prop.tag)}
|
||||
onLabelEnter={this.props.onLabelEnter}
|
||||
onLabelLeave={this.props.onLabelLeave}
|
||||
|
@ -413,9 +420,34 @@ export class TagInput extends React.Component<ITagInputProps, ITagInputState> {
|
|||
}
|
||||
|
||||
private createTagItemProps = (): ITagInputItemProps[] => {
|
||||
const { tags, selectedTag, tagOperation } = this.state;
|
||||
const {tags, selectedTag, tagOperation, onlyCurrentPageTags} = this.state;
|
||||
const selectedRegionTagSet = this.getSelectedRegionTagSet();
|
||||
|
||||
if (onlyCurrentPageTags) {
|
||||
|
||||
const labels = this.props.labels.filter(item => item.value[ 0 ].page === this.props.pageNumber)
|
||||
.map(item => item.label);
|
||||
if (labels.length) {
|
||||
|
||||
return tags.filter(tag => labels.find(a => a === tag.name))
|
||||
.map<ITagInputItemProps>(tag => {
|
||||
return {
|
||||
tag,
|
||||
index: tags.findIndex((t) => isNameEqual(t.name, tag.name)),
|
||||
isLocked: this.props.lockedTags
|
||||
&& this.props.lockedTags.findIndex((str) => isNameEqual(tag.name, str)) > -1,
|
||||
isRenaming: selectedTag && isNameEqual(selectedTag.name, tag.name)
|
||||
&& tagOperation === TagOperationMode.Rename,
|
||||
isSelected: selectedTag && isNameEqual(selectedTag.name, tag.name),
|
||||
appliedToSelectedRegions: selectedRegionTagSet.has(tag.name),
|
||||
onClick: this.onTagItemClick,
|
||||
onRename: this.onTagRename,
|
||||
} as ITagInputItemProps;
|
||||
});
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
return tags.map((tag) => (
|
||||
{
|
||||
tag,
|
||||
|
@ -453,7 +485,7 @@ export class TagInput extends React.Component<ITagInputProps, ITagInputState> {
|
|||
tagOperation: TagOperationMode.Rename,
|
||||
});
|
||||
} else if (props.clickedDropDown) {
|
||||
const { selectedTag } = this.state;
|
||||
const {selectedTag} = this.state;
|
||||
const showContextualMenu = !selectedTag || !isNameEqual(selectedTag.name, tag.name)
|
||||
|| this.state.tagOperation !== TagOperationMode.ContextualMenu;
|
||||
const tagOperation = showContextualMenu ? TagOperationMode.ContextualMenu : TagOperationMode.None;
|
||||
|
@ -462,7 +494,7 @@ export class TagInput extends React.Component<ITagInputProps, ITagInputState> {
|
|||
tagOperation,
|
||||
});
|
||||
} else if (props.clickedColor) {
|
||||
const { selectedTag, tagOperation } = this.state;
|
||||
const {selectedTag, tagOperation} = this.state;
|
||||
const showColorPicker = tagOperation !== TagOperationMode.ColorPicker;
|
||||
const newTagOperation = showColorPicker ? TagOperationMode.ColorPicker : TagOperationMode.None;
|
||||
this.setState({
|
||||
|
@ -470,27 +502,27 @@ export class TagInput extends React.Component<ITagInputProps, ITagInputState> {
|
|||
tagOperation: newTagOperation,
|
||||
});
|
||||
} else { // Select tag
|
||||
const { selectedTag, tagOperation: oldTagOperation } = this.state;
|
||||
const {selectedTag, tagOperation: oldTagOperation} = this.state;
|
||||
const selected = selectedTag && isNameEqual(selectedTag.name, tag.name);
|
||||
const tagOperation = selected ? oldTagOperation : TagOperationMode.None;
|
||||
let deselect = selected && oldTagOperation === TagOperationMode.None;
|
||||
|
||||
// Only fire click event if a region is selected
|
||||
const { selectedRegions, onTagClick, labels } = this.props;
|
||||
const {selectedRegions, onTagClick, labels} = this.props;
|
||||
if (selectedRegions && selectedRegions.length && onTagClick) {
|
||||
const { category } = selectedRegions[0];
|
||||
const { format, type, documentCount, name } = tag;
|
||||
const {category} = selectedRegions[ 0 ];
|
||||
const {format, type, documentCount, name} = tag;
|
||||
const tagCategory = this.getTagCategory(type);
|
||||
const isTagLabelTypeDrawnRegion = this.labelAssignedDrawnRegion(labels, tag.name);
|
||||
const labelAssigned = this.labelAssigned(labels, name);
|
||||
|
||||
if (labelAssigned && ((category === FeatureCategory.DrawnRegion) !== isTagLabelTypeDrawnRegion)) {
|
||||
if (isTagLabelTypeDrawnRegion) {
|
||||
toast.warn(interpolate(strings.tags.warnings.notCompatibleWithDrawnRegionTag, { otherCatagory: category }));
|
||||
toast.warn(interpolate(strings.tags.warnings.notCompatibleWithDrawnRegionTag, {otherCatagory: category}));
|
||||
} else if (tagCategory === FeatureCategory.Checkbox) {
|
||||
toast.warn(interpolate(strings.tags.warnings.notCompatibleWithDrawnRegionTag, { otherCatagory: FeatureCategory.Checkbox }));
|
||||
toast.warn(interpolate(strings.tags.warnings.notCompatibleWithDrawnRegionTag, {otherCatagory: FeatureCategory.Checkbox}));
|
||||
} else {
|
||||
toast.warn(interpolate(strings.tags.warnings.notCompatibleWithDrawnRegionTag, { otherCatagory: FeatureCategory.Text }));
|
||||
toast.warn(interpolate(strings.tags.warnings.notCompatibleWithDrawnRegionTag, {otherCatagory: FeatureCategory.Text}));
|
||||
}
|
||||
return;
|
||||
} else if (tagCategory === category || category === FeatureCategory.DrawnRegion ||
|
||||
|
@ -502,7 +534,7 @@ export class TagInput extends React.Component<ITagInputProps, ITagInputState> {
|
|||
onTagClick(tag);
|
||||
deselect = false;
|
||||
} else {
|
||||
toast.warn(strings.tags.warnings.notCompatibleTagType, { autoClose: 7000 });
|
||||
toast.warn(strings.tags.warnings.notCompatibleTagType, {autoClose: 7000});
|
||||
}
|
||||
}
|
||||
this.setState({
|
||||
|
@ -586,7 +618,7 @@ export class TagInput extends React.Component<ITagInputProps, ITagInputState> {
|
|||
format: FieldFormat.NotSpecified,
|
||||
documentCount: 0,
|
||||
};
|
||||
if (newTag.name.length && ![...this.state.tags, newTag].containsDuplicates((t) => t.name)) {
|
||||
if (newTag.name.length && ![ ...this.state.tags, newTag ].containsDuplicates((t) => t.name)) {
|
||||
this.addTag(newTag);
|
||||
} else if (!newTag.name.length) {
|
||||
toast.warn(strings.tags.warnings.emptyName);
|
||||
|
@ -611,7 +643,7 @@ export class TagInput extends React.Component<ITagInputProps, ITagInputState> {
|
|||
}
|
||||
|
||||
private onHideContextualMenu = () => {
|
||||
this.setState({ tagOperation: TagOperationMode.None });
|
||||
this.setState({tagOperation: TagOperationMode.None});
|
||||
}
|
||||
|
||||
private getContextualMenuItems = (): IContextualMenuItem[] => {
|
||||
|
|
|
@ -21,6 +21,7 @@ describe("Tag Input Item", () => {
|
|||
onRename: jest.fn(),
|
||||
onLabelEnter: jest.fn(),
|
||||
onLabelLeave: jest.fn(),
|
||||
showOriginLabels:false,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
import React, { MouseEvent } from "react";
|
||||
import { FontIcon, IconButton } from "@fluentui/react";
|
||||
import { ITag, ILabel, FieldType, FieldFormat } from "../../../../models/applicationState";
|
||||
import { strings } from "../../../../common/strings";
|
||||
import TagInputItemLabel from "./tagInputItemLabel";
|
||||
import { tagIndexKeys } from "./tagIndexKeys";
|
||||
import {FontIcon, IconButton} from "@fluentui/react";
|
||||
import _ from "lodash";
|
||||
import React, {Fragment, MouseEvent} from "react";
|
||||
import {strings} from "../../../../common/strings";
|
||||
import {FieldFormat, FieldType, ILabel, ITag} from "../../../../models/applicationState";
|
||||
import {tagIndexKeys} from "./tagIndexKeys";
|
||||
import TagInputItemLabel from "./tagInputItemLabel";
|
||||
|
||||
export interface ITagClickProps {
|
||||
ctrlKey?: boolean;
|
||||
|
@ -27,6 +27,8 @@ export interface ITagInputItemProps {
|
|||
index: number;
|
||||
/** Labels owned by the tag */
|
||||
labels: ILabel[];
|
||||
/** show Origin Labels or not */
|
||||
showOriginLabels: boolean;
|
||||
/** Tag is currently renaming */
|
||||
isRenaming: boolean;
|
||||
/** Tag is currently locked for application */
|
||||
|
@ -42,7 +44,7 @@ export interface ITagInputItemProps {
|
|||
onLabelEnter: (label: ILabel) => void;
|
||||
onLabelLeave: (label: ILabel) => void;
|
||||
onTagChanged?: (oldTag: ITag, newTag: ITag) => void;
|
||||
onTagDoubleClick?: (label:ILabel) => void;
|
||||
onTagDoubleClick?: (label: ILabel) => void;
|
||||
}
|
||||
|
||||
export interface ITagInputItemState {
|
||||
|
@ -81,14 +83,8 @@ export default class TagInputItem extends React.Component<ITagInputItemProps, IT
|
|||
const style: any = {
|
||||
background: this.props.tag.color,
|
||||
};
|
||||
const confidence = _.get(this.props, "labels[0].confidence", null);
|
||||
return (
|
||||
<div className={"tag-item-block"}>
|
||||
{confidence &&
|
||||
<div className="tag-item-confidence">
|
||||
{confidence}
|
||||
</div>
|
||||
}
|
||||
<div
|
||||
className={"tag-color"}
|
||||
style={style}
|
||||
|
@ -127,7 +123,7 @@ export default class TagInputItem extends React.Component<ITagInputItemProps, IT
|
|||
e.stopPropagation();
|
||||
|
||||
const clickedDropDown = true;
|
||||
this.props.onClick(this.props.tag, { clickedDropDown });
|
||||
this.props.onClick(this.props.tag, {clickedDropDown});
|
||||
}
|
||||
|
||||
private onColorClick = (e: MouseEvent) => {
|
||||
|
@ -136,7 +132,7 @@ export default class TagInputItem extends React.Component<ITagInputItemProps, IT
|
|||
const ctrlKey = e.ctrlKey || e.metaKey;
|
||||
const altKey = e.altKey;
|
||||
const clickedColor = true;
|
||||
this.props.onClick(this.props.tag, { ctrlKey, altKey, clickedColor });
|
||||
this.props.onClick(this.props.tag, {ctrlKey, altKey, clickedColor});
|
||||
}
|
||||
|
||||
private onNameClick = (e: MouseEvent) => {
|
||||
|
@ -144,12 +140,12 @@ export default class TagInputItem extends React.Component<ITagInputItemProps, IT
|
|||
|
||||
const ctrlKey = e.ctrlKey || e.metaKey;
|
||||
const altKey = e.altKey;
|
||||
this.props.onClick(this.props.tag, { ctrlKey, altKey });
|
||||
this.props.onClick(this.props.tag, {ctrlKey, altKey});
|
||||
}
|
||||
|
||||
private onNameDoubleClick = (e:MouseEvent) => {
|
||||
private onNameDoubleClick = (e: MouseEvent) => {
|
||||
e.stopPropagation();
|
||||
const { labels } = this.props;
|
||||
const {labels} = this.props;
|
||||
if (labels.length > 0) {
|
||||
this.props.onTagDoubleClick(labels[0]);
|
||||
}
|
||||
|
@ -206,7 +202,7 @@ export default class TagInputItem extends React.Component<ITagInputItemProps, IT
|
|||
title={strings.tags.toolbar.contextualMenu}
|
||||
ariaLabel={strings.tags.toolbar.contextualMenu}
|
||||
className="tag-input-toolbar-iconbutton ml-2"
|
||||
iconProps={{ iconName: "ChevronDown" }}
|
||||
iconProps={{iconName: "ChevronDown"}}
|
||||
onClick={this.onDropdownClick} />
|
||||
</div>
|
||||
</div>
|
||||
|
@ -214,15 +210,47 @@ export default class TagInputItem extends React.Component<ITagInputItemProps, IT
|
|||
}
|
||||
|
||||
private renderTagDetail = () => {
|
||||
let confidence = _.get(this.props, "labels[0].confidence", null);
|
||||
if (confidence > .995) {
|
||||
confidence = 0.995;
|
||||
}
|
||||
const revised = _.get(this.props, "labels[0].revised", false);
|
||||
return this.props.labels.map((label, idx) =>
|
||||
<Fragment key={idx}>
|
||||
<div className="tag-item-label-container">
|
||||
{(confidence||revised)&&
|
||||
<div className="tag-item-label-container-item1">
|
||||
{confidence &&
|
||||
<div className="tag-item-confidence">
|
||||
{confidence}
|
||||
</div>
|
||||
}
|
||||
{revised &&
|
||||
<FontIcon iconName="StatusCircleCheckmark" className="ms-Icon-25px" />
|
||||
}
|
||||
</div>
|
||||
}
|
||||
<div className="tag-item-label-container-item2">
|
||||
{ this.props.showOriginLabels && label.originValue &&
|
||||
<TagInputItemLabel
|
||||
key={idx}
|
||||
label={label}
|
||||
isOrigin={true}
|
||||
value={label.originValue}
|
||||
prefixText="Auto-labeled: "
|
||||
/>
|
||||
}
|
||||
<TagInputItemLabel
|
||||
label={label}
|
||||
value={label.value}
|
||||
isOrigin={false}
|
||||
onLabelEnter={this.props.onLabelEnter}
|
||||
onLabelLeave={this.props.onLabelLeave}
|
||||
/>);
|
||||
prefixText={revised?"Revised: ":undefined}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</Fragment>);
|
||||
}
|
||||
|
||||
private onInputRef = (element: HTMLInputElement) => {
|
||||
this.inputElement = element;
|
||||
if (element) {
|
||||
|
@ -274,20 +302,20 @@ export default class TagInputItem extends React.Component<ITagInputItemProps, IT
|
|||
}
|
||||
|
||||
private isTypeOrFormatSpecified = () => {
|
||||
const { tag } = this.props;
|
||||
const {tag} = this.props;
|
||||
return (tag.type && tag.type !== FieldType.String) ||
|
||||
(tag.format && tag.format !== FieldFormat.NotSpecified);
|
||||
}
|
||||
|
||||
private handleMouseEnter = () => {
|
||||
const { labels } = this.props;
|
||||
const {labels} = this.props;
|
||||
if (labels.length > 0) {
|
||||
this.props.onLabelEnter(labels[0]);
|
||||
}
|
||||
}
|
||||
|
||||
private handleMouseLeave = () => {
|
||||
const { labels } = this.props;
|
||||
const {labels} = this.props;
|
||||
if (labels.length > 0) {
|
||||
this.props.onLabelLeave(labels[0]);
|
||||
}
|
||||
|
|
|
@ -2,13 +2,16 @@
|
|||
// Licensed under the MIT license.
|
||||
|
||||
import React from "react";
|
||||
import { ILabel, IFormRegion } from "../../../../models/applicationState";
|
||||
import { FontIcon } from "@fluentui/react";
|
||||
import {ILabel, IFormRegion} from "../../../../models/applicationState";
|
||||
import {FontIcon} from "@fluentui/react";
|
||||
|
||||
export interface ITagInputItemLabelProps {
|
||||
label: ILabel;
|
||||
onLabelEnter: (label: ILabel) => void;
|
||||
onLabelLeave: (label: ILabel) => void;
|
||||
value: IFormRegion[];
|
||||
isOrigin: boolean;
|
||||
onLabelEnter?: (label: ILabel) => void;
|
||||
onLabelLeave?: (label: ILabel) => void;
|
||||
prefixText?:string
|
||||
}
|
||||
|
||||
export interface ITagInputItemLabelState {}
|
||||
|
@ -17,7 +20,7 @@ export default class TagInputItemLabel extends React.Component<ITagInputItemLabe
|
|||
public render() {
|
||||
const texts = [];
|
||||
let hasEmptyTextValue = false;
|
||||
this.props.label.value.forEach((formRegion: IFormRegion, idx) => {
|
||||
this.props.value.forEach((formRegion: IFormRegion, idx) => {
|
||||
if (formRegion.text === "") {
|
||||
hasEmptyTextValue = true;
|
||||
} else {
|
||||
|
@ -27,14 +30,14 @@ export default class TagInputItemLabel extends React.Component<ITagInputItemLabe
|
|||
const text = texts.join(" ");
|
||||
return (
|
||||
<div
|
||||
className={"tag-item-label flex-center px-2"}
|
||||
className={[this.props.isOrigin ? "tag-item-label-origin" : "tag-item-label", "flex-center", "px-2"].join(" ")}
|
||||
onMouseEnter={this.handleMouseEnter}
|
||||
onMouseLeave={this.handleMouseLeave}
|
||||
>
|
||||
<div className="flex-center">
|
||||
{text}
|
||||
{this.props.prefixText} {text}
|
||||
{hasEmptyTextValue &&
|
||||
<FontIcon className="pr-1 pl-1" iconName="RectangleShape"/>
|
||||
<FontIcon className="pr-1 pl-1" iconName="RectangleShape" />
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
@ -42,10 +45,14 @@ export default class TagInputItemLabel extends React.Component<ITagInputItemLabe
|
|||
}
|
||||
|
||||
private handleMouseEnter = () => {
|
||||
if (this.props.onLabelEnter) {
|
||||
this.props.onLabelEnter(this.props.label);
|
||||
}
|
||||
}
|
||||
|
||||
private handleMouseLeave = () => {
|
||||
if (this.props.onLabelLeave) {
|
||||
this.props.onLabelLeave(this.props.label);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,7 +5,7 @@ $tagInputWidth: 275px;
|
|||
$tagColorWidth: 8px;
|
||||
$tagLinkWidth: 22px;
|
||||
$tagItemWidth: $tagInputWidth - $tagColorWidth;
|
||||
$tagTextWidth: $tagItemWidth - 55px;
|
||||
$tagTextWidth: $tagItemWidth - 95px;
|
||||
$tagTextLinkedWidth: $tagTextWidth - $tagLinkWidth;
|
||||
$tagContextualMenuWidth: $tagItemWidth - 8px;
|
||||
$tagColorPickerHeight: 480px;
|
||||
|
|
|
@ -2,9 +2,10 @@
|
|||
// Licensed under the MIT license.
|
||||
|
||||
import React from "react";
|
||||
import { IconButton } from "@fluentui/react";
|
||||
import { strings } from "../../../../common/strings";
|
||||
import { ITag } from "../../../../models/applicationState";
|
||||
import {IconButton} from "@fluentui/react";
|
||||
import {strings} from "../../../../common/strings";
|
||||
import {ITag} from "../../../../models/applicationState";
|
||||
import {constants} from "../../../../common/constants";
|
||||
|
||||
enum Categories {
|
||||
General,
|
||||
|
@ -29,6 +30,8 @@ export interface ITagInputToolbarProps {
|
|||
onDelete: (tag: ITag) => void;
|
||||
/** Function to call when one of the re-order buttons is clicked */
|
||||
onReorder: (tag: ITag, displacement: number) => void;
|
||||
onOnlyCurrentPageTags: (onlyCurrentPageTags: boolean) => void;
|
||||
onShowOriginLabels?: (showOrigin: boolean) => void;
|
||||
searchingTags: boolean;
|
||||
}
|
||||
|
||||
|
@ -40,7 +43,17 @@ interface ITagInputToolbarItemProps {
|
|||
accelerators?: string[];
|
||||
}
|
||||
|
||||
export default class TagInputToolbar extends React.Component<ITagInputToolbarProps> {
|
||||
interface ITagInputToolbarItemState {
|
||||
tagFilterToggled: boolean;
|
||||
showOriginLabels: boolean;
|
||||
}
|
||||
|
||||
export default class TagInputToolbar extends React.Component<ITagInputToolbarProps, ITagInputToolbarItemState> {
|
||||
state = {
|
||||
tagFilterToggled: false,
|
||||
showOriginLabels: constants.showOriginLabelsByDefault,
|
||||
};
|
||||
|
||||
public render() {
|
||||
return (
|
||||
<div className="tag-input-toolbar">
|
||||
|
@ -57,6 +70,18 @@ export default class TagInputToolbar extends React.Component<ITagInputToolbarPro
|
|||
category: Categories.General,
|
||||
handler: this.handleAdd,
|
||||
},
|
||||
{
|
||||
displayName: this.state.tagFilterToggled ? strings.tags.toolbar.showAllTags : strings.tags.toolbar.onlyShowCurrentPageTags,
|
||||
icon: this.state.tagFilterToggled ? "ClearFilter" : "Filter",
|
||||
category: Categories.General,
|
||||
handler: this.handleOnlyCurrentPageTags,
|
||||
},
|
||||
{
|
||||
displayName: this.state.showOriginLabels ? strings.tags.toolbar.hideOriginLabels : strings.tags.toolbar.showOriginLabels,
|
||||
icon: this.state.showOriginLabels ? "GroupList" : "GroupedList",
|
||||
category: Categories.General,
|
||||
handler: this.handleShowOriginLabels,
|
||||
},
|
||||
{
|
||||
displayName: strings.tags.toolbar.search,
|
||||
icon: "Search",
|
||||
|
@ -109,7 +134,7 @@ export default class TagInputToolbar extends React.Component<ITagInputToolbarPro
|
|||
const moveModifierClassName = moveModifierClassNames.join(" ");
|
||||
const renameModifierClassName = renameModifierClassNames.join(" ");
|
||||
|
||||
return(
|
||||
return (
|
||||
this.getToolbarItems().map((itemConfig, index) => {
|
||||
if (itemConfig.category === Categories.General) {
|
||||
return (
|
||||
|
@ -161,6 +186,19 @@ export default class TagInputToolbar extends React.Component<ITagInputToolbarPro
|
|||
private handleAdd = () => {
|
||||
this.props.onAddTags();
|
||||
}
|
||||
private handleOnlyCurrentPageTags = () => {
|
||||
this.setState({tagFilterToggled: !this.state.tagFilterToggled}, () => {
|
||||
this.props.onOnlyCurrentPageTags(this.state.tagFilterToggled);
|
||||
});
|
||||
}
|
||||
|
||||
private handleShowOriginLabels = () => {
|
||||
this.setState({showOriginLabels: !this.state.showOriginLabels}, () => {
|
||||
if (this.props.onShowOriginLabels) {
|
||||
this.props.onShowOriginLabels(this.state.showOriginLabels);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private handleSearch = () => {
|
||||
this.props.onSearchTags();
|
||||
|
|
|
@ -0,0 +1,89 @@
|
|||
import {Customizer, ICustomizations, IModalStyles, Modal, PrimaryButton, Slider} from "@fluentui/react";
|
||||
import React from "react";
|
||||
import {ModalBody} from "reactstrap";
|
||||
import {constants} from "../../../../common/constants";
|
||||
import {getDarkGreyTheme, getPrimaryGreenTheme, getPrimaryGreyTheme} from "../../../../common/themes";
|
||||
|
||||
interface IBatchSizeModalProps {
|
||||
onConfirm?: (batchSize: number) => void;
|
||||
}
|
||||
|
||||
interface IBatchSizeModalState {
|
||||
showModal: boolean;
|
||||
batchSize: number;
|
||||
}
|
||||
|
||||
export class BatchSizeModal extends React.Component<IBatchSizeModalProps, IBatchSizeModalState>{
|
||||
state = {
|
||||
showModal: false,
|
||||
batchSize: 3
|
||||
};
|
||||
|
||||
onConfirm = () => {
|
||||
this.setState({showModal: false});
|
||||
if (this.props.onConfirm) {
|
||||
this.props.onConfirm(this.state.batchSize);
|
||||
}
|
||||
}
|
||||
onCancel = () => {
|
||||
this.setState({showModal: false});
|
||||
}
|
||||
|
||||
onBatchSizeChange = (value: number) => {
|
||||
this.setState({
|
||||
batchSize: value
|
||||
});
|
||||
}
|
||||
|
||||
openModal() {
|
||||
this.setState({
|
||||
batchSize: constants.autoLabelBatchSizeMin,
|
||||
showModal: true,
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
const dark: ICustomizations = {
|
||||
settings: {
|
||||
theme: getDarkGreyTheme(),
|
||||
},
|
||||
scopedSettings: {},
|
||||
};
|
||||
const styles: Partial<IModalStyles> = {
|
||||
main: {
|
||||
width: "400px!important",
|
||||
}
|
||||
};
|
||||
return (
|
||||
<Customizer {...dark}>
|
||||
<Modal
|
||||
isOpen={this.state.showModal}
|
||||
isModeless={false}
|
||||
containerClassName="modal-container"
|
||||
styles={styles}
|
||||
>
|
||||
<h4>Set Auto Labeling Batch Size</h4>
|
||||
<ModalBody>
|
||||
<Slider value={this.state.batchSize}
|
||||
min={constants.autoLabelBatchSizeMin}
|
||||
max={constants.autoLabelBatchSizeMax}
|
||||
onChange={this.onBatchSizeChange} />
|
||||
</ModalBody>
|
||||
<div className="modal-buttons-container">
|
||||
<PrimaryButton
|
||||
className="model-confirm"
|
||||
theme={getPrimaryGreenTheme()}
|
||||
onClick={this.onConfirm}>
|
||||
Ok
|
||||
</PrimaryButton>
|
||||
<PrimaryButton
|
||||
className="modal-cancel"
|
||||
theme={getPrimaryGreyTheme()}
|
||||
onClick={this.onCancel}
|
||||
>Cancel</PrimaryButton>
|
||||
</div>
|
||||
</Modal>
|
||||
</Customizer>
|
||||
);
|
||||
}
|
||||
}
|
|
@ -40,14 +40,14 @@
|
|||
.prev {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50px;
|
||||
left: 0;
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
.next {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
right: 50px;
|
||||
right: 0;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
import React, { ReactElement } from "react";
|
||||
import React, { ReactElement, RefObject } from "react";
|
||||
import { Spinner, SpinnerSize } from "@fluentui/react/lib/Spinner";
|
||||
import { Label } from "@fluentui/react/lib/Label";
|
||||
import { IconButton } from "@fluentui/react/lib/Button";
|
||||
|
@ -39,6 +39,7 @@ import { AutoLabelingStatus, PredictService } from "../../../../services/predict
|
|||
import { AssetService } from "../../../../services/assetService";
|
||||
import { interpolate, strings } from "../../../../common/strings";
|
||||
import { toast } from "react-toastify";
|
||||
import {BatchSizeModal} from "./batchSizeModal";
|
||||
|
||||
|
||||
pdfjsLib.GlobalWorkerOptions.workerSrc = constants.pdfjsWorkerSrc(pdfjsLib.version);
|
||||
|
@ -62,8 +63,9 @@ export interface ICanvasProps extends React.Props<Canvas> {
|
|||
onRunningAutoLabelingStatusChanged?: (isRunning: boolean) => void;
|
||||
onTagChanged?: (oldTag: ITag, newTag: ITag) => void;
|
||||
runOcrForAllDocs?: (runForAllDocs: boolean) => void;
|
||||
runAutoLabelingOnNextBatch?: () => Promise<void>;
|
||||
runAutoLabelingOnNextBatch?: (batchSize: number) => Promise<void>;
|
||||
onAssetDeleted?: () => void;
|
||||
onPageLoaded?: (pageNumber: number) => void;
|
||||
}
|
||||
|
||||
export interface ICanvasState {
|
||||
|
@ -81,7 +83,7 @@ export interface ICanvasState {
|
|||
errorTitle?: string;
|
||||
errorMessage: string;
|
||||
ocrStatus: OcrStatus;
|
||||
autoLableingStatus: AutoLabelingStatus;
|
||||
autoLabelingStatus: AutoLabelingStatus;
|
||||
layers: any;
|
||||
tableIconTooltip: any;
|
||||
hoveringFeature: string;
|
||||
|
@ -141,7 +143,7 @@ export default class Canvas extends React.Component<ICanvasProps, ICanvasState>
|
|||
isError: false,
|
||||
errorMessage: undefined,
|
||||
ocrStatus: OcrStatus.done,
|
||||
autoLableingStatus: AutoLabelingStatus.none,
|
||||
autoLabelingStatus: AutoLabelingStatus.none,
|
||||
layers: { text: true, tables: true, checkboxes: true, label: true, drawnRegions: true },
|
||||
tableIconTooltip: { display: "none", width: 0, height: 0, top: 0, left: 0 },
|
||||
hoveringFeature: null,
|
||||
|
@ -172,6 +174,8 @@ export default class Canvas extends React.Component<ICanvasProps, ICanvasState>
|
|||
|
||||
private tableIDToIndexMap: object;
|
||||
|
||||
autoLabelingBatchSizeModal: RefObject<BatchSizeModal> = React.createRef();
|
||||
|
||||
public componentDidMount = async () => {
|
||||
this.ocrService = new OCRService(this.props.project);
|
||||
const asset = this.state.currentAsset.asset;
|
||||
|
@ -257,7 +261,7 @@ export default class Canvas extends React.Component<ICanvasProps, ICanvasState>
|
|||
handleAssetDeleted={this.props.onAssetDeleted}
|
||||
handleRunOcrForAllDocuments={this.runOcrForAllDocuments}
|
||||
handleRunAutoLabelingOnCurrentDocument={this.runAutoLabelingOnCurrentDocument}
|
||||
handleRunAutoLabelingForRestDocuments={this.runAutoLabelingForRestDocuments}
|
||||
handleRunAutoLabelingOnMultipleUnlabeledDocuments={this.runAutoLabelingOnMultipleUnlabeledDocuments}
|
||||
handleToggleDrawRegionMode={this.handleToggleDrawRegionMode}
|
||||
connectionType={this.props.project.sourceConnection.providerType}
|
||||
drawRegionMode={this.state.drawRegionMode}
|
||||
|
@ -339,11 +343,11 @@ export default class Canvas extends React.Component<ICanvasProps, ICanvasState>
|
|||
<div className="canvas-ocr-loading">
|
||||
<div className="canvas-ocr-loading-spinner">
|
||||
<Label className="p-0" ></Label>
|
||||
<Spinner size={SpinnerSize.large} label="Running OCR..." ariaLive="assertive" labelPosition="right" />
|
||||
<Spinner size={SpinnerSize.large} label="Running Layout..." ariaLive="assertive" labelPosition="right" />
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
{this.state.autoLableingStatus === AutoLabelingStatus.running &&
|
||||
{this.state.autoLabelingStatus === AutoLabelingStatus.running &&
|
||||
<div className="canvas-ocr-loading">
|
||||
<div className="canvas-ocr-loading-spinner">
|
||||
<Label className="p-0" ></Label>
|
||||
|
@ -361,6 +365,10 @@ export default class Canvas extends React.Component<ICanvasProps, ICanvasState>
|
|||
errorMessage: undefined,
|
||||
})}
|
||||
/>
|
||||
<BatchSizeModal
|
||||
ref={this.autoLabelingBatchSizeModal}
|
||||
onConfirm={this.confirmRunAutoLabelingOnMultipleUnlabeledDocuments}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -385,10 +393,13 @@ export default class Canvas extends React.Component<ICanvasProps, ICanvasState>
|
|||
this.setAutoLabelingStatus(AutoLabelingStatus.done);
|
||||
}
|
||||
}
|
||||
private runAutoLabelingForRestDocuments = async () => {
|
||||
this.setState({ autoLableingStatus: AutoLabelingStatus.running });
|
||||
await this.props.runAutoLabelingOnNextBatch();
|
||||
this.setState({ autoLableingStatus: AutoLabelingStatus.done });
|
||||
private runAutoLabelingOnMultipleUnlabeledDocuments = async () => {
|
||||
this.autoLabelingBatchSizeModal.current.openModal();
|
||||
}
|
||||
private confirmRunAutoLabelingOnMultipleUnlabeledDocuments = async (batchSize: number) => {
|
||||
this.setState({ autoLabelingStatus: AutoLabelingStatus.running });
|
||||
await this.props.runAutoLabelingOnNextBatch(batchSize);
|
||||
this.setState({ autoLabelingStatus: AutoLabelingStatus.done });
|
||||
}
|
||||
|
||||
public updateSize() {
|
||||
|
@ -537,8 +548,8 @@ export default class Canvas extends React.Component<ICanvasProps, ICanvasState>
|
|||
}
|
||||
|
||||
private deleteRegions = (regions: IRegion[]) => {
|
||||
this.deleteRegionsFromSelectedRegionIds(regions);
|
||||
this.deleteRegionsFromAsset(regions);
|
||||
this.deleteRegionsFromSelectedRegionIds(regions);
|
||||
this.deleteRegionsFromImageMap(regions);
|
||||
}
|
||||
|
||||
|
@ -1196,10 +1207,11 @@ export default class Canvas extends React.Component<ICanvasProps, ICanvasState>
|
|||
}
|
||||
});
|
||||
}
|
||||
private setAutoLabelingStatus = (autoLableingStatus: AutoLabelingStatus) => {
|
||||
this.setState({ autoLableingStatus }, () => {
|
||||
|
||||
private setAutoLabelingStatus = (autoLabelingStatus: AutoLabelingStatus) => {
|
||||
this.setState({ autoLabelingStatus }, () => {
|
||||
if (this.props.onRunningAutoLabelingStatusChanged) {
|
||||
this.props.onRunningAutoLabelingStatusChanged(autoLableingStatus === AutoLabelingStatus.running);
|
||||
this.props.onRunningAutoLabelingStatusChanged(autoLabelingStatus === AutoLabelingStatus.running);
|
||||
}
|
||||
})
|
||||
}
|
||||
|
@ -1297,6 +1309,9 @@ export default class Canvas extends React.Component<ICanvasProps, ICanvasState>
|
|||
currentPage: pageNumber,
|
||||
pdfFile: pdf,
|
||||
});
|
||||
if (this.props.onPageLoaded) {
|
||||
this.props.onPageLoaded(pageNumber);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1370,7 +1385,19 @@ export default class Canvas extends React.Component<ICanvasProps, ICanvasState>
|
|||
&& this.props.selectedAsset.labelData.labels.map(label => ({
|
||||
...label, value: []
|
||||
}))) || [];
|
||||
|
||||
const selectedRegions = this.getSelectedRegions();
|
||||
if (selectedRegions.length > 0) {
|
||||
const intersectionResult = _.intersection(selectedRegions, regions);
|
||||
if (intersectionResult.length === 0) {
|
||||
const relatedLabels = labels.find(label => selectedRegions.find(sr => sr.tags.find(t => t === label.label)));
|
||||
const originLabel = this.props.selectedAsset!.labelData!.labels.find(a => a.label === relatedLabels.label);
|
||||
if (relatedLabels&&originLabel&&relatedLabels.confidence) {
|
||||
delete relatedLabels.confidence;
|
||||
relatedLabels.revised = true;
|
||||
relatedLabels.originValue = [...originLabel.value];
|
||||
}
|
||||
}
|
||||
}
|
||||
regions.forEach((region) => {
|
||||
const labelType = this.getLabelType(region.category);
|
||||
const boundingBox = region.id.split(",").map(parseFloat);
|
||||
|
@ -1382,8 +1409,11 @@ export default class Canvas extends React.Component<ICanvasProps, ICanvasState>
|
|||
region.tags.forEach((tag) => {
|
||||
const label = labels.find(label => label.label === tag);
|
||||
if (label) {
|
||||
const originLabel = this.props.selectedAsset!.labelData!.labels.find(a=>a.label === tag);
|
||||
if (label.confidence && region.changed) {
|
||||
delete label.confidence;
|
||||
label.revised = true;
|
||||
label.originValue = [...originLabel.value];
|
||||
}
|
||||
label.value.push(formRegion);
|
||||
} else {
|
||||
|
|
|
@ -15,7 +15,7 @@ interface ICanvasCommandBarProps {
|
|||
handleRunOcr?: () => void;
|
||||
handleRunOcrForAllDocuments?: () => void;
|
||||
handleRunAutoLabelingOnCurrentDocument?: () => void;
|
||||
handleRunAutoLabelingForRestDocuments?: () => void;
|
||||
handleRunAutoLabelingOnMultipleUnlabeledDocuments?: () => void;
|
||||
handleLayerChange?: (layer: string) => void;
|
||||
handleToggleDrawRegionMode?: () => void;
|
||||
handleAssetDeleted?: () => void;
|
||||
|
@ -153,11 +153,9 @@ export const CanvasCommandBar: React.FunctionComponent<ICanvasCommandBarProps> =
|
|||
if (props.parentPage === strings.editorPage.title) {
|
||||
commandBarFarItems.push({
|
||||
key: "additionalActions",
|
||||
text: "Actions",
|
||||
title: strings.editorPage.canvas.canvasCommandBar.farItems.additionalActions.text,
|
||||
// This needs an ariaLabel since it's icon-only
|
||||
ariaLabel: strings.editorPage.canvas.canvasCommandBar.farItems.additionalActions.text,
|
||||
className: "additional-action-dropdown",
|
||||
iconProps: { iconName: "More" },
|
||||
subMenuProps: {
|
||||
items: [
|
||||
{
|
||||
|
@ -188,14 +186,14 @@ export const CanvasCommandBar: React.FunctionComponent<ICanvasCommandBarProps> =
|
|||
},
|
||||
},
|
||||
{
|
||||
key: "runAutoLabelingForRestDocuments",
|
||||
text: interpolate(strings.editorPage.canvas.canvasCommandBar.farItems.additionalActions.subIMenuItems.runAutoLabelingOnNotLabelingDocuments, { batchSize: constants.autoLabelBatchSize }),
|
||||
key: "runAutoLabelingOnMultipleUnlabeledDocuments",
|
||||
text: strings.editorPage.canvas.canvasCommandBar.farItems.additionalActions.subIMenuItems.runAutoLabelingOnMultipleUnlabeledDocuments,
|
||||
iconProps: { iconName: "Tag" },
|
||||
disabled: disableAutoLabeling,
|
||||
title: props.project.predictModelId ? "" :
|
||||
strings.editorPage.canvas.canvasCommandBar.farItems.additionalActions.subIMenuItems.noPredictModelOnProject,
|
||||
onClick: () => {
|
||||
if (props.handleRunAutoLabelingForRestDocuments) props.handleRunAutoLabelingForRestDocuments();
|
||||
if (props.handleRunAutoLabelingOnMultipleUnlabeledDocuments) props.handleRunAutoLabelingOnMultipleUnlabeledDocuments();
|
||||
},
|
||||
},
|
||||
{
|
||||
|
|
|
@ -96,6 +96,7 @@ export interface IEditorPageState {
|
|||
errorMessage?: string;
|
||||
tableToView: object;
|
||||
tableToViewId: string;
|
||||
pageNumber: number;
|
||||
}
|
||||
|
||||
function mapStateToProps(state: IApplicationState) {
|
||||
|
@ -132,6 +133,7 @@ export default class EditorPage extends React.Component<IEditorPageProps, IEdito
|
|||
hoveredLabel: null,
|
||||
tableToView: null,
|
||||
tableToViewId: null,
|
||||
pageNumber: 1
|
||||
};
|
||||
|
||||
private tagInputRef: RefObject<TagInput>;
|
||||
|
@ -231,11 +233,11 @@ export default class EditorPage extends React.Component<IEditorPageProps, IEdito
|
|||
<div>
|
||||
<Spinner
|
||||
size={SpinnerSize.small}
|
||||
label="Running OCR"
|
||||
label="Running Layout"
|
||||
ariaLive="off"
|
||||
labelPosition="right"
|
||||
/>
|
||||
</div> : "Run OCR on unvisited documents"
|
||||
</div> : "Run Layout on unvisited documents"
|
||||
}
|
||||
</PrimaryButton>
|
||||
</div>}
|
||||
|
@ -278,6 +280,7 @@ export default class EditorPage extends React.Component<IEditorPageProps, IEdito
|
|||
setTableToView={this.setTableToView}
|
||||
closeTableView={this.closeTableView}
|
||||
runOcrForAllDocs={this.loadOcrForNotVisited}
|
||||
onPageLoaded={this.onPageLoaded}
|
||||
runAutoLabelingOnNextBatch={this.runAutoLabelingOnNextBatch}
|
||||
appSettings={this.props.appSettings}
|
||||
>
|
||||
|
@ -296,6 +299,7 @@ export default class EditorPage extends React.Component<IEditorPageProps, IEdito
|
|||
lockedTags={this.state.lockedTags}
|
||||
selectedRegions={this.state.selectedRegions}
|
||||
labels={labels}
|
||||
pageNumber={this.state.pageNumber}
|
||||
onChange={this.onTagsChanged}
|
||||
onLockedTagsChange={this.onLockedTagsChanged}
|
||||
onTagClick={this.onTagClicked}
|
||||
|
@ -358,7 +362,7 @@ export default class EditorPage extends React.Component<IEditorPageProps, IEdito
|
|||
/>
|
||||
<PreventLeaving
|
||||
when={isRunningOCRs || isCanvasRunningOCR}
|
||||
message={"An OCR operation is currently in progress, are you sure you want to leave?"}
|
||||
message={"An Layout operation is currently in progress, are you sure you want to leave?"}
|
||||
/>
|
||||
<PreventLeaving
|
||||
when={isCanvasRunningAutoLabeling}
|
||||
|
@ -497,7 +501,7 @@ export default class EditorPage extends React.Component<IEditorPageProps, IEdito
|
|||
*/
|
||||
private handleTagHotKey = (event: KeyboardEvent): void => {
|
||||
const tag = this.getTagFromKeyboardEvent(event);
|
||||
const selection = this.canvas.current.getSelectedRegions();
|
||||
const selection = this.canvas?.current?.getSelectedRegions();
|
||||
|
||||
if (tag && selection.length) {
|
||||
const { format, type, documentCount, name } = tag;
|
||||
|
@ -627,6 +631,10 @@ export default class EditorPage extends React.Component<IEditorPageProps, IEdito
|
|||
await this.props.actions.saveProject(project, true, false);
|
||||
}
|
||||
|
||||
private onPageLoaded = async (pageNumber: number) => {
|
||||
this.setState({ pageNumber });
|
||||
}
|
||||
|
||||
private onLockedTagsChanged = (lockedTags: string[]) => {
|
||||
this.setState({ lockedTags });
|
||||
}
|
||||
|
@ -753,7 +761,7 @@ export default class EditorPage extends React.Component<IEditorPageProps, IEdito
|
|||
}
|
||||
}
|
||||
}
|
||||
private runAutoLabelingOnNextBatch = async () => {
|
||||
private runAutoLabelingOnNextBatch = async (batchSize: number) => {
|
||||
if (this.isBusy()) {
|
||||
return;
|
||||
}
|
||||
|
@ -764,7 +772,7 @@ export default class EditorPage extends React.Component<IEditorPageProps, IEdito
|
|||
if (this.state.assets) {
|
||||
this.setState({ isRunningAutoLabelings: true });
|
||||
const unlabeledAssetsBatch = [];
|
||||
for (let i = 0; i < this.state.assets.length && unlabeledAssetsBatch.length < constants.autoLabelBatchSize; i++) {
|
||||
for (let i = 0; i < this.state.assets.length && unlabeledAssetsBatch.length < batchSize; i++) {
|
||||
const asset = this.state.assets[i];
|
||||
if (asset.state === AssetState.NotVisited || asset.state === AssetState.Visited) {
|
||||
unlabeledAssetsBatch.push(asset);
|
||||
|
|
|
@ -142,7 +142,7 @@ export default class EditorSideBar extends React.Component<IEditorSideBarProps,
|
|||
switch (asset.state) {
|
||||
case AssetState.Tagged:
|
||||
return (
|
||||
<span title={_.startCase(AssetLabelingState[asset.labelingState])}
|
||||
<span title={_.capitalize(_.lowerCase(AssetLabelingState[asset.labelingState]))}
|
||||
className={["badge", "badge-tagged", getBadgeTaggedClass(asset.labelingState)].join(" ")}>
|
||||
<FontIcon iconName="Tag" />
|
||||
</span>
|
||||
|
|
|
@ -489,18 +489,13 @@ export default class ModelComposePage extends React.Component<IModelComposePageP
|
|||
|
||||
let nextPageList = nextPage.nextList;
|
||||
nextPageList = nextPageList.filter((model) => recentModelIds.indexOf(model.modelId) === -1);
|
||||
const newList = currentList.concat(nextPageList);
|
||||
|
||||
this.allModels = newList;
|
||||
const newCols = this.state.columns;
|
||||
newCols.forEach((ncol) => {
|
||||
ncol.isSorted = false;
|
||||
ncol.isSortedDescending = true;
|
||||
});
|
||||
const appendedList = currentList.concat(nextPageList);
|
||||
const currerntlySortedColumn: IColumn = this.state.columns.find((column) => column.isSorted);
|
||||
const appendedAndSortedList = this.copyAndSort(appendedList, currerntlySortedColumn.fieldName!, currerntlySortedColumn.isSortedDescending);
|
||||
this.allModels = appendedAndSortedList;
|
||||
this.setState({
|
||||
modelList: newList,
|
||||
modelList: appendedAndSortedList,
|
||||
nextLink: nextPage.nextLink,
|
||||
columns: newCols,
|
||||
}, () => {
|
||||
this.setState({
|
||||
isLoading: false,
|
||||
|
@ -557,7 +552,7 @@ export default class ModelComposePage extends React.Component<IModelComposePageP
|
|||
private handleColumnClick = (event: React.MouseEvent<HTMLElement>, column: IColumn): void => {
|
||||
const {columns, modelList} = this.state;
|
||||
const newColumns: IColumn[] = columns.slice();
|
||||
const currColumn: IColumn = newColumns.filter((col) => column.key === col.key)[0];
|
||||
const currColumn: IColumn = newColumns.find((col) => column.key === col.key);
|
||||
newColumns.forEach((newCol: IColumn) => {
|
||||
if (newCol === currColumn) {
|
||||
currColumn.isSortedDescending = !currColumn.isSortedDescending;
|
||||
|
|
|
@ -2,16 +2,29 @@
|
|||
// Licensed under the MIT license.
|
||||
|
||||
import React from "react";
|
||||
import { FontIcon } from "@fluentui/react";
|
||||
import { constants } from "../../../common/constants";
|
||||
import {FontIcon} from "@fluentui/react";
|
||||
import {constants} from "../../../common/constants";
|
||||
import axios from 'axios';
|
||||
import "./statusBar.scss";
|
||||
import { IProject } from "../../../models/applicationState";
|
||||
|
||||
export interface IStatusBarProps {
|
||||
project: IProject;
|
||||
}
|
||||
interface IStatusBarState {
|
||||
commitHash?: string;
|
||||
}
|
||||
export class StatusBar extends React.Component<IStatusBarProps, IStatusBarState> {
|
||||
componentDidMount() {
|
||||
const commitInfoUrl = require("../../../git-commit-info.txt");
|
||||
axios.get(commitInfoUrl).then(res => {
|
||||
// match the git commit hash
|
||||
const commitHash = /commit ([0-9a-fA-F]{7})/.exec(res?.data)[1];
|
||||
this.setState({ commitHash: commitHash || "" });
|
||||
});
|
||||
}
|
||||
|
||||
export class StatusBar extends React.Component<IStatusBarProps> {
|
||||
|
||||
// export class StatusBar extends React.Component<IStatusBarProps> {
|
||||
public render() {
|
||||
return (
|
||||
<div className="status-bar">
|
||||
|
@ -29,7 +42,7 @@ export class StatusBar extends React.Component<IStatusBarProps> {
|
|||
<li>
|
||||
<a href="https://github.com/microsoft/OCR-Form-Tools/blob/master/CHANGELOG.md" target="blank" rel="noopener noreferrer">
|
||||
<FontIcon iconName="BranchMerge" />
|
||||
<span>{constants.appVersionRaw}-1f33130</span>
|
||||
<span>{constants.appVersion}-{this.state?.commitHash}</span>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
|
|
@ -55,6 +55,7 @@ export function registerIcons() {
|
|||
StatusCircleCheckmark: "\uF13E",
|
||||
CircleRing: "\uEA3A",
|
||||
Filter: "\uE71C",
|
||||
ClearFilter: "\uEF8F",
|
||||
Table: "\uED86",
|
||||
MapLayers: "\uE81E",
|
||||
BookAnswers: "\uF8A4",
|
||||
|
@ -74,6 +75,9 @@ export function registerIcons() {
|
|||
RectangleShape: "\uF1A9",
|
||||
Rotate90CounterClockwise: "\uF80E",
|
||||
Rotate90Clockwise: "\uF80D",
|
||||
AzureAPIManagement: "\uF37F", },
|
||||
AzureAPIManagement: "\uF37F",
|
||||
GroupedList: "\uEF74",
|
||||
GroupList: "\uF168",
|
||||
},
|
||||
});
|
||||
}
|
||||
|
|
|
@ -155,7 +155,7 @@ export class OCRService {
|
|||
setTimeout(checkSucceeded, interval, resolve, reject);
|
||||
} else {
|
||||
// Didn't succeeded after too much time, reject
|
||||
reject(new Error("Timed out for getting OCR results"));
|
||||
reject(new Error("Timed out for getting Layout results"));
|
||||
}
|
||||
});
|
||||
};
|
||||
|
|
|
@ -11753,13 +11753,20 @@ serialize-javascript@^2.1.2:
|
|||
resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-2.1.2.tgz#ecec53b0e0317bdc95ef76ab7074b7384785fa61"
|
||||
integrity sha512-rs9OggEUF0V4jUSecXazOYsLfu7OGK2qIn3c7IPBiffz32XniEp/TX9Xmc9LQfK2nQ2QKHvZ2oygKUGU0lG4jQ==
|
||||
|
||||
serialize-javascript@^3.0.0, serialize-javascript@^3.1.0:
|
||||
serialize-javascript@^3.1.0:
|
||||
version "3.1.0"
|
||||
resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-3.1.0.tgz#8bf3a9170712664ef2561b44b691eafe399214ea"
|
||||
integrity sha512-JIJT1DGiWmIKhzRsG91aS6Ze4sFUrYbltlkg2onR5OrnNM02Kl/hnY/T4FN2omvyeBbQmMJv+K4cPOpGzOTFBg==
|
||||
dependencies:
|
||||
randombytes "^2.1.0"
|
||||
|
||||
serialize-javascript@^5.0.1:
|
||||
version "5.0.1"
|
||||
resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-5.0.1.tgz#7886ec848049a462467a97d3d918ebb2aaf934f4"
|
||||
integrity sha512-SaaNal9imEO737H2c05Og0/8LUXG7EnsZyMa8MzkmuHoELfT6txuj0cMqRj6zfPKnmQ1yasR4PCJc8x+M4JSPA==
|
||||
dependencies:
|
||||
randombytes "^2.1.0"
|
||||
|
||||
serve-index@^1.9.1:
|
||||
version "1.9.1"
|
||||
resolved "https://registry.yarnpkg.com/serve-index/-/serve-index-1.9.1.tgz#d3768d69b1e7d82e5ce050fff5b453bea12a9239"
|
||||
|
|
Загрузка…
Ссылка в новой задаче