Displaying human readable 'ago' when hovering over FileCard datetime.

This commit is contained in:
Roland 2024-08-20 13:13:21 -06:00
parent 215e5280b0
commit feb4e3db52
No known key found for this signature in database
GPG key ID: 1BA620CE6C9A4680
4 changed files with 88 additions and 40 deletions

View file

@ -26,6 +26,7 @@ import {
findPosterPath,
HelpHeader,
humanFileSize,
isoDatetimeToAgoPopup,
isoDatetimeToString,
mimetypeColor,
PageContainer,
@ -44,7 +45,7 @@ import {FileCards, FileRowTagIcon, FilesView} from "./Files";
import Grid from "semantic-ui-react/dist/commonjs/collections/Grid";
import _ from "lodash";
import {Media, ThemeContext} from "../contexts/contexts";
import {Button, Card, CardIcon, darkTheme, Header, Loader, Placeholder, Segment, Tab, TabPane} from "./Theme";
import {Button, Card, CardIcon, darkTheme, Header, Loader, Placeholder, Popup, Segment, Tab, TabPane} from "./Theme";
import {SortableTable} from "./SortableTable";
import {taggedImageLabel, TagsSelector} from "../Tags";
import {toast} from "react-semantic-toasts-2";
@ -291,6 +292,9 @@ export function ArchiveCard({file}) {
const domain = data ? data.domain : null;
const domainUrl = `/archive?domain=${domain}`;
const title = file.title || data.url;
const header = <ExternalCardLink to={singlefileUrl} className='card-title-ellipsis'>{title}</ExternalCardLink>;
const dt = file.published_datetime || file.published_modified_datetime || file.modified;
return <Card color={mimetypeColor(file.mimetype)}>
<div>
<ExternalCardLink to={singlefileUrl}>
@ -300,9 +304,9 @@ export function ArchiveCard({file}) {
<CardContent {...s}>
<CardHeader>
<Container textAlign='left'>
<ExternalCardLink to={singlefileUrl} className='card-title-ellipsis'>
{file.title || data.url}
</ExternalCardLink>
<Popup on='hover'
trigger={header}
content={title}/>
</Container>
</CardHeader>
{domain &&
@ -310,9 +314,7 @@ export function ArchiveCard({file}) {
<span {...s}>{domain}</span>
</CardLink>}
<CardMeta {...s}>
<p>
{isoDatetimeToString(file.published_datetime)}
</p>
{isoDatetimeToAgoPopup(dt, false)}
</CardMeta>
<CardDescription>
<Link to={`/archive/${data.id}`}>

View file

@ -106,7 +106,7 @@ export function divmod(x, y) {
return [Math.floor(x / y), x % y];
}
export function secondsElapsed(seconds) {
export function secondsElapsed(seconds, short = true) {
// Convert the provided seconds into a human-readable string of the time elapsed between the provided timestamp
// and now.
if (!seconds || seconds < 0) {
@ -114,32 +114,47 @@ export function secondsElapsed(seconds) {
}
// Get seconds elapsed between now and `seconds` which is a UTC epoch.
const localNow = (new Date()).getTime();
const localNow = (new Date()).getTime() / 1000;
let years;
let days;
let hours;
let minutes;
seconds = Math.abs((localNow / 1000) - seconds);
seconds = Math.abs(localNow - seconds);
[years, seconds] = divmod(seconds, secondsToYears);
[days, seconds] = divmod(seconds, secondsToDays);
[hours, seconds] = divmod(seconds, secondsToHours);
[minutes, seconds] = divmod(seconds, secondsToMinutes);
seconds = Math.floor(seconds);
if (years > 0 && days > 30) {
return `${years}y${days}d`;
} else if (years > 0) {
return `${years}y`;
} else if (days > 0) {
return `${days}d`;
} else if (hours > 0) {
return `${hours}h`;
} else if (minutes > 0) {
return `${minutes}m`;
if (short) {
if (years > 0 && days > 30) {
return `${years}y${days}d`;
} else if (years > 0) {
return `${years}y`;
} else if (days > 0) {
return `${days}d`;
} else if (hours > 0) {
return `${hours}h`;
} else if (minutes > 0) {
return `${minutes}m`;
}
return `${seconds}s`
} else {
if (years > 0 && days > 30) {
return `${years} years ${days} days`;
} else if (years > 0) {
return `${years} years`;
} else if (days > 0) {
return `${days} days`;
} else if (hours > 0) {
return `${hours} hours`;
} else if (minutes > 0) {
return `${minutes} minutes`;
}
return `${seconds} seconds`
}
return `${seconds}s`
}
export function secondsToElapsedPopup(seconds) {
@ -160,6 +175,21 @@ export function isoDatetimeToElapsedPopup(dt) {
return secondsToElapsedPopup(d.getTime() / 1000);
}
export function isoDatetimeToAgoPopup(dt, short = true) {
const seconds = (new Date(dt)).getTime() / 1000;
// Return a Popup which allows the user see a more detailed timestamp when hovering.
const elapsed = secondsElapsed(seconds, short);
if (seconds === 0 || !elapsed) {
return <></>;
}
const trigger = <span>{isoDatetimeToString(dt)}</span>;
return <Popup
content={<span>{elapsed} ago</span>}
on='hover'
trigger={trigger}
/>
}
export function secondsToHMS(totalSeconds) {
let hours = Math.floor(totalSeconds / 3600);
totalSeconds -= hours * 3600;

View file

@ -24,6 +24,7 @@ import {
findPosterPath,
HandPointMessage,
humanFileSize,
isoDatetimeToAgoPopup,
isoDatetimeToString,
mimetypeColor,
PageContainer,
@ -57,6 +58,7 @@ import {
ModalContent,
ModalHeader,
Placeholder,
Popup,
Progress,
Segment
} from "./Theme";
@ -77,14 +79,18 @@ function EbookCard({file}) {
const viewerUrl = isEpub ? `/epub/epub.html?url=${downloadUrl}` : null;
const color = mimetypeColor(file.mimetype);
const title = file.title || file.stem || file.name;
const header = <ExternalCardLink to={viewerUrl || downloadUrl} className='card-title-ellipsis'>
{title}
</ExternalCardLink>;
return <Card color={color}>
<CardPoster file={file} preview={true}/>
<CardContent {...s}>
<CardHeader>
<Container textAlign='left'>
<ExternalCardLink to={viewerUrl || downloadUrl} className='card-title-ellipsis'>
{file.title || file.stem || file.name}
</ExternalCardLink>
<Popup on='hover'
trigger={header}
content={title}/>
</Container>
</CardHeader>
<CardMeta>
@ -100,18 +106,23 @@ function ImageCard({file}) {
const {s} = useContext(ThemeContext);
const url = `/media/${encodeMediaPath(file.primary_path)}`;
const title = file.title || file.stem || file.primary_path;
const header = <ExternalCardLink to={url} className='no-link-underscore card-link'>
<p>{textEllipsis(title)}</p>
</ExternalCardLink>;
const dt = file.published_datetime || file.published_modified_datetime || file.modified;
return <Card color={mimetypeColor(file.mimetype)}>
<PreviewLink file={file}>
<CardPoster file={file}/>
</PreviewLink>
<CardContent {...s}>
<CardHeader>
<ExternalCardLink to={url} className='no-link-underscore card-link'>
<p>{textEllipsis(file.title || file.stem || file.primary_path)}</p>
</ExternalCardLink>
<Popup on='hover'
trigger={header}
content={title}/>
</CardHeader>
<CardMeta {...s}>
<p>{isoDatetimeToString(file.modified / 1000)}</p>
<p>{isoDatetimeToAgoPopup(dt, false)}</p>
</CardMeta>
<CardDescription {...s}>
<p>{humanFileSize(file.size)}</p>
@ -142,16 +153,21 @@ function FileCard({file}) {
const color = mimetypeColor(file.mimetype);
const size = file.size !== null && file.size !== undefined ? humanFileSize(file.size) : null;
const title = file.title || file.name || file.primary_path;
const header = <ExternalCardLink to={downloadUrl} className='card-title-ellipsis'>
{title}
</ExternalCardLink>;
const dt = file.published_datetime || file.published_modified_datetime || file.modified;
return <Card color={color}>
<CardPoster to={downloadUrl} file={file}/>
<CardContent {...s}>
<CardHeader>
<ExternalCardLink to={downloadUrl} className='card-title-ellipsis'>
{file.title || file.name || file.primary_path}
</ExternalCardLink>
<Popup on='hover'
trigger={header}
content={title}/>
</CardHeader>
{author && <b {...s}>{author}</b>}
<p>{isoDatetimeToString(file.published_datetime) || isoDatetimeToString(file.modified)}</p>
<p>{isoDatetimeToAgoPopup(dt, false)}</p>
<p>{size}</p>
</CardContent>
</Card>

View file

@ -12,7 +12,7 @@ import {
FileIcon,
findPosterPath,
humanFileSize,
humanNumber,
humanNumber, isoDatetimeToAgoPopup,
isoDatetimeToString,
mimetypeColor,
PageContainer,
@ -31,7 +31,6 @@ import {
CardDescription,
CardHeader,
Container,
Dimmer,
Image,
PlaceholderHeader,
PlaceholderLine,
@ -44,7 +43,7 @@ import {useChannel, useSearchOrder, useSearchVideos, useVideo, useVideoStatistic
import {FileRowTagIcon, FilesView} from "./Files";
import Grid from "semantic-ui-react/dist/commonjs/collections/Grid";
import Icon from "semantic-ui-react/dist/commonjs/elements/Icon";
import {Button, Card, Header, Loader, Placeholder, Segment, Statistic, StatisticGroup} from "./Theme";
import {Button, Card, Header, Loader, Placeholder, Popup, Segment, Statistic, StatisticGroup} from "./Theme";
import {deleteVideos} from "../api";
import {Media, QueryContext, ThemeContext} from "../contexts/contexts";
import _ from "lodash";
@ -309,7 +308,6 @@ export function VideoCard({file}) {
// Default to video FilePreview for lone video files.
let video_url;
const published_datetime = isoDatetimeToString(file.published_datetime);
// A video may not have a channel.
const channel = video.channel ? video.channel : null;
let channel_url = null;
@ -324,8 +322,8 @@ export function VideoCard({file}) {
let poster = <CardPoster to={video_url} file={file}/>;
let header = <span {...s}
className='card-title-ellipsis'>{file.title || file.name || video.stem || video.name}</span>;
const title = file.title || file.name || video.stem || video.name;
let header = <span {...s} className='card-title-ellipsis'>{title}</span>;
if (video_url) {
// Link to Channel-Video page or Video page.
header = <Link to={video_url} className="no-link-underscore card-link">{header}</Link>;
@ -343,7 +341,9 @@ export function VideoCard({file}) {
<CardContent {...s}>
<CardHeader>
<Container textAlign='left'>
{header}
<Popup on='hover'
trigger={header}
content={title}/>
</Container>
</CardHeader>
<CardDescription>
@ -351,7 +351,7 @@ export function VideoCard({file}) {
{channel && <Link to={channel_url} className="no-link-underscore card-link">
<b {...s}>{channel.name}</b>
</Link>}
<p {...s}>{published_datetime}</p>
<p {...s}>{isoDatetimeToAgoPopup(file.published_datetime, false)}</p>
</Container>
</CardDescription>
</CardContent>