diff --git a/src/common/components/EditableText.js b/src/common/components/EditableText.js index d11cf6eeb..cf8ef5889 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, @@ -106,7 +109,7 @@ const EditableText = props => { if (isEditing) { return ( - + {label} @@ -133,6 +136,14 @@ const EditableText = props => { ); } + const textValue = truncateLongNames ? ( + + {value} + + ) : ( + value + ); + return ( { ...(viewProps?.sx || {}), }} > - {value || + {addMessage}} + {textValue || + {addMessage}} {isHovering && } ); diff --git a/src/common/components/MiddleEllipsis.js b/src/common/components/MiddleEllipsis.js new file mode 100644 index 000000000..657d13460 --- /dev/null +++ b/src/common/components/MiddleEllipsis.js @@ -0,0 +1,84 @@ +/* + * 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 = 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 + ); + } + }, []); + const measuredParent = useCallback( + node => { + if (node !== null) { + window.addEventListener('resize', () => { + prepEllipses(node); + }); + prepEllipses(node); + } + }, + [prepEllipses] + ); + + 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; diff --git a/src/sharedData/components/SharedDataEntryBase.js b/src/sharedData/components/SharedDataEntryBase.js index 31c848226..96253c140 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'; @@ -138,7 +139,7 @@ const SharedDataEntryBase = props => { @@ -146,17 +147,25 @@ const SharedDataEntryBase = props => { {dataEntry.name}} + FallbackComponent={() => ( + + {dataEntry.name} + + )} > @@ -164,8 +173,8 @@ const SharedDataEntryBase = props => { {info}