Skip to content

Commit

Permalink
Merge pull request #464 from jay-hodgson/PORTALS-2724
Browse files Browse the repository at this point in the history
  • Loading branch information
jay-hodgson authored Sep 10, 2023
2 parents 7c99e86 + 76973bf commit 85dc754
Show file tree
Hide file tree
Showing 5 changed files with 163 additions and 136 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down
Original file line number Diff line number Diff line change
@@ -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 = <MarkdownSynapse markdown={content} />
}
const show =
hasClickedShowMore || descriptionConfig?.showFullDescriptionByDefault
return (
<div className={show ? '' : 'SRC-hidden'}>
<span
data-search-handle={descriptionSubTitle}
className={`SRC-font-size-base ${CARD_LONG_DESCRIPTION_CSS}`}
>
{content}
</span>
</div>
)
}

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 (
<div className={hasClickedShowMore ? 'SRC-hidden' : ''}>
<span
data-search-handle={descriptionSubTitle}
className={`SRC-font-size-base ${CARD_SHORT_DESCRIPTION_CSS} SRC-short-description`}
>
{getCutoff(description).previewText}
</span>
{description.length >= CHAR_COUNT_CUTOFF && (
<Link
style={{
fontSize: '16px',
cursor: 'pointer',
marginLeft: '5px',
}}
onClick={toggleShowMore}
>
...Show More
</Link>
)}
</div>
)
}

export type CollapsibleDescriptionProps = {
description?: string
descriptionConfig?: DescriptionConfig
descriptionSubTitle: string
}

export const CollapsibleDescription: React.FC<CollapsibleDescriptionProps> = ({
description,
descriptionSubTitle,
descriptionConfig,
}) => {
const [hasClickedShowMore, setClickedShowMore] = useState<boolean>(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 && (
<ShortDescription
description={description}
hasClickedShowMore={hasClickedShowMore}
descriptionSubTitle={descriptionSubTitle}
descriptionConfig={descriptionConfig}
toggleShowMore={() => setClickedShowMore(true)}
/>
)}
{description && (
<LongDescription
description={description}
hasClickedShowMore={hasClickedShowMore}
descriptionSubTitle={descriptionSubTitle}
descriptionConfig={descriptionConfig}
/>
)}
</>
)
}
132 changes: 10 additions & 122 deletions packages/synapse-react-client/src/components/GenericCard/GenericCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand All @@ -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
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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 = <MarkdownSynapse markdown={content} />
}
const show =
hasClickedShowMore || descriptionConfig?.showFullDescriptionByDefault
return (
<div className={show ? '' : 'SRC-hidden'}>
<span
data-search-handle={descriptionSubTitle}
className={`SRC-font-size-base ${CARD_LONG_DESCRIPTION_CSS}`}
>
{content}
</span>
</div>
)
}

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 (
<div className={hasClickedShowMore ? 'SRC-hidden' : ''}>
<span
data-search-handle={descriptionSubTitle}
className={`SRC-font-size-base ${CARD_SHORT_DESCRIPTION_CSS} SRC-short-description`}
>
{getCutoff(description).previewText}
</span>
{description.length >= CHAR_COUNT_CUTOFF && (
<Link
style={{
fontSize: '16px',
cursor: 'pointer',
marginLeft: '5px',
}}
onClick={toggleShowMore}
>
...Show More
</Link>
)}
</div>
)
}

/**
* Renders a card from a table query
*/
class _GenericCard extends React.Component<
GenericCardPropsInternal,
GenericCardState
> {
class _GenericCard extends React.Component<GenericCardPropsInternal> {
static contextType = SynapseContext

constructor(props: GenericCardPropsInternal) {
Expand Down Expand Up @@ -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 =
Expand Down Expand Up @@ -676,28 +581,11 @@ class _GenericCard extends React.Component<
{subTitle}
</div>
)}
{/*
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 && (
<ShortDescription
description={description}
hasClickedShowMore={hasClickedShowMore}
descriptionSubTitle={descriptionSubTitle}
descriptionConfig={descriptionConfig}
toggleShowMore={this.toggleShowMore}
/>
)}
{description && (
<LongDescription
description={description}
hasClickedShowMore={hasClickedShowMore}
descriptionSubTitle={descriptionSubTitle}
descriptionConfig={descriptionConfig}
/>
)}
<CollapsibleDescription
description={description}
descriptionSubTitle={descriptionSubTitle}
descriptionConfig={descriptionConfig}
/>
{ctaLinkConfig && ctaHref && ctaTarget && (
<Box sx={{ mt: '20px' }}>
<Link
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,18 @@ export type {
KeyToAliasMap,
GenericCardSchema,
GenericCardPropsInternal,
GenericCardState,
} from './GenericCard'
export {
default,
default as GenericCard,
getCardLinkHref,
getLinkParams,
LongDescription,
ShortDescription,
getFileHandleAssociation,
getValueOrMultiValue,
CARD_SHORT_DESCRIPTION_CSS,
} from './GenericCard'
export {
LongDescription,
ShortDescription,
CARD_SHORT_DESCRIPTION_CSS,
} from './CollapsibleDescription'
export { SynapseCardLabel }
21 changes: 11 additions & 10 deletions packages/synapse-react-client/src/components/HeaderCard.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { CardFooter } from './row_renderers/utils'
import { DescriptionConfig } from './CardContainerLogic'
import MarkdownSynapse from './Markdown/MarkdownSynapse'
import React, { useState, useEffect } from 'react'
import { CollapsibleDescription } from './GenericCard/CollapsibleDescription'

export type HeaderCardProps = {
rgbIndex?: number
Expand Down Expand Up @@ -36,6 +36,11 @@ const HeaderCard: React.FunctionComponent<HeaderCardProps> = ({
const descriptionElement: Element | null = document.querySelector(
'meta[name="description"]',
)
const descriptionConfiguration: DescriptionConfig = {
...descriptionConfig,
showFullDescriptionByDefault:
descriptionConfig?.showFullDescriptionByDefault ?? true,
}
const [docTitle] = useState<string>(document.title)
const [docDescription] = useState<string>(
descriptionElement ? descriptionElement.getAttribute('content')! : '',
Expand Down Expand Up @@ -89,15 +94,11 @@ const HeaderCard: React.FunctionComponent<HeaderCardProps> = ({
</h3>
</div>
{subTitle && <div className="SRC-author"> {subTitle} </div>}
{description && (
<span className="SRC-font-size-base">
{descriptionConfig?.isMarkdown ? (
<MarkdownSynapse markdown={description} />
) : (
description
)}
</span>
)}
<CollapsibleDescription
description={description}
descriptionSubTitle=""
descriptionConfig={descriptionConfiguration}
/>
</div>
<div
style={{
Expand Down

0 comments on commit 85dc754

Please sign in to comment.