Skip to content

Commit 0e6d6c5

Browse files
feat: tooltip overlap pdf/text view (#533)
* feat: text active color and PDF highlight/active colors * feat: support active and highlight * fix: tweak merge * fix: pr review * feat: pdf, single tooltip * feat: pdf single tooltip * fix: pdf active color, css !important * fix: rename css classes and other pr feedback * fix: single line tooltip * feat: add tooltip for text. Next, update tooltip content * feat: tooltip for text view * fix: remove debug msg and clean styles * fix: refactor TooltipHighlight * fix: do not display empty tooltips or duplicate tooltip content * fix: update text highlight style * fix: pr feedback * fix: tooltip content with table to match design * fix: remove sample data for development * fix: pr feedback * fix: move static text to messages * fix: text view, rename TooltipAction to TooltipShow * fix: pr feedback * feat: for tooltip, ellipsis in the middle of long text * fix: pr feedback * fix: remove test data * fix: add testid for cypress test * feat: take-in overlapInfoMap and pass to pdf and text views * fix: add Keyword, create style, move MAX_CONTENT_LENGTH * fix: hide tooltip sample data * fix: keyword case-insensitive * feat: start support multiple mentions (ie overlap) in tooltip * feat: introduce OverlapMeta * fix: update style for active enrichment in overlap * fix: active enrichment in overlap, show tooltip for overlap * feat: track overlap for text view * fix: make a unique ID for text enrichments (work in progress) * feat: unique id for enrichments in text view, remove debug * fix: merge * fix: unit tests with new enrichment id * fix: some tests in CIDocument.spec.tsx by adding type * Revert "fix: some tests in CIDocument.spec.tsx by adding type" This reverts commit 4e43f5d. * fix: getId for Section with and without facets * feat: add const OVERLAP_ID and function initOverlapMeta * fix: fail gracefully if facetId is invalid * fix: align color swatch in tooltip * fix: add comments based on pr feedback (and one rename) * fix: tweak text z-index for overlap * fix: refactor styles for pdf highlight * fix: pr feedback * feat: on active enrichment, tooltip displays overlap info * fix: pr feedback --------- Co-authored-by: DORIAN MILLER <millerbd@us.ibm.com>
1 parent 9bf55b6 commit 0e6d6c5

23 files changed

Lines changed: 308 additions & 124 deletions

File tree

packages/discovery-react-components/src/components/CIDocument/components/CIDocumentContent/CIDocumentContent.tsx

Lines changed: 45 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,8 @@ import { SkeletonText } from 'carbon-components-react';
1010
import Section, { OnFieldClickFn } from '../Section/Section';
1111
import VirtualScroll from '../VirtualScroll/VirtualScroll';
1212
import { defaultTheme, Theme } from 'utils/theme';
13-
import { SectionType, ItemMap, HighlightWithMeta } from 'components/CIDocument/types';
14-
import { FacetInfoMap } from '../../../DocumentPreview/types';
13+
import { SectionType, ItemMap, TextHighlightWithMeta } from 'components/CIDocument/types';
14+
import { FacetInfoMap, initOverlapMeta, OverlapMeta } from '../../../DocumentPreview/types';
1515
import { getId as getLocationId } from 'utils/document/idUtils';
1616

1717
const baseClassName = `${settings.prefix}--ci-doc-content`;
@@ -31,11 +31,19 @@ export interface CIDocumentContentProps {
3131
theme?: Theme;
3232
documentId?: string;
3333
onItemClick?: OnFieldClickFn;
34-
combinedHighlights?: HighlightWithMeta[];
34+
combinedHighlights?: TextHighlightWithMeta[];
3535
facetInfoMap?: FacetInfoMap;
36+
overlapMeta?: OverlapMeta;
3637
activeColor?: string | null;
3738
}
3839

40+
// Explicit control of elements to accomplish mouse interaction with overlap
41+
// Enrichment with highest z-index is content of overlap tooltip
42+
// Fields are transparent, now on-top of text, makes debug in browser inspect easier.
43+
const ZINDEX_BASE = 10;
44+
const ZINDEX_OVERLAP = 20;
45+
const ZINDEX_ACTIVE = 30;
46+
3947
const CIDocumentContent: FC<CIDocumentContentProps> = ({
4048
className,
4149
sections = [],
@@ -53,6 +61,7 @@ const CIDocumentContent: FC<CIDocumentContentProps> = ({
5361
onItemClick = (): void => {},
5462
combinedHighlights,
5563
facetInfoMap,
64+
overlapMeta = initOverlapMeta(),
5665
activeColor
5766
}) => {
5867
const virtualScrollRef = useRef<any>();
@@ -83,19 +92,20 @@ const CIDocumentContent: FC<CIDocumentContentProps> = ({
8392
<style>
8493
{createStyleRules(highlightedIds, [
8594
backgroundColorRule(theme.highlightBackground),
86-
// Set z-index to -1 in order to push non-active fields back
87-
zIndexRule(-1)
95+
zIndexRule(ZINDEX_BASE)
8896
])}
8997
</style>
9098
)}
9199
{activeIds && activeIds.length > 0 && (
92100
<>
93101
<style>
94-
{/*Set z-index to 0 to pull active element in front of overlapping fields */}
102+
{/*Set z-index to pull active element in front of overlapping fields */}
95103
{createStyleRules(activeIds, [
96104
backgroundColorRule(theme.activeHighlightBackground),
97105
outlineRule(activeColor || theme.highlightBackground),
98-
zIndexRule(0)
106+
zIndexRule(ZINDEX_ACTIVE),
107+
activeEnrichmentInOverlapRule(activeIds, overlapMeta),
108+
opacityRule(100)
99109
])}
100110
</style>
101111
</>
@@ -132,6 +142,7 @@ const CIDocumentContent: FC<CIDocumentContentProps> = ({
132142
section={sections[index]}
133143
onFieldClick={onItemClick}
134144
facetInfoMap={facetInfoMap}
145+
overlapMeta={overlapMeta}
135146
/>
136147
)}
137148
</VirtualScroll>
@@ -143,16 +154,21 @@ const CIDocumentContent: FC<CIDocumentContentProps> = ({
143154
};
144155

145156
function createStyleRules(idList: string[], rules: string[]): string {
157+
// remove empty strings
146158
return idList
159+
.filter(item => item.localeCompare('') !== 0)
147160
.map(id => `.${baseClassName} .field[data-field-id="${id}"] > *`)
148161
.join(',')
149162
.concat(`{${rules.join(';')}}`);
150163
}
151164

152-
function highlightColoringFullArray(combinedHighlightsWithMeta: HighlightWithMeta[]) {
153-
return combinedHighlightsWithMeta.map(highlightWithMeta => {
154-
const locationId = getHighlightLocationId(highlightWithMeta);
155-
const rules = `.${baseClassName} .field[data-field-id="${locationId}"] > * {background-color: ${highlightWithMeta.color}; border: 2px solid ${highlightWithMeta.color};}`;
165+
function highlightColoringFullArray(combinedHighlightsWithMeta: TextHighlightWithMeta[]) {
166+
return combinedHighlightsWithMeta.map(textHighlightWithMeta => {
167+
const locationId = getHighlightLocationId(textHighlightWithMeta);
168+
const zIndexValue = textHighlightWithMeta.isOverlap ? ZINDEX_OVERLAP : ZINDEX_BASE;
169+
const rules = `.${baseClassName} .field[data-field-id="${locationId}"] > * {background-color: ${
170+
textHighlightWithMeta.color
171+
}; border: 2px solid ${textHighlightWithMeta.color}; ${zIndexRule(zIndexValue)};}`;
156172
return rules;
157173
});
158174
}
@@ -165,6 +181,20 @@ function zIndexRule(value: number): string {
165181
return `z-index: ${value}`;
166182
}
167183

184+
function activeEnrichmentInOverlapRule(activeIds: string[], overlapMeta: OverlapMeta): string {
185+
// Case: Enrichment in overlap, tooltip should show the overlap info.
186+
// Enrichment element is on top to visually draw the border, but the mouse event must pass through.
187+
if (activeIds.length > 0 && overlapMeta.fieldIdWithOverlap.has(activeIds[0])) {
188+
return 'pointer-events: none';
189+
} else {
190+
return '';
191+
}
192+
}
193+
194+
function opacityRule(value: number): string {
195+
return `opacity: ${value}`;
196+
}
197+
168198
function outlineRule(color: string): string {
169199
return `border: ${color} solid 2px`;
170200
}
@@ -184,11 +214,12 @@ function scrollToActiveItem(
184214
);
185215
}
186216

187-
function getHighlightLocationId(highlightWithMeta: HighlightWithMeta): string {
217+
function getHighlightLocationId(textHighlightWithMeta: TextHighlightWithMeta): string {
188218
return getLocationId({
219+
facetId: textHighlightWithMeta.facetId,
189220
location: {
190-
begin: highlightWithMeta.begin,
191-
end: highlightWithMeta.end
221+
begin: textHighlightWithMeta.begin,
222+
end: textHighlightWithMeta.end
192223
}
193224
});
194225
}

packages/discovery-react-components/src/components/CIDocument/components/MetadataPane/__tests__/MetadataPane.spec.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ describe('<MetadataPane />', () => {
4545
text_normalized: '2008-12-22'
4646
}
4747
],
48-
metadataId: '2533_2549',
48+
metadataId: 'DEFAULT_F_2533_2549',
4949
metadataType: 'effective_dates'
5050
});
5151
});

packages/discovery-react-components/src/components/CIDocument/components/Section/Section.tsx

Lines changed: 47 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ import { createFieldRects, findOffsetInDOM } from 'utils/document/documentUtils'
2222
import { clearNodeChildren } from 'utils/dom';
2323
import elementFromPoint from 'components/CIDocument/utils/elementFromPoint';
2424
import { SectionType, Field, Item } from 'components/CIDocument/types';
25-
import { FacetInfoMap } from '../../../DocumentPreview/types';
25+
import { FacetInfoMap, initOverlapMeta, OverlapMeta } from '../../../DocumentPreview/types';
2626
import { TooltipAction, TooltipEvent, OnTooltipShowFn } from '../../../TooltipHighlight/types';
2727
import { TooltipHighlight, calcToolTipContent } from '../../../TooltipHighlight/TooltipHighlight';
2828

@@ -31,22 +31,22 @@ export type OnFieldClickFn = (field: Field) => void;
3131
const baseClassName = `${settings.prefix}--ci-doc-section`;
3232

3333
interface SectionProps {
34-
/**
35-
* Section to display in this component
36-
*/
34+
// Section to display in this component
3735
section: SectionType;
38-
/**
39-
* Function to call when a field is clicked
40-
*/
36+
// Function to call when a field is clicked
4137
onFieldClick?: OnFieldClickFn;
42-
43-
/**
44-
* Meta-data on facets
45-
*/
38+
// Meta-data on facets
4639
facetInfoMap?: FacetInfoMap;
40+
// Overlap information used by tooltip
41+
overlapMeta?: OverlapMeta;
4742
}
4843

49-
export const Section: FC<SectionProps> = ({ section, onFieldClick, facetInfoMap = {} }) => {
44+
export const Section: FC<SectionProps> = ({
45+
section,
46+
onFieldClick,
47+
facetInfoMap = {},
48+
overlapMeta = initOverlapMeta()
49+
}) => {
5050
const { html } = section;
5151

5252
const [hoveredField, setHoveredField] = useState<HTMLElement | null>(null);
@@ -57,7 +57,13 @@ export const Section: FC<SectionProps> = ({ section, onFieldClick, facetInfoMap
5757

5858
const createSectionFields = (): void => {
5959
try {
60-
renderSectionFields(section, sectionNode.current, contentNode.current, fieldsNode.current);
60+
renderSectionFields(
61+
section,
62+
sectionNode.current,
63+
contentNode.current,
64+
fieldsNode.current,
65+
facetInfoMap
66+
);
6167
} catch (err) {
6268
// eslint-disable-next-line no-console
6369
console.error('Failed to create section fields:', err);
@@ -100,7 +106,13 @@ export const Section: FC<SectionProps> = ({ section, onFieldClick, facetInfoMap
100106
<div
101107
className={cx(`${baseClassName}`, { hasTable: hasTable(html) })}
102108
ref={sectionNode}
103-
onMouseMove={mouseMoveListener(hoveredField, setHoveredField, onTooltipAction, facetInfoMap)}
109+
onMouseMove={mouseMoveListener(
110+
hoveredField,
111+
setHoveredField,
112+
onTooltipAction,
113+
facetInfoMap,
114+
overlapMeta
115+
)}
104116
onMouseLeave={mouseLeaveListener(hoveredField, setHoveredField, onTooltipAction)}
105117
onClick={mouseClickListener(onFieldClick)}
106118
>
@@ -120,7 +132,8 @@ function mouseMoveListener(
120132
hoveredField: HTMLElement | null,
121133
setHoveredField: Dispatch<SetStateAction<HTMLElement | null>>,
122134
onTooltipShow: OnTooltipShowFn,
123-
facetInfoMap: FacetInfoMap
135+
facetInfoMap: FacetInfoMap,
136+
overlapMeta: OverlapMeta
124137
) {
125138
return function _mouseMoveListener(event: MouseEvent): void {
126139
const fieldRect = elementFromPoint(
@@ -149,8 +162,16 @@ function mouseMoveListener(
149162
if (fieldNode) {
150163
fieldNode.classList.add('hover');
151164
const enrichValue = fieldNode.getAttribute('data-field-value') || '';
165+
// In the case of overlap, data-field-value is used for Overlap ID
166+
const enrichFieldId = fieldNode.getAttribute('data-field-value') || '';
152167
const enrichFacetId = fieldNode.getAttribute('data-field-type') || '';
153-
const tooltipContent = calcToolTipContent(facetInfoMap, enrichFacetId, enrichValue);
168+
const tooltipContent = calcToolTipContent(
169+
facetInfoMap,
170+
overlapMeta,
171+
enrichFacetId,
172+
enrichValue,
173+
enrichFieldId
174+
);
154175
const fieldNodeContent = fieldNode?.firstElementChild;
155176
onTooltipShow({
156177
...{
@@ -209,7 +230,8 @@ function renderSectionFields(
209230
section: SectionType,
210231
sectionNode: HTMLElement | null,
211232
contentNode: HTMLElement | null,
212-
fieldsNode: HTMLElement | null
233+
fieldsNode: HTMLElement | null,
234+
facetInfoMap: FacetInfoMap
213235
): void {
214236
if (!sectionNode || !contentNode || !fieldsNode) {
215237
return;
@@ -243,12 +265,19 @@ function renderSectionFields(
243265

244266
const offsets = findOffsetInDOM(contentNode, begin, end);
245267

268+
// Field ID
269+
// 1. W/O Facets: regular ID
270+
// 2: With Facets: __type is same as facetId and is included in ID to distiguish overlap
271+
// Note: FacetID defaults to {}, otherwise facets enabled
272+
const fieldForId =
273+
Object.keys(facetInfoMap).length === 0 ? field : { ...field, facetId: fieldType };
274+
246275
createFieldRects({
247276
fragment,
248277
parentRect: sectionRect as DOMRect,
249278
fieldType,
250279
fieldValue,
251-
fieldId: getId(field as unknown as Item),
280+
fieldId: getId(fieldForId as unknown as Item),
252281
...offsets
253282
});
254283
} catch (err) {

packages/discovery-react-components/src/components/CIDocument/types.ts

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,13 @@ export interface Location {
1616
}
1717

1818
// TODO better name
19+
// Some code-flow to Item use either __type or facetId
20+
// facetId: Used when facets with colors are defined
21+
// __type: Used when raw data from discovery query is processed
1922
export interface Item {
2023
id?: string;
24+
__type?: string;
25+
facetId?: string;
2126
location: Location;
2227
}
2328

@@ -109,10 +114,10 @@ export type HighlightFacetMentions = {
109114
className?: string;
110115
};
111116

112-
export type HighlightWithMeta = {
113-
facetIds: string[];
114-
mentions: HighlightFacetMentions[];
117+
export type TextHighlightWithMeta = {
115118
begin: number;
116119
end: number;
120+
facetId: string;
117121
color: string;
122+
isOverlap: boolean;
118123
};

packages/discovery-react-components/src/components/DocumentPreview/components/Highlight/__tests__/tables.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { QueryResultPassage, QueryTableResult } from 'ibm-watson/discovery/v2';
22
import { ProcessedDoc } from 'utils/document';
3-
import { Location } from 'utils/document/processDoc';
3+
import { Location } from '../../../types';
44
import { getHighlightedTable } from '../tables';
55
import { isTable } from '../typeUtils';
66

packages/discovery-react-components/src/components/DocumentPreview/components/HtmlView/HtmlView.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,14 @@ import { QueryResult, QueryResultPassage, QueryTableResult } from 'ibm-watson/di
1111
import DOMPurify from 'dompurify';
1212
import get from 'lodash/get';
1313
import flatMap from 'lodash/flatMap';
14-
import { processDoc, ProcessedDoc, ProcessedBbox, Location } from 'utils/document/processDoc';
14+
import { processDoc, ProcessedDoc, ProcessedBbox } from 'utils/document/processDoc';
1515
import { findMatchingBbox } from 'components/DocumentPreview/utils/box';
1616
import { findOffsetInDOM, createFieldRects } from 'utils/document/documentUtils';
1717
import { clearNodeChildren } from 'utils/dom';
1818
import { getTextMappings } from 'components/DocumentPreview/utils/documentData';
1919
import { getPassagePageInfo } from '../Highlight/passages';
2020
import { isPassage } from '../Highlight/typeUtils';
21-
import { QueryResultWithOptionalMetadata } from 'components/DocumentPreview/types';
21+
import { QueryResultWithOptionalMetadata, Location } from 'components/DocumentPreview/types';
2222

2323
interface Props extends HTMLAttributes<HTMLElement> {
2424
/**

0 commit comments

Comments
 (0)