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