diff --git a/web/libs/editor/src/components/SidePanels/OutlinerPanel/OutlinerTree.tsx b/web/libs/editor/src/components/SidePanels/OutlinerPanel/OutlinerTree.tsx index cdb84f5498d8..223003633104 100644 --- a/web/libs/editor/src/components/SidePanels/OutlinerPanel/OutlinerTree.tsx +++ b/web/libs/editor/src/components/SidePanels/OutlinerPanel/OutlinerTree.tsx @@ -238,6 +238,14 @@ const useDataTree = ({ regions, rootClass, footer }: any) => { } })(); + // The only source of truth for region indices is here, where they are coming from different + // RegionStore methods and just rendered a second later; so we store them in a region + // to render in other places as well, so indices will be consistent across the app. + // Also `item` here can be a tool or a label when we use groupping, so only add idx to regions. + // It can even be undefined for group titles in Labels mode. + // Later in this file we render (idx + 1), so we will set it as (idx + 1) to incapsulate this logic. + item?.setRegionIndex?.(idx + 1); + return { idx, key: id, diff --git a/web/libs/editor/src/mixins/AreaMixin.js b/web/libs/editor/src/mixins/AreaMixin.js index 4745147f7312..7e5f4554ab86 100644 --- a/web/libs/editor/src/mixins/AreaMixin.js +++ b/web/libs/editor/src/mixins/AreaMixin.js @@ -92,12 +92,15 @@ export const AreaMixinBase = types return Array.from(self.labeling?.mainValue ?? []); }, + // used only in labels on regions for Image and Video tags getLabelText(joinstr) { + const index = self.region_index; const label = self.labeling; const text = self.texting?.mainValue?.[0]?.replace(/\n\r|\n/, " "); const labelNames = label?.getSelectedString(joinstr); const labelText = []; + if (index) labelText.push(String(index)); if (labelNames) labelText.push(labelNames); if (text) labelText.push(text); return labelText.join(": "); @@ -155,9 +158,17 @@ export const AreaMixinBase = types }, })) .volatile(() => ({ - // selected: false, + // index of the region in the regions tree (Outliner); will be updated on any order change + region_index: null, })) .actions((self) => ({ + setRegionIndex(index) { + if (self.region_index !== index) { + self.region_index = index; + // update text regions + self.updateAppearenceFromState?.(); + } + }, beforeDestroy() { self.results.forEach((r) => destroy(r)); }, diff --git a/web/libs/editor/src/mixins/HighlightMixin.js b/web/libs/editor/src/mixins/HighlightMixin.js index d51c24de0cb3..a2e046162484 100644 --- a/web/libs/editor/src/mixins/HighlightMixin.js +++ b/web/libs/editor/src/mixins/HighlightMixin.js @@ -272,7 +272,10 @@ export const HighlightMixin = types }, getLabels() { - return (self.labeling?.selectedLabels ?? []).map((label) => label.value).join(","); + const index = self.region_index; + const text = (self.labeling?.selectedLabels ?? []).map((label) => label.value).join(","); + + return [index, text].filter(Boolean).join(":"); }, getLabelColor() { diff --git a/web/libs/editor/src/mixins/SpanText.js b/web/libs/editor/src/mixins/SpanText.js index 35f013be3d5b..2f2e1ecb8563 100644 --- a/web/libs/editor/src/mixins/SpanText.js +++ b/web/libs/editor/src/mixins/SpanText.js @@ -66,6 +66,7 @@ export default types // @todo multilabeling with different labels? const names = self.labeling?.mainValue; const cssCls = Utils.HTML.labelWithCSS(lastSpan, { + index: self.region_index, labels: names, score: self.score, }); diff --git a/web/libs/editor/src/utils/html.js b/web/libs/editor/src/utils/html.js index aaf02d2fdaa7..96c26602bd38 100644 --- a/web/libs/editor/src/utils/html.js +++ b/web/libs/editor/src/utils/html.js @@ -26,9 +26,10 @@ function toggleLabelsAndScores(show) { const labelWithCSS = (() => { const cache = {}; - return (node, { labels, score }) => { + return (node, { index, labels, score }) => { const labelsStr = labels ? labels.join(",") : ""; - const clsName = Checkers.hashCode(labelsStr + score); + const labelText = [index, labelsStr].filter(Boolean).join(":"); + const clsName = Checkers.hashCode(labelText + score); let cssCls = `htx-label-${clsName}`; @@ -38,7 +39,7 @@ const labelWithCSS = (() => { node.setAttribute("data-labels", labelsStr); - const resSVG = Canvas.labelToSVG({ label: labelsStr, score }); + const resSVG = Canvas.labelToSVG({ label: labelText, score }); const svgURL = `url(${resSVG})`; createClass(`.${cssCls}:after`, `content:${svgURL}`); diff --git a/web/libs/editor/tests/e2e/tests/rich-text/rich-text-regions-displaying.test.js b/web/libs/editor/tests/e2e/tests/rich-text/rich-text-regions-displaying.test.js index 2bbfc0da2696..f37ec499622c 100644 --- a/web/libs/editor/tests/e2e/tests/rich-text/rich-text-regions-displaying.test.js +++ b/web/libs/editor/tests/e2e/tests/rich-text/rich-text-regions-displaying.test.js @@ -287,8 +287,8 @@ Scenario("Displaying label in the region", async ({ I, LabelStudio, AtOutliner, LabelStudio.waitForObjectsReady(); AtOutliner.seeRegions(2); - await checkLabelVisibility(locate(".htx-highlight").at(1), '"Region"', true); - await checkLabelVisibility(locate(".htx-highlight").at(2), "none", true); + await checkLabelVisibility(locate(".htx-highlight").at(1), '"1:Region"', true); + await checkLabelVisibility(locate(".htx-highlight").at(2), '"2"', true); I.say("Hide labels in settings"); AtSettings.open(); @@ -297,6 +297,6 @@ Scenario("Displaying label in the region", async ({ I, LabelStudio, AtOutliner, }); AtSettings.close(); I.say("Make sure that label is hidden"); - await checkLabelVisibility(locate(".htx-highlight").at(1), '"Region"', false); - await checkLabelVisibility(locate(".htx-highlight").at(2), "none", false); + await checkLabelVisibility(locate(".htx-highlight").at(1), '"1:Region"', false); + await checkLabelVisibility(locate(".htx-highlight").at(2), '"2"', false); });