mirror of
https://github.com/kilyabin/psysonic.git
synced 2026-07-21 22:15:40 +00:00
4b1dd3c29f
Findings 5-8 of the dedup audit: - F5 byte formatters: appUpdaterHelpers.fmtBytes + ZipDownloadOverlay .formatMB route through the existing formatBytes; a new formatMb (always-MB) backs playlistDetailHelpers.formatSize, AlbumHeader and the 4 inline DeviceSyncPreSyncModal expressions. SongInfoModal.format Size is intentionally left — it uses decimal (1e6) divisors, not 1024. - F6 sanitizeHtml: extracted to utils/sanitizeHtml.ts; AlbumHeader, ComposerDetail and the (now-empty, deleted) artistDetailHelpers use it directly. nowPlayingHelpers keeps its own export but now delegates to the shared sanitiser and only adds its trailing-link strip on top. - F7 album duration: BecauseYouLikeRail's formatAlbumDuration drops in favour of the shared formatHumanHoursMinutes. Behaviour note: total minutes now floor instead of round (<=1 min display difference, matches every other caller). - F8 clock time: extracted to utils/format/formatClockTime.ts; PlaybackDelayModal + QueueHeader use it (toLocaleTimeString and Intl.DateTimeFormat produced identical output). Behaviour preserved except the two explicitly noted divergences (F7 round->floor; F5 appUpdater/Zip now show GB above 1 GB instead of a large MB number).
78 lines
3.6 KiB
TypeScript
78 lines
3.6 KiB
TypeScript
import React from 'react';
|
|
import { useTranslation } from 'react-i18next';
|
|
import { AlertCircle, Loader2 } from 'lucide-react';
|
|
import type { SyncDelta } from '../../utils/deviceSync/runDeviceSyncExecution';
|
|
import { formatMb } from '../../utils/format/formatBytes';
|
|
|
|
interface Props {
|
|
preSyncOpen: boolean;
|
|
preSyncLoading: boolean;
|
|
syncDelta: SyncDelta;
|
|
onCancel: () => void;
|
|
onProceed: () => void;
|
|
}
|
|
|
|
export default function DeviceSyncPreSyncModal({
|
|
preSyncOpen, preSyncLoading, syncDelta, onCancel, onProceed,
|
|
}: Props) {
|
|
const { t } = useTranslation();
|
|
|
|
if (!preSyncOpen) return null;
|
|
|
|
return (
|
|
<div className="modal-overlay">
|
|
<div className="modal-content device-sync-modal">
|
|
<h2 className="modal-title">{t('deviceSync.syncSummary')}</h2>
|
|
|
|
{preSyncLoading ? (
|
|
<div className="device-sync-loading-modal" style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', margin: '20px' }}>
|
|
<Loader2 size={32} className="spin" />
|
|
<p style={{ marginTop: '10px' }}>{t('deviceSync.calculating')}</p>
|
|
</div>
|
|
) : (
|
|
<div className="device-sync-summary-stats" style={{ display: 'flex', flexDirection: 'column', gap: '8px', margin: '10px 0' }}>
|
|
<div style={{ display: 'flex', justifyContent: 'space-between', padding: '4px 0' }}>
|
|
<span>{t('deviceSync.filesToAdd')}</span>
|
|
<span className="color-success">+{syncDelta.addCount} ({formatMb(syncDelta.addBytes)})</span>
|
|
</div>
|
|
<div style={{ display: 'flex', justifyContent: 'space-between', padding: '4px 0' }}>
|
|
<span>{t('deviceSync.filesToDelete')}</span>
|
|
<span className="color-error">-{syncDelta.delCount} ({formatMb(syncDelta.delBytes)})</span>
|
|
</div>
|
|
<hr style={{ border: 'none', borderTop: '1px solid var(--border)', margin: '10px 0' }} />
|
|
<div style={{ display: 'flex', justifyContent: 'space-between', fontWeight: 'bold' }}>
|
|
<span>{t('deviceSync.netChange')}</span>
|
|
<span>{formatMb(syncDelta.addBytes - syncDelta.delBytes)}</span>
|
|
</div>
|
|
<div style={{ display: 'flex', justifyContent: 'space-between', fontWeight: 'bold', color: syncDelta.addBytes > syncDelta.availableBytes + syncDelta.delBytes ? 'var(--danger)' : 'inherit', marginTop: '10px' }}>
|
|
<span>{t('deviceSync.availableSpace')}</span>
|
|
<span>{formatMb(syncDelta.availableBytes)}</span>
|
|
</div>
|
|
{syncDelta.addBytes > syncDelta.availableBytes + syncDelta.delBytes && (
|
|
<div className="sync-warning error" style={{ background: 'color-mix(in srgb, var(--danger) 15%, transparent)', padding: '10px', borderRadius: 'var(--radius-md)', marginTop: '15px', display: 'flex', gap: '10px', color: 'var(--danger)', alignItems: 'flex-start' }}>
|
|
<AlertCircle size={16} style={{ flexShrink: 0, marginTop: '2px' }} />
|
|
<span>{t('deviceSync.spaceWarning')}</span>
|
|
</div>
|
|
)}
|
|
</div>
|
|
)}
|
|
|
|
{!preSyncLoading && (
|
|
<div className="modal-actions" style={{ display: 'flex', justifyContent: 'flex-end', gap: '10px', marginTop: '25px' }}>
|
|
<button className="btn btn-ghost" onClick={onCancel}>
|
|
{t('deviceSync.cancel')}
|
|
</button>
|
|
<button
|
|
className="btn btn-primary"
|
|
onClick={onProceed}
|
|
disabled={syncDelta.addBytes > syncDelta.availableBytes + syncDelta.delBytes}
|
|
>
|
|
{t('deviceSync.proceed')}
|
|
</button>
|
|
</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|