From dd78ed06761a341908bdb1b09e73fd1f2868431c Mon Sep 17 00:00:00 2001 From: stew-ro <60453211+stew-ro@users.noreply.github.com> Date: Mon, 24 Aug 2020 15:17:23 -0700 Subject: [PATCH] feat: support region labeling (#481) * feat: support region labeling * fix: check temporarily for drawn region features before remove * fix: delete drawn region from current assest on delete * fix: use clockwise coordinates for boundingbox * feat: support modify cancell * fix: remove missing props from merge conflict * fix: resolve tsling check * fix: resolve tslint check * refactor: add strings * fix: support key labeling * feat: add drawn region to keyboard shortcuts * feat: support labelType in label JSON files * fix: support layer sync * refactor: remove console logs for debugging * fix: support clearing drawnRegions on next page * docs: add test cases * fix: disable feature until back-end support --- docs/manual_testing/manual-test-runbook.md | 159 +++- src/assets/sass/fabric-icons-inline.scss | 188 +++-- src/common/localization/en-us.ts | 24 +- src/common/localization/es-cl.ts | 20 +- src/common/strings.ts | 436 +++++----- src/config/fabric-icons.json | 222 ++--- src/models/applicationState.ts | 199 ++--- .../components/common/imageMap/imageMap.tsx | 794 +++++++++++++++--- .../components/common/tagInput/tagInput.scss | 1 + .../components/common/tagInput/tagInput.tsx | 38 +- .../common/tagInput/tagInputItemLabel.tsx | 25 +- .../components/pages/editorPage/canvas.tsx | 440 ++++++++-- .../pages/editorPage/canvasCommandBar.tsx | 25 +- .../pages/editorPage/editorPage.tsx | 31 +- .../components/pages/predict/predictPage.tsx | 3 +- .../components/shell/keyboardShortcuts.scss | 3 + .../components/shell/keyboardShortcuts.tsx | 45 +- src/registerIcons.ts | 2 + 18 files changed, 1918 insertions(+), 737 deletions(-) diff --git a/docs/manual_testing/manual-test-runbook.md b/docs/manual_testing/manual-test-runbook.md index d7821c801..ab14dcbc2 100644 --- a/docs/manual_testing/manual-test-runbook.md +++ b/docs/manual_testing/manual-test-runbook.md @@ -1,5 +1,149 @@ # Test Runbook +## **Feat: support region labeling** + +> ### Feature description ### +- Add a draw region button to the canvas commandbar in the editor page + +> ### Use Case ### + +**As** a user +**I want** draw regions to label in the editor page +**So** I can label regions that are not recognized by OCR + +> ### Acceptance criteria ### + +#### Scenario One #### +**Given** I'm on the editor page and click the draw region botton on the canvas commandbar +**When** I hover the pointer over the current document image +**Then** I should see the cursor change to a crosshair + +#### Scenario Two #### + +**Given** I'm on the editor page and click the draw region botton on the canvas commandbar +**When** I click the layer botton on the canvas commandbar +**Then** I should see the drawn regions layer disabled in the canvas commandbar + +#### Scenario Three #### + +**Given** I'm on the editor page +**When** I click the layer botton on the canvas commandbar +**Then** I should see the drawn regions layer enabled in the canvas commandbar + +#### Scenario Four #### + +**Given** I'm on the editor page and click the draw region botton on the canvas commandbar +**When** I click and drag on the document image +**Then** I should see a region being drawn + +#### Scenario Five #### + +**Given** I'm on the editor page and click the draw region botton on the canvas commandbar +**When** I click and drag outside of the document image +**Then** I should see the document panned + +#### Scenario Six #### + +**Given** I'm on the editor page and click the draw region botton on the canvas commandbar +**When** I click and drag on the document image to outside of the document image +**Then** I should see a region being drawn and then cancelled + +#### Scenario Seven #### + +**Given** I'm on the editor page and click the draw region botton on the canvas commandbar +**When** I finish drawing a region +**Then** I should see a drawn region that is selected + +#### Scenario Eight #### + +**Given** I'm on the editor page and click the draw region botton on the canvas commandbar +**When** I click on the draw region button again +**Then** I should see the cursor return to a pointer while hovering the document image + +#### Scenario Nine #### + +**Given** I've drawn regions +**When** I hover a region's vertex +**Then** I should see a move icon apear on the vertex and the cursor change to grab + +#### Scenario Ten #### + +**Given** I've drawn regions +**When** I hover a region's vertex, click, and hold +**Then** I should see the cursor change to grabbing and the vertex should move with cursor + +#### Scenario Eleven #### + +**Given** I've drawn regions +**When** I hover a region's vertex, click, hold, and drag outside of the document image +**Then** I should see the vertex return to it's original position + +#### Scenario Twelve #### + +**Given** I've drawn regions +**When** I hover a region's vertex, click, hold, drag, and click the Escape or Backspace key +**Then** I should see the vertex return to it's original position + +#### Scenario Thirteen #### + +**Given** I'm on the editor page and click the draw region botton on the canvas commandbar +**When** I click on the document image, hold, drag, and click the Escape or Backspace key +**Then** I should see the drawing cancelled + +#### Scenario Fourteen #### + +**Given** I've drawn regions +**When** I exit draw region mode and click drawn regions +**Then** I should see the drawn regions toggle between selected and unselected + +#### Scenario Fifteen #### + +**Given** I've drawn regions +**When** I exit draw region mode +**Then** I should see the drawn regions still be reshapable + +#### Scenario Sixteen #### + +**Given** I've selected drawn regions +**When** I click an empty tag or a tag with only drawn region values already applied or press it's hot key +**Then** I should see the drawn region applied as a label for the tag + +#### Scenario Seventeen #### + +**Given** I've selected drawn regions +**When** I click a tag with text or checkbox values or press it's hot key +**Then** I should see a message letting me know I can't apply the drawn region to the tag + +#### Scenario Eighteen #### + +**Given** I've labeled drawn regions +**When** I view the label json file +**Then** I should see the drawn region labeled with a clockwise bounding box + +#### Scenario Nineteen #### + +**Given** I've labeled drawn regions +**When** I hover the cursor over a label vertex +**Then** I should see the vertex should be movable + +#### Scenario Twenty #### + +**Given** I've labeled drawn regions +**When** I hover the cursor over a label vertex +**Then** I should see the vertex should be movable + +#### Scenario Twentyone #### + +**Given** I've labeled drawn regions +**When** I hover the cursor over a label vertex and move it +**Then** I should see the label reshaped in the document image and the json file + +#### Scenario Twentytwo #### + +**Given** I've selected drawn regions +**When** I press alt-backspace +**Then** I should see the selected regions should be deleted + ## **Feat: support adding models to project's recent models from the model compose page** > ### Feature description ### @@ -44,20 +188,17 @@ > ### Use Case ### -**`I want`** I want to know the models been used to compose a model -**`So`** I can double click that model to invoke the pop up and checkout models been used - -**`Given`** I've opened a project containing documents and I'm on the Model Compose page -**`When`** I double click a row with composed model -**`Then`** I should see a pop up, which it shows all models we used to compose in the list. Beside, there is also a filter field in the top to filter a specific model out of the list +**As** a user +**I want** I want to know the models been used to compose a model +**So** I can double click that model to invoke the pop up and checkout models been used > ### Acceptance criteria ### #### Scenario One #### -**`Given`** I've opened a project containing documents and I'm on the Model Compose page -**`When`** I double click a row with composed model -**`Then`** I should see a pop up, which it shows all models we used to compose in the list. Beside, there is also a filter field in the top to filter a specific model out of the list +**Given** I've opened a project containing documents and I'm on the Model Compose page +**When`** I double click a row with composed model +**Then** I should see a pop up, which it shows all models we used to compose in the list. Beside, there is also a filter field in the top to filter a specific model out of the list ## Feat: support group selection tool diff --git a/src/assets/sass/fabric-icons-inline.scss b/src/assets/sass/fabric-icons-inline.scss index c9f17cc0d..387e8090a 100644 --- a/src/assets/sass/fabric-icons-inline.scss +++ b/src/assets/sass/fabric-icons-inline.scss @@ -3,7 +3,7 @@ */ @font-face { font-family: 'FabricMDL2Icons'; - src: url('data:application/octet-stream;base64,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') format('truetype'); + src: url('data:application/octet-stream;base64,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') format('truetype'); } .ms-Icon { @@ -17,126 +17,138 @@ } // Mixins -@mixin ms-Icon--SortUp { content: "\EE68"; } -@mixin ms-Icon--SortDown { content: "\EE69"; } @mixin ms-Icon--Table { content: "\ED86"; } @mixin ms-Icon--TextField { content: "\EDC3"; } -@mixin ms-Icon--TextDocument { content: "\F029"; } -@mixin ms-Icon--StatusCircleCheckmark { content: "\F13E"; } -@mixin ms-Icon--DocumentManagement { content: "\EFFC"; } -@mixin ms-Icon--CircleRing { content: "\EA3A"; } -@mixin ms-Icon--Label { content: "\E932"; } -@mixin ms-Icon--Info { content: "\E946"; } -@mixin ms-Icon--Documentation { content: "\EC17"; } @mixin ms-Icon--OpenFolderHorizontal { content: "\ED25"; } - +@mixin ms-Icon--Documentation { content: "\EC17"; } @mixin ms-Icon--AddTo { content: "\ECC8"; } -@mixin ms-Icon--Hide3 { content: "\F6AC"; } -@mixin ms-Icon--WarningSolid { content: "\F736"; } -@mixin ms-Icon--BranchMerge { content: "\F295"; } +@mixin ms-Icon--SortUp { content: "\EE68"; } +@mixin ms-Icon--SortDown { content: "\EE69"; } +@mixin ms-Icon--Info { content: "\E946"; } +@mixin ms-Icon--ChromeMinimize { content: "\E921"; } +@mixin ms-Icon--ChromeRestore { content: "\E923"; } +@mixin ms-Icon--Label { content: "\E932"; } +@mixin ms-Icon--Copy { content: "\E8C8"; } +@mixin ms-Icon--Rename { content: "\E8AC"; } +@mixin ms-Icon--Download { content: "\E896"; } +@mixin ms-Icon--Help { content: "\E897"; } +@mixin ms-Icon--ZoomIn { content: "\E8A3"; } +@mixin ms-Icon--Tag { content: "\E8EC"; } +@mixin ms-Icon--CircleRing { content: "\EA3A"; } +@mixin ms-Icon--SquareShape { content: "\F1A6"; } +@mixin ms-Icon--RectangleShape { content: "\F1A9"; } +@mixin ms-Icon--DocumentManagement { content: "\EFFC"; } +@mixin ms-Icon--Relationship { content: "\F003"; } +@mixin ms-Icon--TextDocument { content: "\F029"; } +@mixin ms-Icon--StatusCircleCheckmark { content: "\F13E"; } @mixin ms-Icon--PlugConnected { content: "\F302"; } @mixin ms-Icon--Plug { content: "\F300"; } @mixin ms-Icon--AlertSolid { content: "\F331"; } +@mixin ms-Icon--BranchMerge { content: "\F295"; } +@mixin ms-Icon--View { content: "\E890"; } +@mixin ms-Icon--ReceiptProcessing { content: "\E496"; } +@mixin ms-Icon--AddField { content: "\E4C7"; } +@mixin ms-Icon--TagGroup { content: "\E3F6"; } +@mixin ms-Icon--Insights { content: "\E3AF"; } +@mixin ms-Icon--MachineLearning { content: "\E3B8"; } +@mixin ms-Icon--Merge { content: "\E7D5"; } +@mixin ms-Icon--MapLayers { content: "\E81E"; } +@mixin ms-Icon--Home { content: "\E80F"; } +@mixin ms-Icon--ZoomOut { content: "\E71F"; } +@mixin ms-Icon--Search { content: "\E721"; } @mixin ms-Icon--Refresh { content: "\E72C"; } -@mixin ms-Icon--CheckboxComposite { content: "\E73A"; } +@mixin ms-Icon--Share { content: "\E72D"; } +@mixin ms-Icon--Link { content: "\E71B"; } +@mixin ms-Icon--ChevronDown { content: "\E70D"; } +@mixin ms-Icon--ChevronUp { content: "\E70E"; } +@mixin ms-Icon--Edit { content: "\E70F"; } +@mixin ms-Icon--Add { content: "\E710"; } +@mixin ms-Icon--Cancel { content: "\E711"; } @mixin ms-Icon--More { content: "\E712"; } @mixin ms-Icon--Settings { content: "\E713"; } -@mixin ms-Icon--Link { content: "\E71B"; } @mixin ms-Icon--Filter { content: "\E71C"; } -@mixin ms-Icon--ZoomOut { content: "\E71F"; } -@mixin ms-Icon--Search { content: "\E721"; } -@mixin ms-Icon--CheckMark { content: "\E73E"; } -@mixin ms-Icon--ChevronRight { content: "\E76C"; } @mixin ms-Icon--ChevronLeft { content: "\E76B"; } -@mixin ms-Icon--Cancel { content: "\E711"; } -@mixin ms-Icon--Up { content: "\E74A"; } +@mixin ms-Icon--ChevronRight { content: "\E76C"; } +@mixin ms-Icon--System { content: "\E770"; } +@mixin ms-Icon--CheckboxComposite { content: "\E73A"; } +@mixin ms-Icon--CheckMark { content: "\E73E"; } @mixin ms-Icon--Down { content: "\E74B"; } @mixin ms-Icon--Delete { content: "\E74D"; } @mixin ms-Icon--Cloud { content: "\E753"; } -@mixin ms-Icon--Add { content: "\E710"; } -@mixin ms-Icon--ChevronUp { content: "\E70E"; } -@mixin ms-Icon--ReceiptProcessing { content: "\E496"; } -@mixin ms-Icon--ChevronDown { content: "\E70D"; } -@mixin ms-Icon--Share { content: "\E72D"; } -@mixin ms-Icon--Edit { content: "\E70F"; } -@mixin ms-Icon--Copy { content: "\E8C8"; } -@mixin ms-Icon--ZoomIn { content: "\E8A3"; } -@mixin ms-Icon--Rename { content: "\E8AC"; } -@mixin ms-Icon--Tag { content: "\E8EC"; } -@mixin ms-Icon--View { content: "\E890"; } -@mixin ms-Icon--Download { content: "\E896"; } -@mixin ms-Icon--Help { content: "\E897"; } -@mixin ms-Icon--Home { content: "\E80F"; } -@mixin ms-Icon--MapLayers { content: "\E81E"; } +@mixin ms-Icon--Up { content: "\E74A"; } @mixin ms-Icon--KeyPhraseExtraction { content: "\E395"; } -@mixin ms-Icon--Insights { content: "\E3AF"; } -@mixin ms-Icon--MachineLearning { content: "\E3B8"; } -@mixin ms-Icon--TagGroup { content: "\E3F6"; } +@mixin ms-Icon--Hide3 { content: "\F6AC"; } +@mixin ms-Icon--WarningSolid { content: "\F736"; } @mixin ms-Icon--BookAnswers { content: "\F8A4"; } -@mixin ms-Icon--ChromeRestore { content: "\E923"; } -@mixin ms-Icon--ChromeMinimize { content: "\E921"; } -@mixin ms-Icon--System { content: "\E770"; } -@mixin ms-Icon--SquareShape { content: "\F1A6"; } -@mixin ms-Icon--Merge { content: "\E7D5"; } + // Classes -.ms-Icon--SortUp:before { @include ms-Icon--SortUp } -.ms-Icon--SortDown:before { @include ms-Icon--SortDown } .ms-Icon--Table:before { @include ms-Icon--Table } .ms-Icon--TextField:before { @include ms-Icon--TextField } -.ms-Icon--TextDocument:before { @include ms-Icon--TextDocument } -.ms-Icon--StatusCircleCheckmark:before { @include ms-Icon--StatusCircleCheckmark } -.ms-Icon--DocumentManagement:before { @include ms-Icon--DocumentManagement } -.ms-Icon--CircleRing:before { @include ms-Icon--CircleRing } -.ms-Icon--Label:before { @include ms-Icon--Label } -.ms-Icon--Info:before { @include ms-Icon--Info } -.ms-Icon--Documentation:before { @include ms-Icon--Documentation } .ms-Icon--OpenFolderHorizontal:before { @include ms-Icon--OpenFolderHorizontal } +.ms-Icon--Documentation:before { @include ms-Icon--Documentation } .ms-Icon--AddTo:before { @include ms-Icon--AddTo } -.ms-Icon--Hide3:before { @include ms-Icon--Hide3 } -.ms-Icon--WarningSolid:before { @include ms-Icon--WarningSolid } -.ms-Icon--BranchMerge:before { @include ms-Icon--BranchMerge } +.ms-Icon--SortUp:before { @include ms-Icon--SortUp } +.ms-Icon--SortDown:before { @include ms-Icon--SortDown } +.ms-Icon--Info:before { @include ms-Icon--Info } +.ms-Icon--ChromeMinimize:before { @include ms-Icon--ChromeMinimize } +.ms-Icon--ChromeRestore:before { @include ms-Icon--ChromeRestore } +.ms-Icon--Label:before { @include ms-Icon--Label } +.ms-Icon--Copy:before { @include ms-Icon--Copy } +.ms-Icon--Rename:before { @include ms-Icon--Rename } +.ms-Icon--Download:before { @include ms-Icon--Download } +.ms-Icon--Help:before { @include ms-Icon--Help } +.ms-Icon--ZoomIn:before { @include ms-Icon--ZoomIn } +.ms-Icon--Tag:before { @include ms-Icon--Tag } +.ms-Icon--CircleRing:before { @include ms-Icon--CircleRing } +.ms-Icon--SquareShape:before { @include ms-Icon--SquareShape } +.ms-Icon--RectangleShape:before { @include ms-Icon--RectangleShape } +.ms-Icon--DocumentManagement:before { @include ms-Icon--DocumentManagement } +.ms-Icon--Relationship:before { @include ms-Icon--Relationship } +.ms-Icon--TextDocument:before { @include ms-Icon--TextDocument } +.ms-Icon--StatusCircleCheckmark:before { @include ms-Icon--StatusCircleCheckmark } .ms-Icon--PlugConnected:before { @include ms-Icon--PlugConnected } .ms-Icon--Plug:before { @include ms-Icon--Plug } .ms-Icon--AlertSolid:before { @include ms-Icon--AlertSolid } +.ms-Icon--BranchMerge:before { @include ms-Icon--BranchMerge } +.ms-Icon--View:before { @include ms-Icon--View } +.ms-Icon--ReceiptProcessing:before { @include ms-Icon--ReceiptProcessing } +.ms-Icon--AddField:before { @include ms-Icon--AddField } +.ms-Icon--TagGroup:before { @include ms-Icon--TagGroup } +.ms-Icon--Insights:before { @include ms-Icon--Insights } +.ms-Icon--MachineLearning:before { @include ms-Icon--MachineLearning } +.ms-Icon--Merge:before { @include ms-Icon--Merge } +.ms-Icon--MapLayers:before { @include ms-Icon--MapLayers } +.ms-Icon--Home:before { @include ms-Icon--Home } +.ms-Icon--ZoomOut:before { @include ms-Icon--ZoomOut } +.ms-Icon--Search:before { @include ms-Icon--Search } .ms-Icon--Refresh:before { @include ms-Icon--Refresh } -.ms-Icon--CheckboxComposite:before { @include ms-Icon--CheckboxComposite } +.ms-Icon--Share:before { @include ms-Icon--Share } +.ms-Icon--Link:before { @include ms-Icon--Link } +.ms-Icon--ChevronDown:before { @include ms-Icon--ChevronDown } +.ms-Icon--ChevronUp:before { @include ms-Icon--ChevronUp } +.ms-Icon--Edit:before { @include ms-Icon--Edit } +.ms-Icon--Add:before { @include ms-Icon--Add } +.ms-Icon--Cancel:before { @include ms-Icon--Cancel } .ms-Icon--More:before { @include ms-Icon--More } .ms-Icon--Settings:before { @include ms-Icon--Settings } -.ms-Icon--Link:before { @include ms-Icon--Link } .ms-Icon--Filter:before { @include ms-Icon--Filter } -.ms-Icon--ZoomOut:before { @include ms-Icon--ZoomOut } -.ms-Icon--Search:before { @include ms-Icon--Search } -.ms-Icon--CheckMark:before { @include ms-Icon--CheckMark } -.ms-Icon--ChevronRight:before { @include ms-Icon--ChevronRight } .ms-Icon--ChevronLeft:before { @include ms-Icon--ChevronLeft } -.ms-Icon--Cancel:before { @include ms-Icon--Cancel } -.ms-Icon--Up:before { @include ms-Icon--Up } +.ms-Icon--ChevronRight:before { @include ms-Icon--ChevronRight } +.ms-Icon--System:before { @include ms-Icon--System } +.ms-Icon--CheckboxComposite:before { @include ms-Icon--CheckboxComposite } +.ms-Icon--CheckMark:before { @include ms-Icon--CheckMark } .ms-Icon--Down:before { @include ms-Icon--Down } .ms-Icon--Delete:before { @include ms-Icon--Delete } .ms-Icon--Cloud:before { @include ms-Icon--Cloud } -.ms-Icon--Add:before { @include ms-Icon--Add } -.ms-Icon--ChevronUp:before { @include ms-Icon--ChevronUp } -.ms-Icon--ReceiptProcessing:before { @include ms-Icon--ReceiptProcessing } -.ms-Icon--ChevronDown:before { @include ms-Icon--ChevronDown } -.ms-Icon--Share:before { @include ms-Icon--Share } -.ms-Icon--Edit:before { @include ms-Icon--Edit } -.ms-Icon--Copy:before { @include ms-Icon--Copy } -.ms-Icon--ZoomIn:before { @include ms-Icon--ZoomIn } -.ms-Icon--Rename:before { @include ms-Icon--Rename } -.ms-Icon--Tag:before { @include ms-Icon--Tag } -.ms-Icon--View:before { @include ms-Icon--View } -.ms-Icon--Download:before { @include ms-Icon--Download } -.ms-Icon--Help:before { @include ms-Icon--Help } -.ms-Icon--Home:before { @include ms-Icon--Home } -.ms-Icon--MapLayers:before { @include ms-Icon--MapLayers } +.ms-Icon--Up:before { @include ms-Icon--Up } .ms-Icon--KeyPhraseExtraction:before { @include ms-Icon--KeyPhraseExtraction } -.ms-Icon--Insights:before { @include ms-Icon--Insights } -.ms-Icon--MachineLearning:before { @include ms-Icon--MachineLearning } -.ms-Icon--TagGroup:before { @include ms-Icon--TagGroup } +.ms-Icon--Hide3:before { @include ms-Icon--Hide3 } +.ms-Icon--WarningSolid:before { @include ms-Icon--WarningSolid } .ms-Icon--BookAnswers:before { @include ms-Icon--BookAnswers } -.ms-Icon--ChromeRestore:before { @include ms-Icon--ChromeRestore } -.ms-Icon--ChromeMinimize:before { @include ms-Icon--ChromeMinimize } -.ms-Icon--System:before { @include ms-Icon--System } -.ms-Icon--SquareShape:before { @include ms-Icon--SquareShape } -.ms-Icon--Merge:before { @include ms-Icon--Merge } + +.ms-Icon--KeyPhraseExtraction:before { @include ms-Icon--KeyPhraseExtraction } +.ms-Icon--Hide3:before { @include ms-Icon--Hide3 } +.ms-Icon--WarningSolid:before { @include ms-Icon--WarningSolid } +.ms-Icon--BookAnswers:before { @include ms-Icon--BookAnswers } + diff --git a/src/common/localization/en-us.ts b/src/common/localization/en-us.ts index 6d8b090d7..82e59deeb 100644 --- a/src/common/localization/en-us.ts +++ b/src/common/localization/en-us.ts @@ -248,6 +248,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 ${otherCatagory} values cannot both be assigned to the same document's tag", }, toolbar: { add: "Add new tag", @@ -409,10 +410,12 @@ export const english: IAppStrings = { subMenuItems: { text: "Text", tables: "Tables", - selectionMarks: "Selection Marks (Preview)", + selectionMarks: "Selection marks (preview)", + drawnRegions: "Drawn regions (preview)", labels: "Labels" }, - } + }, + drawRegion: "Draw region", }, farItems: { zoom: { @@ -483,9 +486,20 @@ export const english: IAppStrings = { backSpace: "Remove selection and delete labels of selected words", }, }, + drawnRegions: { + keys: { + escape: "Escape", + alt: "Alt", + backSpace: "Backspace", + }, + description: { + deleteSelectedDrawnRegions: "Delete selected drawn regions", + cancelDrawOrReshape: "Cancel drawing or reshaping of regions", + } + }, tips: { quickLabeling: { - name: "Quick labeling", + name: "Lable with hot keys", description: "Hotkeys 1 through 0 and all letters are assigned to first 36 tags. After selecting one or multiple words, press tag's assigned hotkey.", }, renameTag: { @@ -493,8 +507,8 @@ export const english: IAppStrings = { description: "Hold Alt key and click on tag name.", }, multipleWordSelection: { - name: "Select multiple words", - description: "Click and hold on word. Then, hover over additional words.", + name: "Select multiple words by dragging pointer across words", + description: "Click and hold on a word. Then, hover over additional words with pointer.", }, deleteAllLabelsForTag: { name: "Delete all labels for a tag", diff --git a/src/common/localization/es-cl.ts b/src/common/localization/es-cl.ts index bbed75be2..4d9dd2d19 100644 --- a/src/common/localization/es-cl.ts +++ b/src/common/localization/es-cl.ts @@ -248,6 +248,7 @@ export const spanish: IAppStrings = { unknownTagName: "Desconocido", notCompatibleTagType: "El tipo de etiqueta no es compatible con esta función. Si desea cambiar el tipo de esta etiqueta, elimine o reasigne todas las etiquetas que utilizan esta etiqueta en su proyecto.", checkboxPerTagLimit: "No se puede asignar más de una casilla de verificación por etiqueta", + notCompatibleWithDrawnRegionTag: "Los valores de drawnRegion y $ {otherCatagory} no pueden asignarse a la misma etiqueta del documento", }, toolbar: { add: "Agregar nueva etiqueta", @@ -412,9 +413,11 @@ export const spanish: IAppStrings = { text: "Texto", tables: "Tablas", selectionMarks: "Marcas de selección (vista previa)", + drawnRegions: "Regiones dibujadas (vista previa)", labels: "Etiquetas" }, - } + }, + drawRegion: "Dibujar regiones", }, farItems: { zoom: { @@ -485,6 +488,17 @@ export const spanish: IAppStrings = { backSpace: "Eliminar selección del mapa del documento o clave de selección de una etiqueta", }, }, + drawnRegions: { + keys: { + escape: "Escape", + alt: "Alt", + backSpace: "Backspace", + }, + description: { + deleteSelectedDrawnRegions: "Eliminar regiones dibujadas seleccionadas", + cancelDrawOrReshape: "Cancelar la modificación o remodelación de regiones", + } + }, tips: { quickLabeling: { name: "Etiquetado rápido", @@ -495,8 +509,8 @@ export const spanish: IAppStrings = { description: "Mantenga presionada la tecla Alt y haga clic en el nombre de la etiqueta, el usuario puede cambiar el nombre de la etiqueta.", }, multipleWordSelection: { - name: "Selección de palabras múltiples", - description: "Haga clic y mantenga presionada la palabra, luego desplace el cursor sobre otras palabras para seleccionar varias palabras a la vez.", + name: "Seleccione varias palabras pasando el cursor con el puntero", + description: "Haga clic y mantenga presionada una palabra. Luego, coloca el cursor sobre palabras adicionales con el puntero.", }, deleteAllLabelsForTag: { name: "Eliminar información asociada a una etiqueta", diff --git a/src/common/strings.ts b/src/common/strings.ts index a3082df98..825fa3dde 100644 --- a/src/common/strings.ts +++ b/src/common/strings.ts @@ -10,48 +10,48 @@ import { spanish } from "./localization/es-cl"; * Language must add all strings to be compliant for localization */ export interface IAppStrings { - appName: string; + appName: string, common: { - displayName: string; - description: string; - submit: string; - cancel: string; - save: string; - delete: string; - provider: string; - homePage: string; - reload: string; - skipToMainContent: string; - skipToSidebar: string; + displayName: string, + description: string, + submit: string, + cancel: string, + save: string, + delete: string, + provider: string, + homePage: string, + reload: string, + skipToMainContent: string, + skipToSidebar: string, }; projectService: { - existingLabelFiles: string; + existingLabelFiles: string, }; titleBar: { - help: string; - minimize: string; - maximize: string; - restore: string; - close: string; + help: string, + minimize: string, + maximize: string, + restore: string, + close: string, }; homePage: { - title: string; - newProject: string; + title: string, + newProject: string, openLocalProject: { - title: string; + title: string, }, openCloudProject: { - title: string; - selectConnection: string; - pasteSharedUri: string; + title: string, + selectConnection: string, + pasteSharedUri: string, }, deleteProject: { - title: string; - confirmation: string; + title: string, + confirmation: string, }, importProject: { - title: string; - confirmation: string; + title: string, + confirmation: string, }, recentProjects: string, messages: { @@ -59,106 +59,106 @@ export interface IAppStrings { }, }; appSettings: { - title: string; - storageTitle: string; - uiHelp: string; - save: string; + title: string, + storageTitle: string, + uiHelp: string, + save: string, securityToken: { name: { - title: string; + title: string, }, key: { - title: string; + title: string, }, duplicateNameErrorMessage: string, }, securityTokens: { - title: string; - description: string; + title: string, + description: string, }, version: { - description: string; + description: string, }, commit: string, devTools: { - description: string; - button: string; + description: string, + button: string, }, reload: { - description: string; - button: string; + description: string, + button: string, }, messages: { - saveSuccess: string; + saveSuccess: string, }, }; projectSettings: { - title: string; + title: string, securityToken: { - title: string; - description: string; + title: string, + description: string, }, - save: string; + save: string, sourceConnection: { - title: string; - description: string; + title: string, + description: string, }, targetConnection: { - title: string; - description: string; + title: string, + description: string, }, videoSettings: { - title: string; - description: string; - frameExtractionRate: string; + title: string, + description: string, + frameExtractionRate: string, }, addConnection: string, messages: { - saveSuccess: string; - projectExisted: string; + saveSuccess: string, + projectExisted: string, }, }; train: { - modelNameTitle: string; - labelFolderTitle: string; - defaultLabelFolderURL: string; - title: string; - training: string; - pleaseWait: string; - notTrainedYet: string; - backEndNotAvailable: string; - addName: string; + modelNameTitle: string, + labelFolderTitle: string, + defaultLabelFolderURL: string, + title: string, + training: string, + pleaseWait: string, + notTrainedYet: string, + backEndNotAvailable: string, + addName: string, }; modelCompose: { - title: string; + title: string, columnAria: { - icon: string; + icon: string, } - loading: string; - composing: string; + loading: string, + composing: string, column: { icon: { - name: string; + name: string, } id: { - headerName: string; - fieldName: string; + headerName: string, + fieldName: string, } name: { - headerName: string; - fieldName: string; + headerName: string, + fieldName: string, } status: { - headerName: string; - fieldName: string; + headerName: string, + fieldName: string, } created: { - headerName: string; - fieldName: string; + headerName: string, + fieldName: string, } lastupdated: { - headerName: string; - fieldName: string; + headerName: string, + fieldName: string, } } modelView: { @@ -171,11 +171,11 @@ export interface IAppStrings { recentModelsAlreadyContainsModel: string, } commandBar: { - ariaLabel: string; - composeAria: string; - refreshAria: string; - filter: string; - filterAria: string; + ariaLabel: string, + composeAria: string, + refreshAria: string, + filter: string, + filterAria: string, }, modelsList: { headerAria: string, @@ -188,16 +188,16 @@ export interface IAppStrings { } } predict: { - title: string; - uploadFile: string; - inProgress: string; - noRecentModels: string; - selectModelHeader: string; - modelIDPrefix: string; - modelNamePrefix: string; - downloadScript: string; - defaultLocalFileInput: string; - defaultURLInput: string; + title: string, + uploadFile: string, + inProgress: string, + noRecentModels: string, + selectModelHeader: string, + modelIDPrefix: string, + modelNamePrefix: string, + downloadScript: string, + defaultLocalFileInput: string, + defaultURLInput: string, }; recentModelsView: { header: string; @@ -205,83 +205,84 @@ export interface IAppStrings { addToRecentModels: string; } projectMetrics: { - title: string; - assetsSectionTitle: string - totalAssetCount: string; - visitedAssets: string; - taggedAssets: string; - nonVisitedAssets: string; - nonTaggedAssets: string; - tagsSectionTitle: string; - totalRegionCount: string; - totalTagCount: string; - avgTagCountPerAsset: string; + title: string, + assetsSectionTitle: string, + totalAssetCount: string, + visitedAssets: string, + taggedAssets: string, + nonVisitedAssets: string, + nonTaggedAssets: string, + tagsSectionTitle: string, + totalRegionCount: string, + totalTagCount: string, + avgTagCountPerAsset: string, }; tags: { - title: string; - placeholder: string; - editor: string; + title: string, + placeholder: string, + editor: string, modal: { - name: string; - color: string; + name: string, + color: string, } toolbar: { - add: string; - contextualMenu: string; - delete: string; - edit: string; - format: string; - lock: string; - moveDown: string; - moveUp: string; - rename: string; - search: string; - type: string; - vertiline: string; + add: string, + contextualMenu: string, + delete: string, + edit: string, + format: string, + lock: string, + moveDown: string, + moveUp: string, + rename: string, + search: string, + type: string, + vertiline: string, } colors: { - white: string; - gray: string; - red: string; - maroon: string; - yellow: string; - olive: string; - lime: string; - green: string; - aqua: string; - teal: string; - blue: string; - navy: string; - fuschia: string; - purple: string; + white: string, + gray: string, + red: string, + maroon: string, + yellow: string, + olive: string, + lime: string, + green: string, + aqua: string, + teal: string, + blue: string, + navy: string, + fuschia: string, + purple: string, } warnings: { - existingName: string; - emptyName: string; - unknownTagName: string; - notCompatibleTagType: string; - checkboxPerTagLimit: string; + existingName: string, + emptyName: string, + unknownTagName: string, + notCompatibleTagType: string, + checkboxPerTagLimit: string, + notCompatibleWithDrawnRegionTag: string, } }; connections: { - title: string; - details: string; - settings: string; - instructions: string; - new: string; - save: string; - genericInvalid: string; + title: string, + details: string, + settings: string, + instructions: string, + new: string, + save: string, + genericInvalid: string, messages: { - saveSuccess: string; - deleteSuccess: string; - doNotAllowDuplicateNames:string; + saveSuccess: string, + deleteSuccess: string, + doNotAllowDuplicateNames:string, }, - imageCorsWarning: string; - blobCorsWarning: string; - azDocLinkText: string; + imageCorsWarning: string, + blobCorsWarning: string, + azDocLinkText: string, providers: { azureBlob: { - title: string; + title: string, description: string, accountName: { title: string, @@ -299,52 +300,52 @@ export interface IAppStrings { title: string, description: string, }, - invalidSASMessage: string; + invalidSASMessage: string, }, bing: { - title: string; - options: string; - apiKey: string; - query: string; + title: string, + options: string, + apiKey: string, + query: string, aspectRatio: { - title: string; - all: string; - square: string; - wide: string; - tall: string; + title: string, + all: string, + square: string, + wide: string, + tall: string, } }, local: { - title: string; - folderPath: string; - browse: string; - selectFolder: string; - chooseFolder: string; - invalidFolderMessage: string; + title: string, + folderPath: string, + browse: string, + selectFolder: string, + chooseFolder: string, + invalidFolderMessage: string, }, } }; editorPage: { - title: string; - width: string; - height: string; - tagged: string; - visited: string; + title: string, + width: string, + height: string, + tagged: string, + visited: string, toolbar: { - select: string; - pan: string; - drawRectangle: string; - drawPolygon: string; - copyRectangle: string; - copy: string; - cut: string; - paste: string; - removeAllRegions: string; - previousAsset: string; - nextAsset: string; - saveProject: string; - exportProject: string; - activeLearning: string; + select: string, + pan: string, + drawRectangle: string, + drawPolygon: string, + copyRectangle: string, + copy: string, + cut: string, + paste: string, + removeAllRegions: string, + previousAsset: string, + nextAsset: string, + saveProject: string, + exportProject: string, + activeLearning: string, } videoPlayer: { nextTaggedFrame: { @@ -361,13 +362,13 @@ export interface IAppStrings { }, } help: { - title: string; - escape: string; + title: string, + escape: string, }, asset: { delete: { - title: string; - confirmation: string; + title: string, + confirmation: string, } }, assetWarning: { @@ -378,25 +379,25 @@ export interface IAppStrings { }, } , - assetError: string; + assetError: string, tags: { hotKey: { - apply: string; - lock: string; + apply: string, + lock: string, }, rename: { - title: string; - confirmation: string; + title: string, + confirmation: string, }, delete: { - title: string; - confirmation: string; + title: string, + confirmation: string, }, } canvas: { removeAllRegions: { - title: string; - confirmation: string; + title: string, + confirmation: string, }, canvasCommandBar: { items: { @@ -405,10 +406,12 @@ export interface IAppStrings { subMenuItems: { text: string, tables: string, - selectionMarks: string; + selectionMarks: string, + drawnRegions: string, labels: string, } }, + drawRegion: string, }, farItems: { zoom: { @@ -433,7 +436,7 @@ export interface IAppStrings { } }; profile: { - settings: string; + settings: string, }; shortcuts: { squareBrackets: { @@ -478,6 +481,17 @@ export interface IAppStrings { backSpace: string, }, }, + drawnRegions: { + keys: { + escape: string, + alt: string, + backSpace: string, + }, + description: { + deleteSelectedDrawnRegions: string, + cancelDrawOrReshape: string, + } + }, tips: { quickLabeling: { name: string, @@ -493,18 +507,18 @@ export interface IAppStrings { }, deleteAllLabelsForTag: { name: string, - description: string; + description: string, }, groupSelect: { name: string, - description: string; + description: string, } }, headers: { keyboardShortcuts: string, otherTips: string, }, - iconTitle: string; + iconTitle: string, }; errors: { unknown: IErrorMetadata, @@ -549,12 +563,12 @@ export interface IAppStrings { copy: { success: string, } - } + }; } interface IErrorMetadata { - title: string; - message: string; + title: string, + message: string, } interface IStrings extends LocalizedStringsMethods, IAppStrings { } diff --git a/src/config/fabric-icons.json b/src/config/fabric-icons.json index 4ca474556..16c657495 100644 --- a/src/config/fabric-icons.json +++ b/src/config/fabric-icons.json @@ -7,152 +7,156 @@ "hashFontFileName": true, "glyphs": [ { - "name": "Combine", - "unicode": "EDBB" + "name": "Table", + "unicode": "ED86" }, { "name": "TextField", "unicode": "EDC3" }, { - "name": "SortUp", - "unicode": "EE68" + "name": "OpenFolderHorizontal", + "unicode": "ED25" }, { - "name": "SortDown", - "unicode": "EE69" + "name": "Documentation", + "unicode": "EC17" }, { - "name": "OpenFolderHorizontal", - "unicode": "ED25" + "name": "AddTo", + "unicode": "ECC8" }, { - "name": "Table", - "unicode": "ED86" + "name": "SortUp", + "unicode": "EE68" }, { - "name": "DocumentManagement", - "unicode": "EFFC" + "name": "SortDown", + "unicode": "EE69" }, { - "name": "TextDocument", - "unicode": "F029" + "name": "Info", + "unicode": "E946" }, { - "name": "CircleRing", - "unicode": "EA3A" + "name": "ChromeMinimize", + "unicode": "E921" + }, + { + "name": "ChromeRestore", + "unicode": "E923" }, { "name": "Label", "unicode": "E932" }, { - "name": "Info", - "unicode": "E946" + "name": "Copy", + "unicode": "E8C8" }, { - "name": "Documentation", - "unicode": "EC17" + "name": "Rename", + "unicode": "E8AC" }, { - "name": "AddTo", - "unicode": "ECC8" + "name": "Download", + "unicode": "E896" }, { - "name": "Hide3", - "unicode": "F6AC" + "name": "Help", + "unicode": "E897" }, { - "name": "WarningSolid", - "unicode": "F736" + "name": "ZoomIn", + "unicode": "E8A3" }, { - "name": "BranchMerge", - "unicode": "F295" + "name": "Tag", + "unicode": "E8EC" }, { - "name": "StatusCircleCheckmark", - "unicode": "F13E" + "name": "CircleRing", + "unicode": "EA3A" }, { - "name": "AlertSolid", - "unicode": "F331" + "name": "SquareShape", + "unicode": "F1A6" }, { - "name": "Plug", - "unicode": "F300" + "name": "RectangleShape", + "unicode": "F1A9" }, { - "name": "PlugConnected", - "unicode": "F302" + "name": "DocumentManagement", + "unicode": "EFFC" }, { - "name": "ChevronDown", - "unicode": "E70D" + "name": "Relationship", + "unicode": "F003" }, { - "name": "ChevronUp", - "unicode": "E70E" + "name": "TextDocument", + "unicode": "F029" }, { - "name": "Edit", - "unicode": "E70F" + "name": "StatusCircleCheckmark", + "unicode": "F13E" }, { - "name": "Add", - "unicode": "E710" + "name": "PlugConnected", + "unicode": "F302" }, { - "name": "Cancel", - "unicode": "E711" + "name": "Plug", + "unicode": "F300" }, { - "name": "More", - "unicode": "E712" + "name": "AlertSolid", + "unicode": "F331" }, { - "name": "Link", - "unicode": "E71B" + "name": "BranchMerge", + "unicode": "F295" }, { - "name": "Settings", - "unicode": "E713" + "name": "View", + "unicode": "E890" }, { "name": "ReceiptProcessing", "unicode": "E496" }, { - "name": "Filter", - "unicode": "E71C" + "name": "AddField", + "unicode": "E4C7" }, { - "name": "ZoomOut", - "unicode": "E71F" + "name": "TagGroup", + "unicode": "E3F6" }, { - "name": "CheckboxComposite", - "unicode": "E73A" + "name": "Insights", + "unicode": "E3AF" }, { - "name": "CheckMark", - "unicode": "E73E" + "name": "MachineLearning", + "unicode": "E3B8" }, { - "name": "Up", - "unicode": "E74A" + "name": "Merge", + "unicode": "E7D5" }, { - "name": "Down", - "unicode": "E74B" + "name": "MapLayers", + "unicode": "E81E" }, { - "name": "Delete", - "unicode": "E74D" + "name": "Home", + "unicode": "E80F" }, { - "name": "Cloud", - "unicode": "E753" + "name": "ZoomOut", + "unicode": "E71F" }, { "name": "Search", @@ -167,36 +171,40 @@ "unicode": "E72D" }, { - "name": "Download", - "unicode": "E896" + "name": "Link", + "unicode": "E71B" }, { - "name": "Help", - "unicode": "E897" + "name": "ChevronDown", + "unicode": "E70D" }, { - "name": "Rename", - "unicode": "E8AC" + "name": "ChevronUp", + "unicode": "E70E" }, { - "name": "ZoomIn", - "unicode": "E8A3" + "name": "Edit", + "unicode": "E70F" }, { - "name": "View", - "unicode": "E890" + "name": "Add", + "unicode": "E710" }, { - "name": "MapLayers", - "unicode": "E81E" + "name": "Cancel", + "unicode": "E711" }, { - "name": "Tag", - "unicode": "E8EC" + "name": "More", + "unicode": "E712" }, { - "name": "Copy", - "unicode": "E8C8" + "name": "Settings", + "unicode": "E713" + }, + { + "name": "Filter", + "unicode": "E71C" }, { "name": "ChevronLeft", @@ -207,48 +215,48 @@ "unicode": "E76C" }, { - "name": "Home", - "unicode": "E80F" + "name": "System", + "unicode": "E770" }, { - "name": "KeyPhraseExtraction", - "unicode": "E395" + "name": "CheckboxComposite", + "unicode": "E73A" }, { - "name": "Insights", - "unicode": "E3AF" + "name": "CheckMark", + "unicode": "E73E" }, { - "name": "MachineLearning", - "unicode": "E3B8" + "name": "Down", + "unicode": "E74B" }, { - "name": "TagGroup", - "unicode": "E3F6" + "name": "Delete", + "unicode": "E74D" }, { - "name": "BookAnswers", - "unicode": "F8A4" + "name": "Cloud", + "unicode": "E753" }, { - "name": "ChromeRestore", - "unicode": "E923" + "name": "Up", + "unicode": "E74A" }, { - "name": "ChromeMinimize", - "unicode": "E921" + "name": "KeyPhraseExtraction", + "unicode": "E395" }, { - "name": "System", - "unicode": "E770" + "name": "Hide3", + "unicode": "F6AC" }, { - "name": "SquareShape", - "unicode": "F1A6" + "name": "WarningSolid", + "unicode": "F736" }, { - "name": "Merge", - "unicode": "E7D5" + "name": "BookAnswers", + "unicode": "F8A4" } ] -} +} \ No newline at end of file diff --git a/src/models/applicationState.ts b/src/models/applicationState.ts index 1cba71c4c..1d5f4eede 100644 --- a/src/models/applicationState.ts +++ b/src/models/applicationState.ts @@ -13,12 +13,12 @@ import { ITrainRecordProps } from "../react/components/pages/train/trainRecord"; * @member appError - error in the app if any */ export interface IApplicationState { - appSettings: IAppSettings; - connections: IConnection[]; - recentProjects: IProject[]; - currentProject: IProject; - appError?: IAppError; - appTitle?: string; + appSettings: IAppSettings, + connections: IConnection[], + recentProjects: IProject[], + currentProject: IProject, + appError?: IAppError, + appTitle?: string, } /** @@ -29,9 +29,9 @@ export interface IApplicationState { * @member errorCode - error category */ export interface IAppError { - errorCode: ErrorCode; - message: any; - title?: string; + errorCode: ErrorCode, + message: any, + title?: string, } /** @@ -55,7 +55,7 @@ export class AppError extends Error implements IAppError { * @description - Property map of key values used within a export / asset / storage provider */ export interface IProviderOptions { - [key: string]: any; + [key: string]: any, } /** @@ -65,8 +65,8 @@ export interface IProviderOptions { * @member securityTokens - Token used to encrypt sensitive project settings */ export interface IAppSettings { - securityTokens: ISecurityToken[]; - thumbnailSize?: ISize; + securityTokens: ISecurityToken[], + thumbnailSize?: ISize, } /** @@ -81,21 +81,21 @@ export interface IAppSettings { * @member assets - Map of assets within a project */ export interface IProject { - id: string; - name: string; - version: string; - securityToken: string; - description?: string; - tags: ITag[]; - sourceConnection: IConnection; - assets?: { [index: string]: IAsset }; - lastVisitedAssetId?: string; - apiUriBase: string; - apiKey?: string | ISecureString; - folderPath: string; - trainRecord: ITrainRecordProps; - recentModelRecords: IRecentModel[]; - predictModelId: string; + id: string, + name: string, + version: string, + securityToken: string, + description?: string, + tags: ITag[], + sourceConnection: IConnection, + assets?: { [index: string]: IAsset }, + lastVisitedAssetId?: string, + apiUriBase: string, + apiKey?: string | ISecureString, + folderPath: string, + trainRecord: ITrainRecordProps, + recentModelRecords: IRecentModel[], + predictModelId: string, } /** @@ -105,8 +105,8 @@ export interface IProject { * @member file - The File object point to the V1 project file */ export interface IFileInfo { - content: string | ArrayBuffer; - file: File; + content: string | ArrayBuffer, + file: File, } /** @@ -116,11 +116,11 @@ export interface IFileInfo { * @member color - User editable color associated to tag */ export interface ITag { - name: string; - color: string; - type: FieldType; - format: FieldFormat; - documentCount?: number; + name: string, + color: string, + type: FieldType, + format: FieldFormat, + documentCount?: number, } /** @@ -133,11 +133,11 @@ export interface ITag { * @member providerOptions - Provider specific options used to connect to the data source */ export interface IConnection { - id: string; - name: string; - description?: string; - providerType: string; - providerOptions: IProviderOptions | ISecureString; + id: string, + name: string, + description?: string, + providerType: string, + providerOptions: IProviderOptions | ISecureString, } /** @@ -151,18 +151,18 @@ export interface IConnection { * @member format - The asset format (jpg, png, mp4, etc) */ export interface IAsset { - id: string; - type: AssetType; - state: AssetState; - name: string; - path: string; - size: ISize; - format?: string; - timestamp?: number; - predicted?: boolean; - ocr?: any; - isRunningOCR?: boolean; - cachedImage?: string; + id: string, + type: AssetType, + state: AssetState, + name: string, + path: string, + size: ISize, + format?: string, + timestamp?: number, + predicted?: boolean, + ocr?: any, + isRunningOCR?: boolean, + cachedImage?: string, } /** @@ -172,10 +172,10 @@ export interface IAsset { * @member regions - The list of regions drawn on the asset */ export interface IAssetMetadata { - asset: IAsset; - regions: IRegion[]; - version: string; - labelData: ILabelData; + asset: IAsset, + regions: IRegion[], + version: string, + labelData: ILabelData, } /** @@ -185,8 +185,8 @@ export interface IAssetMetadata { * @member height - The actual height of an asset */ export interface ISize { - width: number; - height: number; + width: number, + height: number, } /** @@ -198,14 +198,14 @@ export interface ISize { * @member points - Defines a list of points that define a region */ export interface IRegion { - id: string; - type: RegionType; - category: FeatureCategory; - tags: string[]; - points?: IPoint[]; - boundingBox?: IBoundingBox; - value?: string; - pageNumber: number; + id: string, + type: RegionType, + category: FeatureCategory, + tags: string[], + points?: IPoint[], + boundingBox?: IBoundingBox, + value?: string, + pageNumber: number, } /** @@ -213,8 +213,8 @@ export interface IRegion { * @description - Defines a label data correspond to an asset */ export interface ILabelData { - document: string; - labels: ILabel[]; + document: string, + labels: ILabel[], } /** @@ -222,9 +222,10 @@ export interface ILabelData { * @description - Defines a label */ export interface ILabel { - label: string; - key?: IFormRegion[]; - value: IFormRegion[]; + label: string, + key?: IFormRegion[], + value: IFormRegion[], + labelType?: string, } /** @@ -232,9 +233,9 @@ export interface ILabel { * @description - Defines a region which consumed by FormRecognizer */ export interface IFormRegion { - page: number; - text: string; - boundingBoxes: [number[]]; + page: number, + text: string, + boundingBoxes: [number[]], } /** @@ -246,10 +247,10 @@ export interface IFormRegion { * @member height - Defines the height of the bounding box */ export interface IBoundingBox { - left: number; - top: number; - width: number; - height: number; + left: number, + top: number, + width: number, + height: number, } /** @@ -259,39 +260,39 @@ export interface IBoundingBox { * @member y - The y value relative to the asset */ export interface IPoint { - x: number; - y: number; + x: number, + y: number, } export interface ISecureString { - encrypted: string; + encrypted: string, } export interface ISecurityToken { - name: string; - key: string; + name: string, + key: string, } export interface IField { - fieldKey: string; - fieldType: FieldType; - fieldFormat: FieldFormat; + fieldKey: string, + fieldType: FieldType, + fieldFormat: FieldFormat, } export interface IFieldInfo { - fields: IField[]; + fields: IField[], } export interface IRecentModel { - readonly composedTrainResults?: object; - readonly accuracies?: object; - readonly averageAccuracy?: number; + readonly composedTrainResults?: object, + readonly accuracies?: object, + readonly averageAccuracy?: number, readonly modelInfo: { - readonly isComposed: boolean; - readonly modelId: string; - readonly createdDateTime: string; - readonly modelName: string; - }; + readonly isComposed: boolean, + readonly modelId: string, + readonly createdDateTime: string, + readonly modelName: string, + }, } /** @@ -391,6 +392,10 @@ export enum FieldType { SelectionMark = "selectionMark", } +export enum LabelType { + DrawnRegion = "drawnRegion" +} + export enum FieldFormat { NotSpecified = "not-specified", Currency = "currency", @@ -407,4 +412,10 @@ export enum FeatureCategory { Text = "text", Checkbox = "checkbox", Label = "label", + DrawnRegion = "drawnRegion" +} + +export enum ImageMapParent { + Predict = "predict", + Editor = "editor", } diff --git a/src/react/components/common/imageMap/imageMap.tsx b/src/react/components/common/imageMap/imageMap.tsx index 543d5715e..dfcb49d51 100644 --- a/src/react/components/common/imageMap/imageMap.tsx +++ b/src/react/components/common/imageMap/imageMap.tsx @@ -3,8 +3,14 @@ import { Feature, MapBrowserEvent, View } from "ol"; import { Extent, getCenter } from "ol/extent"; -import { defaults as defaultInteractions, DragPan, Interaction, DragBox } from "ol/interaction.js"; -import { shiftKeyOnly } from 'ol/events/condition'; +import { defaults as defaultInteractions, DragPan, Interaction, DragBox, Snap } from "ol/interaction.js"; +import PointerInteraction from 'ol/interaction/Pointer'; +import Draw from "ol/interaction/Draw.js"; +import Style from "ol/style/Style"; +import Collection from 'ol/Collection'; +import { shiftKeyOnly, never } from 'ol/events/condition'; +import { Modify } from "ol/interaction"; +import Polygon from "ol/geom/Polygon"; import ImageLayer from "ol/layer/Image"; import Layer from "ol/layer/Layer"; import VectorLayer from "ol/layer/Vector"; @@ -15,7 +21,7 @@ import VectorSource from "ol/source/Vector"; import * as React from "react"; import "./styles.css"; import Utils from "./utils"; -import { FeatureCategory, IRegion } from "../../../../models/applicationState"; +import { FeatureCategory, IRegion, ImageMapParent } from "../../../../models/applicationState"; interface IImageMapProps { imageUri: string; @@ -23,23 +29,38 @@ interface IImageMapProps { imageHeight: number; imageAngle?: number; - featureStyler?: any; - tableBorderFeatureStyler?: any; - tableIconFeatureStyler?: any; - tableIconBorderFeatureStyler?: any; - checkboxFeatureStyler?: any; - labelFeatureStyler?: any; + featureStyler?: (feature) => Style; + tableBorderFeatureStyler?: (feature) => Style; + tableIconFeatureStyler?: (feature, resolution) => Style; + tableIconBorderFeatureStyler?: (feature) => Style; + checkboxFeatureStyler?: (feature) => Style; + labelFeatureStyler?: (feature) => Style; + drawRegionStyler?: () => Style; + drawnRegionStyler?: (feature) => Style; + modifyStyler?: () => Style; + + parentPage?: string; enableFeatureSelection?: boolean; handleFeatureSelect?: (feature: any, isTaggle: boolean, category: FeatureCategory) => void; groupSelectMode?: boolean; + handleIsPointerOnImage?: (isPointerOnImage: boolean) => void; + isPointerOnImage?: boolean; + drawRegionMode?: boolean; + isSnapped?: boolean; + handleIsSnapped?: (snapped: boolean) => void; + handleVertexDrag?: (dragging: boolean) => void; + isVertexDragging?: boolean; + handleDrawing?: (drawing: boolean) => void; + isDrawing?: boolean; handleRegionSelectByGroup?: (selectedRegions: IRegion[]) => void; handleFeatureSelectByGroup?: (feature) => IRegion; hoveringFeature?: string; - onMapReady: () => void; handleTableToolTipChange?: (display: string, width: number, height: number, top: number, left: number, rows: number, columns: number, featureID: string) => void; + addDrawnRegionFeatureProps?: (feature) => void; + updateFeatureAfterModify?: (features) => any; } @@ -52,24 +73,41 @@ export class ImageMap extends React.Component { private tableIconBorderVectorLayer: VectorLayer; private checkboxVectorLayer: VectorLayer; private labelVectorLayer: VectorLayer; + private drawRegionVectorLayer: VectorLayer; + private drawnLabelVectorLayer: VectorLayer; private mapElement: HTMLDivElement | null = null; + private draw: Draw; + private dragBox: DragBox; + private modify: Modify; + private snap: Snap; + + private drawnFeatures: Collection = new Collection([], {unique: true}); + public modifyStartFeatureCoordinates: any = {}; + private imageExtent: number[]; private countPointerDown: number = 0; private isSwiping: boolean = false; + private readonly IMAGE_LAYER_NAME = "imageLayer"; private readonly TEXT_VECTOR_LAYER_NAME = "textVectorLayer"; private readonly TABLE_BORDER_VECTOR_LAYER_NAME = "tableBorderVectorLayer"; private readonly TABLE_ICON_VECTOR_LAYER_NAME = "tableIconVectorLayer"; private readonly TABLE_ICON_BORDER_VECTOR_LAYER_NAME = "tableIconBorderVectorLayer"; private readonly CHECKBOX_VECTOR_LAYER_NAME = "checkboxBorderVectorLayer"; private readonly LABEL_VECTOR_LAYER_NAME = "labelledVectorLayer"; + private readonly DRAWN_REGION_LABEL_VECTOR_LAYER_NAME = "drawnRegionLabelledVectorLayer"; + private readonly DRAWN_REGION_VECTOR_LAYER_NAME = "drawnRegionVectorLayer"; private ignorePointerMoveEventCount: number = 5; private pointerMoveEventCount: number = 0; + private imageLayerFilter = { + layerFilter: (layer: Layer) => layer.get("name") === this.IMAGE_LAYER_NAME, + }; + private textVectorLayerFilter = { layerFilter: (layer: Layer) => layer.get("name") === this.TEXT_VECTOR_LAYER_NAME, }; @@ -86,6 +124,14 @@ export class ImageMap extends React.Component { layerFilter: (layer: Layer) => layer.get("name") === this.LABEL_VECTOR_LAYER_NAME, }; + private drawnLabelVectorLayerFilter = { + layerFilter: (layer: Layer) => layer.get("name") === this.DRAWN_REGION_LABEL_VECTOR_LAYER_NAME, + }; + + private drawnRegionVectorLayerFilter = { + layerFilter: (layer: Layer) => layer.get("name") === this.DRAWN_REGION_VECTOR_LAYER_NAME, + }; + constructor(props: IImageMapProps) { super(props); @@ -93,10 +139,51 @@ export class ImageMap extends React.Component { } public componentDidMount() { - this.initMap(); + if (this.props.parentPage === ImageMapParent.Editor) { + this.initEditorMap(); + } else { + this.initPredictMap(); + } } public componentDidUpdate(prevProps: IImageMapProps) { + if (this.props.parentPage === ImageMapParent.Editor) { + if (this.props?.drawRegionMode) { + this.removeInteraction(this.dragBox); + this.initializeDraw(); + this.addInteraction(this.draw); + this.initializeModify(); + this.addInteraction(this.modify); + this.addInteraction(this.snap); + if (this.props?.isPointerOnImage) { + if (this.props.isSnapped) { + this.removeInteraction(this.draw); + } + if (this.props.isDrawing) { + this.removeInteraction(this.snap); + } + } else { + this.removeInteraction(this.draw); + this.removeInteraction(this.modify); + this.removeInteraction(this.snap); + } + } else { + this.removeInteraction(this.draw); + this.addInteraction(this.dragBox); + this.initializeModify(); + this.addInteraction(this.modify); + this.addInteraction(this.snap); + if (!this.props?.isPointerOnImage) { + this.removeInteraction(this.modify); + this.removeInteraction(this.dragBox); + } + } + + if (!this.props.isPointerOnImage && prevProps.isPointerOnImage && this.props.isVertexDragging) { + this.cancelModify(); + } + } + if (prevProps.imageUri !== this.props.imageUri) { this.imageExtent = [0, 0, this.props.imageWidth, this.props.imageHeight]; this.setImage(this.props.imageUri, this.imageExtent); @@ -105,8 +192,8 @@ export class ImageMap extends React.Component { public render() { return ( -
-
this.mapElement = el}>
+
+
this.mapElement = el}/>
); } @@ -116,6 +203,7 @@ export class ImageMap extends React.Component { this.toggleLabelFeatureVisibility(true); this.toggleTableFeatureVisibility(true); this.toggleTextFeatureVisibility(true); + this.toggleDrawnRegionsFeatureVisibility(true); } /** @@ -129,6 +217,50 @@ export class ImageMap extends React.Component { public toggleLabelFeatureVisibility = (visible: boolean = false) => { this.labelVectorLayer.setVisible(visible || !this.labelVectorLayer.getVisible()); + let drawLabelVectorLayerVisibility = this.drawnLabelVectorLayer.getVisible(); + this.drawnLabelVectorLayer.setVisible(visible || !drawLabelVectorLayerVisibility); + drawLabelVectorLayerVisibility = this.drawnLabelVectorLayer.getVisible(); + const drawnLabelFeatures = this.getAllDrawnLabelFeatures(); + if (!drawLabelVectorLayerVisibility) { + drawnLabelFeatures?.forEach((feature) => { + this.removeFromDrawnFeatures(feature); + }); + } else { + drawnLabelFeatures?.forEach((feature) => { + this.pushToDrawnFeatures(feature); + }); + } + + } + + public toggleDrawnRegionsFeatureVisibility = (visible: boolean = false) => { + let drawRegionVectorLayerVisibility = this.drawRegionVectorLayer.getVisible(); + this.drawRegionVectorLayer.setVisible(visible || !drawRegionVectorLayerVisibility); + drawRegionVectorLayerVisibility = this.drawRegionVectorLayer.getVisible(); + const drawnRegionFeatures = this.getAllDrawnRegionFeatures(); + if (!drawRegionVectorLayerVisibility) { + drawnRegionFeatures?.forEach((feature) => { + this.removeFromDrawnFeatures(feature); + }); + } else { + drawnRegionFeatures?.forEach((feature) => { + this.pushToDrawnFeatures(feature); + }); + } + } + + private pushToDrawnFeatures = (feature, drawnFeatures: Collection = this.drawnFeatures) => { + const itemAlreadyExists = drawnFeatures.getArray().indexOf(feature) !== -1 + if (!itemAlreadyExists) { + drawnFeatures.push(feature); + } + } + + private removeFromDrawnFeatures = (feature, drawnFeatures: Collection = this.drawnFeatures) => { + const itemAlreadyExists = drawnFeatures.getArray().indexOf(feature) !== -1 + if (itemAlreadyExists) { + drawnFeatures.remove(feature); + } } /** @@ -154,7 +286,7 @@ export class ImageMap extends React.Component { } /** - * Add one feature to the map + * Add one text feature to the map */ public addFeature = (feature: Feature) => { this.textVectorLayer.getSource().addFeature(feature); @@ -168,6 +300,10 @@ export class ImageMap extends React.Component { this.labelVectorLayer.getSource().addFeature(feature); } + public addDrawnLabelFeature = (feature: Feature) => { + this.drawnLabelVectorLayer.getSource().addFeature(feature); + } + public addTableBorderFeature = (feature: Feature) => { this.tableBorderVectorLayer.getSource().addFeature(feature); } @@ -195,6 +331,10 @@ export class ImageMap extends React.Component { this.labelVectorLayer.getSource().addFeatures(features); } + public addDrawnLabelFeatures = (features: Feature[]) => { + this.drawnLabelVectorLayer.getSource().addFeatures(features); + } + public addTableBorderFeatures = (features: Feature[]) => { this.tableBorderVectorLayer.getSource().addFeatures(features); } @@ -207,11 +347,19 @@ export class ImageMap extends React.Component { this.tableIconBorderVectorLayer.getSource().addFeatures(features); } + public addDrawnRegionFeatures = (features: Feature[]) => { + this.drawRegionVectorLayer.getSource().addFeatures(features); + } + /** * Add interaction to the map */ public addInteraction = (interaction: Interaction) => { - this.map.addInteraction(interaction); + if (undefined === this.map.getInteractions().array_.find((existingInteraction) => { + return interaction.constructor.name === existingInteraction.constructor.name + })) { + this.map.addInteraction(interaction); + } } /** @@ -229,6 +377,14 @@ export class ImageMap extends React.Component { return this.labelVectorLayer.getSource().getFeatures(); } + public getAllDrawnLabelFeatures = () => { + return this.drawnLabelVectorLayer.getSource().getFeatures(); + } + + public getAllDrawnRegionFeatures = () => { + return this.drawRegionVectorLayer.getSource().getFeatures(); + } + public getFeatureByID = (featureID) => { return this.textVectorLayer.getSource().getFeatureById(featureID); } @@ -249,19 +405,49 @@ export class ImageMap extends React.Component { return this.tableIconBorderVectorLayer.getSource().getFeatureById(featureID); } + public getDrawnRegionFeatureByID = (featureID) => { + return this.drawRegionVectorLayer.getSource().getFeatureById(featureID); + } + + public getLabelFeatureByID = (featureID) => { + return this.labelVectorLayer.getSource().getFeatureById(featureID); + } + + public getDrawnLabelFeatureByID = (featureID) => { + return this.drawnLabelVectorLayer.getSource().getFeatureById(featureID); + } + /** * Remove specific feature object from the map */ public removeFeature = (feature: Feature) => { - this.textVectorLayer.getSource().removeFeature(feature); + if (feature && this.getFeatureByID(feature.getId())) { + this.textVectorLayer.getSource().removeFeature(feature); + } } public removeCheckboxFeature = (feature: Feature) => { - this.checkboxVectorLayer.getSource().removeFeature(feature); + if (feature && this.getCheckboxFeatureByID(feature.getId())) { + this.checkboxVectorLayer.getSource().removeFeature(feature); + } } public removeLabelFeature = (feature: Feature) => { - this.labelVectorLayer.getSource().removeFeature(feature); + if (feature && this.getLabelFeatureByID(feature.getId())) { + this.labelVectorLayer.getSource().removeFeature(feature); + } + } + + public removeDrawnLabelFeature = (feature: Feature) => { + if (feature && this.getDrawnLabelFeatureByID(feature.getId())) { + this.drawnLabelVectorLayer.getSource().removeFeature(feature); + } + } + + public removeDrawnRegionFeature = (feature: Feature) => { + if (feature && this.getDrawnRegionFeatureByID(feature.getId())) { + this.drawRegionVectorLayer.getSource().removeFeature(feature); + } } /** @@ -271,23 +457,64 @@ export class ImageMap extends React.Component { if (this.props.handleTableToolTipChange) { this.props.handleTableToolTipChange("none", 0, 0, 0, 0, 0, 0, null); } - this.textVectorLayer.getSource().clear(); - this.tableBorderVectorLayer.getSource().clear(); - this.tableIconVectorLayer.getSource().clear(); - this.tableIconBorderVectorLayer.getSource().clear(); - this.checkboxVectorLayer.getSource().clear(); - this.labelVectorLayer.getSource().clear(); + this.textVectorLayer?.getSource().clear(); + this.tableBorderVectorLayer?.getSource().clear(); + this.tableIconVectorLayer?.getSource().clear(); + this.tableIconBorderVectorLayer?.getSource().clear(); + this.checkboxVectorLayer?.getSource().clear(); + this.labelVectorLayer?.getSource().clear(); + this.clearDrawnRegions(); + } + + private clearDrawnRegions = () => { + this.drawRegionVectorLayer?.getSource().clear(); + this.drawnLabelVectorLayer?.getSource().clear(); + + this.drawnFeatures = new Collection([], {unique: true}); + + this.drawRegionVectorLayer.getSource().on("addfeature", (evt) => { + this.pushToDrawnFeatures(evt.feature, this.drawnFeatures); + }); + this.drawRegionVectorLayer.getSource().on("removefeature", (evt) => { + this.removeFromDrawnFeatures(evt.feature, this.drawnFeatures); + }); + this.drawnLabelVectorLayer.getSource().on("addfeature", (evt) => { + this.pushToDrawnFeatures(evt.feature, this.drawnFeatures); + }); + this.drawnLabelVectorLayer.getSource().on("removefeature", (evt) => { + this.removeFromDrawnFeatures(evt.feature, this.drawnFeatures); + }); + + this.removeInteraction(this.snap); + this.initializeSnap(); + this.addInteraction(this.snap) + this.removeInteraction(this.modify); + this.initializeModify(); + this.addInteraction(this.modify); } public removeAllLabelFeatures = () => { - this.labelVectorLayer.getSource().clear(); + this.labelVectorLayer?.getSource().clear(); + } + + public removeAllDrawnLabelFeatures = () => { + this.getAllDrawnLabelFeatures().forEach((feature) => { + this.removeFromDrawnFeatures(feature); + }); + this.drawnLabelVectorLayer?.getSource().clear(); } /** * Remove interaction from the map */ public removeInteraction = (interaction: Interaction) => { - this.map.removeInteraction(interaction); + const existingInteraction = this.map.getInteractions().array_.find((existingInteraction) => { + return interaction.constructor.name === existingInteraction.constructor.name + }); + + if (existingInteraction !== undefined) { + this.map.removeInteraction(existingInteraction); + } } public updateSize = () => { @@ -327,110 +554,23 @@ export class ImageMap extends React.Component { this.map.getView().setZoom(0); } - private initMap = () => { + private initPredictMap = () => { const projection = this.createProjection(this.imageExtent); + const layers = this.initializePredictLayers(projection); + this.initializeMap(projection, layers); + } - this.imageLayer = new ImageLayer({ - source: this.createImageSource(this.props.imageUri, projection, this.imageExtent), - }); - - const textOptions: any = {}; - textOptions.name = this.TEXT_VECTOR_LAYER_NAME; - textOptions.style = this.props.featureStyler; - textOptions.source = new VectorSource(); - this.textVectorLayer = new VectorLayer(textOptions); - - const tableBorderOptions: any = {}; - tableBorderOptions.name = this.TABLE_BORDER_VECTOR_LAYER_NAME; - tableBorderOptions.style = this.props.tableBorderFeatureStyler; - tableBorderOptions.source = new VectorSource(); - this.tableBorderVectorLayer = new VectorLayer(tableBorderOptions); - - const tableIconOptions: any = {}; - tableIconOptions.name = this.TABLE_ICON_VECTOR_LAYER_NAME; - tableIconOptions.style = this.props.tableIconFeatureStyler; - tableIconOptions.updateWhileAnimating = true; - tableIconOptions.updateWhileInteracting = true; - tableIconOptions.source = new VectorSource(); - this.tableIconVectorLayer = new VectorLayer(tableIconOptions); - - const tableIconBorderOptions: any = {}; - tableIconBorderOptions.name = this.TABLE_ICON_BORDER_VECTOR_LAYER_NAME; - tableIconBorderOptions.style = this.props.tableIconBorderFeatureStyler; - tableIconBorderOptions.source = new VectorSource(); - this.tableIconBorderVectorLayer = new VectorLayer(tableIconBorderOptions); - - const checkboxOptions: any = {}; - checkboxOptions.name = this.CHECKBOX_VECTOR_LAYER_NAME; - checkboxOptions.style = this.props.checkboxFeatureStyler; - checkboxOptions.source = new VectorSource(); - this.checkboxVectorLayer = new VectorLayer(checkboxOptions); - - const labelOptions: any = {}; - labelOptions.name = this.LABEL_VECTOR_LAYER_NAME; - labelOptions.style = this.props.labelFeatureStyler; - labelOptions.source = new VectorSource(); - this.labelVectorLayer = new VectorLayer(labelOptions); - - this.map = new Map({ - controls: [] , - interactions: defaultInteractions({ - shiftDragZoom: false, - doubleClickZoom: false, - pinchRotate: false, - }), - target: "map", - layers: [ - this.imageLayer, - this.textVectorLayer, - this.tableBorderVectorLayer, - this.tableIconVectorLayer, - this.tableIconBorderVectorLayer, - this.checkboxVectorLayer, - this.labelVectorLayer, - ], - view: this.createMapView(projection, this.imageExtent), - }); - - if (this.props?.handleRegionSelectByGroup && this.props?.handleFeatureSelectByGroup) { - const dragBox = new DragBox({ - condition: shiftKeyOnly, - className: "ol-dragbox-style", - }); - - this.map.addInteraction(dragBox); - - dragBox.on('boxend', () => { - const featureMap = {}; - const extent = dragBox.getGeometry().getExtent(); - const regionsToAdd: IRegion[] = []; - if (this.labelVectorLayer.getVisible()) { - this.labelVectorLayer.getSource().forEachFeatureInExtent(extent, (feature) => { - const selectedRegion = this.props.handleFeatureSelectByGroup(feature); - if (selectedRegion) { - featureMap[feature.get("id")] = true; - regionsToAdd.push(selectedRegion); - } - }); - } - if (this.textVectorLayer.getVisible()) { - this.textVectorLayer.getSource().forEachFeatureInExtent(extent, (feature) => { - const selectedRegion = this.props.handleFeatureSelectByGroup(feature); - if (selectedRegion && !featureMap.hasOwnProperty(feature.get("id"))) { - regionsToAdd.push(selectedRegion); - } - }); - } - if (regionsToAdd.length > 0) { - this.props.handleRegionSelectByGroup(regionsToAdd); - } - }); - } + private initEditorMap = () => { + const projection = this.createProjection(this.imageExtent); + const layers = this.initializeEditorLayers(projection); + this.initializeMap(projection, layers); this.map.on("pointerdown", this.handlePointerDown); this.map.on("pointermove", this.handlePointerMove); this.map.on("pointermove", this.handlePointerMoveOnTableIcon); this.map.on("pointerup", this.handlePointerUp); + + this.initializeDefaultSelectionMode(); } private setImage = (imageUri: string, imageExtent: number[]) => { @@ -489,6 +629,11 @@ export class ImageMap extends React.Component { } private handlePointerDown = (event: MapBrowserEvent) => { + if (this.props.isSnapped) { + this.props.handleVertexDrag(true); + return; + } + if (!this.props.enableFeatureSelection) { return; } @@ -546,6 +691,24 @@ export class ImageMap extends React.Component { category: FeatureCategory.Text, }; } + const isPointerOnDrawnRegionFeature = this.map.hasFeatureAtPixel( + eventPixel, + this.drawnRegionVectorLayerFilter); + if (isPointerOnDrawnRegionFeature) { + return { + layerfilter: this.drawnRegionVectorLayerFilter, + category: FeatureCategory.DrawnRegion, + }; + } + const isPointerOnDrawnLabelFeature = this.map.hasFeatureAtPixel( + eventPixel, + this.drawnLabelVectorLayerFilter); + if (isPointerOnDrawnLabelFeature) { + return { + layerfilter: this.drawnLabelVectorLayerFilter, + category: FeatureCategory.DrawnRegion, + }; + } return null; } @@ -610,16 +773,24 @@ export class ImageMap extends React.Component { } private handlePointerUp = () => { + if (this.props.isDrawing) { + this.props.handleDrawing(false); + return; + } + + if (this.props.isVertexDragging) { + this.props.handleVertexDrag(false); + return; + } + if (!this.props.enableFeatureSelection) { return; } this.countPointerDown -= 1; - if (this.countPointerDown === 0) { - this.setDragPanInteraction(true /*dragPanEnabled*/); - this.isSwiping = false; - this.pointerMoveEventCount = 0; - } + this.setDragPanInteraction(true /*dragPanEnabled*/); + this.isSwiping = false; + this.pointerMoveEventCount = 0; } private setDragPanInteraction = (dragPanEnabled: boolean) => { @@ -646,4 +817,371 @@ export class ImageMap extends React.Component { return false; } + + public cancelDrawing = () => { + this.removeInteraction(this.draw) + this.initializeDraw(); + this.addInteraction(this.draw); + } + + public cancelModify = () => { + Object.entries(this.modifyStartFeatureCoordinates).forEach((featureCoordinate) => { + let feature = this.getDrawnRegionFeatureByID(featureCoordinate[0]); + if (!feature) { + feature = this.getDrawnLabelFeatureByID(featureCoordinate[0]); + } + if (feature.getGeometry().flatCoordinates.join(",") !== featureCoordinate[1]) { + const oldFlattenedCoordinates = (featureCoordinate[1] as string).split(",").map(parseFloat); + const oldCoordinates = []; + for (let i = 0; i < oldFlattenedCoordinates.length; i += 2) { + oldCoordinates.push([ + oldFlattenedCoordinates[i], + oldFlattenedCoordinates[i + 1], + ]); + } + feature.getGeometry().setCoordinates([oldCoordinates]); + } + }); + this.modifyStartFeatureCoordinates = {}; + this.removeInteraction(this.modify); + this.initializeModify(); + this.addInteraction(this.modify); + this.props.handleIsSnapped(false); + } + + private initializeDefaultSelectionMode = () => { + this.initializeSnapCheck(); + this.initializePointerOnImageCheck(); + this.initializeDragBox(); + this.initializeModify(); + this.initializeSnap(); + this.initializeDraw(); + this.addInteraction(this.modify); + this.addInteraction(this.snap); + } + + private initializeDraw = () => { + const boundingExtent = (coordinates) => { + const extent = createEmpty(); + coordinates.forEach((coordinate) => { + extendCoordinate(extent, coordinate); + }); + return extent; + } + + const createEmpty = () => { + return [Infinity, Infinity, -Infinity, -Infinity]; + } + + const extendCoordinate = (extent, coordinate) => { + if (coordinate[0] < extent[0]) { + extent[0] = coordinate[0]; + } + if (coordinate[0] > extent[2]) { + extent[2] = coordinate[0]; + } + if (coordinate[1] < extent[1]) { + extent[1] = coordinate[1]; + } + if (coordinate[1] > extent[3]) { + extent[3] = coordinate[1]; + } + } + + this.draw = new Draw({ + source: this.drawRegionVectorLayer.getSource(), + style: this.props.drawRegionStyler, + geometryFunction: (coordinates, optGeometry) => { + const extent = boundingExtent(/** @type {LineCoordType} */ (coordinates)); + const boxCoordinates = [[ + [extent[0], extent[3]], + [extent[2], extent[3]], + [extent[2], extent[1]], + [extent[0], extent[1]] + ]]; + let geometry = optGeometry; + if (geometry) { + geometry.setCoordinates(boxCoordinates); + } else { + geometry = new Polygon(boxCoordinates); + } + return geometry; + }, + freehand: true, + stopClick: true, + }); + + this.draw.on('drawstart', (drawEvent) => { + this.props.handleDrawing(true); + }); + + this.draw.on('drawend', (drawEvent) => { + this.props.addDrawnRegionFeatureProps(drawEvent.feature); + }); + + } + + private initializeModify = () => { + this.modify = new Modify({ + deleteCondition: never, + insertVertexCondition: never, + style: this.props.modifyStyler, + features: this.drawnFeatures, + }); + + this.modify.handleUpEvent_old = this.modify.handleUpEvent; + this.modify.handleUpEvent = function (evt) { + try { + this.handleUpEvent_old(evt); + } catch (ex) { + // do nothing + } + } + + this.modify.on('modifystart', (modifyEvent) => { + const features = modifyEvent.features.getArray(); + let featureCoordinates = []; + features.forEach((feature) => { + feature.getGeometry().getCoordinates()[0].forEach((coordinate) => { + featureCoordinates.push(coordinate[0]) + featureCoordinates.push(coordinate[1]) + }); + this.modifyStartFeatureCoordinates[feature.getId()] = featureCoordinates.join(","); + featureCoordinates = []; + }); + }); + + this.modify.on('modifyend', (modifyEvent) => { + const features = modifyEvent.features.getArray(); + this.props.updateFeatureAfterModify(features); + }); + + } + + private initializeSnap = () => { + this.snap = new Snap({ + edge: false, + vertex: true, + features: this.drawnFeatures, + }); + } + + private initializeDragBox = () => { + this.dragBox = new DragBox({ + condition: shiftKeyOnly, + className: "ol-dragbox-style", + });; + + this.dragBox.on('boxend', () => { + const featureMap = {}; + const extent = this.dragBox.getGeometry().getExtent(); + const regionsToAdd: IRegion[] = []; + if (this.labelVectorLayer.getVisible()) { + this.labelVectorLayer.getSource().forEachFeatureInExtent(extent, (feature) => { + const selectedRegion = this.props.handleFeatureSelectByGroup(feature); + if (selectedRegion) { + featureMap[feature.get("id")] = true; + regionsToAdd.push(selectedRegion); + } + }); + } + if (this.textVectorLayer.getVisible()) { + this.textVectorLayer.getSource().forEachFeatureInExtent(extent, (feature) => { + const selectedRegion = this.props.handleFeatureSelectByGroup(feature); + if (selectedRegion && !featureMap.hasOwnProperty(feature.get("id"))) { + regionsToAdd.push(selectedRegion); + } + }); + } + if (regionsToAdd.length > 0) { + this.props.handleRegionSelectByGroup(regionsToAdd); + } + }); + } + + private initializeSnapCheck = () => { + const snapCheck = new Interaction({ + handleEvent: (evt: MapBrowserEvent) => { + if (!this.props.isVertexDragging) { + this.props.handleIsSnapped(this.snap.snapTo(evt.pixel, evt.coordinate, evt.map).snapped && this.props.isPointerOnImage) + } + return true; + } + }); + this.addInteraction(snapCheck); + } + + private initializePointerOnImageCheck = () => { + const checkIfPointerOnMap = new PointerInteraction({ + handleEvent: (evt: MapBrowserEvent) => { + const eventPixel = this.map.getEventPixel(evt.originalEvent); + const test = this.map.forEachLayerAtPixel( + eventPixel, + () => { + return true + }, + this.imageLayerFilter); + if (!Boolean(test) && this.props.isPointerOnImage) { + this.props.handleIsPointerOnImage(false); + } else if (!this.props.isPointerOnImage && Boolean(test)) { + this.props.handleIsPointerOnImage(true); + } + return true + } + }); + this.addInteraction(checkIfPointerOnMap); + } + + private getCursor = () => { + if (this.props.parentPage === ImageMapParent.Editor) { + if (this.props.isVertexDragging) { + return "grabbing"; + } else if (this.props.isSnapped) { + return "grab"; + } else if (this.props?.groupSelectMode || this.props?.drawRegionMode) { + if (this.props.isPointerOnImage) { + return "crosshair"; + } else { + return "default"; + } + } else { + return "default"; + } + } else { + return "default"; + } + } + + private handlePonterLeaveImageMap = () => { + if (this.props.parentPage === ImageMapParent.Editor) { + if (this.props.isDrawing) { + this.cancelDrawing(); + } + this.props.handleIsPointerOnImage(false); + } + } + + private initializeEditorLayers = (projection: Projection) => { + this.initializeImageLayer(projection); + this.initializeTextLayer(); + this.initializeTableLayers(); + this.initializeCheckboxLayers(); + this.initializeLabelLayer(); + this.initializeDrawnRegionLabelLayer(); + this.initializeDrawnRegionLayer(); + return [this.imageLayer, this.textVectorLayer, this.tableBorderVectorLayer, this.tableIconBorderVectorLayer, + this.tableIconVectorLayer, this.checkboxVectorLayer, this.drawRegionVectorLayer, this.labelVectorLayer, + this.drawnLabelVectorLayer]; + } + + private initializePredictLayers = (projection: Projection) => { + this.initializeImageLayer(projection); + this.initializeTextLayer(); + this.initializeLabelLayer(); + return [this.imageLayer, this.textVectorLayer, this.labelVectorLayer]; + } + + private initializeImageLayer = (projection: Projection) => { + this.imageLayer = new ImageLayer({ + source: this.createImageSource(this.props.imageUri, projection, this.imageExtent), + name: this.IMAGE_LAYER_NAME, + }); + } + + private initializeTextLayer = () => { + const textOptions: any = {}; + textOptions.name = this.TEXT_VECTOR_LAYER_NAME; + textOptions.style = this.props.featureStyler; + textOptions.source = new VectorSource(); + this.textVectorLayer = new VectorLayer(textOptions); + } + + private initializeTableLayers = () => { + const tableBorderOptions: any = {}; + tableBorderOptions.name = this.TABLE_BORDER_VECTOR_LAYER_NAME; + tableBorderOptions.style = this.props.tableBorderFeatureStyler; + tableBorderOptions.source = new VectorSource(); + this.tableBorderVectorLayer = new VectorLayer(tableBorderOptions); + + const tableIconOptions: any = {}; + tableIconOptions.name = this.TABLE_ICON_VECTOR_LAYER_NAME; + tableIconOptions.style = this.props.tableIconFeatureStyler; + tableIconOptions.updateWhileAnimating = true; + tableIconOptions.updateWhileInteracting = true; + tableIconOptions.source = new VectorSource(); + this.tableIconVectorLayer = new VectorLayer(tableIconOptions); + + const tableIconBorderOptions: any = {}; + tableIconBorderOptions.name = this.TABLE_ICON_BORDER_VECTOR_LAYER_NAME; + tableIconBorderOptions.style = this.props.tableIconBorderFeatureStyler; + tableIconBorderOptions.source = new VectorSource(); + this.tableIconBorderVectorLayer = new VectorLayer(tableIconBorderOptions); + } + + private initializeCheckboxLayers = () => { + const checkboxOptions: any = {}; + checkboxOptions.name = this.CHECKBOX_VECTOR_LAYER_NAME; + checkboxOptions.style = this.props.checkboxFeatureStyler; + checkboxOptions.source = new VectorSource(); + this.checkboxVectorLayer = new VectorLayer(checkboxOptions); + } + + private initializeDrawnRegionLayer = () => { + const drawnRegionOptions: any = {}; + drawnRegionOptions.name = this.DRAWN_REGION_VECTOR_LAYER_NAME; + drawnRegionOptions.style = this.props.drawnRegionStyler; + drawnRegionOptions.source = new VectorSource(); + + drawnRegionOptions.source.on("addfeature", (evt) => { + this.pushToDrawnFeatures(evt.feature); + }); + + drawnRegionOptions.source.on("removefeature", (evt) => { + this.removeFromDrawnFeatures(evt.feature); + }); + + this.drawRegionVectorLayer = new VectorLayer(drawnRegionOptions); + } + + private initializeLabelLayer = () => { + const labelOptions: any = {}; + labelOptions.name = this.LABEL_VECTOR_LAYER_NAME; + labelOptions.style = this.props.labelFeatureStyler; + labelOptions.source = new VectorSource(); + this.labelVectorLayer = new VectorLayer(labelOptions); + } + + private initializeDrawnRegionLabelLayer = () => { + const drawnRegionLabelOptions: any = {}; + drawnRegionLabelOptions.name = this.DRAWN_REGION_VECTOR_LAYER_NAME; + drawnRegionLabelOptions.style = this.props.labelFeatureStyler; + drawnRegionLabelOptions.source = new VectorSource(); + + drawnRegionLabelOptions.source.on('addfeature', (evt) => { + if (this.drawnLabelVectorLayer.getVisible()) { + this.pushToDrawnFeatures(evt.feature) + } + }); + + drawnRegionLabelOptions.source.on('removefeature', (evt) => { + this.removeFromDrawnFeatures(evt.feature) + + }); + + this.drawnLabelVectorLayer = new VectorLayer(drawnRegionLabelOptions); + } + + private initializeMap = (projection, layers) => { + this.map = new Map({ + controls: [] , + interactions: defaultInteractions({ + shiftDragZoom: false, + doubleClickZoom: false, + pinchRotate: false, + }), + target: "map", + layers, + view: this.createMapView(projection, this.imageExtent), + }); + } } diff --git a/src/react/components/common/tagInput/tagInput.scss b/src/react/components/common/tagInput/tagInput.scss index 483d414f4..8f0b16eb2 100644 --- a/src/react/components/common/tagInput/tagInput.scss +++ b/src/react/components/common/tagInput/tagInput.scss @@ -102,6 +102,7 @@ } &-label { + min-height: 1em; display: flex; flex-direction: row; margin-bottom: 5px; diff --git a/src/react/components/common/tagInput/tagInput.tsx b/src/react/components/common/tagInput/tagInput.tsx index 92b9445c1..da1c30b33 100644 --- a/src/react/components/common/tagInput/tagInput.tsx +++ b/src/react/components/common/tagInput/tagInput.tsx @@ -12,11 +12,11 @@ import { Spinner, SpinnerSize, } from "@fluentui/react"; -import { strings } from "../../../../common/strings"; +import { strings, interpolate } from "../../../../common/strings"; import { getDarkTheme } from "../../../../common/themes"; import { AlignPortal } from "../align/alignPortal"; import { getNextColor } from "../../../../common/utils"; -import { IRegion, ITag, ILabel, FieldType, FieldFormat } from "../../../../models/applicationState"; +import { IRegion, ITag, ILabel, FieldType, FieldFormat, FeatureCategory } from "../../../../models/applicationState"; import { ColorPicker } from "../colorPicker"; import "./tagInput.scss"; import "../condensedList/condensedList.scss"; @@ -478,9 +478,21 @@ export class TagInput extends React.Component { const { category } = selectedRegions[0]; const { format, type, documentCount, name } = tag; const tagCategory = this.getTagCategory(type); - if (tagCategory === category || + const isTagLabelTypeDrawnRegion = this.labelAssignedDrawnRegion(labels, tag.name); + const labelAssigned = this.labelAssigned(labels, name); + + if (labelAssigned && ((category === FeatureCategory.DrawnRegion) !== isTagLabelTypeDrawnRegion)) { + if (isTagLabelTypeDrawnRegion) { + toast.warn(interpolate(strings.tags.warnings.notCompatibleWithDrawnRegionTag, {otherCatagory: category})); + } else if (tagCategory === FeatureCategory.Checkbox) { + toast.warn(interpolate(strings.tags.warnings.notCompatibleWithDrawnRegionTag, {otherCatagory: FeatureCategory.Checkbox})); + } else { + toast.warn(interpolate(strings.tags.warnings.notCompatibleWithDrawnRegionTag, {otherCatagory: FeatureCategory.Text})); + } + return; + } else if (tagCategory === category || category === FeatureCategory.DrawnRegion || (documentCount === 0 && type === FieldType.String && format === FieldFormat.NotSpecified)) { - if (category === "checkbox" && this.labelAssigned(labels, name)) { + if (tagCategory === FeatureCategory.Checkbox && labelAssigned) { toast.warn(strings.tags.warnings.checkboxPerTagLimit); return; } @@ -497,8 +509,22 @@ export class TagInput extends React.Component { } } - public labelAssigned = (labels, name): boolean => { - return labels.find((label) => label.label === name ? true : false); + public labelAssigned = (labels: ILabel[], name): boolean => { + const label = labels.find((label) => label.label === name ? true : false); + if (!label) { + return false; + } else { + return true; + } + } + + public labelAssignedDrawnRegion = (labels: ILabel[], name): boolean => { + const label = labels.find((label) => label.label === name ? true : false); + if (label?.labelType === FeatureCategory.DrawnRegion) { + return true; + } else { + return false; + } } public getTagCategory = (tagType: string) => { diff --git a/src/react/components/common/tagInput/tagInputItemLabel.tsx b/src/react/components/common/tagInput/tagInputItemLabel.tsx index 97bee2cad..ab56dfa3e 100644 --- a/src/react/components/common/tagInput/tagInputItemLabel.tsx +++ b/src/react/components/common/tagInput/tagInputItemLabel.tsx @@ -1,8 +1,9 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT license. -import React from "react"; +import React, { ReactHTMLElement } from "react"; import { ILabel, IFormRegion } from "../../../../models/applicationState"; +import { FontIcon } from "@fluentui/react"; export interface ITagInputItemLabelProps { label: ILabel; @@ -14,12 +15,28 @@ export interface ITagInputItemLabelState {} export default class TagInputItemLabel extends React.Component { public render() { + const drawnRegions = []; + const texts = []; + let hasEmptyTextValue = false; + this.props.label.value.forEach((formRegion: IFormRegion) => { + if (formRegion.text === "" && !hasEmptyTextValue) { + drawnRegions.push() + hasEmptyTextValue = true; + } else { + texts.push(formRegion.text); + } + }) + const text = texts.join(" "); return (
- {this.props.label.value.map((formRegion: IFormRegion) => formRegion.text).join(" ")} + onMouseLeave={this.handleMouseLeave} + > +
+ {text} + {drawnRegions} +
); } diff --git a/src/react/components/pages/editorPage/canvas.tsx b/src/react/components/pages/editorPage/canvas.tsx index c3c541dee..1bc16161a 100644 --- a/src/react/components/pages/editorPage/canvas.tsx +++ b/src/react/components/pages/editorPage/canvas.tsx @@ -9,7 +9,7 @@ import { EditorMode, IAssetMetadata, IProject, IRegion, RegionType, AssetType, ILabelData, ILabel, - ITag, IAsset, IFormRegion, FeatureCategory, FieldType, FieldFormat, + ITag, IAsset, IFormRegion, FeatureCategory, FieldType, FieldFormat, ISecurityToken, ImageMapParent, LabelType, } from "../../../../models/applicationState"; import CanvasHelpers from "./canvasHelpers"; import { AssetPreview } from "../../common/assetPreview/assetPreview"; @@ -78,6 +78,11 @@ export interface ICanvasState { tableIconTooltip: any; hoveringFeature: string; groupSelectMode: boolean; + drawRegionMode: boolean; + isSnapped: boolean; + isVertexDragging: boolean; + isDrawing: boolean; + isPointerOnImage: boolean; } interface IRegionOrder { @@ -127,10 +132,15 @@ export default class Canvas extends React.Component isError: false, errorMessage: undefined, ocrStatus: OcrStatus.done, - layers: {text: true, tables: true, checkboxes: true, label: true}, + layers: {text: true, tables: true, checkboxes: true, label: true, drawnRegions: true}, tableIconTooltip: { display: "none", width: 0, height: 0, top: 0, left: 0}, hoveringFeature: null, groupSelectMode: false, + drawRegionMode: false, + isSnapped: false, + isVertexDragging: false, + isDrawing: false, + isPointerOnImage: false, }; private imageMap: ImageMap; @@ -175,7 +185,7 @@ export default class Canvas extends React.Component pdfFile: null, imageUri: null, tiffImages: [], - layers: { tables : true, text: true, checkboxes: true, label: true }, + layers: {text: true, tables: true, checkboxes: true, label: true, drawnRegions: true}, }, async () => { const asset = this.state.currentAsset.asset; await this.loadImage(); @@ -192,6 +202,7 @@ export default class Canvas extends React.Component if (this.props.hoveredLabel !== prevProps.hoveredLabel) { this.imageMap.getAllLabelFeatures().map(this.updateHighlightStatus); + this.imageMap.getAllDrawnLabelFeatures().map(this.updateHighlightStatus); } } @@ -212,7 +223,7 @@ export default class Canvas extends React.Component displayName={"Delete region"} key={"Delete"} keyEventType={KeyEventType.KeyDown} - accelerators={["Shift", "Delete", "Backspace", "<", ",", ">", ".", + accelerators={["Escape", "Alt+Backspace", "Shift", "Delete", "Backspace", "<", ",", ">", ".", "{", "[", "}", "]", "+", "-", "/", "=", "_", "?"]} handler={this.handleKeyDown} /> @@ -231,16 +242,23 @@ export default class Canvas extends React.Component handleRunOcr={this.runOcr} handleAssetDeleted={this.props.onAssetDeleted} handleRunOcrForAllDocuments={this.runOcrForAllDocuments} + connectionType={this.props.project.sourceConnection.providerType} + handleToggleDrawRegionMode={this.handleToggleDrawRegionMode} + drawRegionMode={this.state.drawRegionMode} /> this.imageMap = ref} imageUri={this.state.imageUri} imageWidth={this.state.imageWidth} imageHeight={this.state.imageHeight} - enableFeatureSelection={!this.state.groupSelectMode} + enableFeatureSelection={!this.state.drawRegionMode && !this.state.groupSelectMode} handleFeatureSelect={this.handleFeatureSelect} featureStyler={this.featureStyler} groupSelectMode={this.state.groupSelectMode} + handleIsPointerOnImage={this.handleIsPointerOnImage} + isPointerOnImage={this.state.isPointerOnImage} + drawRegionMode={this.state.drawRegionMode} handleFeatureSelectByGroup={this.handleFeatureSelectByGroup} handleRegionSelectByGroup={this.handleRegionSelectByGroup} checkboxFeatureStyler={this.checkboxFeatureStyler} @@ -248,9 +266,20 @@ export default class Canvas extends React.Component tableBorderFeatureStyler={this.tableBorderFeatureStyler} tableIconFeatureStyler={this.tableIconFeatureStyler} tableIconBorderFeatureStyler={this.tableIconBorderFeatureStyler} + drawRegionStyler={this.drawRegionStyler} + drawnRegionStyler={this.drawnRegionStyler} + modifyStyler={this.modifyStyler} onMapReady={this.noOp} handleTableToolTipChange={this.handleTableToolTipChange} hoveringFeature={this.state.hoveringFeature} + addDrawnRegionFeatureProps={this.addDrawnRegionFeatureProps} + isSnapped={this.state.isSnapped} + handleIsSnapped={this.handleIsSnapped} + isVertexDragging={this.state.isVertexDragging} + handleVertexDrag={this.handleVertexDrag} + handleDrawing={this.handleDrawing} + isDrawing={this.state.isDrawing} + updateFeatureAfterModify={this.updateFeatureAfterModify} /> if (selectedRegions.length === 1 && selectedRegions[0].category === FeatureCategory.Checkbox) { this.setTagType(inputTag[0], FieldType.SelectionMark); + } else if (selectedRegions[0].category === FeatureCategory.DrawnRegion) { + selectedRegions.forEach((selectedRegion) => { + this.imageMap.removeDrawnRegionFeature(this.imageMap.getDrawnRegionFeatureByID(selectedRegion.id)); + }); } this.redrawAllFeatures(); @@ -382,6 +415,7 @@ export default class Canvas extends React.Component } private addRegionsToAsset = (regions: IRegion[]) => { + const regionsToBeKept = this.state.currentAsset.regions.filter((assetRegion) => { return regions.findIndex((r) => r.id === assetRegion.id) === -1; }); @@ -395,21 +429,36 @@ export default class Canvas extends React.Component const textRegions = regions.filter((r) => r.category === FeatureCategory.Text); const checkboxRegions = regions.filter((r) => r.category === FeatureCategory.Checkbox); - - const allTextFeatures = this.imageMap.getAllFeatures(); - const regionsNotInFeatures = textRegions.filter((region) => - allTextFeatures.findIndex((feature) => feature.get("id") === region.id) === -1); + const drawnRegions = regions.filter((r) => r.category === FeatureCategory.DrawnRegion); const imageExtent = this.imageMap.getImageExtent(); - const featuresToAdd = regionsNotInFeatures.map((region) => this.convertRegionToFeature(region, imageExtent)); - this.imageMap.addFeatures(featuresToAdd); - const allCheckboxFeatures = this.imageMap.getAllCheckboxFeatures(); - const regionsNotInCheckboxFeatures = checkboxRegions.filter((region) => - allCheckboxFeatures.findIndex((feature) => feature.get("id") === region.id) === -1); - const checkboxImageExtent = this.imageMap.getImageExtent(); - const checkboxFeaturesToAdd = regionsNotInCheckboxFeatures.map((region) => - this.convertRegionToFeature(region, checkboxImageExtent)); - this.imageMap.addCheckboxFeatures(checkboxFeaturesToAdd); + + if (textRegions.length > 0) { + const allTextFeatures = this.imageMap.getAllFeatures(); + const regionsNotInFeatures = textRegions.filter((region) => + allTextFeatures.findIndex((feature) => feature.get("id") === region.id) === -1); + const featuresToAdd = regionsNotInFeatures.map((region) => this.convertRegionToFeature(region, imageExtent)); + this.imageMap.addFeatures(featuresToAdd); + } + + if (checkboxRegions.length > 0) { + const allCheckboxFeatures = this.imageMap.getAllCheckboxFeatures(); + const regionsNotInCheckboxFeatures = checkboxRegions.filter((region) => + allCheckboxFeatures.findIndex((feature) => feature.get("id") === region.id) === -1); + const checkboxFeaturesToAdd = regionsNotInCheckboxFeatures.map((region) => + this.convertRegionToFeature(region, imageExtent)); + this.imageMap.addCheckboxFeatures(checkboxFeaturesToAdd); + } + + if (drawnRegions.length > 0) { + const allDrawnRegionFeatures = this.imageMap.getAllDrawnRegionFeatures(); + const regionsNotInDrawnRegionsFeatures = drawnRegions.filter((region) => + allDrawnRegionFeatures.findIndex((feature) => feature.get("id") === region.id) === -1); + const drawnRegionFeaturesToAdd = regionsNotInDrawnRegionsFeatures.map((region) => + this.convertRegionToFeature(region, imageExtent)); + this.imageMap.addDrawnRegionFeatures(drawnRegionFeaturesToAdd); + } + } private convertRegionToFeature = (region: IRegion, imageExtent: Extent, isOcrProposal: boolean = false) => { @@ -423,7 +472,6 @@ export default class Canvas extends React.Component Math.round((1 - boundingBox[i + 1]) * imageHeight), ]); } - const feature = new Feature({ geometry: new Polygon([coordinates]), }); @@ -472,18 +520,31 @@ export default class Canvas extends React.Component const selectedFeatures = allFeatures .filter((feature) => !feature.get("isOcrProposal")) .filter((feature) => textRegions.findIndex((region) => region.id === feature.get("id")) !== -1); - selectedFeatures.map(this.imageMap.removeFeature); + selectedFeatures.forEach((feature) => { + this.imageMap.removeFeature(feature); + }); const allCheckboxFeatures = this.imageMap.getAllCheckboxFeatures(); const selectedCheckboxFeatures = allCheckboxFeatures .filter((feature) => !feature.get("isOcrProposal")) .filter((feature) => checkboxRegions.findIndex((region) => region.id === feature.get("id")) !== -1); - selectedCheckboxFeatures.map(this.imageMap.removeCheckboxFeature); + selectedCheckboxFeatures.forEach((feature) => { + this.imageMap.removeCheckboxFeature(feature); + }); const getAllLabelledFeatures = this.imageMap.getAllLabelFeatures(); const selectedLabelledFeatures = getAllLabelledFeatures .filter((feature) => regions.findIndex((region) => region.id === feature.get("id")) !== -1); - selectedLabelledFeatures.map((feature) => this.imageMap.removeLabelFeature(feature)); + selectedLabelledFeatures.forEach((feature) => { + this.imageMap.removeLabelFeature(feature); + }); + + const getAllDrawnLabelledFeatures = this.imageMap.getAllDrawnLabelFeatures(); + const selectedDrawnLabelledFeatures = getAllDrawnLabelledFeatures + .filter((feature) => regions.findIndex((region) => region.id === feature.get("id")) !== -1); + selectedDrawnLabelledFeatures.forEach((feature) => { + this.imageMap.removeDrawnLabelFeature(feature); + }); this.redrawAllFeatures(); } @@ -502,6 +563,7 @@ export default class Canvas extends React.Component }; if (this.imageMap) { this.imageMap.removeAllLabelFeatures(); + this.imageMap.removeAllDrawnLabelFeatures(); this.addLabelledDataToLayer(regions.filter( (region) => region.tags[0] !== undefined && region.pageNumber === this.state.currentPage)); @@ -574,7 +636,6 @@ export default class Canvas extends React.Component Math.round((boundingBox[i] / ocrWidth) * imageWidth), Math.round((1 - (boundingBox[i + 1] / ocrHeight)) * imageHeight), ]); - polygonPoints.push(boundingBox[i] / ocrWidth); polygonPoints.push(boundingBox[i + 1] / ocrHeight); } @@ -685,6 +746,62 @@ export default class Canvas extends React.Component } } + private drawnRegionStyler = (feature) => { + const regionId = feature.get("id"); + // Selected + if (this.isRegionSelected(regionId)) { + return new Style({ + stroke: new Stroke({ + color: "#a3f0ff", + width: 1, + }), + fill: new Fill({ + color: "rgba(82, 226, 255, 0.4)", + }), + }); + } else { + // Unselected + return new Style({ + stroke: new Stroke({ + color: "#a3f0ff", + width: 1, + }), + fill: new Fill({ + color: "rgba(163, 240, 255, 0.2)", + }), + }); + } + } + + private drawRegionStyler = () => { + return new Style({ + image: null, + stroke: new Stroke({ + color: "#a3f0ff", + width: 1, + }), + fill: new Fill({ + color: "rgba(163, 240, 255, 0.2)", + }), + }); + } + + private modifyStyler = () => { + if (this.imageMap.props.isSnapped) { + return new Style({ + image: new Icon({ + opacity: 0.6, + scale: this.imageMap.getResolutionForZoom(4), + src: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAFwAAABcCAYAAADj79JYAAAEjklEQVR4Xu2cu89NWRTAf5+MoJhiKFCLyDQzOg06NCrjOY9Cg3hEPCIalUZkkIlH0EwymBmPUWnQMc10MxrEHyAKFAqEIMu3T5y5Oefc/ThnfXvLOt397tp7rfXba6+99t7nuxPYo0pgQlWbKcOAKweBATfgygSU1VmEG3BlAsrqLMINuDIBZXUW4QZcmYCyOotwA65MQFmdRbgBVyagrM4i3IArE1BWZxFuwJUJKKsrMcLF5o2O05/Ae2VmSepKAy72bgBOO693AJdLgl4S8Drs2Q74M6Ao6KUAb4JdTe2ioJcAvAt2cdBzB96WRnY60qeAotJLzsDH5WxhXi2gxUDPFfg42FUp6CuXVMr12ThH4KEQQ+X75BfcV27AY+HFtgsGltogN+DTgO+BM8CXQEjJNwr9BbAd+B14lwqqr/a5ARe/xKZNwM/A3sCdZAX9OLAf+CO3XWiOwCvoc4EnEcDEp9i2fQVyaz+5Ah/c8alSYMCVyRtwA95KQCqYecACYKarPF4BD1w1U8S5uHaEi751gMDzPceeAawGdgPLWobjLvALcAN47RG0VTUj5eLViIXZQ0WziCbwep08HdjmUbYtBg474D5OCvBDwL8dwlXZeRZ4o32ergW8aSf4GFgP/N0CZzlwHljkQ7om8xDYAtxpabcUuALMn4pLDA3gMdvur4FfgSU1aJI2TjiQsgOVR04JZWD2jKSbf4DNwP0G6DH2BI55u/jQwGOc+wo453K9WC5b9KPAMeBliyuzgH3AAXckIGKSm7cCz3OCPiTwGNjCZg3wVw3SQQf77Zgw+8JBP1KT+w643tIu1r6kaB8KeKwzcmAlN/I/Oa+6orTJ8dHZccEtijJLmp5YO6OhDwE8xYlvgGvAQudRV4S2OV2fIY+AtcC9DkIp9gaD7xt4qvErgFvOi//cFZpUHSGPVDVS43/rGq0Ebo/pINVub/v6BN5ktExln3q7MvgH4KL7cBOQz0+9vZkUnANcAla5dj+6z+O6qdfnktrkCTmPH9f/x+/7BC67R3kF7WTtJv1zAL4LkFfqernE6BN4NYApN+mWUrzmyf+FUvKhLZoRwFMi3crCSOAp0OsLp/STsvHpWjBTZmI0lr5z+KghMU7Z1j56OCcbxkCXXP5brZaWfuzwKmAg7HjWwRo6pdTHpA7dLiACojVFVKDbFVsKQcW2Mljygo8cbNklsiL4olVp5vCiQfVlvAHvi6RnP7kCr/K1vczpOZApYtW5tL2unELRs+3oJUDIBcDo5ir0LN7TxDSx3FJK0yWGD/S244NeLw/SUH865+ijnz77CD17CZXv09bgvnKL8MoBX4i+csFghmqQK3CfU0aRSbnOG4ppZ785A++Cbv/6PWC4NKWNUXU+C+uAJvp3nXuEd+X06rtiYFdT1n94playuAWyCVcpEd4U6fK3on4NqLQIr0O3Hxmb2mxTjvbSUko5ZFssNeDKQ2jADbgyAWV1FuEGXJmAsjqLcAOuTEBZnUW4AVcmoKzOItyAKxNQVmcRbsCVCSirswg34MoElNVZhBtwZQLK6j4AgoeUbKT4onIAAAAASUVORK5CYII=", + }), + }); + } else { + return new Style({ + image: null, + }); + } + } + private featureStyler = (feature) => { const regionId = feature.get("id"); // Selected @@ -718,14 +835,21 @@ export default class Canvas extends React.Component const tag: ITag = this.getTagFromRegionId(regionId); // Selected if (this.isRegionSelected(regionId)) { + let color; + if (selectedRegion.category === FeatureCategory.DrawnRegion) { + color = "rgba(82, 226, 255, 0.4)"; + } else if (tag.type === FieldType.SelectionMark) { + color = "rgba(255, 105, 180, 0.5)"; + } else { + color = "rgba(110, 255, 80, 0.4)"; + } return new Style({ stroke: new Stroke({ color: tag.color, width: feature.get("highlighted") ? 4 : 2, }), fill: new Fill({ - color: selectedRegion.category === FeatureCategory.Text ? "rgba(110, 255, 80, 0.4)" : - "rgba(255, 105, 180, 0.5)", + color, }), }); } else if (tag != null) { @@ -845,14 +969,25 @@ export default class Canvas extends React.Component private handleMultiSelection = (regionId: any, category: FeatureCategory) => { const selectedRegions = this.getSelectedRegions(); - if (category === FeatureCategory.Checkbox || + if (category === FeatureCategory.DrawnRegion || + (category === FeatureCategory.Label && this.state.currentAsset.regions + .find((r) => r.id === regionId).category === FeatureCategory.DrawnRegion)) + { + selectedRegions.forEach((region) => { + if (region?.category !== FeatureCategory.DrawnRegion) { + this.removeFromSelectedRegions(region.id) + } + }); + } + else if (category === FeatureCategory.Checkbox || (category === FeatureCategory.Label && this.state.currentAsset.regions .find((r) => r.id === regionId).category === FeatureCategory.Checkbox)) { selectedRegions.forEach((region) => this.removeFromSelectedRegions(region.id)); } else if (category === FeatureCategory.Text || (category === FeatureCategory.Label && this.state.currentAsset.regions .find((r) => r.id === regionId).category === FeatureCategory.Text)) { - selectedRegions.filter((region) => region.category === FeatureCategory.Checkbox) + selectedRegions.filter((region) => region.category === FeatureCategory.Checkbox || + region.category === FeatureCategory.DrawnRegion) .forEach((region) => this.removeFromSelectedRegions(region.id)); } } @@ -1113,7 +1248,7 @@ export default class Canvas extends React.Component if (formRegion.boundingBoxes) { formRegion.boundingBoxes.forEach((boundingBox, boundingBoxIndex) => { const text = this.getBoundingBoxTextFromRegion(formRegion, boundingBoxIndex); - regions.push(this.createRegion(boundingBox, text, label.label, formRegion.page)); + regions.push(this.createRegion(boundingBox, text, label.label, formRegion.page, label?.labelType)); }); } }); @@ -1130,40 +1265,50 @@ export default class Canvas extends React.Component labels: [], }; - const fieldNames = []; - - regions.forEach((r) => { - if (r.tags[0] !== undefined && - (fieldNames.find((t) => t.value === r.tags[0])) === undefined) { - fieldNames.push({ - value: r.tags[0], - category: r.category, - }); - } - }); - - fieldNames.forEach((fieldName) => { - const label: ILabel = { - label: fieldName.value, - key: null, - value: [], - }; - const regionsToConvert = regions.filter((region) => region.tags.indexOf(fieldName.value) !== -1); - regionsToConvert.forEach((region) => { - const boundingBox = region.id.split(",").map(parseFloat); - label.value.push({ - page: region.pageNumber, - text: region.value, - boundingBoxes: [boundingBox], - }); + regions.forEach((region) => { + const labelType = this.getLabelType(region.category); + const boundingBox = region.id.split(",").map(parseFloat); + const formRegion = { + page: region.pageNumber, + text: region.value, + boundingBoxes: [boundingBox], + } as IFormRegion; + region.tags.forEach((tag) => { + const label = labelData.labels.find((label) => { return label.label === tag }); + if (label) { + label.value.push(formRegion); + } else { + let newLabel; + if (labelType) { + newLabel = { + label: tag, + key: null, + labelType, + value: [formRegion], + } as ILabel; + } else { + newLabel = { + label: tag, + key: null, + value: [formRegion], + } as ILabel; + } + labelData.labels.push(newLabel); + } }); - - labelData.labels.push(label); }); - return labelData; } + private getLabelType = (regionCategory: string) => { + switch (regionCategory) { + case FeatureCategory.DrawnRegion: + return LabelType.DrawnRegion; + default: + return null; + } + } + private convertToRegionBoundingBox = (polygon: number[]) => { const xAxisValues = polygon.filter((value, index) => index % 2 === 0); const yAxisValues = polygon.filter((value, index) => index % 2 === 1); @@ -1205,6 +1350,13 @@ export default class Canvas extends React.Component } switch (keyEvent.key) { + case "Escape": + if (this.state.isDrawing) { + this.imageMap.cancelDrawing(); + } else if (this.state.isVertexDragging) { + this.imageMap.cancelModify(); + } + break; case "Shift": this.setState({ groupSelectMode: true, @@ -1212,7 +1364,27 @@ export default class Canvas extends React.Component break; case "Delete": case "Backspace": - this.deleteRegions(this.getSelectedRegions()); + if (this.state.isDrawing) { + this.imageMap.cancelDrawing(); + } else if (this.state.isVertexDragging) { + this.imageMap.cancelModify(); + } else if (keyEvent.altKey) { + const allDrawnRegionFeatures = this.imageMap.getAllDrawnRegionFeatures(); + const selectedDrawnRegions = this.getSelectedRegions().filter((selectedRegion) => { + return selectedRegion.category === FeatureCategory.DrawnRegion + }) + allDrawnRegionFeatures?.forEach((drawnRegionFeature) => { + const selectedDrawnRegionFeature = selectedDrawnRegions?.find((selectedDrawnRegion) => { + return selectedDrawnRegion.id === drawnRegionFeature.get("id"); + }); + if (selectedDrawnRegionFeature) { + this.imageMap?.removeDrawnRegionFeature(drawnRegionFeature) + this.onRegionDelete(selectedDrawnRegionFeature.id); + } + }) + } else { + this.deleteRegions(this.getSelectedRegions()); + } break; case "<": @@ -1399,8 +1571,19 @@ export default class Canvas extends React.Component } const imageExtent = this.imageMap.getImageExtent(); - const featuresToAdd = regions.map((region) => this.convertRegionToFeature(region, imageExtent)); - this.imageMap.addLabelFeatures(featuresToAdd); + const labelRegions: IRegion[] = []; + const drawnLabelRegions: IRegion[] = []; + regions.forEach((region) => { + if (region.category === FeatureCategory.DrawnRegion) { + drawnLabelRegions.push(region); + } else { + labelRegions.push(region); + } + }); + const labelFeaturesToAdd = labelRegions.map((region) => this.convertRegionToFeature(region, imageExtent)); + const drawnLabelFeaturesToAdd = drawnLabelRegions.map((region) => this.convertRegionToFeature(region, imageExtent)); + this.imageMap.addLabelFeatures(labelFeaturesToAdd); + this.imageMap.addDrawnLabelFeatures(drawnLabelFeaturesToAdd); } @@ -1439,7 +1622,7 @@ export default class Canvas extends React.Component return orderInfo; } - private getNextIdByOrder = (id: string, currentPage: number) => { + private getNextIdByOrder = (id: string, currentPage: number, index?) => { const currentIdList = this.regionOrderById[currentPage - 1]; const currentIndex = currentIdList.indexOf(id); let nextIndex; @@ -1606,7 +1789,7 @@ export default class Canvas extends React.Component features.forEach((feature) => feature.changed()); } - private createRegion(boundingBox: number[], text: string, tagName: string, pageNumber: number) { + private createRegion(boundingBox: number[], text: string, tagName: string, pageNumber: number, labelType) { const xAxisValues = boundingBox.filter((value, index) => index % 2 === 0); const yAxisValues = boundingBox.filter((value, index) => index % 2 === 1); const left = Math.min(...xAxisValues); @@ -1623,10 +1806,19 @@ export default class Canvas extends React.Component } const tag = this.props.project.tags.find((tag) => tag.name === tagName); + let regionCategory; + if (labelType) { + regionCategory = labelType; + } else if (tag.type === FieldType.SelectionMark) { + regionCategory = FeatureCategory.Checkbox; + } else { + regionCategory = FeatureCategory.Text; + } + const newRegion = { id: this.createRegionIdFromBoundingBox(boundingBox, pageNumber), type: RegionType.Polygon, - category: tag.type === FieldType.SelectionMark ? FeatureCategory.Checkbox : FeatureCategory.Text, + category: regionCategory, tags: [tagName], boundingBox: { height: bottom - top, @@ -1680,6 +1872,9 @@ export default class Canvas extends React.Component case "label": this.imageMap.toggleLabelFeatureVisibility(); break; + case "drawnRegions": + this.imageMap.toggleDrawnRegionsFeatureVisibility(); + break; } const newLayers = Object.assign({}, this.state.layers); newLayers[layer] = !newLayers[layer]; @@ -1725,6 +1920,8 @@ export default class Canvas extends React.Component this.redrawFeatures(this.imageMap.getAllFeatures()); this.redrawFeatures(this.imageMap.getAllCheckboxFeatures()); this.redrawFeatures(this.imageMap.getAllLabelFeatures()); + this.redrawFeatures(this.imageMap.getAllDrawnRegionFeatures()); + this.redrawFeatures(this.imageMap.getAllDrawnLabelFeatures()); } private needUpdateAssetRegionsFromTags = (prevTags: ITag[], tags: ITag[]) => { @@ -1813,4 +2010,121 @@ export default class Canvas extends React.Component } return selectedRegion } + + private handleToggleDrawRegionMode = () => { + this.setState({ + drawRegionMode: !this.state.drawRegionMode + }); + } + + private addDrawnRegionFeatureProps = (feature) => { + const featureCoordinates = feature.getGeometry().getCoordinates()[0]; + const {featureId, boundingBox} = this.getFeatureIDAndBoundingBox(featureCoordinates); + feature.setProperties({ + id: featureId, + text: "", + boundingbox: boundingBox, + highlighted: false, + isOcrProposal: false, + }); + feature.setId(featureId); + this.imageMap.addDrawnRegionFeatures([feature]); + + this.handleFeatureSelect(feature, false, FeatureCategory.DrawnRegion); + } + + private handleIsSnapped = (snapped: boolean) => { + if (this.state.isSnapped !== snapped) { + this.setState({ + isSnapped: snapped, + }) + } + } + + private handleVertexDrag = (dragging: boolean) => { + if (this.state.isVertexDragging !== dragging) { + this.setState({ + isVertexDragging: dragging, + }) + } + } + + private handleDrawing = (drawing: boolean) => { + if (this.state.isDrawing !== drawing) { + this.setState({ + isDrawing: drawing, + }) + } + } + + private handleIsPointerOnImage = (isPointerOnImage: boolean) => { + if (this.state.isPointerOnImage !== isPointerOnImage) { + this.setState({ + isPointerOnImage, + }); + } + } + + private getFeatureIDAndBoundingBox = (featureCoordinates) => { + const imageExtent = this.imageMap.getImageExtent(); + const ocrExtent = [0, 0, this.state.ocrForCurrentPage.readResults.width, this.state.ocrForCurrentPage.readResults.height]; + const ocrPage = this.state.currentPage; + const imageWidth = imageExtent[2] - imageExtent[0]; + const imageHeight = imageExtent[3] - imageExtent[1]; + const ocrWidth = ocrExtent[2] - ocrExtent[0]; + const ocrHeight = ocrExtent[3] - ocrExtent[1]; + const boundingBox = []; + featureCoordinates.forEach((coordinate, index) => { + boundingBox.push(coordinate[0] / imageWidth * ocrWidth); + boundingBox.push(((1 - (coordinate[1] / imageHeight)) * ocrHeight)); + }); + const polygonPoints: number[] = []; + for (let i = 0; i < boundingBox.length; i += 2) { + polygonPoints.push(boundingBox[i] / ocrWidth); + polygonPoints.push(boundingBox[i + 1] / ocrHeight); + } + const featureId = this.createRegionIdFromBoundingBox(polygonPoints, ocrPage); + return {featureId, boundingBox} + } + + private modifySelectedRegion = (existingRegionId, newRegionId) => { + const selectedRegionIndex = this.getIndexOfSelectedRegionIndex(existingRegionId); + if (selectedRegionIndex !== -1) { + this.selectedRegionIds[selectedRegionIndex] = newRegionId; + } + } + + private modifyAssetRegion = (existingRegionId, newRegionId) => { + const regionsAfterModify = this.state.currentAsset.regions.map((assetRegion) => { + if (existingRegionId === assetRegion.id) { + return { + ...assetRegion, + id: newRegionId, + boundingBox: this.convertToRegionBoundingBox(newRegionId.split(",").map(parseFloat)), + points: this.convertToRegionPoints(newRegionId.split(",").map(parseFloat)) + } as IRegion; + } else { + return assetRegion; + } + }); + this.updateAssetRegions(regionsAfterModify); + } + + private updateFeatureAfterModify = (features) => { + features.forEach((feature) => { + const originalFeatureId = feature.getId(); + const featureCoordinates = feature.getGeometry().getCoordinates()[0]; + if (this.imageMap.modifyStartFeatureCoordinates[originalFeatureId] !== featureCoordinates.join(",")) { + const {featureId, boundingBox} = this.getFeatureIDAndBoundingBox(featureCoordinates); + feature.setProperties({ + id: featureId, + boundingbox: boundingBox, + }); + feature.setId(featureId); + this.modifySelectedRegion(originalFeatureId, featureId); + this.modifyAssetRegion(originalFeatureId, featureId); + } + }); + this.imageMap.modifyStartFeatureCoordinates = {}; + } } diff --git a/src/react/components/pages/editorPage/canvasCommandBar.tsx b/src/react/components/pages/editorPage/canvasCommandBar.tsx index bbd465c78..5516d0e4b 100644 --- a/src/react/components/pages/editorPage/canvasCommandBar.tsx +++ b/src/react/components/pages/editorPage/canvasCommandBar.tsx @@ -3,7 +3,7 @@ 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 { ContextualMenuItemType } from "@fluentui/react"; +import { ContextualMenuItemType, IContextualMenuItemStyles, IContextualMenuStyles, IButtonProps, CommandBarButton, concatStyleSets, memoizeFunction, IButtonStyles, ContextualMenuItem, IContextualMenuItemProps } from "@fluentui/react"; interface ICanvasCommandBarProps { handleZoomIn: () => void; @@ -11,6 +11,9 @@ interface ICanvasCommandBarProps { handleRunOcr: () => void; handleRunOcrForAllDocuments: () => void; handleLayerChange: (layer: string) => void; + handleToggleDrawRegionMode: () => void; + drawRegionMode: boolean; + connectionType: string; handleAssetDeleted?: () => void; layers: any; } @@ -54,6 +57,16 @@ export const CanvasCommandBar: React.FunctionComponent = 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: "Label", text: strings.editorPage.canvas.canvasCommandBar.items.layers.subMenuItems.labels, @@ -65,6 +78,16 @@ export const CanvasCommandBar: React.FunctionComponent = ], }, }, + // { + // 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"], + // } ]; const commandBarFarItems: ICommandBarItemProps[] = [ diff --git a/src/react/components/pages/editorPage/editorPage.tsx b/src/react/components/pages/editorPage/editorPage.tsx index 413978da0..f32526dfb 100644 --- a/src/react/components/pages/editorPage/editorPage.tsx +++ b/src/react/components/pages/editorPage/editorPage.tsx @@ -9,11 +9,11 @@ import SplitPane from "react-split-pane"; import { bindActionCreators } from "redux"; import { PrimaryButton } from "@fluentui/react"; import HtmlFileReader from "../../../../common/htmlFileReader"; -import { strings } from "../../../../common/strings"; +import { strings, interpolate } from "../../../../common/strings"; import { AssetState, AssetType, EditorMode, FieldType, IApplicationState, IAppSettings, IAsset, IAssetMetadata, - ILabel, IProject, IRegion, ISize, ITag, FeatureCategory, + ILabel, IProject, IRegion, ISize, ITag, FeatureCategory, FieldFormat, } from "../../../../models/applicationState"; import IApplicationActions, * as applicationActions from "../../../../redux/actions/applicationActions"; import IProjectActions, * as projectActions from "../../../../redux/actions/projectActions"; @@ -488,18 +488,31 @@ export default class EditorPage extends React.Component { return ( this.imageMap = ref} imageUri={this.state.imageUri || ""} imageWidth={this.state.imageWidth} diff --git a/src/react/components/shell/keyboardShortcuts.scss b/src/react/components/shell/keyboardShortcuts.scss index b99c28026..edef1a0dc 100644 --- a/src/react/components/shell/keyboardShortcuts.scss +++ b/src/react/components/shell/keyboardShortcuts.scss @@ -78,6 +78,9 @@ border-radius: 4px; border-bottom: 1px solid rgba(255, 255, 255, 0.4); } + .key-modifier { + margin-right: 8px; + } } } } diff --git a/src/react/components/shell/keyboardShortcuts.tsx b/src/react/components/shell/keyboardShortcuts.tsx index 877ef3dc5..80ef662ca 100644 --- a/src/react/components/shell/keyboardShortcuts.tsx +++ b/src/react/components/shell/keyboardShortcuts.tsx @@ -14,6 +14,8 @@ export interface IHotKeysModalState { showModal: boolean; } interface IKeyItem { + modifierKey?: string; + secondKeyOption?: string; key: string; description: string; } @@ -63,12 +65,18 @@ export const KeyboardShortcuts: React.FC = () => { description: strings.shortcuts.zoomKeys.description.reset, }, { + secondKeyOption: strings.shortcuts.deleteAndBackspace.keys.backSpace, key: strings.shortcuts.deleteAndBackspace.keys.delete, description: strings.shortcuts.deleteAndBackspace.description.delete, }, { - key: strings.shortcuts.deleteAndBackspace.keys.backSpace, - description: strings.shortcuts.deleteAndBackspace.description.backSpace, + key: strings.shortcuts.drawnRegions.keys.escape, + description: strings.shortcuts.drawnRegions.description.cancelDrawOrReshape, + }, + { + modifierKey: "Alt", + key: strings.shortcuts.drawnRegions.keys.backSpace, + description: strings.shortcuts.drawnRegions.description.deleteSelectedDrawnRegions, }, ]; @@ -82,10 +90,6 @@ export const KeyboardShortcuts: React.FC = () => { name: strings.shortcuts.tips.renameTag.name, description: strings.shortcuts.tips.renameTag.description, }, - { - name: strings.shortcuts.tips.multipleWordSelection.name, - description: strings.shortcuts.tips.multipleWordSelection.description, - }, { name: strings.shortcuts.tips.deleteAllLabelsForTag.name, description: strings.shortcuts.tips.deleteAllLabelsForTag.description, @@ -93,17 +97,42 @@ export const KeyboardShortcuts: React.FC = () => { { name: strings.shortcuts.tips.groupSelect.name, description: strings.shortcuts.tips.groupSelect.description, - } + }, + { + name: strings.shortcuts.tips.multipleWordSelection.name, + description: strings.shortcuts.tips.multipleWordSelection.description, + }, ]; const ShortcutsListItems = ({ items }): JSX.Element => { return items.map((item, idx) => (
  • {item.description}
    + {item.secondKeyOption && + <> + + {item.secondKeyOption} + + or + + } + {item.modifierKey && + + {item.modifierKey} + + } {item.key} + > + {item.key} +
  • )); }; diff --git a/src/registerIcons.ts b/src/registerIcons.ts index baca7109c..267055bd3 100644 --- a/src/registerIcons.ts +++ b/src/registerIcons.ts @@ -70,6 +70,8 @@ export function registerIcons() { System: "\uE770", SquareShape: "\uF1A6", Merge: "\uE7D5", + AddField: "\uE4C7", + RectangleShape: "\uF1A9" }, }); }