This commit is contained in:
seb
2026-10-10 18:26:15 +02:00
parent cef9c4da4f
commit 1a9f77264f

View File

@@ -26,10 +26,6 @@ import brandAutomat from '../../automats/brandAutomat';
import { orderAutomat } from '../../automats/orderAutomat';
import SkeleHashImage from './SkeleHashImage';
// Keep only the most recent N image events in state to avoid unbounded growth
// on large imports (thousands of images).
const MAX_STREAMED_IMAGES = 60;
function formatBytes(bytes) {
if (bytes == null || isNaN(bytes) || bytes === 0) return '0 B';
const units = ['B', 'KB', 'MB', 'GB', 'TB'];
@@ -104,8 +100,8 @@ export default class ImportExportSettingsPanel extends Component {
uploadLoaded: 0,
uploadTotal: 0,
importProgress: null, // raw counters from the `progress` event
// Most recent SkeleHashes from the stream ({ id, hash })
importedImages: [],
// Latest SkeleHash shown (first hash of the most recent images batch)
streamedHash: null,
importedImageCount: 0,
statusMessage: null,
importStats: null,
@@ -252,7 +248,6 @@ export default class ImportExportSettingsPanel extends Component {
this.uploadAbortController = new AbortController();
this.imageSeq = 0;
this.setState({
uploading: true,
uploadStage: 'upload',
@@ -260,7 +255,7 @@ export default class ImportExportSettingsPanel extends Component {
uploadLoaded: 0,
uploadTotal: selectedFile.size,
importProgress: null,
importedImages: [],
streamedHash: null,
importedImageCount: 0,
statusMessage: null,
importStats: null,
@@ -294,25 +289,16 @@ export default class ImportExportSettingsPanel extends Component {
onImages: (images) => {
const list = Array.isArray(images) ? images.filter(Boolean) : [];
if (!list.length) return;
this.setState((prev) => {
let seq = this.imageSeq ?? 0;
const batch = list.map((item) => {
seq += 1;
return {
id: seq,
hash: typeof item === 'string' ? item : (item?.skelehash ?? null),
};
});
this.imageSeq = seq;
const next = [...batch.reverse(), ...prev.importedImages];
if (next.length > MAX_STREAMED_IMAGES) next.length = MAX_STREAMED_IMAGES;
return {
uploadStage: 'server',
importedImages: next,
importedImageCount: prev.importedImageCount + list.length,
statusMessage: null,
};
});
// Only the first hash of each batch; it replaces the previously
// shown placeholder.
const first = list[0];
const hash = typeof first === 'string' ? first : (first?.skelehash ?? null);
this.setState((prev) => ({
uploadStage: 'server',
streamedHash: hash,
importedImageCount: prev.importedImageCount + list.length,
statusMessage: null,
}));
},
onDone: (stats) => {
this.setState({
@@ -383,7 +369,7 @@ export default class ImportExportSettingsPanel extends Component {
uploadLoaded,
uploadTotal,
importProgress,
importedImages,
streamedHash,
importedImageCount,
statusMessage,
importStats,
@@ -831,88 +817,44 @@ export default class ImportExportSettingsPanel extends Component {
</>
)}
{/* Live stream of decoded SkeleHash placeholders */}
{importedImages.length > 0 && (
<Box sx={{ mt: 2, pt: 2, borderTop: '1px dashed', borderColor: 'divider' }}>
{/* Latest decoded SkeleHash placeholder (replaces previous) */}
{streamedHash && (
<Box
sx={{
display: 'flex',
alignItems: 'center',
gap: 1,
mt: 2,
pt: 2,
borderTop: '1px dashed',
borderColor: 'divider',
}}
>
<Box
title={streamedHash}
sx={{
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between',
mb: 1,
width: 36,
height: 36,
flex: '0 0 auto',
borderRadius: 1,
overflow: 'hidden',
border: '1px solid',
borderColor: 'divider',
bgcolor: 'background.paper',
}}
>
<Typography variant="caption" fontWeight={600} color="text.secondary">
SkeleHash-Platzhalter (Vorschau)
</Typography>
<Typography variant="caption" color="text.secondary">
{importedImageCount} Bild(er) verarbeitet
</Typography>
<SkeleHashImage hash={streamedHash} />
</Box>
<Box
sx={{
display: 'grid',
gridTemplateColumns: 'repeat(auto-fill, minmax(72px, 1fr))',
gap: 1,
maxHeight: 220,
overflowY: 'auto',
}}
<Typography
variant="caption"
color="text.secondary"
sx={{ fontFamily: 'monospace' }}
>
{importedImages.map((image) => (
<Box
key={image.id}
title={image.hash || 'kein Hash'}
sx={{
position: 'relative',
aspectRatio: '1 / 1',
borderRadius: 1,
overflow: 'hidden',
border: '1px solid',
borderColor: 'divider',
bgcolor: 'background.paper',
}}
>
{image.hash ? (
<SkeleHashImage hash={image.hash} />
) : (
<Box
sx={{
width: '100%',
height: '100%',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
color: 'text.disabled',
fontSize: '0.6rem',
textAlign: 'center',
p: 0.5,
}}
>
Kein Hash
</Box>
)}
<Box
sx={{
position: 'absolute',
inset: 'auto 0 0 0',
bgcolor: 'rgba(0,0,0,0.55)',
color: '#fff',
px: 0.5,
py: 0.25,
}}
>
<Typography
variant="caption"
noWrap
display="block"
sx={{ fontSize: '0.6rem', lineHeight: 1.2, fontFamily: 'monospace' }}
>
{shortHash(image.hash)}
</Typography>
</Box>
</Box>
))}
</Box>
{shortHash(streamedHash)}
</Typography>
<Typography variant="caption" color="text.secondary" sx={{ ml: 'auto' }}>
{importedImageCount} Bild(er) verarbeitet
</Typography>
</Box>
)}
</>