diff --git a/public/locales/en/list.json b/public/locales/en/list.json index ddb04d1..6a6788b 100644 --- a/public/locales/en/list.json +++ b/public/locales/en/list.json @@ -113,36 +113,36 @@ "trimItemsDescription": "Remove leading and trailing spaces before comparing items.", "uniqueItemOptions": "Unique Item Options" }, - "group": { + "chunk": { "deleteEmptyItems": "Delete Empty Items", - "deleteEmptyItemsDescription": "Ignore empty items and don't include them in the groups.", - "description": "World's simplest browser-based utility for grouping list items. Input your list and specify grouping criteria to organize items into logical groups. Perfect for categorizing data, organizing information, or creating structured lists. Supports custom separators and various grouping options.", + "deleteEmptyItemsDescription": "Ignore empty items and exclude them from the result.", + "description": "Split a list into consecutive chunks of a fixed size. This tool separates input items, chunks them sequentially, optionally pads incomplete chunks, and formats the output using custom separators and wrappers.", "emptyItemsAndPadding": "Empty Items and Padding", - "groupNumberDescription": "Number of items in a group", - "groupSeparatorDescription": "Group separator character", - "groupSizeAndSeparators": "Group Size and Separators", + "groupNumberDescription": "Number of items per group", + "groupSeparatorDescription": "Separator placed between groups", + "groupSizeAndSeparators": "Group Size and Output Format", "inputItemSeparator": "Input Item Separator", "inputTitle": "Input list", - "itemSeparatorDescription": "Item separator character", - "leftWrapDescription": "Group's left wrap symbol.", - "padNonFullGroups": "Pad Non-full Groups", - "padNonFullGroupsDescription": "Fill non-full groups with a custom item (enter below).", - "paddingCharDescription": "Use this character or item to pad non-full groups.", - "resultTitle": "Grouped items", - "rightWrapDescription": "Group's right wrap symbol.", - "shortDescription": "Group list items by common properties", + "itemSeparatorDescription": "Separator placed between items inside a group", + "leftWrapDescription": "Symbol added before each group", + "padNonFullGroups": "Pad Incomplete Groups", + "padNonFullGroupsDescription": "Fill the last group if it has fewer items than required.", + "paddingCharDescription": "Character or value used to pad incomplete groups.", + "resultTitle": "Grouped output", + "rightWrapDescription": "Symbol added after each group", + "shortDescription": "Split a list into fixed-size chunks", "splitOperators": { "regex": { - "description": "Delimit input list items with a regular expression.", - "title": "Use a Regex for Splitting" + "description": "Split input items using a regular expression.", + "title": "Use Regex Splitting" }, "symbol": { - "description": "Delimit input list items with a character.", - "title": "Use a Symbol for Splitting" + "description": "Split input items using a single character.", + "title": "Use Symbol Splitting" } }, - "splitSeparatorDescription": "Set a delimiting symbol or regular expression.", - "title": "Group" + "splitSeparatorDescription": "Delimiter symbol or regular expression used to split input items.", + "title": "Chunk List" }, "reverse": { "description": "This is a super simple browser-based application prints all list items in reverse. The input items can be separated by any symbol and you can also change the separator of the reversed list items.", diff --git a/src/pages/tools/list/group/group.service.test.ts b/src/pages/tools/list/group/group.service.test.ts index 5eed8d0..6fe72e1 100644 --- a/src/pages/tools/list/group/group.service.test.ts +++ b/src/pages/tools/list/group/group.service.test.ts @@ -1,8 +1,8 @@ import { describe, expect, it } from 'vitest'; -import { groupList, SplitOperatorType } from './service'; +import { chunkList, SplitOperatorType } from './service'; -describe('groupList', () => { +describe('chunkList', () => { it('splits by symbol, groups, pads, and formats correctly', () => { const input = 'a,b,c,d,e,f,g,h,i,j'; const splitOperatorType: SplitOperatorType = 'symbol'; @@ -18,7 +18,7 @@ describe('groupList', () => { const expectedOutput = '[a-b-c] | [d-e-f] | [g-h-i] | [j-x-x]'; - const result = groupList( + const result = chunkList( splitOperatorType, splitSeparator, input, @@ -49,7 +49,7 @@ describe('groupList', () => { const expectedOutput = '(a,b,c,d) / (e,f,g,h) / (i,j)'; - const result = groupList( + const result = chunkList( splitOperatorType, splitSeparator, input, @@ -80,7 +80,7 @@ describe('groupList', () => { const expectedOutput = ' & & '; - const result = groupList( + const result = chunkList( splitOperatorType, splitSeparator, input, diff --git a/src/pages/tools/list/group/index.tsx b/src/pages/tools/list/group/index.tsx index 84377be..968bbb0 100644 --- a/src/pages/tools/list/group/index.tsx +++ b/src/pages/tools/list/group/index.tsx @@ -2,9 +2,10 @@ import { Box } from '@mui/material'; import React, { useState } from 'react'; import ToolTextInput from '@components/input/ToolTextInput'; import ToolTextResult from '@components/result/ToolTextResult'; -import { groupList, SplitOperatorType } from './service'; +import { chunkList, SplitOperatorType } from './service'; import SimpleRadio from '@components/options/SimpleRadio'; import TextFieldWithDesc from '@components/options/TextFieldWithDesc'; +import { CardExampleType } from '@components/examples/ToolExamples'; import CheckboxWithDesc from '@components/options/CheckboxWithDesc'; import { formatNumber } from '../../../../utils/number'; import ToolContent from '@components/ToolContent'; @@ -23,24 +24,101 @@ const initialValues = { padNonFullGroup: false, paddingChar: '...' }; -const splitOperators: { - title: string; - description: string; - type: SplitOperatorType; -}[] = [ + +type InitialValuesType = typeof initialValues; + +const splitOperators: SplitOperatorType[] = ['symbol', 'regex']; + +const exampleCards: CardExampleType[] = [ { - title: 'Use a Symbol for Splitting', - description: 'Delimit input list items with a character.', - type: 'symbol' + title: 'Group Hexagon Coordinates', + description: + 'In this example, we group the coordinates of a regular hexagon. The input coordinates are given as a space-separated list "x1 y1 x2 y2 x3 y3 …". What we want to do is create vector point pairs such as "(x1, y1); (x2, y2); (x3, y3); …". To do that, we use the space character as the input coordinate separator, and to create vectors, we group them by pairs. We wrap the coordinates in parentheses, put a comma between the x and y group items, and a semicolon between individual groups.', + sampleText: '2.5 9.33 0 5 2.5 0.66 7.5 0.66 10 5 7.5 9.33', + sampleResult: `(2.5, 9.33); (0, 5); (2.5, 0.66); (7.5, 0.66); (10, 5); (7.5, 9.33)`, + sampleOptions: { + splitOperatorType: 'symbol', + splitSeparator: ' ', + groupNumber: 2, + itemSeparator: ', ', + leftWrap: '(', + rightWrap: ')', + groupSeparator: '; ', + deleteEmptyItems: true, + padNonFullGroup: false, + paddingChar: 'x' + } }, { - title: 'Use a Regex for Splitting', - type: 'regex', - description: 'Delimit input list items with a regular expression.' + title: 'Chunks of Size 3', + description: + 'This example demonstrates grouping of list items and creates 9 groups of 3 items. The input list contains all alphabet letters (26 letters, separated by a semicolon) and the output is groups of letter trigrams. As the last group is missing one letter, we enable padding and add the underscore symbol as the padding element.', + sampleText: 'a;b;c;d;e;f;g;h;i;j;k;l;m;n;o;p;q;r;s;t;u;v;w;x;y;z', + sampleResult: `[a, b, c] +[d, e, f] +[g, h, i] +[j, k, l] +[m, n, o] +[p, q, r] +[s, t, u] +[v, w, x] +[y, z, _]`, + sampleOptions: { + splitOperatorType: 'symbol', + splitSeparator: ';', + groupNumber: 3, + itemSeparator: ',', + leftWrap: '[', + rightWrap: ']', + groupSeparator: '\\n', + deleteEmptyItems: false, + padNonFullGroup: true, + paddingChar: '_' + } + }, + { + title: 'Convert a List to a TSV', + description: + 'In this example, we use our list item grouper to convert a food list to tab-separated values (TSV). As spaces are chaotically used between the items of the input list, we use the item separating regular expression "\\s+" to match them. We create a TSV with three columns (three groups), separate them with a tab character, and put newlines between the groups.', + sampleText: `beef buns + cake corn + crab +dill +fish + kiwi kale + + lime meat +mint + milk + pear plum + pate + pork rice +soup + tuna + tart`, + sampleResult: `beef buns cake +corn crab dill +fish kiwi kale +lime meat mint +milk pear plum +pate pork rice +soup tuna tart`, + sampleOptions: { + splitOperatorType: 'regex', + splitSeparator: '\\s+', + groupNumber: 3, + itemSeparator: '\\t', + leftWrap: '', + rightWrap: '', + groupSeparator: '\\n', + deleteEmptyItems: true, + padNonFullGroup: false, + paddingChar: 'x' + } } ]; -export default function FindUnique({ title }: ToolComponentProps) { +export default function ChunkList({ title }: ToolComponentProps) { const { t } = useTranslation('list'); const [input, setInput] = useState(''); const [result, setResult] = useState(''); @@ -59,7 +137,7 @@ export default function FindUnique({ title }: ToolComponentProps) { } = optionsValues; setResult( - groupList( + chunkList( splitOperatorType, splitSeparator, input, @@ -79,33 +157,34 @@ export default function FindUnique({ title }: ToolComponentProps) { } resultComponent={ - + } initialValues={initialValues} getGroups={({ values, updateField }) => [ { - title: t('group.inputItemSeparator'), + title: t('chunk.inputItemSeparator'), component: ( - {splitOperators.map(({ title, description, type }) => ( + {splitOperators.map((type) => ( updateField('splitOperatorType', type)} - title={t(`group.splitOperators.${type}.title`)} - description={t(`group.splitOperators.${type}.description`)} + title={t(`chunk.splitOperators.${type}.title`)} + description={t(`chunk.splitOperators.${type}.description`)} checked={values.splitOperatorType === type} /> ))} updateField('splitSeparator', val)} /> @@ -113,12 +192,12 @@ export default function FindUnique({ title }: ToolComponentProps) { ) }, { - title: t('group.groupSizeAndSeparators'), + title: t('chunk.groupSizeAndSeparators'), component: ( updateField('groupNumber', formatNumber(value, 1)) @@ -126,46 +205,46 @@ export default function FindUnique({ title }: ToolComponentProps) { /> updateField('itemSeparator', value)} /> updateField('groupSeparator', value)} /> updateField('leftWrap', value)} /> updateField('rightWrap', value)} /> ) }, { - title: t('group.emptyItemsAndPadding'), + title: t('chunk.emptyItemsAndPadding'), component: ( updateField('deleteEmptyItems', value)} /> updateField('padNonFullGroup', value)} /> updateField('paddingChar', value)} /> diff --git a/src/pages/tools/list/group/meta.ts b/src/pages/tools/list/group/meta.ts index d6fb646..76131ee 100644 --- a/src/pages/tools/list/group/meta.ts +++ b/src/pages/tools/list/group/meta.ts @@ -2,15 +2,15 @@ import { defineTool } from '@tools/defineTool'; import { lazy } from 'react'; export const tool = defineTool('list', { - path: 'group', - icon: 'pajamas:group', + path: 'chunk', + icon: 'mdi:rhombus-split', - keywords: ['group'], + keywords: ['chuck', 'list', 'partition', 'split'], component: lazy(() => import('./index')), i18n: { - name: 'list:group.title', - description: 'list:group.description', - shortDescription: 'list:group.shortDescription', + name: 'list:chunk.title', + description: 'list:chunk.description', + shortDescription: 'list:chunk.shortDescription', userTypes: ['generalUsers', 'developers'] } }); diff --git a/src/pages/tools/list/group/service.ts b/src/pages/tools/list/group/service.ts index dff7d7d..0772397 100644 --- a/src/pages/tools/list/group/service.ts +++ b/src/pages/tools/list/group/service.ts @@ -1,25 +1,25 @@ export type SplitOperatorType = 'symbol' | 'regex'; // function that split the array into an array of subarray of desired length -function groupMaker(array: string[], groupNumber: number): string[][] { +function chunkMaker(array: string[], chunkNumber: number): string[][] { const result: string[][] = []; - for (let i = 0; i < array.length; i += groupNumber) { - result.push(array.slice(i, i + groupNumber)); + for (let i = 0; i < array.length; i += chunkNumber) { + result.push(array.slice(i, i + chunkNumber)); } return result; } -// function use to handle the case paddingNonFullGroup is enable -function groupFiller( +// function use to handle the case paddingNonFullChunk is enable +function chunkFiller( array: string[][], - groupNumber: number, - padNonFullGroup: boolean, + chunkNumber: number, + padNonFullChunk: boolean, paddingChar: string = '' ): string[][] { - if (padNonFullGroup) { + if (padNonFullChunk) { const lastSubArray: string[] = array[array.length - 1]; - if (lastSubArray.length < groupNumber) { - for (let i = lastSubArray.length; i < groupNumber; i++) { + if (lastSubArray.length < chunkNumber) { + for (let i = lastSubArray.length; i < chunkNumber; i++) { lastSubArray.push(paddingChar); } } @@ -29,7 +29,7 @@ function groupFiller( } // function that join with the item separator and wrap with left and right each subArray of the Array -function groupJoinerAndWrapper( +function chunkJoinerAndWrapper( array: string[][], itemSeparator: string = '', leftWrap: string = '', @@ -40,23 +40,23 @@ function groupJoinerAndWrapper( }); } -export function groupList( +export function chunkList( splitOperatorType: SplitOperatorType, splitSeparator: string, input: string, - groupNumber: number, + chunkNumber: number, itemSeparator: string = '', leftWrap: string = '', rightWrap: string = '', - groupSeparator: string, + chunkSeparator: string, deleteEmptyItems: boolean, - padNonFullGroup: boolean, + padNonFullChunk: boolean, paddingChar: string = '' ): string { + if (!splitSeparator) return ''; + let array: string[]; - let splitedArray: string[][]; - let fullSplitedArray: string[][]; - let result: string[]; + switch (splitOperatorType) { case 'symbol': array = input.split(splitSeparator); @@ -71,24 +71,24 @@ export function groupList( } // split the input into an array of subArray with the desired length - splitedArray = groupMaker(array, groupNumber); + const splitedArray = chunkMaker(array, chunkNumber); - // fill the last subArray is PadNonFullGroup is enabled - fullSplitedArray = groupFiller( + // fill the last subArray is PadNonFullChunk is enabled + const fullSplitedArray = chunkFiller( splitedArray, - groupNumber, - padNonFullGroup, + chunkNumber, + padNonFullChunk, paddingChar ); // get the list of formated subArray with the item separator and left and right wrapper - result = groupJoinerAndWrapper( + const result = chunkJoinerAndWrapper( fullSplitedArray, itemSeparator, leftWrap, rightWrap ); - // finnaly join the group separator before returning - return result.join(groupSeparator); + // finnaly join the chunk separator before returning + return result.join(chunkSeparator); }