From 65a25d3ef045779d6e55327f0e38b7a8acb12c28 Mon Sep 17 00:00:00 2001 From: Alber EE <122263897+Alber-Writer@users.noreply.github.com> Date: Wed, 6 Nov 2024 14:07:10 +0100 Subject: [PATCH 01/11] fix tabsbar transformer width - defaultHeight workaround --- .../front-rich-components/tabsbar/tabsbar-shape.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/common/components/mock-components/front-rich-components/tabsbar/tabsbar-shape.tsx b/src/common/components/mock-components/front-rich-components/tabsbar/tabsbar-shape.tsx index 7e872a5a..b562c563 100644 --- a/src/common/components/mock-components/front-rich-components/tabsbar/tabsbar-shape.tsx +++ b/src/common/components/mock-components/front-rich-components/tabsbar/tabsbar-shape.tsx @@ -15,7 +15,7 @@ const tabsBarShapeSizeRestrictions: ShapeSizeRestrictions = { maxWidth: -1, maxHeight: -1, defaultWidth: 450, - defaultHeight: 150, + defaultHeight: 180, }; export const getTabsBarShapeSizeRestrictions = (): ShapeSizeRestrictions => From 04e9e5758f7fb5f225f55144b1186f1d501e3e35 Mon Sep 17 00:00:00 2001 From: Alber EE <122263897+Alber-Writer@users.noreply.github.com> Date: Tue, 29 Oct 2024 17:47:20 +0100 Subject: [PATCH 02/11] add tabsbar logic business --- .../tabsbar/business/balance-space.spec.ts | 77 +++++++++++++++++++ .../tabsbar/business/balance-space.ts | 75 ++++++++++++++++++ .../tabsbar/business/calc-text-width.ts | 8 ++ .../tabsbar/business/tabsbar.business.spec.ts | 42 ++++++++++ .../tabsbar/business/tabsbar.business.ts | 77 +++++++++++++++++++ 5 files changed, 279 insertions(+) create mode 100644 src/common/components/mock-components/front-rich-components/tabsbar/business/balance-space.spec.ts create mode 100644 src/common/components/mock-components/front-rich-components/tabsbar/business/balance-space.ts create mode 100644 src/common/components/mock-components/front-rich-components/tabsbar/business/calc-text-width.ts create mode 100644 src/common/components/mock-components/front-rich-components/tabsbar/business/tabsbar.business.spec.ts create mode 100644 src/common/components/mock-components/front-rich-components/tabsbar/business/tabsbar.business.ts diff --git a/src/common/components/mock-components/front-rich-components/tabsbar/business/balance-space.spec.ts b/src/common/components/mock-components/front-rich-components/tabsbar/business/balance-space.spec.ts new file mode 100644 index 00000000..a228db36 --- /dev/null +++ b/src/common/components/mock-components/front-rich-components/tabsbar/business/balance-space.spec.ts @@ -0,0 +1,77 @@ +import { describe, it, expect } from 'vitest'; +import { balanceSpacePerItem } from './balance-space'; + +const _sum = (resultado: number[]) => + resultado.reduce((acc, current) => acc + current, 0); + +describe('balanceSpacePerItem tests', () => { + it('should return an array which sums 150 when apply [10, 20, 30, 40, 50]', () => { + // Arrange + const theArray = [10, 20, 30, 40, 50]; + const availableWidth = 150; + + // Act + const result = balanceSpacePerItem(theArray, availableWidth); + const totalSum = _sum(result); + + // Assert + expect(totalSum).toBeGreaterThan(0); + expect(totalSum).toBeLessThanOrEqual(availableWidth); + }); + + it('should return an array which sums equal or less than 100 when apply [10, 20, 30, 40, 50]', () => { + // Arrange + const theArray = [10, 20, 30, 40, 50]; + const availableWidth = 100; + + // Act + const result = balanceSpacePerItem(theArray, availableWidth); + const totalSum = _sum(result); + + // Assert + expect(totalSum).toBeGreaterThan(0); + expect(totalSum).toBeLessThanOrEqual(availableWidth); + }); + + it('should return an array which sums less or equal than 150 when apply [10, 20, 31, 41, 50]', () => { + // Arrange + const theArray = [10, 20, 31, 41, 50]; + const availableWidth = 150; + + // Act + const result = balanceSpacePerItem(theArray, availableWidth); + const totalSum = _sum(result); + + // Assert + expect(totalSum).toBeGreaterThan(0); + expect(totalSum).toBeLessThanOrEqual(availableWidth); + }); + + it('should return an array which sums 10 when apply [10]', () => { + // Arrange + const theArray = [100]; + const availableWidth = 10; + + // Act + const result = balanceSpacePerItem(theArray, availableWidth); + const totalSum = _sum(result); + + // Assert + expect(totalSum).toBeGreaterThan(0); + expect(totalSum).toBeLessThanOrEqual(availableWidth); + }); + + it('should return an array which sums 18 when apply [10, 10]', () => { + // Arrange + const theArray = [10, 10]; + const availableWidth = 18; + + // Act + const result = balanceSpacePerItem(theArray, availableWidth); + const totalSum = _sum(result); + + // Assert + expect(totalSum).toBeGreaterThan(0); + expect(totalSum).toBeLessThanOrEqual(availableWidth); + }); +}); diff --git a/src/common/components/mock-components/front-rich-components/tabsbar/business/balance-space.ts b/src/common/components/mock-components/front-rich-components/tabsbar/business/balance-space.ts new file mode 100644 index 00000000..8de0a60c --- /dev/null +++ b/src/common/components/mock-components/front-rich-components/tabsbar/business/balance-space.ts @@ -0,0 +1,75 @@ +/** + * This calc is made "layer by layer", distributing a larger chunk of width in each iteration + * @param {Array} itemList - List of spaces to balance (Must be provided in ascendent order to work) + * @param {Number} availableSpace - The amount of space to be distributed + */ +export const balanceSpacePerItem = ( + itemList: number[], + availableSpace: number +) => { + const totalSpaceUsed = _spacesFactory(); + const maxItemSize = _spacesFactory(); + + return itemList.reduce((newList: number[], current, index, arr) => { + // Check if the array provided is properly ordered + if (index > 0) _checkListOrder(arr[index - 1], current); + + const lastItemSize: number = index > 0 ? newList[index - 1] : 0; + + // Once the maximum possible size of the item is reached, apply this size directly. + if (maxItemSize.value) { + totalSpaceUsed.add(maxItemSize.value); + return [...newList, lastItemSize]; + } + + /** Precalculate "existingSum + spaceSum" taking into account + * all next items supposing all they use current size */ + const timesToApply = arr.length - index; + const virtualTotalsSum = totalSpaceUsed.value + current * timesToApply; + + /** First "Bigger" tab behaviour: If the virtual-sum of next items using this size + * doesn't fit within available space, calc maxItemSize */ + if (virtualTotalsSum >= availableSpace) { + const remainder = + availableSpace - (totalSpaceUsed.value + lastItemSize * timesToApply); + const remainderPortionPerItem = Math.floor(remainder / timesToApply); + maxItemSize.set(lastItemSize + remainderPortionPerItem); + + totalSpaceUsed.add(maxItemSize.value); + + return [...newList, maxItemSize.value]; + } + + //"Normal" behaviour: Apply this new size to current + totalSpaceUsed.add(current); + return [...newList, current]; + }, []); +}; + +/* Balance helper functions: */ + +function _checkListOrder(prev: number, current: number) { + if (prev > current) { + throw new Error( + 'Disordered list. Please provide an ascendent ordered list as param *itemlist*' + ); + } +} + +function _spacesFactory() { + let _size = 0; + //Assure we are setting natural num w/o decimals + const _adjustNum = (num: number) => { + if (typeof num !== 'number') throw new Error('Number must be provided'); + return Math.max(0, Math.floor(num)); + }; + const add = (qty: number) => (_size += _adjustNum(qty)); + const set = (qty: number) => (_size = _adjustNum(qty)); + return { + get value() { + return _size; + }, + add, + set, + }; +} diff --git a/src/common/components/mock-components/front-rich-components/tabsbar/business/calc-text-width.ts b/src/common/components/mock-components/front-rich-components/tabsbar/business/calc-text-width.ts new file mode 100644 index 00000000..ae08f45e --- /dev/null +++ b/src/common/components/mock-components/front-rich-components/tabsbar/business/calc-text-width.ts @@ -0,0 +1,8 @@ +export const calcTextWidth = ( + inputText: string, + fontSize: number, + _fontfamily: string +) => { + const charAverageWidth = fontSize * 0.5; + return inputText.length * charAverageWidth + charAverageWidth / 2; +}; diff --git a/src/common/components/mock-components/front-rich-components/tabsbar/business/tabsbar.business.spec.ts b/src/common/components/mock-components/front-rich-components/tabsbar/business/tabsbar.business.spec.ts new file mode 100644 index 00000000..5e6b414b --- /dev/null +++ b/src/common/components/mock-components/front-rich-components/tabsbar/business/tabsbar.business.spec.ts @@ -0,0 +1,42 @@ +import { describe, it, expect } from 'vitest'; +import { adjustTabWidths } from './tabsbar.business'; + +const _sum = (resultado: number[]) => + resultado.reduce((acc, current) => acc + current, 0); + +describe('tabsbar.business tests', () => { + it('should return a new array of numbers, which sum is less than or equal to totalWidth', () => { + // Arrange + const tabs = [ + 'Text', + 'Normal text for tab', + 'Extra large text for a tab', + 'Really really large text for a tab', + 'xs', + ]; + const containerWidth = 1000; + const minTabWidth = 100; + const tabsGap = 10; + + // Act + const result = adjustTabWidths({ + tabs, + containerWidth, + minTabWidth, + tabXPadding: 20, + tabsGap, + font: { fontSize: 14, fontFamily: 'Arial' }, + }); + + console.log({ tabs }, { containerWidth }, { minTabWidth }); + console.log({ result }); + + const totalSum = _sum(result.widthList) + (tabs.length - 1) * tabsGap; + console.log('totalSum: ', totalSum); + + // Assert + expect(result.widthList[0]).not.toBe(0); + expect(result.widthList.length).toBe(tabs.length); + expect(totalSum).toBeLessThanOrEqual(containerWidth); + }); +}); diff --git a/src/common/components/mock-components/front-rich-components/tabsbar/business/tabsbar.business.ts b/src/common/components/mock-components/front-rich-components/tabsbar/business/tabsbar.business.ts new file mode 100644 index 00000000..be06ca95 --- /dev/null +++ b/src/common/components/mock-components/front-rich-components/tabsbar/business/tabsbar.business.ts @@ -0,0 +1,77 @@ +import { balanceSpacePerItem } from './balance-space'; +import { calcTextWidth } from './calc-text-width'; + +export const adjustTabWidths = (args: { + tabs: string[]; + containerWidth: number; + minTabWidth: number; + tabXPadding: number; + tabsGap: number; + font: { + fontSize: number; + fontFamily: string; + }; +}) => { + const { tabs, containerWidth, minTabWidth, tabXPadding, tabsGap, font } = + args; + const totalInnerXPadding = tabXPadding * 2; + const totalMinTabSpace = minTabWidth + totalInnerXPadding; + const containerWidthWithoutTabGaps = + containerWidth - (tabs.length - 1) * tabsGap; + + //Create info Object with originalPositions and desired width + interface OriginalTabInfo { + initialTabPosition: number; + desiredWidth: number; + } + const arrangeTabsInfo = tabs.reduce( + (acc: OriginalTabInfo[], tab, index): OriginalTabInfo[] => { + const tabFullTextWidth = + calcTextWidth(tab, font.fontSize, font.fontFamily) + totalInnerXPadding; + const desiredWidth = Math.max(totalMinTabSpace, tabFullTextWidth); + return [ + ...acc, + { + initialTabPosition: index, + desiredWidth, + }, + ]; + }, + [] + ); + + // This order is neccessary to build layer by layer the new sizes + const ascendentTabList = arrangeTabsInfo.sort( + (a, b) => a.desiredWidth - b.desiredWidth + ); + + const onlyWidthList = ascendentTabList.map(tab => tab.desiredWidth); + // Apply adjustments + const adjustedSizeList = balanceSpacePerItem( + onlyWidthList, + containerWidthWithoutTabGaps + ); + + // Reassemble new data with the original order + const reassembledData = ascendentTabList.reduce( + (accList: number[], current, index) => { + const newList = [...accList]; + newList[current.initialTabPosition] = adjustedSizeList[index]; + return newList; + }, + [] + ); + + // Calc item offset position + let sumOfXposition = 0; + const relativeTabPosition = reassembledData.reduce( + (acc: number[], el, index) => { + const currentElementXPos = index ? sumOfXposition : 0; + sumOfXposition += el + tabsGap; + return [...acc, currentElementXPos]; + }, + [] + ); + + return { widthList: reassembledData, relativeTabPosition }; +}; From 061d7bf8ffc869de5340c1437cbb3d9e73b6d71e Mon Sep 17 00:00:00 2001 From: Alber EE <122263897+Alber-Writer@users.noreply.github.com> Date: Thu, 7 Nov 2024 22:41:08 +0100 Subject: [PATCH 03/11] adjust tabsbar text-width methods --- .../tabsbar/business/balance-space.ts | 6 +- .../tabsbar/business/calc-text-width.ts | 26 ++++++- .../tabsbar/business/tabsbar.business.ts | 16 ++--- .../tabsbar/tabsbar-shape.tsx | 68 ++++++++++++------- 4 files changed, 77 insertions(+), 39 deletions(-) diff --git a/src/common/components/mock-components/front-rich-components/tabsbar/business/balance-space.ts b/src/common/components/mock-components/front-rich-components/tabsbar/business/balance-space.ts index 8de0a60c..8e683166 100644 --- a/src/common/components/mock-components/front-rich-components/tabsbar/business/balance-space.ts +++ b/src/common/components/mock-components/front-rich-components/tabsbar/business/balance-space.ts @@ -16,7 +16,7 @@ export const balanceSpacePerItem = ( const lastItemSize: number = index > 0 ? newList[index - 1] : 0; - // Once the maximum possible size of the item is reached, apply this size directly. + // A) Once the maximum possible size of the item is reached, apply this size directly. if (maxItemSize.value) { totalSpaceUsed.add(maxItemSize.value); return [...newList, lastItemSize]; @@ -27,7 +27,7 @@ export const balanceSpacePerItem = ( const timesToApply = arr.length - index; const virtualTotalsSum = totalSpaceUsed.value + current * timesToApply; - /** First "Bigger" tab behaviour: If the virtual-sum of next items using this size + /** B) First "Bigger" tab behaviour: If the virtual-sum of next items using this size * doesn't fit within available space, calc maxItemSize */ if (virtualTotalsSum >= availableSpace) { const remainder = @@ -40,7 +40,7 @@ export const balanceSpacePerItem = ( return [...newList, maxItemSize.value]; } - //"Normal" behaviour: Apply this new size to current + // C) "Normal" behaviour: Apply proposed new size to current totalSpaceUsed.add(current); return [...newList, current]; }, []); diff --git a/src/common/components/mock-components/front-rich-components/tabsbar/business/calc-text-width.ts b/src/common/components/mock-components/front-rich-components/tabsbar/business/calc-text-width.ts index ae08f45e..91535f05 100644 --- a/src/common/components/mock-components/front-rich-components/tabsbar/business/calc-text-width.ts +++ b/src/common/components/mock-components/front-rich-components/tabsbar/business/calc-text-width.ts @@ -3,6 +3,28 @@ export const calcTextWidth = ( fontSize: number, _fontfamily: string ) => { - const charAverageWidth = fontSize * 0.5; - return inputText.length * charAverageWidth + charAverageWidth / 2; + const REGX_DEFS = { + LOWERCASE_BIG: /[mwoq]/, + LOWERCASE: /[a-z0-9]/, + UPPERCASE_BIG: /[MWOQ]/, + UPPERCASE: /[A-Z]/, + MARKS: /[.,;:!?'"(){}\[\]\-]/, + SPACE: /\s/, + DEFAULT: 'default', + }; + + const calcLength = [...inputText].reduce((sum, current) => { + const testChar = (regx: RegExp) => regx.test(current); + const addLength = (value: number) => sum + fontSize * value; + + if (testChar(REGX_DEFS.LOWERCASE_BIG)) return addLength(0.85); + if (testChar(REGX_DEFS.LOWERCASE)) return addLength(0.5); + if (testChar(REGX_DEFS.SPACE)) return addLength(0.2); + if (testChar(REGX_DEFS.UPPERCASE_BIG)) return addLength(0.95); + if (testChar(REGX_DEFS.UPPERCASE)) return addLength(0.7); + if (testChar(REGX_DEFS.MARKS)) return addLength(0.3); + return addLength(0.5); + }, 0); + + return calcLength; }; diff --git a/src/common/components/mock-components/front-rich-components/tabsbar/business/tabsbar.business.ts b/src/common/components/mock-components/front-rich-components/tabsbar/business/tabsbar.business.ts index be06ca95..da48e9ea 100644 --- a/src/common/components/mock-components/front-rich-components/tabsbar/business/tabsbar.business.ts +++ b/src/common/components/mock-components/front-rich-components/tabsbar/business/tabsbar.business.ts @@ -19,9 +19,9 @@ export const adjustTabWidths = (args: { const containerWidthWithoutTabGaps = containerWidth - (tabs.length - 1) * tabsGap; - //Create info Object with originalPositions and desired width + //Create info List with originalPositions and desired width interface OriginalTabInfo { - initialTabPosition: number; + originalTabPosition: number; desiredWidth: number; } const arrangeTabsInfo = tabs.reduce( @@ -32,7 +32,7 @@ export const adjustTabWidths = (args: { return [ ...acc, { - initialTabPosition: index, + originalTabPosition: index, desiredWidth, }, ]; @@ -40,7 +40,7 @@ export const adjustTabWidths = (args: { [] ); - // This order is neccessary to build layer by layer the new sizes + // This order is necessary to build layer by layer the new sizes const ascendentTabList = arrangeTabsInfo.sort( (a, b) => a.desiredWidth - b.desiredWidth ); @@ -56,18 +56,18 @@ export const adjustTabWidths = (args: { const reassembledData = ascendentTabList.reduce( (accList: number[], current, index) => { const newList = [...accList]; - newList[current.initialTabPosition] = adjustedSizeList[index]; + newList[current.originalTabPosition] = adjustedSizeList[index]; return newList; }, [] ); - // Calc item offset position + // Calc item offset position (mixed with external variable to avoid adding to reducer() extra complexity) let sumOfXposition = 0; const relativeTabPosition = reassembledData.reduce( - (acc: number[], el, index) => { + (acc: number[], currentTab, index) => { const currentElementXPos = index ? sumOfXposition : 0; - sumOfXposition += el + tabsGap; + sumOfXposition += currentTab + tabsGap; return [...acc, currentElementXPos]; }, [] diff --git a/src/common/components/mock-components/front-rich-components/tabsbar/tabsbar-shape.tsx b/src/common/components/mock-components/front-rich-components/tabsbar/tabsbar-shape.tsx index b562c563..f560557e 100644 --- a/src/common/components/mock-components/front-rich-components/tabsbar/tabsbar-shape.tsx +++ b/src/common/components/mock-components/front-rich-components/tabsbar/tabsbar-shape.tsx @@ -8,6 +8,7 @@ import { splitCSVContentIntoRows, } from '@/common/utils/active-element-selector.utils'; import { useGroupShapeProps } from '../../mock-components.utils'; +import { adjustTabWidths } from './business/tabsbar.business'; const tabsBarShapeSizeRestrictions: ShapeSizeRestrictions = { minWidth: 450, @@ -47,11 +48,21 @@ export const TabsBarShape = forwardRef((props, ref) => { const tabLabels = headers.map(header => header.text); // Calculate tab dimensions and margin - const tabWidth = 106; // Width of each tab + const minTabWidth = 40; // Min-width of each tab, without xPadding const tabHeight = 30; // Tab height const tabMargin = 10; // Horizontal margin between tabs + const tabXPadding = 20; + const tabFont = { fontSize: 14, fontFamily: 'Arial' }; const bodyHeight = restrictedHeight - tabHeight - 10; // Height of the tabs bar body - const totalTabsWidth = tabLabels.length * (tabWidth + tabMargin) + tabWidth; // Total width required plus one additional tab + + const tabAdjustedWidths = adjustTabWidths({ + tabs: tabLabels, + containerWidth: restrictedWidth - tabMargin * 2, //left and right tabList margin + minTabWidth, + tabXPadding, + tabsGap: tabMargin, + font: tabFont, + }); const activeTab = otherProps?.activeElement ?? 0; @@ -68,36 +79,41 @@ export const TabsBarShape = forwardRef((props, ref) => { {/* Map through headerRow to create tabs */} - {tabLabels.map((header, index) => ( - - - - - ))} + {tabLabels.map((header, index) => { + const { widthList: newWidthList, relativeTabPosition: xPosList } = + tabAdjustedWidths; + + return ( + + + + + ); + })} ); }); From 4baab3378099f9c1d5f2e2f9a3ae790cd5923d84 Mon Sep 17 00:00:00 2001 From: Alber EE <122263897+Alber-Writer@users.noreply.github.com> Date: Tue, 12 Nov 2024 14:18:38 +0100 Subject: [PATCH 04/11] switch method to canvas context approach --- .../tabsbar/business/calc-text-width.ts | 34 ++++++------------- 1 file changed, 10 insertions(+), 24 deletions(-) diff --git a/src/common/components/mock-components/front-rich-components/tabsbar/business/calc-text-width.ts b/src/common/components/mock-components/front-rich-components/tabsbar/business/calc-text-width.ts index 91535f05..f83a5fbe 100644 --- a/src/common/components/mock-components/front-rich-components/tabsbar/business/calc-text-width.ts +++ b/src/common/components/mock-components/front-rich-components/tabsbar/business/calc-text-width.ts @@ -1,30 +1,16 @@ export const calcTextWidth = ( inputText: string, fontSize: number, - _fontfamily: string + fontfamily: string ) => { - const REGX_DEFS = { - LOWERCASE_BIG: /[mwoq]/, - LOWERCASE: /[a-z0-9]/, - UPPERCASE_BIG: /[MWOQ]/, - UPPERCASE: /[A-Z]/, - MARKS: /[.,;:!?'"(){}\[\]\-]/, - SPACE: /\s/, - DEFAULT: 'default', - }; + // Creates an invisible canvas to perform the measurement + let canvas = document.createElement('canvas'); + const context = canvas.getContext('2d'); - const calcLength = [...inputText].reduce((sum, current) => { - const testChar = (regx: RegExp) => regx.test(current); - const addLength = (value: number) => sum + fontSize * value; - - if (testChar(REGX_DEFS.LOWERCASE_BIG)) return addLength(0.85); - if (testChar(REGX_DEFS.LOWERCASE)) return addLength(0.5); - if (testChar(REGX_DEFS.SPACE)) return addLength(0.2); - if (testChar(REGX_DEFS.UPPERCASE_BIG)) return addLength(0.95); - if (testChar(REGX_DEFS.UPPERCASE)) return addLength(0.7); - if (testChar(REGX_DEFS.MARKS)) return addLength(0.3); - return addLength(0.5); - }, 0); - - return calcLength; + if (context) { + context.font = `${fontSize}px ${fontfamily}`; + return context.measureText(inputText).width; + } + const charAverageWidth = fontSize * 0.7; + return inputText.length * charAverageWidth + charAverageWidth * 0.8; }; From 8a2e4e4e73f13ef1f975d58531eb39e380761e4b Mon Sep 17 00:00:00 2001 From: Alber EE <122263897+Alber-Writer@users.noreply.github.com> Date: Tue, 12 Nov 2024 15:39:41 +0100 Subject: [PATCH 05/11] refactor tabsbar with hook --- .../tabsbar/tab-list.hook.ts | 60 +++++++++++++++++++ .../tabsbar/tabsbar-shape.tsx | 38 ++++-------- 2 files changed, 73 insertions(+), 25 deletions(-) create mode 100644 src/common/components/mock-components/front-rich-components/tabsbar/tab-list.hook.ts diff --git a/src/common/components/mock-components/front-rich-components/tabsbar/tab-list.hook.ts b/src/common/components/mock-components/front-rich-components/tabsbar/tab-list.hook.ts new file mode 100644 index 00000000..a4c6694f --- /dev/null +++ b/src/common/components/mock-components/front-rich-components/tabsbar/tab-list.hook.ts @@ -0,0 +1,60 @@ +import { useEffect, useState } from 'react'; +import { adjustTabWidths } from './business/tabsbar.business'; +import { + extractCSVHeaders, + splitCSVContentIntoRows, +} from '@/common/utils/active-element-selector.utils'; + +interface TabListConfig { + text: string; + containerWidth: number; + minTabWidth: number; + tabXPadding: number; + tabsGap: number; + font: { + fontSize: number; + fontFamily: string; + }; +} + +export const useTabList = (tabsConfig: TabListConfig) => { + const { text, containerWidth, minTabWidth, tabXPadding, tabsGap, font } = + tabsConfig; + + const [tabWidthList, setTabWidthList] = useState<{ + widthList: number[]; + relativeTabPosition: number[]; + }>({ widthList: [], relativeTabPosition: [] }); + + const tabLabels = _extractTabLabelTexts(text); + + useEffect(() => { + setTabWidthList( + adjustTabWidths({ + tabs: tabLabels, + containerWidth, + minTabWidth, + tabXPadding, + tabsGap, + font: { + fontSize: font.fontSize, + fontFamily: font.fontFamily, + }, + }) + ); + }, [text, containerWidth]); + + //Return an unique array with all the info required by each tab + return tabLabels.map((tab, index) => ({ + tab, + width: tabWidthList.widthList[index], + xPos: tabWidthList.relativeTabPosition[index], + })); +}; + +// Split text to tab labels List +function _extractTabLabelTexts(text: string) { + const csvData = splitCSVContentIntoRows(text); + const headers = extractCSVHeaders(csvData[0]); + return headers.map(header => header.text); +} diff --git a/src/common/components/mock-components/front-rich-components/tabsbar/tabsbar-shape.tsx b/src/common/components/mock-components/front-rich-components/tabsbar/tabsbar-shape.tsx index f560557e..aabd65b7 100644 --- a/src/common/components/mock-components/front-rich-components/tabsbar/tabsbar-shape.tsx +++ b/src/common/components/mock-components/front-rich-components/tabsbar/tabsbar-shape.tsx @@ -3,12 +3,8 @@ import { Group, Rect, Text } from 'react-konva'; import { ShapeSizeRestrictions, ShapeType } from '@/core/model'; import { fitSizeToShapeSizeRestrictions } from '@/common/utils/shapes/shape-restrictions'; import { ShapeProps } from '../../shape.model'; -import { - extractCSVHeaders, - splitCSVContentIntoRows, -} from '@/common/utils/active-element-selector.utils'; import { useGroupShapeProps } from '../../mock-components.utils'; -import { adjustTabWidths } from './business/tabsbar.business'; +import { useTabList } from './tab-list.hook'; const tabsBarShapeSizeRestrictions: ShapeSizeRestrictions = { minWidth: 450, @@ -43,24 +39,19 @@ export const TabsBarShape = forwardRef((props, ref) => { ); const { width: restrictedWidth, height: restrictedHeight } = restrictedSize; - const csvData = splitCSVContentIntoRows(text); - const headers = extractCSVHeaders(csvData[0]); - const tabLabels = headers.map(header => header.text); - - // Calculate tab dimensions and margin - const minTabWidth = 40; // Min-width of each tab, without xPadding + // Tab dimensions and margin const tabHeight = 30; // Tab height - const tabMargin = 10; // Horizontal margin between tabs + const tabsGap = 10; // Horizontal margin between tabs const tabXPadding = 20; const tabFont = { fontSize: 14, fontFamily: 'Arial' }; const bodyHeight = restrictedHeight - tabHeight - 10; // Height of the tabs bar body - const tabAdjustedWidths = adjustTabWidths({ - tabs: tabLabels, - containerWidth: restrictedWidth - tabMargin * 2, //left and right tabList margin - minTabWidth, + const tabList = useTabList({ + text, + containerWidth: restrictedWidth - tabsGap * 2, //left and right tabList margin + minTabWidth: 40, // Min-width of each tab, without xPadding tabXPadding, - tabsGap: tabMargin, + tabsGap, font: tabFont, }); @@ -86,14 +77,11 @@ export const TabsBarShape = forwardRef((props, ref) => { fill="white" /> {/* Map through headerRow to create tabs */} - {tabLabels.map((header, index) => { - const { widthList: newWidthList, relativeTabPosition: xPosList } = - tabAdjustedWidths; - + {tabList.map(({ tab, width, xPos }, index) => { return ( - + ((props, ref) => { Date: Wed, 6 Nov 2024 14:07:10 +0100 Subject: [PATCH 06/11] fix tabsbar transformer width - defaultHeight workaround --- .../front-rich-components/tabsbar/tabsbar-shape.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/common/components/mock-components/front-rich-components/tabsbar/tabsbar-shape.tsx b/src/common/components/mock-components/front-rich-components/tabsbar/tabsbar-shape.tsx index 7e872a5a..b562c563 100644 --- a/src/common/components/mock-components/front-rich-components/tabsbar/tabsbar-shape.tsx +++ b/src/common/components/mock-components/front-rich-components/tabsbar/tabsbar-shape.tsx @@ -15,7 +15,7 @@ const tabsBarShapeSizeRestrictions: ShapeSizeRestrictions = { maxWidth: -1, maxHeight: -1, defaultWidth: 450, - defaultHeight: 150, + defaultHeight: 180, }; export const getTabsBarShapeSizeRestrictions = (): ShapeSizeRestrictions => From 5c2e03226f67e0bf6b6ff4a37820e0fe41732336 Mon Sep 17 00:00:00 2001 From: Alber EE <122263897+Alber-Writer@users.noreply.github.com> Date: Tue, 29 Oct 2024 17:47:20 +0100 Subject: [PATCH 07/11] add tabsbar logic business --- .../tabsbar/business/balance-space.spec.ts | 77 +++++++++++++++++++ .../tabsbar/business/balance-space.ts | 75 ++++++++++++++++++ .../tabsbar/business/calc-text-width.ts | 8 ++ .../tabsbar/business/tabsbar.business.spec.ts | 42 ++++++++++ .../tabsbar/business/tabsbar.business.ts | 77 +++++++++++++++++++ 5 files changed, 279 insertions(+) create mode 100644 src/common/components/mock-components/front-rich-components/tabsbar/business/balance-space.spec.ts create mode 100644 src/common/components/mock-components/front-rich-components/tabsbar/business/balance-space.ts create mode 100644 src/common/components/mock-components/front-rich-components/tabsbar/business/calc-text-width.ts create mode 100644 src/common/components/mock-components/front-rich-components/tabsbar/business/tabsbar.business.spec.ts create mode 100644 src/common/components/mock-components/front-rich-components/tabsbar/business/tabsbar.business.ts diff --git a/src/common/components/mock-components/front-rich-components/tabsbar/business/balance-space.spec.ts b/src/common/components/mock-components/front-rich-components/tabsbar/business/balance-space.spec.ts new file mode 100644 index 00000000..a228db36 --- /dev/null +++ b/src/common/components/mock-components/front-rich-components/tabsbar/business/balance-space.spec.ts @@ -0,0 +1,77 @@ +import { describe, it, expect } from 'vitest'; +import { balanceSpacePerItem } from './balance-space'; + +const _sum = (resultado: number[]) => + resultado.reduce((acc, current) => acc + current, 0); + +describe('balanceSpacePerItem tests', () => { + it('should return an array which sums 150 when apply [10, 20, 30, 40, 50]', () => { + // Arrange + const theArray = [10, 20, 30, 40, 50]; + const availableWidth = 150; + + // Act + const result = balanceSpacePerItem(theArray, availableWidth); + const totalSum = _sum(result); + + // Assert + expect(totalSum).toBeGreaterThan(0); + expect(totalSum).toBeLessThanOrEqual(availableWidth); + }); + + it('should return an array which sums equal or less than 100 when apply [10, 20, 30, 40, 50]', () => { + // Arrange + const theArray = [10, 20, 30, 40, 50]; + const availableWidth = 100; + + // Act + const result = balanceSpacePerItem(theArray, availableWidth); + const totalSum = _sum(result); + + // Assert + expect(totalSum).toBeGreaterThan(0); + expect(totalSum).toBeLessThanOrEqual(availableWidth); + }); + + it('should return an array which sums less or equal than 150 when apply [10, 20, 31, 41, 50]', () => { + // Arrange + const theArray = [10, 20, 31, 41, 50]; + const availableWidth = 150; + + // Act + const result = balanceSpacePerItem(theArray, availableWidth); + const totalSum = _sum(result); + + // Assert + expect(totalSum).toBeGreaterThan(0); + expect(totalSum).toBeLessThanOrEqual(availableWidth); + }); + + it('should return an array which sums 10 when apply [10]', () => { + // Arrange + const theArray = [100]; + const availableWidth = 10; + + // Act + const result = balanceSpacePerItem(theArray, availableWidth); + const totalSum = _sum(result); + + // Assert + expect(totalSum).toBeGreaterThan(0); + expect(totalSum).toBeLessThanOrEqual(availableWidth); + }); + + it('should return an array which sums 18 when apply [10, 10]', () => { + // Arrange + const theArray = [10, 10]; + const availableWidth = 18; + + // Act + const result = balanceSpacePerItem(theArray, availableWidth); + const totalSum = _sum(result); + + // Assert + expect(totalSum).toBeGreaterThan(0); + expect(totalSum).toBeLessThanOrEqual(availableWidth); + }); +}); diff --git a/src/common/components/mock-components/front-rich-components/tabsbar/business/balance-space.ts b/src/common/components/mock-components/front-rich-components/tabsbar/business/balance-space.ts new file mode 100644 index 00000000..8de0a60c --- /dev/null +++ b/src/common/components/mock-components/front-rich-components/tabsbar/business/balance-space.ts @@ -0,0 +1,75 @@ +/** + * This calc is made "layer by layer", distributing a larger chunk of width in each iteration + * @param {Array} itemList - List of spaces to balance (Must be provided in ascendent order to work) + * @param {Number} availableSpace - The amount of space to be distributed + */ +export const balanceSpacePerItem = ( + itemList: number[], + availableSpace: number +) => { + const totalSpaceUsed = _spacesFactory(); + const maxItemSize = _spacesFactory(); + + return itemList.reduce((newList: number[], current, index, arr) => { + // Check if the array provided is properly ordered + if (index > 0) _checkListOrder(arr[index - 1], current); + + const lastItemSize: number = index > 0 ? newList[index - 1] : 0; + + // Once the maximum possible size of the item is reached, apply this size directly. + if (maxItemSize.value) { + totalSpaceUsed.add(maxItemSize.value); + return [...newList, lastItemSize]; + } + + /** Precalculate "existingSum + spaceSum" taking into account + * all next items supposing all they use current size */ + const timesToApply = arr.length - index; + const virtualTotalsSum = totalSpaceUsed.value + current * timesToApply; + + /** First "Bigger" tab behaviour: If the virtual-sum of next items using this size + * doesn't fit within available space, calc maxItemSize */ + if (virtualTotalsSum >= availableSpace) { + const remainder = + availableSpace - (totalSpaceUsed.value + lastItemSize * timesToApply); + const remainderPortionPerItem = Math.floor(remainder / timesToApply); + maxItemSize.set(lastItemSize + remainderPortionPerItem); + + totalSpaceUsed.add(maxItemSize.value); + + return [...newList, maxItemSize.value]; + } + + //"Normal" behaviour: Apply this new size to current + totalSpaceUsed.add(current); + return [...newList, current]; + }, []); +}; + +/* Balance helper functions: */ + +function _checkListOrder(prev: number, current: number) { + if (prev > current) { + throw new Error( + 'Disordered list. Please provide an ascendent ordered list as param *itemlist*' + ); + } +} + +function _spacesFactory() { + let _size = 0; + //Assure we are setting natural num w/o decimals + const _adjustNum = (num: number) => { + if (typeof num !== 'number') throw new Error('Number must be provided'); + return Math.max(0, Math.floor(num)); + }; + const add = (qty: number) => (_size += _adjustNum(qty)); + const set = (qty: number) => (_size = _adjustNum(qty)); + return { + get value() { + return _size; + }, + add, + set, + }; +} diff --git a/src/common/components/mock-components/front-rich-components/tabsbar/business/calc-text-width.ts b/src/common/components/mock-components/front-rich-components/tabsbar/business/calc-text-width.ts new file mode 100644 index 00000000..ae08f45e --- /dev/null +++ b/src/common/components/mock-components/front-rich-components/tabsbar/business/calc-text-width.ts @@ -0,0 +1,8 @@ +export const calcTextWidth = ( + inputText: string, + fontSize: number, + _fontfamily: string +) => { + const charAverageWidth = fontSize * 0.5; + return inputText.length * charAverageWidth + charAverageWidth / 2; +}; diff --git a/src/common/components/mock-components/front-rich-components/tabsbar/business/tabsbar.business.spec.ts b/src/common/components/mock-components/front-rich-components/tabsbar/business/tabsbar.business.spec.ts new file mode 100644 index 00000000..5e6b414b --- /dev/null +++ b/src/common/components/mock-components/front-rich-components/tabsbar/business/tabsbar.business.spec.ts @@ -0,0 +1,42 @@ +import { describe, it, expect } from 'vitest'; +import { adjustTabWidths } from './tabsbar.business'; + +const _sum = (resultado: number[]) => + resultado.reduce((acc, current) => acc + current, 0); + +describe('tabsbar.business tests', () => { + it('should return a new array of numbers, which sum is less than or equal to totalWidth', () => { + // Arrange + const tabs = [ + 'Text', + 'Normal text for tab', + 'Extra large text for a tab', + 'Really really large text for a tab', + 'xs', + ]; + const containerWidth = 1000; + const minTabWidth = 100; + const tabsGap = 10; + + // Act + const result = adjustTabWidths({ + tabs, + containerWidth, + minTabWidth, + tabXPadding: 20, + tabsGap, + font: { fontSize: 14, fontFamily: 'Arial' }, + }); + + console.log({ tabs }, { containerWidth }, { minTabWidth }); + console.log({ result }); + + const totalSum = _sum(result.widthList) + (tabs.length - 1) * tabsGap; + console.log('totalSum: ', totalSum); + + // Assert + expect(result.widthList[0]).not.toBe(0); + expect(result.widthList.length).toBe(tabs.length); + expect(totalSum).toBeLessThanOrEqual(containerWidth); + }); +}); diff --git a/src/common/components/mock-components/front-rich-components/tabsbar/business/tabsbar.business.ts b/src/common/components/mock-components/front-rich-components/tabsbar/business/tabsbar.business.ts new file mode 100644 index 00000000..be06ca95 --- /dev/null +++ b/src/common/components/mock-components/front-rich-components/tabsbar/business/tabsbar.business.ts @@ -0,0 +1,77 @@ +import { balanceSpacePerItem } from './balance-space'; +import { calcTextWidth } from './calc-text-width'; + +export const adjustTabWidths = (args: { + tabs: string[]; + containerWidth: number; + minTabWidth: number; + tabXPadding: number; + tabsGap: number; + font: { + fontSize: number; + fontFamily: string; + }; +}) => { + const { tabs, containerWidth, minTabWidth, tabXPadding, tabsGap, font } = + args; + const totalInnerXPadding = tabXPadding * 2; + const totalMinTabSpace = minTabWidth + totalInnerXPadding; + const containerWidthWithoutTabGaps = + containerWidth - (tabs.length - 1) * tabsGap; + + //Create info Object with originalPositions and desired width + interface OriginalTabInfo { + initialTabPosition: number; + desiredWidth: number; + } + const arrangeTabsInfo = tabs.reduce( + (acc: OriginalTabInfo[], tab, index): OriginalTabInfo[] => { + const tabFullTextWidth = + calcTextWidth(tab, font.fontSize, font.fontFamily) + totalInnerXPadding; + const desiredWidth = Math.max(totalMinTabSpace, tabFullTextWidth); + return [ + ...acc, + { + initialTabPosition: index, + desiredWidth, + }, + ]; + }, + [] + ); + + // This order is neccessary to build layer by layer the new sizes + const ascendentTabList = arrangeTabsInfo.sort( + (a, b) => a.desiredWidth - b.desiredWidth + ); + + const onlyWidthList = ascendentTabList.map(tab => tab.desiredWidth); + // Apply adjustments + const adjustedSizeList = balanceSpacePerItem( + onlyWidthList, + containerWidthWithoutTabGaps + ); + + // Reassemble new data with the original order + const reassembledData = ascendentTabList.reduce( + (accList: number[], current, index) => { + const newList = [...accList]; + newList[current.initialTabPosition] = adjustedSizeList[index]; + return newList; + }, + [] + ); + + // Calc item offset position + let sumOfXposition = 0; + const relativeTabPosition = reassembledData.reduce( + (acc: number[], el, index) => { + const currentElementXPos = index ? sumOfXposition : 0; + sumOfXposition += el + tabsGap; + return [...acc, currentElementXPos]; + }, + [] + ); + + return { widthList: reassembledData, relativeTabPosition }; +}; From bf367dcd402d3ddd549148c9d5c97160ba8dba92 Mon Sep 17 00:00:00 2001 From: Alber EE <122263897+Alber-Writer@users.noreply.github.com> Date: Thu, 7 Nov 2024 22:41:08 +0100 Subject: [PATCH 08/11] adjust tabsbar text-width methods --- .../tabsbar/business/balance-space.ts | 6 +- .../tabsbar/business/calc-text-width.ts | 26 ++++++- .../tabsbar/business/tabsbar.business.ts | 16 ++--- .../tabsbar/tabsbar-shape.tsx | 68 ++++++++++++------- 4 files changed, 77 insertions(+), 39 deletions(-) diff --git a/src/common/components/mock-components/front-rich-components/tabsbar/business/balance-space.ts b/src/common/components/mock-components/front-rich-components/tabsbar/business/balance-space.ts index 8de0a60c..8e683166 100644 --- a/src/common/components/mock-components/front-rich-components/tabsbar/business/balance-space.ts +++ b/src/common/components/mock-components/front-rich-components/tabsbar/business/balance-space.ts @@ -16,7 +16,7 @@ export const balanceSpacePerItem = ( const lastItemSize: number = index > 0 ? newList[index - 1] : 0; - // Once the maximum possible size of the item is reached, apply this size directly. + // A) Once the maximum possible size of the item is reached, apply this size directly. if (maxItemSize.value) { totalSpaceUsed.add(maxItemSize.value); return [...newList, lastItemSize]; @@ -27,7 +27,7 @@ export const balanceSpacePerItem = ( const timesToApply = arr.length - index; const virtualTotalsSum = totalSpaceUsed.value + current * timesToApply; - /** First "Bigger" tab behaviour: If the virtual-sum of next items using this size + /** B) First "Bigger" tab behaviour: If the virtual-sum of next items using this size * doesn't fit within available space, calc maxItemSize */ if (virtualTotalsSum >= availableSpace) { const remainder = @@ -40,7 +40,7 @@ export const balanceSpacePerItem = ( return [...newList, maxItemSize.value]; } - //"Normal" behaviour: Apply this new size to current + // C) "Normal" behaviour: Apply proposed new size to current totalSpaceUsed.add(current); return [...newList, current]; }, []); diff --git a/src/common/components/mock-components/front-rich-components/tabsbar/business/calc-text-width.ts b/src/common/components/mock-components/front-rich-components/tabsbar/business/calc-text-width.ts index ae08f45e..91535f05 100644 --- a/src/common/components/mock-components/front-rich-components/tabsbar/business/calc-text-width.ts +++ b/src/common/components/mock-components/front-rich-components/tabsbar/business/calc-text-width.ts @@ -3,6 +3,28 @@ export const calcTextWidth = ( fontSize: number, _fontfamily: string ) => { - const charAverageWidth = fontSize * 0.5; - return inputText.length * charAverageWidth + charAverageWidth / 2; + const REGX_DEFS = { + LOWERCASE_BIG: /[mwoq]/, + LOWERCASE: /[a-z0-9]/, + UPPERCASE_BIG: /[MWOQ]/, + UPPERCASE: /[A-Z]/, + MARKS: /[.,;:!?'"(){}\[\]\-]/, + SPACE: /\s/, + DEFAULT: 'default', + }; + + const calcLength = [...inputText].reduce((sum, current) => { + const testChar = (regx: RegExp) => regx.test(current); + const addLength = (value: number) => sum + fontSize * value; + + if (testChar(REGX_DEFS.LOWERCASE_BIG)) return addLength(0.85); + if (testChar(REGX_DEFS.LOWERCASE)) return addLength(0.5); + if (testChar(REGX_DEFS.SPACE)) return addLength(0.2); + if (testChar(REGX_DEFS.UPPERCASE_BIG)) return addLength(0.95); + if (testChar(REGX_DEFS.UPPERCASE)) return addLength(0.7); + if (testChar(REGX_DEFS.MARKS)) return addLength(0.3); + return addLength(0.5); + }, 0); + + return calcLength; }; diff --git a/src/common/components/mock-components/front-rich-components/tabsbar/business/tabsbar.business.ts b/src/common/components/mock-components/front-rich-components/tabsbar/business/tabsbar.business.ts index be06ca95..da48e9ea 100644 --- a/src/common/components/mock-components/front-rich-components/tabsbar/business/tabsbar.business.ts +++ b/src/common/components/mock-components/front-rich-components/tabsbar/business/tabsbar.business.ts @@ -19,9 +19,9 @@ export const adjustTabWidths = (args: { const containerWidthWithoutTabGaps = containerWidth - (tabs.length - 1) * tabsGap; - //Create info Object with originalPositions and desired width + //Create info List with originalPositions and desired width interface OriginalTabInfo { - initialTabPosition: number; + originalTabPosition: number; desiredWidth: number; } const arrangeTabsInfo = tabs.reduce( @@ -32,7 +32,7 @@ export const adjustTabWidths = (args: { return [ ...acc, { - initialTabPosition: index, + originalTabPosition: index, desiredWidth, }, ]; @@ -40,7 +40,7 @@ export const adjustTabWidths = (args: { [] ); - // This order is neccessary to build layer by layer the new sizes + // This order is necessary to build layer by layer the new sizes const ascendentTabList = arrangeTabsInfo.sort( (a, b) => a.desiredWidth - b.desiredWidth ); @@ -56,18 +56,18 @@ export const adjustTabWidths = (args: { const reassembledData = ascendentTabList.reduce( (accList: number[], current, index) => { const newList = [...accList]; - newList[current.initialTabPosition] = adjustedSizeList[index]; + newList[current.originalTabPosition] = adjustedSizeList[index]; return newList; }, [] ); - // Calc item offset position + // Calc item offset position (mixed with external variable to avoid adding to reducer() extra complexity) let sumOfXposition = 0; const relativeTabPosition = reassembledData.reduce( - (acc: number[], el, index) => { + (acc: number[], currentTab, index) => { const currentElementXPos = index ? sumOfXposition : 0; - sumOfXposition += el + tabsGap; + sumOfXposition += currentTab + tabsGap; return [...acc, currentElementXPos]; }, [] diff --git a/src/common/components/mock-components/front-rich-components/tabsbar/tabsbar-shape.tsx b/src/common/components/mock-components/front-rich-components/tabsbar/tabsbar-shape.tsx index b562c563..f560557e 100644 --- a/src/common/components/mock-components/front-rich-components/tabsbar/tabsbar-shape.tsx +++ b/src/common/components/mock-components/front-rich-components/tabsbar/tabsbar-shape.tsx @@ -8,6 +8,7 @@ import { splitCSVContentIntoRows, } from '@/common/utils/active-element-selector.utils'; import { useGroupShapeProps } from '../../mock-components.utils'; +import { adjustTabWidths } from './business/tabsbar.business'; const tabsBarShapeSizeRestrictions: ShapeSizeRestrictions = { minWidth: 450, @@ -47,11 +48,21 @@ export const TabsBarShape = forwardRef((props, ref) => { const tabLabels = headers.map(header => header.text); // Calculate tab dimensions and margin - const tabWidth = 106; // Width of each tab + const minTabWidth = 40; // Min-width of each tab, without xPadding const tabHeight = 30; // Tab height const tabMargin = 10; // Horizontal margin between tabs + const tabXPadding = 20; + const tabFont = { fontSize: 14, fontFamily: 'Arial' }; const bodyHeight = restrictedHeight - tabHeight - 10; // Height of the tabs bar body - const totalTabsWidth = tabLabels.length * (tabWidth + tabMargin) + tabWidth; // Total width required plus one additional tab + + const tabAdjustedWidths = adjustTabWidths({ + tabs: tabLabels, + containerWidth: restrictedWidth - tabMargin * 2, //left and right tabList margin + minTabWidth, + tabXPadding, + tabsGap: tabMargin, + font: tabFont, + }); const activeTab = otherProps?.activeElement ?? 0; @@ -68,36 +79,41 @@ export const TabsBarShape = forwardRef((props, ref) => { {/* Map through headerRow to create tabs */} - {tabLabels.map((header, index) => ( - - - - - ))} + {tabLabels.map((header, index) => { + const { widthList: newWidthList, relativeTabPosition: xPosList } = + tabAdjustedWidths; + + return ( + + + + + ); + })} ); }); From 97becd4933e112fb8bea8535afd8918dfe66f15f Mon Sep 17 00:00:00 2001 From: Alber EE <122263897+Alber-Writer@users.noreply.github.com> Date: Tue, 12 Nov 2024 14:18:38 +0100 Subject: [PATCH 09/11] switch method to canvas context approach --- .../tabsbar/business/calc-text-width.ts | 34 ++++++------------- 1 file changed, 10 insertions(+), 24 deletions(-) diff --git a/src/common/components/mock-components/front-rich-components/tabsbar/business/calc-text-width.ts b/src/common/components/mock-components/front-rich-components/tabsbar/business/calc-text-width.ts index 91535f05..f83a5fbe 100644 --- a/src/common/components/mock-components/front-rich-components/tabsbar/business/calc-text-width.ts +++ b/src/common/components/mock-components/front-rich-components/tabsbar/business/calc-text-width.ts @@ -1,30 +1,16 @@ export const calcTextWidth = ( inputText: string, fontSize: number, - _fontfamily: string + fontfamily: string ) => { - const REGX_DEFS = { - LOWERCASE_BIG: /[mwoq]/, - LOWERCASE: /[a-z0-9]/, - UPPERCASE_BIG: /[MWOQ]/, - UPPERCASE: /[A-Z]/, - MARKS: /[.,;:!?'"(){}\[\]\-]/, - SPACE: /\s/, - DEFAULT: 'default', - }; + // Creates an invisible canvas to perform the measurement + let canvas = document.createElement('canvas'); + const context = canvas.getContext('2d'); - const calcLength = [...inputText].reduce((sum, current) => { - const testChar = (regx: RegExp) => regx.test(current); - const addLength = (value: number) => sum + fontSize * value; - - if (testChar(REGX_DEFS.LOWERCASE_BIG)) return addLength(0.85); - if (testChar(REGX_DEFS.LOWERCASE)) return addLength(0.5); - if (testChar(REGX_DEFS.SPACE)) return addLength(0.2); - if (testChar(REGX_DEFS.UPPERCASE_BIG)) return addLength(0.95); - if (testChar(REGX_DEFS.UPPERCASE)) return addLength(0.7); - if (testChar(REGX_DEFS.MARKS)) return addLength(0.3); - return addLength(0.5); - }, 0); - - return calcLength; + if (context) { + context.font = `${fontSize}px ${fontfamily}`; + return context.measureText(inputText).width; + } + const charAverageWidth = fontSize * 0.7; + return inputText.length * charAverageWidth + charAverageWidth * 0.8; }; From c02f40e3e9a845e1e0db7c4646d2a02eafa303a1 Mon Sep 17 00:00:00 2001 From: Alber EE <122263897+Alber-Writer@users.noreply.github.com> Date: Tue, 12 Nov 2024 15:39:41 +0100 Subject: [PATCH 10/11] refactor tabsbar with hook --- .../tabsbar/tab-list.hook.ts | 60 +++++++++++++++++++ .../tabsbar/tabsbar-shape.tsx | 38 ++++-------- 2 files changed, 73 insertions(+), 25 deletions(-) create mode 100644 src/common/components/mock-components/front-rich-components/tabsbar/tab-list.hook.ts diff --git a/src/common/components/mock-components/front-rich-components/tabsbar/tab-list.hook.ts b/src/common/components/mock-components/front-rich-components/tabsbar/tab-list.hook.ts new file mode 100644 index 00000000..a4c6694f --- /dev/null +++ b/src/common/components/mock-components/front-rich-components/tabsbar/tab-list.hook.ts @@ -0,0 +1,60 @@ +import { useEffect, useState } from 'react'; +import { adjustTabWidths } from './business/tabsbar.business'; +import { + extractCSVHeaders, + splitCSVContentIntoRows, +} from '@/common/utils/active-element-selector.utils'; + +interface TabListConfig { + text: string; + containerWidth: number; + minTabWidth: number; + tabXPadding: number; + tabsGap: number; + font: { + fontSize: number; + fontFamily: string; + }; +} + +export const useTabList = (tabsConfig: TabListConfig) => { + const { text, containerWidth, minTabWidth, tabXPadding, tabsGap, font } = + tabsConfig; + + const [tabWidthList, setTabWidthList] = useState<{ + widthList: number[]; + relativeTabPosition: number[]; + }>({ widthList: [], relativeTabPosition: [] }); + + const tabLabels = _extractTabLabelTexts(text); + + useEffect(() => { + setTabWidthList( + adjustTabWidths({ + tabs: tabLabels, + containerWidth, + minTabWidth, + tabXPadding, + tabsGap, + font: { + fontSize: font.fontSize, + fontFamily: font.fontFamily, + }, + }) + ); + }, [text, containerWidth]); + + //Return an unique array with all the info required by each tab + return tabLabels.map((tab, index) => ({ + tab, + width: tabWidthList.widthList[index], + xPos: tabWidthList.relativeTabPosition[index], + })); +}; + +// Split text to tab labels List +function _extractTabLabelTexts(text: string) { + const csvData = splitCSVContentIntoRows(text); + const headers = extractCSVHeaders(csvData[0]); + return headers.map(header => header.text); +} diff --git a/src/common/components/mock-components/front-rich-components/tabsbar/tabsbar-shape.tsx b/src/common/components/mock-components/front-rich-components/tabsbar/tabsbar-shape.tsx index f560557e..aabd65b7 100644 --- a/src/common/components/mock-components/front-rich-components/tabsbar/tabsbar-shape.tsx +++ b/src/common/components/mock-components/front-rich-components/tabsbar/tabsbar-shape.tsx @@ -3,12 +3,8 @@ import { Group, Rect, Text } from 'react-konva'; import { ShapeSizeRestrictions, ShapeType } from '@/core/model'; import { fitSizeToShapeSizeRestrictions } from '@/common/utils/shapes/shape-restrictions'; import { ShapeProps } from '../../shape.model'; -import { - extractCSVHeaders, - splitCSVContentIntoRows, -} from '@/common/utils/active-element-selector.utils'; import { useGroupShapeProps } from '../../mock-components.utils'; -import { adjustTabWidths } from './business/tabsbar.business'; +import { useTabList } from './tab-list.hook'; const tabsBarShapeSizeRestrictions: ShapeSizeRestrictions = { minWidth: 450, @@ -43,24 +39,19 @@ export const TabsBarShape = forwardRef((props, ref) => { ); const { width: restrictedWidth, height: restrictedHeight } = restrictedSize; - const csvData = splitCSVContentIntoRows(text); - const headers = extractCSVHeaders(csvData[0]); - const tabLabels = headers.map(header => header.text); - - // Calculate tab dimensions and margin - const minTabWidth = 40; // Min-width of each tab, without xPadding + // Tab dimensions and margin const tabHeight = 30; // Tab height - const tabMargin = 10; // Horizontal margin between tabs + const tabsGap = 10; // Horizontal margin between tabs const tabXPadding = 20; const tabFont = { fontSize: 14, fontFamily: 'Arial' }; const bodyHeight = restrictedHeight - tabHeight - 10; // Height of the tabs bar body - const tabAdjustedWidths = adjustTabWidths({ - tabs: tabLabels, - containerWidth: restrictedWidth - tabMargin * 2, //left and right tabList margin - minTabWidth, + const tabList = useTabList({ + text, + containerWidth: restrictedWidth - tabsGap * 2, //left and right tabList margin + minTabWidth: 40, // Min-width of each tab, without xPadding tabXPadding, - tabsGap: tabMargin, + tabsGap, font: tabFont, }); @@ -86,14 +77,11 @@ export const TabsBarShape = forwardRef((props, ref) => { fill="white" /> {/* Map through headerRow to create tabs */} - {tabLabels.map((header, index) => { - const { widthList: newWidthList, relativeTabPosition: xPosList } = - tabAdjustedWidths; - + {tabList.map(({ tab, width, xPos }, index) => { return ( - + ((props, ref) => { Date: Thu, 14 Nov 2024 23:21:43 +0100 Subject: [PATCH 11/11] improve calc-text-width method reusing the Konva canvas --- .../tabsbar/business/calc-text-width.ts | 42 +++++++++++++++++-- .../tabsbar/business/tabsbar.business.ts | 16 +++++-- .../tabsbar/tab-list.hook.ts | 4 ++ .../tabsbar/tabsbar-shape.tsx | 13 +++--- 4 files changed, 63 insertions(+), 12 deletions(-) diff --git a/src/common/components/mock-components/front-rich-components/tabsbar/business/calc-text-width.ts b/src/common/components/mock-components/front-rich-components/tabsbar/business/calc-text-width.ts index f83a5fbe..843bd15e 100644 --- a/src/common/components/mock-components/front-rich-components/tabsbar/business/calc-text-width.ts +++ b/src/common/components/mock-components/front-rich-components/tabsbar/business/calc-text-width.ts @@ -1,16 +1,50 @@ +import { Layer } from 'konva/lib/Layer'; + +/** + * Virtually calculates the width that a text will occupy, by using a canvas. + * If a Konva Layer is provided, it will reuse the already existing canvas. + * Otherwise, it will create a canvas within the document, on the fly, to perform the measurement. + * Finaly, as a safety net, a very generic calculation is provided in case the other options are not available. + */ export const calcTextWidth = ( inputText: string, fontSize: number, + fontfamily: string, + konvaLayer?: Layer +) => { + if (konvaLayer) + return _getTextWidthByKonvaMethod( + konvaLayer, + inputText, + fontSize, + fontfamily + ); + + return _getTextCreatingNewCanvas(inputText, fontSize, fontfamily); +}; + +const _getTextWidthByKonvaMethod = ( + konvaLayer: Layer, + text: string, + fontSize: number, + fontfamily: string +) => { + const context = konvaLayer.getContext(); + context.font = `${fontSize}px ${fontfamily}`; + return context.measureText(text).width; +}; + +const _getTextCreatingNewCanvas = ( + text: string, + fontSize: number, fontfamily: string ) => { - // Creates an invisible canvas to perform the measurement let canvas = document.createElement('canvas'); const context = canvas.getContext('2d'); - if (context) { context.font = `${fontSize}px ${fontfamily}`; - return context.measureText(inputText).width; + return context.measureText(text).width; } const charAverageWidth = fontSize * 0.7; - return inputText.length * charAverageWidth + charAverageWidth * 0.8; + return text.length * charAverageWidth + charAverageWidth * 0.8; }; diff --git a/src/common/components/mock-components/front-rich-components/tabsbar/business/tabsbar.business.ts b/src/common/components/mock-components/front-rich-components/tabsbar/business/tabsbar.business.ts index da48e9ea..ce08f8bb 100644 --- a/src/common/components/mock-components/front-rich-components/tabsbar/business/tabsbar.business.ts +++ b/src/common/components/mock-components/front-rich-components/tabsbar/business/tabsbar.business.ts @@ -1,3 +1,4 @@ +import { Layer } from 'konva/lib/Layer'; import { balanceSpacePerItem } from './balance-space'; import { calcTextWidth } from './calc-text-width'; @@ -11,9 +12,17 @@ export const adjustTabWidths = (args: { fontSize: number; fontFamily: string; }; + konvaLayer?: Layer; }) => { - const { tabs, containerWidth, minTabWidth, tabXPadding, tabsGap, font } = - args; + const { + tabs, + containerWidth, + minTabWidth, + tabXPadding, + tabsGap, + font, + konvaLayer, + } = args; const totalInnerXPadding = tabXPadding * 2; const totalMinTabSpace = minTabWidth + totalInnerXPadding; const containerWidthWithoutTabGaps = @@ -27,7 +36,8 @@ export const adjustTabWidths = (args: { const arrangeTabsInfo = tabs.reduce( (acc: OriginalTabInfo[], tab, index): OriginalTabInfo[] => { const tabFullTextWidth = - calcTextWidth(tab, font.fontSize, font.fontFamily) + totalInnerXPadding; + calcTextWidth(tab, font.fontSize, font.fontFamily, konvaLayer) + + totalInnerXPadding; const desiredWidth = Math.max(totalMinTabSpace, tabFullTextWidth); return [ ...acc, diff --git a/src/common/components/mock-components/front-rich-components/tabsbar/tab-list.hook.ts b/src/common/components/mock-components/front-rich-components/tabsbar/tab-list.hook.ts index a4c6694f..e115f4c2 100644 --- a/src/common/components/mock-components/front-rich-components/tabsbar/tab-list.hook.ts +++ b/src/common/components/mock-components/front-rich-components/tabsbar/tab-list.hook.ts @@ -4,6 +4,7 @@ import { extractCSVHeaders, splitCSVContentIntoRows, } from '@/common/utils/active-element-selector.utils'; +import { useCanvasContext } from '@/core/providers'; interface TabListConfig { text: string; @@ -28,6 +29,8 @@ export const useTabList = (tabsConfig: TabListConfig) => { const tabLabels = _extractTabLabelTexts(text); + const konvaLayer = useCanvasContext().stageRef.current?.getLayers()[0]; + useEffect(() => { setTabWidthList( adjustTabWidths({ @@ -40,6 +43,7 @@ export const useTabList = (tabsConfig: TabListConfig) => { fontSize: font.fontSize, fontFamily: font.fontFamily, }, + konvaLayer, }) ); }, [text, containerWidth]); diff --git a/src/common/components/mock-components/front-rich-components/tabsbar/tabsbar-shape.tsx b/src/common/components/mock-components/front-rich-components/tabsbar/tabsbar-shape.tsx index aabd65b7..d76cb6cf 100644 --- a/src/common/components/mock-components/front-rich-components/tabsbar/tabsbar-shape.tsx +++ b/src/common/components/mock-components/front-rich-components/tabsbar/tabsbar-shape.tsx @@ -40,10 +40,10 @@ export const TabsBarShape = forwardRef((props, ref) => { const { width: restrictedWidth, height: restrictedHeight } = restrictedSize; // Tab dimensions and margin - const tabHeight = 30; // Tab height - const tabsGap = 10; // Horizontal margin between tabs + const tabHeight = 30; + const tabsGap = 10; const tabXPadding = 20; - const tabFont = { fontSize: 14, fontFamily: 'Arial' }; + const tabFont = { fontSize: 14, fontFamily: 'Arial, sans-serif' }; const bodyHeight = restrictedHeight - tabHeight - 10; // Height of the tabs bar body const tabList = useTabList({ @@ -79,7 +79,8 @@ export const TabsBarShape = forwardRef((props, ref) => { {/* Map through headerRow to create tabs */} {tabList.map(({ tab, width, xPos }, index) => { return ( - + + {/* || 0 Workaround to avoid thumbpage NaN issue with konva */} ((props, ref) => {