From 6a2b1bc6595f03d5c493255c4377262c668df13d Mon Sep 17 00:00:00 2001 From: storywithoutend Date: Mon, 15 Apr 2024 11:45:23 +0800 Subject: [PATCH] added tolerance and ZWNJ split to name --- src/components/@atoms/Name/Name.tsx | 4 +- .../Name/utils/calculateInlineName.test.ts | 51 +++++++++++++++++-- .../@atoms/Name/utils/calculateInlineName.ts | 34 +++++++++---- .../Name/utils/calculateWrapName.test.ts | 39 +++++++++----- .../@atoms/Name/utils/calculateWrapName.ts | 40 ++++++++++----- .../@atoms/Name/utils/sharedFunctions.test.ts | 23 +++++++++ .../@atoms/Name/utils/sharedFunctions.ts | 4 ++ src/components/@atoms/Name2/Name.tsx | 4 +- .../@atoms/StyledName/StyledName.tsx | 1 + .../@molecules/NameListView/NameListView.tsx | 2 +- .../[name]/registration/steps/Complete.tsx | 11 +++- 11 files changed, 167 insertions(+), 46 deletions(-) create mode 100644 src/components/@atoms/Name/utils/sharedFunctions.test.ts create mode 100644 src/components/@atoms/Name/utils/sharedFunctions.ts diff --git a/src/components/@atoms/Name/Name.tsx b/src/components/@atoms/Name/Name.tsx index e3c6f2633..da6364ee1 100644 --- a/src/components/@atoms/Name/Name.tsx +++ b/src/components/@atoms/Name/Name.tsx @@ -80,12 +80,12 @@ export const Name = ({ const initialWidth = containerWidth + containerLeft! - nodeRect.left const ellipsisWidth = ellipsisRef.current?.offsetWidth || 0 return calculateWrapName({ - name: children, + name_: children, node, ellipsisWidth, maxWidth: containerWidth, initialWidth, - lines: wrapLines, + maxLines: wrapLines, }) }) .otherwise(() => { diff --git a/src/components/@atoms/Name/utils/calculateInlineName.test.ts b/src/components/@atoms/Name/utils/calculateInlineName.test.ts index e782806e3..51377249c 100644 --- a/src/components/@atoms/Name/utils/calculateInlineName.test.ts +++ b/src/components/@atoms/Name/utils/calculateInlineName.test.ts @@ -3,6 +3,10 @@ import { calculateInlineName } from './calculateInlineName'; const jsdom = require('jsdom'); const { JSDOM } = jsdom; +const longLabel = 'areallyreallyreallyreallyreallyreallyreallyreallyreallyreallyreallyreallyreallyreallyreallyreallyreallyreallyreallyreallyreallyreallyreallyreallyreallyreallyreallyreallyreallyreallyreallyreallyreallyreallyreallyreallyreallylonglabel' +const longName = 'areallyreallyreallyreallyreallyreallyreallyreallyreallyreallyreallyreallyreallyreallyreallyreallyreallyreallyreallyreallyreallyreallyreallyreallyreallyreallyreallyreallyreallyreallyreallyreallyreallyreallyreallyreallyreallylongname.eth' +const longSubname = `${longLabel}.${longName}` + const createNode = (str: string) => { const chars = str.split(''); const innerHtml = chars.map((char) => `${char}`).join('') @@ -38,14 +42,51 @@ const createNode = (str: string) => { return dom.window.document.getElementById('root') } +const removeNonNameSymbols = (str: string) => str.replace(/[\u2026\u200B\u200C]/g, '') + describe('calculateInlineName', () => { - it('should return null if the node is null', () => { + it('should return the correct result if the first label ends on the right side', () => { + const result = calculateInlineName({ + name: longName, + node: createNode(longName), + ellipsisWidth: 5, + maxWidth: 100, + }) + expect(result).toBe('areallyre…​gname‌.eth') + const ZWNJSplit = result.split('\u200C') + expect(ZWNJSplit).toHaveLength(2) + const ZWSSplit = result.split('\u200B') + console.log(removeNonNameSymbols(ZWSSplit[0]), removeNonNameSymbols(ZWSSplit[1])) + expect(removeNonNameSymbols(ZWSSplit[0]).length).toBe(removeNonNameSymbols(ZWSSplit[1]).length) + }) + + it('should return the correct result if the first label ends on the left side', () => { + const shortSubname = `test.${longName}` + const result = calculateInlineName({ + name: shortSubname, + node: createNode(shortSubname), + ellipsisWidth: 5, + maxWidth: 100, + }) + expect(result).toBe('test\u200C.area…​gname.eth') + const ZWNJSplit = result.split('\u200C') + expect(ZWNJSplit).toHaveLength(2) + const ZWSSplit = result.split('\u200B') + console.log(removeNonNameSymbols(ZWSSplit[0]), removeNonNameSymbols(ZWSSplit[1])) + expect(removeNonNameSymbols(ZWSSplit[0]).length).toBe(removeNonNameSymbols(ZWSSplit[1]).length) + }) + + it('should return the correct result if the first label ends in the middle', () => { const result = calculateInlineName({ - name: 'helloworld!', - node: createNode('helloworld!'), + name: longSubname, + node: createNode(longSubname), ellipsisWidth: 5, - maxWidth: 30, + maxWidth: 100, }) - expect(result).toBe('he…​d!') + expect(result).toBe('areallyre…\u200C\u200Bgname.eth') + const ZWNJSplit = result.split('\u200C') + expect(ZWNJSplit).toHaveLength(2) + const ZWSSplit = result.split('\u200B') + expect(removeNonNameSymbols(ZWSSplit[0]).length).toBe(removeNonNameSymbols(ZWSSplit[1]).length) }) }) \ No newline at end of file diff --git a/src/components/@atoms/Name/utils/calculateInlineName.ts b/src/components/@atoms/Name/utils/calculateInlineName.ts index 407ea61d2..9a8de35ce 100644 --- a/src/components/@atoms/Name/utils/calculateInlineName.ts +++ b/src/components/@atoms/Name/utils/calculateInlineName.ts @@ -1,24 +1,33 @@ +import { insertZeroWidthNonJoinerAtLabel } from './sharedFunctions' + export const calculateInlineName = ({ name, node, ellipsisWidth, maxWidth, + tolerance = 5, debug = false, }: { name: string node: HTMLSpanElement | null ellipsisWidth: number maxWidth: number + tolerance?: number debug?: boolean }) => { - if (debug) console.log('calculateInlineName', name, node, ellipsisWidth, maxWidth) - if (!node) return name + const _name = insertZeroWidthNonJoinerAtLabel(name) + if (debug) console.log('calculateInlineName', _name, node, ellipsisWidth, maxWidth) + if (!node) return _name - const parentElementWidth = maxWidth ?? node.parentElement?.offsetWidth ?? Infinity + const _maxWidth = maxWidth ?? node.parentElement?.offsetWidth ?? Infinity const nodeWidth = node.offsetWidth || Infinity - if (debug) console.log('nodeWidth', nodeWidth, 'parentElementWidth', parentElementWidth) - if (nodeWidth <= parentElementWidth) return name + if (debug) console.log('nodeWidth', nodeWidth, 'parentElementWidth', _maxWidth) + if (nodeWidth <= _maxWidth) return _name + + // We use a tolerance because the offsetWidth of the individual characters are rounded to the nearest integer, which creates a potential for inaccuracies. + const _tolerance = 1 - Math.max(0, Math.min(100, tolerance)) / 100 + const maxWidthWithTolerance = _maxWidth * _tolerance const children = node?.children || [] let slice = 0 @@ -27,10 +36,17 @@ export const calculateInlineName = ({ const element = children[index] as HTMLSpanElement const matchElement = children[children.length - 1 - index] as HTMLSpanElement total += element.offsetWidth + matchElement.offsetWidth - if (total > parentElementWidth) - return `${name.slice(0, slice)}\u2026\u200B${name.slice(name.length - slice)}` + if (total >= maxWidthWithTolerance) { + const right = _name.slice(_name.length - slice) + if (right.includes('\u200C')) + return `${_name.slice(0, slice)}\u2026\u200B${_name.slice(_name.length - slice - 1)}` + const left = _name.slice(0, slice) + if (left.includes('\u200C')) + return `${_name.slice(0, slice + 1)}\u2026\u200B${_name.slice(_name.length - slice)}` + return `${left}\u2026\u200C\u200B${right}` + } slice += 1 } - if (debug) console.log('name', name) - return name + if (debug) console.log('name', _name) + return _name } diff --git a/src/components/@atoms/Name/utils/calculateWrapName.test.ts b/src/components/@atoms/Name/utils/calculateWrapName.test.ts index dc02bc24f..10b652795 100644 --- a/src/components/@atoms/Name/utils/calculateWrapName.test.ts +++ b/src/components/@atoms/Name/utils/calculateWrapName.test.ts @@ -23,6 +23,8 @@ const createNode = (str: string) => { return dom.window.document.getElementById('root') } +const removeSpecialCharacters = (str: string) => str.replace(/[\u2026\u200B\u200C]/g, '') + describe('findNumbersAddingUpToSum', () => { it('should return numbers that add up just below the sum', () => { const result = findNumbersAddingUpToSum([1, 2, 3, 4, 5], 7) @@ -63,27 +65,38 @@ describe('sliceStringByNumbers', () => { }) describe('calculateWrapName', () => { - it('should return the correct result', () => { + const longName = 'areallyreallyreallyreallyreallyreallyreallyreallyreallyreallyreallyreallyreallyreallyreallyreallyreallyreallyreallyreallyreallyreallyreallyreallyreallyreallyreallyreallyreallyreallyreallyreallyreallyreallyreallyreallyreallylongname.eth' + it.only('should return the correct result', () => { const result = calculateWrapName({ - name: 'helloworld!', - node: createNode('helloworld!'), + name: longName, + node: createNode(longName), ellipsisWidth: 5, - initialWidth: 10, - maxWidth: 20, - lines: Infinity + initialWidth: 100, + maxWidth: 500, + maxLines: Infinity }) - expect(result).toEqual('h…​ell…​owo…​rld…​!') + expect(result).toEqual('areallyreallyreally…​reallyreallyreallyreallyreallyreallyreallyreallyreallyreallyreallyreallyreallyreallyreallyreallyrea…​llyreallyreallyreallyreallyreallyreallyreallyreallyreallyreallyreallyreallyreallyreallyreallyreally…​reallylongname.eth') + console.log('areallyreallyreally…​reallyreallyreallyreallyreallyreallyreallyreallyreallyreallyreallyreallyreallyreallyreallyreallyrea…​llyreallyreallyreallyreallyreallyreallyreallyreallyreallyreallyreallyreallyreallyreallyreallyreally…​eallylongname‌.eth') + const resultParts = result.split('…​') + expect(resultParts[0]).toHaveLength(19) + expect(resultParts[1]).toHaveLength(99) + expect(resultParts[2]).toHaveLength(99) + expect(resultParts[3]).toHaveLength(longName.length - 19 - 99 - 99) }) it('should return the correct result', () => { const result = calculateWrapName({ - name: 'helloworld!', - node: createNode('helloworld!'), + name: longName, + node: createNode(longName), ellipsisWidth: 5, - initialWidth: 10, - maxWidth: 20, - lines: 2 + initialWidth: 100, + maxWidth: 500, + maxLines: 2 }) - expect(result).toEqual('h…​rld!') + expect(result).toEqual('areallyreallyreally…​allyreallyreallyreallyreallyreallyreallyreallyreallyreallyreallyreallyreallyreallyreallylongname\u200C.eth') + console.log('areallyreallyreally…​llyreallyreallyreallyreallyreallyreallyreallyreallyreallyreallyreallyreallyreallyreallylongname‌.eth') + const resultParts = result.split('…​') + expect(resultParts[0]).toHaveLength(19) + expect(resultParts[1]).toHaveLength(100) }) }) diff --git a/src/components/@atoms/Name/utils/calculateWrapName.ts b/src/components/@atoms/Name/utils/calculateWrapName.ts index 97ee14147..3db62ea93 100644 --- a/src/components/@atoms/Name/utils/calculateWrapName.ts +++ b/src/components/@atoms/Name/utils/calculateWrapName.ts @@ -1,3 +1,5 @@ +import { insertZeroWidthNonJoinerAtLabel } from './sharedFunctions' + export const findNumbersAddingUpToSum = (numbers: number[], sum: number) => { let index = 0 let total = 0 @@ -30,16 +32,18 @@ export const calculateWrapName = ({ maxWidth, initialWidth = maxWidth, minInitialWidth = 0, - lines = Infinity, + maxLines = Infinity, + tolerance = 5, debug = false, }: { name: string node: HTMLSpanElement | null ellipsisWidth: number - maxWidth?: number + maxWidth: number initialWidth?: number minInitialWidth?: number - lines?: number + maxLines?: number + tolerance?: number debug?: boolean }): string => { if (debug) @@ -51,28 +55,38 @@ export const calculateWrapName = ({ maxWidth, initialWidth, minInitialWidth, - lines, + maxLines, ) + + const name_ = insertZeroWidthNonJoinerAtLabel(name) if (!node) { console.error('node is null') - return name + return name_ } + const _maxWdth = maxWidth ?? node.parentElement?.offsetWidth ?? Infinity + const containerWidth = node.offsetWidth || Infinity + if (containerWidth <= _maxWdth) return name_ + let currentGroup: number[] = [] let currentGroupTotal = 0 let result: number[][] = [] const initialWidth_ = initialWidth < minInitialWidth ? maxWidth : initialWidth + const decimalTolerance = 1 - Math.max(0, Math.min(100, tolerance)) / 100 + const initialWidthWithTolerance = initialWidth_ * decimalTolerance + const maxWidthWithTolerance = maxWidth * decimalTolerance + const children = node?.children || [] for (let index = 0; index < children.length; index += 1) { const element = children[index] as HTMLSpanElement const charWidth = element.offsetWidth currentGroupTotal += charWidth - const breakpoint = result.length === 0 ? initialWidth_ : maxWidth + const currentMaxWidth = result.length === 0 ? initialWidthWithTolerance : maxWidthWithTolerance if (debug) - console.log('charWidth', charWidth, 'currentGroupTotal', currentGroupTotal, breakpoint) - if (currentGroupTotal + ellipsisWidth > breakpoint) { + console.log('charWidth', charWidth, 'currentGroupTotal', currentGroupTotal, currentMaxWidth) + if (currentGroupTotal + ellipsisWidth >= currentMaxWidth) { result.push(currentGroup) currentGroup = [charWidth] currentGroupTotal = charWidth @@ -83,10 +97,10 @@ export const calculateWrapName = ({ if (currentGroup.length) result.push(currentGroup) // console.log(result.length, lines) - if (result.length > lines) { - const left = result.slice(0, lines - 1) + if (result.length > maxLines) { + const left = result.slice(0, maxLines - 1) const right = result - .slice(lines - 1) + .slice(maxLines - 1) .reverse() .flat() // console.log('left', left, right) @@ -97,7 +111,7 @@ export const calculateWrapName = ({ const slices = result.map((group) => group.length) const [last, ...reversedFirstSegments] = slices.reverse() const firstSegments = reversedFirstSegments.reverse() - const firstNames = sliceStringByNumbers(firstSegments, name) - const lastSegment = name.slice(-last) + const firstNames = sliceStringByNumbers(firstSegments, name_) + const lastSegment = name_.slice(-last) return [...firstNames, lastSegment].join('\u2026\u200B') } diff --git a/src/components/@atoms/Name/utils/sharedFunctions.test.ts b/src/components/@atoms/Name/utils/sharedFunctions.test.ts new file mode 100644 index 000000000..3d448e7a8 --- /dev/null +++ b/src/components/@atoms/Name/utils/sharedFunctions.test.ts @@ -0,0 +1,23 @@ +import { describe, it, expect } from 'vitest' + +import { insertZeroWidthNonJoinerAtLabel } from './sharedFunctions' + + +describe('insertZeroWidthNonJoinerAtLabel', () => { + it('should insert a ZWNJ after the first label', () => { + const name = insertZeroWidthNonJoinerAtLabel('name.com') + expect(name).toBe('name\u200C.com') + }) + + it('should insert a ZWNJ after the first label with multiple labels', () => { + const name = insertZeroWidthNonJoinerAtLabel('name.co.uk') + expect(name).toBe('name\u200C.co.uk') + }) + + it('should be able to split resulting name by ZWNJ', () => { + const name = insertZeroWidthNonJoinerAtLabel('name.co.uk') + const [label, ...rest] = name.split('\u200C') + expect(label).toBe('name') + expect(rest.join('')).toBe('.co.uk') + }) +}) \ No newline at end of file diff --git a/src/components/@atoms/Name/utils/sharedFunctions.ts b/src/components/@atoms/Name/utils/sharedFunctions.ts new file mode 100644 index 000000000..251eddb79 --- /dev/null +++ b/src/components/@atoms/Name/utils/sharedFunctions.ts @@ -0,0 +1,4 @@ +export const insertZeroWidthNonJoinerAtLabel = (name: string) => { + const [label, ...rest] = name.split('.') + return [`${label}\u200C`, ...rest].join('.') +} diff --git a/src/components/@atoms/Name2/Name.tsx b/src/components/@atoms/Name2/Name.tsx index cb2a404b3..e4223bf2f 100644 --- a/src/components/@atoms/Name2/Name.tsx +++ b/src/components/@atoms/Name2/Name.tsx @@ -111,13 +111,13 @@ export const Name = ({ const initialWidth_ = initialWidth ?? maxWidth_ - hiddenLeft + rootLeft return calculateWrapName({ - name: children, + name_: children, node: hiddenRef.current, ellipsisWidth, maxWidth: Math.round(maxWidth_ * 0.95), initialWidth: Math.round(initialWidth_ * 0.95), minInitialWidth, - lines: wrapLines, + maxLines: wrapLines, debug, }) }) diff --git a/src/components/@atoms/StyledName/StyledName.tsx b/src/components/@atoms/StyledName/StyledName.tsx index ad3642747..2d751a425 100644 --- a/src/components/@atoms/StyledName/StyledName.tsx +++ b/src/components/@atoms/StyledName/StyledName.tsx @@ -9,6 +9,7 @@ const Container = styled.div( font-weight: ${theme.fontWeights.bold}; line-height: 1.36; overflow: hidden; + background: yellow; `, ) diff --git a/src/components/@molecules/NameListView/NameListView.tsx b/src/components/@molecules/NameListView/NameListView.tsx index c9d490bfb..f2f7bc7fb 100644 --- a/src/components/@molecules/NameListView/NameListView.tsx +++ b/src/components/@molecules/NameListView/NameListView.tsx @@ -164,7 +164,7 @@ export const NameListView = ({ address, selfAddress, setError, setLoading }: Nam ) const isLoading = isNamesLoading || !address - + console.log('names', names) let InnerContent: ReactNode if (!isMounted) { InnerContent = null diff --git a/src/components/pages/profile/[name]/registration/steps/Complete.tsx b/src/components/pages/profile/[name]/registration/steps/Complete.tsx index 0901eef0f..3062ce131 100644 --- a/src/components/pages/profile/[name]/registration/steps/Complete.tsx +++ b/src/components/pages/profile/[name]/registration/steps/Complete.tsx @@ -70,6 +70,12 @@ const TitleContainer = styled.div( `, ) +const NameContainer = styled.span( + () => css` + word-break: break-all; + `, +) + const Title = styled(Typography)( ({ theme }) => css` font-size: ${theme.fontSizes.headingOne}; @@ -282,7 +288,10 @@ const Complete = ({ name, beautifiedName, callback, isMoonpayFlow }: Props) => { {t('steps.complete.heading')} {t('steps.complete.subheading')} - {nameWithColourEmojis} +
+ + {nameWithColourEmojis} +
{t('steps.complete.description')}