@pushwoosh/smart-components-kit 1.18.0 → 1.20.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -6,9 +6,9 @@ import { CloseIcon, TrashIcon } from '@pushwoosh/kit-icons';
6
6
  import { Text } from '@pushwoosh/kit-typography';
7
7
  import { useSPAContext } from '@pushwoosh/spa-context';
8
8
  import { useDeleteMediaFile } from './hooks';
9
- import { getCropCaption, getThumbUrl } from './mediaFile.utils';
9
+ import { getAspectRatio, getCropCaption, getThumbUrl } from './mediaFile.utils';
10
10
  import { useLang } from './polyglot';
11
- import { Chips, CropStrip, CropThumb, CropThumbCaption, CropThumbDelete, FieldLabel, PreviewFrame, PreviewImage, PreviewStage, Segment, SegmentItem, SizeInput, TileImage } from './styles';
11
+ import { Chips, CropStrip, CropThumb, CropThumbButton, CropThumbCaption, CropThumbDelete, CropThumbImage, FieldLabel, PreviewFrame, PreviewImage, PreviewStage, Segment, SegmentItem, SizeInput } from './styles';
12
12
  const RATIOS = [{
13
13
  value: 'ASPECT_RATIO_1_1',
14
14
  label: '1:1',
@@ -79,6 +79,7 @@ export const CreateCropDrawer = ({
79
79
  const [quality, setQuality] = useState(DEFAULT_QUALITY);
80
80
  const [isCreating, setIsCreating] = useState(false);
81
81
  const [pendingDeleteCrop, setPendingDeleteCrop] = useState(null);
82
+ const [viewedCrop, setViewedCrop] = useState(null);
82
83
  // The drawer mounts once and is toggled via `isOpen`, so reset every field
83
84
  // whenever it opens for a different original — otherwise values (and the
84
85
  // `ratioExists` guard) leak from the previously edited image.
@@ -88,6 +89,7 @@ export const CreateCropDrawer = ({
88
89
  setDimension('width');
89
90
  setSize(DEFAULT_SIZE);
90
91
  setQuality(DEFAULT_QUALITY);
92
+ setViewedCrop(null);
91
93
  }
92
94
  }, [original]);
93
95
  const isJpeg = /jpe?g/i.test(original?.mimeType ?? '');
@@ -96,8 +98,17 @@ export const CreateCropDrawer = ({
96
98
  const qualityValue = Number(quality);
97
99
  const isQualityValid = !isJpeg || Number.isFinite(qualityValue) && qualityValue >= 1 && qualityValue <= 100;
98
100
  const isOriginalView = ratio === 'original';
101
+ const isOriginalShown = isOriginalView && !viewedCrop;
99
102
  const originalMeta = original?.meta?.type === 'imageMeta' ? original.meta.data : null;
100
- const originalRatio = originalMeta && originalMeta.height > 0 ? originalMeta.width / originalMeta.height : 1;
103
+ const previewFile = viewedCrop ?? original;
104
+ const chooseRatio = next => {
105
+ setRatio(next);
106
+ setViewedCrop(null);
107
+ };
108
+ const viewCrop = crop => {
109
+ setRatio('original');
110
+ setViewedCrop(crop);
111
+ };
101
112
  const selectedRatio = RATIOS.find(item => item.value === ratio) ?? RATIOS[0];
102
113
  const outputWidth = dimension === 'width' ? sizeValue : Math.round(sizeValue * selectedRatio.ar);
103
114
  const outputHeight = dimension === 'height' ? sizeValue : Math.round(sizeValue / selectedRatio.ar);
@@ -144,10 +155,8 @@ export const CreateCropDrawer = ({
144
155
  type: 'success'
145
156
  });
146
157
  onCreated(parent.uuid, response.file);
147
- // Back to the view-only default: the form's ratio+size now match the
148
- // crop that was just created, so leaving it would flash the
149
- // "duplicate exists" note right after a successful create.
150
- setRatio('original');
158
+ // Show the stored result instead of the form, whose ratio+size now duplicate it.
159
+ viewCrop(response.file);
151
160
  } catch (error) {
152
161
  openToast({
153
162
  title: error.message || lang({
@@ -167,6 +176,7 @@ export const CreateCropDrawer = ({
167
176
  const parentUuid = crop.parentUuid ?? original?.uuid;
168
177
  if (!parentUuid) return;
169
178
  if (await deleteMediaFile(crop.uuid)) {
179
+ setViewedCrop(viewed => viewed?.uuid === crop.uuid ? null : viewed);
170
180
  onDeleted(parentUuid, crop.uuid);
171
181
  }
172
182
  };
@@ -194,7 +204,7 @@ export const CreateCropDrawer = ({
194
204
  })]
195
205
  })
196
206
  }), _jsx(DrawerBody, {
197
- children: original && _jsxs(Vertical, {
207
+ children: original && previewFile && _jsxs(Vertical, {
198
208
  "$gap": 20,
199
209
  children: [_jsxs("div", {
200
210
  children: [_jsx(FieldLabel, {
@@ -203,14 +213,14 @@ export const CreateCropDrawer = ({
203
213
  })
204
214
  }), _jsxs(Chips, {
205
215
  children: [_jsx(Chip, {
206
- "$selected": isOriginalView,
207
- onClick: () => setRatio('original'),
216
+ "$selected": isOriginalShown,
217
+ onClick: () => chooseRatio('original'),
208
218
  children: lang({
209
219
  message: 'SMART.MEDIA_STORAGE.CREATE_CROP.ORIGINAL'
210
220
  })
211
221
  }), RATIOS.map(item => _jsx(Chip, {
212
222
  "$selected": item.value === ratio,
213
- onClick: () => setRatio(item.value),
223
+ onClick: () => chooseRatio(item.value),
214
224
  children: item.label
215
225
  }, item.value))]
216
226
  })]
@@ -265,13 +275,15 @@ export const CreateCropDrawer = ({
265
275
  "$gap": 6,
266
276
  children: [_jsx(PreviewStage, {
267
277
  children: _jsx(PreviewFrame, {
268
- "$ratio": isOriginalView ? originalRatio : selectedRatio.ar,
278
+ "$ratio": isOriginalView ? getAspectRatio(previewFile) : selectedRatio.ar,
269
279
  children: _jsx(PreviewImage, {
270
- src: original.url,
271
- alt: original.filename
280
+ src: previewFile.url,
281
+ alt: previewFile.filename
272
282
  })
273
283
  })
274
- }), isOriginalView && originalMeta && _jsxs(Text, {
284
+ }), viewedCrop && _jsx(Text, {
285
+ children: getCropCaption(viewedCrop)
286
+ }), isOriginalShown && originalMeta && _jsxs(Text, {
275
287
  children: [originalMeta.width, ' × ', originalMeta.height]
276
288
  }), !isOriginalView && isSizeValid && _jsxs(Text, {
277
289
  children: [selectedRatio.label, ' · ', outputWidth, ' × ', outputHeight]
@@ -313,9 +325,19 @@ export const CreateCropDrawer = ({
313
325
  })
314
326
  }) : _jsx(CropStrip, {
315
327
  children: existingCrops.map(crop => _jsxs(CropThumb, {
316
- children: [_jsx(TileImage, {
317
- src: getThumbUrl(crop),
318
- alt: crop.filename
328
+ "$selected": viewedCrop?.uuid === crop.uuid,
329
+ children: [_jsxs(CropThumbButton, {
330
+ type: "button",
331
+ title: lang({
332
+ message: 'SMART.MEDIA_STORAGE.CREATE_CROP.PREVIEW_HINT'
333
+ }),
334
+ onClick: () => viewCrop(crop),
335
+ children: [_jsx(CropThumbImage, {
336
+ src: getThumbUrl(crop),
337
+ alt: crop.filename
338
+ }), _jsx(CropThumbCaption, {
339
+ children: getCropCaption(crop)
340
+ })]
319
341
  }), _jsx(CropThumbDelete, {
320
342
  children: _jsx(Button, {
321
343
  color: "secondary",
@@ -329,8 +351,6 @@ export const CreateCropDrawer = ({
329
351
  }),
330
352
  onClick: () => setPendingDeleteCrop(crop)
331
353
  })
332
- }), _jsx(CropThumbCaption, {
333
- children: getCropCaption(crop)
334
354
  })]
335
355
  }, crop.uuid))
336
356
  })]
@@ -11,10 +11,11 @@ import { CanvaImportDrawer } from './CanvaImportDrawer';
11
11
  import { CreateCropDrawer } from './CreateCropDrawer';
12
12
  import { GenerateImageTab } from './GenerateImageTab';
13
13
  import { useCanvaEdit, useDeleteMediaFile, useDeleteMediaFiles } from './hooks';
14
- import { formatBytes, getCropCaption, getSourceLabel, getThumbUrl, hasVersions } from './mediaFile.utils';
14
+ import { compressionSavings, formatBytes, getCropCaption, getSourceLabel, getThumbUrl, hasVersions } from './mediaFile.utils';
15
15
  import { Lang, useLang } from './polyglot';
16
16
  import { StockPhotosTab } from './StockPhotosTab';
17
- import { CanvaActions, CountBadge, Dropzone, DropzoneRow, EmptyState, Grid, HiddenInput, SelectionBar, SearchField, SearchRow, SelectionCount, SourceBadge, TabBody, Tile, TileActions, TileBadges, TileCheckbox, TileImage, UsageLine, WarningBadge } from './styles';
17
+ import { CanvaActions, CountBadge, Dropzone, DropzoneRow, EmptyState, Grid, HiddenInput, MenuItemPreview, MenuItemThumb, SelectionBar, SearchField, SearchRow, SelectionCount, SourceBadge, TabBody, Tile, TileActions, TileBadges, TileCheckbox, TileImage, TooltipAnchor, UsageLine, VersionsBadge, WarningBadge } from './styles';
18
+ import { VersionsDrawer } from './VersionsDrawer';
18
19
  const UPLOAD_PATH = '/api/media/upload';
19
20
  const ALLOWED_IMAGE_EXTENSIONS = ['.jpg', '.jpeg', '.png', '.gif', '.webp'];
20
21
  const TAB_MESSAGES = {
@@ -73,6 +74,7 @@ export const MediaStorage = ({
73
74
  const [isUploading, setIsUploading] = useState(false);
74
75
  const [isDragOver, setIsDragOver] = useState(false);
75
76
  const [createCropFor, setCreateCropFor] = useState(null);
77
+ const [versionsFor, setVersionsFor] = useState(null);
76
78
  const [pendingDelete, setPendingDelete] = useState(null);
77
79
  const [selectedUuids, setSelectedUuids] = useState([]);
78
80
  const [isBulkDeleteOpen, setIsBulkDeleteOpen] = useState(false);
@@ -218,8 +220,13 @@ export const MediaStorage = ({
218
220
  }));
219
221
  const failed = results.filter(result => result.status === 'rejected').length;
220
222
  if (failed < images.length) {
223
+ const savings = compressionSavings(results.flatMap(result => result.status === 'fulfilled' ? [result.value] : []));
221
224
  openToast({
222
- title: lang({
225
+ title: savings ? lang({
226
+ message: 'SMART.MEDIA_STORAGE.UPLOAD_SUCCESS_COMPRESSED',
227
+ percent: savings.percent,
228
+ size: formatBytes(savings.savedBytes)
229
+ }) : lang({
223
230
  message: 'SMART.MEDIA_STORAGE.UPLOAD_SUCCESS'
224
231
  }),
225
232
  type: 'success'
@@ -465,7 +472,12 @@ export const MediaStorage = ({
465
472
  }), _jsxs(TileBadges, {
466
473
  children: [sourceLabel && _jsx(SourceBadge, {
467
474
  children: sourceLabel
468
- }), hasVersions(file) && _jsx(CountBadge, {
475
+ }), hasVersions(file) && _jsx(VersionsBadge, {
476
+ type: "button",
477
+ title: lang({
478
+ message: 'SMART.MEDIA_STORAGE.VERSIONS.HINT'
479
+ }),
480
+ onClick: () => setVersionsFor(file),
469
481
  children: lang({
470
482
  message: 'SMART.MEDIA_STORAGE.VERSIONS_BADGE',
471
483
  count: file.versionsCount
@@ -480,23 +492,38 @@ export const MediaStorage = ({
480
492
  title: warning,
481
493
  children: warning
482
494
  }), _jsxs(TileActions, {
483
- children: [crops.length > 0 ? _jsx(DropdownMenuButton, {
484
- color: "secondary",
485
- size: "compact",
486
- icon: _jsx(CheckIcon, {
487
- size: "small"
495
+ children: [crops.length > 0 ? _jsx(TooltipAnchor, {
496
+ title: lang({
497
+ message: 'SMART.MEDIA_STORAGE.SELECT_HINT'
488
498
  }),
489
- menuPlacement: "bottom-start",
490
- children: _jsxs(DropdownMenuSection, {
491
- children: [_jsx(DropdownMenuItem, {
492
- onClick: () => onSelect(file),
493
- children: lang({
494
- message: 'SMART.MEDIA_STORAGE.SELECT_ORIGINAL'
495
- })
496
- }), crops.map(crop => _jsx(DropdownMenuItem, {
497
- onClick: () => onSelect(crop),
498
- children: getCropCaption(crop)
499
- }, crop.uuid))]
499
+ children: _jsx(DropdownMenuButton, {
500
+ color: "secondary",
501
+ size: "compact",
502
+ icon: _jsx(CheckIcon, {
503
+ size: "small"
504
+ }),
505
+ menuPlacement: "bottom-start",
506
+ children: _jsxs(DropdownMenuSection, {
507
+ children: [_jsx(DropdownMenuItem, {
508
+ onClick: () => onSelect(file),
509
+ children: _jsxs(MenuItemPreview, {
510
+ children: [_jsx(MenuItemThumb, {
511
+ src: getThumbUrl(file),
512
+ alt: ""
513
+ }), lang({
514
+ message: 'SMART.MEDIA_STORAGE.SELECT_ORIGINAL'
515
+ })]
516
+ })
517
+ }), crops.map(crop => _jsx(DropdownMenuItem, {
518
+ onClick: () => onSelect(crop),
519
+ children: _jsxs(MenuItemPreview, {
520
+ children: [_jsx(MenuItemThumb, {
521
+ src: getThumbUrl(crop),
522
+ alt: ""
523
+ }), getCropCaption(crop)]
524
+ })
525
+ }, crop.uuid))]
526
+ })
500
527
  })
501
528
  }) : _jsx(Button, {
502
529
  color: "secondary",
@@ -504,7 +531,7 @@ export const MediaStorage = ({
504
531
  icon: _jsx(CheckIcon, {
505
532
  size: "small"
506
533
  }),
507
- "aria-label": lang({
534
+ title: lang({
508
535
  message: 'SMART.MEDIA_STORAGE.SELECT_HINT'
509
536
  }),
510
537
  onClick: () => onSelect(file)
@@ -515,7 +542,7 @@ export const MediaStorage = ({
515
542
  size: "small",
516
543
  view: "lined"
517
544
  }),
518
- "aria-label": lang({
545
+ title: lang({
519
546
  message: 'SMART.MEDIA_STORAGE.CREATE_CROP_HINT'
520
547
  }),
521
548
  onClick: () => setCreateCropFor(file)
@@ -529,9 +556,6 @@ export const MediaStorage = ({
529
556
  title: lang({
530
557
  message: 'SMART.MEDIA_STORAGE.CANVA.EDIT'
531
558
  }),
532
- "aria-label": lang({
533
- message: 'SMART.MEDIA_STORAGE.CANVA.EDIT'
534
- }),
535
559
  onClick: () => void canvaEdit.edit(file)
536
560
  }), _jsx(Button, {
537
561
  color: "secondary",
@@ -540,7 +564,7 @@ export const MediaStorage = ({
540
564
  size: "small",
541
565
  view: "lined"
542
566
  }),
543
- "aria-label": lang({
567
+ title: lang({
544
568
  message: 'SMART.MEDIA_STORAGE.DELETE_HINT'
545
569
  }),
546
570
  isDisabled: isBulkDeleting,
@@ -644,6 +668,15 @@ export const MediaStorage = ({
644
668
  onClose: () => setCreateCropFor(null),
645
669
  onCreated: handleCropCreated,
646
670
  onDeleted: handleCropDeleted
671
+ }), _jsx(VersionsDrawer, {
672
+ application: application,
673
+ file: versionsFor,
674
+ isOpen: versionsFor !== null,
675
+ onClose: () => setVersionsFor(null),
676
+ onSelect: version => {
677
+ setVersionsFor(null);
678
+ onSelect(version);
679
+ }
647
680
  }), _jsx(ModalConfirm, {
648
681
  isOpen: pendingDelete !== null,
649
682
  title: lang({
@@ -0,0 +1,12 @@
1
+ import { type FC } from 'react';
2
+ import type { MediaFile } from '@pushwoosh/rpc-v2-http-api-data/media_files';
3
+ type VersionsDrawerProps = {
4
+ application: string;
5
+ file: MediaFile | null;
6
+ isOpen: boolean;
7
+ onClose: () => void;
8
+ onSelect: (file: MediaFile) => void;
9
+ };
10
+ /** Drawer listing every stored version of one image, newest first, with a full-size preview. */
11
+ export declare const VersionsDrawer: FC<VersionsDrawerProps>;
12
+ export {};
@@ -0,0 +1,134 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useEffect, useState } from 'react';
3
+ import { Button, Drawer, DrawerBody, DrawerHeader, DrawerTitle, useToast } from '@pushwoosh/dumb-components';
4
+ import { useAsync, Vertical } from '@pushwoosh/kit-helpers';
5
+ import { CloseIcon } from '@pushwoosh/kit-icons';
6
+ import { Text } from '@pushwoosh/kit-typography';
7
+ import { useSPAContext } from '@pushwoosh/spa-context';
8
+ import { formatBytes, getAspectRatio, getThumbUrl } from './mediaFile.utils';
9
+ import { useLang } from './polyglot';
10
+ import { CropStrip, CropThumb, CropThumbButton, CropThumbCaption, CropThumbImage, EmptyState, FieldLabel, PreviewFrame, PreviewImage, PreviewStage } from './styles';
11
+ /** Drawer listing every stored version of one image, newest first, with a full-size preview. */
12
+ export const VersionsDrawer = ({
13
+ application,
14
+ file,
15
+ isOpen,
16
+ onClose,
17
+ onSelect
18
+ }) => {
19
+ const lang = useLang();
20
+ const {
21
+ rpcGateway
22
+ } = useSPAContext();
23
+ const {
24
+ openToast
25
+ } = useToast();
26
+ const [picked, setPicked] = useState(null);
27
+ const versionsAsync = useAsync(async () => {
28
+ if (!file) return [];
29
+ const response = await rpcGateway.mediaFiles.List({
30
+ application,
31
+ versionRootUuid: file.versionRootUuid
32
+ });
33
+ return [...(response.files ?? [])].sort((a, b) => b.version - a.version);
34
+ }, [rpcGateway, application, file]);
35
+ useEffect(() => {
36
+ if (versionsAsync.error) {
37
+ openToast({
38
+ title: versionsAsync.error.message || lang({
39
+ message: 'SMART.MEDIA_STORAGE.LOAD_ERROR'
40
+ }),
41
+ type: 'error'
42
+ });
43
+ }
44
+ }, [versionsAsync.error, openToast, lang]);
45
+ const versions = versionsAsync.value ?? [];
46
+ const newest = versions[0];
47
+ const viewed = picked && versions.includes(picked) ? picked : newest;
48
+ const viewedMeta = viewed?.meta?.type === 'imageMeta' ? viewed.meta.data : null;
49
+ const versionLabel = version => version === newest ? lang({
50
+ message: 'SMART.MEDIA_STORAGE.VERSIONS.CURRENT',
51
+ number: version.version + 1
52
+ }) : lang({
53
+ message: 'SMART.MEDIA_STORAGE.VERSIONS.NUMBER',
54
+ number: version.version + 1
55
+ });
56
+ return _jsxs(Drawer, {
57
+ size: "medium",
58
+ isOpen: isOpen,
59
+ onClose: onClose,
60
+ children: [_jsx(DrawerHeader, {
61
+ children: _jsxs(DrawerTitle, {
62
+ children: [_jsx("span", {
63
+ children: lang({
64
+ message: 'SMART.MEDIA_STORAGE.VERSIONS.TITLE'
65
+ })
66
+ }), _jsx(Button, {
67
+ view: "ghost",
68
+ color: "secondary",
69
+ size: "compact",
70
+ icon: _jsx(CloseIcon, {
71
+ size: "small"
72
+ }),
73
+ "aria-label": lang({
74
+ message: 'SMART.MEDIA_STORAGE.CREATE_CROP.CLOSE'
75
+ }),
76
+ onClick: onClose
77
+ })]
78
+ })
79
+ }), _jsxs(DrawerBody, {
80
+ children: [versionsAsync.loading && _jsx(EmptyState, {
81
+ children: lang({
82
+ message: 'SMART.MEDIA_STORAGE.LOADING'
83
+ })
84
+ }), !versionsAsync.loading && !viewed && _jsx(EmptyState, {
85
+ children: lang({
86
+ message: versionsAsync.error ? 'SMART.MEDIA_STORAGE.LOAD_ERROR' : 'SMART.MEDIA_STORAGE.EMPTY'
87
+ })
88
+ }), !versionsAsync.loading && viewed && _jsxs(Vertical, {
89
+ "$gap": 20,
90
+ children: [_jsxs(Vertical, {
91
+ "$gap": 6,
92
+ children: [_jsx(PreviewStage, {
93
+ children: _jsx(PreviewFrame, {
94
+ "$ratio": getAspectRatio(viewed),
95
+ children: _jsx(PreviewImage, {
96
+ src: viewed.url,
97
+ alt: viewed.filename
98
+ })
99
+ })
100
+ }), _jsxs(Text, {
101
+ children: [versionLabel(viewed), viewedMeta && ` · ${viewedMeta.width} × ${viewedMeta.height}`, ` · ${formatBytes(viewed.fileSize)}`]
102
+ })]
103
+ }), _jsx(Button, {
104
+ color: "primary",
105
+ size: "field",
106
+ onClick: () => onSelect(viewed),
107
+ children: lang({
108
+ message: 'SMART.MEDIA_STORAGE.VERSIONS.SELECT'
109
+ })
110
+ }), _jsxs("div", {
111
+ children: [_jsx(FieldLabel, {
112
+ children: lang({
113
+ message: 'SMART.MEDIA_STORAGE.VERSIONS.LIST_LABEL'
114
+ })
115
+ }), _jsx(CropStrip, {
116
+ children: versions.map(version => _jsx(CropThumb, {
117
+ "$selected": version === viewed,
118
+ children: _jsxs(CropThumbButton, {
119
+ type: "button",
120
+ onClick: () => setPicked(version),
121
+ children: [_jsx(CropThumbImage, {
122
+ src: getThumbUrl(version),
123
+ alt: version.filename
124
+ }), _jsx(CropThumbCaption, {
125
+ children: versionLabel(version)
126
+ })]
127
+ })
128
+ }, version.uuid))
129
+ })]
130
+ })]
131
+ })]
132
+ })]
133
+ });
134
+ };
@@ -5,7 +5,19 @@ export declare const getThumbUrl: (file: MediaFile) => string;
5
5
  export declare const getCropCaption: (file: MediaFile) => string;
6
6
  /** Brand name of the service an image was imported from; undefined for a file uploaded from disk. */
7
7
  export declare const getSourceLabel: (file: MediaFile) => string | undefined;
8
+ /** Width-to-height ratio from image meta; 1 when the meta carries no size. */
9
+ export declare const getAspectRatio: (file: MediaFile) => number;
8
10
  /** True for an image that has been edited at least once, so older versions exist behind it. */
9
11
  export declare const hasVersions: (file: MediaFile) => boolean;
10
12
  /** Human-readable byte size, e.g. 4404019 -> "4.2 MB". */
11
13
  export declare const formatBytes: (bytes: number) => string;
14
+ /** The part of the `/api/media/upload` answer the upload toast reads. */
15
+ export type UploadedFile = {
16
+ file_size: number;
17
+ original_file_size?: number;
18
+ };
19
+ /** Savings of PNG compression over a batch: compressed files only; null when nothing worth a whole percent was saved. */
20
+ export declare const compressionSavings: (files: UploadedFile[]) => {
21
+ percent: number;
22
+ savedBytes: number;
23
+ } | null;
@@ -19,6 +19,8 @@ const SOURCE_LABELS = {
19
19
  };
20
20
  /** Brand name of the service an image was imported from; undefined for a file uploaded from disk. */
21
21
  export const getSourceLabel = file => file.source ? SOURCE_LABELS[file.source] : undefined;
22
+ /** Width-to-height ratio from image meta; 1 when the meta carries no size. */
23
+ export const getAspectRatio = file => file.meta?.type === 'imageMeta' && file.meta.data.height > 0 ? file.meta.data.width / file.meta.data.height : 1;
22
24
  /** True for an image that has been edited at least once, so older versions exist behind it. */
23
25
  export const hasVersions = file => file.versionsCount > 1;
24
26
  const BYTE_UNITS = ['B', 'KB', 'MB', 'GB', 'TB'];
@@ -31,4 +33,20 @@ export const formatBytes = bytes => {
31
33
  // Whole numbers for bytes and for values >= 100; one decimal otherwise.
32
34
  const rounded = exp === 0 || value >= 100 ? Math.round(value) : Math.round(value * 10) / 10;
33
35
  return `${rounded} ${BYTE_UNITS[exp]}`;
36
+ };
37
+ /** Savings of PNG compression over a batch: compressed files only; null when nothing worth a whole percent was saved. */
38
+ export const compressionSavings = files => {
39
+ let before = 0;
40
+ let after = 0;
41
+ for (const file of files) {
42
+ if (file.original_file_size && file.original_file_size > file.file_size) {
43
+ before += file.original_file_size;
44
+ after += file.file_size;
45
+ }
46
+ }
47
+ const percent = before === 0 ? 0 : Math.round((1 - after / before) * 100);
48
+ return percent > 0 ? {
49
+ percent,
50
+ savedBytes: before - after
51
+ } : null;
34
52
  };
@@ -10,9 +10,16 @@ export const messages = {
10
10
  'SMART.MEDIA_STORAGE.UPLOAD_ERROR': 'تعذّر رفع الصورة',
11
11
  'SMART.MEDIA_STORAGE.UNSUPPORTED_FORMAT': 'تنسيق غير مدعوم. استخدم JPEG أو PNG أو GIF أو WebP',
12
12
  'SMART.MEDIA_STORAGE.UPLOAD_SUCCESS': 'تم رفع الصورة',
13
+ 'SMART.MEDIA_STORAGE.UPLOAD_SUCCESS_COMPRESSED': 'تم رفع الصورة. تم ضغط PNG: توفير {{percent}}% ({{size}})',
13
14
  'SMART.MEDIA_STORAGE.USAGE': 'الملفات: {{count}} · {{size}}',
14
15
  'SMART.MEDIA_STORAGE.CROPS_BADGE': 'اقتصاصات: {{count}}',
15
16
  'SMART.MEDIA_STORAGE.VERSIONS_BADGE': 'إصدارات: {{count}}',
17
+ 'SMART.MEDIA_STORAGE.VERSIONS.HINT': 'عرض الإصدارات',
18
+ 'SMART.MEDIA_STORAGE.VERSIONS.TITLE': 'الإصدارات',
19
+ 'SMART.MEDIA_STORAGE.VERSIONS.CURRENT': 'الإصدار {{number}} · الحالي',
20
+ 'SMART.MEDIA_STORAGE.VERSIONS.NUMBER': 'الإصدار {{number}}',
21
+ 'SMART.MEDIA_STORAGE.VERSIONS.SELECT': 'استخدام هذا الإصدار',
22
+ 'SMART.MEDIA_STORAGE.VERSIONS.LIST_LABEL': 'كل الإصدارات',
16
23
  'SMART.MEDIA_STORAGE.DELETE_CONFIRM.DESCRIPTION_VERSION': 'سيتم حذف هذا الإصدار واقتصاصاته فقط، وسيظهر الإصدار السابق بدلًا منه. لا يمكن التراجع عن هذا الإجراء.',
17
24
  'SMART.MEDIA_STORAGE.SELECT_ORIGINAL': 'الأصل',
18
25
  'SMART.MEDIA_STORAGE.CREATE_CROP_HINT': 'إنشاء اقتصاصات',
@@ -45,6 +52,7 @@ export const messages = {
45
52
  'SMART.MEDIA_STORAGE.CREATE_CROP.NO_CROPS': 'لا توجد اقتصاصات بعد',
46
53
  'SMART.MEDIA_STORAGE.CREATE_CROP.SUCCESS': 'تم إنشاء الاقتصاص',
47
54
  'SMART.MEDIA_STORAGE.CREATE_CROP.ERROR': 'تعذّر إنشاء الاقتصاص',
55
+ 'SMART.MEDIA_STORAGE.CREATE_CROP.PREVIEW_HINT': 'عرض هذا الاقتصاص',
48
56
  'SMART.MEDIA_STORAGE.CREATE_CROP.DELETE_HINT': 'حذف الاقتصاص',
49
57
  'SMART.MEDIA_STORAGE.CREATE_CROP.DELETE_CONFIRM.TITLE': 'حذف هذا الاقتصاص؟',
50
58
  'SMART.MEDIA_STORAGE.CREATE_CROP.DELETE_CONFIRM.BUTTON': 'حذف',
@@ -10,9 +10,16 @@ export const messages = {
10
10
  'SMART.MEDIA_STORAGE.UPLOAD_ERROR': 'ছবি আপলোড করা যায়নি',
11
11
  'SMART.MEDIA_STORAGE.UNSUPPORTED_FORMAT': 'অসমর্থিত ফরম্যাট। JPEG, PNG, GIF বা WebP ব্যবহার করুন',
12
12
  'SMART.MEDIA_STORAGE.UPLOAD_SUCCESS': 'ছবি আপলোড হয়েছে',
13
+ 'SMART.MEDIA_STORAGE.UPLOAD_SUCCESS_COMPRESSED': 'ছবি আপলোড হয়েছে। PNG সংকুচিত: {{percent}}% সাশ্রয় ({{size}})',
13
14
  'SMART.MEDIA_STORAGE.USAGE': '{{count}}টি ফাইল · {{size}}',
14
15
  'SMART.MEDIA_STORAGE.CROPS_BADGE': '{{count}}টি ক্রপ',
15
16
  'SMART.MEDIA_STORAGE.VERSIONS_BADGE': '{{count}}টি সংস্করণ',
17
+ 'SMART.MEDIA_STORAGE.VERSIONS.HINT': 'সংস্করণ দেখান',
18
+ 'SMART.MEDIA_STORAGE.VERSIONS.TITLE': 'সংস্করণ',
19
+ 'SMART.MEDIA_STORAGE.VERSIONS.CURRENT': 'সংস্করণ {{number}} · বর্তমান',
20
+ 'SMART.MEDIA_STORAGE.VERSIONS.NUMBER': 'সংস্করণ {{number}}',
21
+ 'SMART.MEDIA_STORAGE.VERSIONS.SELECT': 'এই সংস্করণ ব্যবহার করুন',
22
+ 'SMART.MEDIA_STORAGE.VERSIONS.LIST_LABEL': 'সব সংস্করণ',
16
23
  'SMART.MEDIA_STORAGE.DELETE_CONFIRM.DESCRIPTION_VERSION': 'শুধু এই সংস্করণ এবং এর ক্রপ মুছে যাবে; এর জায়গায় আগের সংস্করণটি দেখানো হবে। এটি পূর্বাবস্থায় ফেরানো যাবে না।',
17
24
  'SMART.MEDIA_STORAGE.SELECT_ORIGINAL': 'মূল',
18
25
  'SMART.MEDIA_STORAGE.CREATE_CROP_HINT': 'ক্রপ তৈরি করুন',
@@ -45,6 +52,7 @@ export const messages = {
45
52
  'SMART.MEDIA_STORAGE.CREATE_CROP.NO_CROPS': 'এখনো কোনো ক্রপ নেই',
46
53
  'SMART.MEDIA_STORAGE.CREATE_CROP.SUCCESS': 'ক্রপ তৈরি হয়েছে',
47
54
  'SMART.MEDIA_STORAGE.CREATE_CROP.ERROR': 'ক্রপ তৈরি করা যায়নি',
55
+ 'SMART.MEDIA_STORAGE.CREATE_CROP.PREVIEW_HINT': 'এই ক্রপ দেখান',
48
56
  'SMART.MEDIA_STORAGE.CREATE_CROP.DELETE_HINT': 'ক্রপ মুছুন',
49
57
  'SMART.MEDIA_STORAGE.CREATE_CROP.DELETE_CONFIRM.TITLE': 'এই ক্রপটি মুছবেন?',
50
58
  'SMART.MEDIA_STORAGE.CREATE_CROP.DELETE_CONFIRM.BUTTON': 'মুছুন',
@@ -10,9 +10,16 @@ export const messages = {
10
10
  'SMART.MEDIA_STORAGE.UPLOAD_ERROR': 'Bild konnte nicht hochgeladen werden',
11
11
  'SMART.MEDIA_STORAGE.UNSUPPORTED_FORMAT': 'Nicht unterstütztes Format. Verwenden Sie JPEG, PNG, GIF oder WebP',
12
12
  'SMART.MEDIA_STORAGE.UPLOAD_SUCCESS': 'Bild hochgeladen',
13
+ 'SMART.MEDIA_STORAGE.UPLOAD_SUCCESS_COMPRESSED': 'Bild hochgeladen. PNG komprimiert: {{percent}} % gespart ({{size}})',
13
14
  'SMART.MEDIA_STORAGE.USAGE': '{{count}} Dateien · {{size}}',
14
15
  'SMART.MEDIA_STORAGE.CROPS_BADGE': '{{count}} Zuschn.',
15
16
  'SMART.MEDIA_STORAGE.VERSIONS_BADGE': '{{count}} Vers.',
17
+ 'SMART.MEDIA_STORAGE.VERSIONS.HINT': 'Versionen anzeigen',
18
+ 'SMART.MEDIA_STORAGE.VERSIONS.TITLE': 'Versionen',
19
+ 'SMART.MEDIA_STORAGE.VERSIONS.CURRENT': 'Version {{number}} · aktuell',
20
+ 'SMART.MEDIA_STORAGE.VERSIONS.NUMBER': 'Version {{number}}',
21
+ 'SMART.MEDIA_STORAGE.VERSIONS.SELECT': 'Diese Version verwenden',
22
+ 'SMART.MEDIA_STORAGE.VERSIONS.LIST_LABEL': 'Alle Versionen',
16
23
  'SMART.MEDIA_STORAGE.DELETE_CONFIRM.DESCRIPTION_VERSION': 'Nur diese Version und ihre Zuschnitte werden gelöscht; stattdessen wird die vorherige Version angezeigt. Dies kann nicht rückgängig gemacht werden.',
17
24
  'SMART.MEDIA_STORAGE.SELECT_ORIGINAL': 'Original',
18
25
  'SMART.MEDIA_STORAGE.CREATE_CROP_HINT': 'Zuschnitte erstellen',
@@ -45,6 +52,7 @@ export const messages = {
45
52
  'SMART.MEDIA_STORAGE.CREATE_CROP.NO_CROPS': 'Noch keine Zuschnitte',
46
53
  'SMART.MEDIA_STORAGE.CREATE_CROP.SUCCESS': 'Zuschnitt erstellt',
47
54
  'SMART.MEDIA_STORAGE.CREATE_CROP.ERROR': 'Zuschnitt konnte nicht erstellt werden',
55
+ 'SMART.MEDIA_STORAGE.CREATE_CROP.PREVIEW_HINT': 'Zuschnitt anzeigen',
48
56
  'SMART.MEDIA_STORAGE.CREATE_CROP.DELETE_HINT': 'Zuschnitt löschen',
49
57
  'SMART.MEDIA_STORAGE.CREATE_CROP.DELETE_CONFIRM.TITLE': 'Diesen Zuschnitt löschen?',
50
58
  'SMART.MEDIA_STORAGE.CREATE_CROP.DELETE_CONFIRM.BUTTON': 'Löschen',
@@ -10,9 +10,16 @@ export const messages = {
10
10
  'SMART.MEDIA_STORAGE.UPLOAD_ERROR': 'Failed to upload image',
11
11
  'SMART.MEDIA_STORAGE.UNSUPPORTED_FORMAT': 'Unsupported format. Use JPEG, PNG, GIF or WebP',
12
12
  'SMART.MEDIA_STORAGE.UPLOAD_SUCCESS': 'Image uploaded',
13
+ 'SMART.MEDIA_STORAGE.UPLOAD_SUCCESS_COMPRESSED': 'Image uploaded. PNG compressed: saved {{percent}}% ({{size}})',
13
14
  'SMART.MEDIA_STORAGE.USAGE': '{{count}} files · {{size}}',
14
15
  'SMART.MEDIA_STORAGE.CROPS_BADGE': '{{count}} cr.',
15
16
  'SMART.MEDIA_STORAGE.VERSIONS_BADGE': '{{count}} ver.',
17
+ 'SMART.MEDIA_STORAGE.VERSIONS.HINT': 'Show versions',
18
+ 'SMART.MEDIA_STORAGE.VERSIONS.TITLE': 'Versions',
19
+ 'SMART.MEDIA_STORAGE.VERSIONS.CURRENT': 'Version {{number}} · current',
20
+ 'SMART.MEDIA_STORAGE.VERSIONS.NUMBER': 'Version {{number}}',
21
+ 'SMART.MEDIA_STORAGE.VERSIONS.SELECT': 'Use this version',
22
+ 'SMART.MEDIA_STORAGE.VERSIONS.LIST_LABEL': 'All versions',
16
23
  'SMART.MEDIA_STORAGE.DELETE_CONFIRM.DESCRIPTION_VERSION': 'Only this version and its crops are deleted; the previous version is shown instead. This can’t be undone.',
17
24
  'SMART.MEDIA_STORAGE.SELECT_ORIGINAL': 'Original',
18
25
  'SMART.MEDIA_STORAGE.CREATE_CROP_HINT': 'Create crops',
@@ -45,6 +52,7 @@ export const messages = {
45
52
  'SMART.MEDIA_STORAGE.CREATE_CROP.NO_CROPS': 'No crops yet',
46
53
  'SMART.MEDIA_STORAGE.CREATE_CROP.SUCCESS': 'Crop created',
47
54
  'SMART.MEDIA_STORAGE.CREATE_CROP.ERROR': 'Failed to create crop',
55
+ 'SMART.MEDIA_STORAGE.CREATE_CROP.PREVIEW_HINT': 'Show this crop',
48
56
  'SMART.MEDIA_STORAGE.CREATE_CROP.DELETE_HINT': 'Delete crop',
49
57
  'SMART.MEDIA_STORAGE.CREATE_CROP.DELETE_CONFIRM.TITLE': 'Delete this crop?',
50
58
  'SMART.MEDIA_STORAGE.CREATE_CROP.DELETE_CONFIRM.BUTTON': 'Delete',
@@ -10,9 +10,16 @@ export const messages = {
10
10
  'SMART.MEDIA_STORAGE.UPLOAD_ERROR': 'No se pudo subir la imagen',
11
11
  'SMART.MEDIA_STORAGE.UNSUPPORTED_FORMAT': 'Formato no compatible. Usa JPEG, PNG, GIF o WebP',
12
12
  'SMART.MEDIA_STORAGE.UPLOAD_SUCCESS': 'Imagen subida',
13
+ 'SMART.MEDIA_STORAGE.UPLOAD_SUCCESS_COMPRESSED': 'Imagen subida. PNG comprimido: ahorro del {{percent}} % ({{size}})',
13
14
  'SMART.MEDIA_STORAGE.USAGE': '{{count}} archivos · {{size}}',
14
15
  'SMART.MEDIA_STORAGE.CROPS_BADGE': '{{count}} rec.',
15
16
  'SMART.MEDIA_STORAGE.VERSIONS_BADGE': '{{count}} vers.',
17
+ 'SMART.MEDIA_STORAGE.VERSIONS.HINT': 'Mostrar versiones',
18
+ 'SMART.MEDIA_STORAGE.VERSIONS.TITLE': 'Versiones',
19
+ 'SMART.MEDIA_STORAGE.VERSIONS.CURRENT': 'Versión {{number}} · actual',
20
+ 'SMART.MEDIA_STORAGE.VERSIONS.NUMBER': 'Versión {{number}}',
21
+ 'SMART.MEDIA_STORAGE.VERSIONS.SELECT': 'Usar esta versión',
22
+ 'SMART.MEDIA_STORAGE.VERSIONS.LIST_LABEL': 'Todas las versiones',
16
23
  'SMART.MEDIA_STORAGE.DELETE_CONFIRM.DESCRIPTION_VERSION': 'Solo se eliminan esta versión y sus recortes; en su lugar se muestra la versión anterior. Esta acción no se puede deshacer.',
17
24
  'SMART.MEDIA_STORAGE.SELECT_ORIGINAL': 'Original',
18
25
  'SMART.MEDIA_STORAGE.CREATE_CROP_HINT': 'Crear recortes',
@@ -45,6 +52,7 @@ export const messages = {
45
52
  'SMART.MEDIA_STORAGE.CREATE_CROP.NO_CROPS': 'Aún no hay recortes',
46
53
  'SMART.MEDIA_STORAGE.CREATE_CROP.SUCCESS': 'Recorte creado',
47
54
  'SMART.MEDIA_STORAGE.CREATE_CROP.ERROR': 'No se pudo crear el recorte',
55
+ 'SMART.MEDIA_STORAGE.CREATE_CROP.PREVIEW_HINT': 'Mostrar este recorte',
48
56
  'SMART.MEDIA_STORAGE.CREATE_CROP.DELETE_HINT': 'Eliminar recorte',
49
57
  'SMART.MEDIA_STORAGE.CREATE_CROP.DELETE_CONFIRM.TITLE': '¿Eliminar este recorte?',
50
58
  'SMART.MEDIA_STORAGE.CREATE_CROP.DELETE_CONFIRM.BUTTON': 'Eliminar',
@@ -10,9 +10,16 @@ export const messages = {
10
10
  'SMART.MEDIA_STORAGE.UPLOAD_ERROR': 'Échec de l’importation de l’image',
11
11
  'SMART.MEDIA_STORAGE.UNSUPPORTED_FORMAT': 'Format non pris en charge. Utilisez JPEG, PNG, GIF ou WebP',
12
12
  'SMART.MEDIA_STORAGE.UPLOAD_SUCCESS': 'Image importée',
13
+ 'SMART.MEDIA_STORAGE.UPLOAD_SUCCESS_COMPRESSED': 'Image importée. PNG compressé : {{percent}} % économisés ({{size}})',
13
14
  'SMART.MEDIA_STORAGE.USAGE': '{{count}} fichiers · {{size}}',
14
15
  'SMART.MEDIA_STORAGE.CROPS_BADGE': '{{count}} recadr.',
15
16
  'SMART.MEDIA_STORAGE.VERSIONS_BADGE': '{{count}} vers.',
17
+ 'SMART.MEDIA_STORAGE.VERSIONS.HINT': 'Afficher les versions',
18
+ 'SMART.MEDIA_STORAGE.VERSIONS.TITLE': 'Versions',
19
+ 'SMART.MEDIA_STORAGE.VERSIONS.CURRENT': 'Version {{number}} · actuelle',
20
+ 'SMART.MEDIA_STORAGE.VERSIONS.NUMBER': 'Version {{number}}',
21
+ 'SMART.MEDIA_STORAGE.VERSIONS.SELECT': 'Utiliser cette version',
22
+ 'SMART.MEDIA_STORAGE.VERSIONS.LIST_LABEL': 'Toutes les versions',
16
23
  'SMART.MEDIA_STORAGE.DELETE_CONFIRM.DESCRIPTION_VERSION': 'Seuls cette version et ses recadrages sont supprimés ; la version précédente s’affiche à la place. Cette action est irréversible.',
17
24
  'SMART.MEDIA_STORAGE.SELECT_ORIGINAL': 'Original',
18
25
  'SMART.MEDIA_STORAGE.CREATE_CROP_HINT': 'Créer des recadrages',
@@ -45,6 +52,7 @@ export const messages = {
45
52
  'SMART.MEDIA_STORAGE.CREATE_CROP.NO_CROPS': 'Aucun recadrage pour le moment',
46
53
  'SMART.MEDIA_STORAGE.CREATE_CROP.SUCCESS': 'Recadrage créé',
47
54
  'SMART.MEDIA_STORAGE.CREATE_CROP.ERROR': 'Échec de la création du recadrage',
55
+ 'SMART.MEDIA_STORAGE.CREATE_CROP.PREVIEW_HINT': 'Afficher ce recadrage',
48
56
  'SMART.MEDIA_STORAGE.CREATE_CROP.DELETE_HINT': 'Supprimer le recadrage',
49
57
  'SMART.MEDIA_STORAGE.CREATE_CROP.DELETE_CONFIRM.TITLE': 'Supprimer ce recadrage ?',
50
58
  'SMART.MEDIA_STORAGE.CREATE_CROP.DELETE_CONFIRM.BUTTON': 'Supprimer',
@@ -10,9 +10,16 @@ export const messages = {
10
10
  'SMART.MEDIA_STORAGE.UPLOAD_ERROR': 'העלאת התמונה נכשלה',
11
11
  'SMART.MEDIA_STORAGE.UNSUPPORTED_FORMAT': 'פורמט לא נתמך. השתמשו ב-JPEG, PNG, GIF או WebP',
12
12
  'SMART.MEDIA_STORAGE.UPLOAD_SUCCESS': 'התמונה הועלתה',
13
+ 'SMART.MEDIA_STORAGE.UPLOAD_SUCCESS_COMPRESSED': 'התמונה הועלתה. PNG נדחס: חיסכון של {{percent}}% ({{size}})',
13
14
  'SMART.MEDIA_STORAGE.USAGE': '{{count}} קבצים · {{size}}',
14
15
  'SMART.MEDIA_STORAGE.CROPS_BADGE': '{{count}} חית.',
15
16
  'SMART.MEDIA_STORAGE.VERSIONS_BADGE': '{{count}} גרס.',
17
+ 'SMART.MEDIA_STORAGE.VERSIONS.HINT': 'הצג גרסאות',
18
+ 'SMART.MEDIA_STORAGE.VERSIONS.TITLE': 'גרסאות',
19
+ 'SMART.MEDIA_STORAGE.VERSIONS.CURRENT': 'גרסה {{number}} · נוכחית',
20
+ 'SMART.MEDIA_STORAGE.VERSIONS.NUMBER': 'גרסה {{number}}',
21
+ 'SMART.MEDIA_STORAGE.VERSIONS.SELECT': 'השתמש בגרסה זו',
22
+ 'SMART.MEDIA_STORAGE.VERSIONS.LIST_LABEL': 'כל הגרסאות',
16
23
  'SMART.MEDIA_STORAGE.DELETE_CONFIRM.DESCRIPTION_VERSION': 'רק הגרסה הזו והחיתוכים שלה יימחקו; במקומה תוצג הגרסה הקודמת. לא ניתן לבטל פעולה זו.',
17
24
  'SMART.MEDIA_STORAGE.SELECT_ORIGINAL': 'מקורי',
18
25
  'SMART.MEDIA_STORAGE.CREATE_CROP_HINT': 'יצירת חיתוכים',
@@ -45,6 +52,7 @@ export const messages = {
45
52
  'SMART.MEDIA_STORAGE.CREATE_CROP.NO_CROPS': 'אין עדיין חיתוכים',
46
53
  'SMART.MEDIA_STORAGE.CREATE_CROP.SUCCESS': 'החיתוך נוצר',
47
54
  'SMART.MEDIA_STORAGE.CREATE_CROP.ERROR': 'יצירת החיתוך נכשלה',
55
+ 'SMART.MEDIA_STORAGE.CREATE_CROP.PREVIEW_HINT': 'הצג חיתוך זה',
48
56
  'SMART.MEDIA_STORAGE.CREATE_CROP.DELETE_HINT': 'מחיקת חיתוך',
49
57
  'SMART.MEDIA_STORAGE.CREATE_CROP.DELETE_CONFIRM.TITLE': 'למחוק את החיתוך הזה?',
50
58
  'SMART.MEDIA_STORAGE.CREATE_CROP.DELETE_CONFIRM.BUTTON': 'מחיקה',
@@ -10,9 +10,16 @@ export const messages = {
10
10
  'SMART.MEDIA_STORAGE.UPLOAD_ERROR': 'छवि अपलोड नहीं हो सकी',
11
11
  'SMART.MEDIA_STORAGE.UNSUPPORTED_FORMAT': 'असमर्थित फ़ॉर्मेट। JPEG, PNG, GIF या WebP का उपयोग करें',
12
12
  'SMART.MEDIA_STORAGE.UPLOAD_SUCCESS': 'छवि अपलोड हो गई',
13
+ 'SMART.MEDIA_STORAGE.UPLOAD_SUCCESS_COMPRESSED': 'छवि अपलोड हो गई। PNG संपीड़ित: {{percent}}% की बचत ({{size}})',
13
14
  'SMART.MEDIA_STORAGE.USAGE': '{{count}} फ़ाइलें · {{size}}',
14
15
  'SMART.MEDIA_STORAGE.CROPS_BADGE': '{{count}} क्रॉप',
15
16
  'SMART.MEDIA_STORAGE.VERSIONS_BADGE': '{{count}} संस्करण',
17
+ 'SMART.MEDIA_STORAGE.VERSIONS.HINT': 'संस्करण दिखाएँ',
18
+ 'SMART.MEDIA_STORAGE.VERSIONS.TITLE': 'संस्करण',
19
+ 'SMART.MEDIA_STORAGE.VERSIONS.CURRENT': 'संस्करण {{number}} · वर्तमान',
20
+ 'SMART.MEDIA_STORAGE.VERSIONS.NUMBER': 'संस्करण {{number}}',
21
+ 'SMART.MEDIA_STORAGE.VERSIONS.SELECT': 'यह संस्करण उपयोग करें',
22
+ 'SMART.MEDIA_STORAGE.VERSIONS.LIST_LABEL': 'सभी संस्करण',
16
23
  'SMART.MEDIA_STORAGE.DELETE_CONFIRM.DESCRIPTION_VERSION': 'केवल यह संस्करण और इसके क्रॉप हटेंगे; इसकी जगह पिछला संस्करण दिखाया जाएगा। इसे पूर्ववत नहीं किया जा सकता।',
17
24
  'SMART.MEDIA_STORAGE.SELECT_ORIGINAL': 'मूल',
18
25
  'SMART.MEDIA_STORAGE.CREATE_CROP_HINT': 'क्रॉप बनाएँ',
@@ -45,6 +52,7 @@ export const messages = {
45
52
  'SMART.MEDIA_STORAGE.CREATE_CROP.NO_CROPS': 'अभी तक कोई क्रॉप नहीं',
46
53
  'SMART.MEDIA_STORAGE.CREATE_CROP.SUCCESS': 'क्रॉप बनाया गया',
47
54
  'SMART.MEDIA_STORAGE.CREATE_CROP.ERROR': 'क्रॉप नहीं बनाया जा सका',
55
+ 'SMART.MEDIA_STORAGE.CREATE_CROP.PREVIEW_HINT': 'यह क्रॉप दिखाएँ',
48
56
  'SMART.MEDIA_STORAGE.CREATE_CROP.DELETE_HINT': 'क्रॉप हटाएँ',
49
57
  'SMART.MEDIA_STORAGE.CREATE_CROP.DELETE_CONFIRM.TITLE': 'यह क्रॉप हटाएँ?',
50
58
  'SMART.MEDIA_STORAGE.CREATE_CROP.DELETE_CONFIRM.BUTTON': 'हटाएँ',
@@ -10,9 +10,16 @@ export const messages = {
10
10
  'SMART.MEDIA_STORAGE.UPLOAD_ERROR': '画像のアップロードに失敗しました',
11
11
  'SMART.MEDIA_STORAGE.UNSUPPORTED_FORMAT': 'サポートされていない形式です。JPEG、PNG、GIF、WebPを使用してください',
12
12
  'SMART.MEDIA_STORAGE.UPLOAD_SUCCESS': '画像をアップロードしました',
13
+ 'SMART.MEDIA_STORAGE.UPLOAD_SUCCESS_COMPRESSED': '画像をアップロードしました。PNG を圧縮: {{percent}}% 削減({{size}})',
13
14
  'SMART.MEDIA_STORAGE.USAGE': '{{count}}ファイル · {{size}}',
14
15
  'SMART.MEDIA_STORAGE.CROPS_BADGE': 'トリミング{{count}}',
15
16
  'SMART.MEDIA_STORAGE.VERSIONS_BADGE': 'バージョン{{count}}',
17
+ 'SMART.MEDIA_STORAGE.VERSIONS.HINT': 'バージョンを表示',
18
+ 'SMART.MEDIA_STORAGE.VERSIONS.TITLE': 'バージョン',
19
+ 'SMART.MEDIA_STORAGE.VERSIONS.CURRENT': 'バージョン {{number}} · 現在',
20
+ 'SMART.MEDIA_STORAGE.VERSIONS.NUMBER': 'バージョン {{number}}',
21
+ 'SMART.MEDIA_STORAGE.VERSIONS.SELECT': 'このバージョンを使用',
22
+ 'SMART.MEDIA_STORAGE.VERSIONS.LIST_LABEL': 'すべてのバージョン',
16
23
  'SMART.MEDIA_STORAGE.DELETE_CONFIRM.DESCRIPTION_VERSION': 'このバージョンとそのトリミングのみが削除され、代わりに以前のバージョンが表示されます。この操作は元に戻せません。',
17
24
  'SMART.MEDIA_STORAGE.SELECT_ORIGINAL': 'オリジナル',
18
25
  'SMART.MEDIA_STORAGE.CREATE_CROP_HINT': 'トリミングを作成',
@@ -45,6 +52,7 @@ export const messages = {
45
52
  'SMART.MEDIA_STORAGE.CREATE_CROP.NO_CROPS': 'トリミングはまだありません',
46
53
  'SMART.MEDIA_STORAGE.CREATE_CROP.SUCCESS': 'トリミングを作成しました',
47
54
  'SMART.MEDIA_STORAGE.CREATE_CROP.ERROR': 'トリミングの作成に失敗しました',
55
+ 'SMART.MEDIA_STORAGE.CREATE_CROP.PREVIEW_HINT': 'このトリミングを表示',
48
56
  'SMART.MEDIA_STORAGE.CREATE_CROP.DELETE_HINT': 'トリミングを削除',
49
57
  'SMART.MEDIA_STORAGE.CREATE_CROP.DELETE_CONFIRM.TITLE': 'このトリミングを削除しますか?',
50
58
  'SMART.MEDIA_STORAGE.CREATE_CROP.DELETE_CONFIRM.BUTTON': '削除',
@@ -10,9 +10,16 @@ export const messages = {
10
10
  'SMART.MEDIA_STORAGE.UPLOAD_ERROR': '이미지를 업로드하지 못했습니다',
11
11
  'SMART.MEDIA_STORAGE.UNSUPPORTED_FORMAT': '지원되지 않는 형식입니다. JPEG, PNG, GIF 또는 WebP를 사용하세요',
12
12
  'SMART.MEDIA_STORAGE.UPLOAD_SUCCESS': '이미지가 업로드되었습니다',
13
+ 'SMART.MEDIA_STORAGE.UPLOAD_SUCCESS_COMPRESSED': '이미지가 업로드되었습니다. PNG 압축: {{percent}}% 절감 ({{size}})',
13
14
  'SMART.MEDIA_STORAGE.USAGE': '파일 {{count}}개 · {{size}}',
14
15
  'SMART.MEDIA_STORAGE.CROPS_BADGE': '크롭 {{count}}개',
15
16
  'SMART.MEDIA_STORAGE.VERSIONS_BADGE': '버전 {{count}}개',
17
+ 'SMART.MEDIA_STORAGE.VERSIONS.HINT': '버전 보기',
18
+ 'SMART.MEDIA_STORAGE.VERSIONS.TITLE': '버전',
19
+ 'SMART.MEDIA_STORAGE.VERSIONS.CURRENT': '버전 {{number}} · 현재',
20
+ 'SMART.MEDIA_STORAGE.VERSIONS.NUMBER': '버전 {{number}}',
21
+ 'SMART.MEDIA_STORAGE.VERSIONS.SELECT': '이 버전 사용',
22
+ 'SMART.MEDIA_STORAGE.VERSIONS.LIST_LABEL': '모든 버전',
16
23
  'SMART.MEDIA_STORAGE.DELETE_CONFIRM.DESCRIPTION_VERSION': '이 버전과 해당 크롭만 삭제되며 대신 이전 버전이 표시됩니다. 이 작업은 되돌릴 수 없습니다.',
17
24
  'SMART.MEDIA_STORAGE.SELECT_ORIGINAL': '원본',
18
25
  'SMART.MEDIA_STORAGE.CREATE_CROP_HINT': '크롭 만들기',
@@ -45,6 +52,7 @@ export const messages = {
45
52
  'SMART.MEDIA_STORAGE.CREATE_CROP.NO_CROPS': '아직 크롭이 없습니다',
46
53
  'SMART.MEDIA_STORAGE.CREATE_CROP.SUCCESS': '크롭이 생성되었습니다',
47
54
  'SMART.MEDIA_STORAGE.CREATE_CROP.ERROR': '크롭을 생성하지 못했습니다',
55
+ 'SMART.MEDIA_STORAGE.CREATE_CROP.PREVIEW_HINT': '이 크롭 보기',
48
56
  'SMART.MEDIA_STORAGE.CREATE_CROP.DELETE_HINT': '크롭 삭제',
49
57
  'SMART.MEDIA_STORAGE.CREATE_CROP.DELETE_CONFIRM.TITLE': '이 크롭을 삭제하시겠습니까?',
50
58
  'SMART.MEDIA_STORAGE.CREATE_CROP.DELETE_CONFIRM.BUTTON': '삭제',
@@ -10,9 +10,16 @@ export const messages = {
10
10
  'SMART.MEDIA_STORAGE.UPLOAD_ERROR': 'Falha ao enviar a imagem',
11
11
  'SMART.MEDIA_STORAGE.UNSUPPORTED_FORMAT': 'Formato não suportado. Use JPEG, PNG, GIF ou WebP',
12
12
  'SMART.MEDIA_STORAGE.UPLOAD_SUCCESS': 'Imagem enviada',
13
+ 'SMART.MEDIA_STORAGE.UPLOAD_SUCCESS_COMPRESSED': 'Imagem enviada. PNG comprimido: economia de {{percent}}% ({{size}})',
13
14
  'SMART.MEDIA_STORAGE.USAGE': '{{count}} arquivos · {{size}}',
14
15
  'SMART.MEDIA_STORAGE.CROPS_BADGE': '{{count}} rec.',
15
16
  'SMART.MEDIA_STORAGE.VERSIONS_BADGE': '{{count}} vers.',
17
+ 'SMART.MEDIA_STORAGE.VERSIONS.HINT': 'Mostrar versões',
18
+ 'SMART.MEDIA_STORAGE.VERSIONS.TITLE': 'Versões',
19
+ 'SMART.MEDIA_STORAGE.VERSIONS.CURRENT': 'Versão {{number}} · atual',
20
+ 'SMART.MEDIA_STORAGE.VERSIONS.NUMBER': 'Versão {{number}}',
21
+ 'SMART.MEDIA_STORAGE.VERSIONS.SELECT': 'Usar esta versão',
22
+ 'SMART.MEDIA_STORAGE.VERSIONS.LIST_LABEL': 'Todas as versões',
16
23
  'SMART.MEDIA_STORAGE.DELETE_CONFIRM.DESCRIPTION_VERSION': 'Apenas esta versão e seus recortes são excluídos; a versão anterior é exibida no lugar. Esta ação não pode ser desfeita.',
17
24
  'SMART.MEDIA_STORAGE.SELECT_ORIGINAL': 'Original',
18
25
  'SMART.MEDIA_STORAGE.CREATE_CROP_HINT': 'Criar recortes',
@@ -45,6 +52,7 @@ export const messages = {
45
52
  'SMART.MEDIA_STORAGE.CREATE_CROP.NO_CROPS': 'Nenhum recorte ainda',
46
53
  'SMART.MEDIA_STORAGE.CREATE_CROP.SUCCESS': 'Recorte criado',
47
54
  'SMART.MEDIA_STORAGE.CREATE_CROP.ERROR': 'Falha ao criar o recorte',
55
+ 'SMART.MEDIA_STORAGE.CREATE_CROP.PREVIEW_HINT': 'Mostrar este recorte',
48
56
  'SMART.MEDIA_STORAGE.CREATE_CROP.DELETE_HINT': 'Excluir recorte',
49
57
  'SMART.MEDIA_STORAGE.CREATE_CROP.DELETE_CONFIRM.TITLE': 'Excluir este recorte?',
50
58
  'SMART.MEDIA_STORAGE.CREATE_CROP.DELETE_CONFIRM.BUTTON': 'Excluir',
@@ -10,9 +10,16 @@ export const messages = {
10
10
  'SMART.MEDIA_STORAGE.UPLOAD_ERROR': 'Не удалось загрузить изображение',
11
11
  'SMART.MEDIA_STORAGE.UNSUPPORTED_FORMAT': 'Неподдерживаемый формат. Используйте JPEG, PNG, GIF или WebP',
12
12
  'SMART.MEDIA_STORAGE.UPLOAD_SUCCESS': 'Изображение загружено',
13
+ 'SMART.MEDIA_STORAGE.UPLOAD_SUCCESS_COMPRESSED': 'Изображение загружено. PNG сжат: экономия {{percent}}% ({{size}})',
13
14
  'SMART.MEDIA_STORAGE.USAGE': '{{count}} файлов · {{size}}',
14
15
  'SMART.MEDIA_STORAGE.CROPS_BADGE': '{{count}} кр.',
15
16
  'SMART.MEDIA_STORAGE.VERSIONS_BADGE': '{{count}} вер.',
17
+ 'SMART.MEDIA_STORAGE.VERSIONS.HINT': 'Показать версии',
18
+ 'SMART.MEDIA_STORAGE.VERSIONS.TITLE': 'Версии',
19
+ 'SMART.MEDIA_STORAGE.VERSIONS.CURRENT': 'Версия {{number}} · текущая',
20
+ 'SMART.MEDIA_STORAGE.VERSIONS.NUMBER': 'Версия {{number}}',
21
+ 'SMART.MEDIA_STORAGE.VERSIONS.SELECT': 'Использовать эту версию',
22
+ 'SMART.MEDIA_STORAGE.VERSIONS.LIST_LABEL': 'Все версии',
16
23
  'SMART.MEDIA_STORAGE.DELETE_CONFIRM.DESCRIPTION_VERSION': 'Удалится только эта версия и её кропы, вместо неё снова появится предыдущая. Действие необратимо.',
17
24
  'SMART.MEDIA_STORAGE.SELECT_ORIGINAL': 'Оригинал',
18
25
  'SMART.MEDIA_STORAGE.CREATE_CROP_HINT': 'Создать кропы',
@@ -45,6 +52,7 @@ export const messages = {
45
52
  'SMART.MEDIA_STORAGE.CREATE_CROP.NO_CROPS': 'Кропов пока нет',
46
53
  'SMART.MEDIA_STORAGE.CREATE_CROP.SUCCESS': 'Кроп создан',
47
54
  'SMART.MEDIA_STORAGE.CREATE_CROP.ERROR': 'Не удалось создать кроп',
55
+ 'SMART.MEDIA_STORAGE.CREATE_CROP.PREVIEW_HINT': 'Показать кроп',
48
56
  'SMART.MEDIA_STORAGE.CREATE_CROP.DELETE_HINT': 'Удалить кроп',
49
57
  'SMART.MEDIA_STORAGE.CREATE_CROP.DELETE_CONFIRM.TITLE': 'Удалить кроп?',
50
58
  'SMART.MEDIA_STORAGE.CREATE_CROP.DELETE_CONFIRM.BUTTON': 'Удалить',
@@ -10,9 +10,16 @@ export const messages = {
10
10
  'SMART.MEDIA_STORAGE.UPLOAD_ERROR': 'อัปโหลดรูปภาพไม่สำเร็จ',
11
11
  'SMART.MEDIA_STORAGE.UNSUPPORTED_FORMAT': 'ไม่รองรับรูปแบบนี้ ใช้ JPEG, PNG, GIF หรือ WebP',
12
12
  'SMART.MEDIA_STORAGE.UPLOAD_SUCCESS': 'อัปโหลดรูปภาพแล้ว',
13
+ 'SMART.MEDIA_STORAGE.UPLOAD_SUCCESS_COMPRESSED': 'อัปโหลดรูปภาพแล้ว บีบอัด PNG: ประหยัด {{percent}}% ({{size}})',
13
14
  'SMART.MEDIA_STORAGE.USAGE': '{{count}} ไฟล์ · {{size}}',
14
15
  'SMART.MEDIA_STORAGE.CROPS_BADGE': '{{count}} ครอป',
15
16
  'SMART.MEDIA_STORAGE.VERSIONS_BADGE': '{{count}} เวอร์ชัน',
17
+ 'SMART.MEDIA_STORAGE.VERSIONS.HINT': 'แสดงเวอร์ชัน',
18
+ 'SMART.MEDIA_STORAGE.VERSIONS.TITLE': 'เวอร์ชัน',
19
+ 'SMART.MEDIA_STORAGE.VERSIONS.CURRENT': 'เวอร์ชัน {{number}} · ปัจจุบัน',
20
+ 'SMART.MEDIA_STORAGE.VERSIONS.NUMBER': 'เวอร์ชัน {{number}}',
21
+ 'SMART.MEDIA_STORAGE.VERSIONS.SELECT': 'ใช้เวอร์ชันนี้',
22
+ 'SMART.MEDIA_STORAGE.VERSIONS.LIST_LABEL': 'ทุกเวอร์ชัน',
16
23
  'SMART.MEDIA_STORAGE.DELETE_CONFIRM.DESCRIPTION_VERSION': 'จะลบเฉพาะเวอร์ชันนี้และครอปของเวอร์ชันนี้ และจะแสดงเวอร์ชันก่อนหน้าแทน การดำเนินการนี้ไม่สามารถย้อนกลับได้',
17
24
  'SMART.MEDIA_STORAGE.SELECT_ORIGINAL': 'ต้นฉบับ',
18
25
  'SMART.MEDIA_STORAGE.CREATE_CROP_HINT': 'สร้างครอป',
@@ -45,6 +52,7 @@ export const messages = {
45
52
  'SMART.MEDIA_STORAGE.CREATE_CROP.NO_CROPS': 'ยังไม่มีครอป',
46
53
  'SMART.MEDIA_STORAGE.CREATE_CROP.SUCCESS': 'สร้างครอปแล้ว',
47
54
  'SMART.MEDIA_STORAGE.CREATE_CROP.ERROR': 'สร้างครอปไม่สำเร็จ',
55
+ 'SMART.MEDIA_STORAGE.CREATE_CROP.PREVIEW_HINT': 'แสดงภาพครอปนี้',
48
56
  'SMART.MEDIA_STORAGE.CREATE_CROP.DELETE_HINT': 'ลบครอป',
49
57
  'SMART.MEDIA_STORAGE.CREATE_CROP.DELETE_CONFIRM.TITLE': 'ลบครอปนี้ใช่ไหม',
50
58
  'SMART.MEDIA_STORAGE.CREATE_CROP.DELETE_CONFIRM.BUTTON': 'ลบ',
@@ -10,9 +10,16 @@ export const messages = {
10
10
  'SMART.MEDIA_STORAGE.UPLOAD_ERROR': '上传图片失败',
11
11
  'SMART.MEDIA_STORAGE.UNSUPPORTED_FORMAT': '不支持的格式。请使用 JPEG、PNG、GIF 或 WebP',
12
12
  'SMART.MEDIA_STORAGE.UPLOAD_SUCCESS': '图片已上传',
13
+ 'SMART.MEDIA_STORAGE.UPLOAD_SUCCESS_COMPRESSED': '图片已上传。PNG 已压缩:节省 {{percent}}%({{size}})',
13
14
  'SMART.MEDIA_STORAGE.USAGE': '{{count}} 个文件 · {{size}}',
14
15
  'SMART.MEDIA_STORAGE.CROPS_BADGE': '{{count}} 个裁剪图',
15
16
  'SMART.MEDIA_STORAGE.VERSIONS_BADGE': '{{count}} 个版本',
17
+ 'SMART.MEDIA_STORAGE.VERSIONS.HINT': '查看版本',
18
+ 'SMART.MEDIA_STORAGE.VERSIONS.TITLE': '版本',
19
+ 'SMART.MEDIA_STORAGE.VERSIONS.CURRENT': '版本 {{number}} · 当前',
20
+ 'SMART.MEDIA_STORAGE.VERSIONS.NUMBER': '版本 {{number}}',
21
+ 'SMART.MEDIA_STORAGE.VERSIONS.SELECT': '使用此版本',
22
+ 'SMART.MEDIA_STORAGE.VERSIONS.LIST_LABEL': '全部版本',
16
23
  'SMART.MEDIA_STORAGE.DELETE_CONFIRM.DESCRIPTION_VERSION': '仅删除此版本及其裁剪图,并改为显示上一个版本。此操作无法撤销。',
17
24
  'SMART.MEDIA_STORAGE.SELECT_ORIGINAL': '原图',
18
25
  'SMART.MEDIA_STORAGE.CREATE_CROP_HINT': '创建裁剪图',
@@ -45,6 +52,7 @@ export const messages = {
45
52
  'SMART.MEDIA_STORAGE.CREATE_CROP.NO_CROPS': '暂无裁剪图',
46
53
  'SMART.MEDIA_STORAGE.CREATE_CROP.SUCCESS': '裁剪图已创建',
47
54
  'SMART.MEDIA_STORAGE.CREATE_CROP.ERROR': '创建裁剪图失败',
55
+ 'SMART.MEDIA_STORAGE.CREATE_CROP.PREVIEW_HINT': '查看此裁剪',
48
56
  'SMART.MEDIA_STORAGE.CREATE_CROP.DELETE_HINT': '删除裁剪图',
49
57
  'SMART.MEDIA_STORAGE.CREATE_CROP.DELETE_CONFIRM.TITLE': '删除此裁剪图?',
50
58
  'SMART.MEDIA_STORAGE.CREATE_CROP.DELETE_CONFIRM.BUTTON': '删除',
@@ -11,6 +11,10 @@ export type MediaStorageMessages = {
11
11
  'SMART.MEDIA_STORAGE.UPLOAD_ERROR': NoParams;
12
12
  'SMART.MEDIA_STORAGE.UNSUPPORTED_FORMAT': NoParams;
13
13
  'SMART.MEDIA_STORAGE.UPLOAD_SUCCESS': NoParams;
14
+ 'SMART.MEDIA_STORAGE.UPLOAD_SUCCESS_COMPRESSED': {
15
+ percent: number;
16
+ size: string;
17
+ };
14
18
  'SMART.MEDIA_STORAGE.USAGE': {
15
19
  count: number;
16
20
  size: string;
@@ -22,6 +26,16 @@ export type MediaStorageMessages = {
22
26
  count: number;
23
27
  };
24
28
  'SMART.MEDIA_STORAGE.DELETE_CONFIRM.DESCRIPTION_VERSION': NoParams;
29
+ 'SMART.MEDIA_STORAGE.VERSIONS.HINT': NoParams;
30
+ 'SMART.MEDIA_STORAGE.VERSIONS.TITLE': NoParams;
31
+ 'SMART.MEDIA_STORAGE.VERSIONS.CURRENT': {
32
+ number: number;
33
+ };
34
+ 'SMART.MEDIA_STORAGE.VERSIONS.NUMBER': {
35
+ number: number;
36
+ };
37
+ 'SMART.MEDIA_STORAGE.VERSIONS.SELECT': NoParams;
38
+ 'SMART.MEDIA_STORAGE.VERSIONS.LIST_LABEL': NoParams;
25
39
  'SMART.MEDIA_STORAGE.SELECT_ORIGINAL': NoParams;
26
40
  'SMART.MEDIA_STORAGE.CREATE_CROP_HINT': NoParams;
27
41
  'SMART.MEDIA_STORAGE.DELETE_HINT': NoParams;
@@ -61,6 +75,7 @@ export type MediaStorageMessages = {
61
75
  'SMART.MEDIA_STORAGE.CREATE_CROP.NO_CROPS': NoParams;
62
76
  'SMART.MEDIA_STORAGE.CREATE_CROP.SUCCESS': NoParams;
63
77
  'SMART.MEDIA_STORAGE.CREATE_CROP.ERROR': NoParams;
78
+ 'SMART.MEDIA_STORAGE.CREATE_CROP.PREVIEW_HINT': NoParams;
64
79
  'SMART.MEDIA_STORAGE.CREATE_CROP.DELETE_HINT': NoParams;
65
80
  'SMART.MEDIA_STORAGE.CREATE_CROP.DELETE_CONFIRM.TITLE': NoParams;
66
81
  'SMART.MEDIA_STORAGE.CREATE_CROP.DELETE_CONFIRM.BUTTON': NoParams;
@@ -15,6 +15,18 @@ export declare const TileBadges: import("styled-components/dist/types").IStyledC
15
15
  export declare const SourceBadge: import("styled-components/dist/types").IStyledComponentBase<"web", Omit<import("react").DetailedHTMLProps<import("react").HTMLAttributes<HTMLSpanElement>, HTMLSpanElement>, "style"> & {
16
16
  style?: import("react").CSSProperties | import("styled-components/dist/types").CSSPropertiesWithVars | undefined;
17
17
  }> & string;
18
+ export declare const VersionsBadge: import("styled-components/dist/types").IStyledComponentBase<"web", Omit<import("react").DetailedHTMLProps<import("react").ButtonHTMLAttributes<HTMLButtonElement>, HTMLButtonElement>, "style"> & {
19
+ style?: import("react").CSSProperties | import("styled-components/dist/types").CSSPropertiesWithVars | undefined;
20
+ }> & string;
21
+ export declare const TooltipAnchor: import("styled-components/dist/types").IStyledComponentBase<"web", Omit<import("react").DetailedHTMLProps<import("react").HTMLAttributes<HTMLSpanElement>, HTMLSpanElement>, "style"> & {
22
+ style?: import("react").CSSProperties | import("styled-components/dist/types").CSSPropertiesWithVars | undefined;
23
+ }> & string;
24
+ export declare const MenuItemPreview: import("styled-components/dist/types").IStyledComponentBase<"web", Omit<import("react").DetailedHTMLProps<import("react").HTMLAttributes<HTMLSpanElement>, HTMLSpanElement>, "style"> & {
25
+ style?: import("react").CSSProperties | import("styled-components/dist/types").CSSPropertiesWithVars | undefined;
26
+ }> & string;
27
+ export declare const MenuItemThumb: import("styled-components/dist/types").IStyledComponentBase<"web", Omit<import("react").DetailedHTMLProps<import("react").ImgHTMLAttributes<HTMLImageElement>, HTMLImageElement>, "style"> & {
28
+ style?: import("react").CSSProperties | import("styled-components/dist/types").CSSPropertiesWithVars | undefined;
29
+ }> & string;
18
30
  export declare const CountBadge: import("styled-components/dist/types").IStyledComponentBase<"web", Omit<import("react").DetailedHTMLProps<import("react").HTMLAttributes<HTMLSpanElement>, HTMLSpanElement>, "style"> & {
19
31
  style?: import("react").CSSProperties | import("styled-components/dist/types").CSSPropertiesWithVars | undefined;
20
32
  }> & string;
@@ -85,7 +97,15 @@ export declare const SegmentItem: import("styled-components/dist/types").IStyled
85
97
  export declare const CropStrip: import("styled-components/dist/types").IStyledComponentBase<"web", Omit<import("react").DetailedHTMLProps<import("react").HTMLAttributes<HTMLDivElement>, HTMLDivElement>, "style"> & {
86
98
  style?: import("react").CSSProperties | import("styled-components/dist/types").CSSPropertiesWithVars | undefined;
87
99
  }> & string;
88
- export declare const CropThumb: import("styled-components/dist/types").IStyledComponentBase<"web", Omit<import("react").DetailedHTMLProps<import("react").HTMLAttributes<HTMLDivElement>, HTMLDivElement>, "style"> & {
100
+ export declare const CropThumb: import("styled-components/dist/types").IStyledComponentBase<"web", import("styled-components/dist/types").Merged<Omit<import("react").DetailedHTMLProps<import("react").HTMLAttributes<HTMLDivElement>, HTMLDivElement>, "style"> & {
101
+ style?: import("react").CSSProperties | import("styled-components/dist/types").CSSPropertiesWithVars | undefined;
102
+ }, {
103
+ $selected?: boolean;
104
+ }>> & string;
105
+ export declare const CropThumbButton: import("styled-components/dist/types").IStyledComponentBase<"web", Omit<import("react").DetailedHTMLProps<import("react").ButtonHTMLAttributes<HTMLButtonElement>, HTMLButtonElement>, "style"> & {
106
+ style?: import("react").CSSProperties | import("styled-components/dist/types").CSSPropertiesWithVars | undefined;
107
+ }> & string;
108
+ export declare const CropThumbImage: import("styled-components/dist/types").IStyledComponentBase<"web", Omit<import("react").DetailedHTMLProps<import("react").ImgHTMLAttributes<HTMLImageElement>, HTMLImageElement>, "style"> & {
89
109
  style?: import("react").CSSProperties | import("styled-components/dist/types").CSSPropertiesWithVars | undefined;
90
110
  }> & string;
91
111
  export declare const CropThumbDelete: import("styled-components/dist/types").IStyledComponentBase<"web", Omit<import("react").DetailedHTMLProps<import("react").HTMLAttributes<HTMLDivElement>, HTMLDivElement>, "style"> & {
@@ -25,37 +25,53 @@ export const SourceBadge = styled.span.withConfig({
25
25
  displayName: "SourceBadge",
26
26
  componentId: "sc-uu3cbo-4"
27
27
  })(["font-size:10px;font-weight:", ";line-height:1;color:", ";background:", ";border-radius:20px;padding:3px 7px;"], FontWeight.BOLD, Color.MAIN, Color.CLEAR);
28
+ export const VersionsBadge = styled.button.withConfig({
29
+ displayName: "VersionsBadge",
30
+ componentId: "sc-uu3cbo-5"
31
+ })(["font:inherit;font-size:10px;font-weight:", ";line-height:1;color:", ";background:", ";border:none;border-radius:20px;padding:3px 7px;cursor:pointer;pointer-events:auto;&:hover{text-decoration:underline;}"], FontWeight.BOLD, Color.CLEAR, Color.PRIMARY);
32
+ export const TooltipAnchor = styled.span.withConfig({
33
+ displayName: "TooltipAnchor",
34
+ componentId: "sc-uu3cbo-6"
35
+ })(["display:contents;"]);
36
+ export const MenuItemPreview = styled.span.withConfig({
37
+ displayName: "MenuItemPreview",
38
+ componentId: "sc-uu3cbo-7"
39
+ })(["display:flex;align-items:center;gap:10px;padding:6px 0;text-align:left;"]);
40
+ export const MenuItemThumb = styled.img.withConfig({
41
+ displayName: "MenuItemThumb",
42
+ componentId: "sc-uu3cbo-8"
43
+ })(["width:64px;height:40px;object-fit:contain;flex-shrink:0;border-radius:4px;background:", ";"], Color.SOFT_LIGHT);
28
44
  export const CountBadge = styled.span.withConfig({
29
45
  displayName: "CountBadge",
30
- componentId: "sc-uu3cbo-5"
46
+ componentId: "sc-uu3cbo-9"
31
47
  })(["font-size:10px;font-weight:", ";line-height:1;color:", ";background:", ";border-radius:20px;padding:3px 7px;"], FontWeight.BOLD, Color.CLEAR, Color.PRIMARY);
32
48
  export const WarningBadge = styled.span.withConfig({
33
49
  displayName: "WarningBadge",
34
- componentId: "sc-uu3cbo-6"
50
+ componentId: "sc-uu3cbo-10"
35
51
  })(["position:absolute;bottom:0;left:0;right:0;font-size:10px;line-height:1.2;text-align:center;color:", ";background:", ";padding:3px 6px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;pointer-events:none;"], Color.CLEAR, Color.WARNING);
36
52
  export const TileCheckbox = styled.div.withConfig({
37
53
  displayName: "TileCheckbox",
38
- componentId: "sc-uu3cbo-7"
54
+ componentId: "sc-uu3cbo-11"
39
55
  })(["position:absolute;top:6px;left:6px;z-index:2;padding:2px;border-radius:4px;background:", ";"], Color.CLEAR);
40
56
  export const SelectionBar = styled.div.withConfig({
41
57
  displayName: "SelectionBar",
42
- componentId: "sc-uu3cbo-8"
58
+ componentId: "sc-uu3cbo-12"
43
59
  })(["display:flex;align-items:center;gap:12px;min-height:32px;margin-bottom:12px;"]);
44
60
  export const SelectionCount = styled.span.withConfig({
45
61
  displayName: "SelectionCount",
46
- componentId: "sc-uu3cbo-9"
62
+ componentId: "sc-uu3cbo-13"
47
63
  })(["font-size:", ";color:", ";"], FontSize.SMALL, Color.LOCKED);
48
64
  export const TileActions = styled.div.withConfig({
49
65
  displayName: "TileActions",
50
- componentId: "sc-uu3cbo-10"
66
+ componentId: "sc-uu3cbo-14"
51
67
  })(["position:absolute;inset:0;z-index:1;display:flex;align-items:center;justify-content:center;gap:6px;background:rgba(0,0,0,0.45);backdrop-filter:blur(2px);opacity:0;transition:opacity 0.15s;", ":hover &{opacity:1;}@media (hover:none){opacity:1;}"], Tile);
52
68
  export const EmptyState = styled.div.withConfig({
53
69
  displayName: "EmptyState",
54
- componentId: "sc-uu3cbo-11"
70
+ componentId: "sc-uu3cbo-15"
55
71
  })(["text-align:center;padding:48px 16px;color:", ";"], Color.LOCKED);
56
72
  export const Dropzone = styled.button.withConfig({
57
73
  displayName: "Dropzone",
58
- componentId: "sc-uu3cbo-12"
74
+ componentId: "sc-uu3cbo-16"
59
75
  })(["width:100%;min-height:96px;margin-bottom:16px;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:8px;padding:16px;font:inherit;font-size:13px;color:", ";cursor:pointer;border:2px dashed ", ";border-radius:8px;background:", ";transition:border-color 0.15s,background 0.15s;&:hover{border-color:", ";}&:disabled{cursor:default;opacity:0.7;}"], Color.LOCKED, ({
60
76
  $dragOver
61
77
  }) => $dragOver ? Color.PRIMARY : Color.DIVIDER, ({
@@ -63,46 +79,46 @@ export const Dropzone = styled.button.withConfig({
63
79
  }) => $dragOver ? Color.SOFT_LIGHT : Color.CLEAR, Color.PRIMARY);
64
80
  export const HiddenInput = styled.input.withConfig({
65
81
  displayName: "HiddenInput",
66
- componentId: "sc-uu3cbo-13"
82
+ componentId: "sc-uu3cbo-17"
67
83
  })(["display:none;"]);
68
84
  export const UsageLine = styled.div.withConfig({
69
85
  displayName: "UsageLine",
70
- componentId: "sc-uu3cbo-14"
86
+ componentId: "sc-uu3cbo-18"
71
87
  })(["margin-left:auto;font-size:", ";color:", ";"], FontSize.SMALL, Color.LOCKED);
72
88
  /* ---------- CreateCropDrawer ---------- */
73
89
  export const PreviewStage = styled.div.withConfig({
74
90
  displayName: "PreviewStage",
75
- componentId: "sc-uu3cbo-15"
91
+ componentId: "sc-uu3cbo-19"
76
92
  })(["display:flex;align-items:center;justify-content:center;min-height:200px;max-height:360px;padding:12px;border:1px solid ", ";border-radius:6px;overflow:hidden;background:", ";"], Color.DIVIDER, Color.SOFT_LIGHT);
77
93
  export const PreviewFrame = styled.div.withConfig({
78
94
  displayName: "PreviewFrame",
79
- componentId: "sc-uu3cbo-16"
95
+ componentId: "sc-uu3cbo-20"
80
96
  })(["aspect-ratio:", ";max-width:100%;max-height:336px;overflow:hidden;border-radius:4px;"], ({
81
97
  $ratio
82
98
  }) => $ratio);
83
99
  export const PreviewImage = styled.img.withConfig({
84
100
  displayName: "PreviewImage",
85
- componentId: "sc-uu3cbo-17"
101
+ componentId: "sc-uu3cbo-21"
86
102
  })(["width:100%;height:100%;object-fit:cover;display:block;"]);
87
103
  export const FieldLabel = styled.div.withConfig({
88
104
  displayName: "FieldLabel",
89
- componentId: "sc-uu3cbo-18"
105
+ componentId: "sc-uu3cbo-22"
90
106
  })(["font-size:11px;font-weight:600;text-transform:uppercase;letter-spacing:0.4px;color:", ";margin-bottom:6px;"], Color.LOCKED);
91
107
  export const SizeInput = styled(NativeInput).withConfig({
92
108
  displayName: "SizeInput",
93
- componentId: "sc-uu3cbo-19"
109
+ componentId: "sc-uu3cbo-23"
94
110
  })(["&[readonly]{background:", ";color:", ";}"], Color.SOFT_LIGHT, Color.LOCKED);
95
111
  export const Chips = styled.div.withConfig({
96
112
  displayName: "Chips",
97
- componentId: "sc-uu3cbo-20"
113
+ componentId: "sc-uu3cbo-24"
98
114
  })(["display:flex;flex-wrap:wrap;gap:7px;"]);
99
115
  export const Segment = styled.div.withConfig({
100
116
  displayName: "Segment",
101
- componentId: "sc-uu3cbo-21"
117
+ componentId: "sc-uu3cbo-25"
102
118
  })(["display:inline-flex;border:1px solid ", ";border-radius:6px;overflow:hidden;"], Color.DIVIDER);
103
119
  export const SegmentItem = styled.button.withConfig({
104
120
  displayName: "SegmentItem",
105
- componentId: "sc-uu3cbo-22"
121
+ componentId: "sc-uu3cbo-26"
106
122
  })(["font:inherit;font-size:13px;cursor:pointer;padding:7px 14px;border:none;background:", ";color:", ";& + &{border-left:1px solid ", ";}"], ({
107
123
  $active
108
124
  }) => $active ? Color.PRIMARY : Color.CLEAR, ({
@@ -110,23 +126,35 @@ export const SegmentItem = styled.button.withConfig({
110
126
  }) => $active ? Color.CLEAR : Color.MAIN, Color.DIVIDER);
111
127
  export const CropStrip = styled.div.withConfig({
112
128
  displayName: "CropStrip",
113
- componentId: "sc-uu3cbo-23"
129
+ componentId: "sc-uu3cbo-27"
114
130
  })(["display:flex;gap:10px;flex-wrap:wrap;align-items:flex-start;"]);
115
131
  export const CropThumb = styled.div.withConfig({
116
132
  displayName: "CropThumb",
117
- componentId: "sc-uu3cbo-24"
118
- })(["position:relative;width:88px;border:1px solid ", ";border-radius:6px;overflow:hidden;background:", ";"], Color.DIVIDER, Color.SOFT_LIGHT);
133
+ componentId: "sc-uu3cbo-28"
134
+ })(["position:relative;width:88px;border:1px solid ", ";box-shadow:", ";border-radius:6px;overflow:hidden;background:", ";"], ({
135
+ $selected
136
+ }) => $selected ? Color.PRIMARY : Color.DIVIDER, ({
137
+ $selected
138
+ }) => $selected ? `0 0 0 1px ${Color.PRIMARY}` : 'none', Color.SOFT_LIGHT);
139
+ export const CropThumbButton = styled.button.withConfig({
140
+ displayName: "CropThumbButton",
141
+ componentId: "sc-uu3cbo-29"
142
+ })(["display:block;width:100%;padding:0;font:inherit;border:none;background:none;cursor:pointer;"]);
143
+ export const CropThumbImage = styled.img.withConfig({
144
+ displayName: "CropThumbImage",
145
+ componentId: "sc-uu3cbo-30"
146
+ })(["width:100%;aspect-ratio:1;object-fit:contain;display:block;"]);
119
147
  export const CropThumbDelete = styled.div.withConfig({
120
148
  displayName: "CropThumbDelete",
121
- componentId: "sc-uu3cbo-25"
149
+ componentId: "sc-uu3cbo-31"
122
150
  })(["position:absolute;top:4px;right:4px;opacity:0;transition:opacity 0.15s;", ":hover &{opacity:1;}@media (hover:none){opacity:1;}"], CropThumb);
123
151
  export const CropThumbCaption = styled.div.withConfig({
124
152
  displayName: "CropThumbCaption",
125
- componentId: "sc-uu3cbo-26"
153
+ componentId: "sc-uu3cbo-32"
126
154
  })(["font-size:10px;text-align:center;padding:3px 2px;color:", ";"], Color.LOCKED);
127
155
  export const DesignTile = styled.button.withConfig({
128
156
  displayName: "DesignTile",
129
- componentId: "sc-uu3cbo-27"
157
+ componentId: "sc-uu3cbo-33"
130
158
  })(["display:flex;flex-direction:column;padding:0;font:inherit;text-align:left;cursor:pointer;background:", ";border-radius:6px;overflow:hidden;border:1px solid ", ";box-shadow:", ";transition:border-color 0.15s,box-shadow 0.15s;&:hover:not(:disabled){border-color:", ";}&:disabled{cursor:default;}"], Color.CLEAR, ({
131
159
  $selected
132
160
  }) => $selected ? Color.PRIMARY : Color.DIVIDER, ({
@@ -134,27 +162,27 @@ export const DesignTile = styled.button.withConfig({
134
162
  }) => $selected ? `0 0 0 1px ${Color.PRIMARY}` : 'none', Color.PRIMARY);
135
163
  export const DesignCaption = styled.span.withConfig({
136
164
  displayName: "DesignCaption",
137
- componentId: "sc-uu3cbo-28"
165
+ componentId: "sc-uu3cbo-34"
138
166
  })(["display:flex;justify-content:space-between;gap:6px;padding:6px 8px;font-size:", ";color:", ";span:first-child{overflow:hidden;white-space:nowrap;text-overflow:ellipsis;}span:last-child{flex-shrink:0;color:", ";}"], FontSize.SMALL, Color.MAIN, Color.LOCKED);
139
167
  export const DropzoneRow = styled.div.withConfig({
140
168
  displayName: "DropzoneRow",
141
- componentId: "sc-uu3cbo-29"
169
+ componentId: "sc-uu3cbo-35"
142
170
  })(["display:flex;align-items:stretch;gap:12px;& > :first-child{flex:1;}"]);
143
171
  export const CanvaActions = styled.div.withConfig({
144
172
  displayName: "CanvaActions",
145
- componentId: "sc-uu3cbo-30"
173
+ componentId: "sc-uu3cbo-36"
146
174
  })(["display:flex;flex-direction:column;justify-content:center;gap:8px;& > *{width:100%;}"]);
147
175
  export const TabBody = styled.div.withConfig({
148
176
  displayName: "TabBody",
149
- componentId: "sc-uu3cbo-31"
177
+ componentId: "sc-uu3cbo-37"
150
178
  })(["padding-top:16px;"]);
151
179
  export const SearchRow = styled.div.withConfig({
152
180
  displayName: "SearchRow",
153
- componentId: "sc-uu3cbo-32"
181
+ componentId: "sc-uu3cbo-38"
154
182
  })(["display:flex;gap:8px;margin-bottom:16px;"]);
155
183
  export const StockTile = styled.button.withConfig({
156
184
  displayName: "StockTile",
157
- componentId: "sc-uu3cbo-33"
185
+ componentId: "sc-uu3cbo-39"
158
186
  })(["all:unset;background-color:", ";opacity:", ";display:block;width:100%;box-sizing:border-box;cursor:pointer;border-radius:6px;overflow:hidden;border:1px solid ", ";transition:border-color 0.15s,opacity 0.15s;&:hover{border-color:", ";}&:disabled{cursor:default;}"], ({
159
187
  $avgColor
160
188
  }) => $avgColor || 'transparent', ({
@@ -162,59 +190,59 @@ export const StockTile = styled.button.withConfig({
162
190
  }) => $dimmed ? 0.6 : 1, Color.DIVIDER, Color.PRIMARY);
163
191
  export const StockCredit = styled.a.withConfig({
164
192
  displayName: "StockCredit",
165
- componentId: "sc-uu3cbo-34"
193
+ componentId: "sc-uu3cbo-40"
166
194
  })(["display:block;margin-top:2px;padding:0 4px 4px;font-size:11px;color:", ";text-decoration:none;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;&:hover{text-decoration:underline;}"], Color.LOCKED);
167
195
  export const ProviderNote = styled.div.withConfig({
168
196
  displayName: "ProviderNote",
169
- componentId: "sc-uu3cbo-35"
197
+ componentId: "sc-uu3cbo-41"
170
198
  })(["margin-top:16px;text-align:center;font-size:12px;color:", ";a{color:", ";}"], Color.LOCKED, Color.MAIN);
171
199
  export const GenerateForm = styled.div.withConfig({
172
200
  displayName: "GenerateForm",
173
- componentId: "sc-uu3cbo-36"
201
+ componentId: "sc-uu3cbo-42"
174
202
  })(["display:flex;flex-direction:column;gap:12px;max-width:560px;"]);
175
203
  export const GenerateControls = styled.div.withConfig({
176
204
  displayName: "GenerateControls",
177
- componentId: "sc-uu3cbo-37"
205
+ componentId: "sc-uu3cbo-43"
178
206
  })(["display:flex;align-items:center;gap:8px;"]);
179
207
  export const GeneratedPreview = styled.img.withConfig({
180
208
  displayName: "GeneratedPreview",
181
- componentId: "sc-uu3cbo-38"
209
+ componentId: "sc-uu3cbo-44"
182
210
  })(["display:block;max-width:100%;max-height:420px;border-radius:8px;border:1px solid ", ";"], Color.DIVIDER);
183
211
  export const SearchField = styled(NativeInput).withConfig({
184
212
  displayName: "SearchField",
185
- componentId: "sc-uu3cbo-39"
213
+ componentId: "sc-uu3cbo-45"
186
214
  })(["flex:1;width:auto;max-width:none;"]);
187
215
  export const PromptField = styled(NativeTextarea).withConfig({
188
216
  displayName: "PromptField",
189
- componentId: "sc-uu3cbo-40"
217
+ componentId: "sc-uu3cbo-46"
190
218
  })(["font-family:inherit;resize:vertical;"]);
191
219
  export const GeneratedGrid = styled.div.withConfig({
192
220
  displayName: "GeneratedGrid",
193
- componentId: "sc-uu3cbo-41"
221
+ componentId: "sc-uu3cbo-47"
194
222
  })(["display:grid;grid-template-columns:", ";gap:16px;"], ({
195
223
  $single
196
224
  }) => $single ? '1fr' : 'repeat(2, minmax(0, 1fr))');
197
225
  export const GeneratedVariant = styled.div.withConfig({
198
226
  displayName: "GeneratedVariant",
199
- componentId: "sc-uu3cbo-42"
227
+ componentId: "sc-uu3cbo-48"
200
228
  })(["display:flex;flex-direction:column;align-items:flex-start;gap:8px;"]);
201
229
  export const StyleRefs = styled.div.withConfig({
202
230
  displayName: "StyleRefs",
203
- componentId: "sc-uu3cbo-43"
231
+ componentId: "sc-uu3cbo-49"
204
232
  })(["display:flex;flex-direction:column;gap:8px;"]);
205
233
  export const StyleRefsHeader = styled.div.withConfig({
206
234
  displayName: "StyleRefsHeader",
207
- componentId: "sc-uu3cbo-44"
235
+ componentId: "sc-uu3cbo-50"
208
236
  })(["display:flex;align-items:center;gap:8px;font-size:", ";color:", ";"], FontSize.SMALL, Color.LOCKED);
209
237
  export const StyleRefsRow = styled.div.withConfig({
210
238
  displayName: "StyleRefsRow",
211
- componentId: "sc-uu3cbo-45"
239
+ componentId: "sc-uu3cbo-51"
212
240
  })(["display:flex;flex-wrap:wrap;gap:8px;"]);
213
241
  export const StyleRefThumb = styled.div.withConfig({
214
242
  displayName: "StyleRefThumb",
215
- componentId: "sc-uu3cbo-46"
243
+ componentId: "sc-uu3cbo-52"
216
244
  })(["position:relative;width:64px;height:64px;border-radius:6px;overflow:hidden;border:1px solid ", ";img{width:100%;height:100%;object-fit:cover;display:block;}"], Color.DIVIDER);
217
245
  export const StyleRefRemove = styled.div.withConfig({
218
246
  displayName: "StyleRefRemove",
219
- componentId: "sc-uu3cbo-47"
247
+ componentId: "sc-uu3cbo-53"
220
248
  })(["position:absolute;top:2px;right:2px;border-radius:4px;background:", ";"], Color.CLEAR);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pushwoosh/smart-components-kit",
3
- "version": "1.18.0",
3
+ "version": "1.20.0",
4
4
  "description": "Stateful, data-connected React components shared by Pushwoosh micro-frontends",
5
5
  "repository": {
6
6
  "type": "git",
@@ -17,7 +17,7 @@
17
17
  },
18
18
  "dependencies": {
19
19
  "@floating-ui/react-dom": "^2.1.2",
20
- "@pushwoosh/dumb-components": "^3.1.0",
20
+ "@pushwoosh/dumb-components": "^3.2.0",
21
21
  "@pushwoosh/kit-constants": "^2.1.1",
22
22
  "@pushwoosh/kit-helpers": "^4.16.0",
23
23
  "@pushwoosh/kit-icons": "^2.3.10",