f5657fb3a0
- ensureSharedBookLocal helper makes sure the local library has both the Book entry and the bytes on disk after /import succeeds; navigating into the reader before this lands on "Book not found" - ShareLanding navigates via navigateToReader (path form on web) so the reader actually renders instead of hitting the App Router stub and going blank - Loading + progress UI on the landing page while bytes stream in; Open-in-app disabled mid-import to avoid races - UserInfo header: vertically center avatar with name/email, tighter mobile gap, and a fillContainer prop on UserAvatar so a parent can size the box via classes without the inline style fighting back - Rename "Share current page" -> "Share reading progress" (and matching post-generation hint) and shrink the dialog from 480 to 460px - Drop the unused Reload Page menu item from SettingsMenu - Translate the two new i18n keys across 31 locales Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
68 lines
2.3 KiB
TypeScript
68 lines
2.3 KiB
TypeScript
import React from 'react';
|
|
import Dialog from '@/components/Dialog';
|
|
import { useTranslation } from '@/hooks/useTranslation';
|
|
import { BookMetadata } from '@/libs/document';
|
|
import { formatLocaleDateTime, getMetadataHashInfo } from '@/utils/book';
|
|
|
|
interface SyncInfoDialogProps {
|
|
isOpen: boolean;
|
|
metadata: BookMetadata | null | undefined;
|
|
storedMetaHash?: string;
|
|
/** Most recent sync timestamp across pull + push of config and notes. */
|
|
lastSyncedAt?: number;
|
|
onClose: () => void;
|
|
}
|
|
|
|
const Row: React.FC<{ label: string; value: React.ReactNode }> = ({ label, value }) => (
|
|
<div className='flex flex-col gap-1'>
|
|
<span className='text-base-content/60 text-sm uppercase tracking-wide sm:text-xs'>{label}</span>
|
|
<div className='bg-base-200 text-base-content/90 break-all rounded-md p-2 font-mono text-sm sm:text-xs'>
|
|
{value}
|
|
</div>
|
|
</div>
|
|
);
|
|
|
|
const SyncInfoDialog: React.FC<SyncInfoDialogProps> = ({
|
|
isOpen,
|
|
metadata,
|
|
storedMetaHash,
|
|
lastSyncedAt,
|
|
onClose,
|
|
}) => {
|
|
const _ = useTranslation();
|
|
const info = metadata ? getMetadataHashInfo(metadata) : undefined;
|
|
const displayHash = storedMetaHash || info?.metaHash || '';
|
|
const hashesMatch = !info || !storedMetaHash || storedMetaHash === info.metaHash;
|
|
const placeholder = _('(none)');
|
|
const lastSyncedLabel = lastSyncedAt ? formatLocaleDateTime(lastSyncedAt) : _('Never synced');
|
|
|
|
return (
|
|
<Dialog
|
|
isOpen={isOpen}
|
|
onClose={onClose}
|
|
snapHeight={0.7}
|
|
title={_('Sync Info')}
|
|
boxClassName='sm:!min-w-[520px] sm:h-auto'
|
|
>
|
|
{isOpen && (
|
|
<div className='mb-4 mt-0 flex flex-col gap-3 p-2 sm:p-4'>
|
|
<Row label={_('Meta Hash')} value={displayHash || placeholder} />
|
|
{!hashesMatch && info && <Row label={_('Computed Hash')} value={info.metaHash} />}
|
|
<Row label={_('Title')} value={info?.title || placeholder} />
|
|
<Row
|
|
label={_('Author')}
|
|
value={info && info.authors.length > 0 ? info.authors.join(', ') : placeholder}
|
|
/>
|
|
<Row
|
|
label={_('Identifiers')}
|
|
value={info && info.identifiers.length > 0 ? info.identifiers.join(', ') : placeholder}
|
|
/>
|
|
<Row label={_('Last Synced')} value={lastSyncedLabel} />
|
|
</div>
|
|
)}
|
|
</Dialog>
|
|
);
|
|
};
|
|
|
|
export default SyncInfoDialog;
|