Skip to content

Commit

Permalink
fix: middle truncate long file names in shared files list (#1339)
Browse files Browse the repository at this point in the history
* feat: add MiddleEllipsis component
* feat: middle-truncate long file names
* fix: in shared files card, increase width of file names column
---------
Co-authored-by: Jose Buitron <[email protected]>
  • Loading branch information
paulschreiber authored Dec 1, 2023
1 parent a5a5a71 commit e256868
Show file tree
Hide file tree
Showing 3 changed files with 111 additions and 7 deletions.
15 changes: 13 additions & 2 deletions src/common/components/EditableText.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ import {
Typography,
} from '@mui/material';

import MiddleEllipsis from 'common/components/MiddleEllipsis';

const EditableText = props => {
const { t } = useTranslation();
const {
Expand All @@ -36,6 +38,7 @@ const EditableText = props => {
processing,
addMessage,
viewProps,
truncateLongNames,
onSave,
isEditing,
setIsEditing,
Expand Down Expand Up @@ -106,7 +109,7 @@ const EditableText = props => {

if (isEditing) {
return (
<Stack direction="row" spacing={1}>
<Stack direction="row" spacing={1} sx={{ width: '100%' }}>
<InputLabel htmlFor={id} className="visually-hidden">
{label}
</InputLabel>
Expand All @@ -133,6 +136,14 @@ const EditableText = props => {
);
}

const textValue = truncateLongNames ? (
<MiddleEllipsis>
<Typography component="span">{value}</Typography>
</MiddleEllipsis>
) : (
value
);

return (
<Typography
component={Stack}
Expand All @@ -154,7 +165,7 @@ const EditableText = props => {
...(viewProps?.sx || {}),
}}
>
{value || <Link href="#">+ {addMessage}</Link>}
{textValue || <Link href="#">+ {addMessage}</Link>}
{isHovering && <EditIcon sx={{ color: 'blue.dark' }} />}
</Typography>
);
Expand Down
84 changes: 84 additions & 0 deletions src/common/components/MiddleEllipsis.js
Original file line number Diff line number Diff line change
@@ -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 (
<span
ref={measuredParent}
style={{
wordBreak: 'keep-all',
overflowWrap: 'normal',
...(props.width && { width: props.width }),
}}
>
{props.children}
</span>
);
};

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;
19 changes: 14 additions & 5 deletions src/sharedData/components/SharedDataEntryBase.js
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -138,34 +139,42 @@ const SharedDataEntryBase = props => {
<Grid
item
xs={isEditingName ? 12 : 8}
md={4}
md={6}
order={{ xs: 2, md: 2 }}
component={StackRow}
>
<EntryTypeIcon resourceType={dataEntry.resourceType} />
<Restricted
permission="sharedData.edit"
resource={permissionsResource}
FallbackComponent={() => <Typography>{dataEntry.name}</Typography>}
FallbackComponent={() => (
<MiddleEllipsis>
<Typography component="span">{dataEntry.name}</Typography>
</MiddleEllipsis>
)}
>
<EditableText
id={`name-${dataEntry.id}`}
label={t('sharedData.name_update')}
value={dataEntry.name}
truncateLongNames
onSave={onUpdateName}
processing={processing}
isEditing={isEditingName}
setIsEditing={setIsEditingName}
viewProps={{ color: 'black', sx: { flexGrow: 1 } }}
viewProps={{
color: 'black',
sx: { flexGrow: 1, overflow: 'hidden' },
}}
/>
</Restricted>
</Grid>
<Grid item xs={1} order={{ xs: 5 }} display={{ md: 'none' }} />
<Grid
item
xs={11}
md={3}
order={{ xs: 6, md: 3 }}
md={1}
order={{ xs: 6, md: 4 }}
sx={{ wordWrap: 'break-word' }}
>
{info}
Expand Down

0 comments on commit e256868

Please sign in to comment.