Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -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);
});
});
Original file line number Diff line number Diff line change
@@ -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;

// 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];
}

/** 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;

/** 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 =
availableSpace - (totalSpaceUsed.value + lastItemSize * timesToApply);
const remainderPortionPerItem = Math.floor(remainder / timesToApply);
maxItemSize.set(lastItemSize + remainderPortionPerItem);

totalSpaceUsed.add(maxItemSize.value);

return [...newList, maxItemSize.value];
}

// C) "Normal" behaviour: Apply proposed 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,
};
}
Original file line number Diff line number Diff line change
@@ -0,0 +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
) => {
let canvas = document.createElement('canvas');
const context = canvas.getContext('2d');
if (context) {
context.font = `${fontSize}px ${fontfamily}`;
return context.measureText(text).width;
}
const charAverageWidth = fontSize * 0.7;
return text.length * charAverageWidth + charAverageWidth * 0.8;
};
Original file line number Diff line number Diff line change
@@ -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);
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import { Layer } from 'konva/lib/Layer';
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;
};
konvaLayer?: Layer;
}) => {
const {
tabs,
containerWidth,
minTabWidth,
tabXPadding,
tabsGap,
font,
konvaLayer,
} = args;
const totalInnerXPadding = tabXPadding * 2;
const totalMinTabSpace = minTabWidth + totalInnerXPadding;
const containerWidthWithoutTabGaps =
containerWidth - (tabs.length - 1) * tabsGap;

//Create info List with originalPositions and desired width
interface OriginalTabInfo {
originalTabPosition: number;
desiredWidth: number;
}
const arrangeTabsInfo = tabs.reduce(
(acc: OriginalTabInfo[], tab, index): OriginalTabInfo[] => {
const tabFullTextWidth =
calcTextWidth(tab, font.fontSize, font.fontFamily, konvaLayer) +
totalInnerXPadding;
const desiredWidth = Math.max(totalMinTabSpace, tabFullTextWidth);
return [
...acc,
{
originalTabPosition: index,
desiredWidth,
},
];
},
[]
);

// This order is necessary 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.originalTabPosition] = adjustedSizeList[index];
return newList;
},
[]
);

// Calc item offset position (mixed with external variable to avoid adding to reducer() extra complexity)
let sumOfXposition = 0;
const relativeTabPosition = reassembledData.reduce(
(acc: number[], currentTab, index) => {
const currentElementXPos = index ? sumOfXposition : 0;
sumOfXposition += currentTab + tabsGap;
return [...acc, currentElementXPos];
},
[]
);

return { widthList: reassembledData, relativeTabPosition };
};
Loading