Skip to content

Commit 1fdf490

Browse files
committed
Enhance last saved timestamp display
1 parent 2a91244 commit 1fdf490

1 file changed

Lines changed: 100 additions & 8 deletions

File tree

components/StatusBar.tsx

Lines changed: 100 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,18 @@ const statusConfig: Record<LLMStatus, { text: string; color: string; tooltip: st
5454
},
5555
};
5656

57+
type TimestampResult = { relative: string; absolute: string } | { fallback: string };
58+
59+
const RELATIVE_TIME_DIVISIONS: { amount: number; unit: Intl.RelativeTimeFormatUnit }[] = [
60+
{ amount: 60, unit: 'second' },
61+
{ amount: 60, unit: 'minute' },
62+
{ amount: 24, unit: 'hour' },
63+
{ amount: 7, unit: 'day' },
64+
{ amount: 4.34524, unit: 'week' },
65+
{ amount: 12, unit: 'month' },
66+
{ amount: Number.POSITIVE_INFINITY, unit: 'year' },
67+
];
68+
5769
interface ZoomButtonProps extends Omit<React.ButtonHTMLAttributes<HTMLButtonElement>, 'children'> {
5870
hint: string;
5971
icon: React.ReactNode;
@@ -185,14 +197,71 @@ const StatusBar: React.FC<StatusBarProps> = ({
185197
onDatabaseMenu(event);
186198
};
187199

188-
const formatTimestamp = (isoString?: string) => {
189-
if (!isoString) return 'Not saved yet';
190-
try {
191-
return new Date(isoString).toLocaleTimeString();
192-
} catch {
193-
return 'Invalid date';
200+
const formatTimestamp = React.useCallback((isoString?: string): TimestampResult => {
201+
if (!isoString) {
202+
return { fallback: 'Not saved yet' };
194203
}
195-
};
204+
205+
const date = new Date(isoString);
206+
207+
if (Number.isNaN(date.getTime())) {
208+
return { fallback: 'Invalid date' };
209+
}
210+
211+
const absolute = date.toLocaleString();
212+
const formatter = new Intl.RelativeTimeFormat(undefined, { numeric: 'auto' });
213+
let duration = Math.round((date.getTime() - Date.now()) / 1000);
214+
215+
for (const division of RELATIVE_TIME_DIVISIONS) {
216+
if (Math.abs(duration) < division.amount) {
217+
return { relative: formatter.format(duration, division.unit), absolute };
218+
}
219+
duration = Math.round(duration / division.amount);
220+
}
221+
222+
return { fallback: 'Invalid date' };
223+
}, []);
224+
225+
const [lastSavedDisplay, setLastSavedDisplay] = React.useState<{ relative: string; absolute: string } | null>(() => {
226+
const result = formatTimestamp(lastSaved);
227+
return 'fallback' in result ? null : result;
228+
});
229+
const [lastSavedFallback, setLastSavedFallback] = React.useState<string | null>(() => {
230+
const result = formatTimestamp(lastSaved);
231+
return 'fallback' in result ? result.fallback : null;
232+
});
233+
const lastSavedTriggerRef = React.useRef<HTMLSpanElement>(null);
234+
const [showLastSavedTooltip, setShowLastSavedTooltip] = React.useState(false);
235+
236+
React.useEffect(() => {
237+
let intervalId: number | undefined;
238+
239+
const updateTimestamp = () => {
240+
const result = formatTimestamp(lastSaved);
241+
if ('fallback' in result) {
242+
setLastSavedDisplay(null);
243+
setLastSavedFallback(result.fallback);
244+
setShowLastSavedTooltip(false);
245+
return false;
246+
}
247+
248+
setLastSavedFallback(null);
249+
setLastSavedDisplay(result);
250+
return true;
251+
};
252+
253+
const hasValidTimestamp = updateTimestamp();
254+
255+
if (hasValidTimestamp && typeof window !== 'undefined') {
256+
intervalId = window.setInterval(updateTimestamp, 60000);
257+
}
258+
259+
return () => {
260+
if (intervalId !== undefined && typeof window !== 'undefined') {
261+
window.clearInterval(intervalId);
262+
}
263+
};
264+
}, [lastSaved, formatTimestamp]);
196265

197266
const selectStyles: React.CSSProperties = {
198267
maxWidth: '160px',
@@ -392,7 +461,30 @@ const StatusBar: React.FC<StatusBarProps> = ({
392461
<div className="h-4 w-px bg-border-color"></div>
393462
<span>Documents: <span className="font-semibold text-text-main">{documentCount}</span></span>
394463
<div className="h-4 w-px bg-border-color"></div>
395-
<span>Last Saved: <span className="font-semibold text-text-main">{formatTimestamp(lastSaved)}</span></span>
464+
<span className="flex items-center gap-1">
465+
Last Saved:
466+
<span
467+
ref={lastSavedTriggerRef}
468+
className="font-semibold text-text-main"
469+
onMouseEnter={() => lastSavedDisplay && setShowLastSavedTooltip(true)}
470+
onMouseLeave={() => setShowLastSavedTooltip(false)}
471+
onFocus={() => lastSavedDisplay && setShowLastSavedTooltip(true)}
472+
onBlur={() => setShowLastSavedTooltip(false)}
473+
tabIndex={lastSavedDisplay ? 0 : undefined}
474+
>
475+
{lastSavedFallback ?? lastSavedDisplay?.relative ?? 'Not saved yet'}
476+
</span>
477+
</span>
478+
{lastSavedDisplay && showLastSavedTooltip && lastSavedTriggerRef.current && (
479+
<Tooltip
480+
targetRef={lastSavedTriggerRef}
481+
content={(
482+
<span className="block whitespace-pre-line break-words leading-snug text-left">
483+
{lastSavedDisplay.absolute}
484+
</span>
485+
)}
486+
/>
487+
)}
396488
{appVersion && <div className="h-4 w-px bg-border-color"></div>}
397489
{appVersion && (
398490
onOpenAbout ? (

0 commit comments

Comments
 (0)