Skip to content

Commit

Permalink
Feature/issue 605 draft issues (#610)
Browse files Browse the repository at this point in the history
* move _redirects to public folder (netlify)

* progressive loading articles page

* add number of articles published to issue

* remove date in Issue label, add name

* add active/disabled class for Issue component

* Update ArticlesFacets.js

* Update translations.json

* add "new" label

* increase space

see issue #605
  • Loading branch information
danieleguido authored Feb 12, 2024
1 parent 2b4f7f1 commit 6a15063
Show file tree
Hide file tree
Showing 14 changed files with 200 additions and 45 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,4 @@ node_modules
.vscode/settings.json
storybook-static
# _redirects
journal-of-digital-history.code-workspace
File renamed without changes.
5 changes: 3 additions & 2 deletions src/components/Articles/ArticlesFacets.js
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ const IssueListItem = (props) => {
const issue = props.items[group.indices[0]].issue
return (
<div className="d-flex align-items-center flex-nowrap" title={issue.name || group.key}>
<IssueLabel pid={issue.pid} publication_date={issue.publication_date} />
<IssueLabel pid={issue.pid} name={issue.name} />
<div>&nbsp;({group.count})</div>
</div>
)
Expand Down Expand Up @@ -105,14 +105,15 @@ const Dimensions = [
},
]

const ArticlesFacets = ({ items, onSelect, className }) => {
const ArticlesFacets = ({ items, onSelect, onShowMore, className }) => {
return (
<Facets
dimensions={Dimensions}
items={items}
onSelect={onSelect}
onInit={(args) => console.debug('[ArticlesFacets] @init', args)}
ShowMoreLabel={ShowMoreLabel}
onShowMore={onShowMore}
className={className}
/>
)
Expand Down
90 changes: 63 additions & 27 deletions src/components/Articles/ArticlesGrid.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,12 @@ import Article from '../../models/Article'
import ArticlesFacets from '../Articles/ArticlesFacets'
import Issue from '../Issue'
import ArticleFingerprintTooltip from '../ArticleV2/ArticleFingerprintTooltip'

import groupBy from 'lodash/groupBy'
import { Container, Row, Col } from 'react-bootstrap'
import { useSpring, config } from 'react-spring'
import { useSpring, config, a } from '@react-spring/web'
import { useHistory } from 'react-router'
import { useBoundingClientRect } from '../../hooks/graphics'
import { useWindowStore } from '../../store'

const ArticlesGrid = ({
items = [],
Expand All @@ -39,6 +39,8 @@ const ArticlesGrid = ({
// tag ategories to keep
categories = ['narrative', 'tool', 'issue'],
}) => {
const facetsRef = useRef()
const timerRef = useRef()
const { t } = useTranslation()
const [{ [OrderByQueryParam]: orderBy }, setQuery] = useQueryParams({
[OrderByQueryParam]: withDefault(
Expand All @@ -64,6 +66,10 @@ const ArticlesGrid = ({
backgroundColor: 'var(--secondary)',
config: config.stiff,
}))
// animation properties to slide up and down the articleFacets block
const [facetsAnimatedProps, setFacetsAnimatedProps] = useSpring(() => ({
height: 0,
}))
const data = (items || []).map((d, idx) => new Article({ ...d, idx }))
const articles = sort(data, AvailablesOrderByComparators[orderBy])
const { articlesByIssue, showFilters } = useMemo(() => {
Expand All @@ -78,14 +84,15 @@ const ArticlesGrid = ({
const sortedItems = data.map((item, idx) => ({
...item,
idx,
selected: selected?.includes(idx),
}))
const articlesByIssue = groupBy(sortedItems, 'issue.pid')

const showFilters = data.reduce((acc, d) => {
return acc || d.tags.some((t) => categories.includes(t.category))
}, false)
return { articlesByIssue, showFilters }
}, [url, status])
}, [url, selected, status])

const onArticleMouseMoveHandler = (e, datum, idx, article, bounds) => {
if (!isNaN(idx) && animatedRef.current.idx !== idx) {
Expand Down Expand Up @@ -154,14 +161,24 @@ const ArticlesGrid = ({
useLayoutEffect(() => {
setAnimatedProps.start({ opacity: 0 })
}, [selected])
console.debug(
'[Articles] \n- articles:',
Array.isArray(articles),
articles,
'\n- issueId:',
issueId,
selected,
)

useLayoutEffect(() => {
if (status === StatusSuccess) {
setFacetsAnimatedProps.start({
height: facetsRef.current.firstChild.scrollHeight,
delay: 1000,
})
return useWindowStore.subscribe(() => {
clearTimeout(timerRef.current)
timerRef.current = setTimeout(() => {
setFacetsAnimatedProps.start({
height: facetsRef.current.firstChild.scrollHeight,
delay: 0,
})
}, 0)
})
}
}, [status])

return (
<Container ref={ref} className="Articles Issue page ">
Expand All @@ -183,27 +200,39 @@ const ArticlesGrid = ({
</div>
</Col>
</Row>
{showFilters && (
<Row className="mb-3">
<Col md={{ offset: 1, span: 10 }}>
{status === StatusSuccess && (
<ArticlesFacets
items={data}
onSelect={onFacetsSelectHandler}
className="Articles_facets"
/>
)}
</Col>
</Row>
)}

<a.div
className="row mb-1 position-relative overflow-hidden"
ref={facetsRef}
style={facetsAnimatedProps}
>
<Col md={{ offset: 1, span: 10 }} className="position-absolute">
{status === StatusSuccess && (
<ArticlesFacets
items={data}
onShowMore={() => {
console.info('[ArticlesGrid] @showMore')
clearTimeout(timerRef.current)
setTimeout(() => {
setFacetsAnimatedProps.start({
height: facetsRef.current.firstChild.scrollHeight,
delay: 0,
})
}, 0)
}}
onSelect={onFacetsSelectHandler}
className="Articles_facets "
/>
)}
</Col>
</a.div>
{orderBy === OrderByIssue &&
issues.map((issue) => {
// const issue = articlesByIssue[id][0].issue
const numArticles = articlesByIssue[issue.pid]?.length
const numSelectedArticles = articlesByIssue[issue.pid]?.filter((d) => d.selected).length

return (
<React.Fragment key={issue.pid}>
<hr />
<a className="anchor" id={`anchor-${issue.pid}`} />

<IssueArticles
Expand All @@ -213,7 +242,14 @@ const ArticlesGrid = ({
onArticleClick={onArticleClickHandler}
onArticleMouseOut={onArticleMouseOutHandler}
>
<Issue item={issue} className="my-2" />
<Issue
numArticles={numArticles}
isInFilterMode={Array.isArray(selected)}
numSelectedArticles={numSelectedArticles}
hasSelectedArticles={!!articlesByIssue[issue.pid]}
item={issue}
className="py-2 mb-1"
/>
</IssueArticles>
</React.Fragment>
)
Expand Down
11 changes: 10 additions & 1 deletion src/components/Facets/Dimension.js
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ const Dimension = ({
onInit,
onSelect,
onMouseEnter,
onShowMore,
children,
ListItem = DimensionGroupListItem,
}) => {
Expand Down Expand Up @@ -174,7 +175,15 @@ const Dimension = ({
))}
{restGroups.length > 0 && (
<li>
<button className="Dimension_toggleShowMoreBtn" onClick={() => setShowMore(!showMore)}>
<button
className="Dimension_toggleShowMoreBtn"
onClick={() => {
if (typeof onShowMore === 'function') {
onShowMore(!showMore)
}
setShowMore(!showMore)
}}
>
<span>
{t(showMore ? 'dimensions.actions.showLess' : 'dimensions.actions.showMore', {
n: restGroups.length,
Expand Down
2 changes: 2 additions & 0 deletions src/components/Facets/Facets.js
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,7 @@ const Facets = ({
onSelect,
onInit,
onMouseEnter,
onShowMore,
className,
style,
}) => {
Expand Down Expand Up @@ -242,6 +243,7 @@ const Facets = ({
onSelect={onSelectHandler}
onInit={onInitHandler}
onMouseEnter={onMouseEnterHandler}
onShowMore={onShowMore}
ListItem={dimension.ListItem}
>
{reset === true && dims[dimension.name].selected.length > 0 && (
Expand Down
15 changes: 15 additions & 0 deletions src/components/Issue/Issue.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
.Issue.disabled > div {
display: none !important;
}
.Issue {
border-top: 1px solid var(--gray-400);
position: sticky;
top: 99px;
background: var(--gray-100);
z-index: 1;
}
.Issue__numArticles b {
border: 1px solid;
padding: 0 6px 0 5px;
border-radius: 5px;
}
44 changes: 40 additions & 4 deletions src/components/Issue/Issue.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,27 @@ import PropTypes from 'prop-types'
import { useTranslation } from 'react-i18next'
import { Col, Row } from 'react-bootstrap'
import { a, useSpring } from '@react-spring/web'
import './Issue.css'

const Issue = ({ item, className = '' }) => {
const Issue = ({
item,
numArticles = -1,
numSelectedArticles = -1,
isInFilterMode = false,
className = '',
}) => {
const ref = useRef()
const { t } = useTranslation()
const descriptionRef = useRef()
const buttonRef = useRef()
const isOpen = useRef(false)

const [{ height }, api] = useSpring(() => ({ height: 0 }))
const { t } = useTranslation()
const label = item.pid.replace(/jdh0+(\d+)/, (m, n) => t('numbers.issue', { n }))

const activeClass = isInFilterMode && numSelectedArticles > 0 ? 'active' : ''
const disabledClass = isInFilterMode && numSelectedArticles < 1 ? 'disabled' : ''

const toggleHeight = () => {
isOpen.current = !isOpen.current
// change label on the button
Expand All @@ -38,9 +48,32 @@ const Issue = ({ item, className = '' }) => {
}, [ref])

return (
<Row className={`Issue align-items-start ${className}`}>
<Row className={`Issue align-items-start ${className} ${activeClass} ${disabledClass}`}>
<Col md={{ span: 11 }} lg={{ span: 5 }} ref={ref}>
{label} {item.status !== 'PUBLISHED' ? <b>&mdash; {t('status.' + item.status)}</b> : null}
{item.status !== 'PUBLISHED' ? <em>{t('status.' + item.status)}</em> : label}
{isInFilterMode && numSelectedArticles > 0 ? (
<>
&nbsp; &mdash; &nbsp;
<div
className="d-inline-block Issue__numArticles"
dangerouslySetInnerHTML={{
__html: t('numbers.articlesFiltered', {
n: numSelectedArticles,
total: numArticles,
}),
}}
/>
</>
) : null}
{!isInFilterMode && numArticles > 0 ? (
<>
&nbsp; &mdash; &nbsp;
<div
className="d-inline-block Issue__numArticles"
dangerouslySetInnerHTML={{ __html: t('numbers.articlesInIssue', { n: numArticles }) }}
/>{' '}
</>
) : null}
<h2>{item.name}</h2>
</Col>

Expand Down Expand Up @@ -74,6 +107,9 @@ Issue.propTypes = {
description: PropTypes.string,
status: PropTypes.string.isRequired,
}).isRequired,
numArticles: PropTypes.number,
numSelectedArticles: PropTypes.number,
isInFilterMode: PropTypes.bool,
className: PropTypes.string,
}

Expand Down
16 changes: 15 additions & 1 deletion src/components/Issue/IssueArticleGridItem.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ import '../../styles/components/IssueArticleGridItem.scss'
import { ArrowRightCircle } from 'react-feather'
import IssueLabel from './IssueLabel'

const JustAddedTimeInterval = 3600000 * 24 * 240

const IssueArticleGridItem = ({
article = {},
isFake = false,
Expand All @@ -25,8 +27,20 @@ const IssueArticleGridItem = ({
const [{ width: size }, ref] = useBoundingClientRect()
const { title, keywords, excerpt, contributor } = extractMetadataFromArticle(article)
const { t } = useTranslation()
const isPrettyRecent = new Date() - new Date(article.publication_date) < JustAddedTimeInterval
console.debug(
'[IssueArticleGridItem]',
article.publication_date,
new Date() - new Date(article.publication_date),
JustAddedTimeInterval,
isPrettyRecent,
)
return (
<div className="IssueArticleGridItem" ref={ref} onMouseOut={onMouseOut}>
<div
className={`IssueArticleGridItem ${isPrettyRecent ? 'just-added' : ''}`}
ref={ref}
onMouseOut={onMouseOut}
>
<div
className={IsMobile ? 'half-squared' : 'squared'}
onMouseOut={onMouseOut}
Expand Down
4 changes: 3 additions & 1 deletion src/components/Issue/IssueArticles.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ const IssueArticles = ({

respectOrdering = false,
children,
className = '',
}) => {
const ref = React.useRef()
const bboxRef = React.useRef()
Expand Down Expand Up @@ -77,8 +78,9 @@ const IssueArticles = ({
console.debug('[IssueArticles] rendered')

return (
<Row ref={ref}>
<Row ref={ref} className={`IssueArticles ${className}`}>
{children}
{data.length > 0 && <Col sm={{ span: 12 }} className="py-3"></Col>}
{editorials.map((article, i) => {
if (Array.isArray(selected) && selected.indexOf(article.idx) === -1) {
return null
Expand Down
17 changes: 14 additions & 3 deletions src/components/Issue/IssueLabel.js
Original file line number Diff line number Diff line change
@@ -1,15 +1,26 @@
import React from 'react'
import PropTypes from 'prop-types'
import { useTranslation } from 'react-i18next'

const IssueLabel = ({ pid, publication_date }) => {
const IssueLabel = ({ pid = '', name = '' }) => {
const { t } = useTranslation()
const shortName = name.length > 16 ? name.slice(0, 16) + '...' : name
return (
<React.Fragment>
{pid.replace(/jdh0+(\d+)/, (m, n) => t('numbers.issue', { n }))}
&nbsp;&middot;&nbsp;
{new Date(publication_date).getFullYear()}
{name.length > 0 ? (
<>
&nbsp;&middot;&nbsp;
{shortName}
</>
) : null}
</React.Fragment>
)
}

IssueLabel.propTypes = {
pid: PropTypes.string,
name: PropTypes.string,
}

export default IssueLabel
Loading

0 comments on commit 6a15063

Please sign in to comment.