From 65c6a1a5527d1a1da360fc80a4473512c968f3e6 Mon Sep 17 00:00:00 2001 From: Jay Hodgson Date: Fri, 8 Sep 2023 14:27:25 -0700 Subject: [PATCH 1/3] PORTALS-2724 --- .../elportal/synapseConfigs/projects.ts | 3 ++ .../src/components/HeaderCard.tsx | 34 +++++++++++++++---- 2 files changed, 30 insertions(+), 7 deletions(-) diff --git a/apps/portals/src/configurations/elportal/synapseConfigs/projects.ts b/apps/portals/src/configurations/elportal/synapseConfigs/projects.ts index e370f6983e..62cdbb4f49 100644 --- a/apps/portals/src/configurations/elportal/synapseConfigs/projects.ts +++ b/apps/portals/src/configurations/elportal/synapseConfigs/projects.ts @@ -17,6 +17,9 @@ import { ColumnMultiValueFunction } from '@sage-bionetworks/synapse-types' const rgbIndex = 4 export const projectCardConfiguration: CardConfiguration = { type: SynapseConstants.GENERIC_CARD, + descriptionConfig: { + showFullDescriptionByDefault: false, + }, genericCardSchema: { type: 'Project', title: 'name', diff --git a/packages/synapse-react-client/src/components/HeaderCard.tsx b/packages/synapse-react-client/src/components/HeaderCard.tsx index 6ba5008936..4c9d127741 100644 --- a/packages/synapse-react-client/src/components/HeaderCard.tsx +++ b/packages/synapse-react-client/src/components/HeaderCard.tsx @@ -2,6 +2,7 @@ import { CardFooter } from './row_renderers/utils' import { DescriptionConfig } from './CardContainerLogic' import MarkdownSynapse from './Markdown/MarkdownSynapse' import React, { useState, useEffect } from 'react' +import { LongDescription, ShortDescription } from './GenericCard' export type HeaderCardProps = { rgbIndex?: number @@ -36,7 +37,13 @@ const HeaderCard: React.FunctionComponent = ({ const descriptionElement: Element | null = document.querySelector( 'meta[name="description"]', ) + const descriptionConfiguration: DescriptionConfig = { + ...descriptionConfig, + showFullDescriptionByDefault: + descriptionConfig?.showFullDescriptionByDefault ?? true, + } const [docTitle] = useState(document.title) + const [hasClickedShowMore, setClickedShowMore] = useState(false) const [docDescription] = useState( descriptionElement ? descriptionElement.getAttribute('content')! : '', ) @@ -89,14 +96,27 @@ const HeaderCard: React.FunctionComponent = ({ {subTitle &&
{subTitle}
} + {/* + Below is a hack that allows word highlighting to work, the Search component insert's + html elements outside of the React DOM which if detected would break the app, + but as written below this avoids that reconcilliation process. + */} {description && ( - - {descriptionConfig?.isMarkdown ? ( - - ) : ( - description - )} - + setClickedShowMore(true)} + /> + )} + {description && ( + )}
Date: Fri, 8 Sep 2023 14:57:06 -0700 Subject: [PATCH 2/3] clean import --- packages/synapse-react-client/src/components/HeaderCard.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/synapse-react-client/src/components/HeaderCard.tsx b/packages/synapse-react-client/src/components/HeaderCard.tsx index 4c9d127741..72457c5ac6 100644 --- a/packages/synapse-react-client/src/components/HeaderCard.tsx +++ b/packages/synapse-react-client/src/components/HeaderCard.tsx @@ -1,6 +1,5 @@ import { CardFooter } from './row_renderers/utils' import { DescriptionConfig } from './CardContainerLogic' -import MarkdownSynapse from './Markdown/MarkdownSynapse' import React, { useState, useEffect } from 'react' import { LongDescription, ShortDescription } from './GenericCard' From 76973bf1d7f01541a416ed2caa33e3e17c1542bf Mon Sep 17 00:00:00 2001 From: Jay Hodgson Date: Fri, 8 Sep 2023 16:38:26 -0700 Subject: [PATCH 3/3] refactor (from code review) --- .../GenericCard/CollapsibleDescription.tsx | 134 ++++++++++++++++++ .../components/GenericCard/GenericCard.tsx | 132 ++--------------- .../src/components/GenericCard/index.ts | 9 +- .../src/components/HeaderCard.tsx | 30 +--- 4 files changed, 155 insertions(+), 150 deletions(-) create mode 100644 packages/synapse-react-client/src/components/GenericCard/CollapsibleDescription.tsx diff --git a/packages/synapse-react-client/src/components/GenericCard/CollapsibleDescription.tsx b/packages/synapse-react-client/src/components/GenericCard/CollapsibleDescription.tsx new file mode 100644 index 0000000000..8f9e373a3f --- /dev/null +++ b/packages/synapse-react-client/src/components/GenericCard/CollapsibleDescription.tsx @@ -0,0 +1,134 @@ +import React, { useState } from 'react' +import { DescriptionConfig } from '../CardContainerLogic' +import { MarkdownSynapse } from '../Markdown' +import { Link } from '@mui/material' + +export const CHAR_COUNT_CUTOFF = 400 + +export const CARD_SHORT_DESCRIPTION_CSS = 'SRC-short-description' +export const CARD_LONG_DESCRIPTION_CSS = 'SRC-long-description' + +// This function isn't in the class only for ease of testing with renderShortDescription +export const getCutoff = (summary: string) => { + let previewText = '' + const summarySplit = summary.split(' ') + // find num words to join such that its >= char_count_cutoff + let i = 0 + while (previewText.length < CHAR_COUNT_CUTOFF && i < summarySplit.length) { + previewText += `${summarySplit[i]} ` + i += 1 + } + previewText = previewText.trim() + return { previewText } +} + +export function LongDescription(props: { + description: string + hasClickedShowMore: boolean + descriptionSubTitle: string + descriptionConfig?: DescriptionConfig +}) { + const { + description, + hasClickedShowMore, + descriptionSubTitle, + descriptionConfig, + } = props + let content: JSX.Element | string = description + if (descriptionConfig?.isMarkdown) { + content = + } + const show = + hasClickedShowMore || descriptionConfig?.showFullDescriptionByDefault + return ( +
+ + {content} + +
+ ) +} + +export function ShortDescription(props: { + description: string + hasClickedShowMore: boolean + descriptionSubTitle: string + descriptionConfig?: DescriptionConfig + toggleShowMore: () => void +}) { + const { + description, + hasClickedShowMore, + descriptionSubTitle, + descriptionConfig, + toggleShowMore, + } = props + if (descriptionConfig?.showFullDescriptionByDefault) { + return <> + } + return ( +
+ + {getCutoff(description).previewText} + + {description.length >= CHAR_COUNT_CUTOFF && ( + + ...Show More + + )} +
+ ) +} + +export type CollapsibleDescriptionProps = { + description?: string + descriptionConfig?: DescriptionConfig + descriptionSubTitle: string +} + +export const CollapsibleDescription: React.FC = ({ + description, + descriptionSubTitle, + descriptionConfig, +}) => { + const [hasClickedShowMore, setClickedShowMore] = useState(false) + /* + Below is a hack that allows word highlighting to work, the Search component insert's + html elements outside of the React DOM which if detected would break the app, + but as written below this avoids that reconcilliation process. + */ + return ( + <> + {description && ( + setClickedShowMore(true)} + /> + )} + {description && ( + + )} + + ) +} diff --git a/packages/synapse-react-client/src/components/GenericCard/GenericCard.tsx b/packages/synapse-react-client/src/components/GenericCard/GenericCard.tsx index 188579200e..15101c0425 100644 --- a/packages/synapse-react-client/src/components/GenericCard/GenericCard.tsx +++ b/packages/synapse-react-client/src/components/GenericCard/GenericCard.tsx @@ -24,13 +24,11 @@ import { CardLink, ColumnIconConfigs, CommonCardProps, - DescriptionConfig, TargetEnum, } from '../CardContainerLogic' import HeaderCard from '../HeaderCard' import IconList from '../IconList' import IconSvg, { type2SvgIconName } from '../IconSvg/IconSvg' -import MarkdownSynapse from '../Markdown/MarkdownSynapse' import { CardFooter, Icon } from '../row_renderers/utils' import { FileHandleLink } from '../widgets/FileHandleLink' import { ImageFileHandle } from '../widgets/ImageFileHandle' @@ -43,6 +41,10 @@ import { calculateFriendlyFileSize } from '../../utils/functions/calculateFriend import { SynapseCardLabel } from './SynapseCardLabel' import { useAtomValue } from 'jotai' import { tableQueryEntityAtom } from '../QueryWrapper/QueryWrapper' +import { + CHAR_COUNT_CUTOFF, + CollapsibleDescription, +} from './CollapsibleDescription' export type KeyToAlias = { key: string @@ -89,28 +91,6 @@ export type GenericCardProps = Omit< 'table' | 'queryVisualizationContext' > -export type GenericCardState = { - hasClickedShowMore: boolean -} - -const CHAR_COUNT_CUTOFF = 400 -export const CARD_SHORT_DESCRIPTION_CSS = 'SRC-short-description' -export const CARD_LONG_DESCRIPTION_CSS = 'SRC-long-description' - -// This function isn't in the class only for ease of testing with renderShortDescription -export const getCutoff = (summary: string) => { - let previewText = '' - const summarySplit = summary.split(' ') - // find num words to join such that its >= char_count_cutoff - let i = 0 - while (previewText.length < CHAR_COUNT_CUTOFF && i < summarySplit.length) { - previewText += `${summarySplit[i]} ` - i += 1 - } - previewText = previewText.trim() - return { previewText } -} - export const getFileHandleAssociation = ( table?: Entity, fileHandleId?: string, @@ -293,84 +273,10 @@ export function getLinkParams( return { href, target } } -export function LongDescription(props: { - description: string - hasClickedShowMore: boolean - descriptionSubTitle: string - descriptionConfig?: DescriptionConfig -}) { - const { - description, - hasClickedShowMore, - descriptionSubTitle, - descriptionConfig, - } = props - let content: JSX.Element | string = description - if (descriptionConfig?.isMarkdown) { - content = - } - const show = - hasClickedShowMore || descriptionConfig?.showFullDescriptionByDefault - return ( -
- - {content} - -
- ) -} - -export function ShortDescription(props: { - description: string - hasClickedShowMore: boolean - descriptionSubTitle: string - descriptionConfig?: DescriptionConfig - toggleShowMore: () => void -}) { - const { - description, - hasClickedShowMore, - descriptionSubTitle, - descriptionConfig, - toggleShowMore, - } = props - if (descriptionConfig?.showFullDescriptionByDefault) { - return <> - } - return ( -
- - {getCutoff(description).previewText} - - {description.length >= CHAR_COUNT_CUTOFF && ( - - ...Show More - - )} -
- ) -} - /** * Renders a card from a table query */ -class _GenericCard extends React.Component< - GenericCardPropsInternal, - GenericCardState -> { +class _GenericCard extends React.Component { static contextType = SynapseContext constructor(props: GenericCardPropsInternal) { @@ -450,7 +356,6 @@ class _GenericCard extends React.Component< // and type, but theres one nuance which is that we can't override if one specific property will be // defined, so we assert genericCardSchema is not null and assign to genericCardSchemaDefined const genericCardSchemaDefined = genericCardSchema! - const { hasClickedShowMore } = this.state const { link = '', type } = genericCardSchemaDefined const title = data[schema[genericCardSchemaDefined.title]] let subTitle = @@ -676,28 +581,11 @@ class _GenericCard extends React.Component< {subTitle}
)} - {/* - Below is a hack that allows word highlighting to work, the Search component insert's - html elements outside of the React DOM which if detected would break the app, - but as written below this avoids that reconcilliation process. - */} - {description && ( - - )} - {description && ( - - )} + {ctaLinkConfig && ctaHref && ctaTarget && ( = ({ descriptionConfig?.showFullDescriptionByDefault ?? true, } const [docTitle] = useState(document.title) - const [hasClickedShowMore, setClickedShowMore] = useState(false) const [docDescription] = useState( descriptionElement ? descriptionElement.getAttribute('content')! : '', ) @@ -95,28 +94,11 @@ const HeaderCard: React.FunctionComponent = ({ {subTitle &&
{subTitle}
} - {/* - Below is a hack that allows word highlighting to work, the Search component insert's - html elements outside of the React DOM which if detected would break the app, - but as written below this avoids that reconcilliation process. - */} - {description && ( - setClickedShowMore(true)} - /> - )} - {description && ( - - )} +