Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat: Add remove all selections option for ui.text_annotator #1563

Merged
merged 3 commits into from
Aug 1, 2022
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 19 additions & 12 deletions ui/src/text_annotator.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,14 @@ describe('TextAnnotator.tsx', () => {
it('Sets correct args on remove', () => {
const { container } = render(<XTextAnnotator model={annotatorProps} />)

fireEvent.mouseUp(container.querySelector('i')!)
fireEvent.mouseUp(container.querySelector('[data-test="remove-icon"]')!)
expect(wave.args[name]).toMatchObject([{ text: 'Hello there! Pretty good day' }])
})

it('Sets correct args on remove all', () => {
const { getByRole } = render(<XTextAnnotator model={annotatorProps} />)

fireEvent.click(getByRole('menuitem'))
expect(wave.args[name]).toMatchObject([{ text: 'Hello there! Pretty good day' }])
})

Expand Down Expand Up @@ -164,7 +171,7 @@ describe('TextAnnotator.tsx', () => {
it('Calls sync on remove if trigger specified', () => {
const { container } = render(<XTextAnnotator model={{ ...annotatorProps, trigger: true }} />)

fireEvent.mouseUp(container.querySelector('i')!)
fireEvent.mouseUp(container.querySelector('[data-test="remove-icon"]')!)
expect(pushMock).toHaveBeenCalled()
})

Expand All @@ -181,13 +188,13 @@ describe('TextAnnotator.tsx', () => {
it('Shows remove icon on hover', () => {
const { container, getByText } = render(<XTextAnnotator model={annotatorProps} />)

expect(container.querySelector('i')).not.toBeVisible()
expect(container.querySelector('[data-test="remove-icon"]')!).not.toBeVisible()
fireEvent.mouseOver(getByText('g'))
expect(container.querySelector('i')).toBeVisible()
expect(container.querySelector('[data-test="remove-icon"]')!).toBeVisible()
fireEvent.mouseOut(getByText('g'))
expect(container.querySelector('i')).not.toBeVisible()
expect(container.querySelector('[data-test="remove-icon"]')!).not.toBeVisible()
fireEvent.mouseOver(getByText('P'))
expect(container.querySelector('i')).toBeVisible()
expect(container.querySelector('[data-test="remove-icon"]')!).toBeVisible()
})

describe('readonly', () => {
Expand All @@ -200,9 +207,9 @@ describe('TextAnnotator.tsx', () => {

describe('smart selection off', () => {
it('Sets correct args on annotate from left to right', () => {
const { getByText, getByRole } = render(<XTextAnnotator model={annotatorProps} />)
const { getByText } = render(<XTextAnnotator model={annotatorProps} />)

fireEvent.click(getByRole('switch'))
fireEvent.click(getByText('Smart selection'))
fireEvent.click(getByText('Tag 1'))
fireEvent.mouseDown(getByText('H'))
fireEvent.mouseUp(getByText('h'))
Expand All @@ -216,9 +223,9 @@ describe('TextAnnotator.tsx', () => {
})

it('Sets correct args on annotate from right to left', () => {
const { getByText, getByRole } = render(<XTextAnnotator model={annotatorProps} />)
const { getByText } = render(<XTextAnnotator model={annotatorProps} />)

fireEvent.click(getByRole('switch'))
fireEvent.click(getByText('Smart selection'))
fireEvent.click(getByText('Tag 1'))
fireEvent.mouseDown(getByText('h'))
fireEvent.mouseUp(getByText('H'))
Expand All @@ -232,10 +239,10 @@ describe('TextAnnotator.tsx', () => {
})

it('Sets correct args on annotate when replacing selection', () => {
const { getByText, getByRole } = render(<XTextAnnotator model={annotatorProps} />)
const { getByText } = render(<XTextAnnotator model={annotatorProps} />)

fireEvent.click(getByText('Tag 2'))
fireEvent.click(getByRole('switch'))
fireEvent.click(getByText('Smart selection'))
fireEvent.mouseDown(getByText('P'))
fireEvent.mouseUp(getByText('g'))

Expand Down
45 changes: 34 additions & 11 deletions ui/src/text_annotator.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,15 @@ const
top: -3,
background: cssVar('$card'),
},
readonly: { pointerEvents: 'none' }
readonly: { pointerEvents: 'none' },
checkbox: {
$nest: {
'&:hover': {
backgroundColor: cssVar('$neutralLighter'),
color: cssVar('$neutralDark')
}
marek-mihok marked this conversation as resolved.
Show resolved Hide resolved
}
}
}),
annotateNumbersAroundToken = (tokenIdx: U, tokens: TokenProps[], activeTag?: S) => {
let charRightIdx = tokenIdx + 1, charLeftIdx = tokenIdx - 1
Expand Down Expand Up @@ -202,6 +210,11 @@ export
setTokens([...tokens])
submitWaveArgs()
},
removeAllAnnotations = () => {
for (let idx = 0; idx < tokens.length; idx++) tokens[idx].tag = undefined
marek-mihok marked this conversation as resolved.
Show resolved Hide resolved
setTokens([...tokens])
submitWaveArgs()
},
annotate = (endElProps: TokenMouseEventProps) => {
if (!startElPropsRef.current) return
const
Expand Down Expand Up @@ -263,7 +276,7 @@ export
className={clas(css.mark, isFirst ? css.firstMark : '', isLast ? css.lastMark : '')}
style={{ backgroundColor: cssVar(color), color: getContrast(color) }}>
{text}
<Fluent.Icon iconName='CircleAdditionSolid' styles={{ root: removeIconStyle }} className={clas(css.removeIcon, 'wave-w6')} onMouseUp={removeAnnotation(idx)} />
<Fluent.Icon iconName='CircleAdditionSolid' data-test={'remove-icon'} styles={{ root: removeIconStyle }} className={clas(css.removeIcon, 'wave-w6')} onMouseUp={removeAnnotation(idx)} />
marek-mihok marked this conversation as resolved.
Show resolved Hide resolved
{/* HACK: Put color underlay under remove icon because its glyph is transparent and doesn't look good on tags. */}
<span style={removeIconStyle as React.CSSProperties} className={css.iconUnderlay}></span>
</mark>
Expand All @@ -283,15 +296,25 @@ export
<div data-test={name} className={readonly ? css.readonly : ''}>
<div className={clas('wave-s16 wave-w6', css.title)}>{title}</div>
<AnnotatorTags tags={tags} activateTag={activateTag} activeTag={activeTag} />
<Fluent.Toggle
data-test='smart-selection'
label='Smart selection'
defaultChecked={smartSelection}
onChange={() => setSmartSelection(prevSelection => !prevSelection)}
onText="On"
offText="Off"
inlineLabel
/>
<div style={{ display: 'flex' }}>
<Fluent.Checkbox
label='Smart selection'
checked={smartSelection}
onChange={() => setSmartSelection(prevSelection => !prevSelection)}
className={css.checkbox}
styles={{ root: { alignItems: 'center', paddingLeft: 8, paddingRight: 8 } }}
/>
<Fluent.CommandBar styles={{ root: { padding: 0 } }} items={[
{
key: 'remove-all',
text: 'Remove all selections',
onClick: removeAllAnnotations,
disabled: !tokens.find(token => token.tag),
iconProps: { iconName: 'DependencyRemove', styles: { root: { fontSize: 20 } } },
},
]}
/>
</div>
<div className={clas(css.content, 'wave-s16 wave-t7 wave-w3')}>{
tokens.map(({ start, end, text, tag }, idx) => {
const activeColor = activeTag ? tagColorMap.get(activeTag) : undefined
Expand Down
Binary file modified website/blog/assets/2021-11-05/text-annotator.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified website/docs/examples/assets/text-annotator.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.