From c44657c09708f96d3063a8bca45a05bd21ffc479 Mon Sep 17 00:00:00 2001 From: Tim Fischbach Date: Tue, 30 Jul 2024 16:13:00 +0200 Subject: [PATCH 1/9] Use entry locale for default hotspot tooltip link text REDMINE-20673 --- .../scrolled/package/src/contentElements/hotspots/Tooltip.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/entry_types/scrolled/package/src/contentElements/hotspots/Tooltip.js b/entry_types/scrolled/package/src/contentElements/hotspots/Tooltip.js index 9972bc575..765b12d8b 100644 --- a/entry_types/scrolled/package/src/contentElements/hotspots/Tooltip.js +++ b/entry_types/scrolled/package/src/contentElements/hotspots/Tooltip.js @@ -36,6 +36,7 @@ export function Tooltip({ panZoomEnabled, imageFile, containerRect, keepInViewport, floatingStrategy, onMouseEnter, onMouseLeave, onClick, onDismiss, }) { + const {t: translateWithEntryLocale} = useI18n(); const {t} = useI18n({locale: 'ui'}); const updateConfiguration = useContentElementConfigurationUpdate(); const {isEditable} = useContentElementEditorState(); @@ -107,7 +108,7 @@ export function Tooltip({ if (utils.isBlankEditableTextValue(tooltipTexts[area.id]?.link)) { handleTextChange('link', [{ type: 'heading', - children: [{text: t('pageflow_scrolled.public.more')}] + children: [{text: translateWithEntryLocale('pageflow_scrolled.public.more')}] }]); } From daacd166480a595b9156e24e66e617d4e291194d Mon Sep 17 00:00:00 2001 From: Tim Fischbach Date: Tue, 30 Jul 2024 16:25:10 +0200 Subject: [PATCH 2/9] Align tooltips with indicator horizontally even with area reference REDMINE-20673 --- .../contentElements/hotspots/Hotspots-spec.js | 9 ++-- .../hotspots/getTooltipReferencePosition.js | 43 ++++++++++++------- 2 files changed, 30 insertions(+), 22 deletions(-) diff --git a/entry_types/scrolled/package/spec/contentElements/hotspots/Hotspots-spec.js b/entry_types/scrolled/package/spec/contentElements/hotspots/Hotspots-spec.js index abdf2b479..3c81c710f 100644 --- a/entry_types/scrolled/package/spec/contentElements/hotspots/Hotspots-spec.js +++ b/entry_types/scrolled/package/spec/contentElements/hotspots/Hotspots-spec.js @@ -570,7 +570,7 @@ describe('Hotspots', () => { image: 100, areas: [ { - indicatorPosition: [10, 20], + indicatorPosition: [15, 20], outline: [[10, 20], [10, 30], [40, 30], [40, 15]], tooltipReference: 'area' } @@ -584,9 +584,8 @@ describe('Hotspots', () => { simulateScrollPosition('near viewport'); expect(container.querySelector(`.${tooltipStyles.reference}`)).toHaveStyle({ - left: '10px', + left: '15px', top: '15px', - width: '30px', height: '15px' }); }); @@ -618,9 +617,8 @@ describe('Hotspots', () => { simulateScrollPosition('near viewport'); expect(container.querySelector(`.${tooltipStyles.reference}`)).toHaveStyle({ - left: '10px', + left: '20px', top: '15px', - width: '30px', height: '15px' }); }); @@ -654,7 +652,6 @@ describe('Hotspots', () => { expect(container.querySelector(`.${tooltipStyles.reference}`)).toHaveStyle({ left: '10px', top: '15px', - width: '30px', height: '15px' }); }); diff --git a/entry_types/scrolled/package/src/contentElements/hotspots/getTooltipReferencePosition.js b/entry_types/scrolled/package/src/contentElements/hotspots/getTooltipReferencePosition.js index cb28633d0..247ebee15 100644 --- a/entry_types/scrolled/package/src/contentElements/hotspots/getTooltipReferencePosition.js +++ b/entry_types/scrolled/package/src/contentElements/hotspots/getTooltipReferencePosition.js @@ -6,38 +6,49 @@ export function getTooltipReferencePosition({ portraitMode, panZoomEnabled, imageFile, containerRect }) { - const referenceType = portraitMode ? area.portraitTooltipReference : area.tooltipReference; - - const referencePositionInPercent = - referenceType === 'area' ? - getBoundingRect(portraitMode ? area.portraitOutline : area.outline) : - getIndicatorRect(portraitMode ? area.portraitIndicatorPosition : area.indicatorPosition) + const referencePositionInPercent = getReferencePositionInPercent({area, portraitMode}); const transform = panZoomEnabled ? getPanZoomStepTransform({ - areaOutline: portraitMode ? area.portraitOutline : area.outline, - areaZoom: (portraitMode ? area.portraitZoom : area.zoom) || 0, - imageFileWidth: imageFile?.width, - imageFileHeight: imageFile?.height, - containerWidth: containerRect.width, - containerHeight: containerRect.height - }) : - {x: 0, y: 0, scale: 1}; + areaOutline: portraitMode ? area.portraitOutline : area.outline, + areaZoom: (portraitMode ? area.portraitZoom : area.zoom) || 0, + imageFileWidth: imageFile?.width, + imageFileHeight: imageFile?.height, + containerWidth: containerRect.width, + containerHeight: containerRect.height + }) : + {x: 0, y: 0, scale: 1}; return { left: containerRect.width * transform.scale * referencePositionInPercent.left / 100 + transform.x, top: containerRect.height * transform.scale * referencePositionInPercent.top / 100 + transform.y, - width: containerRect.width * transform.scale * referencePositionInPercent.width / 100, height: containerRect.height * transform.scale * referencePositionInPercent.height / 100 }; } +function getReferencePositionInPercent({area, portraitMode}) { + const referenceType = portraitMode ? area.portraitTooltipReference : area.tooltipReference; + const indicatorRect = getIndicatorRect(portraitMode ? area.portraitIndicatorPosition : area.indicatorPosition); + + if (referenceType === 'area') { + const boundingRect = getBoundingRect(portraitMode ? area.portraitOutline : area.outline); + + return { + top: boundingRect.top, + height: boundingRect.height, + left: indicatorRect.left + } + } + else { + return indicatorRect; + } +} + function getIndicatorRect(indicatorPosition = [50, 50]) { return { left: indicatorPosition[0], top: indicatorPosition[1], - width: 0, height: 0 }; } From 7520926db4ec6e3499d6eae4963cfbf39791b79f Mon Sep 17 00:00:00 2001 From: Tim Fischbach Date: Tue, 30 Jul 2024 16:40:16 +0200 Subject: [PATCH 3/9] Reduce hotspot tooltip font size REDMINE-20673 --- .../src/contentElements/hotspots/Tooltip.module.css | 7 +++---- entry_types/scrolled/package/src/frontend/Text.module.css | 7 ++++--- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/entry_types/scrolled/package/src/contentElements/hotspots/Tooltip.module.css b/entry_types/scrolled/package/src/contentElements/hotspots/Tooltip.module.css index 28e423acc..af92c0cb5 100644 --- a/entry_types/scrolled/package/src/contentElements/hotspots/Tooltip.module.css +++ b/entry_types/scrolled/package/src/contentElements/hotspots/Tooltip.module.css @@ -24,7 +24,7 @@ background-color: #fff; color: #000; box-sizing: border-box; - padding: 1rem; + padding: 0.75rem; box-shadow: 0px 3px 3px -2px rgba(0,0,0,0.2), 0px 3px 4px 0px rgba(0,0,0,0.14), 0px 1px 8px 0px rgba(0,0,0,0.12); border-radius: 5px; width: calc(100% - 2rem); @@ -47,7 +47,7 @@ .box > h3, .box > div { - margin-bottom: 0.5em; + margin-bottom: 0.5rem; } .box > :last-child { @@ -60,10 +60,9 @@ gap: 0.5em; border-radius: 5px; text-decoration: none; - padding: 0.75rem; + padding: 0.5rem; background-color: var(--theme-widget-primary-color); color: var(--theme-widget-on-primary-color); - font-size: 18px; margin-top: 1rem; font-weight: bold; } diff --git a/entry_types/scrolled/package/src/frontend/Text.module.css b/entry_types/scrolled/package/src/frontend/Text.module.css index e2cf7cfe6..7a1c15ab2 100644 --- a/entry_types/scrolled/package/src/frontend/Text.module.css +++ b/entry_types/scrolled/package/src/frontend/Text.module.css @@ -1,3 +1,4 @@ +@value text-2xs: 16px; @value text-xs: 18px; @value text-s: 20px; @value text-base: 22px; @@ -188,20 +189,20 @@ .hotspotsTooltipTitle { composes: typography-hotspotTooltipTitle from global; - font-size: text-s; + font-size: text-xs; line-height: 1.4; font-weight: bold; } .hotspotsTooltipDescription { composes: typography-hotspotTooltipDescription from global; - font-size: text-s; + font-size: text-xs; line-height: 1.4; } .hotspotsTooltipLink { composes: typography-hotspotTooltipLink from global; - font-size: text-xs; + font-size: text-2xs; line-height: 1.4; font-weight: bold; } From 5a30a0f36e88e5048f11c123b6503ce0be7fcbdf Mon Sep 17 00:00:00 2001 From: Tim Fischbach Date: Wed, 31 Jul 2024 09:52:48 +0200 Subject: [PATCH 4/9] Use medium variant in hotspot tooltip image REDMINE-20673 --- .../package/spec/contentElements/hotspots/Hotspots-spec.js | 4 ++-- .../package/src/contentElements/hotspots/Tooltip.js | 6 +++--- .../package/src/contentElements/hotspots/Tooltip.module.css | 1 + 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/entry_types/scrolled/package/spec/contentElements/hotspots/Hotspots-spec.js b/entry_types/scrolled/package/spec/contentElements/hotspots/Hotspots-spec.js index 3c81c710f..dd5c3ab19 100644 --- a/entry_types/scrolled/package/spec/contentElements/hotspots/Hotspots-spec.js +++ b/entry_types/scrolled/package/spec/contentElements/hotspots/Hotspots-spec.js @@ -356,7 +356,7 @@ describe('Hotspots', () => { const seed = { imageFileUrlTemplates: { large: 'large/:id_partition/image.webp', - linkThumbnailLarge: 'linkThumbnailLarge/:id_partition/image.webp' + medium: 'medium/:id_partition/image.webp' }, imageFiles: [{id: 1, permaId: 100}, {id: 2, permaId: 101}] }; @@ -395,7 +395,7 @@ describe('Hotspots', () => { expect(queryByText('Some link')).not.toBeNull(); expect(getByRole('link')).toHaveAttribute('href', 'https://example.com'); expect(getByRole('link')).toHaveAttribute('target', '_blank'); - expect(getByRole('img')).toHaveAttribute('src', 'linkThumbnailLarge/000/000/002/image.webp'); + expect(getByRole('img')).toHaveAttribute('src', 'medium/000/000/002/image.webp'); }); it('does not render tooltip link if link text is blank', async () => { diff --git a/entry_types/scrolled/package/src/contentElements/hotspots/Tooltip.js b/entry_types/scrolled/package/src/contentElements/hotspots/Tooltip.js index 765b12d8b..6b4504a3d 100644 --- a/entry_types/scrolled/package/src/contentElements/hotspots/Tooltip.js +++ b/entry_types/scrolled/package/src/contentElements/hotspots/Tooltip.js @@ -151,10 +151,10 @@ export function Tooltip({ {...getFloatingProps()}> {presentOrEditing('title') &&

diff --git a/entry_types/scrolled/package/src/contentElements/hotspots/Tooltip.module.css b/entry_types/scrolled/package/src/contentElements/hotspots/Tooltip.module.css index af92c0cb5..0df8719df 100644 --- a/entry_types/scrolled/package/src/contentElements/hotspots/Tooltip.module.css +++ b/entry_types/scrolled/package/src/contentElements/hotspots/Tooltip.module.css @@ -37,6 +37,7 @@ .box img { width: 100%; + height: auto; margin-bottom: 1rem; } From f6de8b3fc13c9eaad881d7158010cbc401f9f233 Mon Sep 17 00:00:00 2001 From: Tim Fischbach Date: Wed, 31 Jul 2024 10:01:52 +0200 Subject: [PATCH 5/9] Apply min width only to tooltips with links REDMINE-20673 --- .../contentElements/hotspots/Hotspots-spec.js | 73 ++++++++++++++++++- .../src/contentElements/hotspots/Tooltip.js | 3 +- .../hotspots/Tooltip.module.css | 4 + 3 files changed, 78 insertions(+), 2 deletions(-) diff --git a/entry_types/scrolled/package/spec/contentElements/hotspots/Hotspots-spec.js b/entry_types/scrolled/package/spec/contentElements/hotspots/Hotspots-spec.js index dd5c3ab19..19de708ff 100644 --- a/entry_types/scrolled/package/spec/contentElements/hotspots/Hotspots-spec.js +++ b/entry_types/scrolled/package/spec/contentElements/hotspots/Hotspots-spec.js @@ -424,14 +424,85 @@ describe('Hotspots', () => { }; const user = userEvent.setup(); - const {container, queryByRole} = renderInContentElement( + const {container, queryByRole, simulateScrollPosition} = renderInContentElement( , {seed} ); + simulateScrollPosition('near viewport'); await user.click(container.querySelector(`.${areaStyles.clip}`)) expect(queryByRole('link')).toBeNull(); }); + it('does not apply min width to tooltip without link', async () => { + const seed = { + imageFileUrlTemplates: {large: ':id_partition/image.webp'}, + imageFiles: [{id: 1, permaId: 100}] + }; + const configuration = { + image: 100, + areas: [ + { + id: 1, + indicatorPosition: [10, 20], + } + ], + tooltipTexts: { + 1: { + title: [{type: 'heading', children: [{text: 'Some title'}]}], + description: [{type: 'paragraph', children: [{text: 'Some description'}]}], + link: [{type: 'paragraph', children: [{text: ''}]}] + } + }, + tooltipLinks: { + 1: {href: 'https://example.com', openInNewTab: true} + } + }; + + const user = userEvent.setup(); + const {container, simulateScrollPosition} = renderInContentElement( + , {seed} + ); + simulateScrollPosition('near viewport'); + await user.click(container.querySelector(`.${areaStyles.clip}`)) + + expect(container.querySelector(`.${tooltipStyles.box}`)).not.toHaveClass(tooltipStyles.minWidth); + }); + + it('applies min width to tooltip with link', async () => { + const seed = { + imageFileUrlTemplates: {large: ':id_partition/image.webp'}, + imageFiles: [{id: 1, permaId: 100}] + }; + const configuration = { + image: 100, + areas: [ + { + id: 1, + indicatorPosition: [10, 20], + } + ], + tooltipTexts: { + 1: { + title: [{type: 'heading', children: [{text: 'Some title'}]}], + description: [{type: 'paragraph', children: [{text: 'Some description'}]}], + link: [{type: 'paragraph', children: [{text: 'Some link'}]}] + } + }, + tooltipLinks: { + 1: {href: 'https://example.com', openInNewTab: true} + } + }; + + const user = userEvent.setup(); + const {container, simulateScrollPosition} = renderInContentElement( + , {seed} + ); + simulateScrollPosition('near viewport'); + await user.click(container.querySelector(`.${areaStyles.clip}`)) + + expect(container.querySelector(`.${tooltipStyles.box}`)).toHaveClass(tooltipStyles.minWidth); + }); + it('does not observe resize by default', () => { const seed = { imageFileUrlTemplates: {large: ':id_partition/image.webp'}, diff --git a/entry_types/scrolled/package/src/contentElements/hotspots/Tooltip.js b/entry_types/scrolled/package/src/contentElements/hotspots/Tooltip.js index 6b4504a3d..f6e5a9066 100644 --- a/entry_types/scrolled/package/src/contentElements/hotspots/Tooltip.js +++ b/entry_types/scrolled/package/src/contentElements/hotspots/Tooltip.js @@ -144,7 +144,8 @@ export function Tooltip({
Date: Wed, 31 Jul 2024 10:46:43 +0200 Subject: [PATCH 6/9] Make hotspot tooltip max width configurable REDMINE-20673 --- .../config/locales/new/hotspots.de.yml | 14 ++++ .../config/locales/new/hotspots.en.yml | 14 ++++ .../contentElements/hotspots/Hotspots-spec.js | 67 ++++++++++++++++ .../src/contentElements/hotspots/Tooltip.js | 2 + .../hotspots/Tooltip.module.css | 16 +++- .../hotspots/editor/SidebarEditAreaView.js | 8 ++ .../ui/views/inputs/SelectInputView-spec.js | 77 +++++++++++++++++++ .../src/ui/views/inputs/SelectInputView.js | 16 +++- .../src/ui/views/inputs/SliderInputView.js | 2 +- 9 files changed, 211 insertions(+), 5 deletions(-) diff --git a/entry_types/scrolled/config/locales/new/hotspots.de.yml b/entry_types/scrolled/config/locales/new/hotspots.de.yml index 0b2003950..bb2776ae3 100644 --- a/entry_types/scrolled/config/locales/new/hotspots.de.yml +++ b/entry_types/scrolled/config/locales/new/hotspots.de.yml @@ -31,6 +31,13 @@ de: values: indicator: Am Indikator area: Am Bereich + tooltipMaxWidth: + label: Tooltip-Maximalbreite + values: + medium: "Mittel" + veryNarrow: Sehr schmal + narrow: Schmal + wide: Breit color: label: Farbe activeImage: @@ -51,6 +58,13 @@ de: values: indicator: Am Indikator area: Am Bereich + portraitTooltipMaxWidth: + label: Tooltip-Maximalbreite (Hochkant) + values: + medium: "Mittel" + narrow: Schmal + veryNarrow: Sehr schmal + wide: Breit portraitColor: label: Farbe (Hochkant) portraitActiveImage: diff --git a/entry_types/scrolled/config/locales/new/hotspots.en.yml b/entry_types/scrolled/config/locales/new/hotspots.en.yml index db1a4f835..1098b5d29 100644 --- a/entry_types/scrolled/config/locales/new/hotspots.en.yml +++ b/entry_types/scrolled/config/locales/new/hotspots.en.yml @@ -31,6 +31,13 @@ en: values: indicator: At indicator area: At area + tooltipMaxWidth: + label: Tooltip maximum width + values: + medium: Medium + narrow: Narrow + veryNarrow: Very Narrow + wide: Wide color: label: Color activeImage: @@ -51,6 +58,13 @@ en: values: indicator: At indicator area: At area + portraitTooltipMaxWidth: + label: Tooltip maximum width (Portrait) + values: + medium: Medium + narrow: Narrow + veryNarrow: Very Narrow + wide: Wide portraitColor: label: Color (Portrait) portraitActiveImage: diff --git a/entry_types/scrolled/package/spec/contentElements/hotspots/Hotspots-spec.js b/entry_types/scrolled/package/spec/contentElements/hotspots/Hotspots-spec.js index 19de708ff..c0e226380 100644 --- a/entry_types/scrolled/package/spec/contentElements/hotspots/Hotspots-spec.js +++ b/entry_types/scrolled/package/spec/contentElements/hotspots/Hotspots-spec.js @@ -503,6 +503,73 @@ describe('Hotspots', () => { expect(container.querySelector(`.${tooltipStyles.box}`)).toHaveClass(tooltipStyles.minWidth); }); + it('applies max width to tooltip', async () => { + const seed = { + imageFileUrlTemplates: {large: ':id_partition/image.webp'}, + imageFiles: [{id: 1, permaId: 100}] + }; + const configuration = { + image: 100, + areas: [ + { + id: 1, + indicatorPosition: [10, 20], + tooltipMaxWidth: 'narrow' + } + ], + tooltipTexts: { + 1: { + title: [{type: 'heading', children: [{text: 'Some title'}]}], + description: [{type: 'paragraph', children: [{text: 'Some description'}]}] + } + } + }; + + const user = userEvent.setup(); + const {container, simulateScrollPosition} = renderInContentElement( + , {seed} + ); + simulateScrollPosition('near viewport'); + await user.click(container.querySelector(`.${areaStyles.clip}`)) + + expect(container.querySelector(`.${tooltipStyles.box}`)).toHaveClass(tooltipStyles['maxWidth-narrow']); + }); + + it('supports separate max width for portrait mode', async () => { + const seed = { + imageFileUrlTemplates: {large: ':id_partition/image.webp'}, + imageFiles: [{id: 1, permaId: 100}] + }; + const configuration = { + image: 100, + portraitImage: 100, + areas: [ + { + id: 1, + indicatorPosition: [10, 20], + tooltipMaxWidth: 'narrow', + portraitTooltipMaxWidth: 'veryNarrow' + } + ], + tooltipTexts: { + 1: { + title: [{type: 'heading', children: [{text: 'Some title'}]}], + description: [{type: 'paragraph', children: [{text: 'Some description'}]}] + } + } + }; + + const user = userEvent.setup(); + window.matchMedia.mockPortrait(); + const {container, simulateScrollPosition} = renderInContentElement( + , {seed} + ); + simulateScrollPosition('near viewport'); + await user.click(container.querySelector(`.${areaStyles.clip}`)) + + expect(container.querySelector(`.${tooltipStyles.box}`)).toHaveClass(tooltipStyles['maxWidth-veryNarrow']); + }); + it('does not observe resize by default', () => { const seed = { imageFileUrlTemplates: {large: ':id_partition/image.webp'}, diff --git a/entry_types/scrolled/package/src/contentElements/hotspots/Tooltip.js b/entry_types/scrolled/package/src/contentElements/hotspots/Tooltip.js index f6e5a9066..876377497 100644 --- a/entry_types/scrolled/package/src/contentElements/hotspots/Tooltip.js +++ b/entry_types/scrolled/package/src/contentElements/hotspots/Tooltip.js @@ -56,6 +56,7 @@ export function Tooltip({ const referenceType = portraitMode ? area.portraitTooltipReference : area.tooltipReference; const position = portraitMode ? area.portraitTooltipPosition : area.tooltipPosition; + const maxWidth = portraitMode ? area.portraitTooltipMaxWidth : area.tooltipMaxWidth; const arrowRef = useRef(); const {refs, floatingStyles, context} = useFloating({ @@ -144,6 +145,7 @@ export function Tooltip({
{ expect($('select', selectInputView.el).val()).toEqual('second'); }); + it('saves value on change', () => { + var model = new Model(); + var selectInputView = new SelectInputView({ + model: model, + propertyName: 'value', + values: ['first', 'second'] + }); + + selectInputView.render(); + selectInputView.ui.select.val('second'); + selectInputView.ui.select.trigger('change'); + + expect(model.get('value')).toEqual('second'); + }); + it('selects first option if value is not among values', () => { var model = new Model({value: 'not there'}); var selectInputView = new SelectInputView({ @@ -240,6 +255,68 @@ describe('pageflow.SelectInputView', () => { }); }); + describe('with defaultValue', () => { + it('selects default value if property is not set', () => { + var model = new Model(); + var selectInputView = new SelectInputView({ + model: model, + propertyName: 'value', + values: ['small', 'medium', 'large'], + defaultValue: 'medium' + }); + + selectInputView.render(); + + expect($('select', selectInputView.el).val()).toEqual('medium'); + }); + + it('selects current value if property is set', () => { + var model = new Model({value: 'small'}); + var selectInputView = new SelectInputView({ + model: model, + propertyName: 'value', + values: ['small', 'medium', 'large'], + defaultValue: 'medium' + }); + + selectInputView.render(); + + expect($('select', selectInputView.el).val()).toEqual('small'); + }); + + it('does not save default value', () => { + var model = new Model({value: 'small'}); + var selectInputView = new SelectInputView({ + model: model, + propertyName: 'value', + values: ['small', 'medium', 'large'], + defaultValue: 'medium' + }); + + selectInputView.render(); + selectInputView.ui.select.val('medium'); + selectInputView.ui.select.trigger('change'); + + expect(model.has('value')).toEqual(false); + }); + + it('saves other values on change', () => { + var model = new Model({value: 'small'}); + var selectInputView = new SelectInputView({ + model: model, + propertyName: 'value', + values: ['small', 'medium', 'large'], + defaultValue: 'medium' + }); + + selectInputView.render(); + selectInputView.ui.select.val('large'); + selectInputView.ui.select.trigger('change'); + + expect(model.get('value')).toEqual('large'); + }); + }); + function optionTexts(view) { view.render(); diff --git a/package/src/ui/views/inputs/SelectInputView.js b/package/src/ui/views/inputs/SelectInputView.js index b26dd3575..e7cdcf021 100644 --- a/package/src/ui/views/inputs/SelectInputView.js +++ b/package/src/ui/views/inputs/SelectInputView.js @@ -17,6 +17,9 @@ import template from '../../templates/inputs/selectInput.jst'; * @param {string[]} [options.values] * List of possible values to persist in the attribute. * + * @param {number} [options.defaultValue] + * Default value to display if property is not set. + * * @param {string[]} [options.texts] * List of display texts for drop down items. * @@ -226,7 +229,14 @@ export const SelectInputView = Marionette.ItemView.extend({ }, save: function() { - this.model.set(this.options.propertyName, this.ui.select.val()); + const value = this.ui.select.val(); + + if ('defaultValue' in this.options && value === this.options.defaultValue) { + this.model.unset(this.options.propertyName); + } + else { + this.model.set(this.options.propertyName, value); + } }, load: function() { @@ -236,7 +246,9 @@ export const SelectInputView = Marionette.ItemView.extend({ if (this.model.has(this.options.propertyName) && this.ui.select.find('option[value="' + value +'"]:not([disabled])').length) { this.ui.select.val(value); - + } + else if ('defaultValue' in this.options) { + this.ui.select.val(this.options.defaultValue); } else { this.ui.select.val(this.ui.select.find('option:not([disabled]):first').val()); diff --git a/package/src/ui/views/inputs/SliderInputView.js b/package/src/ui/views/inputs/SliderInputView.js index 50b161950..21ca0c2e8 100644 --- a/package/src/ui/views/inputs/SliderInputView.js +++ b/package/src/ui/views/inputs/SliderInputView.js @@ -12,7 +12,7 @@ import template from '../../templates/inputs/sliderInput.jst'; * @param {Object} [options] * * @param {number} [options.defaultValue] - * Defaults value to display if property is not set. + * Default value to display if property is not set. * * @param {number} [options.minValue=0] * Value when dragging slider to the very left. From 8fc6b4ab9024bf7b5dea88814bf5bcff4a93ed2a Mon Sep 17 00:00:00 2001 From: Tim Fischbach Date: Wed, 31 Jul 2024 11:11:34 +0200 Subject: [PATCH 7/9] Support different text alignments for hotspot tooltips REDMINE-20673 --- .../config/locales/new/hotspots.de.yml | 6 ++++ .../config/locales/new/hotspots.en.yml | 6 ++++ .../contentElements/hotspots/Hotspots-spec.js | 32 +++++++++++++++++++ .../src/contentElements/hotspots/Tooltip.js | 1 + .../hotspots/Tooltip.module.css | 8 +++++ .../hotspots/editor/SidebarEditAreaView.js | 3 ++ 6 files changed, 56 insertions(+) diff --git a/entry_types/scrolled/config/locales/new/hotspots.de.yml b/entry_types/scrolled/config/locales/new/hotspots.de.yml index bb2776ae3..b0d80a9cb 100644 --- a/entry_types/scrolled/config/locales/new/hotspots.de.yml +++ b/entry_types/scrolled/config/locales/new/hotspots.de.yml @@ -38,6 +38,12 @@ de: veryNarrow: Sehr schmal narrow: Schmal wide: Breit + tooltipTextAlign: + label: Textausrichtung in Tooltip + values: + left: Links + center: Zentriert + right: Rechts color: label: Farbe activeImage: diff --git a/entry_types/scrolled/config/locales/new/hotspots.en.yml b/entry_types/scrolled/config/locales/new/hotspots.en.yml index 1098b5d29..5f758d802 100644 --- a/entry_types/scrolled/config/locales/new/hotspots.en.yml +++ b/entry_types/scrolled/config/locales/new/hotspots.en.yml @@ -38,6 +38,12 @@ en: narrow: Narrow veryNarrow: Very Narrow wide: Wide + tooltipTextAlign: + label: Text alignment in tooltip + values: + left: Left + center: Center + right: Right color: label: Color activeImage: diff --git a/entry_types/scrolled/package/spec/contentElements/hotspots/Hotspots-spec.js b/entry_types/scrolled/package/spec/contentElements/hotspots/Hotspots-spec.js index c0e226380..acb15bfad 100644 --- a/entry_types/scrolled/package/spec/contentElements/hotspots/Hotspots-spec.js +++ b/entry_types/scrolled/package/spec/contentElements/hotspots/Hotspots-spec.js @@ -570,6 +570,38 @@ describe('Hotspots', () => { expect(container.querySelector(`.${tooltipStyles.box}`)).toHaveClass(tooltipStyles['maxWidth-veryNarrow']); }); + it('supports setting tooltip text align', async () => { + const seed = { + imageFileUrlTemplates: {large: ':id_partition/image.webp'}, + imageFiles: [{id: 1, permaId: 100}] + }; + const configuration = { + image: 100, + areas: [ + { + id: 1, + indicatorPosition: [10, 20], + tooltipTextAlign: 'center' + } + ], + tooltipTexts: { + 1: { + title: [{type: 'heading', children: [{text: 'Some title'}]}], + description: [{type: 'paragraph', children: [{text: 'Some description'}]}] + } + } + }; + + const user = userEvent.setup(); + const {container, simulateScrollPosition} = renderInContentElement( + , {seed} + ); + simulateScrollPosition('near viewport'); + await user.click(container.querySelector(`.${areaStyles.clip}`)) + + expect(container.querySelector(`.${tooltipStyles.box}`)).toHaveClass(tooltipStyles['align-center']); + }); + it('does not observe resize by default', () => { const seed = { imageFileUrlTemplates: {large: ':id_partition/image.webp'}, diff --git a/entry_types/scrolled/package/src/contentElements/hotspots/Tooltip.js b/entry_types/scrolled/package/src/contentElements/hotspots/Tooltip.js index 876377497..ecf921e0f 100644 --- a/entry_types/scrolled/package/src/contentElements/hotspots/Tooltip.js +++ b/entry_types/scrolled/package/src/contentElements/hotspots/Tooltip.js @@ -146,6 +146,7 @@ export function Tooltip({ style={floatingStyles} className={classNames(styles.box, styles[`maxWidth-${maxWidth}`], + styles[`align-${area.tooltipTextAlign}`], {[styles.editable]: isEditable, [styles.minWidth]: presentOrEditing('link')})} onMouseEnter={onMouseEnter} diff --git a/entry_types/scrolled/package/src/contentElements/hotspots/Tooltip.module.css b/entry_types/scrolled/package/src/contentElements/hotspots/Tooltip.module.css index 2830a7597..58e91218e 100644 --- a/entry_types/scrolled/package/src/contentElements/hotspots/Tooltip.module.css +++ b/entry_types/scrolled/package/src/contentElements/hotspots/Tooltip.module.css @@ -43,6 +43,14 @@ --max-width: 200px; } +.align-center { + text-align: center; +} + +.align-right { + text-align: right; +} + .minWidth { min-width: 200px; } diff --git a/entry_types/scrolled/package/src/contentElements/hotspots/editor/SidebarEditAreaView.js b/entry_types/scrolled/package/src/contentElements/hotspots/editor/SidebarEditAreaView.js index 94440551c..aa1e316bf 100644 --- a/entry_types/scrolled/package/src/contentElements/hotspots/editor/SidebarEditAreaView.js +++ b/entry_types/scrolled/package/src/contentElements/hotspots/editor/SidebarEditAreaView.js @@ -91,6 +91,9 @@ export const SidebarEditAreaView = Marionette.Layout.extend({ }, positioning: false }); + this.input('tooltipTextAlign', SelectInputView, { + values: ['left', 'right', 'center'] + }); }); if (portraitFile) { From f90d383a95210ad6ffdafba5b7b888ee58021eea Mon Sep 17 00:00:00 2001 From: Tim Fischbach Date: Wed, 31 Jul 2024 11:21:39 +0200 Subject: [PATCH 8/9] Improve hotspots floating options * Auto update when editing tooltips and size changes * Ensure tooltip padding in viewport * Ensure arrow does not conflict with border radius REDMINE-20673 --- .../spec/contentElements/hotspots/Hotspots-spec.js | 1 + .../package/src/contentElements/hotspots/Tooltip.js | 12 ++++-------- 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/entry_types/scrolled/package/spec/contentElements/hotspots/Hotspots-spec.js b/entry_types/scrolled/package/spec/contentElements/hotspots/Hotspots-spec.js index acb15bfad..b1e0b74ae 100644 --- a/entry_types/scrolled/package/spec/contentElements/hotspots/Hotspots-spec.js +++ b/entry_types/scrolled/package/spec/contentElements/hotspots/Hotspots-spec.js @@ -34,6 +34,7 @@ describe('Hotspots', () => { this.callback = callback; this.observe = observeResizeMock; this.unobserve = function(element) {}; + this.disconnect = function() {}; }; }); diff --git a/entry_types/scrolled/package/src/contentElements/hotspots/Tooltip.js b/entry_types/scrolled/package/src/contentElements/hotspots/Tooltip.js index ecf921e0f..5e3a22140 100644 --- a/entry_types/scrolled/package/src/contentElements/hotspots/Tooltip.js +++ b/entry_types/scrolled/package/src/contentElements/hotspots/Tooltip.js @@ -66,18 +66,14 @@ export function Tooltip({ placement: position === 'above' ? 'top' : 'bottom', middleware: [ offset(referenceType === 'area' ? 7 : 20), - shift({crossAxis: keepInViewport}), + shift({crossAxis: keepInViewport, padding: {left: 16, right: 16}}), keepInViewport && flip(), arrow({ - element: arrowRef + element: arrowRef, + padding: 5 }) ], - whileElementsMounted(referenceEl, floatingEl, update) { - return autoUpdate(referenceEl, floatingEl, update, { - elementResize: false, - layoutShift: false, - }); - } + whileElementsMounted: autoUpdate }); const role = useRole(context, {role: 'label'}); From 260b3e99aaa8337e3f102ea00fbc560e71a0843c Mon Sep 17 00:00:00 2001 From: Tim Fischbach Date: Wed, 31 Jul 2024 11:23:17 +0200 Subject: [PATCH 9/9] Move hotspots to interactive category REDMINE-20673 --- .../package/src/contentElements/hotspots/editor/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/entry_types/scrolled/package/src/contentElements/hotspots/editor/index.js b/entry_types/scrolled/package/src/contentElements/hotspots/editor/index.js index e73cd7ec7..207e1a173 100644 --- a/entry_types/scrolled/package/src/contentElements/hotspots/editor/index.js +++ b/entry_types/scrolled/package/src/contentElements/hotspots/editor/index.js @@ -17,7 +17,7 @@ editor.registerSideBarRouting({ editor.contentElementTypes.register('hotspots', { pictogram, - category: 'links', + category: 'interactive', featureName: 'hotspots_content_element', supportedPositions: ['inline', 'sticky', 'standAlone', 'left', 'right', 'backdrop'], supportedWidthRange: ['xxs', 'full'],