From 81783ecad10d0ccfa0f5039df93a2db67f26f461 Mon Sep 17 00:00:00 2001 From: Paul Schreiber Date: Thu, 30 Nov 2023 17:26:58 -0500 Subject: [PATCH 1/6] feat: add MiddleEllipsis component --- src/common/components/MiddleEllipsis.js | 81 +++++++++++++++++++++++++ 1 file changed, 81 insertions(+) create mode 100644 src/common/components/MiddleEllipsis.js diff --git a/src/common/components/MiddleEllipsis.js b/src/common/components/MiddleEllipsis.js new file mode 100644 index 000000000..743902c60 --- /dev/null +++ b/src/common/components/MiddleEllipsis.js @@ -0,0 +1,81 @@ +/* + * Copyright © 2021-2023 Technology Matters + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see https://www.gnu.org/licenses/. + */ + +// Based on https://github.com/bluepeter/react-middle-ellipsis + +import React, { useCallback } from 'react'; + +const Component = props => { + const prepEllipses = node => { + const parent = node.parentNode; + const targetTextNode = node.childNodes[0]; + + if (targetTextNode !== null) { + if (targetTextNode.hasAttribute('data-original')) { + targetTextNode.textContent = + targetTextNode.getAttribute('data-original'); + } + + ellipses( + node.offsetWidth < parent.offsetWidth ? node : parent, + targetTextNode + ); + } + }, + measuredParent = useCallback(node => { + if (node !== null) { + window.addEventListener('resize', () => { + prepEllipses(node); + }); + prepEllipses(node); + } + }); + + return ( + + {props.children} + + ); +}; + +const ellipses = (parentNode, textNode) => { + const containerWidth = parentNode.offsetWidth; + const textWidth = textNode.offsetWidth; + + if (textWidth > containerWidth) { + const characterCount = textNode.textContent.length; + const avgLetterSize = textWidth / characterCount; + const canFit = containerWidth / avgLetterSize; + const charactersToRemove = (characterCount - canFit + 5) / 2; + const endLeft = Math.floor(characterCount / 2 - charactersToRemove); + const startRight = Math.ceil(characterCount / 2 + charactersToRemove); + const startText = textNode.textContent.substr(0, endLeft); + const endText = textNode.textContent.substr(startRight); + + textNode.setAttribute('data-original', textNode.textContent); + textNode.textContent = `${startText}…${endText}`; + } +}; + +export default Component; From df6637a04cec8fec6eb33cbbec488e4ebcf29526 Mon Sep 17 00:00:00 2001 From: Paul Schreiber Date: Thu, 30 Nov 2023 17:30:34 -0500 Subject: [PATCH 2/6] feat: middle-truncate long file names (view mode) --- src/sharedData/components/SharedDataEntryBase.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/sharedData/components/SharedDataEntryBase.js b/src/sharedData/components/SharedDataEntryBase.js index 31c848226..998119a40 100644 --- a/src/sharedData/components/SharedDataEntryBase.js +++ b/src/sharedData/components/SharedDataEntryBase.js @@ -25,6 +25,7 @@ import { daysSince } from 'timeUtils'; import ConfirmButton from 'common/components/ConfirmButton'; import EditableText from 'common/components/EditableText'; +import MiddleEllipsis from 'common/components/MiddleEllipsis'; import { formatDate } from 'localization/utils'; import { useAnalytics } from 'monitoring/analytics'; import Restricted from 'permissions/components/Restricted'; @@ -146,7 +147,11 @@ const SharedDataEntryBase = props => { {dataEntry.name}} + FallbackComponent={() => ( + + {dataEntry.name} + + )} > Date: Thu, 30 Nov 2023 18:06:56 -0500 Subject: [PATCH 3/6] fix: middle-truncate editable text field --- src/common/components/EditableText.js | 13 ++++++++++++- src/sharedData/components/SharedDataEntryBase.js | 6 +++++- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/src/common/components/EditableText.js b/src/common/components/EditableText.js index d11cf6eeb..f02a24ab4 100644 --- a/src/common/components/EditableText.js +++ b/src/common/components/EditableText.js @@ -27,6 +27,8 @@ import { Typography, } from '@mui/material'; +import MiddleEllipsis from 'common/components/MiddleEllipsis'; + const EditableText = props => { const { t } = useTranslation(); const { @@ -36,6 +38,7 @@ const EditableText = props => { processing, addMessage, viewProps, + truncateLongNames, onSave, isEditing, setIsEditing, @@ -133,6 +136,14 @@ const EditableText = props => { ); } + const textValue = truncateLongNames ? ( + + {value} + + ) : ( + value + ); + return ( { ...(viewProps?.sx || {}), }} > - {value || + {addMessage}} + {textValue || + {addMessage}} {isHovering && } ); diff --git a/src/sharedData/components/SharedDataEntryBase.js b/src/sharedData/components/SharedDataEntryBase.js index 998119a40..e0984fbc2 100644 --- a/src/sharedData/components/SharedDataEntryBase.js +++ b/src/sharedData/components/SharedDataEntryBase.js @@ -157,11 +157,15 @@ const SharedDataEntryBase = props => { id={`name-${dataEntry.id}`} label={t('sharedData.name_update')} value={dataEntry.name} + truncateLongNames={true} onSave={onUpdateName} processing={processing} isEditing={isEditingName} setIsEditing={setIsEditingName} - viewProps={{ color: 'black', sx: { flexGrow: 1 } }} + viewProps={{ + color: 'black', + sx: { flexGrow: 1, overflow: 'hidden' }, + }} /> From 4214a8bf19e1ee81e0853e3f0406be3ed97c5049 Mon Sep 17 00:00:00 2001 From: Paul Schreiber Date: Fri, 1 Dec 2023 13:36:58 -0500 Subject: [PATCH 4/6] fix: in shared files card, increase width of file names column --- src/common/components/EditableText.js | 2 +- src/sharedData/components/SharedDataEntryBase.js | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/common/components/EditableText.js b/src/common/components/EditableText.js index f02a24ab4..cf8ef5889 100644 --- a/src/common/components/EditableText.js +++ b/src/common/components/EditableText.js @@ -109,7 +109,7 @@ const EditableText = props => { if (isEditing) { return ( - + {label} diff --git a/src/sharedData/components/SharedDataEntryBase.js b/src/sharedData/components/SharedDataEntryBase.js index e0984fbc2..ef5d8b221 100644 --- a/src/sharedData/components/SharedDataEntryBase.js +++ b/src/sharedData/components/SharedDataEntryBase.js @@ -139,7 +139,7 @@ const SharedDataEntryBase = props => { @@ -173,8 +173,8 @@ const SharedDataEntryBase = props => { {info} From fea6305d01ba93e7e551836089a998401b23eb36 Mon Sep 17 00:00:00 2001 From: Paul Schreiber Date: Fri, 1 Dec 2023 13:52:51 -0500 Subject: [PATCH 5/6] fix: correct useCallback() usage * add useCallback to prepEllipses * add dependencies to to measuredParent * declare prepEllipses and measuredParent separately --- src/common/components/MiddleEllipsis.js | 35 ++++++++++++++----------- 1 file changed, 19 insertions(+), 16 deletions(-) diff --git a/src/common/components/MiddleEllipsis.js b/src/common/components/MiddleEllipsis.js index 743902c60..657d13460 100644 --- a/src/common/components/MiddleEllipsis.js +++ b/src/common/components/MiddleEllipsis.js @@ -20,30 +20,33 @@ import React, { useCallback } from 'react'; const Component = props => { - const prepEllipses = node => { - const parent = node.parentNode; - const targetTextNode = node.childNodes[0]; + const prepEllipses = useCallback(node => { + const parent = node.parentNode; + const targetTextNode = node.childNodes[0]; - if (targetTextNode !== null) { - if (targetTextNode.hasAttribute('data-original')) { - targetTextNode.textContent = - targetTextNode.getAttribute('data-original'); - } - - ellipses( - node.offsetWidth < parent.offsetWidth ? node : parent, - targetTextNode - ); + if (targetTextNode !== null) { + if (targetTextNode.hasAttribute('data-original')) { + targetTextNode.textContent = + targetTextNode.getAttribute('data-original'); } - }, - measuredParent = useCallback(node => { + + ellipses( + node.offsetWidth < parent.offsetWidth ? node : parent, + targetTextNode + ); + } + }, []); + const measuredParent = useCallback( + node => { if (node !== null) { window.addEventListener('resize', () => { prepEllipses(node); }); prepEllipses(node); } - }); + }, + [prepEllipses] + ); return ( Date: Fri, 1 Dec 2023 15:40:32 -0500 Subject: [PATCH 6/6] Update src/sharedData/components/SharedDataEntryBase.js Co-authored-by: Jose Buitron --- src/sharedData/components/SharedDataEntryBase.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sharedData/components/SharedDataEntryBase.js b/src/sharedData/components/SharedDataEntryBase.js index ef5d8b221..96253c140 100644 --- a/src/sharedData/components/SharedDataEntryBase.js +++ b/src/sharedData/components/SharedDataEntryBase.js @@ -157,7 +157,7 @@ const SharedDataEntryBase = props => { id={`name-${dataEntry.id}`} label={t('sharedData.name_update')} value={dataEntry.name} - truncateLongNames={true} + truncateLongNames onSave={onUpdateName} processing={processing} isEditing={isEditingName}