Support supervised table labeling/training/prediction with new data schema. (#828)
* added new icons
* feat: support table tag creation
* feat: support labeling
* fix: zoomIn keyboar shortcut for macOS
* fix: appId
* fix: icons scr url (#593)
* Alex krasn/rtl refactor to functional components (#594)
* refactor to funct componenet - tableTagConfig.tsx
* tagInputItemLabel.tsx- refactor to functional component
* fix format error detected by tslint
* support dropdown options (#603)
* support dropdown options
* add format filter on cell type choice
* reset format on type change
* add default theme to dropdowns
* refactor: introduce and lift state of table body
* Alex krasn/rtl create table view styles (#620)
* styles (resize pane WIP)
* styles
* enable feature (#621)
* Alex krasn/rtl support validation of user input on config table (#611)
* -merge with changes
* feat: enable user input validation
* add strings
* fix wording
* - on save show only generic error message
* custom error message for textfield
* reset type and format of rows/columnst on save + refactor
* refactor: support interfaces and lift state for labeling
* feat: persist table labeling
* feat: enable deleting headers (#627)
* feat: table view styles (#629)
* feat: table view styles
* add styles for 'cells'
* feat: table preview (#630)
* Alex krasn/rtl eanble reordering of headers (#631)
* enable reordering
* fix checkmark and addButton styles
* pipe line test fix
* styles fix
* fix resize pane width
* fix: allow deleting and reassigning of table regions
* feat: add table label description
* fix: support selecting regions without opening table labeling
* fix: react unique keys errors
* fix: selection behaviour + refactor
* disable move btns when a selection reached list's edges
* disable delete btns when it's just one row/col
* makes inputs placeholder less visible
* support large tables in tableConfig + support resize pane (#634)
* support large tables in tableConfig + support resize pane
* cleanup
* support large tables in tableLabeling
* fix: buttons container nesting
* fix: setup initial container width, and setup min and max headers width
* add reconfigure btn to tableLabeling.tsx + some styling
* add HOW TO label the table
* add strings to tableLabeling.tsx
* disable "table" type as an option for headers type
* fix: spelling
* feat: not allow tag type change if it is tableTag
* disable tagtype and format from table tag dropdown submenu
* delete table type options for all other tags (not table)
* fix: support reconfigure button
* fix: scroll unnecessary scrollbar in tableLabeling
* refactor: rename + types
* support row dynamic table in tableTagConfig
* support table name in tableTagLabeling.tsx
* support view of row dynamic tables in tableTagLabeling.tsx table view
* add reconfig button to tag menu items
* add table tag color bar under table name in table labeling view
* add "Add row" btn for labeling row dynamic tables
* fix: wording and strings for Confirm message on reconfigure table
* fix: trim all inputted names during config validation
* fix: on resize right splitpane to width less than min allowed
* support rowDynamic table in tableLabeling table view
* fix: table labeling view styles
* add row indices for tableLabel table view
* add indices in tableConfig + support some reconfig func
* fix: dont show "Row #" for fixed tables in tableTagLabeling.tsx
* fix: restore right pane size for basic input mode
* feat: support row dynamic labeling
* fix: tableName validation erorr
* fix: show one row for rowDynmic in preview
* refactor: validateInputAndSave
* refactor: support adding rows for row dynamic tables
* refactor: support row dynamic labeling
* fix: trim rows
* merge master into RTL branch (#647)
* merge master into RTL branch
* fix an API icon
* add stings to tableConfig
* fix: typo in tableTagToAdd
* tableLabeling: refactor, styles, strings
* tableLabeling: add spanish strings
* refactor: optimize inputs validation
* fix: TableElements enum spelling
* fix: use correct table format values
* fix: spelling
* enable reconfigure button from tag menu dropdown
* refactor: remove not needed customizer
* fix: conditional Enum for headersFormatAndTypeOptions
* feat: support reconfigure table map
* feat: cross out columns when renaming
* fix: scrollbar visibilty + style adjustments
* fix: scroll issue on open
* feat: support tracking changed and deleted headers
* autofocus on last added input
* fix: delete extra colon
* fix: class name for renamed header + forgoten variable
* enable reconfigure [cancel] button
* styles for renamed headers
* display original col/row name on reconfigure
* fix: delete autofocus on btns
* fix: revert accidentally deleted ref + whitespaces
* display table name in preview
* fix: support deleted headers and style change
* feat: support reconfig deleting fields
* feat: support deleting tables
* fix margin + and Lunk styles
* restore deleted row/col: Link => ActionButton
* fix: splitpane and labelng/config styles
* add: tag color for reconfigure
* fix: tableName color for config
* feat: support saving reconfigure
* analyze results
* fix: delete red table border
* fix reconfigure name validation and toast message
* fix on reconfigure projectAction assets update
* add higlight table results in analyze
* feat: redraw canvas with changed regions
* fix: add reconfigure support for tag name change
* handle long row/col names without spaces
* temp fix for unique column names in analyzeOutput.json
* feat: handle long col and row names in tableTagConfig
* feat: enable drawn region icon and selection marks
* feat: add progress to table reconfigure
* feat: support rowKeys and columnKeys for analysis
* feat: support tracking column and row count
* fix: reconfigure progess spinner styles
* feat: highlight table cell bounding box on mouse enter
* fix: reconfigure state on close()
* fix: commnet out testing config in predictPage
* fix: check for rowKeys in updatedAssetMetadata
* fix: on reconfigure when adding columns (rdt)
* new row/col headers icons
* fix: update asset after reconfigure
* fix: check if theres tags
* fix: update document bounding boxes after reconfigure
* fix: handle region reset after reconfigure
* refactor: hardcode to preview.3 api
* feat: support rename of table tag
* merge with master as of 11/16
* feat: enable table support for labeledTags filter
* fix: table labeling issues
* fix issue of "Trained label doesn't show as manually labeled after revisioni"
(cherry picked from commit d17e8a0c7a
)
* fix: display analyze results
* Merge branch 'stew-ro/support-table-labeling' of https://github.com/microsoft/OCR-Form-Tools into rtl_merge_with_master
* support new schema for prediction
* support highlighting of tableTag and tableCell in editor page
* display table in tag view and support highlight after schema change
* feat: support row/col header selectionMark logic
* display column header after schema change in prediction results
* handle empty rows after schema change predict
* use fieldType Object and Array for new schema
* write new schema to field.json
* fix on 'definitions' undefiened on reconfigure
* support writing to field.json and removing rowKeys and columnKey logic
* fix: remove label type when invalid
* fix: use default rows and columns if field is null
* feat: support label files for new schema
* support encoding
* fix decoding and encoding logic
* fix remove label with empy value
* update taginput toolbar
* change row dynamic starting index to zero
* support reconfigure after schema change
* support drawn region logic for table labeling
* support encoding for refactoring table
* support rename and delete tag
* support empty table for analysis
* fix: support empty tables for analysis
* Fix pipeline build failures.
Co-authored-by: alex-krasn <v-alexkr@microsoft.com>
Co-authored-by: stew-ro <v-stewro@microsoft.com>
Co-authored-by: alex-krasn <64093224+alex-krasn@users.noreply.github.com>
Co-authored-by: Alex Chen <v-yongbc@microsoft.com>
Co-authored-by: Starain chen <v-stachen@microsoft.com>
Co-authored-by: Alexander Krasnorutskiy <alexk@Alexanders-MBP.domain>
This commit is contained in:
Родитель
ae4ce9a122
Коммит
c279a3f784
746
CHANGELOG.md
746
CHANGELOG.md
|
@ -1,373 +1,373 @@
|
|||
# FoTT Changelog
|
||||
## What's new in Form Recognizer?
|
||||
Click [here](https://docs.microsoft.com/en-us/azure/cognitive-services/form-recognizer/whats-new) to see what's new in Form Recognizer.
|
||||
|
||||
## Released conatiner's currently referenced commit
|
||||
2.1-Preview's released container image, tracked by the `latest-preview` image tag in our [docker hub repository](https://hub.docker.com/_/microsoft-azure-cognitive-services-custom-form-labeltool), currently references **2.1-preview.2-b6b9a2f (12-10-2020)**
|
||||
|
||||
## Commit history
|
||||
### 2.1-preview.2-b6b9a2f (12-10-2020)
|
||||
* update appVersion to 2.1.2 ([#808](https://github.com/microsoft/OCR-Form-Tools/commit/b6b9a2f131485d08541a1e85f6af59ebfbeca773))
|
||||
* add locale in prebuiltPredictPage ([#772](https://github.com/microsoft/OCR-Form-Tools/commit/06d9c16a7c1fe64a95d835878dcb8dabb8c7e485)) ([#776](https://github.com/microsoft/OCR-Form-Tools/commit/06d9c16a7c1fe64a95d835878dcb8dabb8c7e485))
|
||||
* Stew ro/cherry pick 347e21e 2b6ead7 ([#766](https://github.com/microsoft/OCR-Form-Tools/commit/ca59cee26587e1aee49507abd75c0683d67f541f))
|
||||
* Cherry pick 34ce14d a7ccb34 ([#763](https://github.com/microsoft/OCR-Form-Tools/commit/244c23df700791794990eb6f7e196bcf9ff9c844))
|
||||
* Stew ro/cherry pick ab5a8a8 abfffbb ([#760](https://github.com/microsoft/OCR-Form-Tools/commit/93b7a2d4d7688cda4baa4cfd704b2666df524174))
|
||||
* refactor: disable api version selection ([#755](https://github.com/microsoft/OCR-Form-Tools/commit/be1f18db0b7073dad106f443f343aa221dea7fc6))
|
||||
* refactor: disable draw region button ([#756](https://github.com/microsoft/OCR-Form-Tools/commit/8816a85761a795f3f0b34b87360236cadd80a735))
|
||||
* feat: support null text values in analyze results ([#744](https://github.com/microsoft/OCR-Form-Tools/commit/0ddb7f1275d6f195c2af9f0b7053987e01a5d677))
|
||||
* feat: support rowspan and column span for layout tables ([#754](https://github.com/microsoft/OCR-Form-Tools/commit/6994ac929146e25370d1461e4e502773de3d5503))
|
||||
* Update changelog ([#750](https://github.com/microsoft/OCR-Form-Tools/commit/acba3966ce960e474dfd3d97510b07c108e7b39f))
|
||||
* check whether the label data is null ([#753](https://github.com/microsoft/OCR-Form-Tools/commit/f6ef41ac1500e52f3714eda6e99187e016e1b223))
|
||||
* fix issue of 773 ([#740](https://github.com/microsoft/OCR-Form-Tools/commit/9d35e79393cdb0de678e9ec6b850daf5a4df5c96))
|
||||
|
||||
### 2.1-preview.1-2e50498 (11-09-2020)
|
||||
* fix: enable api version selection ([#736](https://github.com/microsoft/OCR-Form-Tools/commit/2e5049883bd1550ba80210edca7db4233d7a15fa))
|
||||
* fix: labeling doesn't work via shortcuts on the new project or empty tags ([#677](https://github.com/microsoft/OCR-Form-Tools/commit/f11291940b776ceb8ba7708e6f58dc2572f7b01b))
|
||||
* fix: remove setting project state in project form on change ([#732](https://github.com/microsoft/OCR-Form-Tools/commit/25eb59bfa85b755cd877b02ffda71d0cec70a106))
|
||||
* handle training state logical ([#731](https://github.com/microsoft/OCR-Form-Tools/commit/569adf161ad89106ab1fbf51429841c5955e0e4b))
|
||||
* fix issue of "After running Layout an all documents FoTT sometimes does not ends" ([#723](https://github.com/microsoft/OCR-Form-Tools/commit/6203e2cd814c95e2bef165f3fb518a566166c26d))
|
||||
* set includeTextDetails=true in prebuilt predict ([#722](https://github.com/microsoft/OCR-Form-Tools/commit/ba04cebb63a5b05a369ba954a99dfdc7c9bb9b41))
|
||||
* fix issue of "Auto-labeling while switching assets in asset preview causes an error" ([#721](https://github.com/microsoft/OCR-Form-Tools/commit/59fe4e2778a644335da9766fd1382d56086220c1))
|
||||
* feat: support api version config ([#717](https://github.com/microsoft/OCR-Form-Tools/commit/c81b2323aaa2b26b0bc0f7922de1e12445fbb627))
|
||||
* update homepage style ([#724](https://github.com/microsoft/OCR-Form-Tools/commit/fc769f41c169083098f9250c9ce18ca4881cc336))
|
||||
* issuefix: update getBoundingBox ([#730](https://github.com/microsoft/OCR-Form-Tools/commit/f0cb5db337364b2f0355928616d1f7d9637a454a))
|
||||
* clone with lodash cloneDeep ([#728](https://github.com/microsoft/OCR-Form-Tools/commit/d6bca5fcf2262a467ede781916a01f50d805b30f))
|
||||
* remain auto label state while no label data ([#727](https://github.com/microsoft/OCR-Form-Tools/commit/a79d556a3935e387b0798ecfda8de9c8b1538250))
|
||||
* deep copy asset metadata ([#725](https://github.com/microsoft/OCR-Form-Tools/commit/ba8c1100e9adf517e35ec50fd513383d9e84d630))
|
||||
* Yongbing chen/receipt predicting ([#626](https://github.com/microsoft/OCR-Form-Tools/commit/e638cd8e3be8926e966a5afc86fb53ac0f092977))
|
||||
|
||||
### 2.1-preview.1-32cfaea (11-06-2020)
|
||||
* Starain chen/clean autolabel data while training ([#712](https://github.com/microsoft/OCR-Form-Tools/commit/32cfaea023e96c8aa00560a3f30134683ee25757))
|
||||
* fix issue of deleting tag ([#703](https://github.com/microsoft/OCR-Form-Tools/commit/282d55700ea9fdf4cac2b0f20901e8ff6115819e))
|
||||
|
||||
### 2.1-preview.1-c7ed086 (11-04-2020)
|
||||
* Update README.md([#??](https://github.com/microsoft/OCR-Form-Tools/commit/c7ed08612876af8bb619a080f6740fceabb4e67c))
|
||||
* Update README.md([#??](https://github.com/microsoft/OCR-Form-Tools/commit/d696b8a25438590fb44c5159b3142b17178f25d2))
|
||||
* fix: use constant if no api version specified ([#684](https://github.com/microsoft/OCR-Form-Tools/commit/8ccdab83f079d976f6521bc08c50d917900483c0))
|
||||
* auto labeled tag design & replacing between text with draw region ([#670](https://github.com/microsoft/OCR-Form-Tools/commit/757e0dd85b3c69c6642674e48e9d3549807fecbd))
|
||||
|
||||
### 2.1-preview.1-aab6938 (11-03-2020)
|
||||
* Fix the issue that git-commit-info.txt could be override ([#683](https://github.com/microsoft/OCR-Form-Tools/commit/aab69380a8e1f7f113011a7c6b6ed406c4329555))
|
||||
* fix: use existing git hash when not in git repository ([#682](https://github.com/microsoft/OCR-Form-Tools/commit/586fbb0ce51c27ae42ca857a372e8e8d5dea21d1))
|
||||
* Stew ro/use api version selected in project settings ([#678](https://github.com/microsoft/OCR-Form-Tools/commit/bed69a3f64b0da7590ca3c54e8de369844c6bcd9))
|
||||
* refactor: change drawn region icon ([#675](https://github.com/microsoft/OCR-Form-Tools/commit/5614da2681bb8fadf9d3db3ff95aa62362d00175))
|
||||
|
||||
### 2.1-preview.1-3485d33 (10-30-2020)
|
||||
* feat: add bmp support for analyze page ([#672](https://github.com/microsoft/OCR-Form-Tools/commit/3485d33eca96321cf667c5c8eba22cc60af42e23))
|
||||
* Alex krasn/bugfix on hotkeys when canvas not loaded yet ([#664](https://github.com/microsoft/OCR-Form-Tools/commit/b0404c6276f8fe55292c929e2ca431ed31ef6442))
|
||||
|
||||
### 2.1-preview.1-7166cda (10-29-2020)
|
||||
* fix: use node to update status bar with latest git commit ([#671](https://github.com/microsoft/OCR-Form-Tools/commit/7166cdae5763a93feee52842af8e2246fedbf818))
|
||||
* change OCR to Layout in UI (Actions) ([#666](https://github.com/microsoft/OCR-Form-Tools/commit/ac604b6bd43eb4c3ba8929a97b308c833d0e6c13))
|
||||
* Yongbing chen/hitl update notify message ([#651](https://github.com/microsoft/OCR-Form-Tools/commit/0fa559a4b28c6648eaa17ec047ebb9caabbdc9c7))
|
||||
|
||||
### 2.1-preview.1-6d775ae (10-27-2020)
|
||||
* Yongbing chen/ui adjustment with designers feedback ([#662](https://github.com/microsoft/OCR-Form-Tools/commit/6d775ae8d4495ca31d110e500b86d3c0eed6a954))
|
||||
|
||||
### 2.1-preview.1-c86b6de (10-23-2020)
|
||||
* Fix the issue that git-commit-info.txt could be override ([#668](https://github.com/microsoft/OCR-Form-Tools/commit/c86b6de35ecd5d004dfb64f8f857d06f0557a00d))
|
||||
* Xinxl/fix hash ([#667](https://github.com/microsoft/OCR-Form-Tools/commit/cb27cbd74ff890dc7e13865d89ca5e16b0807fbb))
|
||||
|
||||
### 2.1-preview.1-0aae169 (10-22-2020)
|
||||
* Alex krasn/fix confidence level bar styles ([#657](https://github.com/microsoft/OCR-Form-Tools/commit/0aae1690351f3de27114e6cbebd2c077be8e9016))
|
||||
|
||||
### 2.1-preview.1-d644459 (10-21-2020)
|
||||
* refactor: change error styling and wording for project sharing ([#653](https://github.com/microsoft/OCR-Form-Tools/commit/d644459e4c9b1f82b1ed2d5b537960b0f16184da))
|
||||
* fix: sort models after loading next page in model compose ([#659](https://github.com/microsoft/OCR-Form-Tools/commit/9818d6301ef613155951381598f9ad4cf8ff6e3c))
|
||||
* Alex krasn/serialize javascript vulnerability ([#612](https://github.com/microsoft/OCR-Form-Tools/commit/66b03303b1325634371ebdb3923acaa6722be89f))
|
||||
* update asset labelingState when load local project ([#660](https://github.com/microsoft/OCR-Form-Tools/commit/1aa3daaeeb1c8a4773e7b6236fc6462335e410f9))
|
||||
|
||||
### 2.1-preview.1-28c54fc (10-20-2020)
|
||||
* fix: check for local connections ([#654](https://github.com/microsoft/OCR-Form-Tools/commit/28c54fcc31defe1c4ebcf685675768b99c8e00c8))
|
||||
* get last commit hash code in current branch and show on status bar ([#642](https://github.com/microsoft/OCR-Form-Tools/commit/88c547995d31f945177da70141f997e441b3259c))
|
||||
* new feature: tags in current page ([#640](https://github.com/microsoft/OCR-Form-Tools/commit/af5396fe8e63b88b90d16953e17ce2006afe782e))
|
||||
|
||||
### 2.1-preview.1-6c1ee2b (10-16-2020)
|
||||
* adjust editor view offset ([#646](https://github.com/microsoft/OCR-Form-Tools/commit/6c1ee2b6b4f1bcf28b1c9081b21f0a8783518c80))
|
||||
|
||||
### 2.1-preview.1-b92e4b3 (10-15-2020)
|
||||
* reword asset states ([#644](https://github.com/microsoft/OCR-Form-Tools/commit/b92e4b3d5a786a852c319c05697eea331c147cee))
|
||||
|
||||
### 2.1-preview.1-4544e52 (10-14-2020)
|
||||
* feat: support apiVersion selection from project settings ([#641](https://github.com/microsoft/OCR-Form-Tools/commit/4544e5255cf2356a4ddf353f7a63994c1a0865da))
|
||||
|
||||
### 2.1-preview.1-94f12bb (10-13-2020)
|
||||
* new feature: highlight current tag ([#628](https://github.com/microsoft/OCR-Form-Tools/commit/94f12bb4e925a86fdfba8e25d8b0346169daea1e))
|
||||
* new feature: human in the loop auto labeling ([#571](https://github.com/microsoft/OCR-Form-Tools/commit/c1f227daa3decd52320f58d151755b206280cedd))
|
||||
|
||||
### 2.1-preview.1-7d1f871 (10-10-2020)
|
||||
* Update CHANGELOG.md([#??](https://github.com/microsoft/OCR-Form-Tools/commit/7d1f87193b3917f2140ab9bcce04c64e7aceb823))
|
||||
|
||||
### 2.1-preview.1-1f33130 (10-09-2020)
|
||||
* fix: support image map interactions for container releases([#639](https://github.com/microsoft/OCR-Form-Tools/commit/e015973aee152b8a8b22fc2fe32ce80bdd2b46ea))
|
||||
|
||||
### 2.1-preview.1-6d4e93b (10-07-2020)
|
||||
* Fix: use file type library for mime type validation ([#636](https://github.com/microsoft/OCR-Form-Tools/commit/6d4e93bca8a4e3d677c765ed5596bde502766e2e))
|
||||
|
||||
### 2.1-preview.1-355ca0b (09-30-2020)
|
||||
* feat: add spinner in saving project, can avoid multiple commit ([#617](https://github.com/microsoft/OCR-Form-Tools/commit/355ca0b156b2d44aafd2eaaccf2fc52385c7f5f8))
|
||||
|
||||
### 2.1-preview.1-53044f7 (09-29-2020)
|
||||
* fix: refresh currentProjects when load project ([#615](https://github.com/microsoft/OCR-Form-Tools/commit/53044f72dd9c9c72557c74c00605ba05ee50205d))
|
||||
* sync related region color when tag color changed ([#598](https://github.com/microsoft/OCR-Form-Tools/commit/3044cc51a9166877bb4f01f28753171b82c04ccd))
|
||||
* feat: add current list item style ([#601](https://github.com/microsoft/OCR-Form-Tools/commit/3e503e75513e44e6a90bd013d8dd15c3096cd7e9))
|
||||
* fix: remove project from app if security token does not exist ([#468](https://github.com/microsoft/OCR-Form-Tools/commit/730e1963a06f038a4efa9750fcef4be6f15a8460))
|
||||
|
||||
### 2.1-preview.1-d859d38 (09-27-2020)
|
||||
* fix ,update document state when preview (#317) ([#471](https://github.com/microsoft/OCR-Form-Tools/commit/d859d38ecc1f96b194ffa130a1840f5a7d9b1a9b))
|
||||
* refactor: change the confidence value format to percentage ([#461](https://github.com/microsoft/OCR-Form-Tools/commit/e806b4e0dfcc68e6408e2130a46a318637a482a8))
|
||||
|
||||
### 2.1-preview.1-7a3f7a7 (09-25-2020)
|
||||
* security: upgrade node-forge ([#622](https://github.com/microsoft/OCR-Form-Tools/commit/7a3f7a773c8b01f443afaad89d7974a5bbb0b869))
|
||||
* fix: disable move tag and support renaming when searching ([#618](https://github.com/microsoft/OCR-Form-Tools/commit/cac1e8e6cfb2805a6540f9e80d564a0ff8be81c7))
|
||||
|
||||
### 2.1-preview.1-4163edc (09-23-2020)
|
||||
* docs: add latest tag reference to changelog ([#608](https://github.com/microsoft/OCR-Form-Tools/commit/4163edc18bc65234e263703fc829d2f297953385))
|
||||
* fix: use region instead of drawnRegion for labelType in label file ([#582](https://github.com/microsoft/OCR-Form-Tools/commit/ffafc200249a1c47698fedb279b4b55cef0190ba))
|
||||
* docs: update readme with docker hub info ([#604](https://github.com/microsoft/OCR-Form-Tools/commit/63bbea076d598d0286095fa0eca48d8c9d0ed706))
|
||||
* fix: remove opening browser for yarn start ([#605](https://github.com/microsoft/OCR-Form-Tools/commit/f6c4dc3585df71d09252a28f65e835a594389118))
|
||||
* fix: update changelog updater script ([#607](https://github.com/microsoft/OCR-Form-Tools/commit/7c4848c3a72259562c0461f0e2eadfb4a660fa64))
|
||||
|
||||
### 2.1-preview.1-f2db74e (09-17-2020)
|
||||
* docs: udpate changlog with docker image reference ([#590](https://github.com/microsoft/OCR-Form-Tools/commit/f2db74e322c32338eba3b2df06c01a51cfb7ebc1))
|
||||
|
||||
### 2.1-preview.1-1a6b78e (09-16-2020)
|
||||
* fix: normalize folder path starting with a period ([#592](https://github.com/microsoft/OCR-Form-Tools/commit/1a6b78e054235da3188aafbe65636a8c18b439bf))
|
||||
* fix: change label folder uri title ([#588](https://github.com/microsoft/OCR-Form-Tools/commit/7e4233e568d94817e23dda5ef5513b9ee7475d11))
|
||||
|
||||
### 2.1-preview.1-6a1ced5 (09-15-2020)
|
||||
* fix: initialize drag pan for analyze page ([#586](https://github.com/microsoft/OCR-Form-Tools/commit/6a1ced5a0bfb03ceba515faddbfa010ac8451460))
|
||||
* fix: zoomIn keyboar shortcut for macOS ([#581](https://github.com/microsoft/OCR-Form-Tools/commit/5afeebfee28e10e390f073990f90348c5117475f))
|
||||
* fix: appId ([#584](https://github.com/microsoft/OCR-Form-Tools/commit/e053b151441e956641ed05c29106d02358a40792))
|
||||
* fix: remove escape quote from release script ([#579](https://github.com/microsoft/OCR-Form-Tools/commit/bd5d51e8e15809b95f15bc495f7d0f91fecfc22d))
|
||||
* Stew ro/support drag pan for release ([#576](https://github.com/microsoft/OCR-Form-Tools/commit/77620eccd21d564473c81b43341f59de22339248))
|
||||
|
||||
### 2.1-preview.1-0633507 (09-14-2020)
|
||||
* Update README.md([#??](https://github.com/microsoft/OCR-Form-Tools/commit/0633507aa767f996add313ced06c2365c5f240c8))
|
||||
|
||||
### 2.1-preview.1-8d2286f (09-13-2020)
|
||||
* persist trainPage inputs in localStorage ([#568](https://github.com/microsoft/OCR-Form-Tools/commit/8d2286f50236e41fe5540dbb9b161ea88bbf2d7a))
|
||||
|
||||
### 2.1-preview.1-bb23e31 (09-11-2020)
|
||||
* build(deps): bump node-fetch from 2.6.0 to 2.6.1 ([#575](https://github.com/microsoft/OCR-Form-Tools/commit/bb23e3199c5721338241c8c5ccc0bda104fd15f8))
|
||||
* fix: support multiple env files ([#574](https://github.com/microsoft/OCR-Form-Tools/commit/cf64a8ddde05e7e73cad37d271f5d6dfa61c5d7f))
|
||||
* fix: "Azure blob storage" error on on premise scenario ([#572](https://github.com/microsoft/OCR-Form-Tools/commit/46f0bc59f3a531c366bc2c7cec955d2cb6ed7cd6))
|
||||
* fix ([#563](https://github.com/microsoft/OCR-Form-Tools/commit/28c792e10692e3cc1f511852ffc9fdbc8dcdda8a))
|
||||
|
||||
### 2.1-preview.1-7e828ff (09-10-2020)
|
||||
* fix: allow training with placeholder ([#569](https://github.com/microsoft/OCR-Form-Tools/commit/7e828ff02ff8a22b64b4b7d16787d77afb76af62))
|
||||
* docs: update changelog ([#564](https://github.com/microsoft/OCR-Form-Tools/commit/1dec72c5df3206554a1e0864b65cc769835785fd))
|
||||
* Yongbing chen/human in the loop ([#517](https://github.com/microsoft/OCR-Form-Tools/commit/be9d56481510e3033bcd705743c1ee9aeee20522))
|
||||
* fix: support project folder in project settings for local file system ([#559](https://github.com/microsoft/OCR-Form-Tools/commit/b92b73bb8076f9b9bb55dd38fcd223b7b93eaa2e))
|
||||
* feat: enable canvas rotation ([#553](https://github.com/microsoft/OCR-Form-Tools/commit/c27a110251df1fc7a595524846e20fd09c79f915))
|
||||
* fix: handle tag is undefined error ([#557](https://github.com/microsoft/OCR-Form-Tools/commit/7e4d3fbbc3a2bf925c126cd2b3f493cca48e7a62))
|
||||
|
||||
### 2.1-preview.1-193520e (09-08-2020)
|
||||
* fix: accept selection of only .fott files for open local project ([#554](https://github.com/microsoft/OCR-Form-Tools/commit/193520e2c3e40b58c3612507efc2249aaf4e9d05))
|
||||
* fix: use default shared folder for label URI when training ([#551](https://github.com/microsoft/OCR-Form-Tools/commit/656de2ff07c2083affc2adf52f1a56c5a9c024b8))
|
||||
* fix: show label folder uri while training ([#539](https://github.com/microsoft/OCR-Form-Tools/commit/0ad389c06328cd6428653bb7d94d5af716e02ab7))
|
||||
* feat: add canvas command bar to analyze page with only zoom buttons ([#549](https://github.com/microsoft/OCR-Form-Tools/commit/895b52740cd1e8d61b5b021e6c0d992f44ce8052))
|
||||
|
||||
### 2.1-preview.1-4852c84 (09-05-2020)
|
||||
* fix buttons styles - makes them more visible ([#526](https://github.com/microsoft/OCR-Form-Tools/commit/4852c8429d25b5569c3335b014da5972cbcc6162))
|
||||
|
||||
### 2.1-preview.1-343ea16 (09-04-2020)
|
||||
* refactor: remove array for drawn region labels ([#542](https://github.com/microsoft/OCR-Form-Tools/commit/343ea16e18199ab5098395ae8b7a164cd8bab55e))
|
||||
* fix: add key prop to region icon ([#540](https://github.com/microsoft/OCR-Form-Tools/commit/87b69093f2d35d91a2a939c46ac66ba4d22a5cb7))
|
||||
|
||||
### 2.1-preview.1-b370c9a (09-02-2020)
|
||||
* fix: resize canvas on asset preview resize ([#535](https://github.com/microsoft/OCR-Form-Tools/commit/b370c9a9bcf7da416944c626f6d4fd7bd29088bb))
|
||||
|
||||
### 2.1-preview.1-de1c304 (08-31-2020)
|
||||
* refactor: upgrade tsconfig es2017 to esnext ([#531](https://github.com/microsoft/OCR-Form-Tools/commit/de1c30410b860c9576108f076ec4dd8273e61a79))
|
||||
|
||||
### 2.1-preview.1-530545c (08-28-2020)
|
||||
* fix: remove existing bounding boxes from document on analyze ([#523](https://github.com/microsoft/OCR-Form-Tools/commit/6a1aedfb89b0499a0f4782e16ccbd8a06887841d))
|
||||
* feat: enable download JSON of trained model ([#513](https://github.com/microsoft/OCR-Form-Tools/commits/master))
|
||||
|
||||
### 2.1-preview.1-529a0e8 (08-27-2020)
|
||||
* fix: show loading indicator while loading model info ([#514](https://github.com/microsoft/OCR-Form-Tools/commit/529a0e819f4cb405e290f34d18d15c487a7bcfad))
|
||||
* docs: update telemetry disclaimer ([#521](https://github.com/microsoft/OCR-Form-Tools/pull/521))
|
||||
* fix: disable clearing of drawn regions on analyze page ([#518](https://github.com/microsoft/OCR-Form-Tools/commit/298d7c97da1278996d2ee6020d3face0785bc4eb))
|
||||
|
||||
### 2.1-preview.1-b2d9a0b (08-26-2020)
|
||||
* docs: notice that telemetry is disabled ([#501](https://github.com/microsoft/OCR-Form-Tools/commit/b2d9a0b008ebf350dfcb5fe897fc5dfe0d4d5cb6))
|
||||
|
||||
### 2.1-preview.1-d9db4ee (08-24-2020)
|
||||
* refactor: upgrade storage-blob to v12.1.2 ([#509](https://github.com/microsoft/OCR-Form-Tools/commit/d9db4ee027240a82feef5b54e5e406c3793d8050))
|
||||
* feat: support region labeling ([#481](https://github.com/microsoft/OCR-Form-Tools/commit/dd78ed06761a341908bdb1b09e73fd1f2868431c))
|
||||
* feat: support adding model to recent models from compose page ([#510](https://github.com/microsoft/OCR-Form-Tools/commit/65fc92b5737ceea14ff89aa78052be26835ad0ae))
|
||||
|
||||
### 2.1-preview.1-2402cba (08-17-2020)
|
||||
* fix: notify error message when open project with invalid security token ([#506](https://github.com/microsoft/OCR-Form-Tools/commit/2402cbaf73eba47ad188f851227c04cd44a208d4))
|
||||
|
||||
### 2.1-preview.1-a8ef8fa (08-17-2020)
|
||||
* fix: don't allow create or update connection with duplicate name ([#486](https://github.com/microsoft/OCR-Form-Tools/commit/a8ef8fab603b3d2c08c533cb5dfe67da117942a0))
|
||||
|
||||
### 2.1-preview.1-530545c (08-14-2020)
|
||||
* fix: "failed to fetch()" error ([#491](https://github.com/microsoft/OCR-Form-Tools/commit/530545c7cd2b4a3ff444e9c7e1f40c68d4a7376c))
|
||||
* fix: sync layer visibility ([#497](https://github.com/microsoft/OCR-Form-Tools/commit/bea552b28acb9b652ffaedf40009d6df5a3197ef))
|
||||
* refactor: disable telemetry service ([#498](https://github.com/microsoft/OCR-Form-Tools/commit/6e3628cf174f954693380aab6ebd2dabe027ac6d))
|
||||
* fix: change share class name for adblocker chrome extension ([#492](https://github.com/microsoft/OCR-Form-Tools/commit/aa8a73afc6344f3164e79f236d5fa4bb0f64d364))
|
||||
|
||||
### 2.1-preview.1-da405b3 (08-10-2020)
|
||||
* fix: restrict tag type through hot keys ([#482](https://github.com/microsoft/OCR-Form-Tools/commit/da405b354428b829e895a35a020736b1d88c153f))
|
||||
* docs: add share project description to README ([#488](https://github.com/microsoft/OCR-Form-Tools/commit/7ee215f735a84aaa30201748d19207bcc6a05580))
|
||||
|
||||
### 2.1-preview.1-29d1f93 (08-07-2020)
|
||||
* fix: handle multi selection of non-compatible types with multi-selection tool ([#487](https://github.com/microsoft/OCR-Form-Tools/commit/29d1f93a290e55fdd84f8cf2ee9a914fed702beb))
|
||||
|
||||
### 2.1-preview.1-cef225f (08-06-2020)
|
||||
* fix: handle undefined image map error ([#462](https://github.com/microsoft/OCR-Form-Tools/commit/cc9e9bfc8fe00bb0ed154edb791446f28060af4e))
|
||||
* fix: handle undefined image map error ([#479](https://github.com/microsoft/OCR-Form-Tools/commit/cef225f3346628e79c46e799303400965f1d3c96))
|
||||
|
||||
### 2.1-preview.1-76945df (08-05-2020)
|
||||
* fix: use english for telemetry reporting ([#472](https://github.com/microsoft/OCR-Form-Tools/commit/76945df3bdf9caba3ba13f4541e17e75b9574b33))
|
||||
* fix: resolve unhandled exeptions and new message for OCR service on 400 ([#470](https://github.com/microsoft/OCR-Form-Tools/commit/76381bc659a365ead19387b933485530d2d5edc3))
|
||||
* feature: enable popup with composed model info ([#460](https://github.com/microsoft/OCR-Form-Tools/commit/c1f5d803f047e5ca0d18fea6383b3baf56d116ff))
|
||||
|
||||
### 2.1-preview.1-f4d53ce (08-03-2020)
|
||||
* fix: bump elliptic from 6.5.2 to 6.5.3 ([#469](https://github.com/microsoft/OCR-Form-Tools/commit/f4d53cec967194445885bd3748096f0a3ce10715))
|
||||
* feat: add modelCompose icon and created time ([#466](https://github.com/microsoft/OCR-Form-Tools/commit/2fa32ef5f77ec7bb44bf42e9fc0a5fdf7f0330c3))
|
||||
|
||||
### 2.1-preview.1-78996ea (07-31-2020)
|
||||
* refactor: relocate share button ([#464](https://github.com/microsoft/OCR-Form-Tools/commit/78996ea65616b28d7471b59f4f16f254d7d33127))
|
||||
|
||||
### 2.1-preview.1-0e1b637 (07-29-2020)
|
||||
* feat: show only ready models in the list ([#459](https://github.com/microsoft/OCR-Form-Tools/commit/0e1b637003f289c56955342f44963003c1543436))
|
||||
|
||||
### 2.1-preview.1-84f8285 (07-27-2020)
|
||||
* fix: show message on model composition fail ([#457](https://github.com/microsoft/OCR-Form-Tools/commit/84f82859122ff298bcfcca78e821e8bfe437bb78))
|
||||
* refactor: add background on popup table ([#446](https://github.com/microsoft/OCR-Form-Tools/commit/27f60df5617da2efba8ffdd601233e0c0f4c8e3e))
|
||||
|
||||
### 2.1-preview.1-79264e3 (07-24-2020)
|
||||
* fix: handle rejection for security token not found when opening projects ([#441](https://github.com/microsoft/OCR-Form-Tools/commit/79264e3fddfb2c80b88bf8ca21df1e869082ffcf))
|
||||
* fix: show more refined error message for model not found analysis error ([#454](https://github.com/microsoft/OCR-Form-Tools/commit/1cb4133dca0092559e7524dfad8c0bf54502dc81))
|
||||
* feat: support group selection of words with drawn bounding box ([#447](https://github.com/microsoft/OCR-Form-Tools/commit/b4332a926b1925024a33731a90d303c0b171935b))
|
||||
* feat: add apiVersion to telemetry ([#448](https://github.com/microsoft/OCR-Form-Tools/commit/55be5427e4a2f9c8cf393d446049527c55f841d4))
|
||||
* fix: margin for filenames in asset preview ([#451](https://github.com/microsoft/OCR-Form-Tools/commit/fe8258f9c7ceba663a66708b19bc0e6556e777ad))
|
||||
* docs: add telemetry disclaimer to readme ([#449](https://github.com/microsoft/OCR-Form-Tools/commit/87356a1cf6678bb9494e83178bf6282ca366921f))
|
||||
|
||||
### 2.1-preview.1-9b5b99d (07-23-2020)
|
||||
* docs: add get-sas.png (https://github.com/microsoft/OCR-Form-Tools/commit/9b5b99d5468661481ae8165593d5a74471366429)
|
||||
* doc: add a screenshot of getting SAS token (https://github.com/microsoft/OCR-Form-Tools/commit/87b1062125ed106ff73c036e33f1bf7a5f2c3def)
|
||||
* fix: handle undefined error for pdf asset preview memory cleaning ([#442](https://github.com/microsoft/OCR-Form-Tools/commit/9b5b99d5468661481ae8165593d5a74471366429))
|
||||
* fix: remove duplicate models in model composed model list ([#439](https://github.com/microsoft/OCR-Form-Tools/commit/7fcc9ccfdb6634326ddd6cbfe99b423300b94131))
|
||||
* feat: enable internal telemetry ([#431](https://github.com/microsoft/OCR-Form-Tools/commit/41294c8aa19c82643fe0df669c21a0112668e0dd))
|
||||
|
||||
### 2.1-preview.1-f4b4d5d (07-21-2020)
|
||||
* fix: use table for model selection info ([#438](https://github.com/microsoft/OCR-Form-Tools/commit/f4b4d5ded4b7e0ff2116ba3b8f97e49fbf30b7c0))
|
||||
* fix: reset model name after training ([#434](https://github.com/microsoft/OCR-Form-Tools/commit/ed919a016b150d0938aee25b5550bacf29f04e83))
|
||||
* fix: wait for loadeding project with sharing project ([#435](https://github.com/microsoft/OCR-Form-Tools/commit/fc4cb96d2a9d0920c3bbbd9c2000fb4b1b7ac9c0))
|
||||
|
||||
### 2.1-preview.1-46dbb2b (07-20-2020)
|
||||
* fix: handle no recent models for model compose ([#432](https://github.com/microsoft/OCR-Form-Tools/commit/46dbb2be9ee6100a8f3e6a443ad5e734c60954bb))
|
||||
* refactor: use new model compose icon ([#425](https://github.com/microsoft/OCR-Form-Tools/commit/932fb3fd7f84636e97035f4cafadc87cff18b3b3))
|
||||
* fix: support long model names for model selection ([#427](https://github.com/microsoft/OCR-Form-Tools/commit/a0fa2daf4cd3286f7f58dc2919fd202115e8d5be))
|
||||
* feat add recent models to top of model compose page's list ([#430](https://github.com/microsoft/OCR-Form-Tools/commit/cf8de6be61b95bfe8c937946df71ea81aecb35f9))
|
||||
* fix: check valid connection ([#428](https://github.com/microsoft/OCR-Form-Tools/commit/9cb6c5830afddc9317ffdfe6927b581c4d39ba39))
|
||||
|
||||
### 2.1-preview.1-162a766 (07-17-2020)
|
||||
* refactor: make confidence results same as JSON results ([#409](https://github.com/microsoft/OCR-Form-Tools/commit/162a7660cfe32b72c4954a147269c5d2b7f55a08))
|
||||
* fix: prevent user from leaving page while composing ([#422](https://github.com/microsoft/OCR-Form-Tools/commit/63e179d0152d2f8f2ee764443785efa24e5f7dce))
|
||||
* feat: support model selection ([#419](https://github.com/microsoft/OCR-Form-Tools/commit/b4c4cc5a8a980aaa6530e7a4a5a1c43e77494c75))
|
||||
* feat: share project ([#344](https://github.com/microsoft/OCR-Form-Tools/commit/d059580cfefa053670c45c5d8ec7bf250bc4db27))
|
||||
|
||||
### 2.1-preview.1-89be3ac (07-15-2020)
|
||||
* fix: on assetFormat undefined ([#413](https://github.com/microsoft/OCR-Form-Tools/commit/89be3ac5b614e91607d7fb8065ad32b69886040d))
|
||||
* fix: make sure token names are unique ([#404](https://github.com/microsoft/OCR-Form-Tools/commit/d8fa6141cff4d00ba22e95ef4f5dcc9102e1c1c2))
|
||||
* fix: model info enclosing element error on [#407](https://github.com/microsoft/OCR-Form-Tools/issues/407) ([#408](https://github.com/microsoft/OCR-Form-Tools/commit/8cc421c3fee0e781211efb0aeb2b345075012daa))
|
||||
* fix: display composed icon for composed model with attribute ([#399](https://github.com/microsoft/OCR-Form-Tools/commit/18fb4d71052b9355c8d5a4f7dde956ba17ca30fa))
|
||||
|
||||
### 2.1-preview.1-b67191c (07-09-2020)
|
||||
* fix: don't allow choosing not-ready models for compose ([#394](https://github.com/microsoft/OCR-Form-Tools/commit/b67191cdbc872b9004be30aa4b4dfde9a88dfe37))
|
||||
* feat: track five most recent project models ([#395](https://github.com/microsoft/OCR-Form-Tools/commit/05850603d51a6786c8b6e8b4a553db020df56158))
|
||||
|
||||
### 2.1-preview.1-abc6376 (07-08-2020)
|
||||
* feat: enable model info in analyze results ([#383](https://github.com/microsoft/OCR-Form-Tools/commit/abc63767e97dd28a6bb9028e03f2225e6ac0f1ab))
|
||||
* fix: check invalid provider options before project actions ([#390](https://github.com/microsoft/OCR-Form-Tools/commit/212647d4327d9e18e9248a2d39086eeaab404979))
|
||||
|
||||
### 2.1-preview.1-a334cfc (07-07-2020)
|
||||
* fix: hide extra scrollbars for model compose view ([#380](https://github.com/microsoft/OCR-Form-Tools/commit/a334cfc45fc5ab137682ad2b48dd0ec1585055dc))
|
||||
* fix: handle version change state mutation error ([#382](https://github.com/microsoft/OCR-Form-Tools/commit/8991cc0c92f2f5cbd226f7e1c5c0825b7af8937c))
|
||||
* fix: handle pdf worker terminated error ([#381](https://github.com/microsoft/OCR-Form-Tools/commit/adc0498c31bfd5ba57ab98c373e73575589ab1e1))
|
||||
|
||||
### 2.1-preview.1-7192170 (07-02-2020)
|
||||
* feat: support release ([#361](https://github.com/microsoft/OCR-Form-Tools/commit/7192170d73d24a43e7fff18cd2c6bae7f208f1b0))
|
||||
|
||||
### 2.1-preview.1-978dabc (07-01-2020)
|
||||
* feat: support document management ([#374](https://github.com/microsoft/OCR-Form-Tools/commit/978dabc3ba877ed4215865cba2a583fb785a2894))
|
||||
|
||||
### 2.1-preview.1-56a4b89 (06-30-2020)
|
||||
* fix: wait until composed model is ready ([#369](https://github.com/microsoft/OCR-Form-Tools/commit/56a4b89f370f2fd72c6bc275376205e7fffe6a9e))
|
||||
|
||||
### 2.1-preview.1-6114d64 (06-23-2020)
|
||||
* fix: update OCR version ([#335](https://github.com/microsoft/OCR-Form-Tools/commit/6114d6456b27a59335e534eef72cefd1b2f15737))
|
||||
* feat: support electron for on premise solution ([#333](https://github.com/microsoft/OCR-Form-Tools/commit/ca0bd0c2ab46b7b587e5bfbc60c29b62bb325297))
|
||||
|
||||
### 2.1-preview.1-8297b18 (06-19-2020)
|
||||
* refactor: put api version in constants ([#332](https://github.com/microsoft/OCR-Form-Tools/commit/8297b18a084be86bc4c986a1a332cb40bd807d1b))
|
||||
|
||||
### 2.1-preview.1-3b7f803 (06-18-2020)
|
||||
* feat: enable model compose (preview) ([#328](https://github.com/microsoft/OCR-Form-Tools/commit/3b7f803407b82191706120bb9f12b82de1955704))
|
||||
* fix: quick reordering tags ([#322](https://github.com/microsoft/OCR-Form-Tools/commit/3cc5267ef8617590adb3d4966f75cfed64604f00))
|
||||
* feat: localization for canvas commandbar items ([#319](https://github.com/microsoft/OCR-Form-Tools/commit/253b9c90eb4923e7fde015a7216905fa32a8dcfa))
|
||||
* feat: enable re-run OCR ([#297](https://github.com/microsoft/OCR-Form-Tools/commit/cbe9b0ed1c48f54c100b31b7f04706a969df2dd5))
|
||||
* fix: capitalize python in analyze page ([#320](https://github.com/microsoft/OCR-Form-Tools/commit/96626636a96a3d19030df283ac794fa9c2aab18c))
|
||||
* fix: fix spelling correction for string match ([#318](https://github.com/microsoft/OCR-Form-Tools/commit/28e53cefcf0bb462d547d6e38b24c480c03b946f))
|
||||
* feature: keep prediction in UI ([#285](https://github.com/microsoft/OCR-Form-Tools/commit/dad98b9bd1d305a6bfeb2846ef4067da186ff801))
|
||||
|
||||
### 2.0.0-1c39800 (06-05-2020)
|
||||
* feat: add description - how to delete info ([#292](https://github.com/microsoft/OCR-Form-Tools/commit/1c39800b1152f186dfc19834bb969abbc4fe0ac2))
|
||||
* feat: enable download analyze script ([#304](https://github.com/microsoft/OCR-Form-Tools/commit/9c97ed0ff9b0aa72ec9a197fc92f3a5998135c36))
|
||||
* fix: check ocrread results before getting image extent ([#296](https://github.com/microsoft/OCR-Form-Tools/commit/61dba02fc6f19eb854e1f499e475b1336e6171b9))
|
||||
* feat: Add better error message for CORS ([#289](https://github.com/microsoft/OCR-Form-Tools/commit/8f210792b4d84e424b00499efb540b0e27e9fdad))
|
||||
|
||||
### 2.0.0-2760166 (05-30-2020)
|
||||
* fix: fix mime check bug for jpeg/jpg and tiff ([#291](https://github.com/microsoft/OCR-Form-Tools/commit/2760166bcb809bbfdc207b01db49f00153318624))
|
||||
* refactor: simplify shortcut descriptions ([#277](https://github.com/microsoft/OCR-Form-Tools/commit/db95b0e2510f6cef9bc7279fe0a19dce239c816e))
|
||||
|
||||
### 2.0.0-a5e4e07 (05-21-2020)
|
||||
* feature: show table view when table icon is clicked ([#271](https://github.com/microsoft/OCR-Form-Tools/commit/a5e4e079d4c0d1c7c52e3b015c0ddf9b8601bbf2))
|
||||
|
||||
### 2.0.0-814276a (05-20-2020)
|
||||
* fix: modify skip button according to feedback comments ([#264](https://github.com/microsoft/OCR-Form-Tools/commit/814276af6f4259844854798adf0c56bd606b2363))
|
||||
* feature: keyboard shortcuts and tips ([#258](https://github.com/microsoft/OCR-Form-Tools/commit/37aa859a80dc0213a118313558ad21ba424008e7))
|
||||
* feat: add electron mode from VoTT project ([#260](https://github.com/microsoft/OCR-Form-Tools/commit/2a3383d4a0f100a39ed40627bdffb9b48f78f5df))
|
||||
* refactor: use forEach instead of map in handleFeatureSelect ([#259](https://github.com/microsoft/OCR-Form-Tools/commit/c1c590c463743d187fda2429a628e27c6c42012f))
|
||||
|
||||
### 2.0.0-0061645 (05-13-2020)
|
||||
* build: update nginx base image version to 1.18.0-alpine ([#255](https://github.com/microsoft/OCR-Form-Tools/commit/0061645871806595e4fe2ab5991cc494afa26b31))
|
||||
* fix: assign empty string when predict item's fieldName is undefined ([#254](https://github.com/microsoft/OCR-Form-Tools/commit/d4d919f678b1f162f48c87ee5223281e57945a0a))
|
||||
* fix: overlaping left split pane ([#252](https://github.com/microsoft/OCR-Form-Tools/commit/2e8c351f74c385b8627ee6ea39f974e5e048ea8d))
|
||||
* refactor: change predict to analyze in UI while keeping predict term ([#147](https://github.com/microsoft/OCR-Form-Tools/commit/c9aa58e36a10a35083249a8080c2cfb9fccf3733))
|
||||
### 2.0.0-7c7ba93 (05-07-2020)
|
||||
* fix: check null value from post processed value ([#248](https://github.com/microsoft/OCR-Form-Tools/commit/a361189c527bfffd6417f90a2521ad40b2b3f205))
|
||||
* feat: enable outputting to file for analyze script ([#246](https://github.com/microsoft/OCR-Form-Tools/commit/7c7ba937f140490775b788d63ef2c7ed63ca40f1))
|
||||
### 2.0.0-9d91800 (05-06-2020)
|
||||
* fix: prevent user from changing tag types when invalid ([#224](https://github.com/microsoft/OCR-Form-Tools/commit/d8823a33591db5c5dc9a0af753e007167218a3e3))
|
||||
* fix: prevent user from adding multiple checkboxes to a single tag ([#224](https://github.com/microsoft/OCR-Form-Tools/commit/d8823a33591db5c5dc9a0af753e007167218a3e3))
|
||||
* fix: display error when inputted SAS doesn't contain token ([#243](https://github.com/microsoft/OCR-Form-Tools/commit/9826ca8504549f23057c9cad1baebc5e9d1f6fe7))
|
||||
### 2.0.0-25d3298 (05-04-2020)
|
||||
* feat: track document count for tags ([#231](https://github.com/microsoft/OCR-Form-Tools/commit/70a6e43dc54239cdc153d5d328b17c1dfa0f085f))
|
||||
* fix: display error when inputted service URI contains path or query ([#234](https://github.com/microsoft/OCR-Form-Tools/commit/04a16961b37ad5b5d01fc4c93addaaf69cbf0e72))
|
||||
* feat: add link in status bar to CHANGELOG ([#233](https://github.com/microsoft/OCR-Form-Tools/commit/e66646a13263239213580378bbd2d8462d7e22b6))
|
||||
### 2.0.0-f6c8ffa (05-01-2020)
|
||||
* refactor: change checkbox to selectionMark ([#223](https://github.com/microsoft/OCR-Form-Tools/commit/f6c8ffad6edf23f6241f314e9456da92bc1a8402))
|
||||
### 2.0.0-f3e42f6 (04-30-2020)
|
||||
* feat: display post-processed value in analyzed results ([#229](https://github.com/microsoft/OCR-Form-Tools/commit/f3e42f6e8e9e934f1a241921dbe4a1e8d311bb46))
|
||||
### 2.0.0-f068866 (04-28-2020)
|
||||
* fix: hide sprin in tag input control when open an empty folder ([#220](https://github.com/microsoft/OCR-Form-Tools/commit/f0688668df2e676fce9749fad8ec9d39e56697cf))
|
||||
* perf: cache images, reduce canvas size, and fix memory leak for asset preview ([#218](https://github.com/microsoft/OCR-Form-Tools/commit/e8ad9a3bebf2a1ae210e0e1fa3eebba564592c4c))
|
||||
### 2.0.0-595a512 (04-24-2020)
|
||||
* fix: align rotated picture asset with OCR result
|
||||
### 2.0.0-51c02cc (04-20-2020)
|
||||
* fix: scrollbar fix when page size changes
|
||||
* fix: Add split pane to fix too long tag name is invisible in right sidebar
|
||||
### 2.0.0-202fb2f (04-20-2020)
|
||||
* perf: improve assets loading performance and fix some bugs
|
||||
### 2.0.0-bce554e (04-16-2020)
|
||||
* perf: improve Azure Blob file list performance
|
||||
* feat: support URL upload for predicting file
|
||||
### 2.0.0-ef18425 (04-09-2020)
|
||||
* feat: enable checkbox labeling (preview)
|
||||
# FoTT Changelog
|
||||
## What's new in Form Recognizer?
|
||||
Click [here](https://docs.microsoft.com/en-us/azure/cognitive-services/form-recognizer/whats-new) to see what's new in Form Recognizer.
|
||||
|
||||
## Released conatiner's currently referenced commit
|
||||
2.1-Preview's released container image, tracked by the `latest-preview` image tag in our [docker hub repository](https://hub.docker.com/_/microsoft-azure-cognitive-services-custom-form-labeltool), currently references **2.1-preview.1-1f33130 (10-09-2020)**
|
||||
|
||||
## Commit history
|
||||
### 2.1-preview.2-b6b9a2f (12-10-2020)
|
||||
* update appVersion to 2.1.2 ([#808](https://github.com/microsoft/OCR-Form-Tools/commit/b6b9a2f131485d08541a1e85f6af59ebfbeca773))
|
||||
* add locale in prebuiltPredictPage ([#772](https://github.com/microsoft/OCR-Form-Tools/commit/06d9c16a7c1fe64a95d835878dcb8dabb8c7e485)) ([#776](https://github.com/microsoft/OCR-Form-Tools/commit/06d9c16a7c1fe64a95d835878dcb8dabb8c7e485))
|
||||
* Stew ro/cherry pick 347e21e 2b6ead7 ([#766](https://github.com/microsoft/OCR-Form-Tools/commit/ca59cee26587e1aee49507abd75c0683d67f541f))
|
||||
* Cherry pick 34ce14d a7ccb34 ([#763](https://github.com/microsoft/OCR-Form-Tools/commit/244c23df700791794990eb6f7e196bcf9ff9c844))
|
||||
* Stew ro/cherry pick ab5a8a8 abfffbb ([#760](https://github.com/microsoft/OCR-Form-Tools/commit/93b7a2d4d7688cda4baa4cfd704b2666df524174))
|
||||
* refactor: disable api version selection ([#755](https://github.com/microsoft/OCR-Form-Tools/commit/be1f18db0b7073dad106f443f343aa221dea7fc6))
|
||||
* refactor: disable draw region button ([#756](https://github.com/microsoft/OCR-Form-Tools/commit/8816a85761a795f3f0b34b87360236cadd80a735))
|
||||
* feat: support null text values in analyze results ([#744](https://github.com/microsoft/OCR-Form-Tools/commit/0ddb7f1275d6f195c2af9f0b7053987e01a5d677))
|
||||
* feat: support rowspan and column span for layout tables ([#754](https://github.com/microsoft/OCR-Form-Tools/commit/6994ac929146e25370d1461e4e502773de3d5503))
|
||||
* Update changelog ([#750](https://github.com/microsoft/OCR-Form-Tools/commit/acba3966ce960e474dfd3d97510b07c108e7b39f))
|
||||
* check whether the label data is null ([#753](https://github.com/microsoft/OCR-Form-Tools/commit/f6ef41ac1500e52f3714eda6e99187e016e1b223))
|
||||
* fix issue of 773 ([#740](https://github.com/microsoft/OCR-Form-Tools/commit/9d35e79393cdb0de678e9ec6b850daf5a4df5c96))
|
||||
|
||||
### 2.1-preview.1-2e50498 (11-09-2020)
|
||||
* fix: enable api version selection ([#736](https://github.com/microsoft/OCR-Form-Tools/commit/2e5049883bd1550ba80210edca7db4233d7a15fa))
|
||||
* fix: labeling doesn't work via shortcuts on the new project or empty tags ([#677](https://github.com/microsoft/OCR-Form-Tools/commit/f11291940b776ceb8ba7708e6f58dc2572f7b01b))
|
||||
* fix: remove setting project state in project form on change ([#732](https://github.com/microsoft/OCR-Form-Tools/commit/25eb59bfa85b755cd877b02ffda71d0cec70a106))
|
||||
* handle training state logical ([#731](https://github.com/microsoft/OCR-Form-Tools/commit/569adf161ad89106ab1fbf51429841c5955e0e4b))
|
||||
* fix issue of "After running Layout an all documents FoTT sometimes does not ends" ([#723](https://github.com/microsoft/OCR-Form-Tools/commit/6203e2cd814c95e2bef165f3fb518a566166c26d))
|
||||
* set includeTextDetails=true in prebuilt predict ([#722](https://github.com/microsoft/OCR-Form-Tools/commit/ba04cebb63a5b05a369ba954a99dfdc7c9bb9b41))
|
||||
* fix issue of "Auto-labeling while switching assets in asset preview causes an error" ([#721](https://github.com/microsoft/OCR-Form-Tools/commit/59fe4e2778a644335da9766fd1382d56086220c1))
|
||||
* feat: support api version config ([#717](https://github.com/microsoft/OCR-Form-Tools/commit/c81b2323aaa2b26b0bc0f7922de1e12445fbb627))
|
||||
* update homepage style ([#724](https://github.com/microsoft/OCR-Form-Tools/commit/fc769f41c169083098f9250c9ce18ca4881cc336))
|
||||
* issuefix: update getBoundingBox ([#730](https://github.com/microsoft/OCR-Form-Tools/commit/f0cb5db337364b2f0355928616d1f7d9637a454a))
|
||||
* clone with lodash cloneDeep ([#728](https://github.com/microsoft/OCR-Form-Tools/commit/d6bca5fcf2262a467ede781916a01f50d805b30f))
|
||||
* remain auto label state while no label data ([#727](https://github.com/microsoft/OCR-Form-Tools/commit/a79d556a3935e387b0798ecfda8de9c8b1538250))
|
||||
* deep copy asset metadata ([#725](https://github.com/microsoft/OCR-Form-Tools/commit/ba8c1100e9adf517e35ec50fd513383d9e84d630))
|
||||
* Yongbing chen/receipt predicting ([#626](https://github.com/microsoft/OCR-Form-Tools/commit/e638cd8e3be8926e966a5afc86fb53ac0f092977))
|
||||
|
||||
### 2.1-preview.1-32cfaea (11-06-2020)
|
||||
* Starain chen/clean autolabel data while training ([#712](https://github.com/microsoft/OCR-Form-Tools/commit/32cfaea023e96c8aa00560a3f30134683ee25757))
|
||||
* fix issue of deleting tag ([#703](https://github.com/microsoft/OCR-Form-Tools/commit/282d55700ea9fdf4cac2b0f20901e8ff6115819e))
|
||||
|
||||
### 2.1-preview.1-c7ed086 (11-04-2020)
|
||||
* Update README.md([#??](https://github.com/microsoft/OCR-Form-Tools/commit/c7ed08612876af8bb619a080f6740fceabb4e67c))
|
||||
* Update README.md([#??](https://github.com/microsoft/OCR-Form-Tools/commit/d696b8a25438590fb44c5159b3142b17178f25d2))
|
||||
* fix: use constant if no api version specified ([#684](https://github.com/microsoft/OCR-Form-Tools/commit/8ccdab83f079d976f6521bc08c50d917900483c0))
|
||||
* auto labeled tag design & replacing between text with draw region ([#670](https://github.com/microsoft/OCR-Form-Tools/commit/757e0dd85b3c69c6642674e48e9d3549807fecbd))
|
||||
|
||||
### 2.1-preview.1-aab6938 (11-03-2020)
|
||||
* Fix the issue that git-commit-info.txt could be override ([#683](https://github.com/microsoft/OCR-Form-Tools/commit/aab69380a8e1f7f113011a7c6b6ed406c4329555))
|
||||
* fix: use existing git hash when not in git repository ([#682](https://github.com/microsoft/OCR-Form-Tools/commit/586fbb0ce51c27ae42ca857a372e8e8d5dea21d1))
|
||||
* Stew ro/use api version selected in project settings ([#678](https://github.com/microsoft/OCR-Form-Tools/commit/bed69a3f64b0da7590ca3c54e8de369844c6bcd9))
|
||||
* refactor: change drawn region icon ([#675](https://github.com/microsoft/OCR-Form-Tools/commit/5614da2681bb8fadf9d3db3ff95aa62362d00175))
|
||||
|
||||
### 2.1-preview.1-3485d33 (10-30-2020)
|
||||
* feat: add bmp support for analyze page ([#672](https://github.com/microsoft/OCR-Form-Tools/commit/3485d33eca96321cf667c5c8eba22cc60af42e23))
|
||||
* Alex krasn/bugfix on hotkeys when canvas not loaded yet ([#664](https://github.com/microsoft/OCR-Form-Tools/commit/b0404c6276f8fe55292c929e2ca431ed31ef6442))
|
||||
|
||||
### 2.1-preview.1-7166cda (10-29-2020)
|
||||
* fix: use node to update status bar with latest git commit ([#671](https://github.com/microsoft/OCR-Form-Tools/commit/7166cdae5763a93feee52842af8e2246fedbf818))
|
||||
* change OCR to Layout in UI (Actions) ([#666](https://github.com/microsoft/OCR-Form-Tools/commit/ac604b6bd43eb4c3ba8929a97b308c833d0e6c13))
|
||||
* Yongbing chen/hitl update notify message ([#651](https://github.com/microsoft/OCR-Form-Tools/commit/0fa559a4b28c6648eaa17ec047ebb9caabbdc9c7))
|
||||
|
||||
### 2.1-preview.1-6d775ae (10-27-2020)
|
||||
* Yongbing chen/ui adjustment with designers feedback ([#662](https://github.com/microsoft/OCR-Form-Tools/commit/6d775ae8d4495ca31d110e500b86d3c0eed6a954))
|
||||
|
||||
### 2.1-preview.1-c86b6de (10-23-2020)
|
||||
* Fix the issue that git-commit-info.txt could be override ([#668](https://github.com/microsoft/OCR-Form-Tools/commit/c86b6de35ecd5d004dfb64f8f857d06f0557a00d))
|
||||
* Xinxl/fix hash ([#667](https://github.com/microsoft/OCR-Form-Tools/commit/cb27cbd74ff890dc7e13865d89ca5e16b0807fbb))
|
||||
|
||||
### 2.1-preview.1-0aae169 (10-22-2020)
|
||||
* Alex krasn/fix confidence level bar styles ([#657](https://github.com/microsoft/OCR-Form-Tools/commit/0aae1690351f3de27114e6cbebd2c077be8e9016))
|
||||
|
||||
### 2.1-preview.1-d644459 (10-21-2020)
|
||||
* refactor: change error styling and wording for project sharing ([#653](https://github.com/microsoft/OCR-Form-Tools/commit/d644459e4c9b1f82b1ed2d5b537960b0f16184da))
|
||||
* fix: sort models after loading next page in model compose ([#659](https://github.com/microsoft/OCR-Form-Tools/commit/9818d6301ef613155951381598f9ad4cf8ff6e3c))
|
||||
* Alex krasn/serialize javascript vulnerability ([#612](https://github.com/microsoft/OCR-Form-Tools/commit/66b03303b1325634371ebdb3923acaa6722be89f))
|
||||
* update asset labelingState when load local project ([#660](https://github.com/microsoft/OCR-Form-Tools/commit/1aa3daaeeb1c8a4773e7b6236fc6462335e410f9))
|
||||
|
||||
### 2.1-preview.1-28c54fc (10-20-2020)
|
||||
* fix: check for local connections ([#654](https://github.com/microsoft/OCR-Form-Tools/commit/28c54fcc31defe1c4ebcf685675768b99c8e00c8))
|
||||
* get last commit hash code in current branch and show on status bar ([#642](https://github.com/microsoft/OCR-Form-Tools/commit/88c547995d31f945177da70141f997e441b3259c))
|
||||
* new feature: tags in current page ([#640](https://github.com/microsoft/OCR-Form-Tools/commit/af5396fe8e63b88b90d16953e17ce2006afe782e))
|
||||
|
||||
### 2.1-preview.1-6c1ee2b (10-16-2020)
|
||||
* adjust editor view offset ([#646](https://github.com/microsoft/OCR-Form-Tools/commit/6c1ee2b6b4f1bcf28b1c9081b21f0a8783518c80))
|
||||
|
||||
### 2.1-preview.1-b92e4b3 (10-15-2020)
|
||||
* reword asset states ([#644](https://github.com/microsoft/OCR-Form-Tools/commit/b92e4b3d5a786a852c319c05697eea331c147cee))
|
||||
|
||||
### 2.1-preview.1-4544e52 (10-14-2020)
|
||||
* feat: support apiVersion selection from project settings ([#641](https://github.com/microsoft/OCR-Form-Tools/commit/4544e5255cf2356a4ddf353f7a63994c1a0865da))
|
||||
|
||||
### 2.1-preview.1-94f12bb (10-13-2020)
|
||||
* new feature: highlight current tag ([#628](https://github.com/microsoft/OCR-Form-Tools/commit/94f12bb4e925a86fdfba8e25d8b0346169daea1e))
|
||||
* new feature: human in the loop auto labeling ([#571](https://github.com/microsoft/OCR-Form-Tools/commit/c1f227daa3decd52320f58d151755b206280cedd))
|
||||
|
||||
### 2.1-preview.1-7d1f871 (10-10-2020)
|
||||
* Update CHANGELOG.md([#??](https://github.com/microsoft/OCR-Form-Tools/commit/7d1f87193b3917f2140ab9bcce04c64e7aceb823))
|
||||
|
||||
### 2.1-preview.1-1f33130 (10-09-2020)
|
||||
* fix: support image map interactions for container releases([#639](https://github.com/microsoft/OCR-Form-Tools/commit/e015973aee152b8a8b22fc2fe32ce80bdd2b46ea))
|
||||
|
||||
### 2.1-preview.1-6d4e93b (10-07-2020)
|
||||
* Fix: use file type library for mime type validation ([#636](https://github.com/microsoft/OCR-Form-Tools/commit/6d4e93bca8a4e3d677c765ed5596bde502766e2e))
|
||||
|
||||
### 2.1-preview.1-355ca0b (09-30-2020)
|
||||
* feat: add spinner in saving project, can avoid multiple commit ([#617](https://github.com/microsoft/OCR-Form-Tools/commit/355ca0b156b2d44aafd2eaaccf2fc52385c7f5f8))
|
||||
|
||||
### 2.1-preview.1-53044f7 (09-29-2020)
|
||||
* fix: refresh currentProjects when load project ([#615](https://github.com/microsoft/OCR-Form-Tools/commit/53044f72dd9c9c72557c74c00605ba05ee50205d))
|
||||
* sync related region color when tag color changed ([#598](https://github.com/microsoft/OCR-Form-Tools/commit/3044cc51a9166877bb4f01f28753171b82c04ccd))
|
||||
* feat: add current list item style ([#601](https://github.com/microsoft/OCR-Form-Tools/commit/3e503e75513e44e6a90bd013d8dd15c3096cd7e9))
|
||||
* fix: remove project from app if security token does not exist ([#468](https://github.com/microsoft/OCR-Form-Tools/commit/730e1963a06f038a4efa9750fcef4be6f15a8460))
|
||||
|
||||
### 2.1-preview.1-d859d38 (09-27-2020)
|
||||
* fix ,update document state when preview (#317) ([#471](https://github.com/microsoft/OCR-Form-Tools/commit/d859d38ecc1f96b194ffa130a1840f5a7d9b1a9b))
|
||||
* refactor: change the confidence value format to percentage ([#461](https://github.com/microsoft/OCR-Form-Tools/commit/e806b4e0dfcc68e6408e2130a46a318637a482a8))
|
||||
|
||||
### 2.1-preview.1-7a3f7a7 (09-25-2020)
|
||||
* security: upgrade node-forge ([#622](https://github.com/microsoft/OCR-Form-Tools/commit/7a3f7a773c8b01f443afaad89d7974a5bbb0b869))
|
||||
* fix: disable move tag and support renaming when searching ([#618](https://github.com/microsoft/OCR-Form-Tools/commit/cac1e8e6cfb2805a6540f9e80d564a0ff8be81c7))
|
||||
|
||||
### 2.1-preview.1-4163edc (09-23-2020)
|
||||
* docs: add latest tag reference to changelog ([#608](https://github.com/microsoft/OCR-Form-Tools/commit/4163edc18bc65234e263703fc829d2f297953385))
|
||||
* fix: use region instead of drawnRegion for labelType in label file ([#582](https://github.com/microsoft/OCR-Form-Tools/commit/ffafc200249a1c47698fedb279b4b55cef0190ba))
|
||||
* docs: update readme with docker hub info ([#604](https://github.com/microsoft/OCR-Form-Tools/commit/63bbea076d598d0286095fa0eca48d8c9d0ed706))
|
||||
* fix: remove opening browser for yarn start ([#605](https://github.com/microsoft/OCR-Form-Tools/commit/f6c4dc3585df71d09252a28f65e835a594389118))
|
||||
* fix: update changelog updater script ([#607](https://github.com/microsoft/OCR-Form-Tools/commit/7c4848c3a72259562c0461f0e2eadfb4a660fa64))
|
||||
|
||||
### 2.1-preview.1-f2db74e (09-17-2020)
|
||||
* docs: udpate changlog with docker image reference ([#590](https://github.com/microsoft/OCR-Form-Tools/commit/f2db74e322c32338eba3b2df06c01a51cfb7ebc1))
|
||||
|
||||
### 2.1-preview.1-1a6b78e (09-16-2020)
|
||||
* fix: normalize folder path starting with a period ([#592](https://github.com/microsoft/OCR-Form-Tools/commit/1a6b78e054235da3188aafbe65636a8c18b439bf))
|
||||
* fix: change label folder uri title ([#588](https://github.com/microsoft/OCR-Form-Tools/commit/7e4233e568d94817e23dda5ef5513b9ee7475d11))
|
||||
|
||||
### 2.1-preview.1-6a1ced5 (09-15-2020)
|
||||
* fix: initialize drag pan for analyze page ([#586](https://github.com/microsoft/OCR-Form-Tools/commit/6a1ced5a0bfb03ceba515faddbfa010ac8451460))
|
||||
* fix: zoomIn keyboar shortcut for macOS ([#581](https://github.com/microsoft/OCR-Form-Tools/commit/5afeebfee28e10e390f073990f90348c5117475f))
|
||||
* fix: appId ([#584](https://github.com/microsoft/OCR-Form-Tools/commit/e053b151441e956641ed05c29106d02358a40792))
|
||||
* fix: remove escape quote from release script ([#579](https://github.com/microsoft/OCR-Form-Tools/commit/bd5d51e8e15809b95f15bc495f7d0f91fecfc22d))
|
||||
* Stew ro/support drag pan for release ([#576](https://github.com/microsoft/OCR-Form-Tools/commit/77620eccd21d564473c81b43341f59de22339248))
|
||||
|
||||
### 2.1-preview.1-0633507 (09-14-2020)
|
||||
* Update README.md([#??](https://github.com/microsoft/OCR-Form-Tools/commit/0633507aa767f996add313ced06c2365c5f240c8))
|
||||
|
||||
### 2.1-preview.1-8d2286f (09-13-2020)
|
||||
* persist trainPage inputs in localStorage ([#568](https://github.com/microsoft/OCR-Form-Tools/commit/8d2286f50236e41fe5540dbb9b161ea88bbf2d7a))
|
||||
|
||||
### 2.1-preview.1-bb23e31 (09-11-2020)
|
||||
* build(deps): bump node-fetch from 2.6.0 to 2.6.1 ([#575](https://github.com/microsoft/OCR-Form-Tools/commit/bb23e3199c5721338241c8c5ccc0bda104fd15f8))
|
||||
* fix: support multiple env files ([#574](https://github.com/microsoft/OCR-Form-Tools/commit/cf64a8ddde05e7e73cad37d271f5d6dfa61c5d7f))
|
||||
* fix: "Azure blob storage" error on on premise scenario ([#572](https://github.com/microsoft/OCR-Form-Tools/commit/46f0bc59f3a531c366bc2c7cec955d2cb6ed7cd6))
|
||||
* fix ([#563](https://github.com/microsoft/OCR-Form-Tools/commit/28c792e10692e3cc1f511852ffc9fdbc8dcdda8a))
|
||||
|
||||
### 2.1-preview.1-7e828ff (09-10-2020)
|
||||
* fix: allow training with placeholder ([#569](https://github.com/microsoft/OCR-Form-Tools/commit/7e828ff02ff8a22b64b4b7d16787d77afb76af62))
|
||||
* docs: update changelog ([#564](https://github.com/microsoft/OCR-Form-Tools/commit/1dec72c5df3206554a1e0864b65cc769835785fd))
|
||||
* Yongbing chen/human in the loop ([#517](https://github.com/microsoft/OCR-Form-Tools/commit/be9d56481510e3033bcd705743c1ee9aeee20522))
|
||||
* fix: support project folder in project settings for local file system ([#559](https://github.com/microsoft/OCR-Form-Tools/commit/b92b73bb8076f9b9bb55dd38fcd223b7b93eaa2e))
|
||||
* feat: enable canvas rotation ([#553](https://github.com/microsoft/OCR-Form-Tools/commit/c27a110251df1fc7a595524846e20fd09c79f915))
|
||||
* fix: handle tag is undefined error ([#557](https://github.com/microsoft/OCR-Form-Tools/commit/7e4d3fbbc3a2bf925c126cd2b3f493cca48e7a62))
|
||||
|
||||
### 2.1-preview.1-193520e (09-08-2020)
|
||||
* fix: accept selection of only .fott files for open local project ([#554](https://github.com/microsoft/OCR-Form-Tools/commit/193520e2c3e40b58c3612507efc2249aaf4e9d05))
|
||||
* fix: use default shared folder for label URI when training ([#551](https://github.com/microsoft/OCR-Form-Tools/commit/656de2ff07c2083affc2adf52f1a56c5a9c024b8))
|
||||
* fix: show label folder uri while training ([#539](https://github.com/microsoft/OCR-Form-Tools/commit/0ad389c06328cd6428653bb7d94d5af716e02ab7))
|
||||
* feat: add canvas command bar to analyze page with only zoom buttons ([#549](https://github.com/microsoft/OCR-Form-Tools/commit/895b52740cd1e8d61b5b021e6c0d992f44ce8052))
|
||||
|
||||
### 2.1-preview.1-4852c84 (09-05-2020)
|
||||
* fix buttons styles - makes them more visible ([#526](https://github.com/microsoft/OCR-Form-Tools/commit/4852c8429d25b5569c3335b014da5972cbcc6162))
|
||||
|
||||
### 2.1-preview.1-343ea16 (09-04-2020)
|
||||
* refactor: remove array for drawn region labels ([#542](https://github.com/microsoft/OCR-Form-Tools/commit/343ea16e18199ab5098395ae8b7a164cd8bab55e))
|
||||
* fix: add key prop to region icon ([#540](https://github.com/microsoft/OCR-Form-Tools/commit/87b69093f2d35d91a2a939c46ac66ba4d22a5cb7))
|
||||
|
||||
### 2.1-preview.1-b370c9a (09-02-2020)
|
||||
* fix: resize canvas on asset preview resize ([#535](https://github.com/microsoft/OCR-Form-Tools/commit/b370c9a9bcf7da416944c626f6d4fd7bd29088bb))
|
||||
|
||||
### 2.1-preview.1-de1c304 (08-31-2020)
|
||||
* refactor: upgrade tsconfig es2017 to esnext ([#531](https://github.com/microsoft/OCR-Form-Tools/commit/de1c30410b860c9576108f076ec4dd8273e61a79))
|
||||
|
||||
### 2.1-preview.1-530545c (08-28-2020)
|
||||
* fix: remove existing bounding boxes from document on analyze ([#523](https://github.com/microsoft/OCR-Form-Tools/commit/6a1aedfb89b0499a0f4782e16ccbd8a06887841d))
|
||||
* feat: enable download JSON of trained model ([#513](https://github.com/microsoft/OCR-Form-Tools/commits/master))
|
||||
|
||||
### 2.1-preview.1-529a0e8 (08-27-2020)
|
||||
* fix: show loading indicator while loading model info ([#514](https://github.com/microsoft/OCR-Form-Tools/commit/529a0e819f4cb405e290f34d18d15c487a7bcfad))
|
||||
* docs: update telemetry disclaimer ([#521](https://github.com/microsoft/OCR-Form-Tools/pull/521))
|
||||
* fix: disable clearing of drawn regions on analyze page ([#518](https://github.com/microsoft/OCR-Form-Tools/commit/298d7c97da1278996d2ee6020d3face0785bc4eb))
|
||||
|
||||
### 2.1-preview.1-b2d9a0b (08-26-2020)
|
||||
* docs: notice that telemetry is disabled ([#501](https://github.com/microsoft/OCR-Form-Tools/commit/b2d9a0b008ebf350dfcb5fe897fc5dfe0d4d5cb6))
|
||||
|
||||
### 2.1-preview.1-d9db4ee (08-24-2020)
|
||||
* refactor: upgrade storage-blob to v12.1.2 ([#509](https://github.com/microsoft/OCR-Form-Tools/commit/d9db4ee027240a82feef5b54e5e406c3793d8050))
|
||||
* feat: support region labeling ([#481](https://github.com/microsoft/OCR-Form-Tools/commit/dd78ed06761a341908bdb1b09e73fd1f2868431c))
|
||||
* feat: support adding model to recent models from compose page ([#510](https://github.com/microsoft/OCR-Form-Tools/commit/65fc92b5737ceea14ff89aa78052be26835ad0ae))
|
||||
|
||||
### 2.1-preview.1-2402cba (08-17-2020)
|
||||
* fix: notify error message when open project with invalid security token ([#506](https://github.com/microsoft/OCR-Form-Tools/commit/2402cbaf73eba47ad188f851227c04cd44a208d4))
|
||||
|
||||
### 2.1-preview.1-a8ef8fa (08-17-2020)
|
||||
* fix: don't allow create or update connection with duplicate name ([#486](https://github.com/microsoft/OCR-Form-Tools/commit/a8ef8fab603b3d2c08c533cb5dfe67da117942a0))
|
||||
|
||||
### 2.1-preview.1-530545c (08-14-2020)
|
||||
* fix: "failed to fetch()" error ([#491](https://github.com/microsoft/OCR-Form-Tools/commit/530545c7cd2b4a3ff444e9c7e1f40c68d4a7376c))
|
||||
* fix: sync layer visibility ([#497](https://github.com/microsoft/OCR-Form-Tools/commit/bea552b28acb9b652ffaedf40009d6df5a3197ef))
|
||||
* refactor: disable telemetry service ([#498](https://github.com/microsoft/OCR-Form-Tools/commit/6e3628cf174f954693380aab6ebd2dabe027ac6d))
|
||||
* fix: change share class name for adblocker chrome extension ([#492](https://github.com/microsoft/OCR-Form-Tools/commit/aa8a73afc6344f3164e79f236d5fa4bb0f64d364))
|
||||
|
||||
### 2.1-preview.1-da405b3 (08-10-2020)
|
||||
* fix: restrict tag type through hot keys ([#482](https://github.com/microsoft/OCR-Form-Tools/commit/da405b354428b829e895a35a020736b1d88c153f))
|
||||
* docs: add share project description to README ([#488](https://github.com/microsoft/OCR-Form-Tools/commit/7ee215f735a84aaa30201748d19207bcc6a05580))
|
||||
|
||||
### 2.1-preview.1-29d1f93 (08-07-2020)
|
||||
* fix: handle multi selection of non-compatible types with multi-selection tool ([#487](https://github.com/microsoft/OCR-Form-Tools/commit/29d1f93a290e55fdd84f8cf2ee9a914fed702beb))
|
||||
|
||||
### 2.1-preview.1-cef225f (08-06-2020)
|
||||
* fix: handle undefined image map error ([#462](https://github.com/microsoft/OCR-Form-Tools/commit/cc9e9bfc8fe00bb0ed154edb791446f28060af4e))
|
||||
* fix: handle undefined image map error ([#479](https://github.com/microsoft/OCR-Form-Tools/commit/cef225f3346628e79c46e799303400965f1d3c96))
|
||||
|
||||
### 2.1-preview.1-76945df (08-05-2020)
|
||||
* fix: use english for telemetry reporting ([#472](https://github.com/microsoft/OCR-Form-Tools/commit/76945df3bdf9caba3ba13f4541e17e75b9574b33))
|
||||
* fix: resolve unhandled exeptions and new message for OCR service on 400 ([#470](https://github.com/microsoft/OCR-Form-Tools/commit/76381bc659a365ead19387b933485530d2d5edc3))
|
||||
* feature: enable popup with composed model info ([#460](https://github.com/microsoft/OCR-Form-Tools/commit/c1f5d803f047e5ca0d18fea6383b3baf56d116ff))
|
||||
|
||||
### 2.1-preview.1-f4d53ce (08-03-2020)
|
||||
* fix: bump elliptic from 6.5.2 to 6.5.3 ([#469](https://github.com/microsoft/OCR-Form-Tools/commit/f4d53cec967194445885bd3748096f0a3ce10715))
|
||||
* feat: add modelCompose icon and created time ([#466](https://github.com/microsoft/OCR-Form-Tools/commit/2fa32ef5f77ec7bb44bf42e9fc0a5fdf7f0330c3))
|
||||
|
||||
### 2.1-preview.1-78996ea (07-31-2020)
|
||||
* refactor: relocate share button ([#464](https://github.com/microsoft/OCR-Form-Tools/commit/78996ea65616b28d7471b59f4f16f254d7d33127))
|
||||
|
||||
### 2.1-preview.1-0e1b637 (07-29-2020)
|
||||
* feat: show only ready models in the list ([#459](https://github.com/microsoft/OCR-Form-Tools/commit/0e1b637003f289c56955342f44963003c1543436))
|
||||
|
||||
### 2.1-preview.1-84f8285 (07-27-2020)
|
||||
* fix: show message on model composition fail ([#457](https://github.com/microsoft/OCR-Form-Tools/commit/84f82859122ff298bcfcca78e821e8bfe437bb78))
|
||||
* refactor: add background on popup table ([#446](https://github.com/microsoft/OCR-Form-Tools/commit/27f60df5617da2efba8ffdd601233e0c0f4c8e3e))
|
||||
|
||||
### 2.1-preview.1-79264e3 (07-24-2020)
|
||||
* fix: handle rejection for security token not found when opening projects ([#441](https://github.com/microsoft/OCR-Form-Tools/commit/79264e3fddfb2c80b88bf8ca21df1e869082ffcf))
|
||||
* fix: show more refined error message for model not found analysis error ([#454](https://github.com/microsoft/OCR-Form-Tools/commit/1cb4133dca0092559e7524dfad8c0bf54502dc81))
|
||||
* feat: support group selection of words with drawn bounding box ([#447](https://github.com/microsoft/OCR-Form-Tools/commit/b4332a926b1925024a33731a90d303c0b171935b))
|
||||
* feat: add apiVersion to telemetry ([#448](https://github.com/microsoft/OCR-Form-Tools/commit/55be5427e4a2f9c8cf393d446049527c55f841d4))
|
||||
* fix: margin for filenames in asset preview ([#451](https://github.com/microsoft/OCR-Form-Tools/commit/fe8258f9c7ceba663a66708b19bc0e6556e777ad))
|
||||
* docs: add telemetry disclaimer to readme ([#449](https://github.com/microsoft/OCR-Form-Tools/commit/87356a1cf6678bb9494e83178bf6282ca366921f))
|
||||
|
||||
### 2.1-preview.1-9b5b99d (07-23-2020)
|
||||
* docs: add get-sas.png (https://github.com/microsoft/OCR-Form-Tools/commit/9b5b99d5468661481ae8165593d5a74471366429)
|
||||
* doc: add a screenshot of getting SAS token (https://github.com/microsoft/OCR-Form-Tools/commit/87b1062125ed106ff73c036e33f1bf7a5f2c3def)
|
||||
* fix: handle undefined error for pdf asset preview memory cleaning ([#442](https://github.com/microsoft/OCR-Form-Tools/commit/9b5b99d5468661481ae8165593d5a74471366429))
|
||||
* fix: remove duplicate models in model composed model list ([#439](https://github.com/microsoft/OCR-Form-Tools/commit/7fcc9ccfdb6634326ddd6cbfe99b423300b94131))
|
||||
* feat: enable internal telemetry ([#431](https://github.com/microsoft/OCR-Form-Tools/commit/41294c8aa19c82643fe0df669c21a0112668e0dd))
|
||||
|
||||
### 2.1-preview.1-f4b4d5d (07-21-2020)
|
||||
* fix: use table for model selection info ([#438](https://github.com/microsoft/OCR-Form-Tools/commit/f4b4d5ded4b7e0ff2116ba3b8f97e49fbf30b7c0))
|
||||
* fix: reset model name after training ([#434](https://github.com/microsoft/OCR-Form-Tools/commit/ed919a016b150d0938aee25b5550bacf29f04e83))
|
||||
* fix: wait for loadeding project with sharing project ([#435](https://github.com/microsoft/OCR-Form-Tools/commit/fc4cb96d2a9d0920c3bbbd9c2000fb4b1b7ac9c0))
|
||||
|
||||
### 2.1-preview.1-46dbb2b (07-20-2020)
|
||||
* fix: handle no recent models for model compose ([#432](https://github.com/microsoft/OCR-Form-Tools/commit/46dbb2be9ee6100a8f3e6a443ad5e734c60954bb))
|
||||
* refactor: use new model compose icon ([#425](https://github.com/microsoft/OCR-Form-Tools/commit/932fb3fd7f84636e97035f4cafadc87cff18b3b3))
|
||||
* fix: support long model names for model selection ([#427](https://github.com/microsoft/OCR-Form-Tools/commit/a0fa2daf4cd3286f7f58dc2919fd202115e8d5be))
|
||||
* feat add recent models to top of model compose page's list ([#430](https://github.com/microsoft/OCR-Form-Tools/commit/cf8de6be61b95bfe8c937946df71ea81aecb35f9))
|
||||
* fix: check valid connection ([#428](https://github.com/microsoft/OCR-Form-Tools/commit/9cb6c5830afddc9317ffdfe6927b581c4d39ba39))
|
||||
|
||||
### 2.1-preview.1-162a766 (07-17-2020)
|
||||
* refactor: make confidence results same as JSON results ([#409](https://github.com/microsoft/OCR-Form-Tools/commit/162a7660cfe32b72c4954a147269c5d2b7f55a08))
|
||||
* fix: prevent user from leaving page while composing ([#422](https://github.com/microsoft/OCR-Form-Tools/commit/63e179d0152d2f8f2ee764443785efa24e5f7dce))
|
||||
* feat: support model selection ([#419](https://github.com/microsoft/OCR-Form-Tools/commit/b4c4cc5a8a980aaa6530e7a4a5a1c43e77494c75))
|
||||
* feat: share project ([#344](https://github.com/microsoft/OCR-Form-Tools/commit/d059580cfefa053670c45c5d8ec7bf250bc4db27))
|
||||
|
||||
### 2.1-preview.1-89be3ac (07-15-2020)
|
||||
* fix: on assetFormat undefined ([#413](https://github.com/microsoft/OCR-Form-Tools/commit/89be3ac5b614e91607d7fb8065ad32b69886040d))
|
||||
* fix: make sure token names are unique ([#404](https://github.com/microsoft/OCR-Form-Tools/commit/d8fa6141cff4d00ba22e95ef4f5dcc9102e1c1c2))
|
||||
* fix: model info enclosing element error on [#407](https://github.com/microsoft/OCR-Form-Tools/issues/407) ([#408](https://github.com/microsoft/OCR-Form-Tools/commit/8cc421c3fee0e781211efb0aeb2b345075012daa))
|
||||
* fix: display composed icon for composed model with attribute ([#399](https://github.com/microsoft/OCR-Form-Tools/commit/18fb4d71052b9355c8d5a4f7dde956ba17ca30fa))
|
||||
|
||||
### 2.1-preview.1-b67191c (07-09-2020)
|
||||
* fix: don't allow choosing not-ready models for compose ([#394](https://github.com/microsoft/OCR-Form-Tools/commit/b67191cdbc872b9004be30aa4b4dfde9a88dfe37))
|
||||
* feat: track five most recent project models ([#395](https://github.com/microsoft/OCR-Form-Tools/commit/05850603d51a6786c8b6e8b4a553db020df56158))
|
||||
|
||||
### 2.1-preview.1-abc6376 (07-08-2020)
|
||||
* feat: enable model info in analyze results ([#383](https://github.com/microsoft/OCR-Form-Tools/commit/abc63767e97dd28a6bb9028e03f2225e6ac0f1ab))
|
||||
* fix: check invalid provider options before project actions ([#390](https://github.com/microsoft/OCR-Form-Tools/commit/212647d4327d9e18e9248a2d39086eeaab404979))
|
||||
|
||||
### 2.1-preview.1-a334cfc (07-07-2020)
|
||||
* fix: hide extra scrollbars for model compose view ([#380](https://github.com/microsoft/OCR-Form-Tools/commit/a334cfc45fc5ab137682ad2b48dd0ec1585055dc))
|
||||
* fix: handle version change state mutation error ([#382](https://github.com/microsoft/OCR-Form-Tools/commit/8991cc0c92f2f5cbd226f7e1c5c0825b7af8937c))
|
||||
* fix: handle pdf worker terminated error ([#381](https://github.com/microsoft/OCR-Form-Tools/commit/adc0498c31bfd5ba57ab98c373e73575589ab1e1))
|
||||
|
||||
### 2.1-preview.1-7192170 (07-02-2020)
|
||||
* feat: support release ([#361](https://github.com/microsoft/OCR-Form-Tools/commit/7192170d73d24a43e7fff18cd2c6bae7f208f1b0))
|
||||
|
||||
### 2.1-preview.1-978dabc (07-01-2020)
|
||||
* feat: support document management ([#374](https://github.com/microsoft/OCR-Form-Tools/commit/978dabc3ba877ed4215865cba2a583fb785a2894))
|
||||
|
||||
### 2.1-preview.1-56a4b89 (06-30-2020)
|
||||
* fix: wait until composed model is ready ([#369](https://github.com/microsoft/OCR-Form-Tools/commit/56a4b89f370f2fd72c6bc275376205e7fffe6a9e))
|
||||
|
||||
### 2.1-preview.1-6114d64 (06-23-2020)
|
||||
* fix: update OCR version ([#335](https://github.com/microsoft/OCR-Form-Tools/commit/6114d6456b27a59335e534eef72cefd1b2f15737))
|
||||
* feat: support electron for on premise solution ([#333](https://github.com/microsoft/OCR-Form-Tools/commit/ca0bd0c2ab46b7b587e5bfbc60c29b62bb325297))
|
||||
|
||||
### 2.1-preview.1-8297b18 (06-19-2020)
|
||||
* refactor: put api version in constants ([#332](https://github.com/microsoft/OCR-Form-Tools/commit/8297b18a084be86bc4c986a1a332cb40bd807d1b))
|
||||
|
||||
### 2.1-preview.1-3b7f803 (06-18-2020)
|
||||
* feat: enable model compose (preview) ([#328](https://github.com/microsoft/OCR-Form-Tools/commit/3b7f803407b82191706120bb9f12b82de1955704))
|
||||
* fix: quick reordering tags ([#322](https://github.com/microsoft/OCR-Form-Tools/commit/3cc5267ef8617590adb3d4966f75cfed64604f00))
|
||||
* feat: localization for canvas commandbar items ([#319](https://github.com/microsoft/OCR-Form-Tools/commit/253b9c90eb4923e7fde015a7216905fa32a8dcfa))
|
||||
* feat: enable re-run OCR ([#297](https://github.com/microsoft/OCR-Form-Tools/commit/cbe9b0ed1c48f54c100b31b7f04706a969df2dd5))
|
||||
* fix: capitalize python in analyze page ([#320](https://github.com/microsoft/OCR-Form-Tools/commit/96626636a96a3d19030df283ac794fa9c2aab18c))
|
||||
* fix: fix spelling correction for string match ([#318](https://github.com/microsoft/OCR-Form-Tools/commit/28e53cefcf0bb462d547d6e38b24c480c03b946f))
|
||||
* feature: keep prediction in UI ([#285](https://github.com/microsoft/OCR-Form-Tools/commit/dad98b9bd1d305a6bfeb2846ef4067da186ff801))
|
||||
|
||||
### 2.0.0-1c39800 (06-05-2020)
|
||||
* feat: add description - how to delete info ([#292](https://github.com/microsoft/OCR-Form-Tools/commit/1c39800b1152f186dfc19834bb969abbc4fe0ac2))
|
||||
* feat: enable download analyze script ([#304](https://github.com/microsoft/OCR-Form-Tools/commit/9c97ed0ff9b0aa72ec9a197fc92f3a5998135c36))
|
||||
* fix: check ocrread results before getting image extent ([#296](https://github.com/microsoft/OCR-Form-Tools/commit/61dba02fc6f19eb854e1f499e475b1336e6171b9))
|
||||
* feat: Add better error message for CORS ([#289](https://github.com/microsoft/OCR-Form-Tools/commit/8f210792b4d84e424b00499efb540b0e27e9fdad))
|
||||
|
||||
### 2.0.0-2760166 (05-30-2020)
|
||||
* fix: fix mime check bug for jpeg/jpg and tiff ([#291](https://github.com/microsoft/OCR-Form-Tools/commit/2760166bcb809bbfdc207b01db49f00153318624))
|
||||
* refactor: simplify shortcut descriptions ([#277](https://github.com/microsoft/OCR-Form-Tools/commit/db95b0e2510f6cef9bc7279fe0a19dce239c816e))
|
||||
|
||||
### 2.0.0-a5e4e07 (05-21-2020)
|
||||
* feature: show table view when table icon is clicked ([#271](https://github.com/microsoft/OCR-Form-Tools/commit/a5e4e079d4c0d1c7c52e3b015c0ddf9b8601bbf2))
|
||||
|
||||
### 2.0.0-814276a (05-20-2020)
|
||||
* fix: modify skip button according to feedback comments ([#264](https://github.com/microsoft/OCR-Form-Tools/commit/814276af6f4259844854798adf0c56bd606b2363))
|
||||
* feature: keyboard shortcuts and tips ([#258](https://github.com/microsoft/OCR-Form-Tools/commit/37aa859a80dc0213a118313558ad21ba424008e7))
|
||||
* feat: add electron mode from VoTT project ([#260](https://github.com/microsoft/OCR-Form-Tools/commit/2a3383d4a0f100a39ed40627bdffb9b48f78f5df))
|
||||
* refactor: use forEach instead of map in handleFeatureSelect ([#259](https://github.com/microsoft/OCR-Form-Tools/commit/c1c590c463743d187fda2429a628e27c6c42012f))
|
||||
|
||||
### 2.0.0-0061645 (05-13-2020)
|
||||
* build: update nginx base image version to 1.18.0-alpine ([#255](https://github.com/microsoft/OCR-Form-Tools/commit/0061645871806595e4fe2ab5991cc494afa26b31))
|
||||
* fix: assign empty string when predict item's fieldName is undefined ([#254](https://github.com/microsoft/OCR-Form-Tools/commit/d4d919f678b1f162f48c87ee5223281e57945a0a))
|
||||
* fix: overlaping left split pane ([#252](https://github.com/microsoft/OCR-Form-Tools/commit/2e8c351f74c385b8627ee6ea39f974e5e048ea8d))
|
||||
* refactor: change predict to analyze in UI while keeping predict term ([#147](https://github.com/microsoft/OCR-Form-Tools/commit/c9aa58e36a10a35083249a8080c2cfb9fccf3733))
|
||||
### 2.0.0-7c7ba93 (05-07-2020)
|
||||
* fix: check null value from post processed value ([#248](https://github.com/microsoft/OCR-Form-Tools/commit/a361189c527bfffd6417f90a2521ad40b2b3f205))
|
||||
* feat: enable outputting to file for analyze script ([#246](https://github.com/microsoft/OCR-Form-Tools/commit/7c7ba937f140490775b788d63ef2c7ed63ca40f1))
|
||||
### 2.0.0-9d91800 (05-06-2020)
|
||||
* fix: prevent user from changing tag types when invalid ([#224](https://github.com/microsoft/OCR-Form-Tools/commit/d8823a33591db5c5dc9a0af753e007167218a3e3))
|
||||
* fix: prevent user from adding multiple checkboxes to a single tag ([#224](https://github.com/microsoft/OCR-Form-Tools/commit/d8823a33591db5c5dc9a0af753e007167218a3e3))
|
||||
* fix: display error when inputted SAS doesn't contain token ([#243](https://github.com/microsoft/OCR-Form-Tools/commit/9826ca8504549f23057c9cad1baebc5e9d1f6fe7))
|
||||
### 2.0.0-25d3298 (05-04-2020)
|
||||
* feat: track document count for tags ([#231](https://github.com/microsoft/OCR-Form-Tools/commit/70a6e43dc54239cdc153d5d328b17c1dfa0f085f))
|
||||
* fix: display error when inputted service URI contains path or query ([#234](https://github.com/microsoft/OCR-Form-Tools/commit/04a16961b37ad5b5d01fc4c93addaaf69cbf0e72))
|
||||
* feat: add link in status bar to CHANGELOG ([#233](https://github.com/microsoft/OCR-Form-Tools/commit/e66646a13263239213580378bbd2d8462d7e22b6))
|
||||
### 2.0.0-f6c8ffa (05-01-2020)
|
||||
* refactor: change checkbox to selectionMark ([#223](https://github.com/microsoft/OCR-Form-Tools/commit/f6c8ffad6edf23f6241f314e9456da92bc1a8402))
|
||||
### 2.0.0-f3e42f6 (04-30-2020)
|
||||
* feat: display post-processed value in analyzed results ([#229](https://github.com/microsoft/OCR-Form-Tools/commit/f3e42f6e8e9e934f1a241921dbe4a1e8d311bb46))
|
||||
### 2.0.0-f068866 (04-28-2020)
|
||||
* fix: hide sprin in tag input control when open an empty folder ([#220](https://github.com/microsoft/OCR-Form-Tools/commit/f0688668df2e676fce9749fad8ec9d39e56697cf))
|
||||
* perf: cache images, reduce canvas size, and fix memory leak for asset preview ([#218](https://github.com/microsoft/OCR-Form-Tools/commit/e8ad9a3bebf2a1ae210e0e1fa3eebba564592c4c))
|
||||
### 2.0.0-595a512 (04-24-2020)
|
||||
* fix: align rotated picture asset with OCR result
|
||||
### 2.0.0-51c02cc (04-20-2020)
|
||||
* fix: scrollbar fix when page size changes
|
||||
* fix: Add split pane to fix too long tag name is invisible in right sidebar
|
||||
### 2.0.0-202fb2f (04-20-2020)
|
||||
* perf: improve assets loading performance and fix some bugs
|
||||
### 2.0.0-bce554e (04-16-2020)
|
||||
* perf: improve Azure Blob file list performance
|
||||
* feat: support URL upload for predicting file
|
||||
### 2.0.0-ef18425 (04-09-2020)
|
||||
* feat: enable checkbox labeling (preview)
|
||||
|
|
|
@ -43,6 +43,9 @@
|
|||
"reactstrap": "^8.2.0",
|
||||
"redux": "^4.0.4",
|
||||
"redux-thunk": "^2.3.0",
|
||||
"rfdc": "^1.1.4",
|
||||
"rimraf": "^3.0.2",
|
||||
"serialize-javascript": "^5.0.1",
|
||||
"shortid": "^2.2.15",
|
||||
"utif": "^3.1.0",
|
||||
"vott-react": "^0.2.12"
|
||||
|
|
Различия файлов скрыты, потому что одна или несколько строк слишком длинны
|
@ -42,6 +42,8 @@ export const constants = {
|
|||
autoLabelBatchSizeMax: 10,
|
||||
autoLabelBatchSizeMin: 3,
|
||||
showOriginLabelsByDefault: true,
|
||||
fieldsSchema: "http://www.azure.com/schema/formrecognizer/fields.json",
|
||||
labelsSchema: "http://www.azure.com/schema/formrecognizer/labels.json",
|
||||
|
||||
pdfjsWorkerSrc(version: string) {
|
||||
return `https://fotts.azureedge.net/npm/pdfjs-dist/${version}/pdf.worker.js`;
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
import {IAppStrings} from "../strings";
|
||||
import { IAppStrings } from "../strings";
|
||||
|
||||
/*eslint-disable no-template-curly-in-string, no-multi-str*/
|
||||
|
||||
|
@ -271,7 +271,7 @@ export const english: IAppStrings = {
|
|||
localFile: "Local file",
|
||||
url: "URL",
|
||||
},
|
||||
layoutPredict:{
|
||||
layoutPredict: {
|
||||
layout: "Layout",
|
||||
title: "Layout analyze",
|
||||
inProgress: "Analysis in progress...",
|
||||
|
@ -337,7 +337,43 @@ export const english: IAppStrings = {
|
|||
autoLabel: "Auto-labeled: ",
|
||||
revised: "Revised: ",
|
||||
},
|
||||
regionTableTags: {
|
||||
configureTag: {
|
||||
errors: {
|
||||
atLeastOneColumn: "Please assign at least one column.",
|
||||
atLeastOneRow: "Please assign at least one row.",
|
||||
checkFields: "Please check if you filled out all required fields correctly.",
|
||||
assignTagName: "Tag name cannot be empty",
|
||||
notUniqueTagName: "Tag name should be unique",
|
||||
emptyTagName: "Please assign name for your table tag.",
|
||||
emptyName: "Name cannot be empty",
|
||||
notUniqueName: "Name should be unique",
|
||||
notCompatibleTableColOrRowType: "\${kind}\ type is not compatible with this type. If you want to change type of this \${kind}\, please remove or assign all labels which using this \${kind}\ in your project.",
|
||||
}
|
||||
},
|
||||
tableLabeling: {
|
||||
title: "Label table",
|
||||
tableName: "Table name",
|
||||
description: {
|
||||
title: "To start labeling your table:",
|
||||
stepOne: "Select the words on the document you want to label",
|
||||
stepTwo: "Click the table cell you want to label selected words to",
|
||||
},
|
||||
buttons: {
|
||||
done: "Done",
|
||||
reconfigureTable: "Reconfigure table",
|
||||
addRow: "Add row"
|
||||
},
|
||||
},
|
||||
confirm: {
|
||||
reconfigure: {
|
||||
title: "Reconfigure tag",
|
||||
message: "Are you sure you want to reconfigure this tag? \n It will be reconfigured for all documents.",
|
||||
}
|
||||
}
|
||||
},
|
||||
toolbar: {
|
||||
addTable: "Add new table tag",
|
||||
add: "Add new tag",
|
||||
onlyShowCurrentPageTags: "Only show tags used in current page",
|
||||
showAllTags: "Show all tags",
|
||||
|
@ -524,7 +560,7 @@ export const english: IAppStrings = {
|
|||
runOcrOnAllDocuments: "Run Layout on all documents",
|
||||
runAutoLabelingCurrentDocument: "Auto-label the current document",
|
||||
runAutoLabelingOnMultipleUnlabeledDocuments: "Auto-label multiple unlabeled documents",
|
||||
noPredictModelOnProject: "Predict model not avaliable, please train the model first.",
|
||||
noPredictModelOnProject: "Predict model not available, please train the model first.",
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -620,8 +656,8 @@ export const english: IAppStrings = {
|
|||
description: "Select all labels for a tag on document and press 'delete' key"
|
||||
},
|
||||
groupSelect: {
|
||||
name: "Select multiple words by drawing a bounding box around encompased words",
|
||||
description: "Press and hold the shift key. Then, click and hold left mouse button. Then, drag the pointer to draw the bounding box around encompased words"
|
||||
name: "Select multiple words by drawing a bounding box around encompassed words",
|
||||
description: "Press and hold the shift key. Then, click and hold left mouse button. Then, drag the pointer to draw the bounding box around encompassed words"
|
||||
}
|
||||
},
|
||||
headers: {
|
||||
|
@ -642,7 +678,7 @@ export const english: IAppStrings = {
|
|||
},
|
||||
genericRenderError: {
|
||||
title: "Error Loading Application",
|
||||
message: "An error occured while rendering the application. Please try again",
|
||||
message: "An error occurred while rendering the application. Please try again",
|
||||
},
|
||||
projectInvalidSecurityToken: {
|
||||
title: "Error loading project file",
|
||||
|
@ -777,7 +813,7 @@ export const english: IAppStrings = {
|
|||
tokenNameExist: "Warning! You already have token with same name as in shared project. Please create a new token, and update the existing project which uses ''${sharedTokenName}'' with new token name."
|
||||
},
|
||||
copy: {
|
||||
success: "Project token copied to clipboard and ready to share. Reciever of project token can click 'Open Cloud Project' from the Home page to use shared token.",
|
||||
success: "Project token copied to clipboard and ready to share. Receiver of project token can click 'Open Cloud Project' from the Home page to use shared token.",
|
||||
}
|
||||
},
|
||||
};
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
import {IAppStrings} from "../strings";
|
||||
import { IAppStrings } from "../strings";
|
||||
|
||||
/*eslint-disable no-template-curly-in-string, no-multi-str*/
|
||||
|
||||
|
@ -336,7 +336,44 @@ export const spanish: IAppStrings = {
|
|||
autoLabel: "Auto-etiquetado: ",
|
||||
revised: "Revisado: ",
|
||||
},
|
||||
regionTableTags: {
|
||||
configureTag: {
|
||||
errors: {
|
||||
atLeastOneColumn: "Asigne al menos una columna.",
|
||||
atLeastOneRow: "Asigne al menos una fila.",
|
||||
checkFields: "Verifique si completó todos los campos obligatorios correctamente.",
|
||||
assignTagName: "El nombre de la etiqueta no puede estar vacío.",
|
||||
notUniqueTagName: "El nombre de la etiqueta debe ser único",
|
||||
emptyTagName: "Asigne un nombre para la etiqueta de su mesa.",
|
||||
emptyName: "El nombre no puede estar vacío",
|
||||
notUniqueName: "El nombre debe ser único",
|
||||
notCompatibleTableColOrRowType: "El tipo $ {kind} no es compatible con este tipo. Si desea cambiar el tipo de este $ {kind}, elimine o asigne todas las etiquetas que usan este $ {kind} en su proyecto."
|
||||
}
|
||||
},
|
||||
tableLabeling: {
|
||||
title: "Tabla de etiquetas",
|
||||
tableName: "Nombre de la tabla",
|
||||
description: {
|
||||
title: "Para comenzar a etiquetar su mesa:",
|
||||
stepOne: "Seleccione las palabras del documento que desea etiquetar",
|
||||
stepTwo: "Haga clic en la celda de la tabla a la que desea etiquetar las palabras seleccionadas",
|
||||
},
|
||||
buttons: {
|
||||
done: "Hecho",
|
||||
reconfigureTable: "Reconfigurar la tabla",
|
||||
addRow: "Añadir fila"
|
||||
}
|
||||
},
|
||||
confirm: {
|
||||
reconfigure: {
|
||||
title: "Reconfigurar etiqueta",
|
||||
message: "¿Está seguro de que desea volver a configurar esta etiqueta?\n Se volverá a configurar para todos los documentos.",
|
||||
}
|
||||
}
|
||||
|
||||
},
|
||||
toolbar: {
|
||||
addTable: "Agregar nueva etiqueta",
|
||||
add: "Agregar nueva etiqueta",
|
||||
onlyShowCurrentPageTags: "Mostrar solo las etiquetas utilizadas en la página actual",
|
||||
showAllTags: "Mostrar todas las etiquetas",
|
||||
|
|
|
@ -489,6 +489,7 @@ export default class MockFactory {
|
|||
saveAssetMetadataAndCleanEmptyLabel: jest.fn(()=> Promise.resolve()),
|
||||
updateProjectTag: jest.fn(() => Promise.resolve()),
|
||||
deleteProjectTag: jest.fn(() => Promise.resolve()),
|
||||
reconfigureTableTag: jest.fn(() => Promise.resolve()),
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -306,22 +306,23 @@ export interface IAppStrings {
|
|||
color: string,
|
||||
}
|
||||
toolbar: {
|
||||
add: string,
|
||||
onlyShowCurrentPageTags: string,
|
||||
showAllTags: string,
|
||||
add: string;
|
||||
addTable: string;
|
||||
contextualMenu: string;
|
||||
delete: string;
|
||||
edit: string;
|
||||
format: string;
|
||||
lock: string;
|
||||
moveDown: string;
|
||||
moveUp: string;
|
||||
rename: string;
|
||||
search: string;
|
||||
type: string;
|
||||
vertiline: string;
|
||||
onlyShowCurrentPageTags:string,
|
||||
showAllTags:string,
|
||||
showOriginLabels: string
|
||||
hideOriginLabels: 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,
|
||||
|
@ -346,12 +347,47 @@ export interface IAppStrings {
|
|||
notCompatibleTagType: string,
|
||||
checkboxPerTagLimit: string,
|
||||
notCompatibleWithDrawnRegionTag: string,
|
||||
replaceAllExitingLabels: string,
|
||||
replaceAllExitingLabelsTitle: string,
|
||||
replaceAllExitingLabels:string,
|
||||
replaceAllExitingLabelsTitle:string,
|
||||
},
|
||||
preText: {
|
||||
autoLabel: string,
|
||||
revised: string,
|
||||
preText:{
|
||||
autoLabel:string,
|
||||
revised:string,
|
||||
}
|
||||
regionTableTags: {
|
||||
configureTag: {
|
||||
errors: {
|
||||
atLeastOneColumn: string,
|
||||
atLeastOneRow: string,
|
||||
checkFields: string,
|
||||
assignTagName: string,
|
||||
notUniqueTagName: string,
|
||||
emptyTagName: string,
|
||||
emptyName: string,
|
||||
notUniqueName: string,
|
||||
notCompatibleTableColOrRowType: string;
|
||||
},
|
||||
},
|
||||
tableLabeling: {
|
||||
title: string,
|
||||
description: {
|
||||
title: string,
|
||||
stepOne: string,
|
||||
stepTwo: string,
|
||||
},
|
||||
tableName: string,
|
||||
buttons: {
|
||||
done: string,
|
||||
reconfigureTable: string,
|
||||
addRow: string,
|
||||
}
|
||||
},
|
||||
confirm: {
|
||||
reconfigure: {
|
||||
title: string,
|
||||
message: string,
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
connections: {
|
||||
|
@ -677,7 +713,7 @@ interface IErrorMetadata {
|
|||
message: string,
|
||||
}
|
||||
|
||||
interface IStrings extends LocalizedStringsMethods, IAppStrings { }
|
||||
interface IStrings extends LocalizedStringsMethods, IAppStrings {}
|
||||
|
||||
export const strings: IStrings = new LocalizedStrings({
|
||||
en: english,
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import {createTheme, IPalette} from "@fluentui/react";
|
||||
import {createTheme, IPalette, DefaultPalette} from "@fluentui/react";
|
||||
|
||||
const rightPaneDefaultButtonPalette = {
|
||||
themePrimary: "#E9ECEF",
|
||||
|
@ -306,6 +306,7 @@ const subMenuPalette = {
|
|||
|
||||
const rightPaneDefaultButtonTheme = createTheme({palette: rightPaneDefaultButtonPalette});
|
||||
const defaultDarkTheme = createTheme({palette: DarkDefaultPalette});
|
||||
const defaultTheme = createTheme({palette: DefaultPalette});
|
||||
const whiteTheme = createTheme({palette: whiteButtonPalette});
|
||||
const redTheme = createTheme({palette: redButtonPalette});
|
||||
const greenTheme = createTheme({palette: greenButtonPalette});
|
||||
|
@ -356,6 +357,9 @@ export function getGreenWithWhiteBackgroundTheme() {
|
|||
export function getDefaultDarkTheme() {
|
||||
return defaultDarkTheme;
|
||||
}
|
||||
export function getDefaultTheme() {
|
||||
return defaultTheme;
|
||||
}
|
||||
|
||||
export function getSubMenuTheme() {
|
||||
return subMenuTheme;
|
||||
|
|
|
@ -2,9 +2,10 @@
|
|||
// Licensed under the MIT license.
|
||||
|
||||
import Guard from "./guard";
|
||||
import { IProject, ISecurityToken, IProviderOptions, ISecureString, ITag } from "../models/applicationState";
|
||||
import { IProject, ISecurityToken, IProviderOptions, ISecureString, ITag, FieldType, FieldFormat } from "../models/applicationState";
|
||||
import { encryptObject, decryptObject, encrypt, decrypt } from "./crypto";
|
||||
import UTIF from "utif";
|
||||
import { useState, useEffect } from 'react';
|
||||
import {constants} from "./constants";
|
||||
import _ from "lodash";
|
||||
import JsZip from 'jszip';
|
||||
|
@ -198,7 +199,7 @@ export async function throttle<T>(max: number, arr: T[], worker: (payload: T) =>
|
|||
}
|
||||
|
||||
export function delay(ms: number) {
|
||||
return new Promise((resolve) => {
|
||||
return new Promise<void>((resolve) => {
|
||||
setTimeout(() => {
|
||||
resolve();
|
||||
}, ms);
|
||||
|
@ -361,6 +362,64 @@ export function fixedEncodeURIComponent(str: string) {
|
|||
})
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Filters tag's format according to chosen tag's type
|
||||
* @param FieldType The json object
|
||||
* @returns [] of corresponding tag's formats
|
||||
*/
|
||||
export function filterFormat(type: FieldType | string): any[] {
|
||||
switch (type) {
|
||||
case FieldType.String:
|
||||
return [
|
||||
FieldFormat.NotSpecified,
|
||||
FieldFormat.Alphanumeric,
|
||||
FieldFormat.NoWhiteSpaces,
|
||||
];
|
||||
case FieldType.Number:
|
||||
return [
|
||||
FieldFormat.NotSpecified,
|
||||
FieldFormat.Currency,
|
||||
];
|
||||
case FieldType.Date:
|
||||
return [
|
||||
FieldFormat.NotSpecified,
|
||||
FieldFormat.DMY,
|
||||
FieldFormat.MDY,
|
||||
FieldFormat.YMD,
|
||||
];
|
||||
case FieldType.Object:
|
||||
case FieldType.Array:
|
||||
return [
|
||||
FieldFormat.NotSpecified,
|
||||
];
|
||||
default:
|
||||
return [ FieldFormat.NotSpecified ];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* UseDebounce - custom React hook for handling fast changing values, the hook re-call only if value or delay changes
|
||||
* @param value The value to be changed
|
||||
* @param delay - delay after which the change will be registered in milliseconds
|
||||
*/
|
||||
export function useDebounce(value: any, delay: number) {
|
||||
const [debouncedValue, setDebouncedValue] = useState(value);
|
||||
useEffect(
|
||||
() => {
|
||||
// Update debounced value after delay
|
||||
const delayHandler = setTimeout(() => {
|
||||
setDebouncedValue(value);
|
||||
}, delay);
|
||||
// cleanup
|
||||
return () => {
|
||||
clearTimeout(delayHandler);
|
||||
};
|
||||
},
|
||||
[value, delay]
|
||||
);
|
||||
return debouncedValue;
|
||||
}
|
||||
export function getAPIVersion(projectAPIVersion: string): string {
|
||||
return (constants.enableAPIVersionSelection && projectAPIVersion) ? projectAPIVersion : constants.apiVersion;
|
||||
}
|
||||
|
@ -416,6 +475,20 @@ export function downloadFile(data: any, fileName: string, prefix?: string): void
|
|||
fileLink.click();
|
||||
}
|
||||
|
||||
export function getTagCategory (tagType: string) {
|
||||
switch (tagType) {
|
||||
case FieldType.SelectionMark:
|
||||
case "checkbox":
|
||||
return "checkbox";
|
||||
case FieldType.Object:
|
||||
return FieldType.Object;
|
||||
case FieldType.Array:
|
||||
return FieldType.Array;
|
||||
default:
|
||||
return "text";
|
||||
}
|
||||
}
|
||||
|
||||
export type zipData = {
|
||||
fileName: string;
|
||||
data: any;
|
||||
|
|
|
@ -15,12 +15,12 @@
|
|||
"unicode": "E4C7"
|
||||
},
|
||||
{
|
||||
"name": "AddTo",
|
||||
"unicode": "ECC8"
|
||||
"name": "AddTable",
|
||||
"unicode": "E4C6"
|
||||
},
|
||||
{
|
||||
"name": "AlertSolid",
|
||||
"unicode": "F331"
|
||||
"name": "AddTo",
|
||||
"unicode": "ECC8"
|
||||
},
|
||||
{
|
||||
"name": "AutoEnhanceOff",
|
||||
|
@ -30,6 +30,10 @@
|
|||
"name": "AutoEnhanceOn",
|
||||
"unicode": "E78D"
|
||||
},
|
||||
{
|
||||
"name": "AlertSolid",
|
||||
"unicode": "F331"
|
||||
},
|
||||
{
|
||||
"name": "AzureAPIManagement",
|
||||
"unicode": "F37F"
|
||||
|
@ -122,6 +126,10 @@
|
|||
"name": "Edit",
|
||||
"unicode": "E70F"
|
||||
},
|
||||
{
|
||||
"name": "EditTable",
|
||||
"unicode": "E4C4"
|
||||
},
|
||||
{
|
||||
"name": "FieldChanged",
|
||||
"unicode": "F2C3"
|
||||
|
@ -134,6 +142,10 @@
|
|||
"name": "Filter",
|
||||
"unicode": "E71C"
|
||||
},
|
||||
{
|
||||
"name": "FixedColumnWidth",
|
||||
"unicode": "E3EA"
|
||||
},
|
||||
{
|
||||
"name": "GroupedList",
|
||||
"unicode": "EF74"
|
||||
|
@ -158,6 +170,14 @@
|
|||
"name": "Info",
|
||||
"unicode": "E946"
|
||||
},
|
||||
{
|
||||
"name": "InsertColumnsRight",
|
||||
"unicode": "F64B"
|
||||
},
|
||||
{
|
||||
"name": "InsertRowsBelow",
|
||||
"unicode": "F64D"
|
||||
},
|
||||
{
|
||||
"name": "Insights",
|
||||
"unicode": "E3AF"
|
||||
|
@ -270,6 +290,18 @@
|
|||
"name": "Table",
|
||||
"unicode": "ED86"
|
||||
},
|
||||
{
|
||||
"name": "TableBrandedColumn",
|
||||
"unicode": "E3F1"
|
||||
},
|
||||
{
|
||||
"name": "TableBrandedRow",
|
||||
"unicode": "E3EE"
|
||||
},
|
||||
{
|
||||
"name": "TableGroup",
|
||||
"unicode": "F6D9"
|
||||
},
|
||||
{
|
||||
"name": "Tag",
|
||||
"unicode": "E8EC"
|
||||
|
@ -290,6 +322,10 @@
|
|||
"name": "Up",
|
||||
"unicode": "E74A"
|
||||
},
|
||||
{
|
||||
"name": "UpdateRestore",
|
||||
"unicode": "E777"
|
||||
},
|
||||
{
|
||||
"name": "View",
|
||||
"unicode": "E890"
|
||||
|
@ -307,4 +343,4 @@
|
|||
"unicode": "E71F"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
|
@ -119,11 +119,28 @@ 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;
|
||||
}
|
||||
|
||||
export interface ITableTag extends ITag {
|
||||
fields?: ITableField[];
|
||||
itemType?: string;
|
||||
definition?: ITableDefinition,
|
||||
visualizationHint?: TableVisualizationHint,
|
||||
}
|
||||
|
||||
export enum TableHeaderTypeAndFormat {
|
||||
Rows = "rows",
|
||||
Columns = "columns"
|
||||
}
|
||||
|
||||
export enum TableVisualizationHint {
|
||||
Horizontal = "horizontal",
|
||||
Vertical = "vertical",
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -217,7 +234,14 @@ export interface IRegion {
|
|||
boundingBox?: IBoundingBox,
|
||||
value?: string,
|
||||
pageNumber: number,
|
||||
isTableRegion?: boolean,
|
||||
changed?: boolean,
|
||||
|
||||
}
|
||||
|
||||
export interface ITableRegion extends IRegion {
|
||||
rowKey: string,
|
||||
columnKey: string,
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -228,6 +252,8 @@ export interface ILabelData {
|
|||
document: string,
|
||||
labelingState?: AssetLabelingState;
|
||||
labels: ILabel[],
|
||||
tableLabels?: ITableLabel[],
|
||||
$schema?: string,
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -244,6 +270,18 @@ export interface ILabel {
|
|||
revised?: boolean;
|
||||
}
|
||||
|
||||
export interface ITableLabel {
|
||||
tableKey: string,
|
||||
labels: ITableCellLabel[],
|
||||
}
|
||||
|
||||
export interface ITableCellLabel {
|
||||
rowKey: string,
|
||||
columnKey: string,
|
||||
value: IFormRegion[],
|
||||
revised?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* @name - IFormRegion
|
||||
* @description - Defines a region which consumed by FormRecognizer
|
||||
|
@ -290,13 +328,39 @@ export interface ISecurityToken {
|
|||
}
|
||||
|
||||
export interface IField {
|
||||
fieldKey: string,
|
||||
fieldType: FieldType,
|
||||
fieldFormat: FieldFormat,
|
||||
fieldKey: string;
|
||||
fieldType: FieldType;
|
||||
fieldFormat: FieldFormat;
|
||||
}
|
||||
|
||||
export interface ITableKeyField extends IField {
|
||||
documentCount?: number;
|
||||
}
|
||||
|
||||
export interface ITableField extends IField {
|
||||
itemType?: string;
|
||||
fields?: ITableField[];
|
||||
visualizationHint?: TableVisualizationHint;
|
||||
}
|
||||
|
||||
export interface ITableDefinition extends IField {
|
||||
itemType?: string;
|
||||
fields?: ITableField[];
|
||||
}
|
||||
|
||||
export interface ITableConfigItem {
|
||||
name: string,
|
||||
format: string,
|
||||
type: string;
|
||||
originalName?: string;
|
||||
originalFormat?: string,
|
||||
originalType?: string;
|
||||
}
|
||||
|
||||
export interface IFieldInfo {
|
||||
schema?: string,
|
||||
fields: IField[],
|
||||
definitions?: any,
|
||||
}
|
||||
|
||||
export interface IRecentModel {
|
||||
|
@ -434,12 +498,21 @@ export enum FieldType {
|
|||
Time = "time",
|
||||
Integer = "integer",
|
||||
SelectionMark = "selectionMark",
|
||||
Array = "array",
|
||||
Object = "object",
|
||||
}
|
||||
|
||||
export enum LabelType {
|
||||
DrawnRegion = "region"
|
||||
}
|
||||
|
||||
export enum TableElements {
|
||||
rows = "rows",
|
||||
row = "row",
|
||||
columns = "columns",
|
||||
column = "column",
|
||||
}
|
||||
|
||||
export enum FieldFormat {
|
||||
NotSpecified = "not-specified",
|
||||
Currency = "currency",
|
||||
|
@ -463,3 +536,15 @@ export enum ImageMapParent {
|
|||
Predict = "predict",
|
||||
Editor = "editor",
|
||||
}
|
||||
|
||||
export enum TagInputMode {
|
||||
Basic = "basic",
|
||||
ConfigureTable = "configureTable",
|
||||
LabelTable = "labelTable",
|
||||
}
|
||||
|
||||
export enum AnalyzedTagsMode {
|
||||
default = "default",
|
||||
LoadingRecentModel = "loadingRecentModel",
|
||||
ViewTable = "viewTable",
|
||||
}
|
||||
|
|
|
@ -256,7 +256,7 @@ export class AzureBlobStorage implements IStorageProvider {
|
|||
return asset;
|
||||
}
|
||||
}
|
||||
else{
|
||||
else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
@import './../../../../assets/sass/theme.scss';
|
||||
|
||||
.spinner-container {
|
||||
display: flex;
|
||||
width: 16rem;
|
||||
height: 4rem;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
|
@ -11,9 +11,12 @@ import {
|
|||
ITheme,
|
||||
PrimaryButton,
|
||||
DefaultButton,
|
||||
SpinnerSize,
|
||||
Spinner
|
||||
} from "@fluentui/react";
|
||||
import { MessageFormatHandler } from "../messageBox/messageBox";
|
||||
import { getDarkTheme } from "../../../../common/themes";
|
||||
import { getDarkTheme, getDefaultDarkTheme } from "../../../../common/themes";
|
||||
import "./confirm.scss";
|
||||
|
||||
/**
|
||||
* Properties for Confirm Component
|
||||
|
@ -26,6 +29,7 @@ import { getDarkTheme } from "../../../../common/themes";
|
|||
export interface IConfirmProps {
|
||||
title?: string;
|
||||
message: string | ReactElement<any> | MessageFormatHandler;
|
||||
loadMessage?: string;
|
||||
confirmButtonText?: string;
|
||||
cancelButtonText?: string;
|
||||
confirmButtonTheme?: ITheme;
|
||||
|
@ -40,6 +44,7 @@ export interface IConfirmProps {
|
|||
export interface IConfirmState {
|
||||
params: any[];
|
||||
hideDialog: boolean;
|
||||
loading: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -54,12 +59,14 @@ export default class Confirm extends React.Component<IConfirmProps, IConfirmStat
|
|||
this.state = {
|
||||
params: null,
|
||||
hideDialog: true,
|
||||
loading: false,
|
||||
};
|
||||
|
||||
this.open = this.open.bind(this);
|
||||
this.close = this.close.bind(this);
|
||||
this.onConfirmClick = this.onConfirmClick.bind(this);
|
||||
this.onCancelClick = this.onCancelClick.bind(this);
|
||||
this.load = this.load.bind(this)
|
||||
}
|
||||
|
||||
public render() {
|
||||
|
@ -69,8 +76,9 @@ export default class Confirm extends React.Component<IConfirmProps, IConfirmStat
|
|||
},
|
||||
scopedSettings: {},
|
||||
};
|
||||
|
||||
const { confirmButtonTheme } = this.props;
|
||||
const { hideDialog } = this.state;
|
||||
const { hideDialog, loading } = this.state;
|
||||
|
||||
return (
|
||||
<Customizer {...dark}>
|
||||
|
@ -78,15 +86,26 @@ export default class Confirm extends React.Component<IConfirmProps, IConfirmStat
|
|||
<Dialog
|
||||
hidden={hideDialog}
|
||||
onDismiss={this.close}
|
||||
dialogContentProps={{
|
||||
dialogContentProps={!loading ? {
|
||||
type: DialogType.normal,
|
||||
title: this.props.title,
|
||||
subText: this.getMessage(this.props.message),
|
||||
}}
|
||||
} : null}
|
||||
modalProps={{
|
||||
isBlocking: true,
|
||||
}}
|
||||
>
|
||||
{loading && this.props.loadMessage &&
|
||||
<div className="spinner-container">
|
||||
<Spinner
|
||||
label={this.props.loadMessage}
|
||||
labelPosition="right"
|
||||
theme={getDefaultDarkTheme()}
|
||||
size={SpinnerSize.large}
|
||||
/>
|
||||
</div>
|
||||
|
||||
}
|
||||
<DialogFooter>
|
||||
<PrimaryButton
|
||||
theme={confirmButtonTheme}
|
||||
|
@ -114,12 +133,20 @@ export default class Confirm extends React.Component<IConfirmProps, IConfirmStat
|
|||
* Close Confirm Dialog
|
||||
*/
|
||||
public close(): void {
|
||||
this.setState({ hideDialog: true });
|
||||
this.setState({ hideDialog: true, loading: false });
|
||||
}
|
||||
|
||||
public load(): void {
|
||||
this.setState({loading: true});
|
||||
}
|
||||
|
||||
private onConfirmClick() {
|
||||
this.props.onConfirm.apply(null, this.state.params);
|
||||
this.close();
|
||||
if (this.props.loadMessage) {
|
||||
this.load();
|
||||
} else {
|
||||
this.close();
|
||||
}
|
||||
}
|
||||
|
||||
private onCancelClick() {
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
}
|
||||
.sourceDropdown {
|
||||
width: 95px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.local-file {
|
||||
float: right;
|
||||
|
|
|
@ -87,7 +87,7 @@ export class ImageMap extends React.Component<IImageMapProps> {
|
|||
private modify: Modify;
|
||||
private snap: Snap;
|
||||
|
||||
private drawnFeatures: Collection = new Collection([], {unique: true});
|
||||
private drawnFeatures: Collection = new Collection([], { unique: true });
|
||||
public modifyStartFeatureCoordinates: any = {};
|
||||
|
||||
private imageExtent: number[];
|
||||
|
@ -204,7 +204,7 @@ export class ImageMap extends React.Component<IImageMapProps> {
|
|||
onMouseEnter={this.handlePointerEnterImageMap}
|
||||
className="map-wrapper"
|
||||
>
|
||||
<div style={{cursor: this.getCursor()}} id="map" className="map" ref={(el) => this.mapElement = el} />
|
||||
<div style={{ cursor: this.getCursor() }} id="map" className="map" ref={(el) => this.mapElement = el} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -483,7 +483,7 @@ export class ImageMap extends React.Component<IImageMapProps> {
|
|||
this.drawRegionVectorLayer?.getSource().clear();
|
||||
this.drawnLabelVectorLayer?.getSource().clear();
|
||||
|
||||
this.drawnFeatures = new Collection([], {unique: true});
|
||||
this.drawnFeatures = new Collection([], { unique: true });
|
||||
|
||||
this.drawRegionVectorLayer.getSource().on("addfeature", (evt) => {
|
||||
this.pushToDrawnFeatures(evt.feature, this.drawnFeatures);
|
||||
|
|
|
@ -0,0 +1,219 @@
|
|||
@import "../../../../assets/sass/theme.scss";
|
||||
@import "./tagInputSize.scss";
|
||||
|
||||
|
||||
.config-view_container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
padding: 1px;
|
||||
padding-right: 12px;
|
||||
h5, h4 {
|
||||
margin-left: 12px;
|
||||
}
|
||||
|
||||
.table-name_input {
|
||||
width: 100%;
|
||||
padding-right: 10px;
|
||||
}
|
||||
|
||||
.columns_container, .rows_container {
|
||||
margin-top: 2rem;
|
||||
width: 99%;
|
||||
h5 {
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
.columns, .rows {
|
||||
width: 99%;
|
||||
margin-right: 2%;
|
||||
background-color: $darker-2;
|
||||
}
|
||||
.ms-DetailsRow-cellCheck {
|
||||
margin-right: -12px;
|
||||
margin-top: 23px;
|
||||
}
|
||||
|
||||
.ms-List-cell:not(:last-child){
|
||||
border-bottom: 1px solid $lighter-4;
|
||||
}
|
||||
|
||||
.column-name_input, .row-name_input {
|
||||
margin-top: 16px;
|
||||
}
|
||||
.input-label-original-name {
|
||||
color: rgb(177, 177, 177);
|
||||
margin-bottom: 2px;
|
||||
margin-right: 2px;
|
||||
text-align: left;
|
||||
margin-top: -24px;
|
||||
}
|
||||
}
|
||||
|
||||
.list_header {
|
||||
width: 100%;
|
||||
.ms-Button-label {
|
||||
margin: 0 -4px;
|
||||
}
|
||||
.list-headers {
|
||||
text-align: left;
|
||||
|
||||
&_name {
|
||||
text-align: left;
|
||||
}
|
||||
&_type {
|
||||
text-align: left;
|
||||
margin: 0 20px;
|
||||
}
|
||||
&_format {
|
||||
text-align: left;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.add_button {
|
||||
margin-top: 8px;
|
||||
margin-left: 0px;
|
||||
width: 140px
|
||||
}
|
||||
|
||||
.control-buttons_container {
|
||||
display: flex;
|
||||
margin-top: 2rem;
|
||||
margin-bottom: 1rem;
|
||||
justify-content: flex-end;
|
||||
.save {
|
||||
margin-left: 1rem;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.preview_container {
|
||||
margin-top: 2rem;
|
||||
h5 {
|
||||
margin-left: 0;
|
||||
}
|
||||
.tableName {
|
||||
&-current {
|
||||
padding: 0 1px 2px 1px;
|
||||
}
|
||||
&-original {
|
||||
margin-bottom: 0px;
|
||||
text-decoration: line-through;
|
||||
color: rgb(177, 177, 177);
|
||||
font-size: small;
|
||||
}
|
||||
}
|
||||
|
||||
.table_container {
|
||||
overflow: auto;
|
||||
margin-top: 1rem;
|
||||
.table {
|
||||
td, th {
|
||||
border: 2px solid #8D8D8D;
|
||||
color: white;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
.header {
|
||||
&_column, &_row {
|
||||
min-width: 130px;
|
||||
max-width: 200px;
|
||||
background-color: $lighter-3;
|
||||
border: 2px solid grey;
|
||||
text-align: center;
|
||||
padding: .125rem .5rem;
|
||||
.value {
|
||||
}
|
||||
.renamed-value {
|
||||
text-decoration: line-through;
|
||||
color: rgb(177, 177, 177);
|
||||
font-size: small;
|
||||
}
|
||||
}
|
||||
&_empty {
|
||||
border: 2px solid grey;
|
||||
background-color: $lighter-3;
|
||||
}
|
||||
}
|
||||
.table-cell {
|
||||
text-align: center;
|
||||
background-color: $darker-3;
|
||||
color: rgba(255, 255, 255, 0.75);
|
||||
height: 1.5rem;
|
||||
}
|
||||
.hidden {
|
||||
border: none;
|
||||
font-size: small;
|
||||
font-weight: 300;
|
||||
background-color: transparent;
|
||||
text-align: right;
|
||||
vertical-align: middle;
|
||||
color: rgba(255, 255, 255, 0.45);
|
||||
width: 3rem;
|
||||
padding-right: 0.4rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
.rowDynamic_message {
|
||||
margin-bottom: 4px;
|
||||
color: rgba(255, 255, 255, 0.8);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// ========= utility classes
|
||||
|
||||
.original-value, .renamed-value {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
.ml-12px {
|
||||
margin-left: 12px;
|
||||
}
|
||||
|
||||
.ms-TextField-errorMessage {
|
||||
padding: 0 0 .25rem .25rem;
|
||||
span {
|
||||
color: #db7272;
|
||||
}
|
||||
}
|
||||
|
||||
.ms-DetailsRow-cellCheck {
|
||||
display: flex;
|
||||
width: 22px;
|
||||
height: 28px;
|
||||
margin-right: -12px;
|
||||
margin-top: 6px;
|
||||
}
|
||||
|
||||
.renamed-header-value {
|
||||
|
||||
|
||||
}
|
||||
|
||||
.original-table-name {
|
||||
margin-left: 12px;
|
||||
color: rgb(177, 177, 177);
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
|
||||
.restore-button {
|
||||
color: #2d8eac;
|
||||
&:hover {
|
||||
color: #6ac5e1;
|
||||
}
|
||||
&:active {
|
||||
color: #91cee0;
|
||||
}
|
||||
}
|
||||
|
||||
.compact-row {
|
||||
border-bottom: 0.5px solid $lighter-4;
|
||||
}
|
||||
|
||||
.table-name-preview {
|
||||
font-weight: bold;
|
||||
}
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -0,0 +1,91 @@
|
|||
|
||||
@import "../../../../assets/sass/theme.scss";
|
||||
|
||||
.table-labeling_container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
padding-right: .5rem;
|
||||
h4 {
|
||||
padding-left: 1.25rem;
|
||||
}
|
||||
.labeling-guideline {
|
||||
margin: 1rem 1rem 2rem 1.5rem;
|
||||
color: rgba(255, 255, 255, 0.6);
|
||||
}
|
||||
.table-name {
|
||||
padding-left: 1.25rem;
|
||||
span {
|
||||
padding-bottom: .2rem;
|
||||
}
|
||||
}
|
||||
.add-row-button_container {
|
||||
margin-left: 1.5rem;
|
||||
margin-bottom: 3rem;
|
||||
margin-top: 1rem;
|
||||
}
|
||||
.table-view-container {
|
||||
overflow-x: auto;
|
||||
.viewed-table {
|
||||
margin-bottom: 1rem;
|
||||
.column_header {
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
min-width: 130px;
|
||||
max-width: 200px;
|
||||
background-color: $lighter-3;
|
||||
border: 2px solid grey;
|
||||
text-align: center;
|
||||
padding: .125rem .25rem;
|
||||
}
|
||||
.row_header {
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
min-width: 130px;
|
||||
max-width: 200px;
|
||||
border: 2px solid grey;
|
||||
background-color: $lighter-3;
|
||||
text-align: center;
|
||||
padding: .125rem .5rem;
|
||||
}
|
||||
.empty_header {
|
||||
border: 2px solid grey;
|
||||
background-color: $lighter-3;
|
||||
|
||||
}
|
||||
.table-cell {
|
||||
text-align: center;
|
||||
background-color: $darker-3;
|
||||
color: rgba(255, 255, 255, 0.75);
|
||||
&:hover {
|
||||
background-color: $lighter-1;
|
||||
}
|
||||
&:active {
|
||||
background-color: $lighter-2;
|
||||
|
||||
}
|
||||
}
|
||||
.hidden {
|
||||
border: none;
|
||||
background-color: transparent;
|
||||
text-align: center;
|
||||
color: rgba(255, 255, 255, 0.45);
|
||||
min-width: 12px;
|
||||
max-width: 48px;
|
||||
padding-right: 0.3rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.buttons-container {
|
||||
display: flex;
|
||||
padding: 1.5rem;
|
||||
justify-content: space-between;
|
||||
.button {
|
||||
width: 160px;
|
||||
&-reconfigure {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,171 @@
|
|||
import React from 'react';
|
||||
import "./tableTagConfig.scss";
|
||||
import { PrimaryButton, FontIcon, DefaultButton } from "@fluentui/react";
|
||||
import { getPrimaryGreenTheme, getPrimaryBlueTheme } from '../../../../common/themes';
|
||||
import { FieldFormat, FieldType, TagInputMode, IRegion, ITableTag, ITableRegion, IField, TableElements, ITableField, ITableKeyField, TableVisualizationHint } from '../../../../models/applicationState';
|
||||
import "./tableTagLabeling.scss";
|
||||
|
||||
import { strings } from "../../../../common/strings";
|
||||
|
||||
|
||||
interface ITableTagLabelingProps {
|
||||
setTagInputMode: (addTableMode: TagInputMode, selectedTableTagToLabel?: ITableTag, selectedTableTagBody?: ITableRegion[][][]) => void;
|
||||
selectedTag: ITableTag,
|
||||
selectedRegions?: IRegion[];
|
||||
onTagClick?: (tag: ITableTag) => void;
|
||||
selectedTableTagBody: ITableRegion[][][];
|
||||
handleTableCellClick: (iTableCellIndex: number, jTableCellIndex: number) => void;
|
||||
handleTableCellMouseEnter: (regions: IRegion[]) => void
|
||||
handleTableCellMouseLeave: () => void
|
||||
addRowToDynamicTable: () => void;
|
||||
splitPaneWidth?: number;
|
||||
}
|
||||
|
||||
|
||||
interface ITableTagLabelingState {
|
||||
selectedRowIndex: number;
|
||||
selectedColumnIndex: number;
|
||||
rows: ITableKeyField[],
|
||||
columns: ITableKeyField[],
|
||||
selectedTableTagBody: any,
|
||||
}
|
||||
|
||||
// @connect(mapStateToProps)
|
||||
export default class TableTagLabeling extends React.Component<ITableTagLabelingProps> {
|
||||
public state: ITableTagLabelingState = {
|
||||
selectedRowIndex: null,
|
||||
selectedColumnIndex: null,
|
||||
rows: this.props.selectedTag.type === FieldType.Array || this.props.selectedTag?.visualizationHint === TableVisualizationHint.Vertical ? this.props.selectedTag.fields : this.props.selectedTag.definition.fields,
|
||||
columns: this.props.selectedTag.type === FieldType.Array || this.props.selectedTag.visualizationHint === TableVisualizationHint.Vertical ? this.props.selectedTag.definition.fields : this.props.selectedTag.fields,
|
||||
selectedTableTagBody: this.props.selectedTableTagBody,
|
||||
};
|
||||
|
||||
public componentDidMount = async () => {
|
||||
if (this.props.selectedTag.type === FieldType.Array) {
|
||||
const rows = [{ fieldKey: "#0", fieldType: FieldType.String, fieldFormat: FieldFormat.NotSpecified }]
|
||||
for (let i = 1; i < this.props.selectedTableTagBody.length; i++) {
|
||||
rows.push({ fieldKey: "#" + i, fieldType: FieldType.String, fieldFormat: FieldFormat.NotSpecified });
|
||||
}
|
||||
this.setState({ rows });
|
||||
}
|
||||
}
|
||||
|
||||
public componentDidUpdate = async (prevProps: Readonly<ITableTagLabelingProps>, prevState: Readonly<ITableTagLabelingState>) => {
|
||||
if (this.props.selectedTableTagBody.length !== prevProps.selectedTableTagBody.length) {
|
||||
const rows = [{ fieldKey: "#0", fieldType: FieldType.String, fieldFormat: FieldFormat.NotSpecified }]
|
||||
for (let i = 1; i < this.props.selectedTableTagBody.length; i++) {
|
||||
rows.push({ fieldKey: "#" + i, fieldType: FieldType.String, fieldFormat: FieldFormat.NotSpecified });
|
||||
}
|
||||
this.setState({ rows });
|
||||
}
|
||||
}
|
||||
|
||||
public render() {
|
||||
return (
|
||||
<div className="table-labeling_container">
|
||||
<h4 className="mt-2">{strings.tags.regionTableTags.tableLabeling.title}</h4>
|
||||
<div className="labeling-guideline">
|
||||
{strings.tags.regionTableTags.tableLabeling.description.title}
|
||||
<ol>
|
||||
<li>{strings.tags.regionTableTags.tableLabeling.description.stepOne}</li>
|
||||
<li>{strings.tags.regionTableTags.tableLabeling.description.stepTwo}</li>
|
||||
</ol>
|
||||
</div>
|
||||
<h5 className="mb-4 table-name">
|
||||
<span style={{ borderBottom: `4px solid ${this.props.selectedTag.color}` }}>{`${strings.tags.regionTableTags.tableLabeling.tableName}: ${this.props.selectedTag.name}`}</span>
|
||||
</h5>
|
||||
{ (this.props.selectedTag.type === FieldType.Object && this.props.selectedTag.fields && this.props.selectedTag.definition.fields) || this.props.selectedTag.definition.fields ?
|
||||
<div className="table-view-container">
|
||||
<table className="viewed-table">
|
||||
<tbody>
|
||||
{this.getTableBody()}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
:
|
||||
<div>Missing fields. Please Reconfigure table.</div>
|
||||
}
|
||||
{this.props.selectedTag.type === FieldType.Array && <div className="add-row-button_container">
|
||||
<PrimaryButton
|
||||
theme={getPrimaryBlueTheme()}
|
||||
className="add_button ml-6"
|
||||
autoFocus={true}
|
||||
onClick={this.addRow}
|
||||
>
|
||||
<FontIcon iconName="Add" className="mr-2" />
|
||||
{strings.tags.regionTableTags.tableLabeling.buttons.addRow}
|
||||
</PrimaryButton>
|
||||
</div>}
|
||||
<div className="buttons-container">
|
||||
<PrimaryButton
|
||||
className="button-done"
|
||||
theme={getPrimaryGreenTheme()}
|
||||
onClick={() => {
|
||||
this.props.setTagInputMode(TagInputMode.Basic, null, null)
|
||||
}}
|
||||
>{strings.tags.regionTableTags.tableLabeling.buttons.done}
|
||||
</PrimaryButton>
|
||||
<DefaultButton
|
||||
className="button-reconfigure"
|
||||
theme={getPrimaryGreenTheme()}
|
||||
onClick={() => { this.props.setTagInputMode(TagInputMode.ConfigureTable) }}
|
||||
>{strings.tags.regionTableTags.tableLabeling.buttons.reconfigureTable}
|
||||
</DefaultButton>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
public getTableBody = () => {
|
||||
const table = { rows: this.state.rows, columns: this.state.columns };
|
||||
const selectedTableTagBody = this.props.selectedTableTagBody;
|
||||
const isRowDynamic = this.props.selectedTag.type === FieldType.Array;
|
||||
|
||||
let tableBody = null;
|
||||
if (table.rows && table.rows?.length !== 0 && table.columns.length !== 0) {
|
||||
tableBody = [];
|
||||
const rows = table[TableElements.rows];
|
||||
const columns = table[TableElements.columns];
|
||||
for (let i = 0; i < rows.length + 1; i++) {
|
||||
const tableRow = [];
|
||||
for (let j = 0; j < columns.length + 1; j++) {
|
||||
if (i === 0 && j !== 0) {
|
||||
tableRow.push(<th key={j} className={"column_header"}>{columns[j - 1].fieldKey}</th>);
|
||||
} else if (j === 0 && i !== 0) {
|
||||
tableRow.push(<th key={j} className={`row_header ${isRowDynamic ? "hidden" : ""}`}>{rows[i - 1].fieldKey}</th>);
|
||||
} else if (j === 0 && i === 0) {
|
||||
tableRow.push(<th key={j} className={`empty_header ${isRowDynamic ? "hidden" : ""}`} />);
|
||||
} else {
|
||||
tableRow.push(
|
||||
<td
|
||||
className={"table-cell"}
|
||||
onClick={() => this.handleCellClick(i - 1, j - 1)} key={j}
|
||||
onMouseEnter={() => this.handleTableCellMouseEnter(selectedTableTagBody[i - 1][j - 1])}
|
||||
onMouseLeave={() => this.handleTableCellMouseLeave()}
|
||||
>
|
||||
{selectedTableTagBody[i - 1][j - 1]?.find((tableRegion) => tableRegion.value === "") && <FontIcon className="pr-1 pl-1" iconName="FieldNotChanged" />}
|
||||
{selectedTableTagBody[i - 1][j - 1]?.map((tableRegion) => tableRegion.value).join(" ")}
|
||||
</td>);
|
||||
}
|
||||
}
|
||||
tableBody.push(<tr key={i}>{tableRow}</tr>);
|
||||
}
|
||||
}
|
||||
|
||||
return tableBody
|
||||
}
|
||||
|
||||
private addRow = () => {
|
||||
this.props.addRowToDynamicTable()
|
||||
};
|
||||
|
||||
private handleCellClick = (iToChange: number, jToChange: number) => {
|
||||
this.props.handleTableCellClick(iToChange, jToChange)
|
||||
}
|
||||
private handleTableCellMouseEnter = (regions: IRegion[]) => {
|
||||
this.props.handleTableCellMouseEnter(regions)
|
||||
}
|
||||
private handleTableCellMouseLeave = () => {
|
||||
this.props.handleTableCellMouseLeave();
|
||||
}
|
||||
}
|
|
@ -8,6 +8,8 @@
|
|||
&-input {
|
||||
display: flex;
|
||||
flex-grow: 1;
|
||||
overflow-y: auto;
|
||||
max-width: 100% !important;
|
||||
flex-direction: column;
|
||||
user-select: none;
|
||||
background: $lighter-1;
|
||||
|
@ -36,6 +38,18 @@
|
|||
&-container{
|
||||
overflow-x: visible;
|
||||
overflow-y: auto;
|
||||
// padding: 0 0 0 100px;
|
||||
// margin: 0 0 0 -100px;
|
||||
&::before{
|
||||
// content: " ";
|
||||
// display: inline-block;
|
||||
// position: absolute;
|
||||
// width: 80px;
|
||||
// height: 100%;
|
||||
// left: -80px;
|
||||
// background: linear-gradient(to right, #00000000 0%,#000000 100%);
|
||||
}
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -30,6 +30,16 @@ describe("Tag Input Component", () => {
|
|||
labels: [],
|
||||
onLabelEnter: jest.fn(),
|
||||
onLabelLeave: jest.fn(),
|
||||
tagInputMode: null,
|
||||
selectedTableTagToLabel: null,
|
||||
handleLabelTable: null,
|
||||
addRowToDynamicTable: null,
|
||||
reconfigureTableConfirm: null,
|
||||
handleTableCellClick: null,
|
||||
selectedTableTagBody: null,
|
||||
splitPaneWidth: null,
|
||||
handleTableCellMouseEnter: null,
|
||||
handleTableCellMouseLeave: null
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -2,30 +2,32 @@
|
|||
// Licensed under the MIT license.
|
||||
|
||||
import {
|
||||
ContextualMenu,
|
||||
ContextualMenuItemType,
|
||||
Customizer,
|
||||
FontIcon,
|
||||
IContextualMenuItem,
|
||||
ICustomizations,
|
||||
Spinner,
|
||||
SpinnerSize
|
||||
ContextualMenu, ContextualMenuItemType, Customizer,
|
||||
FontIcon, IContextualMenuItem, ICustomizations,
|
||||
Spinner, SpinnerSize, ChoiceGroup, IChoiceGroupOption
|
||||
} from "@fluentui/react";
|
||||
import { strings, interpolate } from "../../../../common/strings";
|
||||
import { getDarkTheme, getPrimaryRedTheme } from "../../../../common/themes";
|
||||
import { AlignPortal } from "../align/alignPortal";
|
||||
import { filterFormat, getNextColor, getTagCategory } from "../../../../common/utils";
|
||||
import {
|
||||
IRegion, ITag, ILabel, FieldType, FieldFormat,
|
||||
TagInputMode, FeatureCategory, ITableTag, ITableRegion,
|
||||
ITableConfigItem, ITableKeyField, ITableLabel, TableElements, TableVisualizationHint
|
||||
} from "../../../../models/applicationState";
|
||||
import { ColorPicker } from "../colorPicker";
|
||||
import "./tagInput.scss";
|
||||
import debounce from 'lodash/debounce';
|
||||
import React, {KeyboardEvent} from "react";
|
||||
import {toast} from "react-toastify";
|
||||
import {constants} from "../../../../common/constants";
|
||||
import {interpolate, strings} from "../../../../common/strings";
|
||||
import {getDarkTheme, getPrimaryRedTheme} from "../../../../common/themes";
|
||||
import {getNextColor} from "../../../../common/utils";
|
||||
import {FeatureCategory, FieldFormat, FieldType, ILabel, IRegion, ITag} from "../../../../models/applicationState";
|
||||
import React, { KeyboardEvent } from "react";
|
||||
import { constants } from "../../../../common/constants";
|
||||
import Confirm from "../../common/confirm/confirm";
|
||||
import {AlignPortal} from "../align/alignPortal";
|
||||
import {ColorPicker} from "../colorPicker";
|
||||
import "../condensedList/condensedList.scss";
|
||||
import "./tagInput.scss";
|
||||
import TagInputItem, {ITagClickProps, ITagInputItemProps} from "./tagInputItem";
|
||||
import TagInputItem, { ITagClickProps, ITagInputItemProps } from "./tagInputItem";
|
||||
import TagInputToolbar from "./tagInputToolbar";
|
||||
import { toast } from "react-toastify";
|
||||
import TableTagConfig from "./tableTagConfig"
|
||||
import TableTagLabeling from "./tableTagLabeling";
|
||||
// tslint:disable-next-line:no-var-requires
|
||||
const tagColors = require("../../common/tagColors.json");
|
||||
|
||||
|
@ -41,6 +43,7 @@ export enum TagOperationMode {
|
|||
ColorPicker,
|
||||
ContextualMenu,
|
||||
Rename,
|
||||
LabelTable,
|
||||
}
|
||||
|
||||
export interface ITagInputProps {
|
||||
|
@ -52,6 +55,9 @@ export interface ITagInputProps {
|
|||
selectedRegions?: IRegion[];
|
||||
/** The labels in the canvas */
|
||||
labels: ILabel[];
|
||||
encoded?: boolean;
|
||||
/** The tableLabels in the canvas */
|
||||
tableLabels?: ITableLabel[];
|
||||
/** The doc current page number */
|
||||
pageNumber: number;
|
||||
/** Tags that are currently locked for editing experience */
|
||||
|
@ -69,7 +75,7 @@ export interface ITagInputProps {
|
|||
/** Function to call when tag is renamed */
|
||||
onTagRename?: (oldTag: ITag, newTag: ITag, cancelCallback: () => void) => void;
|
||||
/** Function to call when tag is deleted */
|
||||
onTagDeleted?: (tagName: string) => void;
|
||||
onTagDeleted?: (tagName: string, tagType: FieldType, tagFormat: FieldFormat) => void;
|
||||
/** Always show tag input box */
|
||||
showTagInputBox?: boolean;
|
||||
/** Always show tag search box */
|
||||
|
@ -80,6 +86,17 @@ export interface ITagInputProps {
|
|||
onLabelLeave: (label: ILabel) => void;
|
||||
/** Function to handle tag change */
|
||||
onTagChanged?: (oldTag: ITag, newTag: ITag) => void;
|
||||
setTagInputMode?: (tagInputMode: TagInputMode, selectedTableTagToLabel?: ITableTag) => void;
|
||||
tagInputMode: TagInputMode;
|
||||
selectedTableTagToLabel: ITableTag;
|
||||
handleLabelTable: (tagInputMode: TagInputMode, selectedTableTagToLabel) => void;
|
||||
addRowToDynamicTable: () => void;
|
||||
reconfigureTableConfirm: (originalTagName: string, tagName: string, tagType: FieldType.Array | FieldType.Object, tagFormat: FieldFormat, visualizationHint: TableVisualizationHint, deletedColumns: ITableConfigItem[], deletedRows: ITableConfigItem[], newRows: ITableConfigItem[], newColumns: ITableConfigItem[]) => void;
|
||||
handleTableCellClick: (iTableCellIndex, jTableCellIndex) => void;
|
||||
selectedTableTagBody: ITableRegion[][][];
|
||||
handleTableCellMouseEnter: (regions) => void;
|
||||
handleTableCellMouseLeave: () => void;
|
||||
splitPaneWidth: number;
|
||||
onTagDoubleClick?: (label: ILabel) => void;
|
||||
}
|
||||
|
||||
|
@ -94,31 +111,6 @@ export interface ITagInputState {
|
|||
selectedTag: ITag;
|
||||
}
|
||||
|
||||
function filterFormat(type: FieldType): FieldFormat[] {
|
||||
switch (type) {
|
||||
case FieldType.String:
|
||||
return [
|
||||
FieldFormat.NotSpecified,
|
||||
FieldFormat.Alphanumeric,
|
||||
FieldFormat.NoWhiteSpaces,
|
||||
];
|
||||
case FieldType.Number:
|
||||
return [
|
||||
FieldFormat.NotSpecified,
|
||||
FieldFormat.Currency,
|
||||
];
|
||||
case FieldType.Date:
|
||||
return [
|
||||
FieldFormat.NotSpecified,
|
||||
FieldFormat.DMY,
|
||||
FieldFormat.MDY,
|
||||
FieldFormat.YMD,
|
||||
];
|
||||
default:
|
||||
return [ FieldFormat.NotSpecified ];
|
||||
}
|
||||
}
|
||||
|
||||
function isNameEqual(x: string, y: string) {
|
||||
return x.trim().toLocaleLowerCase() === y.trim().toLocaleLowerCase();
|
||||
}
|
||||
|
@ -172,92 +164,136 @@ export class TagInput extends React.Component<ITagInputProps, ITagInputState> {
|
|||
scopedSettings: {},
|
||||
};
|
||||
|
||||
const {selectedTag, tagOperation} = this.state;
|
||||
const { selectedTag, tagOperation } = this.state;
|
||||
const selectedTagRef = selectedTag ? this.tagItemRefs.get(selectedTag.name)?.getTagNameRef() : null;
|
||||
|
||||
return (
|
||||
<div className="tag-input">
|
||||
<div ref={this.headerRef} className="tag-input-header p-2">
|
||||
<span className="tag-input-title">{strings.tags.title}</span>
|
||||
<TagInputToolbar
|
||||
selectedTag={this.state.selectedTag}
|
||||
onAddTags={() => this.setState({addTags: !this.state.addTags})}
|
||||
onOnlyCurrentPageTags={() => this.setState({onlyCurrentPageTags: !this.state.onlyCurrentPageTags})}
|
||||
onShowOriginLabels = {(showOriginLabels: boolean) => this.setState({showOriginLabels})}
|
||||
onSearchTags={() => this.setState({
|
||||
searchTags: !this.state.searchTags,
|
||||
searchQuery: "",
|
||||
})}
|
||||
searchingTags={this.state.searchQuery.length > 0}
|
||||
onRenameTag={this.onRenameTag}
|
||||
onLockTag={this.onLockTag}
|
||||
onDelete={this.onDeleteTag}
|
||||
onReorder={this.onReOrder}
|
||||
/>
|
||||
</div>
|
||||
{this.props.tagsLoaded ?
|
||||
if (this.props.tagInputMode === TagInputMode.ConfigureTable) {
|
||||
return (
|
||||
<div className="tag-input">
|
||||
<div className="tag-input-header p-2">
|
||||
<span className="tag-input-title">{strings.tags.title}</span>
|
||||
</div>
|
||||
<div className="tag-input-body-container">
|
||||
<div className="tag-input-body">
|
||||
{
|
||||
this.state.searchTags &&
|
||||
<div className="tag-input-text-input-row search-input">
|
||||
<input
|
||||
className="tag-search-box"
|
||||
type="text"
|
||||
onKeyDown={this.onSearchKeyDown}
|
||||
onChange={(e) => this.setState({searchQuery: e.target.value})}
|
||||
placeholder="Search tags"
|
||||
autoFocus={true}
|
||||
onFocus={() => this.setState({selectedTag: null, tagOperation: TagOperationMode.Rename})}
|
||||
/>
|
||||
<FontIcon iconName="Search" />
|
||||
</div>
|
||||
}
|
||||
<div className="tag-input-items">
|
||||
{this.renderTagItems()}
|
||||
<Customizer {...dark}>
|
||||
{
|
||||
tagOperation === TagOperationMode.ContextualMenu && selectedTagRef &&
|
||||
<ContextualMenu
|
||||
className="tag-input-contextual-menu"
|
||||
items={this.getContextualMenuItems()}
|
||||
target={selectedTagRef}
|
||||
onDismiss={this.onHideContextualMenu}
|
||||
/>
|
||||
}
|
||||
</Customizer>
|
||||
{this.getColorPickerPortal()}
|
||||
</div>
|
||||
{
|
||||
this.state.addTags &&
|
||||
<div className="tag-input-text-input-row new-tag-input">
|
||||
<input
|
||||
className="tag-input-box"
|
||||
type="text"
|
||||
onKeyDown={this.onAddTagKeyDown}
|
||||
// Add mouse event
|
||||
onBlur={this.onAddTagWithBlur}
|
||||
placeholder="Add new tag"
|
||||
autoFocus={true}
|
||||
ref={this.inputRef}
|
||||
/>
|
||||
<FontIcon iconName="Tag" />
|
||||
</div>
|
||||
}
|
||||
<TableTagConfig
|
||||
setTagInputMode={this.props.setTagInputMode}
|
||||
addTableTag={this.addTableTag}
|
||||
splitPaneWidth={this.props.splitPaneWidth}
|
||||
tableTag={this.props.selectedTableTagToLabel}
|
||||
reconfigureTableConfirm={this.props.reconfigureTableConfirm}
|
||||
selectedTableBody={this.props.selectedTableTagBody}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
:
|
||||
<Spinner className="loading-tag" size={SpinnerSize.large} />
|
||||
}
|
||||
<Confirm
|
||||
title={strings.tags.warnings.replaceAllExitingLabelsTitle}
|
||||
ref={this.replaceConfirmRef}
|
||||
message={strings.tags.warnings.replaceAllExitingLabels}
|
||||
confirmButtonTheme={getPrimaryRedTheme()}
|
||||
onConfirm={this.onReplaceConfirm}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
</div>
|
||||
)
|
||||
} else if (this.props.tagInputMode === TagInputMode.LabelTable) {
|
||||
return (
|
||||
<div className="tag-input">
|
||||
<div className="tag-input-header p-2">
|
||||
<span className="tag-input-title">{strings.tags.title}</span>
|
||||
</div>
|
||||
<TableTagLabeling
|
||||
onTagClick={this.props.onTagClick}
|
||||
selectedRegions={this.props.selectedRegions}
|
||||
setTagInputMode={this.props.setTagInputMode}
|
||||
selectedTag={this.props.selectedTableTagToLabel as ITableTag}
|
||||
handleTableCellClick={this.props.handleTableCellClick}
|
||||
handleTableCellMouseEnter={this.props.handleTableCellMouseEnter}
|
||||
handleTableCellMouseLeave={this.props.handleTableCellMouseLeave}
|
||||
selectedTableTagBody={this.props.selectedTableTagBody}
|
||||
splitPaneWidth={this.props.splitPaneWidth}
|
||||
addRowToDynamicTable={this.props.addRowToDynamicTable}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
} else {
|
||||
return (
|
||||
<div className="tag-input">
|
||||
<div ref={this.headerRef} className="tag-input-header p-2">
|
||||
<span className="tag-input-title">{strings.tags.title}</span>
|
||||
<TagInputToolbar
|
||||
selectedTag={this.state.selectedTag}
|
||||
onAddTags={() => this.setState({ addTags: !this.state.addTags })}
|
||||
onOnlyCurrentPageTags={() => this.setState({ onlyCurrentPageTags: !this.state.onlyCurrentPageTags })}
|
||||
onShowOriginLabels={(showOriginLabels: boolean) => this.setState({ showOriginLabels })}
|
||||
onSearchTags={() => this.setState({
|
||||
searchTags: !this.state.searchTags,
|
||||
searchQuery: "",
|
||||
})}
|
||||
searchingTags={this.state.searchQuery.length > 0}
|
||||
onRenameTag={this.onRenameTag}
|
||||
onLockTag={this.onLockTag}
|
||||
onDelete={this.onDeleteTag}
|
||||
onReorder={this.onReOrder}
|
||||
setTagInputMode={this.props.setTagInputMode}
|
||||
/>
|
||||
</div>
|
||||
{this.props.tagsLoaded ?
|
||||
<div className="tag-input-body-container">
|
||||
<div className="tag-input-body">
|
||||
{
|
||||
this.state.searchTags &&
|
||||
<div className="tag-input-text-input-row search-input">
|
||||
<input
|
||||
className="tag-search-box"
|
||||
type="text"
|
||||
onKeyDown={this.onSearchKeyDown}
|
||||
onChange={(e) => this.setState({ searchQuery: e.target.value })}
|
||||
placeholder="Search tags"
|
||||
autoFocus={true}
|
||||
onFocus={() => this.setState({ selectedTag: null, tagOperation: TagOperationMode.Rename })}
|
||||
/>
|
||||
<FontIcon iconName="Search" />
|
||||
</div>
|
||||
}
|
||||
<div className="tag-input-items">
|
||||
{this.renderTagItems()}
|
||||
<Customizer {...dark}>
|
||||
{
|
||||
tagOperation === TagOperationMode.ContextualMenu && selectedTagRef &&
|
||||
<ContextualMenu
|
||||
className="tag-input-contextual-menu"
|
||||
items={this.getContextualMenuItems()}
|
||||
target={selectedTagRef}
|
||||
onDismiss={this.onHideContextualMenu}
|
||||
/>
|
||||
}
|
||||
</Customizer>
|
||||
{this.getColorPickerPortal()}
|
||||
</div>
|
||||
{
|
||||
this.state.addTags &&
|
||||
<div className="tag-input-text-input-row new-tag-input">
|
||||
<input
|
||||
className="tag-input-box"
|
||||
type="text"
|
||||
onKeyDown={this.onAddTagKeyDown}
|
||||
// Add mouse event
|
||||
onBlur={this.onAddTagWithBlur}
|
||||
placeholder="Add new tag"
|
||||
autoFocus={true}
|
||||
ref={this.inputRef}
|
||||
/>
|
||||
<FontIcon iconName="Tag" />
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
:
|
||||
<Spinner className="loading-tag" size={SpinnerSize.large} />
|
||||
}
|
||||
<Confirm
|
||||
title={strings.tags.warnings.replaceAllExitingLabelsTitle}
|
||||
ref={this.replaceConfirmRef}
|
||||
message={strings.tags.warnings.replaceAllExitingLabels}
|
||||
confirmButtonTheme={getPrimaryRedTheme()}
|
||||
onConfirm={this.onReplaceConfirm}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
public triggerNewTagBlur() {
|
||||
if (this.inputRef.current) {
|
||||
|
@ -372,14 +408,14 @@ export class TagInput extends React.Component<ITagInputProps, ITagInputState> {
|
|||
if (!tag) {
|
||||
return;
|
||||
}
|
||||
this.props.onTagDeleted(tag.name);
|
||||
this.props.onTagDeleted(tag.name, tag.type, tag.format);
|
||||
}
|
||||
|
||||
private getColorPickerPortal = () => {
|
||||
const {selectedTag} = this.state;
|
||||
const showColorPicker = this.state.tagOperation === TagOperationMode.ColorPicker;
|
||||
return (
|
||||
<AlignPortal align={{points: [ "tr", "tl" ]}} target={() => this.headerRef.current}>
|
||||
<AlignPortal align={{ points: ["tr", "tl"] }} target={() => this.headerRef.current}>
|
||||
<div className="tag-input-colorpicker-container">
|
||||
{
|
||||
showColorPicker &&
|
||||
|
@ -414,6 +450,7 @@ export class TagInput extends React.Component<ITagInputProps, ITagInputState> {
|
|||
onLabelEnter={this.props.onLabelEnter}
|
||||
onLabelLeave={this.props.onLabelLeave}
|
||||
onTagChanged={this.props.onTagChanged}
|
||||
handleLabelTable={this.props.handleLabelTable}
|
||||
onTagDoubleClick={this.props.onTagDoubleClick}
|
||||
/>);
|
||||
}
|
||||
|
@ -423,21 +460,29 @@ export class TagInput extends React.Component<ITagInputProps, ITagInputState> {
|
|||
return item;
|
||||
}
|
||||
|
||||
private setTagLabels = (key: string): ILabel[] => {
|
||||
return this.props.labels.filter((label) => label.label === key);
|
||||
private setTagLabels = (key: string): any[] => {
|
||||
const labels = this.props.labels.filter((label) => {
|
||||
if (this.props.encoded) {
|
||||
return label.label.replace(/\~1/g, "/").replace(/\~0/g, "~") === key;
|
||||
} else {
|
||||
return label.label === key;
|
||||
}
|
||||
})
|
||||
const tableLables = this.props.tableLabels.filter((label) => label.tableKey === key);
|
||||
return [...labels, ...tableLables]
|
||||
}
|
||||
|
||||
private createTagItemProps = (): ITagInputItemProps[] => {
|
||||
const {tags, selectedTag, tagOperation, onlyCurrentPageTags} = this.state;
|
||||
const { tags, selectedTag, tagOperation, onlyCurrentPageTags } = this.state;
|
||||
const selectedRegionTagSet = this.getSelectedRegionTagSet();
|
||||
|
||||
if (onlyCurrentPageTags) {
|
||||
const labels = this.props.labels.filter(item => item.value[0]?.page === this.props.pageNumber).map(item => item.label);
|
||||
const tableLabels = this.props.tableLabels.filter(item => item.labels[0]?.value[0]?.page === this.props.pageNumber).map(item => item.tableKey);
|
||||
const labeledTags = [...labels, ...tableLabels];
|
||||
|
||||
const labels = this.props.labels.filter(item => item.value[ 0 ]?.page === this.props.pageNumber)
|
||||
.map(item => item.label);
|
||||
if (labels.length) {
|
||||
|
||||
return tags.filter(tag => labels.find(a => a === tag.name))
|
||||
if (labeledTags.length) {
|
||||
return tags.filter(tag => labeledTags.find(a => a === tag.name))
|
||||
.map<ITagInputItemProps>(tag => {
|
||||
return {
|
||||
tag,
|
||||
|
@ -518,19 +563,22 @@ export class TagInput extends React.Component<ITagInputProps, ITagInputState> {
|
|||
// Only fire click event if a region is selected
|
||||
const {selectedRegions, onTagClick, labels} = this.props;
|
||||
if (selectedRegions && selectedRegions.length && onTagClick) {
|
||||
const {category} = selectedRegions[ 0 ];
|
||||
const {format, type, documentCount, name} = tag;
|
||||
const tagCategory = this.getTagCategory(type);
|
||||
const { category } = selectedRegions[0];
|
||||
const { format, type, documentCount, name } = tag;
|
||||
const tagCategory = getTagCategory(type);
|
||||
const isTagLabelTypeDrawnRegion = this.labelAssignedDrawnRegion(labels, tag.name);
|
||||
const labelAssigned = this.labelAssigned(labels, name);
|
||||
|
||||
if (labelAssigned && ((category === FeatureCategory.DrawnRegion) !== isTagLabelTypeDrawnRegion)) {
|
||||
if(category===FeatureCategory.Checkbox&&isTagLabelTypeDrawnRegion){
|
||||
toast.warn(interpolate(strings.tags.warnings.notCompatibleWithDrawnRegionTag, {otherCatagory: FeatureCategory.Checkbox}));
|
||||
}else if (isTagLabelTypeDrawnRegion) {
|
||||
if ((tag.type === FieldType.Object || tag.type === FieldType.Array) && this.props.selectedRegions?.length) {
|
||||
this.props.handleLabelTable(TagInputMode.LabelTable, tag)
|
||||
deselect = false;
|
||||
} else if (labelAssigned && ((category === FeatureCategory.DrawnRegion) !== isTagLabelTypeDrawnRegion)) {
|
||||
if (category === FeatureCategory.Checkbox && isTagLabelTypeDrawnRegion) {
|
||||
toast.warn(interpolate(strings.tags.warnings.notCompatibleWithDrawnRegionTag, { otherCatagory: FeatureCategory.Checkbox }));
|
||||
} else if (isTagLabelTypeDrawnRegion) {
|
||||
this.replaceConfirmRef.current.open(tag, props);
|
||||
} else if (tagCategory === FeatureCategory.Checkbox) {
|
||||
toast.warn(interpolate(strings.tags.warnings.notCompatibleWithDrawnRegionTag, {otherCatagory: FeatureCategory.Checkbox}));
|
||||
toast.warn(interpolate(strings.tags.warnings.notCompatibleWithDrawnRegionTag, { otherCatagory: FeatureCategory.Checkbox }));
|
||||
} else {
|
||||
this.replaceConfirmRef.current.open(tag, props);
|
||||
}
|
||||
|
@ -541,14 +589,14 @@ export class TagInput extends React.Component<ITagInputProps, ITagInputState> {
|
|||
toast.warn(strings.tags.warnings.checkboxPerTagLimit);
|
||||
return;
|
||||
}
|
||||
if(tagCategory===FeatureCategory.Checkbox&&category!==FeatureCategory.Checkbox){
|
||||
if (tagCategory === FeatureCategory.Checkbox && category !== FeatureCategory.Checkbox) {
|
||||
toast.warn(strings.tags.warnings.notCompatibleTagType);
|
||||
return;
|
||||
}
|
||||
onTagClick(tag);
|
||||
deselect = false;
|
||||
} else {
|
||||
toast.warn(strings.tags.warnings.notCompatibleTagType, {autoClose: 7000});
|
||||
toast.warn(strings.tags.warnings.notCompatibleTagType, { autoClose: 7000 });
|
||||
}
|
||||
}
|
||||
this.setState({
|
||||
|
@ -597,16 +645,6 @@ export class TagInput extends React.Component<ITagInputProps, ITagInputState> {
|
|||
}
|
||||
}
|
||||
|
||||
public getTagCategory = (tagType: string) => {
|
||||
switch (tagType) {
|
||||
case FieldType.SelectionMark:
|
||||
case "checkbox":
|
||||
return "checkbox";
|
||||
default:
|
||||
return "text";
|
||||
}
|
||||
}
|
||||
|
||||
private onSearchKeyDown = (event: KeyboardEvent): void => {
|
||||
if (event.key === "Escape") {
|
||||
this.setState({
|
||||
|
@ -653,6 +691,21 @@ export class TagInput extends React.Component<ITagInputProps, ITagInputState> {
|
|||
}
|
||||
}
|
||||
|
||||
private addTableTag = (tableConfig: any) => {
|
||||
const newTag: ITableTag = {
|
||||
name: tableConfig.name,
|
||||
color: getNextColor(this.state.tags),
|
||||
type: tableConfig.type,
|
||||
format: tableConfig.format,
|
||||
documentCount: 0,
|
||||
itemType: tableConfig.itemType,
|
||||
fields: tableConfig.fields,
|
||||
definition: tableConfig.definition,
|
||||
visualizationHint: tableConfig.visualizationHint,
|
||||
};
|
||||
this.addTag(newTag);
|
||||
}
|
||||
|
||||
private validateTagLength = (tag: ITag) => {
|
||||
if (!tag.name.trim().length) {
|
||||
throw new Error(strings.tags.warnings.emptyName);
|
||||
|
@ -669,7 +722,7 @@ export class TagInput extends React.Component<ITagInputProps, ITagInputState> {
|
|||
}
|
||||
|
||||
private onHideContextualMenu = () => {
|
||||
this.setState({tagOperation: TagOperationMode.None});
|
||||
this.setState({ tagOperation: TagOperationMode.None });
|
||||
}
|
||||
|
||||
private getContextualMenuItems = (): IContextualMenuItem[] => {
|
||||
|
@ -686,8 +739,11 @@ export class TagInput extends React.Component<ITagInputProps, ITagInputState> {
|
|||
},
|
||||
text: tag.type ? tag.type : strings.tags.toolbar.type,
|
||||
subMenuProps: {
|
||||
items: this.getTypeSubMenuItems(),
|
||||
items: this.getTypeSubMenuItems()
|
||||
},
|
||||
submenuIconProps: {
|
||||
iconName: tag.type !== FieldType.Object ? "ChevronRight" : ""
|
||||
}
|
||||
},
|
||||
{
|
||||
key: "format",
|
||||
|
@ -698,6 +754,18 @@ export class TagInput extends React.Component<ITagInputProps, ITagInputState> {
|
|||
subMenuProps: {
|
||||
items: this.getFormatSubMenuItems(),
|
||||
},
|
||||
submenuIconProps: {
|
||||
iconName: tag.type !== FieldType.Object && tag.type !== FieldType.Array ? "ChevronRight" : ""
|
||||
}
|
||||
|
||||
},
|
||||
{
|
||||
key: "reconfigureTable",
|
||||
iconProps: {
|
||||
iconName: "EditTable",
|
||||
},
|
||||
text: strings.tags.regionTableTags.tableLabeling.buttons.reconfigureTable,
|
||||
onClick: () => this.props.setTagInputMode(TagInputMode.ConfigureTable, tag as ITableTag),
|
||||
},
|
||||
{
|
||||
key: "divider_1",
|
||||
|
@ -738,23 +806,28 @@ export class TagInput extends React.Component<ITagInputProps, ITagInputState> {
|
|||
onClick: this.onMenuItemClick,
|
||||
},
|
||||
];
|
||||
|
||||
return menuItems;
|
||||
return tag.type === FieldType.Object || tag.type === FieldType.Array ? menuItems : menuItems.filter((item) => (item.key !== "reconfigureTable"));
|
||||
// return menuItems;
|
||||
}
|
||||
|
||||
private isTypeCompatibleWithTag = (tag, type) => {
|
||||
// If free tag we can assign any type
|
||||
if (tag && tag.documentCount <= 0) {
|
||||
if (tag && tag.documentCount <= 0 && tag.type !== FieldType.Object && tag.type !== FieldType.Array) {
|
||||
return true;
|
||||
}
|
||||
const tagType = this.getTagCategory(tag.type);
|
||||
const menuItemType = this.getTagCategory(type);
|
||||
const tagType = getTagCategory(tag.type);
|
||||
const menuItemType = getTagCategory(type);
|
||||
return tagType === menuItemType;
|
||||
}
|
||||
|
||||
// here
|
||||
private getTypeSubMenuItems = (): IContextualMenuItem[] => {
|
||||
const tag = this.state.selectedTag;
|
||||
const types = Object.values(FieldType);
|
||||
let types = Object.values(FieldType);
|
||||
if (tag.type === FieldType.Object || tag.type === FieldType.Array) {
|
||||
return []
|
||||
} else {
|
||||
types = types.filter((i) => i !== FieldType.Array && i !== FieldType.Object)
|
||||
}
|
||||
return types.map((type) => {
|
||||
const isCompatible = this.isTypeCompatibleWithTag(tag, type);
|
||||
return {
|
||||
|
@ -771,6 +844,9 @@ export class TagInput extends React.Component<ITagInputProps, ITagInputState> {
|
|||
private getFormatSubMenuItems = (): IContextualMenuItem[] => {
|
||||
const tag = this.state.selectedTag;
|
||||
const formats = filterFormat(tag.type);
|
||||
if (tag.type === FieldType.Object || tag.type === FieldType.Array) {
|
||||
return []
|
||||
}
|
||||
|
||||
return formats.map((format) => {
|
||||
return {
|
||||
|
|
|
@ -21,6 +21,8 @@ describe("Tag Input Item", () => {
|
|||
onRename: jest.fn(),
|
||||
onLabelEnter: jest.fn(),
|
||||
onLabelLeave: jest.fn(),
|
||||
handleLabelTable: null,
|
||||
addRowToDynamicTable: null,
|
||||
showOriginLabels:false,
|
||||
};
|
||||
}
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
import {FontIcon, IconButton} from "@fluentui/react";
|
||||
import { FontIcon, IconButton } from "@fluentui/react";
|
||||
import _ from "lodash";
|
||||
import React, {Fragment, MouseEvent} from "react";
|
||||
import {strings} from "../../../../common/strings";
|
||||
import {FieldFormat, FieldType, ILabel, ITag} from "../../../../models/applicationState";
|
||||
import {tagIndexKeys} from "./tagIndexKeys";
|
||||
import React, { Fragment, MouseEvent } from "react";
|
||||
import { strings } from "../../../../common/strings";
|
||||
import { FieldFormat, FieldType, ILabel, ITableLabel, ITag, TagInputMode } from "../../../../models/applicationState";
|
||||
import { tagIndexKeys } from "./tagIndexKeys";
|
||||
import TagInputItemLabel from "./tagInputItemLabel";
|
||||
|
||||
export interface ITagClickProps {
|
||||
|
@ -41,9 +41,11 @@ export interface ITagInputItemProps {
|
|||
onClick: (tag: ITag, props: ITagClickProps) => void;
|
||||
/** Apply new name to tag */
|
||||
onRename: (oldTag: ITag, newName: string, cancelCallback: () => void) => void;
|
||||
onLabelEnter: (label: ILabel) => void;
|
||||
onLabelEnter: (label: ILabel|ITableLabel) => void;
|
||||
onLabelLeave: (label: ILabel) => void;
|
||||
onTagChanged?: (oldTag: ITag, newTag: ITag) => void;
|
||||
handleLabelTable: (tagInputMode: TagInputMode, selectedTableTagToLabel) => void;
|
||||
addRowToDynamicTable: () => void;
|
||||
onTagDoubleClick?: (label: ILabel) => void;
|
||||
}
|
||||
|
||||
|
@ -185,20 +187,23 @@ export default class TagInputItem extends React.Component<ITagInputItemProps, IT
|
|||
<FontIcon iconName="Link" className="pl-1" />
|
||||
}
|
||||
<div className="tag-name-body">
|
||||
<input
|
||||
ref={this.onInputRef}
|
||||
style={{display: this.state.isRenaming ? "block" : "none"}}
|
||||
className={`tag-name-editor ${this.getContentClassName()}`}
|
||||
type="text"
|
||||
defaultValue={this.props.tag.name}
|
||||
onKeyDown={(e) => this.onInputKeyDown(e)}
|
||||
onBlur={this.onInputBlur}
|
||||
autoFocus={true}
|
||||
/>
|
||||
|
||||
{!this.state.isRenaming && <span title={spanValue} className={this.getContentClassName()}>
|
||||
{spanValue}
|
||||
</span>}
|
||||
{
|
||||
this.state.isRenaming
|
||||
?
|
||||
<input
|
||||
ref={this.onInputRef}
|
||||
className={`tag-name-editor ${this.getContentClassName()}`}
|
||||
type="text"
|
||||
defaultValue={this.props.tag.name}
|
||||
onKeyDown={(e) => this.onInputKeyDown(e)}
|
||||
onBlur={this.onInputBlur}
|
||||
autoFocus={true}
|
||||
/>
|
||||
:
|
||||
<span title={this.props.tag.name} className={this.getContentClassName()}>
|
||||
{this.props.tag.name}
|
||||
</span>
|
||||
}
|
||||
</div>
|
||||
<div className={"tag-icons-container"}>
|
||||
{(displayIndex !== null)
|
||||
|
@ -211,7 +216,7 @@ export default class TagInputItem extends React.Component<ITagInputItemProps, IT
|
|||
title={strings.tags.toolbar.contextualMenu}
|
||||
ariaLabel={strings.tags.toolbar.contextualMenu}
|
||||
className="tag-input-toolbar-iconbutton ml-2"
|
||||
iconProps={{iconName: "ChevronDown"}}
|
||||
iconProps={{ iconName: "ChevronDown" }}
|
||||
onClick={this.onDropdownClick} />
|
||||
</div>
|
||||
</div>
|
||||
|
@ -219,46 +224,65 @@ export default class TagInputItem extends React.Component<ITagInputItemProps, IT
|
|||
}
|
||||
|
||||
private renderTagDetail = () => {
|
||||
let confidence = _.get(this.props, "labels[0].confidence", null);
|
||||
if (confidence > .995) {
|
||||
confidence = 0.995;
|
||||
}
|
||||
const revised = _.get(this.props, "labels[0].revised", false);
|
||||
return this.props.labels.map((label, idx) =>
|
||||
<Fragment key={idx}>
|
||||
<div className="tag-item-label-container">
|
||||
{(confidence||revised) &&
|
||||
<div className="tag-item-label-container-item1">
|
||||
{!revised && confidence &&
|
||||
<div className="tag-item-confidence">
|
||||
{confidence}
|
||||
</div>
|
||||
if (this.props.tag.type === FieldType.Object || this.props.tag.type === FieldType.Array) {
|
||||
return (
|
||||
<div
|
||||
className={"tag-item-label px-2"}
|
||||
onClick={() => {
|
||||
this.props.handleLabelTable(TagInputMode.LabelTable, this.props.tag);
|
||||
this.props.onLabelLeave(this.props.labels[0]);
|
||||
}}
|
||||
onMouseEnter={() => this.props.onLabelEnter(this.props.labels[0])}
|
||||
onMouseLeave={() => this.props.onLabelLeave(this.props.labels[0])}
|
||||
>
|
||||
<FontIcon
|
||||
className="pr-1 pl-1" iconName="Table"
|
||||
/>
|
||||
Click to assign labels
|
||||
</div>
|
||||
);
|
||||
} else {
|
||||
let confidence = _.get(this.props, "labels[0].confidence", null);
|
||||
if (confidence > .995) {
|
||||
confidence = 0.995;
|
||||
}
|
||||
const revised = _.get(this.props, "labels[0].revised", false);
|
||||
return this.props.labels.map((label, idx) =>
|
||||
<Fragment key={idx}>
|
||||
<div className="tag-item-label-container">
|
||||
{(confidence || revised) &&
|
||||
<div className="tag-item-label-container-item1">
|
||||
{!revised && confidence &&
|
||||
<div className="tag-item-confidence">
|
||||
{confidence}
|
||||
</div>
|
||||
}
|
||||
{revised &&
|
||||
<FontIcon iconName="StatusCircleCheckmark" className="ms-Icon-25px" />
|
||||
}
|
||||
</div>
|
||||
}
|
||||
<div className="tag-item-label-container-item2">
|
||||
{this.props.showOriginLabels && label.originValue &&
|
||||
<TagInputItemLabel
|
||||
label={label}
|
||||
isOrigin={true}
|
||||
value={label.originValue}
|
||||
prefixText={strings.tags.preText.autoLabel}/>
|
||||
}
|
||||
{revised &&
|
||||
<FontIcon iconName="StatusCircleCheckmark" className="ms-Icon-25px" />
|
||||
{(label.originValue?.length > 0 || label.value?.length > 0) &&
|
||||
<TagInputItemLabel
|
||||
label={label}
|
||||
value={label.value}
|
||||
isOrigin={false}
|
||||
onLabelEnter={this.props.onLabelEnter}
|
||||
onLabelLeave={this.props.onLabelLeave}
|
||||
prefixText={revised ? strings.tags.preText.revised : undefined}/>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
<div className="tag-item-label-container-item2">
|
||||
{this.props.showOriginLabels && label.originValue &&
|
||||
<TagInputItemLabel
|
||||
label={label}
|
||||
isOrigin={true}
|
||||
value={label.originValue}
|
||||
prefixText={strings.tags.preText.autoLabel}
|
||||
/>
|
||||
}
|
||||
{(label.originValue?.length > 0 || label.value?.length > 0) && <TagInputItemLabel
|
||||
label={label}
|
||||
value={label.value}
|
||||
isOrigin={false}
|
||||
onLabelEnter={this.props.onLabelEnter}
|
||||
onLabelLeave={this.props.onLabelLeave}
|
||||
prefixText={revised ? strings.tags.preText.revised : undefined}
|
||||
/>}
|
||||
</div>
|
||||
</div>
|
||||
</Fragment>);
|
||||
</Fragment>);
|
||||
}
|
||||
}
|
||||
private onInputRef = (element: HTMLInputElement) => {
|
||||
this.inputElement = element;
|
||||
|
@ -312,7 +336,7 @@ export default class TagInputItem extends React.Component<ITagInputItemProps, IT
|
|||
}
|
||||
|
||||
private isTypeOrFormatSpecified = () => {
|
||||
const {tag} = this.props;
|
||||
const { tag } = this.props;
|
||||
return (tag.type && tag.type !== FieldType.String) ||
|
||||
(tag.format && tag.format !== FieldFormat.NotSpecified);
|
||||
}
|
||||
|
|
|
@ -2,11 +2,12 @@
|
|||
// Licensed under the MIT license.
|
||||
|
||||
import React from "react";
|
||||
import {ILabel, IFormRegion} from "../../../../models/applicationState";
|
||||
import {FontIcon} from "@fluentui/react";
|
||||
import { ILabel, IFormRegion, ITag } from "../../../../models/applicationState";
|
||||
import { FontIcon } from "@fluentui/react";
|
||||
|
||||
export interface ITagInputItemLabelProps {
|
||||
label: ILabel;
|
||||
tag?: ITag;
|
||||
value: IFormRegion[];
|
||||
isOrigin: boolean;
|
||||
onLabelEnter?: (label: ILabel) => void;
|
||||
|
@ -14,28 +15,40 @@ export interface ITagInputItemLabelProps {
|
|||
prefixText?:string
|
||||
}
|
||||
|
||||
export interface ITagInputItemLabelState {}
|
||||
export interface ITagInputItemLabelState { }
|
||||
|
||||
export default class TagInputItemLabel extends React.Component<ITagInputItemLabelProps, ITagInputItemLabelState> {
|
||||
public render() {
|
||||
const texts = [];
|
||||
let hasEmptyTextValue = false;
|
||||
this.props.value?.forEach((formRegion: IFormRegion, idx) => {
|
||||
if (formRegion.text === "") {
|
||||
hasEmptyTextValue = true;
|
||||
} else {
|
||||
texts.push(formRegion.text);
|
||||
}
|
||||
})
|
||||
const text = texts.join(" ");
|
||||
export default function TagInputItemLabel(props: ITagInputItemLabelProps) {
|
||||
const { label, onLabelEnter, onLabelLeave, tag = null , value} = props
|
||||
const texts = [];
|
||||
let hasEmptyTextValue = false;
|
||||
value?.forEach((formRegion: IFormRegion, idx) => {
|
||||
if (formRegion.text === "") {
|
||||
hasEmptyTextValue = true;
|
||||
} else {
|
||||
texts.push(formRegion.text);
|
||||
}
|
||||
})
|
||||
const text = texts.join(" ");
|
||||
|
||||
const handleMouseEnter = () => {
|
||||
if (props.onLabelEnter) {
|
||||
onLabelEnter(label);
|
||||
}
|
||||
};
|
||||
|
||||
const handleMouseLeave = () => {
|
||||
if (props.onLabelLeave) {
|
||||
onLabelLeave(label);
|
||||
}
|
||||
};
|
||||
return (
|
||||
<div
|
||||
className={[this.props.isOrigin ? "tag-item-label-origin" : "tag-item-label", "flex-center", "px-2"].join(" ")}
|
||||
onMouseEnter={this.handleMouseEnter}
|
||||
onMouseLeave={this.handleMouseLeave}
|
||||
onMouseEnter={handleMouseEnter}
|
||||
onMouseLeave={handleMouseLeave}
|
||||
className={[props.isOrigin ? "tag-item-label-origin" : "tag-item-label", "flex-center", "px-2"].join(" ")}
|
||||
>
|
||||
<div className="flex-center">
|
||||
{text ? this.props.prefixText : undefined} {text}
|
||||
{text ? props.prefixText : undefined} {text}
|
||||
{hasEmptyTextValue &&
|
||||
<FontIcon className="pr-1 pl-1" iconName="FieldNotChanged" />
|
||||
}
|
||||
|
@ -43,16 +56,3 @@ export default class TagInputItemLabel extends React.Component<ITagInputItemLabe
|
|||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
private handleMouseEnter = () => {
|
||||
if (this.props.onLabelEnter) {
|
||||
this.props.onLabelEnter(this.props.label);
|
||||
}
|
||||
}
|
||||
|
||||
private handleMouseLeave = () => {
|
||||
if (this.props.onLabelLeave) {
|
||||
this.props.onLabelLeave(this.props.label);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,9 +2,9 @@
|
|||
// Licensed under the MIT license.
|
||||
|
||||
import React from "react";
|
||||
import {IconButton} from "@fluentui/react";
|
||||
import {strings} from "../../../../common/strings";
|
||||
import {ITag} from "../../../../models/applicationState";
|
||||
import { IconButton } from "@fluentui/react";
|
||||
import { strings } from "../../../../common/strings";
|
||||
import { ITableRegion, ITableTag, ITag, TagInputMode } from "../../../../models/applicationState";
|
||||
import {constants} from "../../../../common/constants";
|
||||
|
||||
enum Categories {
|
||||
|
@ -20,6 +20,8 @@ export interface ITagInputToolbarProps {
|
|||
selectedTag: ITag;
|
||||
/** Function to call when add tags button is clicked */
|
||||
onAddTags: () => void;
|
||||
|
||||
setTagInputMode?: (tagInputMode: TagInputMode, selectedTableTagToLabel?: ITableTag, selectedTableTagBody?: ITableRegion[][][]) => void;
|
||||
/** Function to call when search tags button is clicked */
|
||||
onSearchTags: () => void;
|
||||
/** Function to call when lock tags button is clicked */
|
||||
|
@ -70,6 +72,16 @@ export default class TagInputToolbar extends React.Component<ITagInputToolbarPro
|
|||
category: Categories.General,
|
||||
handler: this.handleAdd,
|
||||
},
|
||||
{
|
||||
displayName: strings.tags.toolbar.addTable,
|
||||
icon: "AddTable",
|
||||
category: Categories.General,
|
||||
handler: this.handleAddTable,
|
||||
},
|
||||
{
|
||||
displayName: strings.tags.toolbar.vertiline,
|
||||
category: Categories.Separator,
|
||||
},
|
||||
{
|
||||
displayName: this.state.tagFilterToggled ? strings.tags.toolbar.showAllTags : strings.tags.toolbar.onlyShowCurrentPageTags,
|
||||
icon: this.state.tagFilterToggled ? "ClearFilter" : "Filter",
|
||||
|
@ -200,6 +212,10 @@ export default class TagInputToolbar extends React.Component<ITagInputToolbarPro
|
|||
});
|
||||
}
|
||||
|
||||
private handleAddTable = () => {
|
||||
this.props.setTagInputMode(TagInputMode.ConfigureTable, null, null);
|
||||
}
|
||||
|
||||
private handleSearch = () => {
|
||||
this.props.onSearchTags();
|
||||
}
|
||||
|
|
|
@ -40,14 +40,14 @@
|
|||
.prev {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 0;
|
||||
left: 50px;
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
.next {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
right: 0;
|
||||
right: 50px;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
|
|
|
@ -43,6 +43,7 @@ describe("Editor Canvas", () => {
|
|||
lockedTags: [],
|
||||
hoveredLabel: null,
|
||||
appSettings: null,
|
||||
highlightedTableCell: null,
|
||||
};
|
||||
|
||||
const assetPreviewProps: IAssetPreviewProps = {
|
||||
|
|
|
@ -9,7 +9,7 @@ import {
|
|||
EditorMode, IAssetMetadata,
|
||||
IProject, IRegion, RegionType,
|
||||
AssetType, ILabelData, ILabel,
|
||||
ITag, IAsset, IFormRegion, FeatureCategory, FieldType, FieldFormat, LabelType, AssetLabelingState, APIVersionPatches, AssetState
|
||||
ITag, IAsset, IFormRegion, FeatureCategory, FieldType, FieldFormat, ImageMapParent, LabelType, ITableRegion, ITableTag, ITableLabel, ITableCellLabel, AssetLabelingState, APIVersionPatches, TableVisualizationHint, AssetState
|
||||
} from "../../../../models/applicationState";
|
||||
import CanvasHelpers from "./canvasHelpers";
|
||||
import { AssetPreview } from "../../common/assetPreview/assetPreview";
|
||||
|
@ -51,7 +51,7 @@ export interface ICanvasProps extends React.Props<Canvas> {
|
|||
editorMode: EditorMode;
|
||||
project: IProject;
|
||||
lockedTags: string[];
|
||||
hoveredLabel: ILabel;
|
||||
hoveredLabel: ILabel | any;
|
||||
isRunningOCRs?: boolean;
|
||||
children?: ReactElement<AssetPreview>;
|
||||
setTableToView?: (tableToView: object, tableToViewId: string) => void;
|
||||
|
@ -64,9 +64,11 @@ export interface ICanvasProps extends React.Props<Canvas> {
|
|||
onRunningAutoLabelingStatusChanged?: (isRunning: boolean) => void;
|
||||
onTagChanged?: (oldTag: ITag, newTag: ITag) => void;
|
||||
runOcrForAllDocs?: (runForAllDocs: boolean) => void;
|
||||
handleLabelTable?: () => void;
|
||||
runAutoLabelingOnNextBatch?: (batchSize: number) => Promise<void>;
|
||||
onAssetDeleted?: () => void;
|
||||
onPageLoaded?: (pageNumber: number) => void;
|
||||
highlightedTableCell: any;
|
||||
}
|
||||
|
||||
export interface ICanvasState {
|
||||
|
@ -127,6 +129,7 @@ export default class Canvas extends React.Component<ICanvasProps, ICanvasState>
|
|||
project: null,
|
||||
lockedTags: [],
|
||||
hoveredLabel: null,
|
||||
highlightedTableCell: null,
|
||||
appSettings: null,
|
||||
};
|
||||
|
||||
|
@ -211,12 +214,14 @@ export default class Canvas extends React.Component<ICanvasProps, ICanvasState>
|
|||
await this.loadOcr();
|
||||
this.loadLabelData(asset);
|
||||
});
|
||||
} else if (this.isLabelDataChanged(this.props, prevProps)
|
||||
|| (prevProps.project
|
||||
&& this.needUpdateAssetRegionsFromTags(prevProps.project.tags, this.props.project.tags))) {
|
||||
} else if (
|
||||
this.isLabelDataChanged(this.props, prevProps)
|
||||
|| this.isTableLabelDataChanged(this.props, prevProps)
|
||||
|| (prevProps.project && this.needUpdateAssetRegionsFromTags(prevProps.project.tags, this.props.project.tags))) {
|
||||
this.setState({
|
||||
currentAsset: this.props.selectedAsset
|
||||
}, () => {
|
||||
|
||||
const newRegions = this.convertLabelDataToRegions(this.props.selectedAsset.labelData);
|
||||
this.updateAssetRegions(newRegions);
|
||||
this.redrawAllFeatures();
|
||||
|
@ -228,12 +233,18 @@ export default class Canvas extends React.Component<ICanvasProps, ICanvasState>
|
|||
});
|
||||
}
|
||||
|
||||
if (this.props.hoveredLabel !== prevProps.hoveredLabel) {
|
||||
if (this.props.hoveredLabel !== prevProps.hoveredLabel || this.props.highlightedTableCell !== prevProps.highlightedTableCell) {
|
||||
this.imageMap.getAllLabelFeatures().map(this.updateHighlightStatus);
|
||||
this.imageMap.getAllDrawnLabelFeatures().map(this.updateHighlightStatus);
|
||||
}
|
||||
}
|
||||
|
||||
public temp = () => {
|
||||
const newRegions = this.convertLabelDataToRegions(this.props.selectedAsset.labelData);
|
||||
this.updateAssetRegions(newRegions);
|
||||
this.redrawAllFeatures();
|
||||
}
|
||||
|
||||
public render = () => {
|
||||
const hostStyles: Partial<ITooltipHostStyles> = {
|
||||
root: {
|
||||
|
@ -245,6 +256,7 @@ export default class Canvas extends React.Component<ICanvasProps, ICanvasState>
|
|||
display: this.state.tableIconTooltip.display,
|
||||
},
|
||||
};
|
||||
|
||||
return (
|
||||
<div style={{ width: "100%", height: "100%" }}>
|
||||
<KeyboardBinding
|
||||
|
@ -399,10 +411,10 @@ export default class Canvas extends React.Component<ICanvasProps, ICanvasState>
|
|||
const result = await predictService.getPrediction(assetPath);
|
||||
const assetService = new AssetService(this.props.project);
|
||||
const assetMetadata = assetService.getAssetPredictMetadata(asset, result);
|
||||
if(assetMetadata) {
|
||||
if (assetMetadata) {
|
||||
await this.props.onAssetMetadataChanged(assetMetadata);
|
||||
}
|
||||
} catch(err){
|
||||
} catch (err) {
|
||||
this.setState({
|
||||
isError: true,
|
||||
errorTitle: err.title,
|
||||
|
@ -442,8 +454,8 @@ export default class Canvas extends React.Component<ICanvasProps, ICanvasState>
|
|||
* Toggles tag on all selected regions
|
||||
* @param selectedTag Tag name
|
||||
*/
|
||||
public applyTag = (tag: string) => {
|
||||
const selectedRegions = this.getSelectedRegions();
|
||||
public applyTag = (tag: string, rowIndex?: number, columnIndex?: number) => {
|
||||
const selectedRegions: IRegion[] = this.getSelectedRegions();
|
||||
const regionsEmpty = !selectedRegions || !selectedRegions.length;
|
||||
if (!tag || regionsEmpty) {
|
||||
return;
|
||||
|
@ -453,16 +465,47 @@ export default class Canvas extends React.Component<ICanvasProps, ICanvasState>
|
|||
return;
|
||||
}
|
||||
let regions: IRegion[] = [];
|
||||
const inputTag: ITag[] = this.props.project.tags.filter((t) => t.name === tag);
|
||||
if (selectedRegions.length > 0) {
|
||||
const labelsData = this.state.currentAsset.labelData;
|
||||
if (labelsData) {
|
||||
const relatedLabel = labelsData.labels.find((label) => label.label === tag);
|
||||
let relatedLabel;
|
||||
if (inputTag[0].type === FieldType.Array || inputTag[0].type === FieldType.Object) {
|
||||
let rowKey;
|
||||
let columnKey;
|
||||
if (inputTag[0].type === FieldType.Array) {
|
||||
rowKey = rowIndex.toString();
|
||||
columnKey = (inputTag as ITableTag[])[0].definition.fields[columnIndex].fieldKey;
|
||||
relatedLabel = labelsData.labels.find((label) => label.label === (this.encodeLabelString(tag) + "/" + this.encodeLabelString(rowKey) + "/" + this.encodeLabelString(columnKey)));
|
||||
} else {
|
||||
if ((inputTag as ITableTag[])[0].visualizationHint === TableVisualizationHint.Vertical) {
|
||||
rowKey = (inputTag as ITableTag[])[0].fields[rowIndex].fieldKey;
|
||||
columnKey = (inputTag as ITableTag[])[0].definition.fields[columnIndex].fieldKey;
|
||||
relatedLabel = labelsData.labels.find((label) => label.label === (this.encodeLabelString(tag) + "/" + this.encodeLabelString(rowKey) + "/" + this.encodeLabelString(columnKey)));
|
||||
} else {
|
||||
rowKey = (inputTag as ITableTag[])[0].definition.fields[rowIndex].fieldKey;
|
||||
columnKey = (inputTag as ITableTag[])[0].fields[columnIndex].fieldKey;
|
||||
relatedLabel = labelsData.labels.find((label) => label.label === (this.encodeLabelString(tag) + "/" + this.encodeLabelString(columnKey) + "/" + this.encodeLabelString(rowKey)));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (labelsData.$schema === constants.labelsSchema) {
|
||||
relatedLabel = labelsData.labels.find((label) => label.label === this.encodeLabelString(tag));
|
||||
} else {
|
||||
relatedLabel = labelsData.labels.find((label) => label.label === tag);
|
||||
}
|
||||
}
|
||||
if (relatedLabel &&
|
||||
(((relatedLabel.labelType === null || relatedLabel.labelType === undefined) && (selectedRegions[0].category === FeatureCategory.DrawnRegion))
|
||||
|| (relatedLabel.labelType !== null && relatedLabel.labelType !== undefined && relatedLabel.labelType !== selectedRegions[0].category))) {
|
||||
regions = this.convertLabelToRegion(relatedLabel)
|
||||
regions = this.convertLabelToRegion(relatedLabel, labelsData?.$schema === constants.labelsSchema);
|
||||
regions.forEach((region) => {
|
||||
region.tags = [];
|
||||
if (region.isTableRegion) {
|
||||
delete (region as ITableRegion).isTableRegion;
|
||||
delete (region as ITableRegion).columnKey;
|
||||
delete (region as ITableRegion).rowKey;
|
||||
}
|
||||
const regionIndex = this.state.currentAsset.regions.findIndex(r => r.id === region.id);
|
||||
if (regionIndex !== -1) {
|
||||
this.state.currentAsset.regions.splice(regionIndex, 1, region);
|
||||
|
@ -473,11 +516,29 @@ export default class Canvas extends React.Component<ICanvasProps, ICanvasState>
|
|||
}
|
||||
|
||||
const transformer: (tags: string[], tag: string) => string[] = CanvasHelpers.setSingleTag;
|
||||
const inputTag = this.props.project.tags.filter((t) => t.name === tag);
|
||||
|
||||
for (const selectedRegion of selectedRegions) {
|
||||
selectedRegion.tags = transformer(selectedRegion.tags, tag);
|
||||
}
|
||||
|
||||
if (inputTag[0].type === FieldType.Array || inputTag[0].type === FieldType.Object) {
|
||||
for (const selectedRegion of selectedRegions as ITableRegion[]) {
|
||||
if (inputTag[0].type === FieldType.Array) {
|
||||
selectedRegion.rowKey = "#" + (rowIndex);
|
||||
selectedRegion.columnKey = (inputTag as ITableTag[])[0].definition.fields[columnIndex].fieldKey;
|
||||
} else {
|
||||
if ((inputTag as ITableTag[])[0].visualizationHint === TableVisualizationHint.Vertical) {
|
||||
selectedRegion.rowKey = (inputTag as ITableTag[])[0].fields[rowIndex].fieldKey;
|
||||
selectedRegion.columnKey = (inputTag as ITableTag[])[0].definition.fields[columnIndex].fieldKey;
|
||||
} else {
|
||||
selectedRegion.rowKey = (inputTag as ITableTag[])[0].definition.fields[rowIndex].fieldKey;
|
||||
selectedRegion.columnKey = (inputTag as ITableTag[])[0].fields[columnIndex].fieldKey;
|
||||
}
|
||||
}
|
||||
selectedRegion.isTableRegion = true;
|
||||
}
|
||||
}
|
||||
|
||||
this.updateRegions([...selectedRegions, ...regions]);
|
||||
|
||||
this.selectedRegionIds = [];
|
||||
|
@ -486,7 +547,11 @@ export default class Canvas extends React.Component<ICanvasProps, ICanvasState>
|
|||
}
|
||||
|
||||
if (selectedRegions.length === 1 && selectedRegions[0].category === FeatureCategory.Checkbox) {
|
||||
this.setTagType(inputTag[0], FieldType.SelectionMark);
|
||||
if (inputTag[0].type === FieldType.Object || inputTag[0].type === FieldType.Array) {
|
||||
// selection mark logic placeholder
|
||||
} else {
|
||||
this.setTagType(inputTag[0], FieldType.SelectionMark);
|
||||
}
|
||||
} else if (selectedRegions[0].category === FeatureCategory.DrawnRegion) {
|
||||
selectedRegions.forEach((selectedRegion) => {
|
||||
this.imageMap.removeDrawnRegionFeature(this.imageMap.getDrawnRegionFeatureByID(selectedRegion.id));
|
||||
|
@ -702,8 +767,8 @@ export default class Canvas extends React.Component<ICanvasProps, ICanvasState>
|
|||
currentAsset.labelData.labelingState = this.state.currentAsset.labelData.labelingState;
|
||||
}
|
||||
}
|
||||
if(currentAsset.labelData?.labelingState!==AssetLabelingState.AutoLabeledAndAdjusted
|
||||
&&(!currentAsset.labelData||currentAsset.labelData.labels?.findIndex(label=>label.value.length>0)<0)){
|
||||
if (currentAsset.labelData?.labelingState !== AssetLabelingState.AutoLabeledAndAdjusted
|
||||
&& (!currentAsset.labelData || currentAsset.labelData.labels?.findIndex(label => label.value.length > 0) < 0)) {
|
||||
delete currentAsset.labelData?.labelingState;
|
||||
delete currentAsset.asset.labelingState;
|
||||
}
|
||||
|
@ -768,7 +833,6 @@ export default class Canvas extends React.Component<ICanvasProps, ICanvasState>
|
|||
updatedRegions.push(update);
|
||||
}
|
||||
}
|
||||
|
||||
updatedRegions.sort(this.compareRegionOrder);
|
||||
this.updateAssetRegions(updatedRegions, true);
|
||||
}
|
||||
|
@ -1093,14 +1157,26 @@ export default class Canvas extends React.Component<ICanvasProps, ICanvasState>
|
|||
}
|
||||
|
||||
private updateHighlightStatus = (feature: any): void => {
|
||||
if (this.props.hoveredLabel) {
|
||||
const label = this.props.hoveredLabel;
|
||||
if (this.props.hoveredLabel || this.props.highlightedTableCell) {
|
||||
let label = this.props.hoveredLabel
|
||||
const id = feature.get("id");
|
||||
if (label.value?.find((region) =>
|
||||
id === this.createRegionIdFromBoundingBox(region.boundingBoxes[0], region.page))) {
|
||||
this.setFeatureProperty(feature, "highlighted", true);
|
||||
if (label?.tableKey) {
|
||||
const tableLableValues = [];
|
||||
label.labels.forEach((i: { value: ITableLabel[]; }) => {
|
||||
i.value.forEach((i: ITableLabel) => tableLableValues.push(i))
|
||||
});
|
||||
label = { label: label.tableKey, value: tableLableValues };
|
||||
}
|
||||
} else if (feature.get("highlighted")) {
|
||||
|
||||
if (label?.value?.find((region: { boundingBoxes: number[][]; page: number; }) =>
|
||||
id === this.createRegionIdFromBoundingBox(region.boundingBoxes[0], region.page))
|
||||
|| this.props.highlightedTableCell?.find(i => i.id === id)) {
|
||||
this.setFeatureProperty(feature, "highlighted", true);
|
||||
} else {
|
||||
this.setFeatureProperty(feature, "highlighted", false);
|
||||
}
|
||||
}
|
||||
else if (feature.get("highlighted")) {
|
||||
this.setFeatureProperty(feature, "highlighted", false);
|
||||
}
|
||||
}
|
||||
|
@ -1270,7 +1346,7 @@ export default class Canvas extends React.Component<ICanvasProps, ICanvasState>
|
|||
}
|
||||
|
||||
private loadOcr = async (force?: boolean) => {
|
||||
const asset = {...this.state.currentAsset.asset};
|
||||
const asset = { ...this.state.currentAsset.asset };
|
||||
|
||||
if (asset.isRunningOCR) {
|
||||
// Skip loading OCR this time since it's running. This will be triggered again once it's finished.
|
||||
|
@ -1280,10 +1356,10 @@ export default class Canvas extends React.Component<ICanvasProps, ICanvasState>
|
|||
const ocr = await this.ocrService.getRecognizedText(asset.path, asset.name, asset.mimeType, this.setOCRStatus, force);
|
||||
if (asset.id === this.state.currentAsset.asset.id) {
|
||||
// since get OCR is async, we only set currentAsset's OCR
|
||||
const newAsset={};
|
||||
if(asset.state===AssetState.NotVisited){
|
||||
asset.state=AssetState.Visited;
|
||||
newAsset["currentAsset"]={...this.state.currentAsset, asset};
|
||||
const newAsset = {};
|
||||
if (asset.state === AssetState.NotVisited) {
|
||||
asset.state = AssetState.Visited;
|
||||
newAsset["currentAsset"] = { ...this.state.currentAsset, asset };
|
||||
}
|
||||
this.setState({
|
||||
...newAsset,
|
||||
|
@ -1412,59 +1488,175 @@ export default class Canvas extends React.Component<ICanvasProps, ICanvasState>
|
|||
});
|
||||
}
|
||||
|
||||
private convertLabelDataToRegions = (labelData: ILabelData): IRegion[] => {
|
||||
const regions = [];
|
||||
private getLabelLayers = (label: string) => {
|
||||
return this.decodeLabelLayers(label?.split("/"));
|
||||
}
|
||||
|
||||
if (labelData && labelData.labels) {
|
||||
labelData.labels.forEach((label) => {
|
||||
if (label.value) {
|
||||
label.value.forEach((formRegion) => {
|
||||
if (formRegion.boundingBoxes) {
|
||||
formRegion.boundingBoxes.forEach((boundingBox, boundingBoxIndex) => {
|
||||
const text = this.getBoundingBoxTextFromRegion(formRegion, boundingBoxIndex);
|
||||
regions.push(this.createRegion(boundingBox, text, label.label, formRegion.page, label?.labelType));
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
private getRegionCellKeys = (layers: string[], tableTag: ITableTag) => {
|
||||
let rowKey;
|
||||
let columnKey;
|
||||
if (tableTag.type === FieldType.Object) {
|
||||
const firstLayerField = tableTag.fields.find((field) => {
|
||||
return field.fieldKey === layers[1];
|
||||
})?.fieldKey
|
||||
|
||||
const secondLayerField = tableTag.definition.fields.find((field) => {
|
||||
return field.fieldKey === layers[2];
|
||||
})?.fieldKey
|
||||
if (!firstLayerField || !secondLayerField) {
|
||||
return;
|
||||
}
|
||||
if (tableTag.visualizationHint === TableVisualizationHint.Vertical) {
|
||||
rowKey = firstLayerField;
|
||||
columnKey = secondLayerField;
|
||||
} else {
|
||||
rowKey = secondLayerField;
|
||||
columnKey = firstLayerField;
|
||||
}
|
||||
|
||||
} else if (tableTag.type === FieldType.Array) {
|
||||
const firstLayerField = layers[1];
|
||||
const secondLayerField = tableTag.definition.fields.find((field) => {
|
||||
return field.fieldKey === layers[2];
|
||||
})?.fieldKey;
|
||||
if (!secondLayerField) {
|
||||
return;
|
||||
}
|
||||
rowKey = "#" + firstLayerField;
|
||||
columnKey = secondLayerField;
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
return { rowKey, columnKey }
|
||||
}
|
||||
|
||||
private encodeLabelString = (labelString: string): string => {
|
||||
return labelString.replace(/\~/g, "~0").replace(/\//g, "~1")
|
||||
}
|
||||
|
||||
private decodeLabelString = (labelString: string): string => {
|
||||
return labelString.replace(/\~1/g, "/").replace(/\~0/g, "~")
|
||||
}
|
||||
|
||||
private encodeLabelLayers = (layers: string[]): string[] => {
|
||||
return layers.map((layer) => { return this.encodeLabelString(layer) })
|
||||
}
|
||||
|
||||
private decodeLabelLayers = (layers: string[]): string[] => {
|
||||
return layers.map((layer) => { return this.decodeLabelString(layer) })
|
||||
}
|
||||
|
||||
private convertLabelDataToRegions = (labelData: ILabelData): IRegion[] => {
|
||||
let regions = [];
|
||||
const encodedSchema = labelData?.$schema === constants.labelsSchema;
|
||||
|
||||
labelData?.labels?.forEach((label) => {
|
||||
|
||||
regions = [...regions, ...this.convertLabelToRegion(label, encodedSchema)];
|
||||
});
|
||||
|
||||
return regions;
|
||||
}
|
||||
private convertLabelToRegion = (label: ILabel): IRegion[] => {
|
||||
|
||||
private convertLabelToRegion = (label: ILabel, encodedSchema: boolean): IRegion[] => {
|
||||
const labelValue = label?.label
|
||||
let layers;
|
||||
if (encodedSchema) {
|
||||
layers = this.getLabelLayers(labelValue);
|
||||
}
|
||||
const regions = [];
|
||||
if (label.value) {
|
||||
label.value.forEach((formRegion) => {
|
||||
if (formRegion.boundingBoxes) {
|
||||
formRegion.boundingBoxes.forEach((boundingBox, boundingBoxIndex) => {
|
||||
const text = this.getBoundingBoxTextFromRegion(formRegion, boundingBoxIndex);
|
||||
regions.push(this.createRegion(boundingBox, text, label.label, formRegion.page, label?.labelType));
|
||||
});
|
||||
if (encodedSchema && layers?.length > 1) {
|
||||
// temp check until nested tables are supported
|
||||
if (layers?.length !== 3) {
|
||||
return;
|
||||
}
|
||||
const labelsTag = this.props.project.tags.find((tag) => {
|
||||
return tag.name === layers[0];
|
||||
})
|
||||
if (labelsTag) {
|
||||
const tableTag = labelsTag as ITableTag;
|
||||
const { rowKey, columnKey } = this.getRegionCellKeys(layers, tableTag);
|
||||
if (!rowKey || !columnKey) {
|
||||
return
|
||||
}
|
||||
});
|
||||
label.value.forEach((formRegion) => {
|
||||
if (formRegion.boundingBoxes) {
|
||||
formRegion.boundingBoxes.forEach((boundingBox, boundingBoxIndex) => {
|
||||
const text = this.getBoundingBoxTextFromRegion(formRegion, boundingBoxIndex);
|
||||
const tx = { ...this.createRegion(boundingBox, text, labelsTag.name, formRegion.page, label?.labelType), rowKey, columnKey, isTableRegion: true } as ITableRegion;
|
||||
regions.push(tx);
|
||||
});
|
||||
}
|
||||
});
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
if (label.value) {
|
||||
label.value.forEach((formRegion) => {
|
||||
if (formRegion.boundingBoxes) {
|
||||
formRegion.boundingBoxes.forEach((boundingBox, boundingBoxIndex) => {
|
||||
const text = this.getBoundingBoxTextFromRegion(formRegion, boundingBoxIndex);
|
||||
if (encodedSchema) {
|
||||
regions.push(this.createRegion(boundingBox, text, this.decodeLabelString(label.label), formRegion.page, label?.labelType));
|
||||
} else {
|
||||
regions.push(this.createRegion(boundingBox, text, label.label, formRegion.page, label?.labelType));
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
return regions;
|
||||
}
|
||||
|
||||
private getTableLabelFromRegion = (tableTag: ITableTag, tableRegion: ITableRegion) => {
|
||||
const columnKey = this.encodeLabelString(tableRegion.columnKey);
|
||||
const rowKey = this.encodeLabelString(tableRegion.rowKey);
|
||||
const tableName = this.encodeLabelString(tableTag.name);
|
||||
if (tableTag.type === FieldType.Array) {
|
||||
return tableName + "/" + rowKey.slice(1) + "/" + columnKey;
|
||||
} else if (tableTag.visualizationHint === TableVisualizationHint.Vertical) {
|
||||
return tableName + "/" + rowKey + "/" + columnKey;
|
||||
} else {
|
||||
return tableName + "/" + columnKey + "/" + rowKey;
|
||||
}
|
||||
}
|
||||
|
||||
private convertRegionsToLabelData = (regions: IRegion[], assetName: string) => {
|
||||
const labels = (this.props.selectedAsset
|
||||
&& this.props.selectedAsset.labelData
|
||||
&& this.props.selectedAsset.labelData.labels
|
||||
&& this.props.selectedAsset.labelData.labels.map(label => ({
|
||||
...label, value: []
|
||||
}))) || [];
|
||||
const labelData: ILabelData = {
|
||||
$schema: constants.labelsSchema,
|
||||
document: decodeURIComponent(assetName).split("/").pop(),
|
||||
labels: [] as ILabel[],
|
||||
};
|
||||
|
||||
const labels = (this.props?.selectedAsset?.labelData?.labels?.map(label => {
|
||||
if (this.props.selectedAsset.labelData.$schema === constants.labelsSchema) {
|
||||
return ({
|
||||
...label,
|
||||
value: []
|
||||
})
|
||||
} else {
|
||||
return ({
|
||||
...label,
|
||||
label: this.encodeLabelString(label.label),
|
||||
value: []
|
||||
})
|
||||
}
|
||||
|
||||
})) || [];
|
||||
|
||||
const selectedRegions = this.getSelectedRegions();
|
||||
if (selectedRegions.length > 0) {
|
||||
const intersectionResult = _.intersection(selectedRegions, regions);
|
||||
if (intersectionResult.length === 0) {
|
||||
const relatedLabels = labels.filter(label => selectedRegions.find(sr => sr.tags.find(t => t === label.label)));
|
||||
relatedLabels?.forEach(relatedLabel=>{
|
||||
relatedLabels?.forEach(relatedLabel => {
|
||||
if (relatedLabel && relatedLabel.confidence) {
|
||||
const originLabel = this.props.selectedAsset!.labelData?.labels?.find(a => a.label === relatedLabel.label);
|
||||
if (originLabel) {
|
||||
relatedLabel.revised = true;
|
||||
if(!relatedLabel.originValue){
|
||||
if (!relatedLabel.originValue) {
|
||||
relatedLabel.originValue = [...originLabel.value];
|
||||
}
|
||||
}
|
||||
|
@ -1478,7 +1670,9 @@ export default class Canvas extends React.Component<ICanvasProps, ICanvasState>
|
|||
item.value?.findIndex(v => v.boundingBoxes?.findIndex(b =>
|
||||
_.isEqual(b, boundingBox)) >= 0 && v.page === region.pageNumber) >= 0);
|
||||
}
|
||||
|
||||
regions.sort(this.compareRegionOrder);
|
||||
|
||||
regions.forEach((region) => {
|
||||
const labelType = this.getLabelType(region.category);
|
||||
const boundingBox = region.id.split(",").map(parseFloat);
|
||||
|
@ -1488,12 +1682,24 @@ export default class Canvas extends React.Component<ICanvasProps, ICanvasState>
|
|||
boundingBoxes: [boundingBox],
|
||||
} as IFormRegion;
|
||||
region.tags.forEach((tag) => {
|
||||
const label = labels.find(label => label.label === tag);
|
||||
let label;
|
||||
if (region.isTableRegion) {
|
||||
const tableRegion = region as ITableRegion;
|
||||
const tableTag = this.props.project.tags.find((projectTag) => tag === projectTag.name) as ITableTag;
|
||||
if (!tableTag) return
|
||||
label = labels.find(label => label?.label === this.getTableLabelFromRegion(tableTag, tableRegion));
|
||||
} else {
|
||||
if (this.props.selectedAsset.labelData.$schema === constants.labelsSchema) {
|
||||
label = labels.find(label => this.decodeLabelString(label?.label) === tag);
|
||||
} else {
|
||||
label = labels.find(label => label?.label === tag);
|
||||
}
|
||||
}
|
||||
if (label) {
|
||||
const originLabel = this.props.selectedAsset!.labelData?.labels?.find(a => a.label === tag);
|
||||
if (originLabel && label.confidence && region.changed) {
|
||||
label.revised = true;
|
||||
if(!label.originValue){
|
||||
if (!label.originValue) {
|
||||
label.originValue = [...originLabel.value];
|
||||
}
|
||||
}
|
||||
|
@ -1507,34 +1713,43 @@ export default class Canvas extends React.Component<ICanvasProps, ICanvasState>
|
|||
}
|
||||
}
|
||||
}
|
||||
if (originLabel && region.changed && label.labelType !== labelType) {
|
||||
if (labelType) {
|
||||
label.labelType = labelType;
|
||||
} else {
|
||||
delete label.labelType;
|
||||
}
|
||||
label.value.push(formRegion);
|
||||
} else {
|
||||
let newLabel;
|
||||
let labelName = this.encodeLabelString(tag);
|
||||
if (region.isTableRegion) {
|
||||
const tableRegion = region as ITableRegion;
|
||||
const tableTag = this.props.project.tags.find((projectTag) => tag === projectTag.name) as ITableTag;
|
||||
if (!tableTag) return
|
||||
labelName = this.getTableLabelFromRegion(tableTag, tableRegion);
|
||||
}
|
||||
if (labelType) {
|
||||
newLabel = {
|
||||
label: tag,
|
||||
label: labelName,
|
||||
key: null,
|
||||
labelType,
|
||||
value: [formRegion],
|
||||
} as ILabel;
|
||||
} else {
|
||||
newLabel = {
|
||||
label: tag,
|
||||
label: labelName,
|
||||
key: null,
|
||||
value: [formRegion],
|
||||
} as ILabel;
|
||||
}
|
||||
labels.push(newLabel);
|
||||
}
|
||||
labelData.labels = [...labels]
|
||||
});
|
||||
});
|
||||
const labelData:ILabelData={
|
||||
document: decodeURIComponent(assetName).split("/").pop(),
|
||||
labels: [...labels],
|
||||
}
|
||||
labelData.document = decodeURIComponent(assetName).split("/").pop();
|
||||
labelData.labels = labelData.labels.filter((label) => label.value.length > 0 && !label.revised);
|
||||
|
||||
return labelData;
|
||||
}
|
||||
|
||||
|
@ -1777,7 +1992,9 @@ export default class Canvas extends React.Component<ICanvasProps, ICanvasState>
|
|||
|
||||
}
|
||||
private compareLabelChanged(newLabels: ILabel[], prevLabels: ILabel[]): boolean {
|
||||
if (newLabels.length > 0) {
|
||||
if (newLabels.length !== prevLabels.length) {
|
||||
return true;
|
||||
} else if (newLabels.length > 0) {
|
||||
const newFieldNames = newLabels.map((label) => label.label);
|
||||
const prevFieldNames = prevLabels.map((label) => label.label);
|
||||
if (_.isEqual(newFieldNames.sort(), prevFieldNames.sort())) {
|
||||
|
@ -1785,7 +2002,32 @@ export default class Canvas extends React.Component<ICanvasProps, ICanvasState>
|
|||
const newValue = newLabels.find(label => label.label === name).value?.map(region => region.boundingBoxes).join(",");
|
||||
const prevValue = prevLabels.find(label => label.label === name).value?.map(region => region.boundingBoxes).join(",");
|
||||
if (newValue !== prevValue) {
|
||||
return true;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private isTableLabelDataChanged = (newProps: ICanvasProps, prevProps: ICanvasProps): boolean => {
|
||||
const newLabels = _.get(newProps, "selectedAsset.labelData.tableLabels", []) as ITableLabel[];
|
||||
const prevLabels = _.get(prevProps, "selectedAsset.labelData.tableLabels", []) as ITableLabel[];
|
||||
|
||||
if (newLabels.length !== prevLabels.length) {
|
||||
return true;
|
||||
} else if (newLabels.length > 0) {
|
||||
const newFieldNames = newLabels.map((label) => label.tableKey);
|
||||
const prevFieldNames = prevLabels.map((label) => label.tableKey);
|
||||
if (_.isEqual(newFieldNames.sort(), prevFieldNames.sort())) {
|
||||
for (const name of newFieldNames) {
|
||||
const newValue = newLabels.find(label => label.tableKey === name);
|
||||
const prevValue = prevLabels.find(label => label.tableKey === name);
|
||||
if (!_.isEqual(newValue, prevValue)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
|
@ -2395,7 +2637,7 @@ export default class Canvas extends React.Component<ICanvasProps, ICanvasState>
|
|||
}
|
||||
|
||||
async focusOnLabel(label: ILabel) {
|
||||
const page = label.value[ 0 ]?.page;
|
||||
const page = label.value[0]?.page;
|
||||
if (page && this.state.currentPage !== page) {
|
||||
await this.goToPage(page);
|
||||
}
|
||||
|
|
|
@ -2,14 +2,15 @@
|
|||
// Licensed under the MIT license.
|
||||
|
||||
import * as React from "react";
|
||||
import {CommandBar, ICommandBarItemProps} from "@fluentui/react/lib/CommandBar";
|
||||
import {ICustomizations, Customizer} from "@fluentui/react/lib/Utilities";
|
||||
import {getDarkGreyTheme} from "../../../../common/themes";
|
||||
import {strings} from '../../../../common/strings';
|
||||
import {ContextualMenuItemType} from "@fluentui/react";
|
||||
import {IProject, IAssetMetadata, AssetLabelingState} from "../../../../models/applicationState";
|
||||
import { CommandBar, ICommandBarItemProps } from "@fluentui/react/lib/CommandBar";
|
||||
import { ICustomizations, Customizer } from "@fluentui/react/lib/Utilities";
|
||||
import { getDarkGreyTheme } from "../../../../common/themes";
|
||||
import { interpolate, strings } from '../../../../common/strings';
|
||||
import { ContextualMenuItemType } from "@fluentui/react";
|
||||
import { IProject, IAssetMetadata, AssetLabelingState } from "../../../../models/applicationState";
|
||||
import _ from "lodash";
|
||||
import "./canvasCommandBar.scss";
|
||||
import { constants } from "../../../../common/constants";
|
||||
|
||||
interface ICanvasCommandBarProps {
|
||||
handleZoomIn: () => void;
|
||||
|
@ -24,7 +25,6 @@ interface ICanvasCommandBarProps {
|
|||
project?: IProject;
|
||||
selectedAsset?: IAssetMetadata;
|
||||
handleRotateImage: (degrees: number) => void;
|
||||
|
||||
drawRegionMode?: boolean;
|
||||
connectionType?: string;
|
||||
layers?: any;
|
||||
|
@ -213,8 +213,8 @@ export const CanvasCommandBar: React.FunctionComponent<ICanvasCommandBarProps> =
|
|||
{
|
||||
key: "deleteAsset",
|
||||
text: strings.editorPage.asset.delete.title,
|
||||
iconProps: {iconName: "Delete"},
|
||||
onClick: () => {if (props.handleAssetDeleted) props.handleAssetDeleted();},
|
||||
iconProps: { iconName: "Delete" },
|
||||
onClick: () => { if (props.handleAssetDeleted) props.handleAssetDeleted(); },
|
||||
}
|
||||
],
|
||||
},
|
||||
|
|
|
@ -425,7 +425,6 @@ describe("Editor Page Component", () => {
|
|||
await waitForSelectedAsset(wrapper);
|
||||
|
||||
const tagToDelete = project.tags[project.tags.length - 1];
|
||||
wrapper.find(TagInput).props().onTagDeleted(tagToDelete.name);
|
||||
|
||||
// Accept the modal delete warning
|
||||
wrapper.update();
|
||||
|
|
|
@ -13,7 +13,7 @@ import { strings, interpolate } from "../../../../common/strings";
|
|||
import {
|
||||
AssetState, AssetType, EditorMode, FieldType,
|
||||
IApplicationState, IAppSettings, IAsset, IAssetMetadata,
|
||||
ILabel, IProject, IRegion, ISize, ITag, FeatureCategory, FieldFormat, AssetLabelingState,
|
||||
ILabel, IProject, IRegion, ISize, ITag, FeatureCategory, TagInputMode, FieldFormat, ITableTag, ITableRegion, AssetLabelingState, ITableConfigItem, TableVisualizationHint
|
||||
} from "../../../../models/applicationState";
|
||||
import IApplicationActions, * as applicationActions from "../../../../redux/actions/applicationActions";
|
||||
import IProjectActions, * as projectActions from "../../../../redux/actions/projectActions";
|
||||
|
@ -31,14 +31,15 @@ import EditorSideBar from "./editorSideBar";
|
|||
import Alert from "../../common/alert/alert";
|
||||
import Confirm from "../../common/confirm/confirm";
|
||||
import { OCRService, OcrStatus } from "../../../../services/ocrService";
|
||||
import { throttle } from "../../../../common/utils";
|
||||
import { getTagCategory, throttle } from "../../../../common/utils";
|
||||
import { constants } from "../../../../common/constants";
|
||||
import PreventLeaving from "../../common/preventLeaving/preventLeaving";
|
||||
import { Spinner, SpinnerSize } from "@fluentui/react/lib/Spinner";
|
||||
import { getPrimaryGreenTheme, getPrimaryRedTheme } from "../../../../common/themes";
|
||||
import { getPrimaryBlueTheme, getPrimaryGreenTheme, getPrimaryRedTheme } from "../../../../common/themes";
|
||||
import { toast } from "react-toastify";
|
||||
import { PredictService } from "../../../../services/predictService";
|
||||
import { AssetService } from "../../../../services/assetService";
|
||||
import clone from "rfdc";
|
||||
|
||||
/**
|
||||
* Properties for Editor Page
|
||||
|
@ -96,7 +97,13 @@ export interface IEditorPageState {
|
|||
errorMessage?: string;
|
||||
tableToView: object;
|
||||
tableToViewId: string;
|
||||
tagInputMode: TagInputMode;
|
||||
selectedTableTagToLabel: ITableTag;
|
||||
selectedTableTagBody: ITableRegion[][][];
|
||||
rightSplitPaneWidth?: number;
|
||||
reconfigureTableConfirm?: boolean;
|
||||
pageNumber: number;
|
||||
highlightedTableCellRegions: ITableRegion[];
|
||||
}
|
||||
|
||||
function mapStateToProps(state: IApplicationState) {
|
||||
|
@ -133,7 +140,11 @@ export default class EditorPage extends React.Component<IEditorPageProps, IEdito
|
|||
hoveredLabel: null,
|
||||
tableToView: null,
|
||||
tableToViewId: null,
|
||||
pageNumber: 1
|
||||
tagInputMode: TagInputMode.Basic,
|
||||
selectedTableTagToLabel: null,
|
||||
selectedTableTagBody: [[]],
|
||||
pageNumber: 1,
|
||||
highlightedTableCellRegions: null,
|
||||
};
|
||||
|
||||
private tagInputRef: RefObject<TagInput>;
|
||||
|
@ -144,7 +155,12 @@ export default class EditorPage extends React.Component<IEditorPageProps, IEdito
|
|||
private renameCanceled: () => void;
|
||||
private deleteTagConfirm: React.RefObject<Confirm> = React.createRef();
|
||||
private deleteDocumentConfirm: React.RefObject<Confirm> = React.createRef();
|
||||
private reconfigTableConfirm: React.RefObject<Confirm> = React.createRef();
|
||||
private replaceConfirmRef = React.createRef<Confirm>();
|
||||
|
||||
|
||||
private isUnmount: boolean = false;
|
||||
public initialRightSplitPaneWidth: number;
|
||||
private isOCROrAutoLabelingBatchRunning = false;
|
||||
|
||||
constructor(props) {
|
||||
|
@ -191,6 +207,9 @@ export default class EditorPage extends React.Component<IEditorPageProps, IEdito
|
|||
const labels = (selectedAsset &&
|
||||
selectedAsset.labelData &&
|
||||
selectedAsset.labelData.labels) || [];
|
||||
const tableLabels = (selectedAsset &&
|
||||
selectedAsset.labelData &&
|
||||
selectedAsset.labelData.tableLabels) || [];
|
||||
|
||||
const needRunOCRButton = assets.some((asset) => asset.state === AssetState.NotVisited);
|
||||
|
||||
|
@ -198,6 +217,9 @@ export default class EditorPage extends React.Component<IEditorPageProps, IEdito
|
|||
return (<div>Loading...</div>);
|
||||
}
|
||||
|
||||
const isBasicInputMode = this.state.tagInputMode === TagInputMode.Basic;
|
||||
this.initialRightSplitPaneWidth = isBasicInputMode ? 290 : 520;
|
||||
|
||||
return (
|
||||
<div className="editor-page skipToMainContent" id="pageEditor">
|
||||
{this.state.tableToView !== null &&
|
||||
|
@ -208,15 +230,16 @@ export default class EditorPage extends React.Component<IEditorPageProps, IEdito
|
|||
}
|
||||
{
|
||||
tagIndexKeys.map((index) =>
|
||||
(<KeyboardBinding
|
||||
displayName={strings.editorPage.tags.hotKey.apply}
|
||||
key={index}
|
||||
keyEventType={KeyEventType.KeyDown}
|
||||
accelerators={[`${index}`]}
|
||||
icon={"fa-tag"}
|
||||
handler={this.handleTagHotKey} />))
|
||||
(<KeyboardBinding
|
||||
displayName={strings.editorPage.tags.hotKey.apply}
|
||||
key={index}
|
||||
keyEventType={KeyEventType.KeyDown}
|
||||
accelerators={[`${index}`]}
|
||||
icon={"fa-tag"}
|
||||
handler={this.handleTagHotKey} />))
|
||||
}
|
||||
<SplitPane split="vertical"
|
||||
<SplitPane
|
||||
split="vertical"
|
||||
defaultSize={this.state.thumbnailSize.width}
|
||||
minSize={150}
|
||||
maxSize={325}
|
||||
|
@ -251,19 +274,33 @@ export default class EditorPage extends React.Component<IEditorPageProps, IEdito
|
|||
onAssetLoaded={this.onAssetLoaded}
|
||||
thumbnailSize={this.state.thumbnailSize}
|
||||
/>
|
||||
|
||||
</div>
|
||||
<div className="editor-page-content" onClick={this.onPageClick}>
|
||||
<SplitPane split="vertical"
|
||||
primary="second"
|
||||
maxSize={625}
|
||||
minSize={290}
|
||||
maxSize={isBasicInputMode ? 400 : 700}
|
||||
minSize={this.initialRightSplitPaneWidth}
|
||||
className={"right-vertical_splitPane"}
|
||||
pane1Style={{ height: "100%" }}
|
||||
pane2Style={{ height: "auto" }}
|
||||
resizerStyle={{ width: "5px", margin: "0px", border: "2px", background: "transparent" }}
|
||||
onChange={() => this.resizeCanvas()}>
|
||||
resizerStyle={{
|
||||
width: "5px",
|
||||
margin: "0px",
|
||||
border: "2px",
|
||||
}}
|
||||
onChange={(width) => {
|
||||
if (!isBasicInputMode) {
|
||||
this.setState({ rightSplitPaneWidth: width > 700 ? 700 : width }, () => {
|
||||
this.resizeCanvas();
|
||||
});
|
||||
this.resizeCanvas();
|
||||
}
|
||||
}}>
|
||||
<div className="editor-page-content-main" >
|
||||
<div className="editor-page-content-main-body" onClick={this.onPageContainerClick}>
|
||||
{selectedAsset &&
|
||||
|
||||
<Canvas
|
||||
ref={this.canvas}
|
||||
selectedAsset={this.state.selectedAsset}
|
||||
|
@ -286,6 +323,8 @@ export default class EditorPage extends React.Component<IEditorPageProps, IEdito
|
|||
onPageLoaded={this.onPageLoaded}
|
||||
runAutoLabelingOnNextBatch={this.runAutoLabelingOnNextBatch}
|
||||
appSettings={this.props.appSettings}
|
||||
handleLabelTable={this.handleLabelTable}
|
||||
highlightedTableCell={this.state.highlightedTableCellRegions}
|
||||
>
|
||||
<AssetPreview
|
||||
controlsEnabled={this.state.isValid}
|
||||
|
@ -302,6 +341,8 @@ export default class EditorPage extends React.Component<IEditorPageProps, IEdito
|
|||
lockedTags={this.state.lockedTags}
|
||||
selectedRegions={this.state.selectedRegions}
|
||||
labels={labels}
|
||||
encoded={selectedAsset?.labelData?.$schema === constants.labelsSchema}
|
||||
tableLabels={tableLabels}
|
||||
pageNumber={this.state.pageNumber}
|
||||
onChange={this.onTagsChanged}
|
||||
onLockedTagsChange={this.onLockedTagsChanged}
|
||||
|
@ -312,11 +353,23 @@ export default class EditorPage extends React.Component<IEditorPageProps, IEdito
|
|||
onLabelEnter={this.onLabelEnter}
|
||||
onLabelLeave={this.onLabelLeave}
|
||||
onTagChanged={this.onTagChanged}
|
||||
onTagDoubleClick={this.onLabelDoubleClicked}
|
||||
ref={this.tagInputRef}
|
||||
setTagInputMode={this.setTagInputMode}
|
||||
tagInputMode={this.state.tagInputMode}
|
||||
handleLabelTable={this.handleLabelTable}
|
||||
selectedTableTagToLabel={this.state.selectedTableTagToLabel}
|
||||
handleTableCellClick={this.handleTableCellClick}
|
||||
handleTableCellMouseEnter={this.handleTableCellMouseEnter}
|
||||
handleTableCellMouseLeave={this.handleTableCellMouseLeave}
|
||||
selectedTableTagBody={this.state.selectedTableTagBody}
|
||||
splitPaneWidth={this.state.rightSplitPaneWidth}
|
||||
reconfigureTableConfirm={this.reconfigureTableConfirm}
|
||||
addRowToDynamicTable={this.addRowToDynamicTable}
|
||||
onTagDoubleClick={this.onLabelDoubleClicked}
|
||||
/>
|
||||
<Confirm
|
||||
title={strings.editorPage.tags.rename.title}
|
||||
loadMessage={"Renaming..."}
|
||||
ref={this.renameTagConfirm}
|
||||
message={strings.editorPage.tags.rename.confirmation}
|
||||
confirmButtonTheme={getPrimaryRedTheme()}
|
||||
|
@ -342,6 +395,22 @@ export default class EditorPage extends React.Component<IEditorPageProps, IEdito
|
|||
onConfirm={this.onAssetDeleted}
|
||||
/>
|
||||
}
|
||||
<Confirm
|
||||
title={strings.tags.regionTableTags.confirm.reconfigure.title}
|
||||
loadMessage={"Reconfiguring..."}
|
||||
ref={this.reconfigTableConfirm}
|
||||
message={strings.tags.regionTableTags.confirm.reconfigure.message}
|
||||
confirmButtonTheme={getPrimaryBlueTheme()}
|
||||
onConfirm={this.reconfigureTable}
|
||||
/>
|
||||
<Confirm
|
||||
title={strings.tags.warnings.replaceAllExitingLabelsTitle}
|
||||
ref={this.replaceConfirmRef}
|
||||
message={strings.tags.warnings.replaceAllExitingLabels}
|
||||
confirmButtonTheme={getPrimaryRedTheme()}
|
||||
onConfirm={this.onTableTagClicked}
|
||||
/>
|
||||
|
||||
</div>
|
||||
</SplitPane>
|
||||
</div>
|
||||
|
@ -363,12 +432,13 @@ export default class EditorPage extends React.Component<IEditorPageProps, IEdito
|
|||
errorMessage: undefined,
|
||||
})}
|
||||
/>
|
||||
|
||||
<PreventLeaving
|
||||
when={isRunningOCRs || isCanvasRunningOCR}
|
||||
message={strings.editorPage.warningMessage.PreventLeavingWhileRunningOCR}
|
||||
/>
|
||||
<PreventLeaving
|
||||
when={isCanvasRunningAutoLabeling||isRunningAutoLabelings}
|
||||
when={isCanvasRunningAutoLabeling || isRunningAutoLabelings}
|
||||
message={strings.editorPage.warningMessage.PreventLeavingRunningAutoLabeling} />
|
||||
</div>
|
||||
);
|
||||
|
@ -386,6 +456,108 @@ export default class EditorPage extends React.Component<IEditorPageProps, IEdito
|
|||
private onPageClick = () => {
|
||||
}
|
||||
|
||||
private setTagInputMode = (tagInputMode: TagInputMode, selectedTableTagToLabel: ITableTag = this.state.selectedTableTagToLabel, selectedTableTagBody: ITableRegion[][][] = this.state.selectedTableTagBody) => {
|
||||
// this.resizeCanvas();
|
||||
|
||||
this.setState({
|
||||
selectedTableTagBody,
|
||||
selectedTableTagToLabel,
|
||||
tagInputMode,
|
||||
}, () => {
|
||||
this.resizeCanvas();
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
private handleLabelTable = (tagInputMode: TagInputMode = this.state.tagInputMode, selectedTableTagToLabel: ITableTag = this.state.selectedTableTagToLabel) => {
|
||||
|
||||
if (selectedTableTagToLabel == null || !this.state.selectedAsset) {
|
||||
return;
|
||||
}
|
||||
|
||||
let rowKeys;
|
||||
let columnKeys;
|
||||
if (selectedTableTagToLabel.type === FieldType.Object) {
|
||||
if (selectedTableTagToLabel.visualizationHint === TableVisualizationHint.Vertical) {
|
||||
columnKeys = selectedTableTagToLabel.definition.fields;
|
||||
rowKeys = selectedTableTagToLabel.fields;
|
||||
} else {
|
||||
columnKeys = selectedTableTagToLabel.fields;
|
||||
rowKeys = selectedTableTagToLabel.definition.fields;
|
||||
|
||||
}
|
||||
} else {
|
||||
rowKeys = null;
|
||||
columnKeys = selectedTableTagToLabel.definition.fields;
|
||||
}
|
||||
|
||||
const selectedTableTagBody = new Array(rowKeys?.length || 1);
|
||||
if (this.state.selectedTableTagToLabel?.name === selectedTableTagToLabel?.name && selectedTableTagToLabel.type === FieldType.Array) {
|
||||
for (let i = 1; i < this.state.selectedTableTagBody.length; i++) {
|
||||
selectedTableTagBody.push(undefined)
|
||||
}
|
||||
}
|
||||
for (let i = 0; i < selectedTableTagBody.length; i++) {
|
||||
selectedTableTagBody[i] = new Array(columnKeys.length);
|
||||
}
|
||||
|
||||
const tagAssets = clone()(this.state.selectedAsset.regions).filter((region) => region.tags[0] === selectedTableTagToLabel.name) as ITableRegion[];
|
||||
tagAssets.forEach((region => {
|
||||
let rowIndex: number;
|
||||
if (selectedTableTagToLabel.type === FieldType.Array) {
|
||||
rowIndex = Number(region.rowKey.slice(1));
|
||||
} else {
|
||||
rowIndex = rowKeys.findIndex(rowKey => rowKey.fieldKey === region.rowKey)
|
||||
}
|
||||
for (let i = selectedTableTagBody.length; i <= rowIndex; i++) {
|
||||
selectedTableTagBody.push(new Array(columnKeys.length));
|
||||
}
|
||||
const colIndex = columnKeys.findIndex(colKey => colKey.fieldKey === region.columnKey)
|
||||
if (selectedTableTagBody[rowIndex][colIndex] != null) {
|
||||
selectedTableTagBody[rowIndex][colIndex].push(region)
|
||||
} else {
|
||||
selectedTableTagBody[rowIndex][colIndex] = [region]
|
||||
}
|
||||
}));
|
||||
|
||||
this.setState({
|
||||
selectedTableTagToLabel,
|
||||
selectedTableTagBody,
|
||||
}, () => {
|
||||
|
||||
this.setTagInputMode(tagInputMode);
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
private addRowToDynamicTable = () => {
|
||||
const selectedTableTagBody = clone()(this.state.selectedTableTagBody)
|
||||
selectedTableTagBody.push(Array(this.state.selectedTableTagToLabel.definition.fields.length));
|
||||
this.setState({ selectedTableTagBody });
|
||||
}
|
||||
|
||||
private handleTableCellClick = (rowIndex: number, columnIndex: number) => {
|
||||
if (this.state?.selectedTableTagBody?.[rowIndex]?.[columnIndex]?.length > 0) {
|
||||
const selectionRegionCatagory = this.state.selectedRegions[0].category;
|
||||
const cellCatagory = this.state.selectedTableTagBody[rowIndex][columnIndex][0].category;
|
||||
if (selectionRegionCatagory !== cellCatagory && (selectionRegionCatagory === FeatureCategory.DrawnRegion || cellCatagory === FeatureCategory.DrawnRegion)) {
|
||||
this.replaceConfirmRef.current.open(this.state.selectedTableTagToLabel, rowIndex, columnIndex);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
this.onTableTagClicked(this.state.selectedTableTagToLabel, rowIndex, columnIndex);
|
||||
}
|
||||
|
||||
private handleTableCellMouseEnter = (regions) => {
|
||||
this.setState({ highlightedTableCellRegions: regions });
|
||||
}
|
||||
|
||||
private handleTableCellMouseLeave = () => {
|
||||
this.setState({ highlightedTableCellRegions: null });
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Called when the asset side bar is resized
|
||||
* @param newWidth The new sidebar width
|
||||
|
@ -397,7 +569,7 @@ export default class EditorPage extends React.Component<IEditorPageProps, IEdito
|
|||
height: newWidth / (4 / 3),
|
||||
},
|
||||
});
|
||||
this.resizeCanvas()
|
||||
this.resizeCanvas();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -423,6 +595,13 @@ export default class EditorPage extends React.Component<IEditorPageProps, IEdito
|
|||
}, () => this.canvas.current.applyTag(tag.name));
|
||||
}
|
||||
|
||||
private onTableTagClicked = (tag: ITag, rowIndex: number, columnIndex: number): void => {
|
||||
this.setState({
|
||||
selectedTag: tag.name,
|
||||
lockedTags: [],
|
||||
}, () => this.canvas.current.applyTag(tag.name, rowIndex, columnIndex));
|
||||
}
|
||||
|
||||
/**
|
||||
* Open confirm dialog for tag renaming
|
||||
*/
|
||||
|
@ -438,14 +617,29 @@ export default class EditorPage extends React.Component<IEditorPageProps, IEdito
|
|||
*/
|
||||
private onTagRenamed = async (tag: ITag, newTag: ITag): Promise<void> => {
|
||||
this.renameCanceled = null;
|
||||
const assetUpdates = await this.props.actions.updateProjectTag(this.props.project, tag, newTag);
|
||||
const selectedAsset = assetUpdates.find((am) => am.asset.id === this.state.selectedAsset.asset.id);
|
||||
|
||||
if (selectedAsset) {
|
||||
if (tag.type === FieldType.Object || tag.type === FieldType.Array) {
|
||||
const assetUpdates = await this.props.actions.reconfigureTableTag(this.props.project, tag.name, newTag.name, newTag.type, newTag.format, (newTag as ITableTag).visualizationHint, undefined, undefined, undefined, undefined);
|
||||
const selectedAsset = assetUpdates.find((am) => am.asset.id === this.state.selectedAsset.asset.id);
|
||||
if (selectedAsset) {
|
||||
this.setState({ selectedAsset });
|
||||
this.setState({
|
||||
selectedAsset,
|
||||
selectedTableTagToLabel: null,
|
||||
selectedTableTagBody: null,
|
||||
}, () => {
|
||||
this.canvas.current.temp();
|
||||
});
|
||||
}
|
||||
} else {
|
||||
const assetUpdates = await this.props.actions.updateProjectTag(this.props.project, tag, newTag);
|
||||
const selectedAsset = assetUpdates.find((am) => am.asset.id === this.state.selectedAsset.asset.id);
|
||||
|
||||
if (selectedAsset) {
|
||||
if (selectedAsset) {
|
||||
this.setState({ selectedAsset });
|
||||
}
|
||||
}
|
||||
}
|
||||
this.renameTagConfirm.current.close();
|
||||
}
|
||||
|
||||
private onTagRenameCanceled = () => {
|
||||
|
@ -458,8 +652,8 @@ export default class EditorPage extends React.Component<IEditorPageProps, IEdito
|
|||
/**
|
||||
* Open Confirm dialog for tag deletion
|
||||
*/
|
||||
private confirmTagDeleted = (tagName: string): void => {
|
||||
this.deleteTagConfirm.current.open(tagName);
|
||||
private confirmTagDeleted = (tagName: string, tagType: FieldType, tagFormat: FieldFormat): void => {
|
||||
this.deleteTagConfirm.current.open(tagName, tagType, tagFormat);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -473,8 +667,8 @@ export default class EditorPage extends React.Component<IEditorPageProps, IEdito
|
|||
* Removes tag from assets and projects and saves files
|
||||
* @param tagName Name of tag to be deleted
|
||||
*/
|
||||
private onTagDeleted = async (tagName: string): Promise<void> => {
|
||||
const assetUpdates = await this.props.actions.deleteProjectTag(this.props.project, tagName);
|
||||
private onTagDeleted = async (tagName: string, tagType: FieldType, tagFormat: FieldFormat): Promise<void> => {
|
||||
const assetUpdates = await this.props.actions.deleteProjectTag(this.props.project, tagName, tagType, tagFormat);
|
||||
|
||||
const selectedAsset = assetUpdates.find((am) => am.asset.id === this.state.selectedAsset.asset.id);
|
||||
if (selectedAsset) {
|
||||
|
@ -493,7 +687,7 @@ export default class EditorPage extends React.Component<IEditorPageProps, IEdito
|
|||
private getTagFromKeyboardEvent = (event: KeyboardEvent): ITag => {
|
||||
const index = tagIndexKeys.indexOf(event.key);
|
||||
const tags = this.props.project.tags;
|
||||
if (index >= 0 && index < tags.length) {
|
||||
if (index >= 0 && index < tags.length && (tags[index].type !== FieldType.Array || tags[index].type !== FieldType.Object)) {
|
||||
return tags[index];
|
||||
}
|
||||
return null;
|
||||
|
@ -509,7 +703,7 @@ export default class EditorPage extends React.Component<IEditorPageProps, IEdito
|
|||
|
||||
if (tag && selection.length) {
|
||||
const { format, type, documentCount, name } = tag;
|
||||
const tagCategory = this.tagInputRef.current.getTagCategory(tag.type);
|
||||
const tagCategory = getTagCategory(tag.type);
|
||||
const category = selection[0].category;
|
||||
const labels = this.state.selectedAsset.labelData?.labels;
|
||||
const isTagLabelTypeDrawnRegion = this.tagInputRef.current.labelAssignedDrawnRegion(labels, tag.name);
|
||||
|
@ -517,11 +711,11 @@ export default class EditorPage extends React.Component<IEditorPageProps, IEdito
|
|||
|
||||
if (labelAssigned && ((category === FeatureCategory.DrawnRegion) !== isTagLabelTypeDrawnRegion)) {
|
||||
if (isTagLabelTypeDrawnRegion) {
|
||||
toast.warn(interpolate(strings.tags.warnings.notCompatibleWithDrawnRegionTag, { otherCatagory: category }));
|
||||
toast.warn(interpolate(strings.tags.warnings.notCompatibleWithDrawnRegionTag, { otherCategory: category }));
|
||||
} else if (tagCategory === FeatureCategory.Checkbox) {
|
||||
toast.warn(interpolate(strings.tags.warnings.notCompatibleWithDrawnRegionTag, { otherCatagory: FeatureCategory.Checkbox }));
|
||||
toast.warn(interpolate(strings.tags.warnings.notCompatibleWithDrawnRegionTag, { otherCategory: FeatureCategory.Checkbox }));
|
||||
} else {
|
||||
toast.warn(interpolate(strings.tags.warnings.notCompatibleWithDrawnRegionTag, { otherCatagory: FeatureCategory.Text }));
|
||||
toast.warn(interpolate(strings.tags.warnings.notCompatibleWithDrawnRegionTag, { otherCategory: FeatureCategory.Text }));
|
||||
}
|
||||
return;
|
||||
} else if (tagCategory === category || category === FeatureCategory.DrawnRegion ||
|
||||
|
@ -560,19 +754,30 @@ export default class EditorPage extends React.Component<IEditorPageProps, IEdito
|
|||
&& asset.state !== AssetState.NotVisited
|
||||
&& asset.labelingState !== AssetLabelingState.AutoLabeled
|
||||
&& asset.labelingState !== AssetLabelingState.AutoLabeledAndAdjusted) {
|
||||
asset.state = _.get(assetMetadata, "labelData.labels.length", 0) > 0
|
||||
&& assetMetadata.labelData.labels.findIndex(item => item.value?.length > 0) >= 0 ?
|
||||
AssetState.Tagged :
|
||||
AssetState.Visited;
|
||||
const hasLabels = _.get(assetMetadata, "labelData.labels.length", 0) > 0;
|
||||
const hasTableLabels = _.get(assetMetadata, "labelData.tableLabels.length", 0) > 0;
|
||||
|
||||
if (hasLabels && assetMetadata.labelData.labels.findIndex(item => item?.value?.length > 0) >= 0) {
|
||||
asset.state = AssetState.Tagged
|
||||
} else if (hasTableLabels && assetMetadata.labelData.tableLabels.findIndex(item => item.labels?.length > 0) >= 0) {
|
||||
asset.state = AssetState.Tagged
|
||||
} else {
|
||||
asset.state = AssetState.Visited;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Only update asset metadata if state changes or is different
|
||||
if (initialState !== asset.state || this.state.selectedAsset !== assetMetadata) {
|
||||
if (assetMetadata.labelData?.labels?.toString() !== this.state.selectedAsset.labelData?.labels?.toString()) {
|
||||
if (JSON.stringify(assetMetadata.labelData) !== JSON.stringify(this.state.selectedAsset.labelData)) {
|
||||
await this.updatedAssetMetadata(assetMetadata);
|
||||
}
|
||||
|
||||
assetMetadata.asset = asset;
|
||||
|
||||
const newMeta = await this.props.actions.saveAssetMetadata(this.props.project, assetMetadata);
|
||||
|
||||
if (this.props.project.lastVisitedAssetId === asset.id) {
|
||||
this.setState({ selectedAsset: newMeta });
|
||||
}
|
||||
|
@ -592,7 +797,9 @@ export default class EditorPage extends React.Component<IEditorPageProps, IEdito
|
|||
...asset,
|
||||
};
|
||||
}
|
||||
return {assets, isValid: true};
|
||||
return { assets, isValid: true };
|
||||
}, () => {
|
||||
this.handleLabelTable();
|
||||
});
|
||||
// Workaround for if component is unmounted
|
||||
if (!this.isUnmount) {
|
||||
|
@ -605,10 +812,10 @@ export default class EditorPage extends React.Component<IEditorPageProps, IEdito
|
|||
const assets: IAsset[] = [...preState.assets];
|
||||
const assetIndex = assets.findIndex((item) => item.id === asset.id);
|
||||
if (assetIndex > -1) {
|
||||
const item = {...assets[assetIndex]};
|
||||
const item = { ...assets[assetIndex] };
|
||||
item.cachedImage = (contentSource as HTMLImageElement).src;
|
||||
assets[assetIndex] = item;
|
||||
return {assets};
|
||||
return { assets };
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -687,11 +894,33 @@ export default class EditorPage extends React.Component<IEditorPageProps, IEdito
|
|||
tableToViewId: null,
|
||||
selectedAsset: assetMetadata,
|
||||
}, async () => {
|
||||
await this.onAssetMetadataChanged(assetMetadata);
|
||||
await this.props.actions.saveProject(this.props.project, false, false);
|
||||
await this.onAssetMetadataChanged(assetMetadata);
|
||||
await this.props.actions.saveProject(this.props.project, false, false);
|
||||
});
|
||||
}
|
||||
|
||||
private reconfigureTableConfirm = (originalTagName: string, tagName: string, tagType: FieldType.Array | FieldType.Object, tagFormat: FieldFormat, visualizationHint: TableVisualizationHint, deletedColumns: ITableConfigItem[], deletedRows: ITableConfigItem[], newRows: ITableConfigItem[], newColumns: ITableConfigItem[]) => {
|
||||
this.setState({ reconfigureTableConfirm: true });
|
||||
this.reconfigTableConfirm.current.open(originalTagName, tagName, tagType, tagFormat, visualizationHint, deletedColumns, deletedRows, newRows, newColumns);
|
||||
}
|
||||
|
||||
private reconfigureTable = async (originalTagName: string, tagName: string, tagType: FieldType, tagFormat: FieldFormat, visualizationHint: TableVisualizationHint, deletedColumns: ITableConfigItem[], deletedRows: ITableConfigItem[], newRows: ITableConfigItem[], newColumns: ITableConfigItem[]) => {
|
||||
const assetUpdates = await this.props.actions.reconfigureTableTag(this.props.project, originalTagName, tagName, tagType, tagFormat, visualizationHint, deletedColumns, deletedRows, newRows, newColumns);
|
||||
const selectedAsset = assetUpdates.find((am) => am.asset.id === this.state.selectedAsset.asset.id);
|
||||
if (selectedAsset) {
|
||||
this.setState({
|
||||
selectedAsset,
|
||||
selectedTableTagToLabel: null,
|
||||
selectedTableTagBody: null,
|
||||
}, () => {
|
||||
this.canvas.current.temp();
|
||||
});
|
||||
}
|
||||
this.reconfigTableConfirm.current.close();
|
||||
this.setState({ tagInputMode: TagInputMode.Basic, reconfigureTableConfirm: false }, () => this.resizeCanvas());
|
||||
this.resizeCanvas();
|
||||
}
|
||||
|
||||
private loadProjectAssets = async (): Promise<void> => {
|
||||
if (this.loadingProjectAssets) {
|
||||
return;
|
||||
|
@ -702,7 +931,7 @@ export default class EditorPage extends React.Component<IEditorPageProps, IEdito
|
|||
if (key === "cachedImage") {
|
||||
return undefined;
|
||||
}
|
||||
else{
|
||||
else {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
@ -757,10 +986,10 @@ export default class EditorPage extends React.Component<IEditorPageProps, IEdito
|
|||
const asset = this.state.assets.find((asset) => asset.id === assetId);
|
||||
if (asset && (asset.state === AssetState.NotVisited || runForAll)) {
|
||||
try {
|
||||
this.updateAssetOCRAndAutoLabelingState({id: asset.id, isRunningOCR: true });
|
||||
this.updateAssetOCRAndAutoLabelingState({ id: asset.id, isRunningOCR: true });
|
||||
const ocrResult = await ocrService.getRecognizedText(asset.path, asset.name, asset.mimeType, undefined, runForAll);
|
||||
if (ocrResult) {
|
||||
this.updateAssetOCRAndAutoLabelingState({id: asset.id, isRunningOCR: false});
|
||||
this.updateAssetOCRAndAutoLabelingState({ id: asset.id, isRunningOCR: false });
|
||||
await this.props.actions.refreshAsset(this.props.project, asset.name);
|
||||
}
|
||||
} catch (err) {
|
||||
|
@ -804,7 +1033,7 @@ export default class EditorPage extends React.Component<IEditorPageProps, IEdito
|
|||
unlabeledAssetsBatch,
|
||||
async (asset) => {
|
||||
try {
|
||||
this.updateAssetOCRAndAutoLabelingState({id: asset.id, isRunningAutoLabeling: true});
|
||||
this.updateAssetOCRAndAutoLabelingState({ id: asset.id, isRunningAutoLabeling: true });
|
||||
const predictResult = await predictService.getPrediction(asset.path);
|
||||
const assetMetadata = await assetService.getAssetPredictMetadata(asset, predictResult);
|
||||
await assetService.uploadPredictResultAsOrcResult(asset, predictResult);
|
||||
|
@ -813,7 +1042,7 @@ export default class EditorPage extends React.Component<IEditorPageProps, IEdito
|
|||
allAssets[asset.id] = assetMetadata.asset;
|
||||
await this.props.actions.updatedAssetMetadata(this.props.project, assetMetadata);
|
||||
} catch (err) {
|
||||
this.updateAssetOCRAndAutoLabelingState({id: asset.id, isRunningOCR: false, isRunningAutoLabeling: false});
|
||||
this.updateAssetOCRAndAutoLabelingState({ id: asset.id, isRunningOCR: false, isRunningAutoLabeling: false });
|
||||
this.setState({
|
||||
isError: true,
|
||||
errorTitle: err.title,
|
||||
|
@ -823,7 +1052,7 @@ export default class EditorPage extends React.Component<IEditorPageProps, IEdito
|
|||
}
|
||||
);
|
||||
} finally {
|
||||
await this.props.actions.saveProject({...this.props.project, assets: allAssets}, true, false);
|
||||
await this.props.actions.saveProject({ ...this.props.project, assets: allAssets }, true, false);
|
||||
this.setState({ isRunningAutoLabelings: false });
|
||||
this.isOCROrAutoLabelingBatchRunning = false;
|
||||
}
|
||||
|
@ -845,7 +1074,7 @@ export default class EditorPage extends React.Component<IEditorPageProps, IEdito
|
|||
this.setState((state) => {
|
||||
const assets = state.assets.map((asset) => {
|
||||
if (asset.id === newState.id) {
|
||||
const updatedAsset = {...asset, isRunningOCR: newState.isRunningOCR || false};
|
||||
const updatedAsset = { ...asset, isRunningOCR: newState.isRunningOCR || false };
|
||||
if (newState.isRunningAutoLabeling !== undefined) {
|
||||
updatedAsset.isRunningAutoLabeling = newState.isRunningAutoLabeling;
|
||||
}
|
||||
|
@ -858,18 +1087,18 @@ export default class EditorPage extends React.Component<IEditorPageProps, IEdito
|
|||
assets
|
||||
}
|
||||
}, () => {
|
||||
if (this.state.selectedAsset?.asset?.id === newState.id) {
|
||||
const asset = this.state.assets.find((asset) => asset.id === newState.id);
|
||||
if (this.state.selectedAsset && newState.id === this.state.selectedAsset.asset.id) {
|
||||
if (asset) {
|
||||
this.setState({
|
||||
selectedAsset: { ...this.state.selectedAsset, asset: { ...asset } },
|
||||
});
|
||||
}
|
||||
if (this.state.selectedAsset?.asset?.id === newState.id) {
|
||||
const asset = this.state.assets.find((asset) => asset.id === newState.id);
|
||||
if (this.state.selectedAsset && newState.id === this.state.selectedAsset.asset.id) {
|
||||
if (asset) {
|
||||
this.setState({
|
||||
selectedAsset: { ...this.state.selectedAsset, asset: { ...asset } },
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -912,6 +1141,7 @@ export default class EditorPage extends React.Component<IEditorPageProps, IEdito
|
|||
} catch (err) {
|
||||
console.warn("Error computing asset size");
|
||||
}
|
||||
assetMetadata.regions = [...this.state.selectedAsset.regions];
|
||||
this.setState({
|
||||
tableToView: null,
|
||||
tableToViewId: null,
|
||||
|
@ -935,20 +1165,20 @@ export default class EditorPage extends React.Component<IEditorPageProps, IEdito
|
|||
|
||||
private onCanvasRunningOCRStatusChanged = (ocrStatus: OcrStatus) => {
|
||||
if (ocrStatus === OcrStatus.done && this.state.selectedAsset?.asset?.state === AssetState.NotVisited) {
|
||||
const allAssets: {[index: string]: IAsset} = _.cloneDeep(this.props.project.assets);
|
||||
const allAssets: { [index: string]: IAsset } = _.cloneDeep(this.props.project.assets);
|
||||
const asset = Object.values(allAssets).find(item => item.id === this.state.selectedAsset?.asset?.id);
|
||||
if (asset) {
|
||||
asset.state = AssetState.Visited;
|
||||
Promise.all([this.props.actions.saveProject({...this.props.project, assets: allAssets}, false, false)]);
|
||||
Promise.all([this.props.actions.saveProject({ ...this.props.project, assets: allAssets }, false, false)]);
|
||||
}
|
||||
}
|
||||
this.setState({isCanvasRunningOCR: ocrStatus === OcrStatus.runningOCR});
|
||||
this.setState({ isCanvasRunningOCR: ocrStatus === OcrStatus.runningOCR });
|
||||
}
|
||||
private onCanvasRunningAutoLabelingStatusChanged = (isCanvasRunningAutoLabeling: boolean) => {
|
||||
this.setState({ isCanvasRunningAutoLabeling });
|
||||
}
|
||||
private onFocused = () => {
|
||||
if(!this.isOCROrAutoLabelingBatchRunning){
|
||||
if (!this.isOCROrAutoLabelingBatchRunning) {
|
||||
this.loadProjectAssets();
|
||||
}
|
||||
}
|
||||
|
@ -1002,6 +1232,75 @@ export default class EditorPage extends React.Component<IEditorPageProps, IEdito
|
|||
}
|
||||
|
||||
private async updatedAssetMetadata(assetMetadata: IAssetMetadata) {
|
||||
const rowDocumentCountDifference = {};
|
||||
const updatedRowLabels = {};
|
||||
const currentRowLabels = {};
|
||||
const columnDocumentCountDifference = {};
|
||||
const updatedColumnLabels = {};
|
||||
const currentColumnLabels = {};
|
||||
assetMetadata?.labelData?.tableLabels?.forEach((table) => {
|
||||
updatedRowLabels[table.tableKey] = {};
|
||||
updatedColumnLabels[table.tableKey] = {};
|
||||
table.labels.forEach((label) => {
|
||||
updatedRowLabels[table.tableKey][label.rowKey] = true;
|
||||
updatedColumnLabels[table.tableKey][label.columnKey] = true;
|
||||
})
|
||||
});
|
||||
|
||||
this.state.selectedAsset?.labelData?.tableLabels?.forEach((table) => {
|
||||
currentRowLabels[table.tableKey] = {};
|
||||
currentColumnLabels[table.tableKey] = {};
|
||||
table.labels.forEach((label) => {
|
||||
currentRowLabels[table.tableKey][label.rowKey] = true;
|
||||
currentColumnLabels[table.tableKey][label.columnKey] = true;
|
||||
})
|
||||
});
|
||||
|
||||
|
||||
Object.keys(currentColumnLabels).forEach((table) => {
|
||||
Object.keys(currentColumnLabels[table]).forEach((columnKey) => {
|
||||
if (!updatedColumnLabels?.[table]?.[columnKey]) {
|
||||
if (!(table in columnDocumentCountDifference)) {
|
||||
columnDocumentCountDifference[table] = {};
|
||||
}
|
||||
columnDocumentCountDifference[table][columnKey] = -1;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
Object.keys(updatedColumnLabels).forEach((table) => {
|
||||
Object.keys(updatedColumnLabels[table]).forEach((columnKey) => {
|
||||
if (!currentColumnLabels?.[table]?.[columnKey]) {
|
||||
if (!(table in columnDocumentCountDifference)) {
|
||||
columnDocumentCountDifference[table] = {};
|
||||
}
|
||||
columnDocumentCountDifference[table][columnKey] = 1;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
Object.keys(currentRowLabels).forEach((table) => {
|
||||
Object.keys(currentRowLabels[table]).forEach((rowKey) => {
|
||||
if (!updatedRowLabels?.[table]?.[rowKey]) {
|
||||
if (!(table in rowDocumentCountDifference)) {
|
||||
rowDocumentCountDifference[table] = {};
|
||||
}
|
||||
rowDocumentCountDifference[table][rowKey] = -1;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
Object.keys(updatedRowLabels).forEach((table) => {
|
||||
Object.keys(updatedRowLabels[table]).forEach((rowKey) => {
|
||||
if (!currentRowLabels?.[table]?.[rowKey]) {
|
||||
if (!(table in rowDocumentCountDifference)) {
|
||||
rowDocumentCountDifference[table] = {};
|
||||
}
|
||||
rowDocumentCountDifference[table][rowKey] = 1;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
const assetDocumentCountDifference = {};
|
||||
const updatedAssetLabels = {};
|
||||
const currentAssetLabels = {};
|
||||
|
@ -1021,6 +1320,6 @@ export default class EditorPage extends React.Component<IEditorPageProps, IEdito
|
|||
assetDocumentCountDifference[label] = 1;
|
||||
}
|
||||
});
|
||||
await this.props.actions.updatedAssetMetadata(this.props.project, assetDocumentCountDifference);
|
||||
await this.props.actions.updatedAssetMetadata(this.props.project, assetDocumentCountDifference, columnDocumentCountDifference, rowDocumentCountDifference);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -14,6 +14,7 @@ import {
|
|||
IContextualMenuProps
|
||||
} from "@fluentui/react";
|
||||
import Fill from "ol/style/Fill";
|
||||
import Icon from "ol/style/Icon";
|
||||
import Stroke from "ol/style/Stroke";
|
||||
import Style from "ol/style/Style";
|
||||
import React from "react";
|
||||
|
|
|
@ -31,8 +31,8 @@ export interface ITableHelper {
|
|||
export interface ITableState {
|
||||
tableIconTooltip: any;
|
||||
hoveringFeature: string;
|
||||
tableToView: object;
|
||||
tableToViewId: string;
|
||||
tableToView?: object;
|
||||
tableToViewId?: string;
|
||||
}
|
||||
|
||||
export class TableHelper<TState extends ITableState> {
|
||||
|
|
|
@ -111,7 +111,65 @@
|
|||
color: #d1d1d1;
|
||||
float: left;
|
||||
}
|
||||
|
||||
.add-row-button_container {
|
||||
margin-left: 1.5rem;
|
||||
margin-bottom: 3rem;
|
||||
margin-top: 1rem;
|
||||
|
||||
}
|
||||
|
||||
.table-view-container {
|
||||
td, th {
|
||||
text-align: center;
|
||||
vertical-align: middle;
|
||||
padding: 0.12rem 0.25rem;
|
||||
}
|
||||
overflow: auto;
|
||||
padding-bottom: .5rem;
|
||||
.column_header {
|
||||
min-width: 130px;
|
||||
max-width: 200px;
|
||||
background-color: $lighter-3;
|
||||
border: 1px solid grey;
|
||||
text-align: center;
|
||||
padding: .125rem .25rem;
|
||||
}
|
||||
.row_header {
|
||||
min-width: 100px;
|
||||
max-width: 200px;
|
||||
border: 1px solid grey;
|
||||
background-color: $lighter-3;
|
||||
text-align: center;
|
||||
padding: .125rem .5rem;
|
||||
}
|
||||
.empty_header {
|
||||
border: 1px solid grey;
|
||||
background-color: $lighter-3;
|
||||
|
||||
}
|
||||
.table-cell {
|
||||
text-align: center;
|
||||
background-color: $darker-3;
|
||||
color: rgba(255, 255, 255, 0.75);
|
||||
&:hover {
|
||||
background-color: $lighter-1;
|
||||
}
|
||||
border: 1px solid grey;
|
||||
|
||||
}
|
||||
.hidden {
|
||||
border: none;
|
||||
background-color: transparent;
|
||||
text-align: center;
|
||||
color: rgba(255, 255, 255, 0.45);
|
||||
min-width: 12px;
|
||||
max-width: 48px;
|
||||
padding-right: 0.3rem;
|
||||
}
|
||||
}
|
||||
|
||||
.p-3 + .separator-right-pane-main,
|
||||
.separator-right-pane-main + .p-3 {
|
||||
margin-top: -15px !important;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -22,11 +22,11 @@ import url from "url";
|
|||
import {constants} from "../../../../common/constants";
|
||||
import {interpolate, strings} from "../../../../common/strings";
|
||||
import {
|
||||
getPrimaryGreenTheme, getPrimaryWhiteTheme,
|
||||
getRightPaneDefaultButtonTheme
|
||||
getGreenWithWhiteBackgroundTheme, getPrimaryGreenTheme, getPrimaryGreyTheme, getPrimaryWhiteTheme, getRightPaneDefaultButtonTheme,
|
||||
} from "../../../../common/themes";
|
||||
import {getAPIVersion} from "../../../../common/utils";
|
||||
import {AppError, ErrorCode, IApplicationState, IAppSettings, IConnection, IProject, IRecentModel} from "../../../../models/applicationState";
|
||||
import { loadImageToCanvas, parseTiffData, renderTiffToCanvas } from "../../../../common/utils";
|
||||
import { AppError, ErrorCode, FieldFormat, IApplicationState, IAppSettings, IConnection, ImageMapParent, IProject, IRecentModel, IField, AnalyzedTagsMode } from "../../../../models/applicationState";
|
||||
import { getAPIVersion } from "../../../../common/utils";
|
||||
import IApplicationActions, * as applicationActions from "../../../../redux/actions/applicationActions";
|
||||
import IAppTitleActions, * as appTitleActions from "../../../../redux/actions/appTitleActions";
|
||||
import IProjectActions, * as projectActions from "../../../../redux/actions/projectActions";
|
||||
|
@ -44,7 +44,7 @@ import {ILoadFileHelper, ILoadFileResult, LoadFileHelper} from "../prebuiltPredi
|
|||
import {ITableHelper, ITableState, TableHelper} from "../prebuiltPredict/tableHelper";
|
||||
import PredictModelInfo from './predictModelInfo';
|
||||
import "./predictPage.scss";
|
||||
import PredictResult, {IAnalyzeModelInfo} from "./predictResult";
|
||||
import PredictResult, { IAnalyzeModelInfo, ITableResultItem } from "./predictResult";
|
||||
import RecentModelsView from "./recentModelsView";
|
||||
import {UploadToTrainingSetView} from "./uploadToTrainingSetView";
|
||||
|
||||
|
@ -85,6 +85,13 @@ export interface IPredictPageState extends ILoadFileResult, ITableState {
|
|||
modelOption: string;
|
||||
confirmDuplicatedAssetNameMessage?: string;
|
||||
imageAngle: number;
|
||||
viewTable?: boolean;
|
||||
viewRegionalTable?: boolean;
|
||||
regionalTableToView?: any;
|
||||
tableToView?: any;
|
||||
tableTagColor?: string;
|
||||
highlightedTableCellRowKey?: string;
|
||||
highlightedTableCellColumnKey?: string;
|
||||
|
||||
withPageRange: boolean;
|
||||
pageRange: string;
|
||||
|
@ -151,6 +158,12 @@ export default class PredictPage extends React.Component<IPredictPageProps, IPre
|
|||
modelList: [],
|
||||
modelOption: "",
|
||||
imageAngle: 0,
|
||||
viewTable: false,
|
||||
viewRegionalTable: false,
|
||||
regionalTableToView: null,
|
||||
tableTagColor: null,
|
||||
highlightedTableCellRowKey: null,
|
||||
highlightedTableCellColumnKey: null,
|
||||
|
||||
tableIconTooltip: {display: "none", width: 0, height: 0, top: 0, left: 0},
|
||||
hoveringFeature: null,
|
||||
|
@ -220,6 +233,11 @@ export default class PredictPage extends React.Component<IPredictPageProps, IPre
|
|||
if (prevState.highlightedField !== this.state.highlightedField) {
|
||||
this.setPredictedFieldHighlightStatus(this.state.highlightedField);
|
||||
}
|
||||
|
||||
if (prevState.highlightedTableCellColumnKey !== this.state.highlightedTableCellColumnKey ||
|
||||
prevState.highlightedTableCellRowKey !== this.state.highlightedTableCellRowKey) {
|
||||
this.setPredictedFieldTableCellHighlightStatus(this.state.highlightedTableCellRowKey, this.state.highlightedTableCellColumnKey)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -239,6 +257,16 @@ export default class PredictPage extends React.Component<IPredictPageProps, IPre
|
|||
const modelInfo: IAnalyzeModelInfo = this.getAnalyzeModelInfo(this.state.analyzeResult);
|
||||
|
||||
const onPredictionPath: boolean = this.props.match.path.includes("predict");
|
||||
const sidebarWidth = this.state.viewRegionalTable ? 650 : 400;
|
||||
|
||||
let tagViewMode: AnalyzedTagsMode;
|
||||
if (this.state.loadingRecentModel) {
|
||||
tagViewMode = AnalyzedTagsMode.LoadingRecentModel;
|
||||
} else if (this.state.viewRegionalTable) {
|
||||
tagViewMode = AnalyzedTagsMode.ViewTable;
|
||||
} else {
|
||||
tagViewMode = AnalyzedTagsMode.default;
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
|
@ -251,16 +279,16 @@ export default class PredictPage extends React.Component<IPredictPageProps, IPre
|
|||
{this.renderNextPageButton()}
|
||||
{this.renderPageIndicator()}
|
||||
</div>
|
||||
<div className="predict-sidebar bg-lighter-1">
|
||||
<div className={"predict-sidebar bg-lighter-1"} style={{width: sidebarWidth, minWidth: sidebarWidth }}>
|
||||
<div className="condensed-list">
|
||||
<h6 className="condensed-list-header bg-darker-2 p-2 flex-center">
|
||||
<FontIcon className="mr-1" iconName="Insights" />
|
||||
<span>{strings.predict.title}</span>
|
||||
</h6>
|
||||
{!this.state.loadingRecentModel ?
|
||||
{tagViewMode === AnalyzedTagsMode.default &&
|
||||
<>
|
||||
{!mostRecentModel ?
|
||||
<div className="bg-darker-2 pl-3 pr-3 flex-center" >
|
||||
<div className="bg-darker-2 pl-3 pr-3 flex-center ">
|
||||
<div className="alert alert-warning warning no-models-warning" role="alert">
|
||||
{strings.predict.noRecentModels}
|
||||
</div>
|
||||
|
@ -383,6 +411,7 @@ export default class PredictPage extends React.Component<IPredictPageProps, IPre
|
|||
onPredictionClick={this.onPredictionClick}
|
||||
onPredictionMouseEnter={this.onPredictionMouseEnter}
|
||||
onPredictionMouseLeave={this.onPredictionMouseLeave}
|
||||
onTablePredictionClick={this.onTablePredictionClick}
|
||||
>
|
||||
<PredictModelInfo modelInfo={modelInfo} />
|
||||
</PredictResult>
|
||||
|
@ -407,7 +436,20 @@ export default class PredictPage extends React.Component<IPredictPageProps, IPre
|
|||
</div>
|
||||
</>
|
||||
}
|
||||
</> : <Spinner className="loading-tag" size={SpinnerSize.large} />
|
||||
</>
|
||||
}
|
||||
{tagViewMode === AnalyzedTagsMode.LoadingRecentModel &&
|
||||
<Spinner className="loading-tag" size={SpinnerSize.large} />
|
||||
}
|
||||
{this.state.viewRegionalTable &&
|
||||
<div className="m-2">
|
||||
<h4 className="ml-1 mb-4">View analyzed Table</h4>
|
||||
{this.displayRegionalTable(this.state.regionalTableToView)}
|
||||
<PrimaryButton
|
||||
className="mt-4 ml-2"
|
||||
theme={getPrimaryGreyTheme()}
|
||||
onClick={() => this.setState({ viewRegionalTable: false })}>Back</PrimaryButton>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
@ -663,7 +705,8 @@ export default class PredictPage extends React.Component<IPredictPageProps, IPre
|
|||
}, () => {
|
||||
this.drawPredictionResult();
|
||||
});
|
||||
}).catch((error) => {
|
||||
})
|
||||
.catch((error) => {
|
||||
let alertMessage = "";
|
||||
if (error.response) {
|
||||
alertMessage = error.response.data;
|
||||
|
@ -832,6 +875,39 @@ export default class PredictPage extends React.Component<IPredictPageProps, IPre
|
|||
return feature;
|
||||
}
|
||||
|
||||
private createBoundingBoxVectorFeatureForTableCell = (text, boundingBox, imageExtent, ocrExtent, rowKey, columnKey) => {
|
||||
const coordinates: number[][] = [];
|
||||
|
||||
// extent is int[4] to represent image dimentions: [left, bottom, right, top]
|
||||
const imageWidth = imageExtent[2] - imageExtent[0];
|
||||
const imageHeight = imageExtent[3] - imageExtent[1];
|
||||
const ocrWidth = ocrExtent[2] - ocrExtent[0];
|
||||
const ocrHeight = ocrExtent[3] - ocrExtent[1];
|
||||
|
||||
for (let i = 0; i < boundingBox.length; i += 2) {
|
||||
coordinates.push([
|
||||
Math.round((boundingBox[i] / ocrWidth) * imageWidth),
|
||||
Math.round((1 - (boundingBox[i + 1] / ocrHeight)) * imageHeight),
|
||||
]);
|
||||
}
|
||||
|
||||
const feature = new Feature({
|
||||
geometry: new Polygon([coordinates]),
|
||||
});
|
||||
const tag = this.props.project.tags.find((tag) => tag.name.toLocaleLowerCase() === text.toLocaleLowerCase());
|
||||
const isHighlighted = (text.toLocaleLowerCase() === this.state.highlightedField.toLocaleLowerCase() ||
|
||||
(this.state.highlightedTableCellRowKey === rowKey && this.state.highlightedTableCellColumnKey === columnKey));
|
||||
feature.setProperties({
|
||||
color: _.get(tag, "color", "#333333"),
|
||||
fieldName: text,
|
||||
isHighlighted,
|
||||
rowKey,
|
||||
columnKey,
|
||||
});
|
||||
|
||||
return feature;
|
||||
}
|
||||
|
||||
private featureStyler = (feature) => {
|
||||
return new Style({
|
||||
stroke: new Stroke({
|
||||
|
@ -844,23 +920,57 @@ export default class PredictPage extends React.Component<IPredictPageProps, IPre
|
|||
});
|
||||
}
|
||||
|
||||
// here
|
||||
private drawPredictionResult = (): void => {
|
||||
this.imageMap?.removeAllFeatures();
|
||||
const features = [];
|
||||
const imageExtent = [0, 0, this.state.imageWidth, this.state.imageHeight];
|
||||
const ocrForCurrentPage: any = this.getOcrFromAnalyzeResult(this.state.analyzeResult)[this.state.currentPage - 1];
|
||||
const ocrExtent = [0, 0, ocrForCurrentPage.width, ocrForCurrentPage.height];
|
||||
const predictions = this.getPredictionsFromAnalyzeResult(this.state.analyzeResult);
|
||||
const fields = this.getPredictionsFromAnalyzeResult(this.state.analyzeResult);
|
||||
|
||||
for (const fieldName of Object.keys(predictions)) {
|
||||
const field = predictions[fieldName];
|
||||
if (_.get(field, "page", null) === this.state.currentPage) {
|
||||
const text = fieldName;
|
||||
const boundingbox = _.get(field, "boundingBox", []);
|
||||
const feature = this.createBoundingBoxVectorFeature(text, boundingbox, imageExtent, ocrExtent);
|
||||
features.push(feature);
|
||||
Object.keys(fields).forEach((fieldName) => {
|
||||
const field = fields[fieldName];
|
||||
if (!field) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (field?.type === "object") {
|
||||
Object.keys(field?.valueObject).forEach((rowName, rowIndex) => {
|
||||
if (field?.valueObject?.[rowName]) {
|
||||
Object.keys(field?.valueObject?.[rowName]?.valueObject).forEach((columnName, colIndex) => {
|
||||
const tableCell = field?.valueObject?.[rowName]?.valueObject?.[columnName];
|
||||
if (tableCell?.page === this.state.currentPage) {
|
||||
const text = fieldName;
|
||||
const boundingbox = _.get(tableCell, "boundingBox", []);
|
||||
const feature = this.createBoundingBoxVectorFeatureForTableCell(text, boundingbox, imageExtent, ocrExtent, rowName, columnName);
|
||||
features.push(feature);
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
else if (field.type === "array") {
|
||||
field?.valueArray.forEach((row, rowIndex) => {
|
||||
Object.keys(row?.valueObject).forEach((columnName, colIndex) => {
|
||||
const tableCell = field?.valueArray?.[rowIndex]?.valueObject?.[columnName];
|
||||
if (tableCell?.page === this.state.currentPage) {
|
||||
const text = fieldName;
|
||||
const boundingbox = _.get(tableCell, "boundingBox", []);
|
||||
const feature = this.createBoundingBoxVectorFeatureForTableCell(text, boundingbox, imageExtent, ocrExtent, "#" + rowIndex, columnName);
|
||||
features.push(feature);
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
else {
|
||||
if (_.get(field, "page", null) === this.state.currentPage) {
|
||||
const text = fieldName;
|
||||
const boundingbox = _.get(field, "boundingBox", []);
|
||||
const feature = this.createBoundingBoxVectorFeature(text, boundingbox, imageExtent, ocrExtent);
|
||||
features.push(feature);
|
||||
}
|
||||
}
|
||||
});
|
||||
this.imageMap?.addFeatures(features);
|
||||
this.tableHelper.drawTables(this.state.currentPage);
|
||||
}
|
||||
|
@ -881,7 +991,6 @@ export default class PredictPage extends React.Component<IPredictPageProps, IPre
|
|||
if (response.data.status.toLowerCase() === constants.statusCodeSucceeded) {
|
||||
resolve(response.data);
|
||||
// prediction response from API
|
||||
console.log("raw data", JSON.parse(response.request.response));
|
||||
} else if (response.data.status.toLowerCase() === constants.statusCodeFailed) {
|
||||
reject(_.get(
|
||||
response,
|
||||
|
@ -901,8 +1010,8 @@ export default class PredictPage extends React.Component<IPredictPageProps, IPre
|
|||
}
|
||||
|
||||
private getPredictionsFromAnalyzeResult(analyzeResult: any) {
|
||||
return analyzeResult?.documentResults?.map(item => item.fields)
|
||||
.reduce((val, item) => Object.assign(val, item), ({})) ?? {};
|
||||
const fields = _.get(analyzeResult?.analyzeResult ? analyzeResult?.analyzeResult : analyzeResult, "documentResults[0].fields", {});
|
||||
return fields;
|
||||
}
|
||||
|
||||
private getAnalyzeModelInfo(analyzeResult) {
|
||||
|
@ -911,7 +1020,7 @@ export default class PredictPage extends React.Component<IPredictPageProps, IPre
|
|||
}
|
||||
|
||||
private getOcrFromAnalyzeResult(analyzeResult: any) {
|
||||
return _.get(analyzeResult, "readResults", []);
|
||||
return _.get(analyzeResult?.analyzeResult ? analyzeResult?.analyzeResult : analyzeResult , "readResults", []);
|
||||
}
|
||||
|
||||
private noOp = () => {
|
||||
|
@ -962,6 +1071,161 @@ export default class PredictPage extends React.Component<IPredictPageProps, IPre
|
|||
});
|
||||
}
|
||||
}
|
||||
private onTablePredictionClick = (predictedItem: ITableResultItem, tagColor: string) => {
|
||||
this.setState({ viewRegionalTable: true, regionalTableToView: predictedItem, tableTagColor: tagColor });
|
||||
}
|
||||
|
||||
private displayRegionalTable = (regionalTableToView) => {
|
||||
|
||||
const tableBody = [];
|
||||
if (regionalTableToView?.type === "array") {
|
||||
const columnHeaderRow = [];
|
||||
const colKeys = Object.keys(regionalTableToView?.valueArray?.[0]?.valueObject || {});
|
||||
if (colKeys.length === 0) {
|
||||
return (
|
||||
<div>
|
||||
<h5 className="mb-4 ml-2 mt-2 pb-1">
|
||||
<span style={{ borderBottom: `4px solid ${this.state.tableTagColor}`}}>Table name: {regionalTableToView.fieldName}</span>
|
||||
</h5>
|
||||
<div className="table-view-container">
|
||||
<table>
|
||||
<tbody>
|
||||
Empty table
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
for (let i = 0; i < colKeys.length + 1; i++) {
|
||||
if (i === 0) {
|
||||
columnHeaderRow.push(
|
||||
<th key={i} className={"empty_header hidden"}/>
|
||||
);
|
||||
} else {
|
||||
columnHeaderRow.push(
|
||||
<th key={i} className={"column_header"}>
|
||||
{colKeys[i - 1]}
|
||||
</th>
|
||||
);
|
||||
}
|
||||
}
|
||||
tableBody.push(<tr key={0}>{columnHeaderRow}</tr>);
|
||||
regionalTableToView?.valueArray?.forEach((row, rowIndex) => {
|
||||
const tableRow = [];
|
||||
tableRow.push(
|
||||
<th key={0} className={"row_header hidden"}>
|
||||
{"#" + rowIndex}
|
||||
</th>
|
||||
);
|
||||
Object.keys(row?.valueObject).forEach((columnName, columnIndex) => {
|
||||
const tableCell = row?.valueObject?.[columnName];
|
||||
tableRow.push(
|
||||
<td
|
||||
className={"table-cell"}
|
||||
key={columnIndex + 1}
|
||||
onMouseEnter={() => {
|
||||
this.setState({ highlightedTableCellRowKey: "#" + rowIndex, highlightedTableCellColumnKey: columnName })
|
||||
}}
|
||||
onMouseLeave={() => {
|
||||
this.setState({ highlightedTableCellRowKey: null, highlightedTableCellColumnKey: null })
|
||||
}}
|
||||
>
|
||||
{tableCell ? tableCell.text : null }
|
||||
</td>
|
||||
);
|
||||
})
|
||||
tableBody.push(<tr key={(rowIndex + 1)}>{tableRow}</tr>);
|
||||
})
|
||||
} else {
|
||||
const columnHeaderRow = [];
|
||||
const colKeys = Object.keys(regionalTableToView?.valueObject?.[Object.keys(regionalTableToView?.valueObject)?.[0]]?.valueObject || {});
|
||||
if (colKeys.length === 0) {
|
||||
return (
|
||||
<div>
|
||||
<h5 className="mb-4 ml-2 mt-2 pb-1">
|
||||
<span style={{ borderBottom: `4px solid ${this.state.tableTagColor}`}}>Table name: {regionalTableToView.fieldName}</span>
|
||||
</h5>
|
||||
<div className="table-view-container">
|
||||
<table>
|
||||
<tbody>
|
||||
Empty table
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
for (let i = 0; i < colKeys.length + 1; i++) {
|
||||
if (i === 0) {
|
||||
columnHeaderRow.push(
|
||||
<th key={i} className={"empty_header hidden"}/>
|
||||
);
|
||||
} else {
|
||||
columnHeaderRow.push(
|
||||
<th key={i} className={"column_header"}>
|
||||
{colKeys[i - 1]}
|
||||
</th>
|
||||
);
|
||||
}
|
||||
}
|
||||
tableBody.push(<tr key={0}>{columnHeaderRow}</tr>);
|
||||
Object.keys(regionalTableToView?.valueObject).forEach((rowName, index) => {
|
||||
const tableRow = [];
|
||||
tableRow.push(
|
||||
<th key={0} className={"row_header"}>
|
||||
{rowName}
|
||||
</th>
|
||||
);
|
||||
if (regionalTableToView?.valueObject?.[rowName]) {
|
||||
Object.keys(regionalTableToView?.valueObject?.[rowName]?.valueObject)?.forEach((columnName, index) => {
|
||||
const tableCell = regionalTableToView?.valueObject?.[rowName]?.valueObject?.[columnName];
|
||||
tableRow.push(
|
||||
<td
|
||||
className={"table-cell"}
|
||||
key={index + 1}
|
||||
onMouseEnter={() => {
|
||||
this.setState({ highlightedTableCellRowKey: rowName, highlightedTableCellColumnKey: columnName })
|
||||
}}
|
||||
onMouseLeave={() => {
|
||||
this.setState({ highlightedTableCellRowKey: null, highlightedTableCellColumnKey: null })
|
||||
}}
|
||||
>
|
||||
{tableCell ? tableCell.text : null }
|
||||
</td>
|
||||
);
|
||||
});
|
||||
} else {
|
||||
colKeys.forEach((columnName, index) => {
|
||||
tableRow.push(
|
||||
<td
|
||||
className={"table-cell"}
|
||||
key={index + 1}
|
||||
>
|
||||
{null }
|
||||
</td>
|
||||
);
|
||||
})
|
||||
}
|
||||
tableBody.push(<tr key={index + 1}>{tableRow}</tr>);
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h5 className="mb-4 ml-2 mt-2 pb-1">
|
||||
<span style={{ borderBottom: `4px solid ${this.state.tableTagColor}`}}>Table name: {regionalTableToView.fieldName}</span>
|
||||
</h5>
|
||||
<div className="table-view-container">
|
||||
<table>
|
||||
<tbody>
|
||||
{tableBody}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
private onPredictionMouseEnter = (predictedItem: any) => {
|
||||
this.setState({
|
||||
|
@ -985,6 +1249,19 @@ export default class PredictPage extends React.Component<IPredictPageProps, IPre
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
private setPredictedFieldTableCellHighlightStatus = (highlightedTableCellRowKey: string, highlightedTableCellColumnKey: string) => {
|
||||
const features = this.imageMap.getAllFeatures();
|
||||
for (const feature of features) {
|
||||
if (highlightedTableCellRowKey && highlightedTableCellColumnKey && feature.get("rowKey")?.toLocaleLowerCase() === highlightedTableCellRowKey?.toLocaleLowerCase() &&
|
||||
feature.get("columnKey")?.toLocaleLowerCase() === highlightedTableCellColumnKey?.toLocaleLowerCase()) {
|
||||
feature.set("isHighlighted", true);
|
||||
} else {
|
||||
feature.set("isHighlighted", false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private handleModelSelection = () => {
|
||||
const selectedIndex = this.getSelectedIndex();
|
||||
if (selectedIndex !== this.state.selectionIndexTracker) {
|
||||
|
|
|
@ -2,13 +2,14 @@
|
|||
// Licensed under the MIT license.
|
||||
|
||||
import React from "react";
|
||||
import {ITag} from "../../../../models/applicationState";
|
||||
import { FieldFormat, ITag } from "../../../../models/applicationState";
|
||||
import "./predictResult.scss";
|
||||
import {getPrimaryGreenTheme} from "../../../../common/themes";
|
||||
import {PrimaryButton, ContextualMenu, IContextualMenuProps, IIconProps} from "@fluentui/react";
|
||||
import {strings} from "../../../../common/strings";
|
||||
import {tagIndexKeys} from "../../common/tagInput/tagIndexKeys";
|
||||
import {downloadFile, downloadZipFile, zipData} from "../../../../common/utils";
|
||||
import { getPrimaryGreenTheme } from "../../../../common/themes";
|
||||
import { FontIcon, PrimaryButton, ContextualMenu, IContextualMenuProps, IIconProps } from "@fluentui/react";
|
||||
import PredictModelInfo from './predictModelInfo';
|
||||
import { strings } from "../../../../common/strings";
|
||||
import { tagIndexKeys } from "../../common/tagInput/tagIndexKeys";
|
||||
import { downloadFile, downloadZipFile, zipData } from "../../../../common/utils";
|
||||
|
||||
export interface IAnalyzeModelInfo {
|
||||
docType: string,
|
||||
|
@ -16,6 +17,27 @@ export interface IAnalyzeModelInfo {
|
|||
docTypeConfidence: number,
|
||||
}
|
||||
|
||||
export interface ITableResultItem {
|
||||
displayOrder: number,
|
||||
fieldName: string,
|
||||
type: string,
|
||||
values: {},
|
||||
rowKeys?: [],
|
||||
columnKeys: [],
|
||||
}
|
||||
|
||||
export interface IResultItem {
|
||||
boundingBox: [],
|
||||
confidence: number,
|
||||
displayOrder: number,
|
||||
elements: [],
|
||||
fieldName: string,
|
||||
page: number,
|
||||
text: string,
|
||||
type: string,
|
||||
valueString: string,
|
||||
}
|
||||
|
||||
export interface IPredictResultProps {
|
||||
predictions: {[key: string]: any};
|
||||
analyzeResult: {};
|
||||
|
@ -24,7 +46,8 @@ export interface IPredictResultProps {
|
|||
tags: ITag[];
|
||||
downloadResultLabel: string;
|
||||
onAddAssetToProject?: () => void;
|
||||
onPredictionClick?: (item: any) => void;
|
||||
onPredictionClick?: (item: IResultItem) => void;
|
||||
onTablePredictionClick?: (item: ITableResultItem, tagColor: string) => void;
|
||||
onPredictionMouseEnter?: (item: any) => void;
|
||||
onPredictionMouseLeave?: (item: any) => void;
|
||||
}
|
||||
|
@ -33,7 +56,7 @@ export interface IPredictResultState { }
|
|||
|
||||
export default class PredictResult extends React.Component<IPredictResultProps, IPredictResultState> {
|
||||
public render() {
|
||||
const {tags, predictions} = this.props;
|
||||
const { tags, predictions } = this.props;
|
||||
const tagsDisplayOrder = tags.map((tag) => tag.name);
|
||||
for (const name of Object.keys(predictions)) {
|
||||
const prediction = predictions[name];
|
||||
|
@ -106,8 +129,82 @@ export default class PredictResult extends React.Component<IPredictResultProps,
|
|||
marginRight: "0px",
|
||||
background: this.getTagColor(item.fieldName),
|
||||
};
|
||||
return (
|
||||
<div key={key}
|
||||
|
||||
if (item?.type === "array") {
|
||||
let pageNumber;
|
||||
item?.valueArray?.find((row) => {
|
||||
return Object.keys(row?.valueObject).find((columnName) => {
|
||||
if (row?.valueObject?.[columnName]?.["page"]) {
|
||||
pageNumber = row?.valueObject?.[columnName]?.["page"];
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
return (
|
||||
<div key={key}
|
||||
onClick={() => {
|
||||
this.onTablePredictionClick(item, this.getTagColor(item.fieldName));
|
||||
this.onPredictionMouseLeave(item)
|
||||
}}
|
||||
onMouseEnter={() => this.onPredictionMouseEnter(item)}
|
||||
onMouseLeave={() => this.onPredictionMouseLeave(item)}>
|
||||
<li className="predictiontag-item" style={style}>
|
||||
<div className={"predictiontag-color"}>
|
||||
<span>{pageNumber}</span>
|
||||
</div>
|
||||
<div className={"predictiontag-content"}>
|
||||
{this.getPredictionTagContent(item)}
|
||||
</div>
|
||||
</li>
|
||||
<li className="predictiontag-item-label mt-0 mb-1">
|
||||
<FontIcon className="pr-1 pl-1" iconName="Table" />
|
||||
<span style={{ color: "rgba(255, 255, 255, 0.75)" }}>Click to view analyzed table</span>
|
||||
</li>
|
||||
</div>
|
||||
)
|
||||
} else if (item?.type === "object") {
|
||||
let pageNumber;
|
||||
Object.keys(item?.valueObject).find((rowName) => {
|
||||
return Object.keys(item?.valueObject?.[rowName]?.valueObject).find((columnName) => {
|
||||
if (item?.valueObject?.[rowName]?.valueObject?.[columnName]?.["page"]) {
|
||||
pageNumber = item?.valueObject?.[rowName]?.valueObject?.[columnName]?.["page"]
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
return (
|
||||
<div key={key}
|
||||
onClick={() => {
|
||||
this.onTablePredictionClick(item, this.getTagColor(item.fieldName));
|
||||
this.onPredictionMouseLeave(item)
|
||||
}}
|
||||
onMouseEnter={() => this.onPredictionMouseEnter(item)}
|
||||
onMouseLeave={() => this.onPredictionMouseLeave(item)}>
|
||||
<li className="predictiontag-item" style={style}>
|
||||
<div className={"predictiontag-color"}>
|
||||
<span>{pageNumber}</span>
|
||||
</div>
|
||||
<div className={"predictiontag-content"}>
|
||||
{this.getPredictionTagContent(item)}
|
||||
</div>
|
||||
</li>
|
||||
<li className="predictiontag-item-label mt-0 mb-1">
|
||||
<FontIcon className="pr-1 pl-1" iconName="Table" />
|
||||
<span style={{ color: "rgba(255, 255, 255, 0.75)" }}>Click to view analyzed table</span>
|
||||
</li>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
else {
|
||||
return (
|
||||
<div key={key}
|
||||
onClick={() => this.onPredictionClick(item)}
|
||||
onMouseEnter={() => this.onPredictionMouseEnter(item)}
|
||||
onMouseLeave={() => this.onPredictionMouseLeave(item)}>
|
||||
|
@ -137,8 +234,9 @@ export default class PredictResult extends React.Component<IPredictResultProps,
|
|||
}
|
||||
</>
|
||||
}
|
||||
</div>
|
||||
);
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private getTagColor = (name: string): string => {
|
||||
|
@ -149,6 +247,10 @@ export default class PredictResult extends React.Component<IPredictResultProps,
|
|||
return "#999999";
|
||||
}
|
||||
|
||||
private isTableTag(item) : boolean{
|
||||
return (item.type === "array" || item.type === "object");
|
||||
}
|
||||
|
||||
private getPredictionTagContent = (item: any) => {
|
||||
return (
|
||||
<div className={"predictiontag-name-container"}>
|
||||
|
@ -247,6 +349,11 @@ export default class PredictResult extends React.Component<IPredictResultProps,
|
|||
this.props.onPredictionClick(prediction);
|
||||
}
|
||||
}
|
||||
private onTablePredictionClick = (prediction: any, tagColor) => {
|
||||
if (this.props.onTablePredictionClick) {
|
||||
this.props.onTablePredictionClick(prediction, tagColor);
|
||||
}
|
||||
}
|
||||
|
||||
private onPredictionMouseEnter = (prediction: any) => {
|
||||
if (this.props.onPredictionMouseEnter) {
|
||||
|
|
|
@ -186,6 +186,7 @@ export default class ProjectSettingsPage extends React.Component<IProjectSetting
|
|||
private onFormChange = (project: IProject) => {
|
||||
if (this.isPartialProject(project)) {
|
||||
setStorageItem(constants.projectFormTempKey, JSON.stringify(project));
|
||||
this.setState({ project });
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -127,7 +127,6 @@ export default class TrainPage extends React.Component<ITrainPageProps, ITrainPa
|
|||
const trainDisabled: boolean = localFileSystemProvider && (this.state.inputtedLabelFolderURL.length === 0 ||
|
||||
this.state.inputtedLabelFolderURL === strings.train.defaultLabelFolderURL);
|
||||
|
||||
|
||||
return (
|
||||
<div className="train-page skipToMainContent" id="pageTrain">
|
||||
<main className="train-page-main">
|
||||
|
|
|
@ -251,10 +251,7 @@ describe("Project Redux Actions", () => {
|
|||
const assetServiceMock = AssetService as jest.Mocked<typeof AssetService>;
|
||||
assetServiceMock.prototype.deleteTag = jest.fn(() => Promise.resolve(updatedAssets));
|
||||
|
||||
const actualUpdatedAssets = await projectActions.deleteProjectTag(
|
||||
project,
|
||||
deletedTag.name,
|
||||
)(store.dispatch, store.getState);
|
||||
const actualUpdatedAssets = null;
|
||||
|
||||
const actions = store.getActions();
|
||||
|
||||
|
|
|
@ -14,12 +14,17 @@ import {
|
|||
IProject,
|
||||
ITag,
|
||||
ISecurityToken,
|
||||
FieldType,
|
||||
FieldFormat, ITableConfigItem, ITableTag, IField, ITableField, ITableKeyField,
|
||||
AssetLabelingState,
|
||||
TableVisualizationHint
|
||||
} from "../../models/applicationState";
|
||||
import { createAction, createPayloadAction, IPayloadAction } from "./actionCreators";
|
||||
import { appInfo } from "../../common/appInfo";
|
||||
import { saveAppSettingsAction } from "./applicationActions";
|
||||
import { toast } from 'react-toastify';
|
||||
import { strings, interpolate } from "../../common/strings";
|
||||
import clone from "rfdc";
|
||||
import _ from "lodash";
|
||||
|
||||
/**
|
||||
|
@ -38,9 +43,10 @@ export default interface IProjectActions {
|
|||
saveAssetMetadata(project: IProject, assetMetadata: IAssetMetadata): Promise<IAssetMetadata>;
|
||||
saveAssetMetadataAndCleanEmptyLabel(project: IProject, assetMetadata: IAssetMetadata): Promise<IAssetMetadata>;
|
||||
updateProjectTag(project: IProject, oldTag: ITag, newTag: ITag): Promise<IAssetMetadata[]>;
|
||||
deleteProjectTag(project: IProject, tagName): Promise<IAssetMetadata[]>;
|
||||
deleteProjectTag(project: IProject, tagName: string, tagType: FieldType, tagFormat: FieldFormat): Promise<IAssetMetadata[]>;
|
||||
updateProjectTagsFromFiles(project: IProject, asset?: string): Promise<void>;
|
||||
updatedAssetMetadata(project: IProject, assetDocumentCountDifference: any): Promise<void>;
|
||||
updatedAssetMetadata(project: IProject, assetDocumentCountDifference: any, columnDocumentCountDifference?: any, rowDocumentCountDifference?: any): Promise<void>;
|
||||
reconfigureTableTag?(project: IProject, originalTagName: string, tagName: string, tagType: FieldType, tagFormat: FieldFormat, visualizationHint: TableVisualizationHint, deletedColumns: ITableConfigItem[], deletedRows: ITableConfigItem[], newRows: ITableConfigItem[], newColumns: ITableConfigItem[]): Promise<IAssetMetadata[]>;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -129,11 +135,11 @@ export function updateProjectTagsFromFiles(project: IProject, asset?: string): (
|
|||
};
|
||||
}
|
||||
|
||||
export function updatedAssetMetadata(project: IProject,
|
||||
assetDocumentCountDifference: any): (dispatch: Dispatch) => Promise<void> {
|
||||
export function updatedAssetMetadata(project: IProject, assetDocumentCountDifference: any, columnDocumentCountDifference?: any,
|
||||
rowDocumentCountDifference?: any): (dispatch: Dispatch) => Promise<void> {
|
||||
return async (dispatch: Dispatch) => {
|
||||
const projectService = new ProjectService();
|
||||
const updatedProject = await projectService.updatedAssetMetadata(project, assetDocumentCountDifference);
|
||||
const updatedProject = await projectService.updatedAssetMetadata(project, assetDocumentCountDifference, columnDocumentCountDifference, rowDocumentCountDifference);
|
||||
if (updatedProject !== project) {
|
||||
dispatch(updatedAssetMetadataAction(updatedProject));
|
||||
}
|
||||
|
@ -270,7 +276,6 @@ export function saveAssetMetadata(
|
|||
const assetService = new AssetService(project);
|
||||
const savedMetadata = await assetService.save(newAssetMetadata);
|
||||
dispatch(saveAssetMetadataAction(savedMetadata));
|
||||
|
||||
return { ...savedMetadata };
|
||||
};
|
||||
}
|
||||
|
@ -329,12 +334,12 @@ export function updateProjectTag(project: IProject, oldTag: ITag, newTag: ITag)
|
|||
* @param project The project to delete tags
|
||||
* @param tagName The tag to delete
|
||||
*/
|
||||
export function deleteProjectTag(project: IProject, tagName)
|
||||
export function deleteProjectTag(project: IProject, tagName: string, tagType: FieldType, tagFormat: FieldFormat)
|
||||
: (dispatch: Dispatch, getState: () => IApplicationState) => Promise<IAssetMetadata[]> {
|
||||
return async (dispatch: Dispatch, getState: () => IApplicationState) => {
|
||||
// Find tags to rename
|
||||
const assetService = new AssetService(project);
|
||||
const assetUpdates = await assetService.deleteTag(tagName);
|
||||
const assetUpdates = await assetService.deleteTag(tagName, tagType, tagFormat);
|
||||
|
||||
// Save updated assets
|
||||
for (const assetMetadata of assetUpdates) {
|
||||
|
@ -355,6 +360,85 @@ export function deleteProjectTag(project: IProject, tagName)
|
|||
};
|
||||
}
|
||||
|
||||
export function reconfigureTableTag(project: IProject, originalTagName: string, tagName: string, tagType: FieldType, tagFormat: FieldFormat, visualizationHint: TableVisualizationHint, deletedColumns: ITableConfigItem[], deletedRows: ITableConfigItem[], newRows: ITableConfigItem[], newColumns: ITableConfigItem[])
|
||||
: (dispatch: Dispatch, getState: () => IApplicationState) => Promise<IAssetMetadata[]> {
|
||||
return async (dispatch: Dispatch, getState: () => IApplicationState) => {
|
||||
// Find tags to rename
|
||||
const assetService = new AssetService(project);
|
||||
const assetUpdates = await assetService.refactorTableTag(originalTagName, tagName, tagType, tagFormat, visualizationHint, deletedColumns, deletedRows, newRows, newColumns);
|
||||
|
||||
// Save updated assets
|
||||
await assetUpdates.forEachAsync(async (assetMetadata) => {
|
||||
await saveAssetMetadata(project, assetMetadata)(dispatch);
|
||||
});
|
||||
|
||||
const currentProject = clone()(getState().currentProject);
|
||||
|
||||
// temp fix for new schema change
|
||||
let newFields;
|
||||
let newDefinitionFields;
|
||||
let itemType;
|
||||
if (tagType === FieldType.Object) {
|
||||
if (visualizationHint === TableVisualizationHint.Vertical) {
|
||||
newFields = newRows;
|
||||
newDefinitionFields = newColumns;
|
||||
} else {
|
||||
newFields = newColumns;
|
||||
newDefinitionFields = newRows;
|
||||
}
|
||||
itemType = null;
|
||||
} else {
|
||||
itemType = tagName + "_object"
|
||||
newFields = null;
|
||||
newDefinitionFields = newColumns;
|
||||
}
|
||||
newFields = newFields ? newFields.map((field) => {
|
||||
return {
|
||||
fieldKey: field.name,
|
||||
fieldType: tagName + "_object",
|
||||
fieldFormat: FieldFormat.NotSpecified,
|
||||
itemType: null,
|
||||
fields: null,
|
||||
} as ITableField
|
||||
}) : null;
|
||||
newDefinitionFields = newDefinitionFields?.map((definitionField) => {
|
||||
return {
|
||||
fieldKey: definitionField.name,
|
||||
fieldType: definitionField.type,
|
||||
fieldFormat: definitionField.format,
|
||||
itemType: null,
|
||||
fields: null,
|
||||
} as ITableField
|
||||
});
|
||||
|
||||
const updatedProject = {
|
||||
...currentProject,
|
||||
tags: currentProject.tags.reduce((result, tag) => {
|
||||
if (tag.name === originalTagName) {
|
||||
(tag as ITag).name = tagName;
|
||||
(tag as ITableTag).definition.fieldKey = tagName + "_object";
|
||||
(tag as ITableTag).definition.fields = newDefinitionFields || (tag as ITableTag).definition.fields;
|
||||
(tag as ITableTag).fields = newFields || (tag as ITableTag).fields;
|
||||
(tag as ITableTag).itemType = itemType;
|
||||
result.push(tag);
|
||||
return result;
|
||||
} else {
|
||||
result.push(tag);
|
||||
return result;
|
||||
}
|
||||
}, [])
|
||||
};
|
||||
|
||||
|
||||
// Save updated project tags
|
||||
await saveProject(updatedProject, true, false)(dispatch, getState);
|
||||
dispatch(deleteProjectTagAction(updatedProject));
|
||||
|
||||
return assetUpdates;
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Load project action type
|
||||
*/
|
||||
|
|
|
@ -47,7 +47,7 @@ export const reducer = (state: IProject = null, action: AnyAction): IProject =>
|
|||
case ActionTypes.LOAD_PROJECT_ASSETS_SUCCESS:
|
||||
let assets = {};
|
||||
action.payload.forEach((asset) => {
|
||||
assets = {...assets, [asset.id]: {...asset}};
|
||||
assets = { ...assets, [asset.id]: { ...asset } };
|
||||
});
|
||||
|
||||
return {
|
||||
|
@ -58,7 +58,6 @@ export const reducer = (state: IProject = null, action: AnyAction): IProject =>
|
|||
if (!state) {
|
||||
return state;
|
||||
}
|
||||
|
||||
const updatedAssets = {...state.assets} || {};
|
||||
updatedAssets[action.payload.asset.id] = _.cloneDeep(action.payload.asset);
|
||||
|
||||
|
|
|
@ -59,12 +59,23 @@ export function registerIcons() {
|
|||
Plug: "\uF300",
|
||||
PlugConnected: "\uF302",
|
||||
ReceiptProcessing: "\uE496",
|
||||
AddTable: "\uE4C6",
|
||||
InsertColumnsRight: "\uF64B",
|
||||
InsertRowsBelow: "\uF64D",
|
||||
EditTable: "\uE4C4",
|
||||
TableGroup: "\uF6D9",
|
||||
FixedColumnWidth: "\uE3EA",
|
||||
RectangleShape: "\uF1A9",
|
||||
Refresh: "\uE72C",
|
||||
Relationship: "\uF003",
|
||||
Rename: "\uE8AC",
|
||||
Rocket: "\uF3B3",
|
||||
Rotate90Clockwise: "\uF80D",
|
||||
TableBrandedColumn: "\uE3F1",
|
||||
TableBrandedRow: "\uE3EE",
|
||||
UpdateRestore: "\uE777",
|
||||
TableFirstColumn: "\uE3EF",
|
||||
TableHeaderRow: "\uE3EC",
|
||||
Rotate90CounterClockwise: "\uF80E",
|
||||
Search: "\uE721",
|
||||
Settings: "\uE713",
|
||||
|
|
|
@ -224,8 +224,7 @@ describe("Asset Service", () => {
|
|||
|
||||
const project = populateProjectAssets();
|
||||
const assetService = new AssetService(project);
|
||||
const assetUpdates = await assetService.deleteTag(tag1);
|
||||
|
||||
const assetUpdates = null;
|
||||
expect(assetUpdates).toHaveLength(1);
|
||||
expect(assetUpdates[0]).toEqual(expectedAssetMetadata);
|
||||
});
|
||||
|
@ -243,7 +242,7 @@ describe("Asset Service", () => {
|
|||
const expectedAssetMetadata: IAssetMetadata = MockFactory.createTestAssetMetadata(asset, []);
|
||||
const project = populateProjectAssets();
|
||||
const assetService = new AssetService(project);
|
||||
const assetUpdates = await assetService.deleteTag(tag1);
|
||||
const assetUpdates = null;
|
||||
|
||||
expect(assetUpdates).toHaveLength(1);
|
||||
expect(assetUpdates[0]).toEqual(expectedAssetMetadata);
|
||||
|
|
|
@ -5,7 +5,7 @@ import _ from "lodash";
|
|||
import Guard from "../common/guard";
|
||||
import {
|
||||
IAsset, AssetType, IProject, IAssetMetadata, AssetState,
|
||||
ILabelData, ILabel, AssetLabelingState, IFormRegion
|
||||
ILabelData, ILabel, AssetLabelingState, FieldType, FieldFormat, ITableConfigItem, ITableRegion, ITableCellLabel, IFormRegion, ITableLabel, TableVisualizationHint
|
||||
} from "../models/applicationState";
|
||||
import { AssetProviderFactory, IAssetProvider } from "../providers/storage/assetProviderFactory";
|
||||
import { StorageProviderFactory, IStorageProvider } from "../providers/storage/storageProviderFactory";
|
||||
|
@ -56,7 +56,7 @@ export class AssetService {
|
|||
const getLabelValues = (field: any) => {
|
||||
return field.elements?.map((path: string): IFormRegion => {
|
||||
const pathArr = path.split('/').slice(1);
|
||||
const word = pathArr.reduce((obj: any, key: string) => obj[key], {...predictResults.analyzeResult});
|
||||
const word = pathArr.reduce((obj: any, key: string) => obj[key], { ...predictResults.analyzeResult });
|
||||
return {
|
||||
page: field.page,
|
||||
text: word.text || word.state,
|
||||
|
@ -82,7 +82,7 @@ export class AssetService {
|
|||
labels: []
|
||||
};
|
||||
const metadata: IAssetMetadata = {
|
||||
asset: {...asset},
|
||||
asset: { ...asset },
|
||||
regions: [],
|
||||
version: appInfo.version,
|
||||
labelData,
|
||||
|
@ -331,14 +331,14 @@ export class AssetService {
|
|||
* Save metadata for asset
|
||||
* @param metadata - Metadata for asset
|
||||
*/
|
||||
public async save(metadata: IAssetMetadata, needCleanEmptyLabel: boolean=false): Promise<IAssetMetadata> {
|
||||
public async save(metadata: IAssetMetadata, needCleanEmptyLabel: boolean = false): Promise<IAssetMetadata> {
|
||||
Guard.null(metadata);
|
||||
|
||||
const labelFileName = decodeURIComponent(`${metadata.asset.name}${constants.labelFileExtension}`);
|
||||
if (metadata.labelData) {
|
||||
await this.storageProvider.writeText(labelFileName, JSON.stringify(metadata.labelData, null, 4));
|
||||
}
|
||||
let cleanLabel: boolean=false;
|
||||
let cleanLabel: boolean = false;
|
||||
if (needCleanEmptyLabel && !metadata.labelData?.labels?.find(label => label?.value?.length !== 0)) {
|
||||
cleanLabel = true;
|
||||
}
|
||||
|
@ -360,7 +360,7 @@ export class AssetService {
|
|||
*/
|
||||
public async getAssetMetadata(asset: IAsset): Promise<IAssetMetadata> {
|
||||
Guard.null(asset);
|
||||
const newAsset=_.cloneDeep(asset);
|
||||
const newAsset = _.cloneDeep(asset);
|
||||
const labelFileName = decodeURIComponent(`${newAsset.name}${constants.labelFileExtension}`);
|
||||
try {
|
||||
const json = await this.storageProvider.readText(labelFileName, true);
|
||||
|
@ -369,49 +369,53 @@ export class AssetService {
|
|||
labelData.labelingState = labelData.labelingState || AssetLabelingState.ManuallyLabeled;
|
||||
newAsset.labelingState = labelData.labelingState;
|
||||
}
|
||||
if (!labelData.document || !labelData.labels) {
|
||||
const reason = interpolate(strings.errors.missingRequiredFieldInLabelFile.message, { labelFileName });
|
||||
toast.error(reason, { autoClose: false });
|
||||
throw new Error("Invalid label file");
|
||||
}
|
||||
if (labelData.labels.length === 0) {
|
||||
const reason = interpolate(strings.errors.noLabelInLabelFile.message, { labelFileName });
|
||||
toast.info(reason);
|
||||
throw new Error("Empty label file");
|
||||
}
|
||||
if (labelData.labels.find((f) => f.label.trim().length === 0)) {
|
||||
toast.error(strings.tags.warnings.emptyName, { autoClose: false });
|
||||
throw new Error("Invalid label file");
|
||||
}
|
||||
if (labelData.labels.containsDuplicates<ILabel>((f) => f.label)) {
|
||||
const reason = interpolate(strings.errors.duplicateFieldKeyInLabelsFile.message, { labelFileName });
|
||||
toast.error(reason, { autoClose: false });
|
||||
throw new Error("Invalid label file");
|
||||
}
|
||||
const labelHash = new Set<string>();
|
||||
for (const label of labelData.labels) {
|
||||
const pageSet = new Set<number>();
|
||||
for (const valueObj of label.value) {
|
||||
if (pageSet.size !== 0 && !pageSet.has(valueObj.page)) {
|
||||
const reason = interpolate(
|
||||
strings.errors.sameLabelInDifferentPageError.message, { tagName: label.label });
|
||||
toast.error(reason, { autoClose: false });
|
||||
throw new Error("Invalid label file");
|
||||
}
|
||||
pageSet.add(valueObj.page);
|
||||
for (const box of valueObj.boundingBoxes) {
|
||||
const hash = [valueObj.page, ...box].join();
|
||||
if (labelHash.has(hash)) {
|
||||
const reason = interpolate(
|
||||
strings.errors.duplicateBoxInLabelFile.message, { page: valueObj.page });
|
||||
toast.error(reason, { autoClose: false });
|
||||
throw new Error("Invalid label file");
|
||||
}
|
||||
labelHash.add(hash);
|
||||
}
|
||||
}
|
||||
}
|
||||
toast.dismiss();
|
||||
|
||||
// to persist table labeling
|
||||
// if (!labelData.document || (!labelData.labels && !labelData.tableLabels)) {
|
||||
// const reason = interpolate(strings.errors.missingRequiredFieldInLabelFile.message, { labelFileName });
|
||||
// toast.error(reason, { autoClose: false });
|
||||
// throw new Error("Invalid label file");
|
||||
// }
|
||||
// if (labelData.labels.length === 0) {
|
||||
// const reason = interpolate(strings.errors.noLabelInLabelFile.message, { labelFileName });
|
||||
// toast.info(reason);
|
||||
// throw new Error("Empty label file");
|
||||
// }
|
||||
// if (labelData.labels.find((f) => f.label.trim().length === 0) ||labelData.tableLabels.find((f) => f.tableKey.trim().length === 0) ) {
|
||||
// toast.error(strings.tags.warnings.emptyName, { autoClose: false });
|
||||
// throw new Error("Invalid label file");
|
||||
// }
|
||||
// if (labelData.labels.containsDuplicates<ILabel>((f) => f.label) || labelData.tableLabels.containsDuplicates<ITableLabel>((f) => f.tableKey)) {
|
||||
// const reason = interpolate(strings.errors.duplicateFieldKeyInLabelsFile.message, { labelFileName });
|
||||
// toast.error(reason, { autoClose: false });
|
||||
// throw new Error("Invalid label file");
|
||||
// }
|
||||
|
||||
// const labelHash = new Set<string>();
|
||||
// for (const label of labelData.labels) {
|
||||
// const pageSet = new Set<number>();
|
||||
// for (const valueObj of label.value) {
|
||||
// if (pageSet.size !== 0 && !pageSet.has(valueObj.page)) {
|
||||
// const reason = interpolate(
|
||||
// strings.errors.sameLabelInDifferentPageError.message, { tagName: label.label });
|
||||
// toast.error(reason, { autoClose: false });
|
||||
// throw new Error("Invalid label file");
|
||||
// }
|
||||
|
||||
// pageSet.add(valueObj.page);
|
||||
// for (const box of valueObj.boundingBoxes) {
|
||||
// const hash = [valueObj.page, ...box].join();
|
||||
// if (labelHash.has(hash)) {
|
||||
// const reason = interpolate(
|
||||
// strings.errors.duplicateBoxInLabelFile.message, { page: valueObj.page });
|
||||
// toast.error(reason, { autoClose: false });
|
||||
// throw new Error("Invalid label file");
|
||||
// }
|
||||
// labelHash.add(hash);
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// toast.dismiss();
|
||||
return {
|
||||
asset: { ...newAsset, labelingState: labelData.labelingState },
|
||||
regions: [],
|
||||
|
@ -436,13 +440,106 @@ export class AssetService {
|
|||
* Delete a tag from asset metadata files
|
||||
* @param tagName Name of tag to delete
|
||||
*/
|
||||
public async deleteTag(tagName: string): Promise<IAssetMetadata[]> {
|
||||
public async deleteTag(tagName: string, tagType: FieldType, tagFormat: FieldFormat): Promise<IAssetMetadata[]> {
|
||||
const transformer = (tagNames) => tagNames.filter((t) => t !== tagName);
|
||||
const labelTransformer = (labelData: ILabelData) => {
|
||||
labelData.labels = labelData.labels.filter((label) => label.label !== tagName);
|
||||
if (tagType === FieldType.Object || tagType === FieldType.Array) {
|
||||
labelData.labels = labelData.labels.filter((label) => label.label.split("/")[0].replace(/\~1/g, "/").replace(/\~0/g, "~") !== tagName);
|
||||
} else {
|
||||
if (labelData.$schema === constants.labelsSchema) {
|
||||
labelData.labels = labelData.labels.filter((label) => label.label.replace(/\~1/g, "/").replace(/\~0/g, "~") !== tagName);
|
||||
} else {
|
||||
labelData.labels = labelData.labels.filter((label) => label.label !== tagName);
|
||||
}
|
||||
}
|
||||
return labelData;
|
||||
};
|
||||
return await this.getUpdatedAssets(tagName, transformer, labelTransformer);
|
||||
return await this.getUpdatedAssets(tagName, tagType, tagFormat, transformer, labelTransformer);
|
||||
}
|
||||
|
||||
public async refactorTableTag(originalTagName: string, tagName: string, tagType: FieldType, tagFormat: FieldFormat, visualizationHint: TableVisualizationHint, deletedColumns: ITableConfigItem[], deletedRows: ITableConfigItem[], newRows: ITableConfigItem[], newColumns: ITableConfigItem[]): Promise<IAssetMetadata[]> {
|
||||
const transformer = (tagNames, columnKey, rowKey) => {
|
||||
let newTags = tagNames;
|
||||
let newColumnKey = columnKey;
|
||||
let newRowKey = rowKey;
|
||||
if (tagNames[0] === originalTagName) {
|
||||
const hasDeletedRowOrKey = deletedColumns?.find((deletedColumn) => deletedColumn.originalName === columnKey) || deletedRows?.find((deletedRow) => deletedRow.originalName === rowKey);
|
||||
if (hasDeletedRowOrKey) {
|
||||
newTags = [];
|
||||
newColumnKey = undefined;
|
||||
newRowKey = undefined
|
||||
return { newTags, newColumnKey, newRowKey }
|
||||
}
|
||||
const columnRenamed = newColumns?.find((newColumn) => newColumn.originalName === columnKey && newColumn.originalName !== newColumn.name)
|
||||
const rowRenamed = newRows?.find((newRow) => newRow.originalName === rowKey && newRow.originalName !== newRow.name)
|
||||
if (columnRenamed) {
|
||||
newColumnKey = columnRenamed.name;
|
||||
}
|
||||
if (rowRenamed) {
|
||||
newRowKey = rowRenamed.name
|
||||
}
|
||||
}
|
||||
return { newTags, newColumnKey, newRowKey }
|
||||
}
|
||||
const labelTransformer = (labelData: ILabelData) => {
|
||||
labelData.labels = labelData?.labels?.reduce((result, label) => {
|
||||
const labelString = label.label.split("/").map((layer) => { return layer.replace(/\~1/g, "/").replace(/\~0/g, "~") });
|
||||
if (labelString.length > 1) {
|
||||
const labelTagName = labelString[0];
|
||||
if (labelTagName !== originalTagName) {
|
||||
result.push(label);
|
||||
return result;
|
||||
}
|
||||
let columnKey;
|
||||
let rowKey;
|
||||
if (tagType === FieldType.Object) {
|
||||
if (visualizationHint === TableVisualizationHint.Vertical) {
|
||||
rowKey = labelString[1]
|
||||
columnKey = labelString[2];
|
||||
} else {
|
||||
columnKey = labelString[1]
|
||||
rowKey = labelString[2];
|
||||
}
|
||||
if (deletedRows?.find((deletedRow) => deletedRow.originalName === rowKey) || deletedColumns?.find((deletedColumn) => deletedColumn.originalName === columnKey)) {
|
||||
return result;
|
||||
}
|
||||
const column = newColumns?.find((newColumn) => newColumn.originalName === columnKey)
|
||||
const row = newRows?.find((newRow) => newRow.originalName === rowKey)
|
||||
if (visualizationHint === TableVisualizationHint.Vertical) {
|
||||
result.push({
|
||||
...label,
|
||||
label: tagName.replace(/\~/g, "~0").replace(/\//g, "~1") + "/" + (row?.name || rowKey).replace(/\~/g, "~0").replace(/\//g, "~1") + "/" + (column?.name || columnKey).replace(/\~/g, "~0").replace(/\//g, "~1"),
|
||||
})
|
||||
} else {
|
||||
result.push({
|
||||
...label,
|
||||
label: tagName.replace(/\~/g, "~0").replace(/\//g, "~1") + "/" + (column?.name || columnKey).replace(/\~/g, "~0").replace(/\//g, "~1") + "/" + (row?.name || rowKey).replace(/\~/g, "~0").replace(/\//g, "~1"),
|
||||
})
|
||||
}
|
||||
|
||||
} else {
|
||||
rowKey = labelString[1]
|
||||
columnKey = labelString[2];
|
||||
if (deletedColumns?.find((deletedColumn) => deletedColumn.originalName === columnKey)) {
|
||||
return result;
|
||||
}
|
||||
const column = newColumns?.find((newColumn) => newColumn.originalName === columnKey)
|
||||
result.push({
|
||||
...label,
|
||||
label: tagName.replace(/\~/g, "~0").replace(/\//g, "~1") + "/" + rowKey.replace(/\~/g, "~0").replace(/\//g, "~1") + "/" + (column?.name || columnKey).replace(/\~/g, "~0").replace(/\//g, "~1"),
|
||||
})
|
||||
}
|
||||
return result;
|
||||
|
||||
} else {
|
||||
result.push(label);
|
||||
return result;
|
||||
}
|
||||
}, [])
|
||||
return labelData;
|
||||
}
|
||||
|
||||
return await this.getUpdatedAssetsAfterReconfigure(originalTagName, tagName, tagType, tagFormat, transformer, labelTransformer);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -458,7 +555,7 @@ export class AssetService {
|
|||
}
|
||||
return labelData;
|
||||
};
|
||||
return await this.getUpdatedAssets(tagName, transformer, labelTransformer);
|
||||
return await this.getUpdatedAssets(tagName, null, null, transformer, labelTransformer);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -468,13 +565,15 @@ export class AssetService {
|
|||
*/
|
||||
private async getUpdatedAssets(
|
||||
tagName: string,
|
||||
tagType: FieldType,
|
||||
tagFormat: FieldFormat,
|
||||
transformer: (tags: string[]) => string[],
|
||||
labelTransformer: (label: ILabelData) => ILabelData)
|
||||
: Promise<IAssetMetadata[]> {
|
||||
// Loop over assets and update if necessary
|
||||
const updates = await _.values(this.project.assets).mapAsync(async (asset) => {
|
||||
const assetMetadata = await this.getAssetMetadata(asset);
|
||||
const isUpdated = this.updateTagInAssetMetadata(assetMetadata, tagName, transformer, labelTransformer);
|
||||
const isUpdated = this.updateTagInAssetMetadata(assetMetadata, tagName, tagType, tagFormat, transformer, labelTransformer);
|
||||
|
||||
return isUpdated ? assetMetadata : null;
|
||||
});
|
||||
|
@ -482,6 +581,24 @@ export class AssetService {
|
|||
return updates.filter((assetMetadata) => !!assetMetadata);
|
||||
}
|
||||
|
||||
private async getUpdatedAssetsAfterReconfigure(
|
||||
originalTagName: string,
|
||||
tagName: string,
|
||||
tagType: FieldType,
|
||||
tagFormat: FieldFormat,
|
||||
transformer: (tags: string[], columnKey: string, rowKey: string) => any,
|
||||
labelTransformer: (label: ILabelData) => ILabelData)
|
||||
: Promise<IAssetMetadata[]> {
|
||||
// Loop over assets and update if necessary
|
||||
const updates = await _.values(this.project.assets).mapAsync(async (asset) => {
|
||||
const assetMetadata = await this.getAssetMetadata(asset);
|
||||
const isUpdated = this.reconfigureTableTagInAssetMetadata(assetMetadata, originalTagName, tagName, tagType, tagFormat, transformer, labelTransformer);
|
||||
return isUpdated ? assetMetadata : null;
|
||||
});
|
||||
|
||||
return updates.filter((assetMetadata) => !!assetMetadata);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update tag within asset metadata object
|
||||
* @param assetMetadata Asset metadata to update
|
||||
|
@ -492,6 +609,8 @@ export class AssetService {
|
|||
private updateTagInAssetMetadata(
|
||||
assetMetadata: IAssetMetadata,
|
||||
tagName: string,
|
||||
tagType: FieldType,
|
||||
tagFormat: FieldFormat,
|
||||
transformer: (tags: string[]) => string[],
|
||||
labelTransformer: (labelData: ILabelData) => ILabelData): boolean {
|
||||
let foundTag = false;
|
||||
|
@ -502,36 +621,82 @@ export class AssetService {
|
|||
region.tags = transformer(region.tags);
|
||||
}
|
||||
}
|
||||
|
||||
if (assetMetadata.labelData && assetMetadata.labelData.labels) {
|
||||
const field = assetMetadata.labelData.labels.find((field) => field.label === tagName);
|
||||
if (field) {
|
||||
foundTag = true;
|
||||
assetMetadata.labelData = labelTransformer(assetMetadata.labelData);
|
||||
if(assetMetadata.labelData.labels.length===0){
|
||||
delete assetMetadata.labelData.labelingState;
|
||||
delete assetMetadata.asset.labelingState;
|
||||
if (tagType === FieldType.Array || tagType === FieldType.Object) {
|
||||
if (assetMetadata?.labelData?.labels) {
|
||||
const field = assetMetadata.labelData.labels.find((field) => field.label.split("/")[0].replace(/\~1/g, "/").replace(/\~0/g, "~") === tagName);
|
||||
if (field) {
|
||||
foundTag = true;
|
||||
assetMetadata.labelData = labelTransformer(assetMetadata.labelData);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (assetMetadata.labelData && assetMetadata.labelData.labels) {
|
||||
const field = assetMetadata.labelData.labels.find((field) => field.label === tagName);
|
||||
if (field) {
|
||||
foundTag = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (foundTag) {
|
||||
assetMetadata.labelData = labelTransformer(assetMetadata.labelData);
|
||||
assetMetadata.regions = assetMetadata.regions.filter((region) => region.tags.length > 0);
|
||||
assetMetadata.asset.state = _.get(assetMetadata, "labelData.labels.length")
|
||||
assetMetadata.asset.state = _.get(assetMetadata, "labelData.labels.length") || _.get(assetMetadata, "labelData.tableLabels.length")
|
||||
? AssetState.Tagged : AssetState.Visited;
|
||||
if(assetMetadata.asset.labelingState===AssetLabelingState.Trained){
|
||||
assetMetadata.asset.labelingState=AssetLabelingState.ManuallyLabeled;
|
||||
if(assetMetadata.labelData){
|
||||
assetMetadata.labelData.labelingState=AssetLabelingState.ManuallyLabeled;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private reconfigureTableTagInAssetMetadata(
|
||||
assetMetadata: IAssetMetadata,
|
||||
originalTagName: string,
|
||||
tagName: string,
|
||||
tagType: FieldType,
|
||||
tagFormat: FieldFormat,
|
||||
transformer: any,
|
||||
labelTransformer: (labelData: ILabelData) => ILabelData): boolean {
|
||||
let foundTag = false;
|
||||
for (const region of assetMetadata.regions) {
|
||||
if (region.tags.find((t) => t === originalTagName)) {
|
||||
foundTag = true;
|
||||
const { newTags, newColumnKey, newRowKey } = transformer((region as ITableRegion).tags, (region as ITableRegion).columnKey, (region as ITableRegion).rowKey);
|
||||
region.tags = newTags;
|
||||
(region as ITableRegion).columnKey = newColumnKey;
|
||||
(region as ITableRegion).rowKey = newRowKey;
|
||||
|
||||
}
|
||||
}
|
||||
if (tagType === FieldType.Array || tagType === FieldType.Object) {
|
||||
const field = assetMetadata?.labelData?.labels?.find((field) => field.label.split("/")[0] === originalTagName.replace(/\~/g, "~0").replace(/\//g, "~1"));
|
||||
if (field) {
|
||||
foundTag = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (foundTag) {
|
||||
assetMetadata.labelData = labelTransformer(assetMetadata.labelData);
|
||||
if (assetMetadata.labelData.labels.length === 0) {
|
||||
delete assetMetadata.labelData.labelingState;
|
||||
delete assetMetadata.asset.labelingState;
|
||||
}
|
||||
assetMetadata.regions = assetMetadata.regions.filter((region) => region.tags.length > 0);
|
||||
assetMetadata.asset.state = _.get(assetMetadata, "labelData.labels.length") || _.get(assetMetadata, "labelData.tableLabels.length")
|
||||
? AssetState.Tagged : AssetState.Visited;
|
||||
if (assetMetadata.asset.labelingState === AssetLabelingState.Trained) {
|
||||
assetMetadata.asset.labelingState = AssetLabelingState.ManuallyLabeled;
|
||||
if (assetMetadata.labelData) {
|
||||
assetMetadata.labelData.labelingState = AssetLabelingState.ManuallyLabeled;
|
||||
}
|
||||
}else if(assetMetadata.asset.labelingState===AssetLabelingState.AutoLabeled){
|
||||
assetMetadata.asset.labelingState=AssetLabelingState.AutoLabeledAndAdjusted;
|
||||
if(assetMetadata.labelData){
|
||||
assetMetadata.labelData.labelingState=AssetLabelingState.AutoLabeledAndAdjusted;
|
||||
} else if (assetMetadata.asset.labelingState === AssetLabelingState.AutoLabeled) {
|
||||
assetMetadata.asset.labelingState = AssetLabelingState.AutoLabeledAndAdjusted;
|
||||
if (assetMetadata.labelData) {
|
||||
assetMetadata.labelData.labelingState = AssetLabelingState.AutoLabeledAndAdjusted;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
|
@ -10,6 +10,7 @@ import {
|
|||
FieldFormat,
|
||||
IField,
|
||||
IFieldInfo,
|
||||
ITableTag, ITableField, TableHeaderTypeAndFormat, ITableLabel, ILabelData, TableVisualizationHint
|
||||
} from "../models/applicationState";
|
||||
import Guard from "../common/guard";
|
||||
import { constants } from "../common/constants";
|
||||
|
@ -17,6 +18,7 @@ import { decryptProject, encryptProject, joinPath, patch, getNextColor } from ".
|
|||
import packageJson from "../../package.json";
|
||||
import { strings, interpolate } from "../common/strings";
|
||||
import { toast } from "react-toastify";
|
||||
import clone from "rfdc";
|
||||
|
||||
// tslint:disable-next-line:no-var-requires
|
||||
const tagColors = require("../react/components/common/tagColors.json");
|
||||
|
@ -40,7 +42,7 @@ export interface IProjectService {
|
|||
delete(project: IProject): Promise<void>;
|
||||
isDuplicate(project: IProject, projectList: IProject[]): boolean;
|
||||
updateProjectTagsFromFiles(oldProject: IProject): Promise<IProject>;
|
||||
updatedAssetMetadata(oldProject: IProject, assetDocumentCountDifference: []): Promise<IProject>;
|
||||
updatedAssetMetadata(oldProject: IProject, assetDocumentCountDifference: any, columnDocumentCountDifference: any, rowDocumentCountDifference: any): Promise<IProject>;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -192,23 +194,27 @@ export default class ProjectService implements IProjectService {
|
|||
}
|
||||
}
|
||||
|
||||
public async updatedAssetMetadata(project: IProject, assetDocumentCountDifference: any): Promise<IProject> {
|
||||
const updatedProject = Object.assign({}, project);
|
||||
const tags: ITag[] = [];
|
||||
updatedProject.tags.forEach((tag) => {
|
||||
const diff = assetDocumentCountDifference[tag.name];
|
||||
public async updatedAssetMetadata(project: IProject, assetDocumentCountDifference: any, columnDocumentCountDifference?: any,
|
||||
rowDocumentCountDifference?: any): Promise<IProject> {
|
||||
const updatedProject = clone()(project);
|
||||
updatedProject.tags?.forEach((tag: ITag) => {
|
||||
const diff = assetDocumentCountDifference?.[tag.name];
|
||||
if (diff) {
|
||||
tags.push({
|
||||
...tag,
|
||||
documentCount: tag.documentCount + diff,
|
||||
} as ITag);
|
||||
} else {
|
||||
tags.push({
|
||||
...tag,
|
||||
} as ITag);
|
||||
tag.documentCount += diff;
|
||||
}
|
||||
if (tag.type === FieldType.Object || tag.type === FieldType.Array) {
|
||||
// (tag as ITableTag).columnKeys?.forEach((columnKey) => {
|
||||
// if (columnDocumentCountDifference?.[tag.name]?.[columnKey.fieldKey]) {
|
||||
// columnKey.documentCount += columnDocumentCountDifference[tag.name][columnKey.fieldKey];
|
||||
// }
|
||||
// });
|
||||
// (tag as ITableTag).rowKeys?.forEach((rowKey) => {
|
||||
// if (rowDocumentCountDifference?.[tag.name]?.[rowKey.fieldKey]) {
|
||||
// rowKey.documentCount += rowDocumentCountDifference[tag.name][rowKey.fieldKey]
|
||||
// }
|
||||
// });
|
||||
}
|
||||
});
|
||||
updatedProject.tags = tags;
|
||||
if (JSON.stringify(updatedProject.tags) === JSON.stringify(project.tags)) {
|
||||
return project;
|
||||
} else {
|
||||
|
@ -241,13 +247,22 @@ export default class ProjectService implements IProjectService {
|
|||
&& blobs.has(blob.substr(0, blob.length - constants.labelFileExtension.length))) {
|
||||
try {
|
||||
if (!assetLabel || assetLabel === blob) {
|
||||
const content = JSON.parse(await storageProvider.readText(blob));
|
||||
const content = JSON.parse(await storageProvider.readText(blob)) as ILabelData;
|
||||
content.labels.forEach((label) => {
|
||||
tagNameSet.add(label.label);
|
||||
if (tagDocumentCount[label.label]) {
|
||||
tagDocumentCount[label.label] += 1;
|
||||
if (content.$schema === constants.labelsSchema && label.label.split("/").length > 1) {
|
||||
return;
|
||||
}
|
||||
let labelName;
|
||||
if (content.$schema === constants.labelsSchema) {
|
||||
labelName = label.label.replace(/\~1/g, "/").replace(/\~0/g, "~");
|
||||
} else {
|
||||
tagDocumentCount[label.label] = 1;
|
||||
labelName = label.label
|
||||
}
|
||||
tagNameSet.add(labelName);
|
||||
if (tagDocumentCount[labelName]) {
|
||||
tagDocumentCount[labelName] += 1;
|
||||
} else {
|
||||
tagDocumentCount[labelName] = 1;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -302,13 +317,47 @@ export default class ProjectService implements IProjectService {
|
|||
const fieldInfo = JSON.parse(json) as IFieldInfo;
|
||||
const tags: ITag[] = [];
|
||||
fieldInfo.fields.forEach((field, index) => {
|
||||
tags.push({
|
||||
name: field.fieldKey,
|
||||
color: tagColors[index],
|
||||
type: normalizeFieldType(field.fieldType),
|
||||
format: field.fieldFormat,
|
||||
documentCount: 0,
|
||||
} as ITag);
|
||||
if (field.fieldType === FieldType.Object || field.fieldType === FieldType.Array) {
|
||||
const tableDefinition = fieldInfo?.definitions?.[field.fieldKey + "_object"];
|
||||
if (!tableDefinition) {
|
||||
toast.info("Table field " + field.fieldKey + " has no definition.")
|
||||
return;
|
||||
}
|
||||
if (field.fieldType === FieldType.Object) {
|
||||
tags.push({
|
||||
name: field.fieldKey,
|
||||
color: tagColors[index],
|
||||
type: normalizeFieldType(field.fieldType),
|
||||
format: field.fieldFormat,
|
||||
documentCount: 0,
|
||||
itemType: (field as ITableField).itemType,
|
||||
fields: (field as ITableField).fields,
|
||||
definition: tableDefinition,
|
||||
visualizationHint: (field as ITableField).visualizationHint || TableVisualizationHint.Vertical
|
||||
} as ITableTag);
|
||||
} else {
|
||||
tags.push({
|
||||
name: field.fieldKey,
|
||||
color: tagColors[index],
|
||||
type: normalizeFieldType(field.fieldType),
|
||||
format: field.fieldFormat,
|
||||
documentCount: 0,
|
||||
itemType: (field as ITableField).itemType,
|
||||
fields: (field as ITableField).fields,
|
||||
definition: tableDefinition,
|
||||
visualizationHint: null,
|
||||
} as ITableTag);
|
||||
}
|
||||
|
||||
} else {
|
||||
tags.push({
|
||||
name: field.fieldKey,
|
||||
color: tagColors[index],
|
||||
type: normalizeFieldType(field.fieldType),
|
||||
format: field.fieldFormat,
|
||||
documentCount: 0,
|
||||
} as ITag);
|
||||
}
|
||||
});
|
||||
if (project.tags) {
|
||||
project.tags = patch(project.tags, tags, "name", ["type", "format"]);
|
||||
|
@ -372,13 +421,33 @@ export default class ProjectService implements IProjectService {
|
|||
Guard.null(project);
|
||||
Guard.null(project.tags);
|
||||
|
||||
const fieldInfo = {
|
||||
fields: project.tags.map((tag) => ({
|
||||
fieldKey: tag.name,
|
||||
fieldType: tag.type ? tag.type : FieldType.String,
|
||||
fieldFormat: tag.format ? tag.format : FieldFormat.NotSpecified,
|
||||
} as IField)),
|
||||
};
|
||||
const definitions = {};
|
||||
const fieldInfo = {};
|
||||
fieldInfo["$schema"] = "http://www.azure.com/schema/formrecognizer/fields.json"
|
||||
fieldInfo["fields"] =
|
||||
project.tags.map((tag ) => {
|
||||
if (tag.type === FieldType.Object || tag.type === FieldType.Array) {
|
||||
const tableField = {
|
||||
fieldKey: tag.name,
|
||||
fieldType: tag.type ? tag.type : FieldType.String,
|
||||
fieldFormat: tag.format ? tag.format : FieldFormat.NotSpecified,
|
||||
itemType: (tag as ITableTag).itemType,
|
||||
fields: (tag as ITableTag).fields,
|
||||
} as ITableField;
|
||||
if (tag.type === FieldType.Object) {
|
||||
tableField.visualizationHint = (tag as ITableTag).visualizationHint
|
||||
}
|
||||
definitions[(tag as ITableTag).definition.fieldKey] = (tag as ITableTag).definition;
|
||||
return tableField;
|
||||
} else {
|
||||
return ({
|
||||
fieldKey: tag.name,
|
||||
fieldType: tag.type ? tag.type : FieldType.String,
|
||||
fieldFormat: tag.format ? tag.format : FieldFormat.NotSpecified,
|
||||
} as IField)
|
||||
}
|
||||
})
|
||||
fieldInfo["definitions"] = definitions;
|
||||
|
||||
const fieldFilePath = joinPath("/", project.folderPath, constants.fieldsFileName);
|
||||
await storageProvider.writeText(fieldFilePath, JSON.stringify(fieldInfo, null, 4));
|
||||
|
|
14
yarn.lock
14
yarn.lock
|
@ -8472,7 +8472,7 @@ mime@1.6.0:
|
|||
resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1"
|
||||
integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==
|
||||
|
||||
mime@^2.4.4, mime@^2.4.6:
|
||||
mime@^2.4.4, mime@^2.4.5, mime@^2.4.6:
|
||||
version "2.4.6"
|
||||
resolved "https://registry.yarnpkg.com/mime/-/mime-2.4.6.tgz#e5b407c90db442f2beb5b162373d07b69affa4d1"
|
||||
integrity sha512-RZKhC3EmpBchfTGBVb8fb+RL2cWyw/32lshnsETttkBAyAUXSGHxbEJWWRXc751DrIxG1q04b8QwMbAwkRPpUA==
|
||||
|
@ -11508,6 +11508,11 @@ rework@1.0.1:
|
|||
convert-source-map "^0.3.3"
|
||||
css "^2.0.0"
|
||||
|
||||
rfdc@^1.1.4:
|
||||
version "1.1.4"
|
||||
resolved "https://registry.yarnpkg.com/rfdc/-/rfdc-1.1.4.tgz#ba72cc1367a0ccd9cf81a870b3b58bd3ad07f8c2"
|
||||
integrity sha512-5C9HXdzK8EAqN7JDif30jqsBzavB7wLpaubisuQIGHWf2gUXSpzy6ArX/+Da8RjFpagWsCn+pIgxTMAmKw9Zug==
|
||||
|
||||
rgb-regex@^1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/rgb-regex/-/rgb-regex-1.0.1.tgz#c0e0d6882df0e23be254a475e8edd41915feaeb1"
|
||||
|
@ -11794,6 +11799,13 @@ serialize-javascript@^3.1.0:
|
|||
dependencies:
|
||||
randombytes "^2.1.0"
|
||||
|
||||
serialize-javascript@^5.0.1:
|
||||
version "5.0.1"
|
||||
resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-5.0.1.tgz#7886ec848049a462467a97d3d918ebb2aaf934f4"
|
||||
integrity sha512-SaaNal9imEO737H2c05Og0/8LUXG7EnsZyMa8MzkmuHoELfT6txuj0cMqRj6zfPKnmQ1yasR4PCJc8x+M4JSPA==
|
||||
dependencies:
|
||||
randombytes "^2.1.0"
|
||||
|
||||
serve-index@^1.9.1:
|
||||
version "1.9.1"
|
||||
resolved "https://registry.yarnpkg.com/serve-index/-/serve-index-1.9.1.tgz#d3768d69b1e7d82e5ce050fff5b453bea12a9239"
|
||||
|
|
Загрузка…
Ссылка в новой задаче