Skip to content

Commit

Permalink
auto labeled tag design & replacing between text with draw region (#670)
Browse files Browse the repository at this point in the history
* change autolabel icon

* fix bug of page flashing after deleting all regions

* change workflow of replace between text and draw region

* fix labelsData is null issue

* fix Can replace labels and change tag type to selectionmark for non empty tag

* Issue of App stuck in looing state when deleting tag

* remove whitespace

* fix issues of 1.	Label icon not layers menu, Text can be applied to selection mark tag, 3.	Can change tag type to selection mark when tag already has text, 4.	Run ocr on unvisted does not mark as visited

* fix: handle storage provider factory test

* fix issue of deleting tags can't change document state

* add logical to change autolabeling state while deleting tag

* fixed the issue of label shows as revised when it hasn't been changed

* add logical to check whether it requires to callback

* restore code style

* check whether assetMetadata is null or not

* change error message show while service return error

* remove comment, change error message, global

Co-authored-by: Starain <v-stache@microsoft.com>
Co-authored-by: Alex Chen <68627897+yongbing-chen@users.noreply.github.com>
Co-authored-by: Robert Stewart (eXcell <v-stewro@microsoft.com>
Co-authored-by: stew-ro <60453211+stew-ro@users.noreply.github.com>
  • Loading branch information
5 people authored Nov 4, 2020
1 parent aab6938 commit 757e0dd
Show file tree
Hide file tree
Showing 28 changed files with 430 additions and 175 deletions.
9 changes: 6 additions & 3 deletions src/assets/sass/fabric-icons-inline.scss

Large diffs are not rendered by default.

10 changes: 10 additions & 0 deletions src/common/localization/en-us.ts
Original file line number Diff line number Diff line change
Expand Up @@ -269,6 +269,12 @@ export const english: IAppStrings = {
notCompatibleTagType: "Tag type is not compatible with this feature. If you want to change type of this tag, please remove or reassign all labels which using this tag in your project.",
checkboxPerTagLimit: "Cannot assign more than one checkbox per tag",
notCompatibleWithDrawnRegionTag: "Drawn regions and ${otherCatagory} values cannot both be assigned to the same document's tag",
replaceAllExitingLabels:"Are you sure you want to replace selected tag's labels?",
replaceAllExitingLabelsTitle:"Replace tag's labels",
},
preText:{
autoLabel:"Auto-labeled: ",
revised:"Revised: ",
},
toolbar: {
add: "Add new tag",
Expand Down Expand Up @@ -473,6 +479,10 @@ export const english: IAppStrings = {
continuing to next asset.",
},
},
warningMessage: {
PreventLeavingWhileRunningOCR: "An Layout operation is currently in progress, are you sure you want to leave?",
PreventLeavingRunningAutoLabeling: "Auto-labeling is currently in progress, are you sure you want to leave?",
}
},
profile: {
settings: "Profile Settings",
Expand Down
10 changes: 10 additions & 0 deletions src/common/localization/es-cl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -268,6 +268,12 @@ export const spanish: IAppStrings = {
notCompatibleTagType: "El tipo de etiqueta no es compatible con esta función. Si desea cambiar el tipo de esta etiqueta, elimine o reasigne todas las etiquetas que utilizan esta etiqueta en su proyecto.",
checkboxPerTagLimit: "No se puede asignar más de una casilla de verificación por etiqueta",
notCompatibleWithDrawnRegionTag: "Los valores de drawnRegion y $ {otherCatagory} no pueden asignarse a la misma etiqueta del documento",
replaceAllExitingLabels:"¿Está seguro de que desea reemplazar las etiquetas de la etiqueta seleccionada?",
replaceAllExitingLabelsTitle:"Reemplazar las etiquetas de la etiqueta",
},
preText:{
autoLabel:"Auto-etiquetado: ",
revised:"Revisado: ",
},
toolbar: {
add: "Agregar nueva etiqueta",
Expand Down Expand Up @@ -474,6 +480,10 @@ export const spanish: IAppStrings = {
Por favor, etiquete todas las regiones antes de continuar con el siguiente activo.",
},
},
warningMessage: {
PreventLeavingWhileRunningOCR: "Una operación de diseño está actualmente en curso, ¿está seguro de que desea salir?",
PreventLeavingRunningAutoLabeling: "El etiquetado automático está actualmente en curso, ¿está seguro de que desea irse?",
}
},
profile: {
settings: "Configuración de Perfíl",
Expand Down
5 changes: 5 additions & 0 deletions src/common/mockFactory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -319,6 +319,7 @@ export default class MockFactory {
createContainer: jest.fn(),
deleteContainer: jest.fn(),
getAssets: jest.fn(),
getAsset: jest.fn(),
isFileExists: jest.fn(),
};
}
Expand All @@ -343,6 +344,9 @@ export default class MockFactory {
getAssets(folderPath?: string, folderName?: string): Promise<IAsset[]> {
throw new Error("Method not implemented.");
},
getAsset(folderPath: string, assetName: string): Promise<IAsset> {
throw new Error("Method not implemented.");
}
};
}

Expand Down Expand Up @@ -480,6 +484,7 @@ export default class MockFactory {
deleteAsset: jest.fn(() => Promise.resolve()),
loadAssets: jest.fn(() => Promise.resolve()),
loadAssetMetadata: jest.fn(() => Promise.resolve()),
refreshAsset: jest.fn(() => Promise.resolve()),
saveAssetMetadata: jest.fn(() => Promise.resolve()),
updateProjectTag: jest.fn(() => Promise.resolve()),
deleteProjectTag: jest.fn(() => Promise.resolve()),
Expand Down
10 changes: 10 additions & 0 deletions src/common/strings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -285,6 +285,12 @@ export interface IAppStrings {
notCompatibleTagType: string,
checkboxPerTagLimit: string,
notCompatibleWithDrawnRegionTag: string,
replaceAllExitingLabels:string,
replaceAllExitingLabelsTitle:string,
},
preText:{
autoLabel:string,
revised:string,
}
};
connections: {
Expand Down Expand Up @@ -466,6 +472,10 @@ export interface IAppStrings {
title: string,
description: string,
},
},
warningMessage: {
PreventLeavingWhileRunningOCR: string,
PreventLeavingRunningAutoLabeling: string,
}
};
profile: {
Expand Down
8 changes: 8 additions & 0 deletions src/config/fabric-icons.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,14 @@
"name": "AlertSolid",
"unicode": "F331"
},
{
"name": "AutoEnhanceOff",
"unicode": "E78E"
},
{
"name": "AutoEnhanceOn",
"unicode": "E78D"
},
{
"name": "AzureAPIManagement",
"unicode": "F37F"
Expand Down
29 changes: 29 additions & 0 deletions src/electron/providers/storage/localFileSystem.ts
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,35 @@ export default class LocalFileSystem implements IStorageProvider {
return result;
}

public async getAsset(folderPath: string, assetName: string): Promise<IAsset>{
const files = await this.listFiles(path.normalize(folderPath));
if(files.findIndex(f=>f===assetName)!==-1){
const fileParts = assetName.split(/[\\\/]/);
const fileName = fileParts[fileParts.length - 1];
const asset = await AssetService.createAssetFromFilePath(assetName, folderPath + "/" + fileName, true);
if (this.isSupportedAssetType(asset.type)) {
const labelFileName = decodeURIComponent(`${assetName}${constants.labelFileExtension}`);
const ocrFileName = decodeURIComponent(`${assetName}${constants.ocrFileExtension}`);
if (files.find((str) => str === labelFileName)) {
asset.state = AssetState.Tagged;
const json = await this.readText(labelFileName);
const labelData = JSON.parse(json) as ILabelData;
if (labelData) {
asset.labelingState = labelData.labelingState || AssetLabelingState.ManuallyLabeled;
}
} else if (files.find((str) => str === ocrFileName)) {
asset.state = AssetState.Visited;
} else {
asset.state = AssetState.NotVisited;
}

return asset;
}
}
else{
return null;
}
}
/**
* Gets a list of file system items matching the specified predicate within the folderPath
* @param {string} folderPath
Expand Down
3 changes: 3 additions & 0 deletions src/providers/storage/assetProviderFactory.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,4 +31,7 @@ class TestAssetProvider implements IAssetProvider {
public getAssets(folderPath?: string): Promise<IAsset[]> {
throw new Error("Method not implemented.");
}
public getAsset(folderPath: string, assetName: string): Promise<IAsset>{
throw new Error("Method not implemented.");
}
}
1 change: 1 addition & 0 deletions src/providers/storage/assetProviderFactory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import getHostProcess, { HostProcessType } from "../../common/hostProcess";
export interface IAssetProvider {
initialize?(): Promise<void>;
getAssets(folderPath?: string, folderName?: string): Promise<IAsset[]>;
getAsset(folderPath: string, assetName: string): Promise<IAsset>;
}

/**
Expand Down
30 changes: 30 additions & 0 deletions src/providers/storage/azureBlobStorage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -231,6 +231,36 @@ export class AzureBlobStorage implements IStorageProvider {
return result;
}

public async getAsset(folderPath: string, assetName: string): Promise<IAsset>{
const files: string[] = await this.listFiles(folderPath);
if(files.findIndex(f=>f===assetName)!==-1){
const url = this.getUrl(assetName);
const asset = await AssetService.createAssetFromFilePath(url, this.getFileName(url));
if (this.isSupportedAssetType(asset.type)) {
const labelFileName = decodeURIComponent(`${asset.name}${constants.labelFileExtension}`);
const ocrFileName = decodeURIComponent(`${asset.name}${constants.ocrFileExtension}`);

if (files.find((str) => str === labelFileName)) {
asset.state = AssetState.Tagged;
const labelFileName = decodeURIComponent(`${asset.name}${constants.labelFileExtension}`);
const json = await this.readText(labelFileName, true);
const labelData = JSON.parse(json) as ILabelData;
if (labelData) {
asset.labelingState = labelData.labelingState || AssetLabelingState.ManuallyLabeled;
}
} else if (files.find((str) => str === ocrFileName)) {
asset.state = AssetState.Visited;
} else {
asset.state = AssetState.NotVisited;
}
return asset;
}
}
else{
return null;
}
}

/**
*
* @param url - URL for Azure Blob
Expand Down
4 changes: 4 additions & 0 deletions src/providers/storage/localFileSystemProxy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -153,4 +153,8 @@ export class LocalFileSystemProxy implements IStorageProvider, IAssetProvider {
public getAssets(folderName?: string): Promise<IAsset[]> {
return IpcRendererProxy.send(`${PROXY_NAME}:getAssets`, [this.options.folderPath, folderName]);
}

public getAsset(folderPath: string, assetName: string): Promise<IAsset>{
return IpcRendererProxy.send(`${PROXY_NAME}:getAsset`, [this.options.folderPath, `${folderPath}/${assetName}`]);
}
}
3 changes: 3 additions & 0 deletions src/providers/storage/storageProviderFactory.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,4 +63,7 @@ class TestStorageProvider implements IStorageProvider {
public getAssets(folderPath?: string): Promise<IAsset[]> {
throw new Error("Method not implemented.");
}
public getAsset(folderPath: string, assetName: string): Promise<IAsset> {
throw new Error("Method not implemented.");
}
}
13 changes: 0 additions & 13 deletions src/react/components/common/tagInput/tagInput.scss
Original file line number Diff line number Diff line change
Expand Up @@ -36,19 +36,6 @@
&-container{
overflow-x: visible;
overflow-y: auto;
padding: 0 0 0 100px;
margin: 0 0 0 -100px;
&::before{
content: " ";
display: inline-block;
position: absolute;
top: 44px;
width: 50px;
height: calc(100% - 44px);
left: -55px;
margin-right: 4px;
background: linear-gradient(to right, #00000000 10%,#00000080 80%);
}
};
}

Expand Down
38 changes: 32 additions & 6 deletions src/react/components/common/tagInput/tagInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,10 @@ import React, {KeyboardEvent} from "react";
import {toast} from "react-toastify";
import {constants} from "../../../../common/constants";
import {interpolate, strings} from "../../../../common/strings";
import {getDarkTheme} from "../../../../common/themes";
import {getDarkTheme, getPrimaryRedTheme} from "../../../../common/themes";
import {getNextColor} from "../../../../common/utils";
import {FeatureCategory, FieldFormat, FieldType, ILabel, IRegion, ITag} from "../../../../models/applicationState";
import Confirm from "../../common/confirm/confirm";
import {AlignPortal} from "../align/alignPortal";
import {ColorPicker} from "../colorPicker";
import "../condensedList/condensedList.scss";
Expand Down Expand Up @@ -143,7 +144,7 @@ export class TagInput extends React.Component<ITagInputProps, ITagInputState> {
private tagItemRefs: Map<string, TagInputItem> = new Map<string, TagInputItem>();
private headerRef = React.createRef<HTMLDivElement>();
private inputRef = React.createRef<HTMLInputElement>();

private replaceConfirmRef = React.createRef<Confirm>();
public componentDidUpdate(prevProps: ITagInputProps) {
if (prevProps.tags !== this.props.tags) {
let selectedTag = this.state.selectedTag;
Expand Down Expand Up @@ -248,6 +249,13 @@ export class TagInput extends React.Component<ITagInputProps, ITagInputState> {
:
<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>
);
}
Expand Down Expand Up @@ -321,7 +329,7 @@ export class TagInput extends React.Component<ITagInputProps, ITagInputState> {
return;
}

const tags = [ ...this.state.tags, tag ];
const tags = [...this.state.tags, tag];
this.setState({
tags,
}, () => this.props.onChange(tags));
Expand Down Expand Up @@ -517,12 +525,14 @@ export class TagInput extends React.Component<ITagInputProps, ITagInputState> {
const labelAssigned = this.labelAssigned(labels, name);

if (labelAssigned && ((category === FeatureCategory.DrawnRegion) !== isTagLabelTypeDrawnRegion)) {
if (isTagLabelTypeDrawnRegion) {
toast.warn(interpolate(strings.tags.warnings.notCompatibleWithDrawnRegionTag, {otherCatagory: category}));
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}));
} else {
toast.warn(interpolate(strings.tags.warnings.notCompatibleWithDrawnRegionTag, {otherCatagory: FeatureCategory.Text}));
this.replaceConfirmRef.current.open(tag, props);
}
return;
} else if (tagCategory === category || category === FeatureCategory.DrawnRegion ||
Expand All @@ -531,6 +541,10 @@ export class TagInput extends React.Component<ITagInputProps, ITagInputState> {
toast.warn(strings.tags.warnings.checkboxPerTagLimit);
return;
}
if(tagCategory===FeatureCategory.Checkbox&&category!==FeatureCategory.Checkbox){
toast.warn(strings.tags.warnings.notCompatibleTagType);
return;
}
onTagClick(tag);
deselect = false;
} else {
Expand All @@ -543,6 +557,18 @@ export class TagInput extends React.Component<ITagInputProps, ITagInputState> {
});
}
}
private onReplaceConfirm = (tag: ITag, props: ITagClickProps) => {
const {onTagClick} = this.props;
const {selectedTag, tagOperation: oldTagOperation} = this.state;
const selected = selectedTag && isNameEqual(selectedTag.name, tag.name);
const tagOperation = selected ? oldTagOperation : TagOperationMode.None;
const deselect = selected && oldTagOperation === TagOperationMode.None;
onTagClick(tag);
this.setState({
selectedTag: deselect ? null : tag,
tagOperation,
});
}
focusTag(tag: string) {
const tagItemRef = this.tagItemRefs.get(tag)?.getTagNameRef();
if (tagItemRef) {
Expand Down
6 changes: 3 additions & 3 deletions src/react/components/common/tagInput/tagInputItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -218,7 +218,7 @@ export default class TagInputItem extends React.Component<ITagInputItemProps, IT
return this.props.labels.map((label, idx) =>
<Fragment key={idx}>
<div className="tag-item-label-container">
{(confidence||revised)&&
{(confidence||revised) &&
<div className="tag-item-label-container-item1">
{confidence &&
<div className="tag-item-confidence">
Expand All @@ -236,7 +236,7 @@ export default class TagInputItem extends React.Component<ITagInputItemProps, IT
label={label}
isOrigin={true}
value={label.originValue}
prefixText="Auto-labeled: "
prefixText={strings.tags.preText.autoLabel}
/>
}
<TagInputItemLabel
Expand All @@ -245,7 +245,7 @@ export default class TagInputItem extends React.Component<ITagInputItemProps, IT
isOrigin={false}
onLabelEnter={this.props.onLabelEnter}
onLabelLeave={this.props.onLabelLeave}
prefixText={revised?"Revised: ":undefined}
prefixText={revised ? strings.tags.preText.revised : undefined}
/>
</div>
</div>
Expand Down
Loading

0 comments on commit 757e0dd

Please sign in to comment.