Skip to content
This repository was archived by the owner on Apr 18, 2024. It is now read-only.

Commit 05b67ad

Browse files
hlomzikfarioas
andauthored
fix: LEAP-33: Fixes for labels by Taxonomy (#1555)
* fix: LEAP-33: Mimic labeling Taxonomy result as Label - `findLabel()` for starters to use in Label context - fix `selectedLabels` back to act the same for Labels and Taxonomy - fix styling detection * Attempt to respect `showFullPath` in New Taxonomy It should work, but it doesn't * Fix node type detection temporary fix, should find a proper fix and source of problem later * Fix labeling=true for usual taxonomy (no api) * Fix region removal upon deleting last label Last label couldn't be deleted now, the same as for Labels * Add color to Choice for labeling Works only on regions, taxonomy item has no color yet * Use colors in taxonomy as well * Simplify styles, make global; move to .styl * typo * Fix last item removal for old taxonomy as well * Allow to remove items when region is not selected * Also fix this last item for new taxonomy * Remove excess @todo * Reorder imports and remove unused one * Simplify Visibility checks That also should improve performance for huge Taxonomies * Currently revert old code and wrap new one with FF Final decision about using stored values everywhere wil s=come soon. For now stable bahaviour is always active with FF off. FF on has regress for now. --------- Co-authored-by: hlomzik <[email protected]> Co-authored-by: farioas <[email protected]>
1 parent c0b6489 commit 05b67ad

File tree

9 files changed

+100
-28
lines changed

9 files changed

+100
-28
lines changed
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
:global(.htx-taxonomy-item-color)
2+
padding 4px 4px
3+
border-radius 2px

src/components/NewTaxonomy/NewTaxonomy.tsx

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ import React, { useCallback, useEffect, useState } from 'react';
33

44
import { Tooltip } from '../../common/Tooltip/Tooltip';
55

6+
import './NewTaxonomy.styl';
7+
68
type TaxonomyPath = string[];
79
type onAddLabelCallback = (path: string[]) => any;
810
type onDeleteLabelCallback = (path: string[]) => any;
@@ -15,6 +17,7 @@ type TaxonomyItem = {
1517
children?: TaxonomyItem[],
1618
origin?: 'config' | 'user' | 'session',
1719
hint?: string,
20+
color?: string,
1821
};
1922

2023
type AntTaxonomyItem = {
@@ -57,17 +60,32 @@ const convert = (
5760
options: TaxonomyExtendedOptions,
5861
selectedPaths: string[],
5962
): AntTaxonomyItem[] => {
63+
// generate string or component to be the `title` of the item
64+
const enrich = (item: TaxonomyItem) => {
65+
const color = (item: TaxonomyItem) => (
66+
// no BEM here to make it more lightweight
67+
// global classname to allow to change it in Style tag
68+
<span className="htx-taxonomy-item-color" style={{ background: item.color }}>
69+
{item.label}
70+
</span>
71+
);
72+
73+
if (!item.hint) return item.color ? color(item) : item.label;
74+
75+
return (
76+
<Tooltip title={item.hint} mouseEnterDelay={500}>
77+
{item.color ? color(item) : <span>{item.label}</span>}
78+
</Tooltip>
79+
);
80+
};
81+
6082
const convertItem = (item: TaxonomyItem): AntTaxonomyItem => {
6183
const value = item.path.join(options.pathSeparator);
6284
const disabledNode = options.leafsOnly && (item.isLeaf === false || !!item.children);
6385
const maxUsagesReached = options.maxUsagesReached && !selectedPaths.includes(value);
6486

6587
return {
66-
title: item.hint ? (
67-
<Tooltip title={item.hint} mouseEnterDelay={500}>
68-
<span>{item.label}</span>
69-
</Tooltip>
70-
) : item.label,
88+
title: enrich(item),
7189
value,
7290
key: value,
7391
isLeaf: item.isLeaf !== false && !item.children,

src/components/Node/Node.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,9 @@ const NodeMinimal: FC<any> = observer(({ node }) => {
188188
});
189189

190190
const useNodeName = (node: any) => {
191+
// @todo sometimes node is control tag, not a region
192+
// @todo and for new taxonomy it can be plain object
193+
if (!node.$treenode) return null;
191194
return getType(node).name as keyof typeof NodeViews;
192195
};
193196

src/components/Taxonomy/Taxonomy.tsx

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,15 +9,15 @@ import React, {
99
} from 'react';
1010
import { Dropdown, Menu } from 'antd';
1111

12+
import { LsChevron } from '../../assets/icons';
13+
import { Tooltip } from '../../common/Tooltip/Tooltip';
1214
import { useToggle } from '../../hooks/useToggle';
15+
import { CNTagName } from '../../utils/bem';
16+
import { FF_DEV_4075, FF_PROD_309, isFF } from '../../utils/feature-flags';
1317
import { isArraysEqual } from '../../utils/utilities';
14-
import { LsChevron } from '../../assets/icons';
1518
import TreeStructure from '../TreeStructure/TreeStructure';
1619

1720
import styles from './Taxonomy.module.scss';
18-
import { FF_DEV_4075, FF_PROD_309, isFF } from '../../utils/feature-flags';
19-
import { Tooltip } from '../../common/Tooltip/Tooltip';
20-
import { CNTagName } from '../../utils/bem';
2121

2222
type TaxonomyPath = string[];
2323
type onAddLabelCallback = (path: string[]) => any;
@@ -33,6 +33,7 @@ type TaxonomyItem = {
3333
};
3434

3535
type TaxonomyOptions = {
36+
canRemoveItems?: boolean,
3637
leafsOnly?: boolean,
3738
showFullPath?: boolean,
3839
pathSeparator?: string,
@@ -504,6 +505,10 @@ const Taxonomy = ({
504505
const setSelected = (path: TaxonomyPath, value: boolean) => {
505506
const newSelected = value ? [...selected, path] : selected.filter(current => !isArraysEqual(current, path));
506507

508+
// don't remove last item when taxonomy is used as labeling tool
509+
// canRemoveItems is undefined when FF is off; false only when region is active
510+
if (options.canRemoveItems === false && !newSelected.length) return;
511+
507512
setInternalSelected(newSelected);
508513
onChange && onChange(null, newSelected);
509514
};

src/mixins/HighlightMixin.js

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -153,14 +153,8 @@ export const HighlightMixin = types
153153
updateSpans() {
154154
if (self._hasSpans || (isFF(FF_LSDV_4620_3) && self._spans?.length)) {
155155
const lastSpan = self._spans[self._spans.length - 1];
156-
const label = self.getLabels();
157156

158-
// label is array, string or null, so check for length
159-
if (!label?.length) {
160-
lastSpan.removeAttribute('data-label');
161-
} else {
162-
lastSpan.setAttribute('data-label', label);
163-
}
157+
Utils.Selection.applySpanStyles(lastSpan, { label: self.getLabels() });
164158
}
165159
},
166160

@@ -274,7 +268,7 @@ export const HighlightMixin = types
274268
},
275269

276270
getLabels() {
277-
return self.labeling?.mainValue ?? [];
271+
return (self.labeling?.selectedLabels ?? []).map(label => label.value).join(',');
278272
},
279273

280274
getLabelColor() {

src/mixins/SelectedChoiceMixin.js

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,19 @@ const SelectedChoiceMixin = types
2121

2222
return isDefined(choice1) && isDefined(choice2) && choice1 === choice2;
2323
},
24+
// @todo it's better to only take final values into account
25+
// @todo (meaning alias only, not alias + value when alias is present)
26+
// @todo so this should be the final and simpliest method
27+
hasChoiceSelectionSimple(choiceValue) {
28+
if (choiceValue?.length) {
29+
// grab the string value; for taxonomy, it's the last value in the array
30+
const selectedValues = self.selectedValues().map(s => Array.isArray(s) ? s.at(-1) : s);
31+
32+
return choiceValue.some(value => selectedValues.includes(value));
33+
}
34+
35+
return self.isSelected;
36+
},
2437
hasChoiceSelection(choiceValue, selectedValues = []) {
2538
if (choiceValue?.length) {
2639
// @todo Revisit this and make it more consistent, and refactor this

src/regions/Result.js

Lines changed: 5 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -140,19 +140,14 @@ const Result = types
140140
return self.mainValue?.join(joinstr) || '';
141141
},
142142

143+
// @todo check all usages of selectedLabels:
144+
// — check usages of non-array values (like `if selectedValues ...`)
145+
// - check empty labels, they should be returned as an array
143146
get selectedLabels() {
144-
if (self.type === 'taxonomy') {
145-
const sep = self.from_name.pathseparator;
146-
const join = self.from_name.showfullpath;
147-
148-
return (self.mainValue || [])
149-
.map(v => join ? v.join(sep) : v.at(-1))
150-
.map(v => ({ value: v, id: v }));
151-
}
152147
if (self.mainValue?.length === 0 && self.from_name.allowempty) {
153148
return self.from_name.findLabel(null);
154149
}
155-
return self.mainValue?.map(value => self.from_name.findLabel(value)).filter(Boolean);
150+
return self.mainValue?.map(value => self.from_name.findLabel(value)).filter(Boolean) ?? [];
156151
},
157152

158153
/**
@@ -212,7 +207,7 @@ const Result = types
212207

213208
get style() {
214209
if (!self.tag) return null;
215-
const fillcolor = self.tag.background || self.tag.parent.fillcolor;
210+
const fillcolor = self.tag.background || self.tag.parent?.fillcolor;
216211

217212
if (!fillcolor) return null;
218213
const strokecolor = self.tag.background || self.tag.parent.strokecolor;

src/tags/control/Choice.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,8 +44,9 @@ import { HintTooltip } from '../../components/Taxonomy/Taxonomy';
4444
* @param {string} [alias] - Alias for the choice. If used, the alias replaces the choice value in the annotation results. Alias does not display in the interface.
4545
* @param {style} [style] - CSS style of the checkbox element
4646
* @param {string} [hotkey] - Hotkey for the selection
47-
* @param {string} [html] - can be used to show enriched content[^FF_DEV_2007], it has higher priority than `value`, however `value` will be used in the exported result (should be properly escaped)
47+
* @param {string} [html] - Can be used to show enriched content[^FF_DEV_2007], it has higher priority than `value`, however `value` will be used in the exported result (should be properly escaped)
4848
* @param {string} [hint] - Hint for choice on hover[^FF_PROD_309]
49+
* @param {string} [color] - Color for Taxonomy item
4950
*/
5051
const TagAttrs = types.model({
5152
...(isFF(FF_DEV_3391) ? { id: types.identifier } : {}),
@@ -54,6 +55,7 @@ const TagAttrs = types.model({
5455
value: types.maybeNull(types.string),
5556
hotkey: types.maybeNull(types.string),
5657
style: types.maybeNull(types.string),
58+
color: types.maybeNull(types.string),
5759
...(isFF(FF_DEV_2007) ? { html: types.maybeNull(types.string) } : {}),
5860
...(isFF(FF_PROD_309) ? { hint: types.maybeNull(types.string) } : {}),
5961
});

src/tags/control/Taxonomy/Taxonomy.js

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,7 @@ function traverse(root) {
112112
const depth = parents.length;
113113
const obj = { label, path, depth, hint };
114114

115+
if (node.color) obj.color = node.color;
115116
if (node.children) {
116117
obj.children = visitUnique(node.children, path);
117118
}
@@ -150,6 +151,10 @@ const TaxonomyLabelingResult = types
150151

151152
return self.annotation.results.find(r => r.from_name === self && r.area === area);
152153
},
154+
get canRemoveItems() {
155+
if (!self.isLabeling) return true;
156+
return !self.result;
157+
},
153158
}))
154159
.actions(self => {
155160
const Super = {
@@ -163,6 +168,35 @@ const TaxonomyLabelingResult = types
163168
self.result.area.setValue(self);
164169
}
165170
},
171+
172+
/**
173+
* @param {string[]} path saved value from Taxonomy
174+
* @returns quazi-label object to act as Label in most places
175+
*/
176+
findLabel(path) {
177+
let title = '';
178+
let items = self.items;
179+
let item;
180+
181+
for (const value of path) {
182+
item = items?.find(item => item.path.at(-1) === value);
183+
184+
if (!item) return null;
185+
186+
items = item.children;
187+
title = self.showfullpath && title ? title + self.pathseparator + item.label : item.label;
188+
}
189+
190+
const label = { value: title, id: path.join(self.pathseparator) };
191+
192+
if (item.color) {
193+
// to conform the current format of our Result#style (and it requires parent)
194+
label.background = item.color;
195+
label.parent = {};
196+
}
197+
198+
return label;
199+
},
166200
};
167201
});
168202

@@ -411,6 +445,10 @@ const Model = types
411445
},
412446

413447
onChange(_node, checked) {
448+
// don't remove last label from region if region is selected (so canRemoveItems is false)
449+
// should be checked only for Taxonomy as labbeling tool
450+
if (self.canRemoveItems === false && !checked.length) return;
451+
414452
self.selected = checked.map(s => s.path ?? s);
415453
self.maxUsagesReached = self.selected.length >= self.maxusages;
416454
self.updateResult();
@@ -507,6 +545,7 @@ const HtxTaxonomy = observer(({ item }) => {
507545
minWidth: item.minwidth,
508546
dropdownWidth: item.dropdownwidth,
509547
placeholder: item.placeholder,
548+
canRemoveItems: item.canRemoveItems,
510549
};
511550

512551
return (

0 commit comments

Comments
 (0)