merge master into RTL branch (#647)
* merge master into RTL branch * fix an API icon
This commit is contained in:
Родитель
b6557e864c
Коммит
34627adaa0
|
@ -3,3 +3,4 @@
|
|||
# without it, you'll see error like this
|
||||
# Failed to load resource: net::ERR_FILE_NOT_FOUND /favicon.ico:1
|
||||
PUBLIC_URL=
|
||||
BROWSER=none
|
38
CHANGELOG.md
38
CHANGELOG.md
|
@ -1,8 +1,44 @@
|
|||
# FoTT Changelog
|
||||
## What's new in Form Recognizer?
|
||||
Click [here](https://docs.microsoft.com/en-us/azure/cognitive-services/form-recognizer/whats-new) to see what's new in Form Recognizer.
|
||||
|
||||
## Released conatiner's currently referenced commit
|
||||
2.1-Preview's released container image (mcr.microsoft.com/azure-cognitive-services/custom-form/labeltool:2.1.012970002-amd64-preview) currently references **2.1-preview.1-0633507 (09-14-2020)**
|
||||
2.1-Preview's released container image, tracked by the `latest-preview` image tag in our [docker hub repository](https://hub.docker.com/_/microsoft-azure-cognitive-services-custom-form-labeltool), currently references **2.1-preview.1-1f33130 (10-09-2020)**
|
||||
|
||||
## Commit history
|
||||
### 2.1-preview.1-1f33130 (10-09-2020)
|
||||
* fix: support image map interactions for container releases([#639](https://github.com/microsoft/OCR-Form-Tools/commit/1f33130e3b6ad8a876f18fc1c05f82c4a14d36fa))
|
||||
|
||||
### 2.1-preview.1-6d4e93b (10-07-2020)
|
||||
* Fix: use file type library for mime type validation ([#636](https://github.com/microsoft/OCR-Form-Tools/commit/6d4e93bca8a4e3d677c765ed5596bde502766e2e))
|
||||
|
||||
### 2.1-preview.1-355ca0b (09-30-2020)
|
||||
* feat: add spinner in saving project, can avoid multiple commit ([#617](https://github.com/microsoft/OCR-Form-Tools/commit/355ca0b156b2d44aafd2eaaccf2fc52385c7f5f8))
|
||||
|
||||
### 2.1-preview.1-53044f7 (09-29-2020)
|
||||
* fix: refresh currentProjects when load project ([#615](https://github.com/microsoft/OCR-Form-Tools/commit/53044f72dd9c9c72557c74c00605ba05ee50205d))
|
||||
* sync related region color when tag color changed ([#598](https://github.com/microsoft/OCR-Form-Tools/commit/3044cc51a9166877bb4f01f28753171b82c04ccd))
|
||||
* feat: add current list item style ([#601](https://github.com/microsoft/OCR-Form-Tools/commit/3e503e75513e44e6a90bd013d8dd15c3096cd7e9))
|
||||
* fix: remove project from app if security token does not exist ([#468](https://github.com/microsoft/OCR-Form-Tools/commit/730e1963a06f038a4efa9750fcef4be6f15a8460))
|
||||
|
||||
### 2.1-preview.1-d859d38 (09-27-2020)
|
||||
* fix ,update document state when preview (#317) ([#471](https://github.com/microsoft/OCR-Form-Tools/commit/d859d38ecc1f96b194ffa130a1840f5a7d9b1a9b))
|
||||
* refactor: change the confidence value format to percentage ([#461](https://github.com/microsoft/OCR-Form-Tools/commit/e806b4e0dfcc68e6408e2130a46a318637a482a8))
|
||||
|
||||
### 2.1-preview.1-7a3f7a7 (09-25-2020)
|
||||
* security: upgrade node-forge ([#622](https://github.com/microsoft/OCR-Form-Tools/commit/7a3f7a773c8b01f443afaad89d7974a5bbb0b869))
|
||||
* fix: disable move tag and support renaming when searching ([#618](https://github.com/microsoft/OCR-Form-Tools/commit/cac1e8e6cfb2805a6540f9e80d564a0ff8be81c7))
|
||||
|
||||
### 2.1-preview.1-4163edc (09-23-2020)
|
||||
* docs: add latest tag reference to changelog ([#608](https://github.com/microsoft/OCR-Form-Tools/commit/4163edc18bc65234e263703fc829d2f297953385))
|
||||
* fix: use region instead of drawnRegion for labelType in label file ([#582](https://github.com/microsoft/OCR-Form-Tools/commit/ffafc200249a1c47698fedb279b4b55cef0190ba))
|
||||
* docs: update readme with docker hub info ([#604](https://github.com/microsoft/OCR-Form-Tools/commit/63bbea076d598d0286095fa0eca48d8c9d0ed706))
|
||||
* fix: remove opening browser for yarn start ([#605](https://github.com/microsoft/OCR-Form-Tools/commit/f6c4dc3585df71d09252a28f65e835a594389118))
|
||||
* fix: update changelog updater script ([#607](https://github.com/microsoft/OCR-Form-Tools/commit/7c4848c3a72259562c0461f0e2eadfb4a660fa64))
|
||||
|
||||
### 2.1-preview.1-f2db74e (09-17-2020)
|
||||
* docs: udpate changlog with docker image reference ([#590](https://github.com/microsoft/OCR-Form-Tools/commit/f2db74e322c32338eba3b2df06c01a51cfb7ebc1))
|
||||
|
||||
### 2.1-preview.1-1a6b78e (09-16-2020)
|
||||
* fix: normalize folder path starting with a period ([#592](https://github.com/microsoft/OCR-Form-Tools/commit/1a6b78e054235da3188aafbe65636a8c18b439bf))
|
||||
* fix: change label folder uri title ([#588](https://github.com/microsoft/OCR-Form-Tools/commit/7e4233e568d94817e23dda5ef5513b9ee7475d11))
|
||||
|
|
|
@ -38,7 +38,7 @@ Form Labeling Tool requires [NodeJS (>= 10.x, Dubnium) and NPM](https://github.c
|
|||
|
||||
### Set up this tool with Docker
|
||||
|
||||
Please see instructions [here](https://docs.microsoft.com/en-us/azure/cognitive-services/form-recognizer/quickstarts/label-tool#set-up-the-sample-labeling-tool)
|
||||
Please see instructions [here](https://docs.microsoft.com/en-us/azure/cognitive-services/form-recognizer/quickstarts/label-tool#set-up-the-sample-labeling-tool), and view our docker hub repository [here](https://hub.docker.com/_/microsoft-azure-cognitive-services-custom-form-labeltool?tab=description) for the latest container image info. The `latest-preview` and `latest` docker image tags track the preview and general availability releases of FOTT.
|
||||
|
||||
### Run as web application
|
||||
|
||||
|
|
|
@ -51,7 +51,7 @@
|
|||
"yarn": "^1.22.4"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "nf start -p 3000",
|
||||
"start": "env-cmd -f .env.electron nf start -p 3000",
|
||||
"compile": "tsc",
|
||||
"build": "react-scripts build",
|
||||
"react-start": "react-scripts start",
|
||||
|
@ -64,7 +64,7 @@
|
|||
"electron:start:dev": "yarn electron-start",
|
||||
"electron:start:prod": "yarn webpack:prod && yarn electron-start",
|
||||
"electron-start": "node src/electron/start",
|
||||
"release": "env-cmd -f .env.release yarn build && yarn webpack:prod && yarn electron-builder",
|
||||
"release": "env-cmd -f .env.electron yarn build && yarn webpack:prod && yarn electron-builder",
|
||||
"tslint": "./node_modules/.bin/tslint 'src/**/*.ts*'",
|
||||
"tslintfix": "./node_modules/.bin/tslint 'src/**/*.ts*' --fix"
|
||||
},
|
||||
|
@ -108,7 +108,9 @@
|
|||
"foreman": "^3.0.1",
|
||||
"jquery": "^3.5.0",
|
||||
"kind-of": "^6.0.3",
|
||||
"mime": "^2.4.6",
|
||||
"minimist": "^1.2.2",
|
||||
"node-forge": "^0.10.0",
|
||||
"node-sass": "^4.14.1",
|
||||
"pdfjs-dist": "^2.4.456",
|
||||
"react-scripts": "3.4.1",
|
||||
|
|
|
@ -37,15 +37,15 @@ repo = git.Repo("../")
|
|||
commits = list(repo.iter_commits("master"))
|
||||
for commit in commits:
|
||||
commitHex = commit.hexsha[:7]
|
||||
if commitHex == lastChanglogCommit:
|
||||
print("found last change log commit")
|
||||
break
|
||||
commitDate = commit.committed_datetime.strftime("%m-%d-%Y")
|
||||
if currentCommitDate != commitDate:
|
||||
if currentCommitDate is not None:
|
||||
insterIntoChanglogContents("\n")
|
||||
currentCommitDate = commitDate
|
||||
insterIntoChanglogContents("### " + appVersion + "-" + commitHex + " (" + commitDate + ")\n")
|
||||
if commitHex == lastChanglogCommit:
|
||||
print("found last change log commit")
|
||||
break
|
||||
commitMessage = commit.message.partition('\n')[0]
|
||||
commitMessageRegex = re.compile("(.*)\(\#(\d+)\)\s*$")
|
||||
match = commitMessageRegex.search(commitMessage)
|
||||
|
|
|
@ -95,7 +95,7 @@ export default class App extends React.Component<IAppProps> {
|
|||
<Sidebar project={this.props.currentProject} />
|
||||
<MainContentRouter />
|
||||
</div>
|
||||
<StatusBar>
|
||||
<StatusBar project={this.props.currentProject} >
|
||||
<StatusBarMetrics project={this.props.currentProject} />
|
||||
</StatusBar>
|
||||
<ToastContainer className="frtt-toast-container" role="alert" />
|
||||
|
|
Различия файлов скрыты, потому что одна или несколько строк слишком длинны
|
@ -3,7 +3,8 @@
|
|||
|
||||
import { appInfo } from "./appInfo"
|
||||
|
||||
const appVersionArr = appInfo.version.split(".");
|
||||
const appVersionRaw = appInfo.version
|
||||
const appVersionArr = appVersionRaw.split(".");
|
||||
appVersionArr[1] = appVersionArr[1] + "-preview";
|
||||
const appVersion = appVersionArr.join(".");
|
||||
|
||||
|
@ -14,6 +15,7 @@ const apiVersion = "v2.1-preview.1";
|
|||
*/
|
||||
export const constants = {
|
||||
version: "pubpreview_1.0",
|
||||
appVersionRaw,
|
||||
appVersion,
|
||||
apiVersion,
|
||||
projectFormTempKey: "projectForm",
|
||||
|
@ -35,6 +37,7 @@ export const constants = {
|
|||
convertedThumbnailQuality: 0.2,
|
||||
recentModelRecordsCount: 5,
|
||||
apiModelsPath: `/formrecognizer/${apiVersion}/custom/models`,
|
||||
autoLabelBatchSize: 10,
|
||||
|
||||
pdfjsWorkerSrc(version: string) {
|
||||
return `https://fotts.azureedge.net/npm/pdfjs-dist/${version}/pdf.worker.js`;
|
||||
|
|
|
@ -129,6 +129,10 @@ export const english: IAppStrings = {
|
|||
backEndNotAvailable: "Checkbox feature will work in future version of Form Recognizer service, please stay tuned.",
|
||||
addName: "Add a model name...",
|
||||
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?"
|
||||
},
|
||||
errors: {
|
||||
electron: {
|
||||
cantAccessFiles: "Cannot access files in '${folderUri}' for training. Please check if specified folder URI is correct."
|
||||
|
@ -145,7 +149,7 @@ export const english: IAppStrings = {
|
|||
composing: "Model is composing, please wait...",
|
||||
column: {
|
||||
icon: {
|
||||
name:"Composed Icon",
|
||||
name: "Composed Icon",
|
||||
},
|
||||
id: {
|
||||
headerName: "Model Id",
|
||||
|
@ -209,7 +213,7 @@ export const english: IAppStrings = {
|
|||
defaultURLInput: "Paste or type URL...",
|
||||
editAndUploadToTrainingSet: "Edit & upload to training set",
|
||||
editAndUploadToTrainingSetNotify: "by clicking on this button, this form will be added to this project, where you can edit these labels.",
|
||||
editAndUploadToTrainingSetNotify2: "We are adding this file to your training set, where you could edit the labels and re-train the model.",
|
||||
editAndUploadToTrainingSetNotify2: "We are adding this file to your training set, where you can edit the labels and re-train the model.",
|
||||
uploadInPrgoress: "Upload in progress...",
|
||||
confirmDuplicatedAssetName: {
|
||||
title: "Asset name exists",
|
||||
|
@ -264,7 +268,7 @@ export const english: IAppStrings = {
|
|||
unknownTagName: "Unknown",
|
||||
notCompatibleTagType: "Tag type is not compatible with this feature. If you want to change type of this tag, please remove or reassign all labels which using this tag in your project.",
|
||||
checkboxPerTagLimit: "Cannot assign more than one checkbox per tag",
|
||||
notCompatibleWithDrawnRegionTag: "drawnRegion and ${otherCategory} values cannot both be assigned to the same document's tag",
|
||||
notCompatibleWithDrawnRegionTag: "Drawn regions and ${otherCatagory} values cannot both be assigned to the same document's tag",
|
||||
},
|
||||
regionTableTags: {
|
||||
configureTag: {
|
||||
|
@ -476,10 +480,14 @@ export const english: IAppStrings = {
|
|||
subIMenuItems: {
|
||||
runOcrOnCurrentDocument: "Run OCR on current document",
|
||||
runOcrOnAllDocuments: "Run OCR on all documents",
|
||||
runAutoLabelingCurrentDocument: "Run AutoLabeling on current document",
|
||||
runAutoLabelingCurrentDocument: "Auto-label the current document",
|
||||
runAutoLabelingOnNotLabelingDocuments: "Auto-label next ${batchSize} unlabeled documents",
|
||||
noPredictModelOnProject: "Predict model not avaliable, please train the model first.",
|
||||
}
|
||||
}
|
||||
},
|
||||
warings: {
|
||||
drawRegionUnsupportedAPIVersion: "Region labeling is not supported with API ${apiVersion}. It will be supported with the release of v2.1-preview.3",
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -604,6 +612,10 @@ export const english: IAppStrings = {
|
|||
message: `An error occured while deleting the project.
|
||||
Validate the project file and security token exist and try again`,
|
||||
},
|
||||
projectDeleteErrorSecurityTokenNotFound: {
|
||||
title: "Security token not found when delete project",
|
||||
message: "Security Token Not Found. Project [${project.name}] has been removed from FoTT tool."
|
||||
},
|
||||
projectNotFound: {
|
||||
title: "Error loading project",
|
||||
message: "We couldn't find the project file ${file} at the target blob container ${container}.\
|
||||
|
@ -701,6 +713,10 @@ export const english: IAppStrings = {
|
|||
title: "Model not found",
|
||||
message: "Model \"${modelID}\" not found. Please use another model.",
|
||||
},
|
||||
connectionNotExistError: {
|
||||
title: "Connection doesn't exist",
|
||||
message: "Connection doesn't exist."
|
||||
},
|
||||
getOcrError: {
|
||||
title: "Cannot load OCR file",
|
||||
message: "Failed to load from OCR file. Please check your connection or network settings.",
|
||||
|
|
|
@ -130,6 +130,10 @@ export const spanish: IAppStrings = {
|
|||
backEndNotAvailable: "La función de casilla de verificación funcionará en la versión futura del servicio de reconocimiento de formularios, manténgase atento.",
|
||||
addName: "Agregar nombre de modelo ...",
|
||||
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?"
|
||||
},
|
||||
errors: {
|
||||
electron: {
|
||||
cantAccessFiles: "No se puede acceder a los archivos en '${folderUri}' para entrenamiento. Compruebe si el URI de la carpeta especificada es correcto."
|
||||
|
@ -451,7 +455,7 @@ export const spanish: IAppStrings = {
|
|||
},
|
||||
canvasCommandBar: {
|
||||
items: {
|
||||
layers:{
|
||||
layers: {
|
||||
text: "Capas",
|
||||
subMenuItems: {
|
||||
text: "Texto",
|
||||
|
@ -477,10 +481,14 @@ export const spanish: IAppStrings = {
|
|||
subIMenuItems: {
|
||||
runOcrOnCurrentDocument: "Ejecutar OCR en el documento actual",
|
||||
runOcrOnAllDocuments: "Ejecute OCR en todos los documentos",
|
||||
runAutoLabelingCurrentDocument: "Ejecutar AutoLabeling en el documento actual",
|
||||
runAutoLabelingCurrentDocument: "Etiquetar automáticamente el documento actual",
|
||||
runAutoLabelingOnNotLabelingDocuments: "Etiquetar automáticamente los siguientes ${batchSize} documentos sin etiquetar",
|
||||
noPredictModelOnProject: "Predecir modelo no disponible, entrene el modelo primero.",
|
||||
}
|
||||
}
|
||||
},
|
||||
warings: {
|
||||
drawRegionUnsupportedAPIVersion: "Las regiones de dibujo no son compatibles con la versión de API ${apiVersion}. Será compatible con el lanzamiento de v2.1-preview.3",
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -604,6 +612,10 @@ export const spanish: IAppStrings = {
|
|||
message: `Se ha producido un error al eliminar el proyecto.
|
||||
Validar el archivo de proyecto y el token de seguridad existen e inténtelo de nuevo`,
|
||||
},
|
||||
projectDeleteErrorSecurityTokenNotFound: {
|
||||
title: 'No se encontró el token de seguridad al eliminar el proyecto',
|
||||
message: "Token de seguridad no encontrado. El proyecto [$ {project.name}] se ha eliminado de la herramienta FoTT."
|
||||
},
|
||||
projectNotFound: {
|
||||
title: "",
|
||||
message: "",
|
||||
|
@ -701,6 +713,10 @@ export const spanish: IAppStrings = {
|
|||
title: "Modelo no encontrado",
|
||||
message: "Modelo \"${modelID}\" no encontrado. Por favor use otro modelo.",
|
||||
},
|
||||
connectionNotExistError: {
|
||||
title: "La conexión no existe",
|
||||
message: "La conexión no existe."
|
||||
},
|
||||
getOcrError: {
|
||||
title: "No se puede cargar el archivo OCR",
|
||||
message: "Error al cargar desde el archivo OCR. Verifique su conexión o configuración de red."
|
||||
|
|
|
@ -319,6 +319,7 @@ export default class MockFactory {
|
|||
createContainer: jest.fn(),
|
||||
deleteContainer: jest.fn(),
|
||||
getAssets: jest.fn(),
|
||||
isFileExists: jest.fn(),
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -129,6 +129,10 @@ export interface IAppStrings {
|
|||
backEndNotAvailable: string,
|
||||
addName: string,
|
||||
downloadJson: string;
|
||||
trainConfirm: {
|
||||
title: string;
|
||||
message: string;
|
||||
},
|
||||
errors: {
|
||||
electron: {
|
||||
cantAccessFiles: string;
|
||||
|
@ -472,9 +476,13 @@ export interface IAppStrings {
|
|||
runOcrOnCurrentDocument: string,
|
||||
runOcrOnAllDocuments: string,
|
||||
runAutoLabelingCurrentDocument: string,
|
||||
runAutoLabelingOnNotLabelingDocuments: string,
|
||||
noPredictModelOnProject: string,
|
||||
}
|
||||
}
|
||||
},
|
||||
warings: {
|
||||
drawRegionUnsupportedAPIVersion: string,
|
||||
}
|
||||
},
|
||||
},
|
||||
|
@ -576,6 +584,7 @@ export interface IAppStrings {
|
|||
projectInvalidSecurityToken: IErrorMetadata,
|
||||
projectUploadError: IErrorMetadata,
|
||||
projectDeleteError: IErrorMetadata,
|
||||
projectDeleteErrorSecurityTokenNotFound: IErrorMetadata,
|
||||
projectNotFound: IErrorMetadata,
|
||||
genericRenderError: IErrorMetadata,
|
||||
securityTokenNotFound: IErrorMetadata,
|
||||
|
@ -600,6 +609,7 @@ export interface IAppStrings {
|
|||
modelCountLimitExceeded: IErrorMetadata,
|
||||
requestSendError: IErrorMetadata,
|
||||
modelNotFound: IErrorMetadata,
|
||||
connectionNotExistError: IErrorMetadata,
|
||||
getOcrError: IErrorMetadata,
|
||||
};
|
||||
shareProject: {
|
||||
|
|
|
@ -7,217 +7,37 @@
|
|||
"hashFontFileName": true,
|
||||
"glyphs": [
|
||||
{
|
||||
"name": "Table",
|
||||
"unicode": "ED86"
|
||||
},
|
||||
{
|
||||
"name": "TextField",
|
||||
"unicode": "EDC3"
|
||||
},
|
||||
{
|
||||
"name": "OpenFolderHorizontal",
|
||||
"unicode": "ED25"
|
||||
},
|
||||
{
|
||||
"name": "Documentation",
|
||||
"unicode": "EC17"
|
||||
},
|
||||
{
|
||||
"name": "AddTo",
|
||||
"unicode": "ECC8"
|
||||
},
|
||||
{
|
||||
"name": "SortUp",
|
||||
"unicode": "EE68"
|
||||
},
|
||||
{
|
||||
"name": "SortDown",
|
||||
"unicode": "EE69"
|
||||
},
|
||||
{
|
||||
"name": "Info",
|
||||
"unicode": "E946"
|
||||
},
|
||||
{
|
||||
"name": "ChromeMinimize",
|
||||
"unicode": "E921"
|
||||
},
|
||||
{
|
||||
"name": "ChromeRestore",
|
||||
"unicode": "E923"
|
||||
},
|
||||
{
|
||||
"name": "Label",
|
||||
"unicode": "E932"
|
||||
},
|
||||
{
|
||||
"name": "Copy",
|
||||
"unicode": "E8C8"
|
||||
},
|
||||
{
|
||||
"name": "Rename",
|
||||
"unicode": "E8AC"
|
||||
},
|
||||
{
|
||||
"name": "Download",
|
||||
"unicode": "E896"
|
||||
},
|
||||
{
|
||||
"name": "Help",
|
||||
"unicode": "E897"
|
||||
},
|
||||
{
|
||||
"name": "ZoomIn",
|
||||
"unicode": "E8A3"
|
||||
},
|
||||
{
|
||||
"name": "Tag",
|
||||
"unicode": "E8EC"
|
||||
},
|
||||
{
|
||||
"name": "CircleRing",
|
||||
"unicode": "EA3A"
|
||||
},
|
||||
{
|
||||
"name": "SquareShape",
|
||||
"unicode": "F1A6"
|
||||
},
|
||||
{
|
||||
"name": "RectangleShape",
|
||||
"unicode": "F1A9"
|
||||
},
|
||||
{
|
||||
"name": "DocumentManagement",
|
||||
"unicode": "EFFC"
|
||||
},
|
||||
{
|
||||
"name": "Relationship",
|
||||
"unicode": "F003"
|
||||
},
|
||||
{
|
||||
"name": "TextDocument",
|
||||
"unicode": "F029"
|
||||
},
|
||||
{
|
||||
"name": "StatusCircleCheckmark",
|
||||
"unicode": "F13E"
|
||||
},
|
||||
{
|
||||
"name": "PlugConnected",
|
||||
"unicode": "F302"
|
||||
},
|
||||
{
|
||||
"name": "Plug",
|
||||
"unicode": "F300"
|
||||
},
|
||||
{
|
||||
"name": "AlertSolid",
|
||||
"unicode": "F331"
|
||||
},
|
||||
{
|
||||
"name": "BranchMerge",
|
||||
"unicode": "F295"
|
||||
},
|
||||
{
|
||||
"name": "View",
|
||||
"unicode": "E890"
|
||||
},
|
||||
{
|
||||
"name": "ReceiptProcessing",
|
||||
"unicode": "E496"
|
||||
"name": "Add",
|
||||
"unicode": "E710"
|
||||
},
|
||||
{
|
||||
"name": "AddField",
|
||||
"unicode": "E4C7"
|
||||
},
|
||||
{
|
||||
"name": "TagGroup",
|
||||
"unicode": "E3F6"
|
||||
"name": "AddTo",
|
||||
"unicode": "ECC8"
|
||||
},
|
||||
{
|
||||
"name": "Insights",
|
||||
"unicode": "E3AF"
|
||||
"name": "AlertSolid",
|
||||
"unicode": "F331"
|
||||
},
|
||||
{
|
||||
"name": "MachineLearning",
|
||||
"unicode": "E3B8"
|
||||
"name": "AzureAPIManagement",
|
||||
"unicode": "F37F"
|
||||
},
|
||||
{
|
||||
"name": "Merge",
|
||||
"unicode": "E7D5"
|
||||
"name": "BookAnswers",
|
||||
"unicode": "F8A4"
|
||||
},
|
||||
{
|
||||
"name": "MapLayers",
|
||||
"unicode": "E81E"
|
||||
},
|
||||
{
|
||||
"name": "Home",
|
||||
"unicode": "E80F"
|
||||
},
|
||||
{
|
||||
"name": "ZoomOut",
|
||||
"unicode": "E71F"
|
||||
},
|
||||
{
|
||||
"name": "Search",
|
||||
"unicode": "E721"
|
||||
},
|
||||
{
|
||||
"name": "Refresh",
|
||||
"unicode": "E72C"
|
||||
},
|
||||
{
|
||||
"name": "Share",
|
||||
"unicode": "E72D"
|
||||
},
|
||||
{
|
||||
"name": "Link",
|
||||
"unicode": "E71B"
|
||||
},
|
||||
{
|
||||
"name": "ChevronDown",
|
||||
"unicode": "E70D"
|
||||
},
|
||||
{
|
||||
"name": "ChevronUp",
|
||||
"unicode": "E70E"
|
||||
},
|
||||
{
|
||||
"name": "Edit",
|
||||
"unicode": "E70F"
|
||||
},
|
||||
{
|
||||
"name": "Add",
|
||||
"unicode": "E710"
|
||||
"name": "BranchMerge",
|
||||
"unicode": "F295"
|
||||
},
|
||||
{
|
||||
"name": "Cancel",
|
||||
"unicode": "E711"
|
||||
},
|
||||
{
|
||||
"name": "More",
|
||||
"unicode": "E712"
|
||||
},
|
||||
{
|
||||
"name": "Settings",
|
||||
"unicode": "E713"
|
||||
},
|
||||
{
|
||||
"name": "Filter",
|
||||
"unicode": "E71C"
|
||||
},
|
||||
{
|
||||
"name": "ChevronLeft",
|
||||
"unicode": "E76B"
|
||||
},
|
||||
{
|
||||
"name": "ChevronRight",
|
||||
"unicode": "E76C"
|
||||
},
|
||||
{
|
||||
"name": "System",
|
||||
"unicode": "E770"
|
||||
},
|
||||
{
|
||||
"name": "CheckboxComposite",
|
||||
"unicode": "E73A"
|
||||
|
@ -227,36 +47,228 @@
|
|||
"unicode": "E73E"
|
||||
},
|
||||
{
|
||||
"name": "Down",
|
||||
"unicode": "E74B"
|
||||
"name": "ChevronDown",
|
||||
"unicode": "E70D"
|
||||
},
|
||||
{
|
||||
"name": "Delete",
|
||||
"unicode": "E74D"
|
||||
"name": "ChevronLeft",
|
||||
"unicode": "E76B"
|
||||
},
|
||||
{
|
||||
"name": "ChevronRight",
|
||||
"unicode": "E76C"
|
||||
},
|
||||
{
|
||||
"name": "ChevronUp",
|
||||
"unicode": "E70E"
|
||||
},
|
||||
{
|
||||
"name": "ChromeMinimize",
|
||||
"unicode": "E921"
|
||||
},
|
||||
{
|
||||
"name": "ChromeRestore",
|
||||
"unicode": "E923"
|
||||
},
|
||||
{
|
||||
"name": "CircleRing",
|
||||
"unicode": "EA3A"
|
||||
},
|
||||
{
|
||||
"name": "Cloud",
|
||||
"unicode": "E753"
|
||||
},
|
||||
{
|
||||
"name": "Up",
|
||||
"unicode": "E74A"
|
||||
"name": "Copy",
|
||||
"unicode": "E8C8"
|
||||
},
|
||||
{
|
||||
"name": "KeyPhraseExtraction",
|
||||
"unicode": "E395"
|
||||
"name": "Delete",
|
||||
"unicode": "E74D"
|
||||
},
|
||||
{
|
||||
"name": "Documentation",
|
||||
"unicode": "EC17"
|
||||
},
|
||||
{
|
||||
"name": "DocumentManagement",
|
||||
"unicode": "EFFC"
|
||||
},
|
||||
{
|
||||
"name": "Down",
|
||||
"unicode": "E74B"
|
||||
},
|
||||
{
|
||||
"name": "Download",
|
||||
"unicode": "E896"
|
||||
},
|
||||
{
|
||||
"name": "Edit",
|
||||
"unicode": "E70F"
|
||||
},
|
||||
{
|
||||
"name": "Filter",
|
||||
"unicode": "E71C"
|
||||
},
|
||||
{
|
||||
"name": "Help",
|
||||
"unicode": "E897"
|
||||
},
|
||||
{
|
||||
"name": "Hide3",
|
||||
"unicode": "F6AC"
|
||||
},
|
||||
{
|
||||
"name": "Home",
|
||||
"unicode": "E80F"
|
||||
},
|
||||
{
|
||||
"name": "Info",
|
||||
"unicode": "E946"
|
||||
},
|
||||
{
|
||||
"name": "Insights",
|
||||
"unicode": "E3AF"
|
||||
},
|
||||
{
|
||||
"name": "KeyPhraseExtraction",
|
||||
"unicode": "E395"
|
||||
},
|
||||
{
|
||||
"name": "Label",
|
||||
"unicode": "E932"
|
||||
},
|
||||
{
|
||||
"name": "Link",
|
||||
"unicode": "E71B"
|
||||
},
|
||||
{
|
||||
"name": "MachineLearning",
|
||||
"unicode": "E3B8"
|
||||
},
|
||||
{
|
||||
"name": "MapLayers",
|
||||
"unicode": "E81E"
|
||||
},
|
||||
{
|
||||
"name": "Merge",
|
||||
"unicode": "E7D5"
|
||||
},
|
||||
{
|
||||
"name": "More",
|
||||
"unicode": "E712"
|
||||
},
|
||||
{
|
||||
"name": "OpenFolderHorizontal",
|
||||
"unicode": "ED25"
|
||||
},
|
||||
{
|
||||
"name": "Plug",
|
||||
"unicode": "F300"
|
||||
},
|
||||
{
|
||||
"name": "PlugConnected",
|
||||
"unicode": "F302"
|
||||
},
|
||||
{
|
||||
"name": "ReceiptProcessing",
|
||||
"unicode": "E496"
|
||||
},
|
||||
{
|
||||
"name": "RectangleShape",
|
||||
"unicode": "F1A9"
|
||||
},
|
||||
{
|
||||
"name": "Refresh",
|
||||
"unicode": "E72C"
|
||||
},
|
||||
{
|
||||
"name": "Relationship",
|
||||
"unicode": "F003"
|
||||
},
|
||||
{
|
||||
"name": "Rename",
|
||||
"unicode": "E8AC"
|
||||
},
|
||||
{
|
||||
"name": "Rotate90Clockwise",
|
||||
"unicode": "F80D"
|
||||
},
|
||||
{
|
||||
"name": "Rotate90CounterClockwise",
|
||||
"unicode": "F80E"
|
||||
},
|
||||
{
|
||||
"name": "Search",
|
||||
"unicode": "E721"
|
||||
},
|
||||
{
|
||||
"name": "Settings",
|
||||
"unicode": "E713"
|
||||
},
|
||||
{
|
||||
"name": "Share",
|
||||
"unicode": "E72D"
|
||||
},
|
||||
{
|
||||
"name": "SortDown",
|
||||
"unicode": "EE69"
|
||||
},
|
||||
{
|
||||
"name": "SortUp",
|
||||
"unicode": "EE68"
|
||||
},
|
||||
{
|
||||
"name": "SquareShape",
|
||||
"unicode": "F1A6"
|
||||
},
|
||||
{
|
||||
"name": "StatusCircleCheckmark",
|
||||
"unicode": "F13E"
|
||||
},
|
||||
{
|
||||
"name": "System",
|
||||
"unicode": "E770"
|
||||
},
|
||||
{
|
||||
"name": "Table",
|
||||
"unicode": "ED86"
|
||||
},
|
||||
{
|
||||
"name": "Tag",
|
||||
"unicode": "E8EC"
|
||||
},
|
||||
{
|
||||
"name": "TagGroup",
|
||||
"unicode": "E3F6"
|
||||
},
|
||||
{
|
||||
"name": "TextDocument",
|
||||
"unicode": "F029"
|
||||
},
|
||||
{
|
||||
"name": "TextField",
|
||||
"unicode": "EDC3"
|
||||
},
|
||||
{
|
||||
"name": "Up",
|
||||
"unicode": "E74A"
|
||||
},
|
||||
{
|
||||
"name": "View",
|
||||
"unicode": "E890"
|
||||
},
|
||||
{
|
||||
"name": "WarningSolid",
|
||||
"unicode": "F736"
|
||||
},
|
||||
{
|
||||
"name": "BookAnswers",
|
||||
"unicode": "F8A4"
|
||||
"name": "ZoomIn",
|
||||
"unicode": "E8A3"
|
||||
},
|
||||
{
|
||||
"name": "ZoomOut",
|
||||
"unicode": "E71F"
|
||||
}
|
||||
]
|
||||
}
|
|
@ -123,6 +123,10 @@ export default class LocalFileSystem implements IStorageProvider {
|
|||
return this.listItems(path.normalize(folderPath), (stats) => !stats.isDirectory());
|
||||
}
|
||||
|
||||
public isFileExists(filePath: string): Promise<boolean> {
|
||||
return Promise.resolve(fs.existsSync(path.normalize(filePath)));
|
||||
}
|
||||
|
||||
public listContainers(folderPath: string): Promise<string[]> {
|
||||
return this.listItems(path.normalize(folderPath), (stats) => stats.isDirectory());
|
||||
}
|
||||
|
|
|
@ -93,6 +93,7 @@ export interface IProject {
|
|||
lastVisitedAssetId?: string,
|
||||
apiUriBase: string,
|
||||
apiKey?: string | ISecureString,
|
||||
apiVersion?: string;
|
||||
folderPath: string,
|
||||
trainRecord: ITrainRecordProps,
|
||||
recentModelRecords: IRecentModel[],
|
||||
|
@ -166,6 +167,7 @@ export interface IAsset {
|
|||
id: string,
|
||||
type: AssetType,
|
||||
state: AssetState,
|
||||
labelingState?: AssetLabelingState,
|
||||
name: string,
|
||||
path: string,
|
||||
size: ISize,
|
||||
|
@ -174,7 +176,9 @@ export interface IAsset {
|
|||
predicted?: boolean,
|
||||
ocr?: any,
|
||||
isRunningOCR?: boolean,
|
||||
isRunningAutoLabeling?: boolean,
|
||||
cachedImage?: string,
|
||||
mimeType?: string,
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -219,6 +223,8 @@ export interface IRegion {
|
|||
value?: string,
|
||||
pageNumber: number,
|
||||
isTableRegion?: boolean,
|
||||
changed?: boolean,
|
||||
|
||||
}
|
||||
|
||||
export interface ITableRegion extends IRegion {
|
||||
|
@ -232,6 +238,7 @@ export interface ITableRegion extends IRegion {
|
|||
*/
|
||||
export interface ILabelData {
|
||||
document: string,
|
||||
labelingState?: AssetLabelingState;
|
||||
labels: ILabel[],
|
||||
tableLabels?: ITableLabel[],
|
||||
}
|
||||
|
@ -245,6 +252,7 @@ export interface ILabel {
|
|||
key?: IFormRegion[],
|
||||
value: IFormRegion[],
|
||||
labelType?: string,
|
||||
confidence?: number,
|
||||
}
|
||||
|
||||
export interface ITableLabel {
|
||||
|
@ -356,6 +364,12 @@ export enum ErrorCode {
|
|||
ProjectUploadError = "ProjectUploadError",
|
||||
}
|
||||
|
||||
export enum APIVersionPatches {
|
||||
patch1 = "v2.1-preview.1",
|
||||
patch2 = "v2.1-preview.2",
|
||||
patch3 = "v2.1-preview.3",
|
||||
}
|
||||
|
||||
/**
|
||||
* @enum LOCAL - Local storage type
|
||||
* @enum CLOUD - Cloud storage type
|
||||
|
@ -381,6 +395,14 @@ export enum AssetType {
|
|||
TIFF = 6,
|
||||
}
|
||||
|
||||
export enum AssetMimeType {
|
||||
PDF = "application/pdf",
|
||||
TIFF = "image/tiff",
|
||||
JPG = "image/jpg",
|
||||
PNG = "image/png",
|
||||
BMP = "image/bmp",
|
||||
}
|
||||
|
||||
/**
|
||||
* @name - Asset State
|
||||
* @description - Defines the state of the asset with regard to the tagging process
|
||||
|
@ -393,6 +415,20 @@ export enum AssetState {
|
|||
Visited = 1,
|
||||
Tagged = 2,
|
||||
}
|
||||
/**
|
||||
* @name - Asset Labeling State
|
||||
* @description - Defines the labeling state for the asset
|
||||
* @member ManualLabeling - Specifies as an asset that has manual labeling the tags
|
||||
* @member Training - Specifies as an asset tagged data has been used for training model
|
||||
* @member AutoLabeling - Specifies as an asset that has run auto-labeling
|
||||
* @member AutoLabeledAndAdjusted -specifies as an asset that has run auto-labeling and tags manual adjusted
|
||||
*/
|
||||
export enum AssetLabelingState {
|
||||
ManuallyLabeled = 1,
|
||||
Trained = 2,
|
||||
AutoLabeled = 3,
|
||||
AutoLabeledAndAdjusted = 4,
|
||||
}
|
||||
|
||||
/**
|
||||
* @name - Region Type
|
||||
|
@ -430,7 +466,7 @@ export enum FieldType {
|
|||
}
|
||||
|
||||
export enum LabelType {
|
||||
DrawnRegion = "drawnRegion"
|
||||
DrawnRegion = "region"
|
||||
}
|
||||
|
||||
export enum FieldFormat {
|
||||
|
@ -451,7 +487,7 @@ export enum FeatureCategory {
|
|||
Text = "text",
|
||||
Checkbox = "checkbox",
|
||||
Label = "label",
|
||||
DrawnRegion = "drawnRegion"
|
||||
DrawnRegion = "region"
|
||||
}
|
||||
|
||||
export enum ImageMapParent {
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
import { BlobServiceClient, ContainerClient } from "@azure/storage-blob";
|
||||
import { constants } from "../../common/constants";
|
||||
import { strings } from "../../common/strings";
|
||||
import { AppError, AssetState, AssetType, ErrorCode, IAsset, StorageType } from "../../models/applicationState";
|
||||
import { AppError, AssetState, AssetType, ErrorCode, IAsset, StorageType, ILabelData, AssetLabelingState } from "../../models/applicationState";
|
||||
import { throwUnhandledRejectionForEdge } from "../../react/components/common/errorHandler/errorHandler";
|
||||
import { AssetService } from "../../services/assetService";
|
||||
import { IStorageProvider } from "./storageProviderFactory";
|
||||
|
@ -150,6 +150,14 @@ export class AzureBlobStorage implements IStorageProvider {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* check file is exists
|
||||
* @param filePath
|
||||
*/
|
||||
public async isFileExists(filePath: string) :Promise<boolean> {
|
||||
const client = this.containerClient.getBlobClient(filePath);
|
||||
return await client.exists();
|
||||
}
|
||||
/**
|
||||
* Lists the containers with in the Azure Blob Storage account
|
||||
* @param path - NOT USED IN CURRENT IMPLEMENTATION. Lists containers in storage account.
|
||||
|
@ -206,12 +214,17 @@ export class AzureBlobStorage implements IStorageProvider {
|
|||
|
||||
if (files.find((str) => str === labelFileName)) {
|
||||
asset.state = AssetState.Tagged;
|
||||
const labelFileName = decodeURIComponent(`${asset.name}${constants.labelFileExtension}`);
|
||||
const json = await this.readText(labelFileName, true);
|
||||
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 {
|
||||
asset.state = AssetState.NotVisited;
|
||||
}
|
||||
|
||||
result.push(asset);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -110,6 +110,15 @@ export class LocalFileSystemProxy implements IStorageProvider, IAssetProvider {
|
|||
return IpcRendererProxy.send(`${PROXY_NAME}:listFiles`, [folderPath]);
|
||||
}
|
||||
|
||||
/**
|
||||
* check file is exists
|
||||
* @param fileName Name of target file
|
||||
*/
|
||||
public isFileExists(fileName: string): Promise<boolean> {
|
||||
const filePath = [this.options.folderPath, fileName].join("/");
|
||||
return IpcRendererProxy.send(`${PROXY_NAME}:isFileExists`, [filePath]);
|
||||
}
|
||||
|
||||
/**
|
||||
* List directories inside another directory
|
||||
* @param folderName - Directory from which to list directories
|
||||
|
|
|
@ -48,6 +48,9 @@ class TestStorageProvider implements IStorageProvider {
|
|||
public listFiles(folderPath?: string): Promise<string[]> {
|
||||
throw new Error("Method not implemented.");
|
||||
}
|
||||
isFileExists(filepath: string): Promise<boolean> {
|
||||
throw new Error("Method not implemented.");
|
||||
}
|
||||
public listContainers(folderPath?: string): Promise<string[]> {
|
||||
throw new Error("Method not implemented.");
|
||||
}
|
||||
|
|
|
@ -35,6 +35,7 @@ export interface IStorageProvider extends IAssetProvider {
|
|||
isValidProjectConnection(filepath?): Promise<boolean>;
|
||||
|
||||
listFiles(folderPath?: string, ext?: string): Promise<string[]>;
|
||||
isFileExists(filepath: string): Promise<boolean>;
|
||||
listContainers(folderPath?: string): Promise<string[]>;
|
||||
|
||||
createContainer(folderPath: string): Promise<void>;
|
||||
|
|
|
@ -0,0 +1,46 @@
|
|||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
import React, { SyntheticEvent } from "react";
|
||||
import { APIVersionPatches } from "../../../../models/applicationState";
|
||||
|
||||
/**
|
||||
* api version Picker Properties
|
||||
* @member id - The id to bind to the input element
|
||||
* @member value - The value to bind to the input element
|
||||
* @member onChange - The event handler to call when the input value changes
|
||||
*/
|
||||
export interface IAPIVersionPickerProps {
|
||||
id?: string;
|
||||
value: string;
|
||||
onChange: (value: string) => void;
|
||||
}
|
||||
|
||||
/**
|
||||
* api version Picker
|
||||
*/
|
||||
export class APIVersionPicker extends React.Component<IAPIVersionPickerProps> {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.onChange = this.onChange.bind(this);
|
||||
}
|
||||
|
||||
public render() {
|
||||
return (
|
||||
<select id={this.props.id}
|
||||
className="form-control"
|
||||
value={this.props.value}
|
||||
onChange={this.onChange}>
|
||||
<option value={APIVersionPatches.patch1}>{APIVersionPatches.patch1}</option>
|
||||
<option value={APIVersionPatches.patch2}>{APIVersionPatches.patch2}</option>
|
||||
<option value={APIVersionPatches.patch3}>{APIVersionPatches.patch3 + " (testing)"}</option>
|
||||
</select>
|
||||
);
|
||||
}
|
||||
|
||||
private onChange(e: SyntheticEvent) {
|
||||
const inputElement = e.target as HTMLSelectElement;
|
||||
this.props.onChange(inputElement.value ? inputElement.value : "2.1-preview.3");
|
||||
}
|
||||
}
|
|
@ -110,7 +110,15 @@ 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 OCR..." ariaLive="off" labelPosition="right" />
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
{this.props.asset.isRunningAutoLabeling &&
|
||||
<div className="asset-loading">
|
||||
<div className="asset-loading-ocr-spinner">
|
||||
<Label className="p-0" ></Label>
|
||||
<Spinner size={SpinnerSize.small} label="Auto Labeling..." ariaLive="off" labelPosition="right" />
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
|
|
@ -42,6 +42,9 @@ ul.condensed-list-items {
|
|||
&.active, &:hover {
|
||||
background-color: $lighter-2;
|
||||
}
|
||||
&.current{
|
||||
background-color: $lighter-3;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -27,13 +27,18 @@ interface ICondensedListProps {
|
|||
onDelete?: (item) => void;
|
||||
}
|
||||
|
||||
interface ICondensedListState {
|
||||
currentId: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* @name - Condensed List
|
||||
* @description - Clickable, deletable and linkable list of items
|
||||
*/
|
||||
export default class CondensedList extends React.Component<ICondensedListProps> {
|
||||
export default class CondensedList extends React.Component<ICondensedListProps, ICondensedListState> {
|
||||
constructor(props, context) {
|
||||
super(props, context);
|
||||
this.state = { currentId: null };
|
||||
|
||||
this.onItemClick = this.onItemClick.bind(this);
|
||||
this.onItemDelete = this.onItemDelete.bind(this);
|
||||
|
@ -66,6 +71,7 @@ export default class CondensedList extends React.Component<ICondensedListProps>
|
|||
<ul className="condensed-list-items">
|
||||
{items.map((item) => <Component key={item.id}
|
||||
item={item}
|
||||
currentId={this.state.currentId}
|
||||
onClick={(e) => this.onItemClick(e, item)}
|
||||
onDelete={(e) => this.onItemDelete(e, item)} />)}
|
||||
</ul>
|
||||
|
@ -79,6 +85,7 @@ export default class CondensedList extends React.Component<ICondensedListProps>
|
|||
if (this.props.onClick) {
|
||||
this.props.onClick(item);
|
||||
}
|
||||
this.setState({ currentId: item.id });
|
||||
}
|
||||
|
||||
private onItemDelete = (e: SyntheticEvent, item) => {
|
||||
|
@ -95,11 +102,11 @@ export default class CondensedList extends React.Component<ICondensedListProps>
|
|||
* Generic list item with an onClick function and a name
|
||||
* @param param0 - {item: {name: ""}, onClick: (item) => void;}
|
||||
*/
|
||||
export function ListItem({ item, onClick }) {
|
||||
export function ListItem({ item, onClick, currentId }) {
|
||||
return (
|
||||
<li>
|
||||
{/* eslint-disable-next-line */}
|
||||
<a className="condensed-list-item" onClick={onClick}>
|
||||
<a className={["condensed-list-item", currentId === item.id? "current":""].join(" ")} onClick={onClick}>
|
||||
<span className="px-2">{item.name}</span>
|
||||
</a>
|
||||
</li>
|
||||
|
|
|
@ -43,6 +43,7 @@ interface IImageMapProps {
|
|||
|
||||
enableFeatureSelection?: boolean;
|
||||
handleFeatureSelect?: (feature: any, isTaggle: boolean, category: FeatureCategory) => void;
|
||||
handleFeatureDoubleClick?: (feature: any, isTaggle: boolean, category: FeatureCategory) => void;
|
||||
groupSelectMode?: boolean;
|
||||
handleIsPointerOnImage?: (isPointerOnImage: boolean) => void;
|
||||
isPointerOnImage?: boolean;
|
||||
|
@ -84,7 +85,7 @@ export class ImageMap extends React.Component<IImageMapProps> {
|
|||
private modify: Modify;
|
||||
private snap: Snap;
|
||||
|
||||
private drawnFeatures: Collection = new Collection([], {unique: true});
|
||||
private drawnFeatures: Collection = new Collection([], { unique: true });
|
||||
public modifyStartFeatureCoordinates: any = {};
|
||||
|
||||
private imageExtent: number[];
|
||||
|
@ -198,7 +199,7 @@ export class ImageMap extends React.Component<IImageMapProps> {
|
|||
onMouseEnter={this.handlePointerEnterImageMap}
|
||||
className="map-wrapper"
|
||||
>
|
||||
<div style={{cursor: this.getCursor()}} id="map" className="map" ref={(el) => this.mapElement = el}/>
|
||||
<div style={{ cursor: this.getCursor() }} id="map" className="map" ref={(el) => this.mapElement = el} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -361,7 +362,7 @@ export class ImageMap extends React.Component<IImageMapProps> {
|
|||
*/
|
||||
public addInteraction = (interaction: Interaction) => {
|
||||
if (undefined === this.map.getInteractions().array_.find((existingInteraction) => {
|
||||
return interaction.constructor.name === existingInteraction.constructor.name
|
||||
return interaction.constructor === existingInteraction.constructor;
|
||||
})) {
|
||||
this.map.addInteraction(interaction);
|
||||
}
|
||||
|
@ -477,7 +478,7 @@ export class ImageMap extends React.Component<IImageMapProps> {
|
|||
this.drawRegionVectorLayer?.getSource().clear();
|
||||
this.drawnLabelVectorLayer?.getSource().clear();
|
||||
|
||||
this.drawnFeatures = new Collection([], {unique: true});
|
||||
this.drawnFeatures = new Collection([], { unique: true });
|
||||
|
||||
this.drawRegionVectorLayer.getSource().on("addfeature", (evt) => {
|
||||
this.pushToDrawnFeatures(evt.feature, this.drawnFeatures);
|
||||
|
@ -516,7 +517,7 @@ export class ImageMap extends React.Component<IImageMapProps> {
|
|||
*/
|
||||
public removeInteraction = (interaction: Interaction) => {
|
||||
const existingInteraction = this.map.getInteractions().array_.find((existingInteraction) => {
|
||||
return interaction.constructor.name === existingInteraction.constructor.name
|
||||
return interaction.constructor === existingInteraction.constructor;
|
||||
});
|
||||
|
||||
if (existingInteraction !== undefined) {
|
||||
|
@ -577,6 +578,7 @@ export class ImageMap extends React.Component<IImageMapProps> {
|
|||
this.map.on("pointermove", this.handlePointerMove);
|
||||
this.map.on("pointermove", this.handlePointerMoveOnTableIcon);
|
||||
this.map.on("pointerup", this.handlePointerUp);
|
||||
this.map.on("dblclick", this.handleDoubleClick);
|
||||
|
||||
this.initializeDefaultSelectionMode();
|
||||
this.initializeDragPan();
|
||||
|
@ -652,7 +654,7 @@ export class ImageMap extends React.Component<IImageMapProps> {
|
|||
const filter = this.getLayerFilterAtPixel(eventPixel);
|
||||
|
||||
const isPixelOnFeature = !!filter;
|
||||
if (isPixelOnFeature) {
|
||||
if (isPixelOnFeature && !this.props.isSnapped) {
|
||||
this.setDragPanInteraction(false);
|
||||
}
|
||||
|
||||
|
@ -666,6 +668,20 @@ export class ImageMap extends React.Component<IImageMapProps> {
|
|||
);
|
||||
}
|
||||
}
|
||||
private handleDoubleClick = (event: MapBrowserEvent) => {
|
||||
const eventPixel = this.map.getEventPixel(event.originalEvent);
|
||||
|
||||
const filter = this.getLayerFilterAtPixel(eventPixel);
|
||||
if (filter && this.props.handleFeatureDoubleClick) {
|
||||
this.map.forEachFeatureAtPixel(
|
||||
eventPixel,
|
||||
(feature) => {
|
||||
this.props.handleFeatureDoubleClick(feature, true, filter.category);
|
||||
},
|
||||
filter.layerfilter,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private getLayerFilterAtPixel = (eventPixel: any) => {
|
||||
const isPointerOnLabelledFeature = this.map.hasFeatureAtPixel(
|
||||
|
@ -691,7 +707,7 @@ export class ImageMap extends React.Component<IImageMapProps> {
|
|||
this.textVectorLayerFilter);
|
||||
if (isPointerOnTextFeature) {
|
||||
return {
|
||||
layerfilter : this.textVectorLayerFilter,
|
||||
layerfilter: this.textVectorLayerFilter,
|
||||
category: FeatureCategory.Text,
|
||||
};
|
||||
}
|
||||
|
@ -719,9 +735,9 @@ export class ImageMap extends React.Component<IImageMapProps> {
|
|||
private handlePointerMoveOnTableIcon = (event: MapBrowserEvent) => {
|
||||
if (this.props.handleTableToolTipChange) {
|
||||
const eventPixel = this.map.getEventPixel(event.originalEvent);
|
||||
const isPointerOnTableIconFeature = this.map.hasFeatureAtPixel(eventPixel,this.tableIconBorderVectorLayerFilter);
|
||||
const isPointerOnTableIconFeature = this.map.hasFeatureAtPixel(eventPixel, this.tableIconBorderVectorLayerFilter);
|
||||
if (isPointerOnTableIconFeature) {
|
||||
const features = this.map.getFeaturesAtPixel( eventPixel, this.tableIconBorderVectorLayerFilter);
|
||||
const features = this.map.getFeaturesAtPixel(eventPixel, this.tableIconBorderVectorLayerFilter);
|
||||
if (features.length > 0) {
|
||||
const feature = features[0];
|
||||
if (feature && this.props.hoveringFeature !== feature.get("id")) {
|
||||
|
@ -789,6 +805,9 @@ export class ImageMap extends React.Component<IImageMapProps> {
|
|||
}
|
||||
|
||||
this.setDragPanInteraction(true);
|
||||
this.removeInteraction(this.modify);
|
||||
this.initializeModify();
|
||||
this.addInteraction(this.modify)
|
||||
}
|
||||
|
||||
private setDragPanInteraction = (dragPanEnabled: boolean) => {
|
||||
|
@ -855,6 +874,7 @@ export class ImageMap extends React.Component<IImageMapProps> {
|
|||
this.initializeModify();
|
||||
this.initializeSnap();
|
||||
this.initializeDraw();
|
||||
this.addInteraction(this.dragBox);
|
||||
this.addInteraction(this.modify);
|
||||
this.addInteraction(this.snap);
|
||||
}
|
||||
|
@ -891,7 +911,7 @@ export class ImageMap extends React.Component<IImageMapProps> {
|
|||
source: this.drawRegionVectorLayer.getSource(),
|
||||
style: this.props.drawRegionStyler,
|
||||
geometryFunction: (coordinates, optGeometry) => {
|
||||
const extent = boundingExtent(/** @type {LineCoordType} */ (coordinates));
|
||||
const extent = boundingExtent(/** @type {LineCoordType} */(coordinates));
|
||||
const boxCoordinates = [[
|
||||
[extent[0], extent[3]],
|
||||
[extent[2], extent[3]],
|
||||
|
@ -1181,7 +1201,7 @@ export class ImageMap extends React.Component<IImageMapProps> {
|
|||
|
||||
private initializeMap = (projection, layers) => {
|
||||
this.map = new Map({
|
||||
controls: [] ,
|
||||
controls: [],
|
||||
interactions: defaultInteractions({
|
||||
shiftDragZoom: false,
|
||||
doubleClickZoom: false,
|
||||
|
|
|
@ -37,6 +37,18 @@
|
|||
&-container {
|
||||
overflow-x: visible;
|
||||
overflow-y: auto;
|
||||
padding: 0 0 0 100px;
|
||||
margin: 0 0 0 -100px;
|
||||
&::before{
|
||||
content: " ";
|
||||
display: inline-block;
|
||||
position: absolute;
|
||||
width: 80px;
|
||||
height: 100%;
|
||||
left: -80px;
|
||||
background: linear-gradient(to right, #00000000 0%,#000000 100%);
|
||||
}
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -73,6 +85,7 @@
|
|||
}
|
||||
|
||||
&-item-block {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
margin: 2px 0;
|
||||
|
@ -80,11 +93,23 @@
|
|||
&-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;
|
||||
}
|
||||
}
|
||||
|
||||
&-item {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
.tag-content {
|
||||
transition: 1s;
|
||||
}
|
||||
|
||||
&-selected {
|
||||
.tag-content {
|
||||
|
@ -100,7 +125,12 @@
|
|||
background: $darker-10 !important;
|
||||
}
|
||||
}
|
||||
|
||||
&-highlight {
|
||||
.tag-content {
|
||||
background-color: $lighter-5 !important;
|
||||
box-shadow:4px 4px 5px $lighter-5;
|
||||
}
|
||||
}
|
||||
&-label {
|
||||
min-height: 1em;
|
||||
display: flex;
|
||||
|
@ -172,7 +202,7 @@
|
|||
}
|
||||
|
||||
&-item-label {
|
||||
color: #A0A0A0;
|
||||
color: #a0a0a0;
|
||||
}
|
||||
|
||||
&-item-label:hover {
|
||||
|
@ -224,7 +254,7 @@
|
|||
width: 0.1px;
|
||||
border: 0.5px solid $lighter-2;
|
||||
height: 18px;
|
||||
margin: 0 0.25em
|
||||
margin: 0 0.25em;
|
||||
}
|
||||
|
||||
&-iconbutton {
|
||||
|
@ -234,7 +264,8 @@
|
|||
padding: 0 0.25em;
|
||||
background-color: transparent;
|
||||
|
||||
&.active, &:hover {
|
||||
&.active,
|
||||
&:hover {
|
||||
background-color: transparent;
|
||||
color: #fff;
|
||||
}
|
||||
|
|
|
@ -81,7 +81,6 @@ export interface ITagInputProps {
|
|||
onLabelLeave: (label: ILabel) => void;
|
||||
/** Function to handle tag change */
|
||||
onTagChanged?: (oldTag: ITag, newTag: ITag) => void;
|
||||
|
||||
setTagInputMode?: (tagInputMode: TagInputMode, selectedTableTagToLabel?: ITableTag) => void;
|
||||
tagInputMode: TagInputMode;
|
||||
selectedTableTagToLabel: ITableTag;
|
||||
|
@ -91,6 +90,7 @@ export interface ITagInputProps {
|
|||
handleTableCellClick: (iTableCellIndex, jTableCellIndex) => void;
|
||||
selectedTableTagBody: ITableRegion[][][];
|
||||
splitPaneWidth: number;
|
||||
onTagDoubleClick?: (label: ILabel) => void;
|
||||
}
|
||||
|
||||
export interface ITagInputState {
|
||||
|
@ -207,6 +207,7 @@ export class TagInput extends React.Component<ITagInputProps, ITagInputState> {
|
|||
searchTags: !this.state.searchTags,
|
||||
searchQuery: "",
|
||||
})}
|
||||
searchingTags={this.state.searchQuery.length > 0}
|
||||
onRenameTag={this.onRenameTag}
|
||||
onLockTag={this.onLockTag}
|
||||
onDelete={this.onDeleteTag}
|
||||
|
@ -227,6 +228,7 @@ export class TagInput extends React.Component<ITagInputProps, ITagInputState> {
|
|||
onChange={(e) => this.setState({ searchQuery: e.target.value })}
|
||||
placeholder="Search tags"
|
||||
autoFocus={true}
|
||||
onFocus={() => this.setState({ selectedTag: null, tagOperation: TagOperationMode.Rename })}
|
||||
/>
|
||||
<FontIcon iconName="Search" />
|
||||
</div>
|
||||
|
@ -245,6 +247,7 @@ export class TagInput extends React.Component<ITagInputProps, ITagInputState> {
|
|||
}
|
||||
</Customizer>
|
||||
{this.getColorPickerPortal()}
|
||||
|
||||
</div>
|
||||
{
|
||||
this.state.addTags &&
|
||||
|
@ -394,7 +397,7 @@ export class TagInput extends React.Component<ITagInputProps, ITagInputState> {
|
|||
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 &&
|
||||
|
@ -429,6 +432,7 @@ export class TagInput extends React.Component<ITagInputProps, ITagInputState> {
|
|||
onLabelLeave={this.props.onLabelLeave}
|
||||
onTagChanged={this.props.onTagChanged}
|
||||
handleLabelTable={this.props.handleLabelTable}
|
||||
onTagDoubleClick={this.props.onTagDoubleClick}
|
||||
/>);
|
||||
}
|
||||
|
||||
|
@ -519,11 +523,11 @@ export class TagInput extends React.Component<ITagInputProps, ITagInputState> {
|
|||
deselect = false;
|
||||
} else if (labelAssigned && ((category === FeatureCategory.DrawnRegion) !== isTagLabelTypeDrawnRegion)) {
|
||||
if (isTagLabelTypeDrawnRegion) {
|
||||
toast.warn(interpolate(strings.tags.warnings.notCompatibleWithDrawnRegionTag, {otherCategory: category}));
|
||||
toast.warn(interpolate(strings.tags.warnings.notCompatibleWithDrawnRegionTag, { otherCatagory: category}));
|
||||
} else if (tagCategory === FeatureCategory.Checkbox) {
|
||||
toast.warn(interpolate(strings.tags.warnings.notCompatibleWithDrawnRegionTag, {otherCategory: FeatureCategory.Checkbox}));
|
||||
toast.warn(interpolate(strings.tags.warnings.notCompatibleWithDrawnRegionTag, { otherCatagory: FeatureCategory.Checkbox}));
|
||||
} else {
|
||||
toast.warn(interpolate(strings.tags.warnings.notCompatibleWithDrawnRegionTag, {otherCategory: FeatureCategory.Text}));
|
||||
toast.warn(interpolate(strings.tags.warnings.notCompatibleWithDrawnRegionTag, { otherCatagory: FeatureCategory.Text}));
|
||||
}
|
||||
return;
|
||||
} else if (tagCategory === category || category === FeatureCategory.DrawnRegion ||
|
||||
|
@ -535,7 +539,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({
|
||||
|
@ -544,7 +548,16 @@ export class TagInput extends React.Component<ITagInputProps, ITagInputState> {
|
|||
});
|
||||
}
|
||||
}
|
||||
|
||||
focusTag(tag: string) {
|
||||
const tagItemRef = this.tagItemRefs.get(tag)?.getTagNameRef();
|
||||
if (tagItemRef) {
|
||||
tagItemRef.current.scrollIntoView({ behavior: "smooth", block: "nearest" });
|
||||
tagItemRef.current.classList.add("tag-item-highlight");
|
||||
setTimeout(() => {
|
||||
tagItemRef.current.classList.remove("tag-item-highlight");
|
||||
}, 2000);
|
||||
}
|
||||
}
|
||||
public labelAssigned = (labels: ILabel[], name): boolean => {
|
||||
const label = labels.find((label) => label.label === name ? true : false);
|
||||
if (!label) {
|
||||
|
@ -664,7 +677,7 @@ export class TagInput extends React.Component<ITagInputProps, ITagInputState> {
|
|||
}
|
||||
|
||||
private onHideContextualMenu = () => {
|
||||
this.setState({tagOperation: TagOperationMode.None});
|
||||
this.setState({ tagOperation: TagOperationMode.None });
|
||||
}
|
||||
|
||||
private getContextualMenuItems = (): IContextualMenuItem[] => {
|
||||
|
@ -728,6 +741,7 @@ export class TagInput extends React.Component<ITagInputProps, ITagInputState> {
|
|||
},
|
||||
text: strings.tags.toolbar.moveUp,
|
||||
onClick: this.onMenuItemClick,
|
||||
disabled: this.state.searchQuery.length > 0,
|
||||
},
|
||||
{
|
||||
key: TagMenuItem.MoveDown,
|
||||
|
@ -736,6 +750,7 @@ export class TagInput extends React.Component<ITagInputProps, ITagInputState> {
|
|||
},
|
||||
text: strings.tags.toolbar.moveDown,
|
||||
onClick: this.onMenuItemClick,
|
||||
disabled: this.state.searchQuery.length > 0,
|
||||
},
|
||||
{
|
||||
key: TagMenuItem.Delete,
|
||||
|
|
|
@ -7,6 +7,7 @@ import { ITag, ILabel, FieldType, FieldFormat, TagInputMode } from "../../../../
|
|||
import { strings } from "../../../../common/strings";
|
||||
import TagInputItemLabel from "./tagInputItemLabel";
|
||||
import { tagIndexKeys } from "./tagIndexKeys";
|
||||
import _ from "lodash";
|
||||
|
||||
export interface ITagClickProps {
|
||||
ctrlKey?: boolean;
|
||||
|
@ -43,6 +44,7 @@ export interface ITagInputItemProps {
|
|||
onTagChanged?: (oldTag: ITag, newTag: ITag) => void;
|
||||
handleLabelTable: (tagInputMode: TagInputMode, selectedTableTagToLabel) => void;
|
||||
addRowToDynamicTable: () => void;
|
||||
onTagDoubleClick?: (label:ILabel) => void;
|
||||
}
|
||||
|
||||
export interface ITagInputItemState {
|
||||
|
@ -81,9 +83,14 @@ 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}
|
||||
|
@ -98,6 +105,7 @@ export default class TagInputItem extends React.Component<ITagInputItemProps, IT
|
|||
style={style}>
|
||||
<div
|
||||
className={"tag-content pr-2"}
|
||||
onDoubleClick={this.onNameDoubleClick}
|
||||
onClick={this.onNameClick}>
|
||||
{this.getTagContent()}
|
||||
</div>
|
||||
|
@ -141,6 +149,14 @@ export default class TagInputItem extends React.Component<ITagInputItemProps, IT
|
|||
this.props.onClick(this.props.tag, { ctrlKey, altKey });
|
||||
}
|
||||
|
||||
private onNameDoubleClick = (e:MouseEvent) => {
|
||||
e.stopPropagation();
|
||||
const { labels } = this.props;
|
||||
if (labels.length > 0) {
|
||||
this.props.onTagDoubleClick(labels[0]);
|
||||
}
|
||||
}
|
||||
|
||||
private getItemClassName = () => {
|
||||
const classNames = ["tag-item"];
|
||||
if (this.props.isSelected) {
|
||||
|
@ -192,7 +208,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>
|
||||
|
@ -276,7 +292,7 @@ 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);
|
||||
}
|
||||
|
|
|
@ -9,7 +9,8 @@ import { ITableRegion, ITableTag, ITag, TagInputMode } from "../../../../models/
|
|||
enum Categories {
|
||||
General,
|
||||
Separator,
|
||||
Modifier,
|
||||
RenameModifier,
|
||||
MoveModifier,
|
||||
}
|
||||
|
||||
/** Properties for tag input toolbar */
|
||||
|
@ -30,6 +31,7 @@ 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;
|
||||
searchingTags: boolean;
|
||||
}
|
||||
|
||||
interface ITagInputToolbarItemProps {
|
||||
|
@ -76,38 +78,44 @@ export default class TagInputToolbar extends React.Component<ITagInputToolbarPro
|
|||
{
|
||||
displayName: strings.tags.toolbar.rename,
|
||||
icon: "Rename",
|
||||
category: Categories.Modifier,
|
||||
category: Categories.RenameModifier,
|
||||
handler: this.handleRename,
|
||||
},
|
||||
{
|
||||
displayName: strings.tags.toolbar.moveUp,
|
||||
icon: "Up",
|
||||
category: Categories.Modifier,
|
||||
category: Categories.MoveModifier,
|
||||
handler: this.handleMoveUp,
|
||||
},
|
||||
{
|
||||
displayName: strings.tags.toolbar.moveDown,
|
||||
icon: "Down",
|
||||
category: Categories.Modifier,
|
||||
category: Categories.MoveModifier,
|
||||
handler: this.handleMoveDown,
|
||||
},
|
||||
{
|
||||
displayName: strings.tags.toolbar.delete,
|
||||
icon: "Delete",
|
||||
category: Categories.Modifier,
|
||||
category: Categories.MoveModifier,
|
||||
handler: this.handleDelete,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
private renderItems = () => {
|
||||
const modifierDisabled = !this.props.selectedTag;
|
||||
const modifierClassNames = ["tag-input-toolbar-iconbutton"];
|
||||
if (modifierDisabled) {
|
||||
modifierClassNames.push("tag-input-toolbar-iconbutton-disabled");
|
||||
const moveModifierDisabled = !this.props.selectedTag || this.props.searchingTags;
|
||||
const renameModifierDisabled = !this.props.selectedTag;
|
||||
const moveModifierClassNames = ["tag-input-toolbar-iconbutton"];
|
||||
const renameModifierClassNames = ["tag-input-toolbar-iconbutton"];
|
||||
if (moveModifierDisabled) {
|
||||
moveModifierClassNames.push("tag-input-toolbar-iconbutton-disabled");
|
||||
}
|
||||
if (renameModifierDisabled) {
|
||||
renameModifierClassNames.push("tag-input-toolbar-iconbutton-disabled");
|
||||
}
|
||||
|
||||
const modifierClassName = modifierClassNames.join(" ");
|
||||
const moveModifierClassName = moveModifierClassNames.join(" ");
|
||||
const renameModifierClassName = renameModifierClassNames.join(" ");
|
||||
|
||||
return(
|
||||
this.getToolbarItems().map((itemConfig, index) => {
|
||||
|
@ -124,14 +132,25 @@ export default class TagInputToolbar extends React.Component<ITagInputToolbarPro
|
|||
);
|
||||
} else if (itemConfig.category === Categories.Separator) {
|
||||
return (<div className="tag-input-toolbar-separator" key={itemConfig.displayName}></div>);
|
||||
} else if (itemConfig.category === Categories.Modifier) {
|
||||
} else if (itemConfig.category === Categories.RenameModifier) {
|
||||
return (
|
||||
<IconButton
|
||||
key={itemConfig.displayName}
|
||||
disabled={modifierDisabled}
|
||||
disabled={renameModifierDisabled}
|
||||
title={itemConfig.displayName}
|
||||
ariaLabel={itemConfig.displayName}
|
||||
className={modifierClassName}
|
||||
className={renameModifierClassName}
|
||||
iconProps={{iconName: itemConfig.icon}}
|
||||
onClick={(e) => this.onToolbarItemClick(e, itemConfig)} />
|
||||
);
|
||||
} else if (itemConfig.category === Categories.MoveModifier) {
|
||||
return (
|
||||
<IconButton
|
||||
key={itemConfig.displayName}
|
||||
disabled={moveModifierDisabled}
|
||||
title={itemConfig.displayName}
|
||||
ariaLabel={itemConfig.displayName}
|
||||
className={moveModifierClassName}
|
||||
iconProps={{iconName: itemConfig.icon}}
|
||||
onClick={(e) => this.onToolbarItemClick(e, itemConfig)} />
|
||||
);
|
||||
|
|
|
@ -22,6 +22,7 @@
|
|||
background-color: $darker-1;
|
||||
border: solid 1px $lighter-2;
|
||||
color: rgb(0, 161, 241);
|
||||
z-index: 10;
|
||||
|
||||
&:hover, &.active {
|
||||
background-color: $darker-2;
|
||||
|
@ -39,14 +40,14 @@
|
|||
.prev {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 0;
|
||||
left: 50px;
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
.next {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
right: 0;
|
||||
right: 50px;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
|
@ -77,6 +78,7 @@
|
|||
background-color: rgba(0, 0, 0, 0.8);
|
||||
text-align: center;
|
||||
display: flex;
|
||||
z-index: 11;
|
||||
}
|
||||
|
||||
.canvas-ocr-loading-spinner {
|
||||
|
|
|
@ -9,7 +9,7 @@ import {
|
|||
EditorMode, IAssetMetadata,
|
||||
IProject, IRegion, RegionType,
|
||||
AssetType, ILabelData, ILabel,
|
||||
ITag, IAsset, IFormRegion, FeatureCategory, FieldType, FieldFormat, ImageMapParent, LabelType, ITableRegion, ITableTag, ITableLabel, ITableCellLabel
|
||||
ITag, IAsset, IFormRegion, FeatureCategory, FieldType, FieldFormat, ImageMapParent, LabelType, ITableRegion, ITableTag, ITableLabel, ITableCellLabel, AssetLabelingState, APIVersionPatches
|
||||
} from "../../../../models/applicationState";
|
||||
import CanvasHelpers from "./canvasHelpers";
|
||||
import { AssetPreview } from "../../common/assetPreview/assetPreview";
|
||||
|
@ -37,7 +37,8 @@ import { TooltipHost, ITooltipHostStyles } from "@fluentui/react";
|
|||
import { IAppSettings } from '../../../../models/applicationState';
|
||||
import { AutoLabelingStatus, PredictService } from "../../../../services/predictService";
|
||||
import { AssetService } from "../../../../services/assetService";
|
||||
import { strings } from "../../../../common/strings";
|
||||
import { interpolate, strings } from "../../../../common/strings";
|
||||
import { toast } from "react-toastify";
|
||||
|
||||
|
||||
pdfjsLib.GlobalWorkerOptions.workerSrc = constants.pdfjsWorkerSrc(pdfjsLib.version);
|
||||
|
@ -55,11 +56,13 @@ export interface ICanvasProps extends React.Props<Canvas> {
|
|||
closeTableView?: (state: string) => void;
|
||||
onAssetMetadataChanged?: (assetMetadata: IAssetMetadata) => void;
|
||||
onSelectedRegionsChanged?: (regions: IRegion[]) => void;
|
||||
onRegionDoubleClick?: (region: IRegion) => void;
|
||||
onCanvasRendered?: (canvas: HTMLCanvasElement) => void;
|
||||
onRunningOCRStatusChanged?: (isRunning: boolean) => void;
|
||||
onRunningAutoLabelingStatusChanged?: (isRunning: boolean) => void;
|
||||
onTagChanged?: (oldTag: ITag, newTag: ITag) => void;
|
||||
runOcrForAllDocs?: (runForAllDocs: boolean) => void;
|
||||
runAutoLabelingOnNextBatch?: () => Promise<void>;
|
||||
onAssetDeleted?: () => void;
|
||||
handleLabelTable?: () => void;
|
||||
}
|
||||
|
@ -181,7 +184,9 @@ export default class Canvas extends React.Component<ICanvasProps, ICanvasState>
|
|||
public componentDidUpdate = async (prevProps: Readonly<ICanvasProps>, prevState: Readonly<ICanvasState>) => {
|
||||
// Handles asset changing
|
||||
if (this.props.selectedAsset.asset.name !== prevProps.selectedAsset.asset.name ||
|
||||
this.props.selectedAsset.asset.isRunningOCR !== prevProps.selectedAsset.asset.isRunningOCR) {
|
||||
this.props.selectedAsset.asset.isRunningOCR !== prevProps.selectedAsset.asset.isRunningOCR ||
|
||||
this.props.selectedAsset.asset.labelingState !== prevProps.selectedAsset.asset.labelingState
|
||||
) {
|
||||
this.selectedRegionIds = [];
|
||||
this.imageMap.removeAllFeatures();
|
||||
this.imageMap.resetAllLayerVisibility();
|
||||
|
@ -253,10 +258,12 @@ export default class Canvas extends React.Component<ICanvasProps, ICanvasState>
|
|||
handleAssetDeleted={this.props.onAssetDeleted}
|
||||
handleRunOcrForAllDocuments={this.runOcrForAllDocuments}
|
||||
handleRunAutoLabelingOnCurrentDocument={this.runAutoLabelingOnCurrentDocument}
|
||||
connectionType={this.props.project.sourceConnection.providerType}
|
||||
handleRunAutoLabelingForRestDocuments={this.runAutoLabelingForRestDocuments}
|
||||
handleToggleDrawRegionMode={this.handleToggleDrawRegionMode}
|
||||
connectionType={this.props.project.sourceConnection.providerType}
|
||||
drawRegionMode={this.state.drawRegionMode}
|
||||
project={this.props.project}
|
||||
selectedAsset={this.props.selectedAsset}
|
||||
parentPage={strings.editorPage.title}
|
||||
/>
|
||||
<ImageMap
|
||||
|
@ -267,6 +274,7 @@ export default class Canvas extends React.Component<ICanvasProps, ICanvasState>
|
|||
imageHeight={this.state.imageHeight}
|
||||
enableFeatureSelection={!this.state.drawRegionMode && !this.state.groupSelectMode}
|
||||
handleFeatureSelect={this.handleFeatureSelect}
|
||||
handleFeatureDoubleClick={this.handleFeatureDoubleClick}
|
||||
featureStyler={this.featureStyler}
|
||||
groupSelectMode={this.state.groupSelectMode}
|
||||
handleIsPointerOnImage={this.handleIsPointerOnImage}
|
||||
|
@ -370,16 +378,19 @@ export default class Canvas extends React.Component<ICanvasProps, ICanvasState>
|
|||
const assetPath = asset.path;
|
||||
const predictService = new PredictService(this.props.project);
|
||||
const result = await predictService.getPrediction(assetPath);
|
||||
|
||||
const assetService = new AssetService(this.props.project);
|
||||
await assetService.uploadAssetPredictResult(asset, result);
|
||||
const assetMetadata = await assetService.getAssetMetadata(asset);
|
||||
const assetMetadata = assetService.getAssetPredictMetadata(asset, result);
|
||||
await this.props.onAssetMetadataChanged(assetMetadata);
|
||||
}
|
||||
finally {
|
||||
this.setAutoLabelingStatus(AutoLabelingStatus.done);
|
||||
}
|
||||
}
|
||||
private runAutoLabelingForRestDocuments = async () => {
|
||||
this.setState({ autoLableingStatus: AutoLabelingStatus.running });
|
||||
await this.props.runAutoLabelingOnNextBatch();
|
||||
this.setState({ autoLableingStatus: AutoLabelingStatus.done });
|
||||
}
|
||||
|
||||
public updateSize() {
|
||||
this.imageMap.updateSize();
|
||||
|
@ -557,7 +568,7 @@ export default class Canvas extends React.Component<ICanvasProps, ICanvasState>
|
|||
const filteredRegions = this.state.currentAsset.regions.filter((assetRegion) => {
|
||||
return regions.findIndex((r) => r.id === assetRegion.id) === -1;
|
||||
});
|
||||
this.updateAssetRegions(filteredRegions);
|
||||
this.updateAssetRegions(filteredRegions, regions.length > 0);
|
||||
}
|
||||
|
||||
private deleteRegionsFromImageMap = (regions: IRegion[]) => {
|
||||
|
@ -606,7 +617,7 @@ export default class Canvas extends React.Component<ICanvasProps, ICanvasState>
|
|||
* @param regions
|
||||
* @param selectedRegions
|
||||
*/
|
||||
private updateAssetRegions = (regions: IRegion[]) => {
|
||||
private updateAssetRegions = (regions: IRegion[], manualOption: boolean = false) => {
|
||||
const labelData = this.convertRegionsToLabelData(regions, this.state.currentAsset.asset.name);
|
||||
console.log("Canvas -> privateupdateAssetRegions -> labelData", labelData)
|
||||
const currentAsset: IAssetMetadata = {
|
||||
|
@ -621,6 +632,41 @@ export default class Canvas extends React.Component<ICanvasProps, ICanvasState>
|
|||
(region) => region.tags[0] !== undefined &&
|
||||
region.pageNumber === this.state.currentPage));
|
||||
}
|
||||
if (manualOption) {
|
||||
if (currentAsset.labelData) {
|
||||
const labelingState = _.get(this.state, "currentAsset.labelData.labelingState", null);
|
||||
if (labelingState) {
|
||||
switch (labelingState) {
|
||||
case AssetLabelingState.AutoLabeled:
|
||||
case AssetLabelingState.AutoLabeledAndAdjusted:
|
||||
currentAsset.labelData.labelingState = AssetLabelingState.AutoLabeledAndAdjusted;
|
||||
break;
|
||||
case AssetLabelingState.ManuallyLabeled:
|
||||
case AssetLabelingState.Trained:
|
||||
currentAsset.labelData.labelingState = AssetLabelingState.ManuallyLabeled;
|
||||
break;
|
||||
default:
|
||||
currentAsset.labelData.labelingState = AssetLabelingState.ManuallyLabeled;
|
||||
break;
|
||||
}
|
||||
}
|
||||
else {
|
||||
currentAsset.labelData.labelingState = AssetLabelingState.ManuallyLabeled;
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (this.state.currentAsset.labelData && currentAsset.labelData) {
|
||||
currentAsset.labelData.labelingState = this.state.currentAsset.labelData.labelingState;
|
||||
}
|
||||
}
|
||||
|
||||
if (currentAsset.labelData) {
|
||||
currentAsset.asset.labelingState = currentAsset.labelData.labelingState;
|
||||
} else if (currentAsset.asset.labelingState) {
|
||||
delete currentAsset.asset.labelingState;
|
||||
}
|
||||
|
||||
this.setState({
|
||||
currentAsset,
|
||||
}, () => {
|
||||
|
@ -640,7 +686,7 @@ export default class Canvas extends React.Component<ICanvasProps, ICanvasState>
|
|||
const deletedRegionIndex = currentRegions.findIndex((region) => region.id === id);
|
||||
currentRegions.splice(deletedRegionIndex, 1);
|
||||
|
||||
this.updateAssetRegions(currentRegions);
|
||||
this.updateAssetRegions(currentRegions, true);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -655,6 +701,12 @@ export default class Canvas extends React.Component<ICanvasProps, ICanvasState>
|
|||
this.props.onSelectedRegionsChanged(selectedRegions);
|
||||
}
|
||||
}
|
||||
private onRegionDoubleClick = (id: string) => {
|
||||
if (this.props.onRegionDoubleClick) {
|
||||
const region = this.state.currentAsset.regions.find(region=>region.id === id);
|
||||
this.props.onRegionDoubleClick(region);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates regions in both Canvas Tools and the asset data store
|
||||
|
@ -667,14 +719,14 @@ export default class Canvas extends React.Component<ICanvasProps, ICanvasState>
|
|||
for (const update of updates) {
|
||||
const region = regions.find((r) => r.id === update.id);
|
||||
if (region) {
|
||||
// skip
|
||||
region.changed = true;
|
||||
} else {
|
||||
updatedRegions.push(update);
|
||||
}
|
||||
}
|
||||
console.log("Canvas -> privateupdateRegions -> updatedRegions", updatedRegions)
|
||||
updatedRegions.sort(this.compareRegionOrder);
|
||||
this.updateAssetRegions(updatedRegions);
|
||||
this.updateAssetRegions(updatedRegions, true);
|
||||
}
|
||||
|
||||
private createBoundingBoxVectorFeature = (text, boundingBox, imageExtent, ocrExtent, page) => {
|
||||
|
@ -1021,6 +1073,12 @@ export default class Canvas extends React.Component<ICanvasProps, ICanvasState>
|
|||
}
|
||||
this.redrawAllFeatures();
|
||||
}
|
||||
private handleFeatureDoubleClick = (feature: Feature, isToggle: boolean = true, category: FeatureCategory) => {
|
||||
const regionId = feature.get("id");
|
||||
if (this.isRegionSelected(regionId)) {
|
||||
this.onRegionDoubleClick(regionId);
|
||||
}
|
||||
}
|
||||
|
||||
private handleMultiSelection = (regionId: any, category: FeatureCategory) => {
|
||||
const selectedRegions = this.getSelectedRegions();
|
||||
|
@ -1174,7 +1232,7 @@ export default class Canvas extends React.Component<ICanvasProps, ICanvasState>
|
|||
return;
|
||||
}
|
||||
try {
|
||||
const ocr = await this.ocrService.getRecognizedText(asset.path, asset.name, this.setOCRStatus, force);
|
||||
const ocr = await this.ocrService.getRecognizedText(asset.path, asset.name, asset.mimeType, this.setOCRStatus, force);
|
||||
if (asset.id === this.state.currentAsset.asset.id) {
|
||||
// since get OCR is async, we only set currentAsset's OCR
|
||||
this.setState({
|
||||
|
@ -1340,6 +1398,13 @@ export default class Canvas extends React.Component<ICanvasProps, ICanvasState>
|
|||
}
|
||||
|
||||
private convertRegionsToLabelData = (regions: IRegion[], assetName: string) => {
|
||||
const labels = (this.props.selectedAsset
|
||||
&& this.props.selectedAsset.labelData
|
||||
&& this.props.selectedAsset.labelData.labels
|
||||
&& this.props.selectedAsset.labelData.labels.map(label => ({
|
||||
...label, value: []
|
||||
}))) || [];
|
||||
|
||||
const labelData: ILabelData = {
|
||||
document: decodeURIComponent(assetName).split("/").pop(),
|
||||
labels: [] as ILabel[],
|
||||
|
@ -1357,9 +1422,9 @@ export default class Canvas extends React.Component<ICanvasProps, ICanvasState>
|
|||
region.tags.forEach((tag) => {
|
||||
if (region.isTableRegion) {
|
||||
const tableRegion = region as ITableRegion;
|
||||
const tableLabel: ITableLabel = labelData.tableLabels.find((tableLabel) => { return tableLabel.tableKey === tag });
|
||||
const tableLabel: ITableLabel = labelData.tableLabels.find((tableLabel) => tableLabel.tableKey === tag);
|
||||
if (tableLabel) {
|
||||
const tableLabelCell = tableLabel.labels.find((tableLabelCell) => { return tableLabelCell.columnKey === tableRegion.columnKey &&tableLabelCell.rowKey === tableRegion.rowKey });
|
||||
const tableLabelCell = tableLabel.labels.find((tableLabelCell) => tableLabelCell.columnKey === tableRegion.columnKey && tableLabelCell.rowKey === tableRegion.rowKey);
|
||||
if (tableLabelCell) {
|
||||
tableLabelCell.value.push(formRegion)
|
||||
} else {
|
||||
|
@ -1380,9 +1445,13 @@ export default class Canvas extends React.Component<ICanvasProps, ICanvasState>
|
|||
labels: [tableCellLabel]
|
||||
})
|
||||
}
|
||||
|
||||
} else {
|
||||
const label = labelData.labels.find((label) => { return label.label === tag });
|
||||
const label = labelData.labels.find((label) => label.label === tag);
|
||||
if (label) {
|
||||
if (label.confidence && region.changed) {
|
||||
delete label.confidence;
|
||||
}
|
||||
label.value.push(formRegion);
|
||||
} else {
|
||||
let newLabel;
|
||||
|
@ -1404,9 +1473,15 @@ export default class Canvas extends React.Component<ICanvasProps, ICanvasState>
|
|||
}
|
||||
}
|
||||
});
|
||||
|
||||
});
|
||||
return labelData;
|
||||
const newLabels = labelData.labels.filter(label => label.value.length > 0);
|
||||
|
||||
return newLabels.length > 0 || labelData.tableLabels.length > 0 ?
|
||||
{
|
||||
document: decodeURIComponent(assetName).split("/").pop(),
|
||||
labels: newLabels,
|
||||
tableLabels: labelData.tableLabels
|
||||
} as ILabelData : null;
|
||||
}
|
||||
|
||||
private getLabelType = (regionCategory: string) => {
|
||||
|
@ -1650,11 +1725,21 @@ export default class Canvas extends React.Component<ICanvasProps, ICanvasState>
|
|||
} else if (newLabels.length > 0) {
|
||||
const newFieldNames = newLabels.map((label) => label.label);
|
||||
const prevFieldNames = prevLabels.map((label) => label.label);
|
||||
return !_.isEqual(newFieldNames.sort(), prevFieldNames.sort());
|
||||
if (_.isEqual(newFieldNames.sort(), prevFieldNames.sort())) {
|
||||
for (const name of newFieldNames) {
|
||||
const newValue = newLabels.find(label => label.label === name).value.map(region => region.boundingBoxes).join(",");
|
||||
const prevValue = prevLabels.find(label => label.label === name).value.map(region => region.boundingBoxes).join(",");
|
||||
if (newValue !== prevValue) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private getBoundingBoxTextFromRegion = (formRegion: IFormRegion, boundingBoxIndex: number) => {
|
||||
// get value from formRegion.text
|
||||
|
@ -1918,7 +2003,6 @@ export default class Canvas extends React.Component<ICanvasProps, ICanvasState>
|
|||
});
|
||||
}
|
||||
const tag: ITag = this.props.project.tags.find((tag) => tag.name === tagName);
|
||||
|
||||
let regionCategory: string;
|
||||
if (labelType) {
|
||||
regionCategory = labelType;
|
||||
|
@ -2057,11 +2141,17 @@ export default class Canvas extends React.Component<ICanvasProps, ICanvasState>
|
|||
}
|
||||
|
||||
const prevTypes = {};
|
||||
prevTags.forEach((tag) => prevTypes[tag.name] = tag.type);
|
||||
|
||||
const prevColors = {};
|
||||
prevTags.forEach((tag) => {
|
||||
prevTypes[tag.name] = tag.type;
|
||||
prevColors[tag.name] = tag.color;
|
||||
});
|
||||
const types = {};
|
||||
tags.forEach((tag) => types[tag.name] = tag.type);
|
||||
|
||||
const colors = {};
|
||||
tags.forEach((tag) => {
|
||||
types[tag.name] = tag.type;
|
||||
colors[tag.name] = tag.color;
|
||||
});
|
||||
for (const name of names) {
|
||||
const prevType = prevTypes[name];
|
||||
const type = types[name];
|
||||
|
@ -2070,6 +2160,12 @@ export default class Canvas extends React.Component<ICanvasProps, ICanvasState>
|
|||
// some tag change between checkbox and text
|
||||
return true;
|
||||
}
|
||||
const prevColor = prevColors[name];
|
||||
const color = colors[name];
|
||||
if (prevColor !== color) {
|
||||
// some tag color changed
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
|
@ -2125,6 +2221,9 @@ export default class Canvas extends React.Component<ICanvasProps, ICanvasState>
|
|||
}
|
||||
|
||||
private handleToggleDrawRegionMode = () => {
|
||||
if (!this.state.drawRegionMode && this.props.project.apiVersion !== APIVersionPatches.patch3) {
|
||||
toast.warn(interpolate(strings.editorPage.canvas.canvasCommandBar.warings.drawRegionUnsupportedAPIVersion, { apiVersion: (this.props.project.apiVersion || constants.appVersion ) }), {autoClose: 7000});
|
||||
}
|
||||
this.setState({
|
||||
drawRegionMode: !this.state.drawRegionMode
|
||||
});
|
||||
|
@ -2240,4 +2339,11 @@ export default class Canvas extends React.Component<ICanvasProps, ICanvasState>
|
|||
});
|
||||
this.imageMap.modifyStartFeatureCoordinates = {};
|
||||
}
|
||||
|
||||
async focusOnLabel(label: ILabel) {
|
||||
const { page } = label.value[ 0 ];
|
||||
if (this.state.currentPage !== page) {
|
||||
await this.goToPage(page);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,24 +2,29 @@ import * as React from "react";
|
|||
import { CommandBar, ICommandBarItemProps } from "@fluentui/react/lib/CommandBar";
|
||||
import { ICustomizations, Customizer } from "@fluentui/react/lib/Utilities";
|
||||
import { getDarkGreyTheme } from "../../../../common/themes";
|
||||
import { strings } from '../../../../common/strings';
|
||||
import { interpolate, strings } from '../../../../common/strings';
|
||||
import { ContextualMenuItemType } from "@fluentui/react";
|
||||
import { IProject } from "../../../../models/applicationState";
|
||||
import { IProject, IAssetMetadata, AssetLabelingState } from "../../../../models/applicationState";
|
||||
import _ from "lodash";
|
||||
import "./canvasCommandBar.scss";
|
||||
import { constants } from "../../../../common/constants";
|
||||
|
||||
interface ICanvasCommandBarProps {
|
||||
handleZoomIn: () => void;
|
||||
handleZoomOut: () => void;
|
||||
handleRunAutoLabelingOnCurrentDocument?: () => void;
|
||||
project: IProject;
|
||||
handleRotateImage: (degrees: number) => void;
|
||||
handleRunOcr?: () => void;
|
||||
handleRunOcrForAllDocuments?: () => void;
|
||||
handleRunAutoLabelingOnCurrentDocument?: () => void;
|
||||
handleRunAutoLabelingForRestDocuments?: () => void;
|
||||
handleLayerChange?: (layer: string) => void;
|
||||
handleToggleDrawRegionMode?: () => void;
|
||||
handleAssetDeleted?: () => void;
|
||||
project: IProject;
|
||||
selectedAsset?: IAssetMetadata;
|
||||
handleRotateImage: (degrees: number) => void;
|
||||
|
||||
drawRegionMode?: boolean;
|
||||
connectionType?: string;
|
||||
handleAssetDeleted?: () => void;
|
||||
layers?: any;
|
||||
parentPage: string;
|
||||
}
|
||||
|
@ -31,6 +36,14 @@ export const CanvasCommandBar: React.FunctionComponent<ICanvasCommandBarProps> =
|
|||
},
|
||||
scopedSettings: {},
|
||||
};
|
||||
const disableAutoLabeling = !props.project.predictModelId;
|
||||
let disableAutoLabelingCurrentAsset = disableAutoLabeling;
|
||||
if (!disableAutoLabeling) {
|
||||
const labelingState = _.get(props.selectedAsset, "labelData.labelingState");
|
||||
if (labelingState === AssetLabelingState.ManuallyLabeled || labelingState === AssetLabelingState.Trained) {
|
||||
disableAutoLabelingCurrentAsset = true;
|
||||
}
|
||||
}
|
||||
|
||||
let commandBarItems: ICommandBarItemProps[] = [];
|
||||
if (props.parentPage === strings.editorPage.title) {
|
||||
|
@ -64,16 +77,16 @@ export const CanvasCommandBar: React.FunctionComponent<ICanvasCommandBarProps> =
|
|||
isChecked: props.layers["checkboxes"],
|
||||
onClick: () => props.handleLayerChange("checkboxes"),
|
||||
},
|
||||
// {
|
||||
// key: "DrawnRegions",
|
||||
// text: strings.editorPage.canvas.canvasCommandBar.items.layers.subMenuItems.drawnRegions,
|
||||
// canCheck: true,
|
||||
// iconProps: { iconName: "AddField" },
|
||||
// isChecked: props.layers["drawnRegions"],
|
||||
// className: props.drawRegionMode ? "disabled" : "",
|
||||
// onClick: () => props.handleLayerChange("drawnRegions"),
|
||||
// disabled: props.drawRegionMode
|
||||
// },
|
||||
{
|
||||
key: "DrawnRegions",
|
||||
text: strings.editorPage.canvas.canvasCommandBar.items.layers.subMenuItems.drawnRegions,
|
||||
canCheck: true,
|
||||
iconProps: { iconName: "AddField" },
|
||||
isChecked: props.layers["drawnRegions"],
|
||||
className: props.drawRegionMode ? "disabled" : "",
|
||||
onClick: () => props.handleLayerChange("drawnRegions"),
|
||||
disabled: props.drawRegionMode
|
||||
},
|
||||
{
|
||||
key: "Label",
|
||||
text: strings.editorPage.canvas.canvasCommandBar.items.layers.subMenuItems.labels,
|
||||
|
@ -85,16 +98,16 @@ export const CanvasCommandBar: React.FunctionComponent<ICanvasCommandBarProps> =
|
|||
],
|
||||
},
|
||||
},
|
||||
// {
|
||||
// key: "drawRegion",
|
||||
// text: strings.editorPage.canvas.canvasCommandBar.items.drawRegion,
|
||||
// iconProps: { iconName: "AddField" },
|
||||
// toggle: true,
|
||||
// checked: props.drawRegionMode,
|
||||
// className: !props.layers["drawnRegions"] ? "disabled" : "",
|
||||
// onClick: () => props.handleToggleDrawRegionMode(),
|
||||
// disabled: !props.layers["drawnRegions"],
|
||||
// }
|
||||
{
|
||||
key: "drawRegion",
|
||||
text: strings.editorPage.canvas.canvasCommandBar.items.drawRegion,
|
||||
iconProps: { iconName: "AddField" },
|
||||
toggle: true,
|
||||
checked: props.drawRegionMode,
|
||||
className: !props.layers["drawnRegions"] ? "disabled" : "",
|
||||
onClick: () => props.handleToggleDrawRegionMode(),
|
||||
disabled: !props.layers["drawnRegions"],
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
|
@ -155,23 +168,34 @@ export const CanvasCommandBar: React.FunctionComponent<ICanvasCommandBarProps> =
|
|||
key: "runOcrForCurrentDocument",
|
||||
text: strings.editorPage.canvas.canvasCommandBar.farItems.additionalActions.subIMenuItems.runOcrOnCurrentDocument,
|
||||
iconProps: { iconName: "TextDocument" },
|
||||
onClick: () => props.handleRunOcr(),
|
||||
onClick: () => { if (props.handleRunOcr) props.handleRunOcr(); },
|
||||
},
|
||||
{
|
||||
key: "runOcrForAllDocuments",
|
||||
text: strings.editorPage.canvas.canvasCommandBar.farItems.additionalActions.subIMenuItems.runOcrOnAllDocuments,
|
||||
iconProps: { iconName: "Documentation" },
|
||||
onClick: () => props.handleRunOcrForAllDocuments(),
|
||||
onClick: () => { if (props.handleRunOcrForAllDocuments) props.handleRunOcrForAllDocuments(); },
|
||||
},
|
||||
{
|
||||
key: "runAutoLabelingCurrentDocument",
|
||||
text: strings.editorPage.canvas.canvasCommandBar.farItems.additionalActions.subIMenuItems.runAutoLabelingCurrentDocument,
|
||||
iconProps: { iconName: "Tag" },
|
||||
disabled: !props.project.predictModelId,
|
||||
disabled: disableAutoLabelingCurrentAsset,
|
||||
title: props.project.predictModelId ? "" :
|
||||
strings.editorPage.canvas.canvasCommandBar.farItems.additionalActions.subIMenuItems.noPredictModelOnProject,
|
||||
onClick: () => {
|
||||
props.handleRunAutoLabelingOnCurrentDocument();
|
||||
if (props.handleRunAutoLabelingOnCurrentDocument) props.handleRunAutoLabelingOnCurrentDocument();
|
||||
},
|
||||
},
|
||||
{
|
||||
key: "runAutoLabelingForRestDocuments",
|
||||
text: interpolate(strings.editorPage.canvas.canvasCommandBar.farItems.additionalActions.subIMenuItems.runAutoLabelingOnNotLabelingDocuments, { batchSize: constants.autoLabelBatchSize }),
|
||||
iconProps: { iconName: "Tag" },
|
||||
disabled: disableAutoLabeling,
|
||||
title: props.project.predictModelId ? "" :
|
||||
strings.editorPage.canvas.canvasCommandBar.farItems.additionalActions.subIMenuItems.noPredictModelOnProject,
|
||||
onClick: () => {
|
||||
if (props.handleRunAutoLabelingForRestDocuments) props.handleRunAutoLabelingForRestDocuments();
|
||||
},
|
||||
},
|
||||
{
|
||||
|
@ -182,7 +206,7 @@ export const CanvasCommandBar: React.FunctionComponent<ICanvasCommandBarProps> =
|
|||
key: "deleteAsset",
|
||||
text: strings.editorPage.asset.delete.title,
|
||||
iconProps: { iconName: "Delete" },
|
||||
onClick: () => props.handleAssetDeleted(),
|
||||
onClick: () => { if (props.handleAssetDeleted) props.handleAssetDeleted(); },
|
||||
}
|
||||
],
|
||||
},
|
||||
|
|
|
@ -232,6 +232,18 @@ canvas {
|
|||
.badge-tagged {
|
||||
background-color: rgba(green, 0.9);
|
||||
border: 1px solid $lighter-2;
|
||||
&-ManuallyLabeled{
|
||||
background-color: rgba(rgb(128, 41, 0), 0.9);
|
||||
}
|
||||
&-Trained {
|
||||
background-color: rgba(green, 0.9);
|
||||
}
|
||||
&-AutoLabeled {
|
||||
background-color: rgba(rgb(136, 0, 91), 0.9);
|
||||
}
|
||||
&-AutoLabeledAndAdjusted{
|
||||
background-color: rgba(rgb(0, 92, 128), 0.9);
|
||||
}
|
||||
}
|
||||
|
||||
.badge-visited {
|
||||
|
|
|
@ -13,7 +13,7 @@ import { strings, interpolate } from "../../../../common/strings";
|
|||
import {
|
||||
AssetState, AssetType, EditorMode, FieldType,
|
||||
IApplicationState, IAppSettings, IAsset, IAssetMetadata,
|
||||
ILabel, IProject, IRegion, ISize, ITag, FeatureCategory, TagInputMode,FieldFormat, ITableTag, ITableRegion
|
||||
ILabel, IProject, IRegion, ISize, ITag, FeatureCategory, TagInputMode, FieldFormat, ITableTag, ITableRegion, AssetLabelingState
|
||||
} from "../../../../models/applicationState";
|
||||
import IApplicationActions, * as applicationActions from "../../../../redux/actions/applicationActions";
|
||||
import IProjectActions, * as projectActions from "../../../../redux/actions/projectActions";
|
||||
|
@ -89,6 +89,7 @@ export interface IEditorPageState {
|
|||
hoveredLabel: ILabel;
|
||||
/** Whether the task for loading all OCRs is running */
|
||||
isRunningOCRs?: boolean;
|
||||
isRunningAutoLabelings?: boolean;
|
||||
/** Whether OCR is running in the main canvas */
|
||||
isCanvasRunningOCR?: boolean;
|
||||
isCanvasRunningAutoLabeling?: boolean;
|
||||
|
@ -301,6 +302,7 @@ export default class EditorPage extends React.Component<IEditorPageProps, IEdito
|
|||
onAssetMetadataChanged={this.onAssetMetadataChanged}
|
||||
onCanvasRendered={this.onCanvasRendered}
|
||||
onSelectedRegionsChanged={this.onSelectedRegionsChanged}
|
||||
onRegionDoubleClick={this.onRegionDoubleClick}
|
||||
onRunningOCRStatusChanged={this.onCanvasRunningOCRStatusChanged}
|
||||
onRunningAutoLabelingStatusChanged={this.onCanvasRunningAutoLabelingStatusChanged}
|
||||
onTagChanged={this.onTagChanged}
|
||||
|
@ -312,6 +314,7 @@ export default class EditorPage extends React.Component<IEditorPageProps, IEdito
|
|||
setTableToView={this.setTableToView}
|
||||
closeTableView={this.closeTableView}
|
||||
runOcrForAllDocs={this.loadOcrForNotVisited}
|
||||
runAutoLabelingOnNextBatch={this.runAutoLabelingOnNextBatch}
|
||||
appSettings={this.props.appSettings}
|
||||
handleLabelTable={this.handleLabelTable}
|
||||
>
|
||||
|
@ -349,6 +352,7 @@ export default class EditorPage extends React.Component<IEditorPageProps, IEdito
|
|||
splitPaneWidth={this.state.rightSplitPaneWidth}
|
||||
reconfigureTableConfirm={this.reconfigureTableConfirm}
|
||||
addRowToDynamicTable={this.addRowToDynamicTable}
|
||||
onTagDoubleClick={this.onLabelDoubleClicked}
|
||||
/>
|
||||
<Confirm
|
||||
title={strings.editorPage.tags.rename.title}
|
||||
|
@ -710,30 +714,30 @@ export default class EditorPage extends React.Component<IEditorPageProps, IEdito
|
|||
private onAssetMetadataChanged = async (assetMetadata: IAssetMetadata): Promise<void> => {
|
||||
console.log("EditorPage -> assetMetadata", assetMetadata)
|
||||
// Comment out below code as we allow regions without tags, it would make labeler's work easier.
|
||||
|
||||
assetMetadata = JSON.parse(JSON.stringify(assetMetadata)); // alex
|
||||
const initialState = assetMetadata.asset.state;
|
||||
|
||||
const asset = { ...assetMetadata.asset };
|
||||
|
||||
console.log("EditorPage -> asset", asset)
|
||||
if (this.isTaggableAssetType(assetMetadata.asset)) {
|
||||
// console.log("EditorPage -> asset", asset)
|
||||
if (this.isTaggableAssetType(asset)) {
|
||||
const hasLabels = _.get(assetMetadata, "labelData.labels.length", 0) > 0;
|
||||
const hasTableLabels = _.get(assetMetadata, "labelData.tableLabels.length", 0) > 0
|
||||
assetMetadata.asset.state = hasLabels || hasTableLabels ?
|
||||
const hasTableLabels = _.get(assetMetadata, "labelData.tableLabels.length", 0) > 0;
|
||||
asset.state = hasLabels || hasTableLabels ?
|
||||
AssetState.Tagged :
|
||||
AssetState.Visited;
|
||||
} else if (assetMetadata.asset.state === AssetState.NotVisited) {
|
||||
assetMetadata.asset.state = AssetState.Visited;
|
||||
} else if (asset.state === AssetState.NotVisited) {
|
||||
asset.state = AssetState.Visited;
|
||||
}
|
||||
|
||||
// Only update asset metadata if state changes or is different
|
||||
if (initialState !== assetMetadata.asset.state || this.state.selectedAsset !== assetMetadata) {
|
||||
if (this.state.selectedAsset?.labelData?.labels && assetMetadata?.labelData?.labels &&
|
||||
assetMetadata.labelData.labels.toString() !== this.state.selectedAsset.labelData.labels.toString()) {
|
||||
if (initialState !== asset.state || this.state.selectedAsset !== assetMetadata) {
|
||||
if (this.state.selectedAsset?.labelData?.labels && assetMetadata?.labelData?.labels && assetMetadata.labelData.labels.toString() !== this.state.selectedAsset.labelData.labels.toString()) {
|
||||
await this.updatedAssetMetadata(assetMetadata);
|
||||
}
|
||||
assetMetadata.asset = asset;
|
||||
await this.props.actions.saveAssetMetadata(this.props.project, assetMetadata);
|
||||
if (this.props.project.lastVisitedAssetId === assetMetadata.asset.id) {
|
||||
if (this.props.project.lastVisitedAssetId === asset.id) {
|
||||
this.setState({ selectedAsset: assetMetadata });
|
||||
}
|
||||
}
|
||||
|
@ -742,6 +746,7 @@ export default class EditorPage extends React.Component<IEditorPageProps, IEdito
|
|||
// This forces the root assets that are displayed in the sidebar to
|
||||
// accurately show their correct state (not-visited, visited or tagged)
|
||||
const assets = [...this.state.assets];
|
||||
// const asset = { ...assetMetadata.asset };
|
||||
const assetIndex = assets.findIndex((a) => a.id === asset.id);
|
||||
if (assetIndex > -1) {
|
||||
assets[assetIndex] = {
|
||||
|
@ -783,6 +788,12 @@ export default class EditorPage extends React.Component<IEditorPageProps, IEdito
|
|||
this.setState({ selectedRegions });
|
||||
}
|
||||
|
||||
private onRegionDoubleClick = (region: IRegion) => {
|
||||
if (region.tags?.length > 0) {
|
||||
this.tagInputRef.current.focusTag(region.tags[0]);
|
||||
}
|
||||
}
|
||||
|
||||
private onTagsChanged = async (tags) => {
|
||||
const project = {
|
||||
...this.props.project,
|
||||
|
@ -816,6 +827,9 @@ export default class EditorPage extends React.Component<IEditorPageProps, IEdito
|
|||
if (this.state.isCanvasRunningAutoLabeling) {
|
||||
return;
|
||||
}
|
||||
if (this.state.isRunningAutoLabelings) {
|
||||
return;
|
||||
}
|
||||
|
||||
const assetMetadata = await this.props.actions.loadAssetMetadata(this.props.project, asset);
|
||||
|
||||
|
@ -901,11 +915,11 @@ export default class EditorPage extends React.Component<IEditorPageProps, IEdito
|
|||
const asset = this.state.assets.find((asset) => asset.id === assetId);
|
||||
if (asset && (asset.state === AssetState.NotVisited || runForAll)) {
|
||||
try {
|
||||
this.updateAssetState(asset.id, true);
|
||||
await ocrService.getRecognizedText(asset.path, asset.name, undefined, runForAll);
|
||||
this.updateAssetState(asset.id, false, AssetState.Visited);
|
||||
this.updateAssetState({ id: asset.id, isRunningOCR: true });
|
||||
await ocrService.getRecognizedText(asset.path, asset.name, asset.mimeType, undefined, runForAll);
|
||||
this.updateAssetState({ id: asset.id, isRunningOCR: false, assetState: AssetState.Visited });
|
||||
} catch (err) {
|
||||
this.updateAssetState(asset.id, false);
|
||||
this.updateAssetState({ id: asset.id, isRunningOCR: false });
|
||||
this.setState({
|
||||
isError: true,
|
||||
errorTitle: err.title,
|
||||
|
@ -920,14 +934,75 @@ export default class EditorPage extends React.Component<IEditorPageProps, IEdito
|
|||
}
|
||||
}
|
||||
}
|
||||
private runAutoLabelingOnNextBatch = async () => {
|
||||
if (this.isBusy()) {
|
||||
return;
|
||||
}
|
||||
const { project } = this.props;
|
||||
const predictService = new PredictService(project);
|
||||
const assetService = new AssetService(project);
|
||||
|
||||
private updateAssetState = (id: string, isRunningOCR: boolean, assetState?: AssetState) => {
|
||||
if (this.state.assets) {
|
||||
this.setState({ isRunningAutoLabelings: true });
|
||||
const unlabeledAssetsBatch = [];
|
||||
for (let i = 0; i < this.state.assets.length && unlabeledAssetsBatch.length < constants.autoLabelBatchSize; i++) {
|
||||
const asset = this.state.assets[i];
|
||||
if (asset.state === AssetState.NotVisited || asset.state === AssetState.Visited) {
|
||||
unlabeledAssetsBatch.push(asset);
|
||||
}
|
||||
}
|
||||
try {
|
||||
await throttle(constants.maxConcurrentServiceRequests,
|
||||
unlabeledAssetsBatch,
|
||||
async (asset) => {
|
||||
try {
|
||||
this.updateAssetState({ id: asset.id, isRunningAutoLabeling: true });
|
||||
const predictResult = await predictService.getPrediction(asset.path);
|
||||
const assetMetadata = await assetService.getAssetPredictMetadata(asset, predictResult);
|
||||
await assetService.uploadPredictResultAsOrcResult(asset, predictResult);
|
||||
this.onAssetMetadataChanged(assetMetadata);
|
||||
this.updateAssetState({
|
||||
id: asset.id, isRunningAutoLabeling: false,
|
||||
assetState: AssetState.Tagged,
|
||||
labelingState: AssetLabelingState.AutoLabeled,
|
||||
});
|
||||
this.props.actions.updatedAssetMetadata(this.props.project, assetMetadata);
|
||||
} catch (err) {
|
||||
this.updateAssetState({ id: asset.id, isRunningOCR: false, isRunningAutoLabeling: false });
|
||||
this.setState({
|
||||
isError: true,
|
||||
errorTitle: err.title,
|
||||
errorMessage: err.message
|
||||
})
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
} finally {
|
||||
this.setState({ isRunningAutoLabelings: false });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private updateAssetState = (newState: {
|
||||
id: string,
|
||||
isRunningOCR?: boolean,
|
||||
isRunningAutoLabeling?: boolean,
|
||||
assetState?: AssetState,
|
||||
labelingState?: AssetLabelingState
|
||||
}) => {
|
||||
this.setState((state) => ({
|
||||
assets: state.assets.map((asset) => {
|
||||
if (asset.id === id) {
|
||||
const updatedAsset = { ...asset, isRunningOCR };
|
||||
if (assetState !== undefined && asset.state === AssetState.NotVisited) {
|
||||
updatedAsset.state = assetState;
|
||||
if (asset.id === newState.id) {
|
||||
const updatedAsset = { ...asset, isRunningOCR: newState.isRunningOCR || false };
|
||||
if (newState.assetState !== undefined && asset.state === AssetState.NotVisited) {
|
||||
updatedAsset.state = newState.assetState;
|
||||
}
|
||||
if (newState.labelingState) {
|
||||
updatedAsset.labelingState = newState.labelingState;
|
||||
}
|
||||
if (newState.isRunningAutoLabeling !== undefined) {
|
||||
updatedAsset.isRunningAutoLabeling = newState.isRunningAutoLabeling;
|
||||
}
|
||||
return updatedAsset;
|
||||
} else {
|
||||
|
@ -935,8 +1010,8 @@ export default class EditorPage extends React.Component<IEditorPageProps, IEdito
|
|||
}
|
||||
}),
|
||||
}), () => {
|
||||
if (this.state.selectedAsset && id === this.state.selectedAsset.asset.id) {
|
||||
const asset = this.state.assets.find((asset) => asset.id === id);
|
||||
const asset = this.state.assets.find((asset) => asset.id === newState.id);
|
||||
if (this.state.selectedAsset && newState.id === this.state.selectedAsset.asset.id) {
|
||||
if (asset) {
|
||||
this.setState({
|
||||
selectedAsset: { ...this.state.selectedAsset, asset: { ...asset } },
|
||||
|
@ -953,24 +1028,56 @@ export default class EditorPage extends React.Component<IEditorPageProps, IEdito
|
|||
const updatedAssets = [...this.state.assets];
|
||||
let needUpdate = false;
|
||||
updatedAssets.forEach((asset) => {
|
||||
const projectAsset = _.get(this.props, "project.assets[asset.id]", null);
|
||||
const projectAsset = _.get(this.props, `project.assets[${asset.id}]`, null);
|
||||
if (projectAsset) {
|
||||
if (asset.state !== projectAsset.state) {
|
||||
if (asset.state !== projectAsset.state || asset.labelingState !== projectAsset.labelingState) {
|
||||
needUpdate = true;
|
||||
asset.state = projectAsset.state;
|
||||
asset.labelingState = projectAsset.labelingState;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (needUpdate) {
|
||||
this.setState({ assets: updatedAssets });
|
||||
if (this.state.selectedAsset) {
|
||||
const asset = this.state.selectedAsset.asset;
|
||||
const currentAsset = _.get(this.props, `project.assets[${this.state.selectedAsset.asset.id}]`, null);
|
||||
if (asset.state !== currentAsset.state || asset.labelingState !== currentAsset.labelingState) {
|
||||
this.updateSelectAsset(asset);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private updateSelectAsset = async (asset: IAsset) => {
|
||||
const assetMetadata = await this.props.actions.loadAssetMetadata(this.props.project, asset);
|
||||
|
||||
try {
|
||||
if (!assetMetadata.asset.size) {
|
||||
const assetProps = await HtmlFileReader.readAssetAttributes(asset);
|
||||
assetMetadata.asset.size = { width: assetProps.width, height: assetProps.height };
|
||||
}
|
||||
} catch (err) {
|
||||
console.warn("Error computing asset size");
|
||||
}
|
||||
this.setState({
|
||||
tableToView: null,
|
||||
tableToViewId: null,
|
||||
selectedAsset: assetMetadata,
|
||||
}, async () => {
|
||||
await this.onAssetMetadataChanged(assetMetadata);
|
||||
await this.props.actions.saveProject(this.props.project, false, false);
|
||||
});
|
||||
}
|
||||
private onLabelEnter = (label: ILabel) => {
|
||||
this.setState({ hoveredLabel: label });
|
||||
}
|
||||
|
||||
private onLabelDoubleClicked = (label:ILabel) =>{
|
||||
this.canvas.current.focusOnLabel(label);
|
||||
}
|
||||
|
||||
private onLabelLeave = (label: ILabel) => {
|
||||
this.setState({ hoveredLabel: null });
|
||||
}
|
||||
|
|
|
@ -4,9 +4,10 @@
|
|||
import React from "react";
|
||||
import { AutoSizer, List } from "react-virtualized";
|
||||
import { FontIcon } from "@fluentui/react";
|
||||
import { IAsset, AssetState, ISize } from "../../../../models/applicationState";
|
||||
import {AssetPreview, ContentSource} from "../../common/assetPreview/assetPreview";
|
||||
import { IAsset, AssetState, ISize, AssetLabelingState } from "../../../../models/applicationState";
|
||||
import { AssetPreview, ContentSource } from "../../common/assetPreview/assetPreview";
|
||||
import { strings } from "../../../../common/strings";
|
||||
import _ from "lodash";
|
||||
|
||||
/**
|
||||
* Properties for Editor Side Bar
|
||||
|
@ -135,11 +136,14 @@ export default class EditorSideBar extends React.Component<IEditorSideBarProps,
|
|||
}
|
||||
|
||||
private renderBadges = (asset: IAsset): JSX.Element => {
|
||||
const getBadgeTaggedClass = (state: AssetLabelingState): string => {
|
||||
return state ? `badge-tagged-${AssetLabelingState[state]}` : "";
|
||||
};
|
||||
switch (asset.state) {
|
||||
case AssetState.Tagged:
|
||||
return (
|
||||
<span title={strings.editorPage.tagged}
|
||||
className="badge badge-tagged">
|
||||
<span title={_.startCase(AssetLabelingState[asset.labelingState])}
|
||||
className={["badge", "badge-tagged", getBadgeTaggedClass(asset.labelingState)].join(" ")}>
|
||||
<FontIcon iconName="Tag" />
|
||||
</span>
|
||||
);
|
||||
|
|
|
@ -217,7 +217,16 @@ export default class HomePage extends React.Component<IHomePageProps, IHomePageS
|
|||
}
|
||||
|
||||
private deleteProject = async (project: IProject) => {
|
||||
try {
|
||||
await this.props.actions.deleteProject(project);
|
||||
} catch (error) {
|
||||
if(error instanceof AppError && error.errorCode === ErrorCode.SecurityTokenNotFound){
|
||||
toast.error(error.message, {autoClose:false});
|
||||
}
|
||||
else{
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private onProjectFileUpload = async (e, project) => {
|
||||
|
|
|
@ -369,13 +369,13 @@ export default class ModelComposePage extends React.Component<IModelComposePageP
|
|||
if (model.attributes.isComposed) {
|
||||
const inclModels = model.composedTrainResults ?
|
||||
model.composedTrainResults
|
||||
: (await this.getModelByURl(constants.apiModelsPath + "/" + model.modelId)).composedTrainResults;
|
||||
: (await this.getModelByURl(interpolate(constants.apiModelsPath, {apiVersion : (constants.apiVersion || constants.appVersion) }) + "/" + model.modelId)).composedTrainResults;
|
||||
|
||||
for (const i of Object.keys(inclModels)) {
|
||||
let _model: IModel;
|
||||
let modelInfo: IComposedModelInfo;
|
||||
try {
|
||||
_model = await this.getModelByURl(constants.apiModelsPath + "/" + inclModels[i].modelId);
|
||||
_model = await this.getModelByURl(interpolate(constants.apiModelsPath, {apiVersion : (constants.apiVersion || constants.appVersion) }) + "/" + inclModels[i].modelId);
|
||||
modelInfo = {
|
||||
id: _model.modelId,
|
||||
name: _model.modelName,
|
||||
|
@ -458,7 +458,7 @@ export default class ModelComposePage extends React.Component<IModelComposePageP
|
|||
private getRecentModels = async ():Promise<IModel[]> => {
|
||||
const recentModelsList: IModel[] = [];
|
||||
const recentModelRequest = await allSettled(this.props.project.recentModelRecords.map(async (model) => {
|
||||
return this.getModelByURl(constants.apiModelsPath + "/" + model.modelInfo.modelId);
|
||||
return this.getModelByURl(interpolate(constants.apiModelsPath, {apiVersion : (constants.apiVersion || constants.appVersion) }) + "/" + model.modelInfo.modelId);
|
||||
}))
|
||||
recentModelRequest.forEach((recentModelRequest) => {
|
||||
if (recentModelRequest.status === "fulfilled") {
|
||||
|
@ -528,7 +528,7 @@ export default class ModelComposePage extends React.Component<IModelComposePageP
|
|||
private async getResponse(nextLink?: string) {
|
||||
const baseURL = nextLink === undefined ? url.resolve(
|
||||
this.props.project.apiUriBase,
|
||||
constants.apiModelsPath,
|
||||
interpolate(constants.apiModelsPath, {apiVersion : (constants.apiVersion || constants.appVersion) }),
|
||||
) : url.resolve(
|
||||
this.props.project.apiUriBase,
|
||||
nextLink,
|
||||
|
@ -734,7 +734,7 @@ export default class ModelComposePage extends React.Component<IModelComposePageP
|
|||
modelName: name,
|
||||
};
|
||||
|
||||
const link = constants.apiModelsPath + "/compose";
|
||||
const link = interpolate(constants.apiModelsPath, {apiVersion : (constants.apiVersion || constants.appVersion) }) + "/compose";
|
||||
const composeRes = await this.post(link, payload);
|
||||
const composedModel = await this.waitUntilModelIsReady(composeRes["headers"]["location"]);
|
||||
|
||||
|
|
|
@ -5,20 +5,16 @@ import React from 'react'
|
|||
import './predictModelInfo.scss';
|
||||
|
||||
export default function PredictModelInfo({ modelInfo }) {
|
||||
const { docType, modelId, docTypeConfidence } = modelInfo;
|
||||
const { modelId, docTypeConfidence } = modelInfo;
|
||||
return (
|
||||
<div className="model-info-container">
|
||||
<div className="model-info-item">
|
||||
<span className="title" >docType:</span>
|
||||
<span className="value" >{docType}</span>
|
||||
</div>
|
||||
<div className="model-info-item">
|
||||
<span className="title" >modelId:</span>
|
||||
<span className="value" >{modelId}</span>
|
||||
</div>
|
||||
<div className="model-info-item">
|
||||
<span className="title" >docTypeConfidence:</span>
|
||||
<span className="value" >{docTypeConfidence}</span>
|
||||
<span className="value" >{(docTypeConfidence * 100).toFixed(2) + "%"}</span>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
|
|
@ -649,6 +649,7 @@ export default class PredictPage extends React.Component<IPredictPageProps, IPre
|
|||
handleRotateImage={this.handleRotateCanvas}
|
||||
project={this.props.project}
|
||||
parentPage={"predict"}
|
||||
layers={{}}
|
||||
/>
|
||||
<ImageMap
|
||||
parentPage={ImageMapParent.Predict}
|
||||
|
@ -767,7 +768,7 @@ export default class PredictPage extends React.Component<IPredictPageProps, IPre
|
|||
case "<model_id>":
|
||||
return modelID;
|
||||
case "<API_version>":
|
||||
return constants.apiVersion;
|
||||
return (this.props.project?.apiVersion || constants.apiVersion);
|
||||
}
|
||||
});
|
||||
const fileURL = window.URL.createObjectURL(
|
||||
|
@ -811,7 +812,7 @@ export default class PredictPage extends React.Component<IPredictPageProps, IPre
|
|||
}
|
||||
const endpointURL = url.resolve(
|
||||
this.props.project.apiUriBase,
|
||||
`${constants.apiModelsPath}/${modelID}/analyze?includeTextDetails=true`,
|
||||
`${interpolate(constants.apiModelsPath, {apiVersion : (constants.apiVersion || constants.appVersion) })}/${modelID}/analyze?includeTextDetails=true`,
|
||||
);
|
||||
let headers;
|
||||
let body;
|
||||
|
@ -1037,7 +1038,7 @@ export default class PredictPage extends React.Component<IPredictPageProps, IPre
|
|||
} else if (response.data.status.toLowerCase() === constants.statusCodeFailed) {
|
||||
reject(_.get(
|
||||
response,
|
||||
"data.analyzeResult.errors[0].errorMessage",
|
||||
"data.analyzeResult.errors[0]",
|
||||
"Generic error during prediction"));
|
||||
} else if (Number(new Date()) < endTime) {
|
||||
// If the request isn't succeeded and the timeout hasn't elapsed, go again
|
||||
|
@ -1075,7 +1076,6 @@ export default class PredictPage extends React.Component<IPredictPageProps, IPre
|
|||
}
|
||||
private onAddAssetToProjectClick = async () => {
|
||||
if (this.state.file) {
|
||||
// this.props.project.assets
|
||||
const fileName = `${this.props.project.folderPath}/${decodeURIComponent(this.state.file.name)}`;
|
||||
const asset = Object.values(this.props.project.assets).find(asset => asset.name === fileName);
|
||||
if (asset) {
|
||||
|
@ -1167,7 +1167,7 @@ export default class PredictPage extends React.Component<IPredictPageProps, IPre
|
|||
const modelID = this.props.project.predictModelId;
|
||||
const endpointURL = url.resolve(
|
||||
this.props.project.apiUriBase,
|
||||
`${constants.apiModelsPath}/${modelID}`,
|
||||
`${interpolate(constants.apiModelsPath, {apiVersion : (constants.apiVersion || constants.appVersion) })}/${modelID}`,
|
||||
);
|
||||
let response;
|
||||
try {
|
||||
|
|
|
@ -127,7 +127,7 @@ export default class PredictResult extends React.Component<IPredictResultProps,
|
|||
}
|
||||
</div>
|
||||
<div className={"predictiontag-confidence"}>
|
||||
<span>{item.confidence}</span>
|
||||
<span>{(item.confidence * 100).toFixed(2)+"%" }</span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -26,6 +26,11 @@
|
|||
"description": "API key",
|
||||
"type": "string"
|
||||
},
|
||||
"apiVersion" : {
|
||||
"title": "API version",
|
||||
"description": "API version",
|
||||
"type": "string"
|
||||
},
|
||||
"description": {
|
||||
"title": "${strings.common.description}",
|
||||
"type": "string"
|
||||
|
|
|
@ -31,6 +31,11 @@
|
|||
"description": "API key",
|
||||
"type": "string"
|
||||
},
|
||||
"apiVersion" : {
|
||||
"title": "API version",
|
||||
"description": "API version",
|
||||
"type": "string"
|
||||
},
|
||||
"description": {
|
||||
"title": "${strings.common.description}",
|
||||
"type": "string"
|
||||
|
|
|
@ -16,6 +16,7 @@ import { ProjectSettingAction } from "./projectSettingAction";
|
|||
import { ProtectedInput } from "../../common/protectedInput/protectedInput";
|
||||
import { PrimaryButton } from "@fluentui/react";
|
||||
import { getPrimaryGreenTheme, getPrimaryGreyTheme } from "../../../../common/themes";
|
||||
import { APIVersionPicker, IAPIVersionPickerProps } from "../../common/apiVersionPicker/apiVersionPicker";
|
||||
|
||||
// tslint:disable-next-line:no-var-requires
|
||||
const newFormSchema = addLocValues(require("./newProjectForm.json"));
|
||||
|
@ -62,6 +63,7 @@ export interface IProjectFormState {
|
|||
export default class ProjectForm extends React.Component<IProjectFormProps, IProjectFormState> {
|
||||
private widgets = {
|
||||
protectedInput: (ProtectedInput as any) as Widget,
|
||||
apiVersion: (APIVersionPicker as any) as Widget
|
||||
};
|
||||
|
||||
constructor(props, context) {
|
||||
|
@ -155,6 +157,11 @@ export default class ProjectForm extends React.Component<IProjectFormProps, IPro
|
|||
onChange: props.onChange,
|
||||
};
|
||||
}),
|
||||
apiVersion: CustomField<IAPIVersionPickerProps>(APIVersionPicker, (props) => ({
|
||||
id: props.idSchema.$id,
|
||||
value: props.formData,
|
||||
onChange: props.onChange,
|
||||
})),
|
||||
targetConnection: CustomField<IConnectionProviderPickerProps>(ConnectionPickerWithRouter, (props) => {
|
||||
const targetConnections = this.props.connections
|
||||
.filter((connection) => StorageProviderFactory.isRegistered(connection.providerType));
|
||||
|
@ -209,6 +216,7 @@ export default class ProjectForm extends React.Component<IProjectFormProps, IPro
|
|||
sourceConnection: args.formData.sourceConnection,
|
||||
folderPath: this.normalizeFolderPath(args.formData.folderPath),
|
||||
apiUriBase: args.formData.apiUriBase.trim(),
|
||||
apiVersion: args.formData.apiVersion,
|
||||
};
|
||||
this.props.onSubmit(project);
|
||||
}
|
||||
|
|
|
@ -16,5 +16,8 @@
|
|||
},
|
||||
"apiKey": {
|
||||
"ui:widget": "protectedInput"
|
||||
},
|
||||
"apiVersion": {
|
||||
"ui:widget": "apiVersion"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -80,3 +80,18 @@
|
|||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.project-saving {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
background-color: rgba(0, 0, 0, 0.8);
|
||||
text-align: center;
|
||||
display: flex;
|
||||
.project-saving-spinner {
|
||||
margin: auto;
|
||||
font-size: 24px;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,7 +6,7 @@ import { Redirect } from "react-router";
|
|||
import { connect } from "react-redux";
|
||||
import { bindActionCreators } from "redux";
|
||||
import { RouteComponentProps } from "react-router-dom";
|
||||
import { FontIcon } from "@fluentui/react";
|
||||
import { FontIcon, Label, Spinner, SpinnerSize } from "@fluentui/react";
|
||||
import ProjectForm from "./projectForm";
|
||||
import { constants } from "../../../../common/constants";
|
||||
import { strings, interpolate } from "../../../../common/strings";
|
||||
|
@ -43,6 +43,7 @@ export interface IProjectSettingsPageState {
|
|||
project: IProject;
|
||||
action: ProjectSettingAction;
|
||||
isError: boolean;
|
||||
isCommiting: boolean;
|
||||
}
|
||||
|
||||
function mapStateToProps(state: IApplicationState) {
|
||||
|
@ -72,6 +73,7 @@ export default class ProjectSettingsPage extends React.Component<IProjectSetting
|
|||
project: this.props.project,
|
||||
action: null,
|
||||
isError: false,
|
||||
isCommiting: false,
|
||||
};
|
||||
|
||||
public async componentDidMount() {
|
||||
|
@ -107,6 +109,11 @@ export default class ProjectSettingsPage extends React.Component<IProjectSetting
|
|||
}
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
if (this.state.project?.id) {
|
||||
removeStorageItem(constants.projectFormTempKey);
|
||||
}
|
||||
}
|
||||
// Hide ProjectMetrics for private-preview
|
||||
public render() {
|
||||
return (
|
||||
|
@ -132,6 +139,14 @@ export default class ProjectSettingsPage extends React.Component<IProjectSetting
|
|||
{this.state.isError &&
|
||||
<Redirect to="/" />
|
||||
}
|
||||
{this.state.isCommiting &&
|
||||
<div className="project-saving">
|
||||
<div className="project-saving-spinner">
|
||||
<Label className="p-0" ></Label>
|
||||
<Spinner size={SpinnerSize.large} label="Saving Project..." ariaLive="assertive" labelPosition="right" />
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -171,12 +186,14 @@ export default class ProjectSettingsPage extends React.Component<IProjectSetting
|
|||
private onFormChange = (project: IProject) => {
|
||||
if (this.isPartialProject(project)) {
|
||||
setStorageItem(constants.projectFormTempKey, JSON.stringify(project));
|
||||
this.setState({ project });
|
||||
}
|
||||
}
|
||||
|
||||
private onFormSubmit = async (project: IProject) => {
|
||||
const isNew = !(!!project.id);
|
||||
|
||||
try {
|
||||
this.setState({ isCommiting: true });
|
||||
const projectService = new ProjectService();
|
||||
if (!(await projectService.isValidProjectConnection(project))) {
|
||||
return;
|
||||
|
@ -190,7 +207,7 @@ export default class ProjectSettingsPage extends React.Component<IProjectSetting
|
|||
await this.deleteOldProjectWhenRenamed(project, isNew);
|
||||
await this.props.applicationActions.ensureSecurityToken(project);
|
||||
await this.props.projectActions.saveProject(project, false, true);
|
||||
removeStorageItem(constants.projectFormTempKey);
|
||||
// removeStorageItem(constants.projectFormTempKey);
|
||||
|
||||
toast.success(interpolate(strings.projectSettings.messages.saveSuccess, { project }));
|
||||
|
||||
|
@ -199,6 +216,10 @@ export default class ProjectSettingsPage extends React.Component<IProjectSetting
|
|||
} else {
|
||||
this.props.history.goBack();
|
||||
}
|
||||
} finally {
|
||||
this.setState({ isCommiting: false });
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private onFormCancel = () => {
|
||||
|
@ -210,7 +231,7 @@ export default class ProjectSettingsPage extends React.Component<IProjectSetting
|
|||
* Checks whether a project is partially populated
|
||||
*/
|
||||
private isPartialProject = (project: IProject): boolean => {
|
||||
return project && !(!!project.id) &&
|
||||
return project &&
|
||||
(
|
||||
!!project.name
|
||||
|| !!project.description
|
||||
|
|
|
@ -5,12 +5,12 @@ import React from "react";
|
|||
import { connect } from "react-redux";
|
||||
import { RouteComponentProps } from "react-router-dom";
|
||||
import { bindActionCreators } from "redux";
|
||||
import { FontIcon, PrimaryButton, Spinner, SpinnerSize, TextField} from "@fluentui/react";
|
||||
import { FontIcon, PrimaryButton, Spinner, SpinnerSize, TextField } from "@fluentui/react";
|
||||
import IProjectActions, * as projectActions from "../../../../redux/actions/projectActions";
|
||||
import IApplicationActions, * as applicationActions from "../../../../redux/actions/applicationActions";
|
||||
import IAppTitleActions, * as appTitleActions from "../../../../redux/actions/appTitleActions";
|
||||
import {
|
||||
IApplicationState, IConnection, IProject, IAppSettings, FieldType, IRecentModel,
|
||||
IApplicationState, IConnection, IProject, IAppSettings, FieldType, IRecentModel, AssetLabelingState,
|
||||
} from "../../../../models/applicationState";
|
||||
import TrainChart from "./trainChart";
|
||||
import TrainPanel from "./trainPanel";
|
||||
|
@ -26,6 +26,8 @@ import PreventLeaving from "../../common/preventLeaving/preventLeaving";
|
|||
import ServiceHelper from "../../../../services/serviceHelper";
|
||||
import { getPrimaryGreenTheme, getGreenWithWhiteBackgroundTheme } from "../../../../common/themes";
|
||||
import { getAppInsights } from '../../../../services/telemetryService';
|
||||
import { AssetService } from "../../../../services/assetService";
|
||||
import Confirm from "../../common/confirm/confirm";
|
||||
import UseLocalStorage from '../../../../services/useLocalStorage';
|
||||
import { isElectron } from "../../../../common/hostProcess";
|
||||
|
||||
|
@ -80,6 +82,7 @@ function mapDispatchToProps(dispatch) {
|
|||
@connect(mapStateToProps, mapDispatchToProps)
|
||||
export default class TrainPage extends React.Component<ITrainPageProps, ITrainPageState> {
|
||||
private appInsights: any = null;
|
||||
private notAdjustedLabelsConfirm: React.RefObject<Confirm> = React.createRef();
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
@ -123,6 +126,8 @@ export default class TrainPage extends React.Component<ITrainPageProps, ITrainPa
|
|||
const currTrainRecord = this.state.currTrainRecord;
|
||||
const localFileSystemProvider: boolean = this.props.project && this.props.project.sourceConnection &&
|
||||
this.props.project.sourceConnection.providerType === "localFileSystemProxy";
|
||||
const trainDisabled: boolean = localFileSystemProvider && (this.state.inputtedLabelFolderURL.length === 0 ||
|
||||
this.state.inputtedLabelFolderURL === strings.train.defaultLabelFolderURL);
|
||||
|
||||
return (
|
||||
<div className="train-page skipToMainContent" id="pageTrain">
|
||||
|
@ -180,7 +185,7 @@ export default class TrainPage extends React.Component<ITrainPageProps, ITrainPa
|
|||
{!this.state.isTraining ? (
|
||||
<div className="container-items-end">
|
||||
<PrimaryButton
|
||||
style={{"margin": "15px 0px"}}
|
||||
style={{ "margin": "15px 0px" }}
|
||||
id="train_trainButton"
|
||||
theme={getPrimaryGreenTheme()}
|
||||
autoFocus={true}
|
||||
|
@ -221,10 +226,10 @@ export default class TrainPage extends React.Component<ITrainPageProps, ITrainPa
|
|||
autoFocus={true}
|
||||
className="flex-center"
|
||||
onClick={this.handleDownloadJSONClick}
|
||||
disabled={this.state.isTraining}>
|
||||
disabled={trainDisabled}>
|
||||
<FontIcon
|
||||
iconName="Download"
|
||||
style={{ fontWeight: 600 }}/>
|
||||
style={{ fontWeight: 600 }} />
|
||||
<h6 className="d-inline text-shadow-none ml-2 mb-0">
|
||||
{strings.train.downloadJson}</h6>
|
||||
</PrimaryButton>
|
||||
|
@ -244,6 +249,13 @@ export default class TrainPage extends React.Component<ITrainPageProps, ITrainPa
|
|||
when={this.state.isTraining}
|
||||
message={"A training operation is currently in progress, are you sure you want to leave?"}
|
||||
/>
|
||||
<Confirm
|
||||
ref={this.notAdjustedLabelsConfirm}
|
||||
title={strings.train.trainConfirm.title}
|
||||
message={strings.train.trainConfirm.message}
|
||||
onConfirm={this.handleModelTrainConfirm}
|
||||
confirmButtonTheme={getPrimaryGreenTheme()}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -268,7 +280,7 @@ export default class TrainPage extends React.Component<ITrainPageProps, ITrainPa
|
|||
|
||||
private removeDefaultInputtedLabelFolderURL = () => {
|
||||
if (this.state.inputtedLabelFolderURL === strings.train.defaultLabelFolderURL) {
|
||||
this.setState({inputtedLabelFolderURL: ""});
|
||||
this.setState({ inputtedLabelFolderURL: "" });
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -284,18 +296,43 @@ export default class TrainPage extends React.Component<ITrainPageProps, ITrainPa
|
|||
}
|
||||
|
||||
private handleTrainClick = () => {
|
||||
const assets = Object.values(this.props.project.assets)
|
||||
.filter(asset => asset.labelingState === AssetLabelingState.AutoLabeled);
|
||||
if (assets.length > 0) {
|
||||
this.notAdjustedLabelsConfirm.current.open();
|
||||
} else {
|
||||
this.handleModelTrain();
|
||||
}
|
||||
}
|
||||
|
||||
private handleModelTrainConfirm = () => {
|
||||
this.handleModelTrain();
|
||||
}
|
||||
|
||||
private handleModelTrain = () => {
|
||||
this.setState({
|
||||
isTraining: true,
|
||||
trainMessage: strings.train.training,
|
||||
});
|
||||
|
||||
this.trainProcess().then((trainResult) => {
|
||||
this.trainProcess().then(async (trainResult) => {
|
||||
this.setState((prevState, props) => ({
|
||||
isTraining: false,
|
||||
trainMessage: this.getTrainMessage(trainResult),
|
||||
currTrainRecord: this.getProjectTrainRecord(),
|
||||
modelName: "",
|
||||
}));
|
||||
const assets = Object.values(this.props.project.assets);
|
||||
const assetService = new AssetService(this.props.project);
|
||||
for (const asset of assets) {
|
||||
const newAsset = JSON.parse(JSON.stringify(asset));
|
||||
newAsset.labelingState = AssetLabelingState.Trained;
|
||||
const metadata = await assetService.getAssetMetadata(newAsset);
|
||||
if (metadata.labelData && metadata.labelData.labelingState !== AssetLabelingState.Trained) {
|
||||
metadata.labelData.labelingState = AssetLabelingState.Trained;
|
||||
await assetService.save({ ...metadata });
|
||||
}
|
||||
}
|
||||
// reset localStorage successful train process
|
||||
localStorage.setItem("trainPage_inputs", "{}");
|
||||
}).catch((err) => {
|
||||
|
@ -305,7 +342,7 @@ export default class TrainPage extends React.Component<ITrainPageProps, ITrainPa
|
|||
});
|
||||
});
|
||||
if (this.appInsights) {
|
||||
this.appInsights.trackEvent({name: "TRAIN_MODEL_EVENT"});
|
||||
this.appInsights.trackEvent({ name: "TRAIN_MODEL_EVENT" });
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -338,7 +375,7 @@ export default class TrainPage extends React.Component<ITrainPageProps, ITrainPa
|
|||
private async train(): Promise<any> {
|
||||
const baseURL = url.resolve(
|
||||
this.props.project.apiUriBase,
|
||||
constants.apiModelsPath,
|
||||
interpolate(constants.apiModelsPath, {apiVersion : (constants.apiVersion || constants.appVersion) }),
|
||||
);
|
||||
const provider = this.props.project.sourceConnection.providerOptions as any;
|
||||
let trainSourceURL;
|
||||
|
@ -367,7 +404,7 @@ export default class TrainPage extends React.Component<ITrainPageProps, ITrainPa
|
|||
{},
|
||||
this.props.project.apiKey as string,
|
||||
);
|
||||
this.setState({modelUrl: result.headers.location});
|
||||
this.setState({ modelUrl: result.headers.location });
|
||||
return result;
|
||||
} catch (err) {
|
||||
ServiceHelper.handleServiceError(err);
|
||||
|
@ -390,7 +427,7 @@ export default class TrainPage extends React.Component<ITrainPageProps, ITrainPa
|
|||
private buildUpdatedProject = (newTrainRecord: ITrainRecordProps): IProject => {
|
||||
const recentModelRecords: IRecentModel[] = this.props.project.recentModelRecords ?
|
||||
[...this.props.project.recentModelRecords] : [];
|
||||
recentModelRecords.unshift({...newTrainRecord, isComposed: false} as IRecentModel);
|
||||
recentModelRecords.unshift({ ...newTrainRecord, isComposed: false } as IRecentModel);
|
||||
if (recentModelRecords.length > constants.recentModelRecordsCount) {
|
||||
recentModelRecords.pop();
|
||||
}
|
||||
|
@ -487,7 +524,7 @@ export default class TrainPage extends React.Component<ITrainPageProps, ITrainPa
|
|||
}
|
||||
|
||||
private async triggerJsonDownload(): Promise<any> {
|
||||
const currModelUrl = this.props.project.apiUriBase + constants.apiModelsPath + "/" + this.state.currTrainRecord.modelInfo.modelId;
|
||||
const currModelUrl = this.props.project.apiUriBase + interpolate(constants.apiModelsPath, {apiVersion : (constants.apiVersion || constants.appVersion) }) + "/" + this.state.currTrainRecord.modelInfo.modelId;
|
||||
const modelUrl = this.state.modelUrl.length ? this.state.modelUrl : currModelUrl;
|
||||
const modelJSON = await this.getModelsJson(this.props.project, modelUrl);
|
||||
|
||||
|
@ -495,7 +532,7 @@ export default class TrainPage extends React.Component<ITrainPageProps, ITrainPa
|
|||
new Blob([modelJSON]));
|
||||
const fileLink = document.createElement("a");
|
||||
const fileBaseName = "model";
|
||||
const downloadFileName =`${fileBaseName}-${this.state.currTrainRecord.modelInfo.modelId}.json`;
|
||||
const downloadFileName = `${fileBaseName}-${this.state.currTrainRecord.modelInfo.modelId}.json`;
|
||||
|
||||
fileLink.href = fileURL;
|
||||
fileLink.setAttribute("download", downloadFileName);
|
||||
|
|
|
@ -41,7 +41,7 @@ export default class TrainRecord extends React.Component<ITrainRecordProps, ITra
|
|||
</p>
|
||||
<h6>Average accuracy:</h6>
|
||||
<p>
|
||||
{this.props.averageAccuracy}
|
||||
{(this.props.averageAccuracy * 100).toFixed(2)+"%"}
|
||||
</p>
|
||||
<div className="accuracy-info">
|
||||
<a href="https://aka.ms/form-recognizer/docs/train" target="_blank" rel="noopener noreferrer">
|
||||
|
|
|
@ -39,7 +39,7 @@ export default class TrainTable
|
|||
Object.entries(this.props.accuracies).map((entry) =>
|
||||
<tr key={entry[0]}>
|
||||
<td>{entry[0]}</td>
|
||||
<td className="text-right">{entry[1]}</td>
|
||||
<td className="text-right">{(entry[1] * 100).toFixed(2) + "%"}</td>
|
||||
</tr>)
|
||||
}
|
||||
</tbody>
|
||||
|
|
|
@ -11,7 +11,7 @@ describe("StatusBar component", () => {
|
|||
|
||||
function createComponent() {
|
||||
return mount(
|
||||
<StatusBar>
|
||||
<StatusBar project={undefined}>
|
||||
<div className="child-component">Child Component</div>
|
||||
</StatusBar>,
|
||||
);
|
||||
|
|
|
@ -5,18 +5,31 @@ import React from "react";
|
|||
import { FontIcon } from "@fluentui/react";
|
||||
import { constants } from "../../../common/constants";
|
||||
import "./statusBar.scss";
|
||||
import { IProject } from "../../../models/applicationState";
|
||||
|
||||
export class StatusBar extends React.Component {
|
||||
export interface IStatusBarProps {
|
||||
project: IProject;
|
||||
}
|
||||
|
||||
export class StatusBar extends React.Component<IStatusBarProps> {
|
||||
public render() {
|
||||
return (
|
||||
<div className="status-bar">
|
||||
<div className="status-bar-main">{this.props.children}</div>
|
||||
<div className="status-bar-version">
|
||||
<ul>
|
||||
{this.props.project &&
|
||||
<li>
|
||||
<a href="https://github.com/microsoft/OCR-Form-Tools/blob/master/CHANGELOG.md" target="blank" rel="noopener noreferrer">
|
||||
<FontIcon iconName="AzureAPIManagement" />
|
||||
<span>{ this.props.project.apiVersion || constants.apiVersion }</span>
|
||||
</a>
|
||||
</li>
|
||||
}
|
||||
<li>
|
||||
<a href="https://github.com/microsoft/OCR-Form-Tools/blob/master/CHANGELOG.md" target="blank" rel="noopener noreferrer">
|
||||
<FontIcon iconName="BranchMerge" />
|
||||
<span>{constants.appVersion}-b92b73b</span>
|
||||
<span>{constants.appVersionRaw}-1f33130</span>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
|
|
@ -152,11 +152,11 @@ export function deleteProject(project: IProject)
|
|||
.find((securityToken) => securityToken.name === project.securityToken);
|
||||
|
||||
if (!projectToken) {
|
||||
throw new AppError(ErrorCode.SecurityTokenNotFound, "Security Token Not Found");
|
||||
dispatch(deleteProjectAction(project));
|
||||
throw new AppError(ErrorCode.SecurityTokenNotFound, interpolate(strings.errors.projectDeleteErrorSecurityTokenNotFound.message, {project}));
|
||||
}
|
||||
|
||||
const decryptedProject = await projectService.load(project, projectToken);
|
||||
|
||||
await projectService.delete(decryptedProject);
|
||||
dispatch(deleteProjectAction(decryptedProject));
|
||||
};
|
||||
|
@ -181,7 +181,7 @@ export function addAssetToProject(project: IProject, fileName: string, buffer: B
|
|||
const assetName = project.folderPath ? `${project.folderPath}/${fileName}` : fileName;
|
||||
const asset = assets.find(a => a.name === assetName);
|
||||
|
||||
await assetService.uploadAssetPredictResult(asset, analyzeResult);
|
||||
await assetService.syncAssetPredictResult(asset, analyzeResult);
|
||||
dispatch(addAssetToProjectAction(asset));
|
||||
return asset;
|
||||
};
|
||||
|
|
|
@ -38,9 +38,9 @@ export const reducer = (state: IProject = null, action: AnyAction): IProject =>
|
|||
};
|
||||
case ActionTypes.DELETE_PROJECT_ASSET_SUCCESS:
|
||||
case ActionTypes.LOAD_PROJECT_ASSETS_SUCCESS:
|
||||
const assets = {};
|
||||
let assets = {};
|
||||
action.payload.forEach((asset) => {
|
||||
assets[asset.id] = asset;
|
||||
assets = { ...assets, [asset.id]: { ...asset } };
|
||||
});
|
||||
|
||||
return {
|
||||
|
|
|
@ -22,6 +22,7 @@ export const reducer = (state: IProject[] = [], action: AnyAction): IProject[] =
|
|||
let newState: IProject[] = null;
|
||||
|
||||
switch (action.type) {
|
||||
case ActionTypes.LOAD_PROJECT_SUCCESS:
|
||||
case ActionTypes.SAVE_PROJECT_SUCCESS:
|
||||
return [
|
||||
{ ...action.payload },
|
||||
|
@ -38,6 +39,9 @@ export const reducer = (state: IProject[] = [], action: AnyAction): IProject[] =
|
|||
return updatedProject;
|
||||
});
|
||||
return newState;
|
||||
case ActionTypes.UPDATE_TAG_LABEL_COUNTS_SUCCESS:
|
||||
return [{ ...action.payload },
|
||||
...state.filter(project => project.id !== action.payload.id)];
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
|
|
|
@ -80,6 +80,6 @@ export function registerIcons() {
|
|||
RectangleShape: "\uF1A9",
|
||||
Rotate90CounterClockwise: "\uF80E",
|
||||
Rotate90Clockwise: "\uF80D",
|
||||
},
|
||||
AzureAPIManagement: "\uF37F", },
|
||||
});
|
||||
}
|
||||
|
|
|
@ -5,7 +5,7 @@ import _ from "lodash";
|
|||
import Guard from "../common/guard";
|
||||
import {
|
||||
IAsset, AssetType, IProject, IAssetMetadata, AssetState,
|
||||
ILabelData, ILabel,
|
||||
ILabelData, ILabel, AssetLabelingState
|
||||
} from "../models/applicationState";
|
||||
import { AssetProviderFactory, IAssetProvider } from "../providers/storage/assetProviderFactory";
|
||||
import { StorageProviderFactory, IStorageProvider } from "../providers/storage/storageProviderFactory";
|
||||
|
@ -16,6 +16,9 @@ import { strings, interpolate } from "../common/strings";
|
|||
import { sha256Hash } from "../common/crypto";
|
||||
import { toast } from "react-toastify";
|
||||
import allSettled from "promise.allsettled"
|
||||
import mime from 'mime';
|
||||
import FileType from 'file-type';
|
||||
import BrowserFileType from 'file-type/browser';
|
||||
|
||||
const supportedImageFormats = {
|
||||
jpg: null, jpeg: null, null: null, png: null, bmp: null, tif: null, tiff: null, pdf: null,
|
||||
|
@ -65,9 +68,10 @@ export class AssetService {
|
|||
private getOcrFromAnalyzeResult(analyzeResult: any) {
|
||||
return _.get(analyzeResult, "analyzeResult.readResults", []);
|
||||
}
|
||||
async uploadAssetPredictResult(asset: IAsset, readResults: any): Promise<void> {
|
||||
getAssetPredictMetadata(asset: IAsset, predictResults: any) {
|
||||
asset = JSON.parse(JSON.stringify(asset));
|
||||
const getBoundingBox = (pageIndex, arr: number[]) => {
|
||||
const ocrForCurrentPage: any = this.getOcrFromAnalyzeResult(readResults)[pageIndex - 1];
|
||||
const ocrForCurrentPage: any = this.getOcrFromAnalyzeResult(predictResults)[pageIndex - 1];
|
||||
const ocrExtent = [0, 0, ocrForCurrentPage.width, ocrForCurrentPage.height];
|
||||
const ocrWidth = ocrExtent[2] - ocrExtent[0];
|
||||
const ocrHeight = ocrExtent[3] - ocrExtent[1];
|
||||
|
@ -83,7 +87,7 @@ export class AssetService {
|
|||
const getLabelValues = (field: any) => {
|
||||
return field.elements.map((path: string) => {
|
||||
const pathArr = path.split('/').slice(1);
|
||||
const word = pathArr.reduce((obj: any, key: string) => obj[key], { ...readResults.analyzeResult });
|
||||
const word = pathArr.reduce((obj: any, key: string) => obj[key], { ...predictResults.analyzeResult });
|
||||
return {
|
||||
page: field.page,
|
||||
text: word.text || word.state,
|
||||
|
@ -92,59 +96,76 @@ export class AssetService {
|
|||
};
|
||||
});
|
||||
};
|
||||
const labels = [];
|
||||
readResults.analyzeResult.documentResults
|
||||
const labels =
|
||||
predictResults.analyzeResult.documentResults
|
||||
.map(result => Object.keys(result.fields)
|
||||
.filter(key => result.fields[key])
|
||||
.map<ILabel>(key => (
|
||||
{
|
||||
label: key,
|
||||
key: null,
|
||||
confidence: result.fields[key].confidence,
|
||||
value: getLabelValues(result.fields[key])
|
||||
}))).forEach(items => {
|
||||
labels.push(...items);
|
||||
});
|
||||
}))).flat(2);
|
||||
|
||||
if (labels.length > 0) {
|
||||
const fileName = decodeURIComponent(asset.name).split('/').pop();
|
||||
const labelData: ILabelData = {
|
||||
document: fileName,
|
||||
labelingState: AssetLabelingState.AutoLabeled,
|
||||
labels
|
||||
};
|
||||
const metadata = {
|
||||
...await this.getAssetMetadata(asset),
|
||||
labelData
|
||||
const metadata: IAssetMetadata = {
|
||||
asset: { ...asset, labelingState: AssetLabelingState.AutoLabeled },
|
||||
regions: [],
|
||||
version: appInfo.version,
|
||||
labelData,
|
||||
};
|
||||
metadata.asset.state = AssetState.Tagged;
|
||||
|
||||
const ocrData = JSON.parse(JSON.stringify(readResults));
|
||||
return metadata;
|
||||
}
|
||||
else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
async uploadPredictResultAsOrcResult(asset: IAsset, predictResults: any): Promise<void> {
|
||||
const ocrData = JSON.parse(JSON.stringify(predictResults));
|
||||
delete ocrData.analyzeResult.documentResults;
|
||||
if (ocrData.analyzeResult.errors) {
|
||||
delete ocrData.analyzeResult.errors;
|
||||
}
|
||||
const ocrFileName = `${asset.name}${constants.ocrFileExtension}`;
|
||||
await Promise.all([
|
||||
this.save(metadata),
|
||||
this.storageProvider.writeText(ocrFileName, JSON.stringify(ocrData, null, 2))
|
||||
]);
|
||||
await this.storageProvider.writeText(ocrFileName, JSON.stringify(ocrData, null, 2));
|
||||
}
|
||||
else {
|
||||
const ocrData = { ...readResults };
|
||||
|
||||
async syncAssetPredictResult(asset: IAsset, predictResults: any): Promise<IAssetMetadata> {
|
||||
const assetMeatadata = this.getAssetPredictMetadata(asset, predictResults);
|
||||
const ocrData = JSON.parse(JSON.stringify(predictResults));
|
||||
delete ocrData.analyzeResult.documentResults;
|
||||
if (ocrData.analyzeResult.errors) {
|
||||
delete ocrData.analyzeResult.errors;
|
||||
}
|
||||
const ocrFileName = `${asset.name}${constants.ocrFileExtension}`;
|
||||
if (assetMeatadata) {
|
||||
|
||||
|
||||
await Promise.all([
|
||||
this.save(assetMeatadata),
|
||||
this.storageProvider.writeText(ocrFileName, JSON.stringify(ocrData, null, 2))
|
||||
]);
|
||||
return assetMeatadata;
|
||||
}
|
||||
else {
|
||||
const labelFileName = decodeURIComponent(`${asset.name}${constants.labelFileExtension}`);
|
||||
const ocrFileName = decodeURIComponent(`${asset.name}${constants.ocrFileExtension}`);
|
||||
try {
|
||||
await Promise.all([
|
||||
this.storageProvider.deleteFile(labelFileName, true, true),
|
||||
this.storageProvider.writeText(ocrFileName, JSON.stringify(ocrData, null, 2))
|
||||
]);
|
||||
} catch (err) {
|
||||
// The label file may not exist - that's OK.
|
||||
}
|
||||
catch{
|
||||
return;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
/**
|
||||
|
@ -175,26 +196,42 @@ export class AssetService {
|
|||
// eslint-disable-next-line
|
||||
const extensionParts = fileNameParts[fileNameParts.length - 1].split(/[\?#]/);
|
||||
let assetFormat = extensionParts[0].toLowerCase();
|
||||
|
||||
let assetMimeType = mime.getType(assetFormat);
|
||||
if (supportedImageFormats.hasOwnProperty(assetFormat)) {
|
||||
let types;
|
||||
let checkFileType;
|
||||
let corruptFileName;
|
||||
if (nodejsMode) {
|
||||
const FileType = require('file-type');
|
||||
const fileType = await FileType.fromFile(normalizedPath);
|
||||
types = [fileType.ext];
|
||||
try {
|
||||
checkFileType = await FileType.fromFile(normalizedPath);
|
||||
} catch {
|
||||
// do nothing
|
||||
}
|
||||
corruptFileName = fileName.split(/[\\\/]/).pop().replace(/%20/g, " ");
|
||||
|
||||
} else {
|
||||
types = await this.getMimeType(filePath);
|
||||
try {
|
||||
const getFetchSteam = (): Promise<Response> => this.pollForFetchAPI(() => fetch(filePath), 1000, 200);
|
||||
const response = await getFetchSteam();
|
||||
checkFileType = await BrowserFileType.fromStream(response.body);
|
||||
} catch {
|
||||
// do nothing
|
||||
}
|
||||
corruptFileName = fileName.split("%2F").pop().replace(/%20/g, " ");
|
||||
}
|
||||
if (!types) {
|
||||
let fileType;
|
||||
let mimeType;
|
||||
if (checkFileType) {
|
||||
fileType = checkFileType.ext;
|
||||
mimeType = checkFileType.mime;
|
||||
}
|
||||
|
||||
if (!fileType) {
|
||||
console.error(interpolate(strings.editorPage.assetWarning.incorrectFileExtension.failedToFetch, { fileName: corruptFileName.toLocaleUpperCase() }));
|
||||
}
|
||||
// If file was renamed/spoofed - fix file extension to true MIME type and show message
|
||||
else if (!types.includes(assetFormat)) {
|
||||
assetFormat = types[0];
|
||||
// If file was renamed/spoofed - fix file extension to true MIME if it's type is in supported file types and show message
|
||||
else if (fileType !== assetFormat) {
|
||||
assetFormat = fileType;
|
||||
assetMimeType = mimeType;
|
||||
console.error(`${strings.editorPage.assetWarning.incorrectFileExtension.attention} ${corruptFileName.toLocaleUpperCase()} ${strings.editorPage.assetWarning.incorrectFileExtension.text} ${corruptFileName.toLocaleUpperCase()}`);
|
||||
}
|
||||
}
|
||||
|
@ -209,6 +246,7 @@ export class AssetService {
|
|||
name: fileName,
|
||||
path: filePath,
|
||||
size: null,
|
||||
mimeType: assetMimeType,
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -233,36 +271,6 @@ export class AssetService {
|
|||
}
|
||||
}
|
||||
|
||||
// If extension of a file was spoofed, we fetch only first 4 or needed amount of bytes of the file and read MIME type
|
||||
public static async getMimeType(uri: string): Promise<string[]> {
|
||||
const getFirst4bytes = (): Promise<Response> => this.pollForFetchAPI(() => fetch(uri, { headers: { range: `bytes=0-${mimeBytesNeeded}` } }), 1000, 200);
|
||||
let first4bytes: Response;
|
||||
try {
|
||||
first4bytes = await getFirst4bytes()
|
||||
} catch {
|
||||
return new Promise<string[]>((resolve) => {
|
||||
resolve(null);
|
||||
});
|
||||
}
|
||||
const arrayBuffer: ArrayBuffer = await first4bytes.arrayBuffer();
|
||||
const blob: Blob = new Blob([new Uint8Array(arrayBuffer).buffer]);
|
||||
const isMime = (bytes: Uint8Array, mime: IMime): boolean => {
|
||||
return mime.pattern.every((p, i) => !p || bytes[i] === p);
|
||||
};
|
||||
const fileReader: FileReader = new FileReader();
|
||||
|
||||
return new Promise<string[]>((resolve, reject) => {
|
||||
fileReader.onloadend = (e) => {
|
||||
if (!e || !fileReader.result) {
|
||||
return [];
|
||||
}
|
||||
const bytes: Uint8Array = new Uint8Array(fileReader.result as ArrayBuffer);
|
||||
const type: string[] = imageMimes.filter((mime) => isMime(bytes, mime))?.[0]?.types;
|
||||
resolve(type || []);
|
||||
};
|
||||
fileReader.readAsArrayBuffer(blob);
|
||||
});
|
||||
}
|
||||
|
||||
private assetProviderInstance: IAssetProvider;
|
||||
private storageProviderInstance: IStorageProvider;
|
||||
|
@ -369,7 +377,7 @@ export class AssetService {
|
|||
// The file may not exist - that's OK.
|
||||
}
|
||||
}
|
||||
return metadata;
|
||||
return JSON.parse(JSON.stringify(metadata));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -383,6 +391,11 @@ export class AssetService {
|
|||
try {
|
||||
const json = await this.storageProvider.readText(labelFileName, true);
|
||||
const labelData = JSON.parse(json) as ILabelData;
|
||||
|
||||
if (labelData) {
|
||||
labelData.labelingState = labelData.labelingState || AssetLabelingState.ManuallyLabeled;
|
||||
asset.labelingState = labelData.labelingState;
|
||||
}
|
||||
// if (!labelData.document || !labelData.labels && !labelData.tableLabels) {
|
||||
// const reason = interpolate(strings.errors.missingRequiredFieldInLabelFile.message, { labelFileName });
|
||||
// toast.error(reason, { autoClose: false });
|
||||
|
@ -427,7 +440,7 @@ export class AssetService {
|
|||
// }
|
||||
// toast.dismiss();
|
||||
return {
|
||||
asset: { ...asset },
|
||||
asset: { ...asset, labelingState: labelData.labelingState },
|
||||
regions: [],
|
||||
version: appInfo.version,
|
||||
labelData,
|
||||
|
|
|
@ -33,6 +33,7 @@ export class OCRService {
|
|||
public async getRecognizedText(
|
||||
filePath: string,
|
||||
fileName: string,
|
||||
mimeType: string,
|
||||
onStatusChanged?: (ocrStatus: OcrStatus) => void,
|
||||
rewrite?: boolean
|
||||
): Promise<any> {
|
||||
|
@ -47,11 +48,11 @@ export class OCRService {
|
|||
notifyStatusChanged(OcrStatus.loadingFromAzureBlob);
|
||||
ocrJson = await this.readOcrFile(ocrFileName);
|
||||
if (!this.isValidOcrFormat(ocrJson) || rewrite) {
|
||||
ocrJson = await this.fetchOcrUriResult(filePath, fileName, ocrFileName);
|
||||
ocrJson = await this.fetchOcrUriResult(filePath, fileName, ocrFileName, mimeType);
|
||||
}
|
||||
} catch (e) {
|
||||
notifyStatusChanged(OcrStatus.runningOCR);
|
||||
ocrJson = await this.fetchOcrUriResult(filePath, fileName, ocrFileName);
|
||||
ocrJson = await this.fetchOcrUriResult(filePath, fileName, ocrFileName, mimeType);
|
||||
} finally {
|
||||
notifyStatusChanged(OcrStatus.done);
|
||||
}
|
||||
|
@ -81,7 +82,7 @@ export class OCRService {
|
|||
}
|
||||
}
|
||||
|
||||
private fetchOcrUriResult = async (filePath: string, fileName: string, ocrFileName: string) => {
|
||||
private fetchOcrUriResult = async (filePath: string, fileName: string, ocrFileName: string, mimeType: string) => {
|
||||
try {
|
||||
let body;
|
||||
let headers;
|
||||
|
@ -93,15 +94,13 @@ export class OCRService {
|
|||
]
|
||||
);
|
||||
body = bodyAndType[0];
|
||||
const fileType = bodyAndType[1].mime;
|
||||
headers = { "Content-Type": fileType, "cache-control": "no-cache" };
|
||||
}
|
||||
else {
|
||||
headers = { "Content-Type": mimeType, "cache-control": "no-cache" };
|
||||
} else {
|
||||
body = { url: filePath };
|
||||
headers = { "Content-Type": "application/json" };
|
||||
}
|
||||
const response = await ServiceHelper.postWithAutoRetry(
|
||||
this.project.apiUriBase + `/formrecognizer/${constants.apiVersion}/layout/analyze`,
|
||||
this.project.apiUriBase + `/formrecognizer/${ (this.project.apiVersion || constants.apiVersion) }/layout/analyze`,
|
||||
body,
|
||||
{ headers },
|
||||
this.project.apiKey as string,
|
||||
|
|
|
@ -24,7 +24,7 @@ export class PredictService {
|
|||
}
|
||||
const endpointURL = url.resolve(
|
||||
this.project.apiUriBase,
|
||||
`${constants.apiModelsPath}/${modelID}/analyze?includeTextDetails=true`,
|
||||
`${interpolate(constants.apiModelsPath, {apiVersion : (constants.apiVersion || constants.appVersion) })}/${modelID}/analyze?includeTextDetails=true`,
|
||||
);
|
||||
|
||||
const headers = { "Content-Type": "application/json", "cache-control": "no-cache" };
|
||||
|
@ -60,11 +60,10 @@ export class PredictService {
|
|||
if (response.data.status.toLowerCase() === constants.statusCodeSucceeded) {
|
||||
resolve(response.data);
|
||||
// prediction response from API
|
||||
console.log("raw data", JSON.parse(response.request.response));
|
||||
} else if (response.data.status.toLowerCase() === constants.statusCodeFailed) {
|
||||
reject(_.get(
|
||||
response,
|
||||
"data.analyzeResult.errors[0].errorMessage",
|
||||
"data.analyzeResult.errors[0]",
|
||||
"Generic error during prediction"));
|
||||
} else if (Number(new Date()) < endTime) {
|
||||
// If the request isn't succeeded and the timeout hasn't elapsed, go again
|
||||
|
|
|
@ -152,14 +152,7 @@ export default class ProjectService implements IProjectService {
|
|||
|
||||
public async isProjectNameAlreadyUsed(project: IProject): Promise<boolean> {
|
||||
const storageProvider = StorageProviderFactory.createFromConnection(project.sourceConnection);
|
||||
const fileList = await storageProvider.listFiles("", constants.projectFileExtension/*ext*/);
|
||||
for (const fileName of fileList) {
|
||||
if (fileName === `${project.name}${constants.projectFileExtension}`) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
return await storageProvider.isFileExists(`${project.name}${constants.projectFileExtension}`);
|
||||
}
|
||||
|
||||
public async isValidProjectConnection(project: IProject): Promise<boolean> {
|
||||
|
|
|
@ -8438,7 +8438,7 @@ mime@1.6.0:
|
|||
resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1"
|
||||
integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==
|
||||
|
||||
mime@^2.4.4, mime@^2.4.5:
|
||||
mime@^2.4.4, mime@^2.4.5, mime@^2.4.6:
|
||||
version "2.4.6"
|
||||
resolved "https://registry.yarnpkg.com/mime/-/mime-2.4.6.tgz#e5b407c90db442f2beb5b162373d07b69affa4d1"
|
||||
integrity sha512-RZKhC3EmpBchfTGBVb8fb+RL2cWyw/32lshnsETttkBAyAUXSGHxbEJWWRXc751DrIxG1q04b8QwMbAwkRPpUA==
|
||||
|
@ -8708,6 +8708,11 @@ node-forge@0.9.0:
|
|||
resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-0.9.0.tgz#d624050edbb44874adca12bb9a52ec63cb782579"
|
||||
integrity sha512-7ASaDa3pD+lJ3WvXFsxekJQelBKRpne+GOVbLbtHYdd7pFspyeuJHnWfLplGf3SwKGbfs/aYl5V/JCIaHVUKKQ==
|
||||
|
||||
node-forge@^0.10.0:
|
||||
version "0.10.0"
|
||||
resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-0.10.0.tgz#32dea2afb3e9926f02ee5ce8794902691a676bf3"
|
||||
integrity sha512-PPmu8eEeG9saEUvI97fm4OYxXVB6bFvyNTyiUOBichBpFG8A1Ljw3bY62+5oOjDEMHRnd0Y7HQ+x7uzxOzC6JA==
|
||||
|
||||
node-gyp@^3.8.0:
|
||||
version "3.8.0"
|
||||
resolved "https://registry.yarnpkg.com/node-gyp/-/node-gyp-3.8.0.tgz#540304261c330e80d0d5edce253a68cb3964218c"
|
||||
|
|
Загрузка…
Ссылка в новой задаче