@pushwoosh/smart-components-kit 1.0.77 → 1.0.89

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.
Files changed (75) hide show
  1. package/DocsButton/DocsButton.js +5 -1
  2. package/DocsButton/polyglot/index.d.ts +5 -0
  3. package/DocsButton/polyglot/index.js +10 -0
  4. package/DocsButton/polyglot/messages_en.d.ts +3 -0
  5. package/DocsButton/polyglot/messages_en.js +3 -0
  6. package/DocsButton/polyglot/messages_ru.d.ts +3 -0
  7. package/DocsButton/polyglot/messages_ru.js +3 -0
  8. package/DocsButton/polyglot/types.d.ts +4 -0
  9. package/DocsButton/polyglot/types.js +1 -0
  10. package/DynamicContentPicker/styled.d.ts +16 -104
  11. package/DynamicContentPickerForm/styled.d.ts +18 -106
  12. package/LanguagesSelector/LanguagesModal.js +11 -3
  13. package/LanguagesSelector/LanguagesSelectForm.js +34 -16
  14. package/LanguagesSelector/LanguagesSelector.js +10 -6
  15. package/LanguagesSelector/polyglot/index.d.ts +2 -0
  16. package/LanguagesSelector/polyglot/index.js +10 -0
  17. package/LanguagesSelector/polyglot/messages_en.d.ts +3 -0
  18. package/LanguagesSelector/polyglot/messages_en.js +13 -0
  19. package/LanguagesSelector/polyglot/messages_ru.d.ts +3 -0
  20. package/LanguagesSelector/polyglot/messages_ru.js +13 -0
  21. package/LanguagesSelector/polyglot/types.d.ts +17 -0
  22. package/LanguagesSelector/polyglot/types.js +1 -0
  23. package/LanguagesSelector/styled.d.ts +8 -52
  24. package/MediaStore/CreateVariantDrawer.d.ts +12 -0
  25. package/MediaStore/CreateVariantDrawer.js +216 -0
  26. package/MediaStore/MediaStore.d.ts +12 -0
  27. package/MediaStore/MediaStore.js +258 -0
  28. package/MediaStore/index.d.ts +1 -0
  29. package/MediaStore/index.js +1 -0
  30. package/MediaStore/mediaFile.utils.d.ts +5 -0
  31. package/MediaStore/mediaFile.utils.js +10 -0
  32. package/MediaStore/polyglot/index.d.ts +2 -0
  33. package/MediaStore/polyglot/index.js +10 -0
  34. package/MediaStore/polyglot/messages_en.d.ts +3 -0
  35. package/MediaStore/polyglot/messages_en.js +28 -0
  36. package/MediaStore/polyglot/messages_ru.d.ts +3 -0
  37. package/MediaStore/polyglot/messages_ru.js +28 -0
  38. package/MediaStore/polyglot/types.d.ts +31 -0
  39. package/MediaStore/polyglot/types.js +1 -0
  40. package/MediaStore/styled.d.ts +27 -0
  41. package/MediaStore/styled.js +95 -0
  42. package/ModalCloningJourney/ModalCloningJourney.js +67 -22
  43. package/ModalCloningJourney/polyglot/index.d.ts +2 -0
  44. package/ModalCloningJourney/polyglot/index.js +10 -0
  45. package/ModalCloningJourney/polyglot/messages_en.d.ts +3 -0
  46. package/ModalCloningJourney/polyglot/messages_en.js +22 -0
  47. package/ModalCloningJourney/polyglot/messages_ru.d.ts +3 -0
  48. package/ModalCloningJourney/polyglot/messages_ru.js +22 -0
  49. package/ModalCloningJourney/polyglot/types.d.ts +25 -0
  50. package/ModalCloningJourney/polyglot/types.js +1 -0
  51. package/OpenActions/components/OpenActionsSummary/styles.d.ts +8 -52
  52. package/RichMessageEditorSmart/NewDynamicContentForm.js +5 -1
  53. package/RichMessageEditorSmart/polyglot/index.d.ts +5 -0
  54. package/RichMessageEditorSmart/polyglot/index.js +10 -0
  55. package/RichMessageEditorSmart/polyglot/messages_en.d.ts +3 -0
  56. package/RichMessageEditorSmart/polyglot/messages_en.js +3 -0
  57. package/RichMessageEditorSmart/polyglot/messages_ru.d.ts +3 -0
  58. package/RichMessageEditorSmart/polyglot/messages_ru.js +3 -0
  59. package/RichMessageEditorSmart/polyglot/types.d.ts +4 -0
  60. package/RichMessageEditorSmart/polyglot/types.js +1 -0
  61. package/Table/components/DataTable/DataTable.js +27 -8
  62. package/Table/components/DataTableList/DataTableList.js +29 -9
  63. package/Table/components/TablePagination/TablePagination.js +5 -1
  64. package/Table/polyglot/index.d.ts +2 -0
  65. package/Table/polyglot/index.js +10 -0
  66. package/Table/polyglot/messages_en.d.ts +3 -0
  67. package/Table/polyglot/messages_en.js +18 -0
  68. package/Table/polyglot/messages_ru.d.ts +3 -0
  69. package/Table/polyglot/messages_ru.js +18 -0
  70. package/Table/polyglot/types.d.ts +21 -0
  71. package/Table/polyglot/types.js +1 -0
  72. package/Table/styles.d.ts +34 -210
  73. package/index.d.ts +1 -0
  74. package/index.js +1 -0
  75. package/package.json +3 -2
@@ -1,56 +1,12 @@
1
- export declare const ButtonGroup: import("styled-components").StyledComponent<"div", any, {
2
- $alignContent?: "end" | "start" | "center" | "space-between" | "space-around" | "space-evenly" | "stretch" | undefined;
3
- $alignItems?: "end" | "start" | "center" | "stretch" | "baseline" | undefined;
4
- $bgColor?: string | undefined;
5
- $border?: string | ({
6
- top?: string | undefined;
7
- right?: string | undefined;
8
- bottom?: string | undefined;
9
- left?: string | undefined;
10
- } | {
11
- horizontal: string;
12
- top?: string | undefined;
13
- bottom?: string | undefined;
14
- } | {
15
- vertical: string;
16
- right?: string | undefined;
17
- left?: string | undefined;
18
- }) | undefined;
19
- $borderRadius?: (string | number) | undefined;
20
- $boxShadow?: string | undefined;
21
- $gap?: (string | number) | undefined;
1
+ export declare const ButtonGroup: import("styled-components").StyledComponent<"div", any, import("@pushwoosh/kit-helpers/styled-helpers").CommonBoxProps & {
2
+ $alignContent?: import("@pushwoosh/kit-helpers/styled-helpers").AlignContentValue | undefined;
3
+ $alignItems?: import("@pushwoosh/kit-helpers/styled-helpers").ItemAlignmentValue | undefined;
4
+ $gap?: import("@pushwoosh/kit-helpers/styled-helpers").StrNum | undefined;
5
+ $justifyContent?: import("@pushwoosh/kit-helpers/styled-helpers").JustifyContentValue | undefined;
6
+ } & {
22
7
  $gridAutoFlow?: "row" | "column" | "dense" | "row dense" | "column dense" | undefined;
23
- $justifyContent?: "end" | "start" | "center" | "space-between" | "space-around" | "space-evenly" | "stretch" | undefined;
24
- $justifyItems?: "end" | "start" | "center" | "stretch" | "baseline" | undefined;
25
- $margin?: (string | number) | ({
26
- top?: (string | number) | undefined;
27
- right?: (string | number) | undefined;
28
- bottom?: (string | number) | undefined;
29
- left?: (string | number) | undefined;
30
- } | {
31
- horizontal: string | number;
32
- top?: (string | number) | undefined;
33
- bottom?: (string | number) | undefined;
34
- } | {
35
- vertical: string | number;
36
- right?: (string | number) | undefined;
37
- left?: (string | number) | undefined;
38
- }) | undefined;
39
- $padding?: (string | number) | ({
40
- top?: (string | number) | undefined;
41
- right?: (string | number) | undefined;
42
- bottom?: (string | number) | undefined;
43
- left?: (string | number) | undefined;
44
- } | {
45
- horizontal: string | number;
46
- top?: (string | number) | undefined;
47
- bottom?: (string | number) | undefined;
48
- } | {
49
- vertical: string | number;
50
- right?: (string | number) | undefined;
51
- left?: (string | number) | undefined;
52
- }) | undefined;
53
- $placeItems?: "end" | "start" | "center" | "stretch" | undefined;
8
+ $justifyItems?: import("@pushwoosh/kit-helpers/styled-helpers").ItemAlignmentValue | undefined;
9
+ $placeItems?: import("@pushwoosh/kit-helpers/styled-helpers").ItemAlignmentValue | undefined;
54
10
  } & {
55
11
  $gridAutoFlow: string;
56
12
  }, "$gridAutoFlow">;
@@ -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 CreateVariantDrawerProps = {
4
+ application: string;
5
+ original: MediaFile | null;
6
+ isOpen: boolean;
7
+ existingVariants: MediaFile[];
8
+ onClose: () => void;
9
+ onCreated: (parentUuid: string, variant: MediaFile) => void;
10
+ };
11
+ export declare const CreateVariantDrawer: FC<CreateVariantDrawerProps>;
12
+ export {};
@@ -0,0 +1,216 @@
1
+ import React, { useEffect, useState } from 'react';
2
+ import { Button, Drawer, DrawerBody, DrawerHeader, DrawerTitle, NativeInput, useToast } from '@pushwoosh/dumb-components';
3
+ import { Columns, Vertical } from '@pushwoosh/kit-helpers';
4
+ import { CloseIcon } from '@pushwoosh/kit-icons';
5
+ import { Text } from '@pushwoosh/kit-typography';
6
+ import { useSPAContext } from '@pushwoosh/spa-context';
7
+ import { getRatioLabel, getThumbUrl } from './mediaFile.utils';
8
+ import { useLang } from './polyglot';
9
+ import { Chip, Chips, FieldLabel, PreviewFrame, PreviewImage, PreviewStage, Segment, SegmentItem, TileImage, VariantStrip, VariantThumb, VariantThumbCaption } from './styled';
10
+ const RATIOS = [{
11
+ value: 'ASPECT_RATIO_1_1',
12
+ label: '1:1',
13
+ ar: 1
14
+ }, {
15
+ value: 'ASPECT_RATIO_4_3',
16
+ label: '4:3',
17
+ ar: 4 / 3
18
+ }, {
19
+ value: 'ASPECT_RATIO_3_2',
20
+ label: '3:2',
21
+ ar: 3 / 2
22
+ }, {
23
+ value: 'ASPECT_RATIO_4_5',
24
+ label: '4:5',
25
+ ar: 4 / 5
26
+ }, {
27
+ value: 'ASPECT_RATIO_5_4',
28
+ label: '5:4',
29
+ ar: 5 / 4
30
+ }, {
31
+ value: 'ASPECT_RATIO_16_9',
32
+ label: '16:9',
33
+ ar: 16 / 9
34
+ }, {
35
+ value: 'ASPECT_RATIO_16_10',
36
+ label: '16:10',
37
+ ar: 16 / 10
38
+ }, {
39
+ value: 'ASPECT_RATIO_21_9',
40
+ label: '21:9',
41
+ ar: 21 / 9
42
+ }, {
43
+ value: 'ASPECT_RATIO_9_16',
44
+ label: '9:16',
45
+ ar: 9 / 16
46
+ }];
47
+ const DEFAULT_RATIO = 'ASPECT_RATIO_16_9';
48
+ const DEFAULT_SIZE = '1080';
49
+ const DEFAULT_QUALITY = '90';
50
+ export const CreateVariantDrawer = ({
51
+ application,
52
+ original,
53
+ isOpen,
54
+ existingVariants,
55
+ onClose,
56
+ onCreated
57
+ }) => {
58
+ const lang = useLang();
59
+ const {
60
+ rpcGateway
61
+ } = useSPAContext();
62
+ const {
63
+ openToast
64
+ } = useToast();
65
+ const [ratio, setRatio] = useState(DEFAULT_RATIO);
66
+ const [dimension, setDimension] = useState('width');
67
+ const [size, setSize] = useState(DEFAULT_SIZE);
68
+ const [quality, setQuality] = useState(DEFAULT_QUALITY);
69
+ const [isCreating, setIsCreating] = useState(false);
70
+ // The drawer mounts once and is toggled via `isOpen`, so reset every field
71
+ // whenever it opens for a different original — otherwise values (and the
72
+ // `ratioExists` guard) leak from the previously edited image.
73
+ useEffect(() => {
74
+ if (original) {
75
+ setRatio(DEFAULT_RATIO);
76
+ setDimension('width');
77
+ setSize(DEFAULT_SIZE);
78
+ setQuality(DEFAULT_QUALITY);
79
+ }
80
+ }, [original]);
81
+ const isJpeg = /jpe?g/i.test((original === null || original === void 0 ? void 0 : original.mimeType) ?? '');
82
+ const sizeValue = Number(size);
83
+ const isSizeValid = Number.isFinite(sizeValue) && sizeValue > 0;
84
+ const qualityValue = Number(quality);
85
+ const isQualityValid = !isJpeg || Number.isFinite(qualityValue) && qualityValue >= 1 && qualityValue <= 100;
86
+ const selectedRatio = RATIOS.find(item => item.value === ratio) ?? RATIOS[0];
87
+ const outputWidth = dimension === 'width' ? sizeValue : Math.round(sizeValue * selectedRatio.ar);
88
+ const outputHeight = dimension === 'height' ? sizeValue : Math.round(sizeValue / selectedRatio.ar);
89
+ const ratioExists = existingVariants.some(variant => {
90
+ var _variant$meta;
91
+ return ((_variant$meta = variant.meta) === null || _variant$meta === void 0 ? void 0 : _variant$meta.type) === 'imageMeta' && variant.meta.data.ratio === selectedRatio.label;
92
+ });
93
+ const handleCreate = async () => {
94
+ if (!original || !isSizeValid || !isQualityValid || ratioExists) return;
95
+ // Capture the parent up front so a concurrent reopen for another image
96
+ // can't misroute the created variant.
97
+ const parent = original;
98
+ setIsCreating(true);
99
+ try {
100
+ const response = await rpcGateway.mediaFiles.CreateVariant({
101
+ application,
102
+ uuid: parent.uuid,
103
+ ratio,
104
+ size: {
105
+ type: dimension,
106
+ data: sizeValue
107
+ },
108
+ ...(isJpeg ? {
109
+ quality: qualityValue
110
+ } : {})
111
+ });
112
+ openToast({
113
+ title: lang({
114
+ message: 'SMART.MEDIA_STORE.CREATE_VARIANT.SUCCESS'
115
+ }),
116
+ type: 'success'
117
+ });
118
+ onCreated(parent.uuid, response.file);
119
+ } catch (error) {
120
+ openToast({
121
+ title: error.message || lang({
122
+ message: 'SMART.MEDIA_STORE.CREATE_VARIANT.ERROR'
123
+ }),
124
+ type: 'error'
125
+ });
126
+ } finally {
127
+ setIsCreating(false);
128
+ }
129
+ };
130
+ return React.createElement(Drawer, {
131
+ size: "medium",
132
+ isOpen: isOpen,
133
+ onClose: onClose
134
+ }, React.createElement(DrawerHeader, null, React.createElement(DrawerTitle, null, React.createElement("span", null, lang({
135
+ message: 'SMART.MEDIA_STORE.CREATE_VARIANT.TITLE'
136
+ })), React.createElement(Button, {
137
+ view: "ghost",
138
+ color: "secondary",
139
+ size: "compact",
140
+ icon: React.createElement(CloseIcon, {
141
+ size: "small"
142
+ }),
143
+ "aria-label": lang({
144
+ message: 'SMART.MEDIA_STORE.CREATE_VARIANT.CLOSE'
145
+ }),
146
+ onClick: onClose
147
+ }))), React.createElement(DrawerBody, null, original && React.createElement(Vertical, {
148
+ "$gap": 20
149
+ }, React.createElement("div", null, React.createElement(FieldLabel, null, lang({
150
+ message: 'SMART.MEDIA_STORE.CREATE_VARIANT.RATIO_LABEL'
151
+ })), React.createElement(Chips, null, RATIOS.map(item => React.createElement(Chip, {
152
+ key: item.value,
153
+ type: "button",
154
+ "$active": item.value === ratio,
155
+ onClick: () => setRatio(item.value)
156
+ }, item.label)))), React.createElement("div", null, React.createElement(FieldLabel, null, lang({
157
+ message: 'SMART.MEDIA_STORE.CREATE_VARIANT.SIZE_LABEL'
158
+ })), React.createElement(Columns, {
159
+ "$sizes": "auto 1fr",
160
+ "$gap": 8,
161
+ "$alignItems": "center"
162
+ }, React.createElement(Segment, null, React.createElement(SegmentItem, {
163
+ type: "button",
164
+ "$active": dimension === 'width',
165
+ onClick: () => setDimension('width')
166
+ }, lang({
167
+ message: 'SMART.MEDIA_STORE.CREATE_VARIANT.WIDTH'
168
+ })), React.createElement(SegmentItem, {
169
+ type: "button",
170
+ "$active": dimension === 'height',
171
+ onClick: () => setDimension('height')
172
+ }, lang({
173
+ message: 'SMART.MEDIA_STORE.CREATE_VARIANT.HEIGHT'
174
+ }))), React.createElement(NativeInput, {
175
+ type: "number",
176
+ min: 1,
177
+ value: size,
178
+ "$isErrored": !isSizeValid,
179
+ onChange: event => setSize(event.target.value)
180
+ }))), React.createElement(Vertical, {
181
+ "$gap": 6
182
+ }, React.createElement(PreviewStage, null, React.createElement(PreviewFrame, {
183
+ "$ratio": selectedRatio.ar
184
+ }, React.createElement(PreviewImage, {
185
+ src: original.url,
186
+ alt: original.filename
187
+ }))), isSizeValid && React.createElement(Text, null, selectedRatio.label, ' · ', outputWidth, ' × ', outputHeight)), isJpeg && React.createElement("div", null, React.createElement(FieldLabel, null, lang({
188
+ message: 'SMART.MEDIA_STORE.CREATE_VARIANT.QUALITY_LABEL'
189
+ })), React.createElement(NativeInput, {
190
+ type: "number",
191
+ min: 1,
192
+ max: 100,
193
+ value: quality,
194
+ "$isErrored": !isQualityValid,
195
+ onChange: event => setQuality(event.target.value)
196
+ })), ratioExists && React.createElement(Text, null, lang({
197
+ message: 'SMART.MEDIA_STORE.CREATE_VARIANT.RATIO_EXISTS'
198
+ })), React.createElement(Button, {
199
+ color: "primary",
200
+ size: "field",
201
+ isDisabled: !isSizeValid || !isQualityValid || ratioExists,
202
+ isLoading: isCreating,
203
+ onClick: () => void handleCreate()
204
+ }, lang({
205
+ message: 'SMART.MEDIA_STORE.CREATE_VARIANT.CREATE_BUTTON'
206
+ })), React.createElement("div", null, React.createElement(FieldLabel, null, lang({
207
+ message: 'SMART.MEDIA_STORE.CREATE_VARIANT.EXISTING_LABEL'
208
+ })), existingVariants.length === 0 ? React.createElement(Text, null, lang({
209
+ message: 'SMART.MEDIA_STORE.CREATE_VARIANT.NO_VARIANTS'
210
+ })) : React.createElement(VariantStrip, null, existingVariants.map(variant => React.createElement(VariantThumb, {
211
+ key: variant.uuid
212
+ }, React.createElement(TileImage, {
213
+ src: getThumbUrl(variant),
214
+ alt: variant.filename
215
+ }), React.createElement(VariantThumbCaption, null, getRatioLabel(variant) || variant.filename))))))));
216
+ };
@@ -0,0 +1,12 @@
1
+ import { type FC } from 'react';
2
+ export type MediaStoreProps = {
3
+ /** Application code whose media files are listed. */
4
+ application: string;
5
+ /** Initial page to load (zero-based). Defaults to 0. */
6
+ page?: number;
7
+ /** Page size. Defaults to 30. */
8
+ perPage?: number;
9
+ /** Called with the chosen file URL (original or variant). */
10
+ onSelect: (url: string) => void;
11
+ };
12
+ export declare const MediaStore: FC<MediaStoreProps>;
@@ -0,0 +1,258 @@
1
+ import React, { useCallback, useEffect, useRef, useState } from 'react';
2
+ import { Button, DropdownMenuButton, DropdownMenuItem, DropdownMenuSection, useToast } from '@pushwoosh/dumb-components';
3
+ import { Horizontal } from '@pushwoosh/kit-helpers';
4
+ import { CheckIcon, EditIcon, UploadIcon } from '@pushwoosh/kit-icons';
5
+ import { Text } from '@pushwoosh/kit-typography';
6
+ import { useSPAContext } from '@pushwoosh/spa-context';
7
+ import { CreateVariantDrawer } from './CreateVariantDrawer';
8
+ import { getRatioLabel, getThumbUrl } from './mediaFile.utils';
9
+ import { useLang } from './polyglot';
10
+ import { Dropzone, EmptyState, Grid, HiddenInput, Tile, TileActions, TileImage, VariantBadge } from './styled';
11
+ const DEFAULT_PER_PAGE = 30;
12
+ const UPLOAD_PATH = '/api/media/upload';
13
+ export const MediaStore = ({
14
+ application,
15
+ page: initialPage = 0,
16
+ perPage = DEFAULT_PER_PAGE,
17
+ onSelect
18
+ }) => {
19
+ const lang = useLang();
20
+ const {
21
+ rpcGateway,
22
+ httpClient,
23
+ helpers: {
24
+ getApiOrigin
25
+ }
26
+ } = useSPAContext();
27
+ const {
28
+ openToast
29
+ } = useToast();
30
+ const uploadInputRef = useRef(null);
31
+ const loadTokenRef = useRef(0);
32
+ const [files, setFiles] = useState([]);
33
+ const [variantsByUuid, setVariantsByUuid] = useState({});
34
+ const [loading, setLoading] = useState(false);
35
+ const [page, setPage] = useState(initialPage);
36
+ const [total, setTotal] = useState(0);
37
+ const [isUploading, setIsUploading] = useState(false);
38
+ const [isDragOver, setIsDragOver] = useState(false);
39
+ const [createVariantFor, setCreateVariantFor] = useState(null);
40
+ const fetchFiles = useCallback(async p => {
41
+ // Guard against races: a newer load invalidates in-flight results of older ones.
42
+ const token = ++loadTokenRef.current;
43
+ setLoading(true);
44
+ setVariantsByUuid({});
45
+ let originals;
46
+ try {
47
+ const response = await rpcGateway.mediaFiles.List({
48
+ application,
49
+ page: p,
50
+ perPage
51
+ });
52
+ if (token !== loadTokenRef.current) return;
53
+ // protobuf JSON omits empty `repeated` fields, so `files` can be undefined.
54
+ originals = response.files ?? [];
55
+ setFiles(originals);
56
+ setTotal(response.total);
57
+ setPage(response.page);
58
+ } catch (error) {
59
+ if (token === loadTokenRef.current) {
60
+ openToast({
61
+ title: error.message || lang({
62
+ message: 'SMART.MEDIA_STORE.LOAD_ERROR'
63
+ }),
64
+ type: 'error'
65
+ });
66
+ }
67
+ return;
68
+ } finally {
69
+ if (token === loadTokenRef.current) {
70
+ setLoading(false);
71
+ }
72
+ }
73
+ // Originals are already on screen. The List of originals carries no variant
74
+ // count, so fetch each original's variants in the background and let the
75
+ // "N variants" badge appear per file as soon as its request resolves.
76
+ originals.forEach(file => {
77
+ void (async () => {
78
+ try {
79
+ const response = await rpcGateway.mediaFiles.List({
80
+ application,
81
+ parentUuid: file.uuid
82
+ });
83
+ if (token !== loadTokenRef.current) return;
84
+ setVariantsByUuid(prev => ({
85
+ ...prev,
86
+ [file.uuid]: response.files ?? []
87
+ }));
88
+ } catch {
89
+ // Ignore — the badge just won't appear for this file.
90
+ }
91
+ })();
92
+ });
93
+ }, [rpcGateway, application, perPage, openToast, lang]);
94
+ useEffect(() => {
95
+ void fetchFiles(initialPage);
96
+ }, [fetchFiles, initialPage]);
97
+ const uploadFiles = async selected => {
98
+ const images = selected.filter(file => file.type.startsWith('image/'));
99
+ if (images.length === 0) return;
100
+ setIsUploading(true);
101
+ try {
102
+ const results = await Promise.allSettled(images.map(file => {
103
+ const formData = new FormData();
104
+ formData.append('application', application);
105
+ formData.append('file', file);
106
+ formData.append('filename', file.name);
107
+ return httpClient.post(`${getApiOrigin('rpc-api')}${UPLOAD_PATH}`, {
108
+ body: formData,
109
+ params: undefined,
110
+ query: undefined,
111
+ withAuthorization: true
112
+ });
113
+ }));
114
+ const failed = results.filter(result => result.status === 'rejected').length;
115
+ if (failed < images.length) {
116
+ openToast({
117
+ title: lang({
118
+ message: 'SMART.MEDIA_STORE.UPLOAD_SUCCESS'
119
+ }),
120
+ type: 'success'
121
+ });
122
+ void fetchFiles(0);
123
+ }
124
+ if (failed > 0) {
125
+ openToast({
126
+ title: lang({
127
+ message: 'SMART.MEDIA_STORE.UPLOAD_ERROR'
128
+ }),
129
+ type: 'error'
130
+ });
131
+ }
132
+ } finally {
133
+ if (uploadInputRef.current) {
134
+ uploadInputRef.current.value = '';
135
+ }
136
+ setIsUploading(false);
137
+ }
138
+ };
139
+ const handleVariantCreated = (parentUuid, variant) => {
140
+ setVariantsByUuid(prev => ({
141
+ ...prev,
142
+ [parentUuid]: [...(prev[parentUuid] ?? []), variant]
143
+ }));
144
+ };
145
+ const totalPages = Math.ceil(total / perPage);
146
+ return React.createElement(React.Fragment, null, React.createElement(Dropzone, {
147
+ type: "button",
148
+ "$dragOver": isDragOver,
149
+ disabled: isUploading,
150
+ onClick: () => {
151
+ var _uploadInputRef$curre;
152
+ return (_uploadInputRef$curre = uploadInputRef.current) === null || _uploadInputRef$curre === void 0 ? void 0 : _uploadInputRef$curre.click();
153
+ },
154
+ onDragOver: event => {
155
+ event.preventDefault();
156
+ setIsDragOver(true);
157
+ },
158
+ onDragLeave: () => setIsDragOver(false),
159
+ onDrop: event => {
160
+ event.preventDefault();
161
+ setIsDragOver(false);
162
+ void uploadFiles(Array.from(event.dataTransfer.files));
163
+ }
164
+ }, React.createElement(UploadIcon, {
165
+ size: "medium",
166
+ view: "lined"
167
+ }), isUploading ? lang({
168
+ message: 'SMART.MEDIA_STORE.UPLOADING'
169
+ }) : lang({
170
+ message: 'SMART.MEDIA_STORE.DROPZONE_HINT'
171
+ })), React.createElement(HiddenInput, {
172
+ ref: uploadInputRef,
173
+ type: "file",
174
+ accept: "image/*",
175
+ multiple: true,
176
+ onChange: event => void uploadFiles(Array.from(event.target.files ?? []))
177
+ }), loading && React.createElement(EmptyState, null, lang({
178
+ message: 'SMART.MEDIA_STORE.LOADING'
179
+ })), !loading && files.length === 0 && React.createElement(EmptyState, null, lang({
180
+ message: 'SMART.MEDIA_STORE.EMPTY'
181
+ })), !loading && files.length > 0 && React.createElement(React.Fragment, null, React.createElement(Grid, null, files.map(file => {
182
+ const variants = variantsByUuid[file.uuid] ?? [];
183
+ return React.createElement(Tile, {
184
+ key: file.uuid
185
+ }, React.createElement(TileImage, {
186
+ src: getThumbUrl(file),
187
+ alt: file.filename
188
+ }), variants.length > 0 && React.createElement(VariantBadge, null, lang({
189
+ message: 'SMART.MEDIA_STORE.VARIANTS_BADGE',
190
+ count: variants.length
191
+ })), React.createElement(TileActions, null, variants.length > 0 ? React.createElement(DropdownMenuButton, {
192
+ color: "secondary",
193
+ size: "compact",
194
+ icon: React.createElement(CheckIcon, {
195
+ size: "small"
196
+ }),
197
+ menuPlacement: "bottom-start"
198
+ }, React.createElement(DropdownMenuSection, null, React.createElement(DropdownMenuItem, {
199
+ onClick: () => onSelect(file.url)
200
+ }, lang({
201
+ message: 'SMART.MEDIA_STORE.SELECT_ORIGINAL'
202
+ })), variants.map(variant => React.createElement(DropdownMenuItem, {
203
+ key: variant.uuid,
204
+ onClick: () => onSelect(variant.url)
205
+ }, getRatioLabel(variant) || variant.filename)))) : React.createElement(Button, {
206
+ color: "secondary",
207
+ size: "compact",
208
+ icon: React.createElement(CheckIcon, {
209
+ size: "small"
210
+ }),
211
+ "aria-label": lang({
212
+ message: 'SMART.MEDIA_STORE.SELECT_HINT'
213
+ }),
214
+ onClick: () => onSelect(file.url)
215
+ }), React.createElement(Button, {
216
+ color: "secondary",
217
+ size: "compact",
218
+ icon: React.createElement(EditIcon, {
219
+ size: "small",
220
+ view: "lined"
221
+ }),
222
+ "aria-label": lang({
223
+ message: 'SMART.MEDIA_STORE.CREATE_VARIANT_HINT'
224
+ }),
225
+ onClick: () => setCreateVariantFor(file)
226
+ })));
227
+ })), totalPages > 1 && React.createElement(Horizontal, {
228
+ "$justifyContent": "center",
229
+ "$alignItems": "center",
230
+ "$gap": 8,
231
+ "$margin": {
232
+ top: 16
233
+ }
234
+ }, React.createElement(Button, {
235
+ view: "ghost",
236
+ color: "secondary",
237
+ size: "field",
238
+ isDisabled: page <= 0,
239
+ onClick: () => void fetchFiles(page - 1)
240
+ }, lang({
241
+ message: 'SMART.MEDIA_STORE.PREV_BUTTON'
242
+ })), React.createElement(Text, null, `${page + 1} / ${totalPages}`), React.createElement(Button, {
243
+ view: "ghost",
244
+ color: "secondary",
245
+ size: "field",
246
+ isDisabled: page + 1 >= totalPages,
247
+ onClick: () => void fetchFiles(page + 1)
248
+ }, lang({
249
+ message: 'SMART.MEDIA_STORE.NEXT_BUTTON'
250
+ })))), React.createElement(CreateVariantDrawer, {
251
+ application: application,
252
+ original: createVariantFor,
253
+ isOpen: createVariantFor !== null,
254
+ existingVariants: createVariantFor ? variantsByUuid[createVariantFor.uuid] ?? [] : [],
255
+ onClose: () => setCreateVariantFor(null),
256
+ onCreated: handleVariantCreated
257
+ }));
258
+ };
@@ -0,0 +1 @@
1
+ export { MediaStore, type MediaStoreProps } from './MediaStore';
@@ -0,0 +1 @@
1
+ export { MediaStore } from './MediaStore';
@@ -0,0 +1,5 @@
1
+ import type { MediaFile } from '@pushwoosh/rpc-v2-http-api-data/media_files';
2
+ /** Thumbnail URL from image meta, falling back to the full-size URL. */
3
+ export declare const getThumbUrl: (file: MediaFile) => string;
4
+ /** Aspect-ratio label (e.g. "16:9") for a variant; empty for originals. */
5
+ export declare const getRatioLabel: (file: MediaFile) => string;
@@ -0,0 +1,10 @@
1
+ /** Thumbnail URL from image meta, falling back to the full-size URL. */
2
+ export const getThumbUrl = file => {
3
+ var _file$meta;
4
+ return ((_file$meta = file.meta) === null || _file$meta === void 0 ? void 0 : _file$meta.type) === 'imageMeta' ? file.meta.data.thumbnailUrl : file.url;
5
+ };
6
+ /** Aspect-ratio label (e.g. "16:9") for a variant; empty for originals. */
7
+ export const getRatioLabel = file => {
8
+ var _file$meta2;
9
+ return ((_file$meta2 = file.meta) === null || _file$meta2 === void 0 ? void 0 : _file$meta2.type) === 'imageMeta' ? file.meta.data.ratio : '';
10
+ };
@@ -0,0 +1,2 @@
1
+ import { type MediaStoreMessages } from './types';
2
+ export declare const Lang: import("react").FC<import("@pushwoosh/dumb-components/polyglot").LangProps<MediaStoreMessages>>, useLang: () => (props: import("@pushwoosh/dumb-components/polyglot").LangProps<MediaStoreMessages>) => string;
@@ -0,0 +1,10 @@
1
+ import { createLang } from '@pushwoosh/dumb-components/polyglot';
2
+ import { messages as en } from './messages_en';
3
+ import { messages as ru } from './messages_ru';
4
+ export const {
5
+ Lang,
6
+ useLang
7
+ } = createLang({
8
+ en,
9
+ ru
10
+ });
@@ -0,0 +1,3 @@
1
+ import type { LangMessages } from '@pushwoosh/dumb-components/polyglot';
2
+ import { type MediaStoreMessages } from './types';
3
+ export declare const messages: LangMessages<MediaStoreMessages>;
@@ -0,0 +1,28 @@
1
+ export const messages = {
2
+ 'SMART.MEDIA_STORE.UPLOADING': 'Uploading...',
3
+ 'SMART.MEDIA_STORE.DROPZONE_HINT': 'Drag & drop images here or click to upload',
4
+ 'SMART.MEDIA_STORE.LOADING': 'Loading...',
5
+ 'SMART.MEDIA_STORE.EMPTY': 'No media files found',
6
+ 'SMART.MEDIA_STORE.SELECT_HINT': 'Click to select',
7
+ 'SMART.MEDIA_STORE.PREV_BUTTON': 'Previous',
8
+ 'SMART.MEDIA_STORE.NEXT_BUTTON': 'Next',
9
+ 'SMART.MEDIA_STORE.LOAD_ERROR': 'Failed to load media files',
10
+ 'SMART.MEDIA_STORE.UPLOAD_ERROR': 'Failed to upload image',
11
+ 'SMART.MEDIA_STORE.UPLOAD_SUCCESS': 'Image uploaded',
12
+ 'SMART.MEDIA_STORE.VARIANTS_BADGE': '{{count}} var.',
13
+ 'SMART.MEDIA_STORE.SELECT_ORIGINAL': 'Original',
14
+ 'SMART.MEDIA_STORE.CREATE_VARIANT_HINT': 'Create variants',
15
+ 'SMART.MEDIA_STORE.CREATE_VARIANT.TITLE': 'Create variant',
16
+ 'SMART.MEDIA_STORE.CREATE_VARIANT.CLOSE': 'Close',
17
+ 'SMART.MEDIA_STORE.CREATE_VARIANT.RATIO_LABEL': 'Aspect ratio',
18
+ 'SMART.MEDIA_STORE.CREATE_VARIANT.SIZE_LABEL': 'Size',
19
+ 'SMART.MEDIA_STORE.CREATE_VARIANT.WIDTH': 'Width',
20
+ 'SMART.MEDIA_STORE.CREATE_VARIANT.HEIGHT': 'Height',
21
+ 'SMART.MEDIA_STORE.CREATE_VARIANT.QUALITY_LABEL': 'JPEG quality (1–100)',
22
+ 'SMART.MEDIA_STORE.CREATE_VARIANT.CREATE_BUTTON': 'Create variant',
23
+ 'SMART.MEDIA_STORE.CREATE_VARIANT.RATIO_EXISTS': 'A variant with this aspect ratio already exists',
24
+ 'SMART.MEDIA_STORE.CREATE_VARIANT.EXISTING_LABEL': 'Existing variants',
25
+ 'SMART.MEDIA_STORE.CREATE_VARIANT.NO_VARIANTS': 'No variants yet',
26
+ 'SMART.MEDIA_STORE.CREATE_VARIANT.SUCCESS': 'Variant created',
27
+ 'SMART.MEDIA_STORE.CREATE_VARIANT.ERROR': 'Failed to create variant'
28
+ };
@@ -0,0 +1,3 @@
1
+ import type { LangMessages } from '@pushwoosh/dumb-components/polyglot';
2
+ import { type MediaStoreMessages } from './types';
3
+ export declare const messages: LangMessages<MediaStoreMessages>;
@@ -0,0 +1,28 @@
1
+ export const messages = {
2
+ 'SMART.MEDIA_STORE.UPLOADING': 'Загрузка...',
3
+ 'SMART.MEDIA_STORE.DROPZONE_HINT': 'Перетащите изображения сюда или нажмите для загрузки',
4
+ 'SMART.MEDIA_STORE.LOADING': 'Загрузка...',
5
+ 'SMART.MEDIA_STORE.EMPTY': 'Медиафайлы не найдены',
6
+ 'SMART.MEDIA_STORE.SELECT_HINT': 'Нажмите, чтобы выбрать',
7
+ 'SMART.MEDIA_STORE.PREV_BUTTON': 'Назад',
8
+ 'SMART.MEDIA_STORE.NEXT_BUTTON': 'Вперёд',
9
+ 'SMART.MEDIA_STORE.LOAD_ERROR': 'Не удалось загрузить медиафайлы',
10
+ 'SMART.MEDIA_STORE.UPLOAD_ERROR': 'Не удалось загрузить изображение',
11
+ 'SMART.MEDIA_STORE.UPLOAD_SUCCESS': 'Изображение загружено',
12
+ 'SMART.MEDIA_STORE.VARIANTS_BADGE': '{{count}} вар.',
13
+ 'SMART.MEDIA_STORE.SELECT_ORIGINAL': 'Оригинал',
14
+ 'SMART.MEDIA_STORE.CREATE_VARIANT_HINT': 'Создать варианты',
15
+ 'SMART.MEDIA_STORE.CREATE_VARIANT.TITLE': 'Создание варианта',
16
+ 'SMART.MEDIA_STORE.CREATE_VARIANT.CLOSE': 'Закрыть',
17
+ 'SMART.MEDIA_STORE.CREATE_VARIANT.RATIO_LABEL': 'Соотношение сторон',
18
+ 'SMART.MEDIA_STORE.CREATE_VARIANT.SIZE_LABEL': 'Размер',
19
+ 'SMART.MEDIA_STORE.CREATE_VARIANT.WIDTH': 'Ширина',
20
+ 'SMART.MEDIA_STORE.CREATE_VARIANT.HEIGHT': 'Высота',
21
+ 'SMART.MEDIA_STORE.CREATE_VARIANT.QUALITY_LABEL': 'Качество JPEG (1–100)',
22
+ 'SMART.MEDIA_STORE.CREATE_VARIANT.CREATE_BUTTON': 'Создать вариант',
23
+ 'SMART.MEDIA_STORE.CREATE_VARIANT.RATIO_EXISTS': 'Вариант с таким соотношением сторон уже существует',
24
+ 'SMART.MEDIA_STORE.CREATE_VARIANT.EXISTING_LABEL': 'Созданные варианты',
25
+ 'SMART.MEDIA_STORE.CREATE_VARIANT.NO_VARIANTS': 'Вариантов пока нет',
26
+ 'SMART.MEDIA_STORE.CREATE_VARIANT.SUCCESS': 'Вариант создан',
27
+ 'SMART.MEDIA_STORE.CREATE_VARIANT.ERROR': 'Не удалось создать вариант'
28
+ };