@pushwoosh/smart-components-kit 1.1.1 → 1.1.3
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.
- package/MediaStore/CreateCropDrawer.d.ts +13 -0
- package/MediaStore/{CreateVariantDrawer.js → CreateCropDrawer.js} +70 -31
- package/MediaStore/MediaStore.d.ts +1 -1
- package/MediaStore/MediaStore.js +106 -41
- package/MediaStore/hooks.d.ts +7 -0
- package/MediaStore/hooks.js +42 -0
- package/MediaStore/mediaFile.utils.d.ts +3 -1
- package/MediaStore/mediaFile.utils.js +12 -1
- package/MediaStore/polyglot/messages_en.js +25 -15
- package/MediaStore/polyglot/messages_ru.js +25 -15
- package/MediaStore/polyglot/types.d.ts +28 -15
- package/MediaStore/styled.d.ts +6 -4
- package/MediaStore/styled.js +29 -21
- package/MediaStore/types.d.ts +2 -2
- package/package.json +2 -2
- package/MediaStore/CreateVariantDrawer.d.ts +0 -12
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { type FC } from 'react';
|
|
2
|
+
import type { MediaFile } from '@pushwoosh/rpc-v2-http-api-data/media_files';
|
|
3
|
+
type CreateCropDrawerProps = {
|
|
4
|
+
application: string;
|
|
5
|
+
original: MediaFile | null;
|
|
6
|
+
isOpen: boolean;
|
|
7
|
+
existingCrops: MediaFile[];
|
|
8
|
+
onClose: () => void;
|
|
9
|
+
onCreated: (parentUuid: string, crop: MediaFile) => void;
|
|
10
|
+
onDeleted: (parentUuid: string, cropUuid: string) => void;
|
|
11
|
+
};
|
|
12
|
+
export declare const CreateCropDrawer: FC<CreateCropDrawerProps>;
|
|
13
|
+
export {};
|
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { useEffect, useState } from 'react';
|
|
3
|
-
import { Button, Drawer, DrawerBody, DrawerHeader, DrawerTitle, NativeInput, useToast } from '@pushwoosh/dumb-components';
|
|
3
|
+
import { Button, Drawer, DrawerBody, DrawerHeader, DrawerTitle, ModalConfirm, NativeInput, useToast } from '@pushwoosh/dumb-components';
|
|
4
4
|
import { Columns, Vertical } from '@pushwoosh/kit-helpers';
|
|
5
|
-
import { CloseIcon } from '@pushwoosh/kit-icons';
|
|
5
|
+
import { CloseIcon, TrashIcon } from '@pushwoosh/kit-icons';
|
|
6
6
|
import { Text } from '@pushwoosh/kit-typography';
|
|
7
7
|
import { useSPAContext } from '@pushwoosh/spa-context';
|
|
8
|
+
import { useDeleteMediaFile } from './hooks';
|
|
8
9
|
import { getRatioLabel, getThumbUrl } from './mediaFile.utils';
|
|
9
10
|
import { useLang } from './polyglot';
|
|
10
|
-
import { Chip, Chips, FieldLabel, PreviewFrame, PreviewImage, PreviewStage, Segment, SegmentItem, TileImage
|
|
11
|
+
import { Chip, Chips, CropStrip, CropThumb, CropThumbCaption, CropThumbDelete, FieldLabel, PreviewFrame, PreviewImage, PreviewStage, Segment, SegmentItem, TileImage } from './styled';
|
|
11
12
|
const RATIOS = [{
|
|
12
13
|
value: 'ASPECT_RATIO_1_1',
|
|
13
14
|
label: '1:1',
|
|
@@ -48,13 +49,14 @@ const RATIOS = [{
|
|
|
48
49
|
const DEFAULT_RATIO = 'ASPECT_RATIO_16_9';
|
|
49
50
|
const DEFAULT_SIZE = '1080';
|
|
50
51
|
const DEFAULT_QUALITY = '90';
|
|
51
|
-
export const
|
|
52
|
+
export const CreateCropDrawer = ({
|
|
52
53
|
application,
|
|
53
54
|
original,
|
|
54
55
|
isOpen,
|
|
55
|
-
|
|
56
|
+
existingCrops,
|
|
56
57
|
onClose,
|
|
57
|
-
onCreated
|
|
58
|
+
onCreated,
|
|
59
|
+
onDeleted
|
|
58
60
|
}) => {
|
|
59
61
|
const lang = useLang();
|
|
60
62
|
const {
|
|
@@ -63,11 +65,13 @@ export const CreateVariantDrawer = ({
|
|
|
63
65
|
const {
|
|
64
66
|
openToast
|
|
65
67
|
} = useToast();
|
|
68
|
+
const deleteMediaFile = useDeleteMediaFile(application);
|
|
66
69
|
const [ratio, setRatio] = useState(DEFAULT_RATIO);
|
|
67
70
|
const [dimension, setDimension] = useState('width');
|
|
68
71
|
const [size, setSize] = useState(DEFAULT_SIZE);
|
|
69
72
|
const [quality, setQuality] = useState(DEFAULT_QUALITY);
|
|
70
73
|
const [isCreating, setIsCreating] = useState(false);
|
|
74
|
+
const [pendingDeleteCrop, setPendingDeleteCrop] = useState(null);
|
|
71
75
|
// The drawer mounts once and is toggled via `isOpen`, so reset every field
|
|
72
76
|
// whenever it opens for a different original — otherwise values (and the
|
|
73
77
|
// `ratioExists` guard) leak from the previously edited image.
|
|
@@ -87,14 +91,14 @@ export const CreateVariantDrawer = ({
|
|
|
87
91
|
const selectedRatio = RATIOS.find(item => item.value === ratio) ?? RATIOS[0];
|
|
88
92
|
const outputWidth = dimension === 'width' ? sizeValue : Math.round(sizeValue * selectedRatio.ar);
|
|
89
93
|
const outputHeight = dimension === 'height' ? sizeValue : Math.round(sizeValue / selectedRatio.ar);
|
|
90
|
-
const ratioExists =
|
|
91
|
-
var
|
|
92
|
-
return ((
|
|
94
|
+
const ratioExists = existingCrops.some(crop => {
|
|
95
|
+
var _crop$meta;
|
|
96
|
+
return ((_crop$meta = crop.meta) === null || _crop$meta === void 0 ? void 0 : _crop$meta.type) === 'imageMeta' && crop.meta.data.ratio === selectedRatio.label;
|
|
93
97
|
});
|
|
94
98
|
const handleCreate = async () => {
|
|
95
99
|
if (!original || !isSizeValid || !isQualityValid || ratioExists) return;
|
|
96
100
|
// Capture the parent up front so a concurrent reopen for another image
|
|
97
|
-
// can't misroute the created
|
|
101
|
+
// can't misroute the created crop.
|
|
98
102
|
const parent = original;
|
|
99
103
|
setIsCreating(true);
|
|
100
104
|
try {
|
|
@@ -112,7 +116,7 @@ export const CreateVariantDrawer = ({
|
|
|
112
116
|
});
|
|
113
117
|
openToast({
|
|
114
118
|
title: lang({
|
|
115
|
-
message: 'SMART.MEDIA_STORE.
|
|
119
|
+
message: 'SMART.MEDIA_STORE.CREATE_CROP.SUCCESS'
|
|
116
120
|
}),
|
|
117
121
|
type: 'success'
|
|
118
122
|
});
|
|
@@ -120,7 +124,7 @@ export const CreateVariantDrawer = ({
|
|
|
120
124
|
} catch (error) {
|
|
121
125
|
openToast({
|
|
122
126
|
title: error.message || lang({
|
|
123
|
-
message: 'SMART.MEDIA_STORE.
|
|
127
|
+
message: 'SMART.MEDIA_STORE.CREATE_CROP.ERROR'
|
|
124
128
|
}),
|
|
125
129
|
type: 'error'
|
|
126
130
|
});
|
|
@@ -128,6 +132,17 @@ export const CreateVariantDrawer = ({
|
|
|
128
132
|
setIsCreating(false);
|
|
129
133
|
}
|
|
130
134
|
};
|
|
135
|
+
const handleDeleteCrop = async () => {
|
|
136
|
+
const crop = pendingDeleteCrop;
|
|
137
|
+
setPendingDeleteCrop(null);
|
|
138
|
+
if (!crop) return;
|
|
139
|
+
// `parent_uuid` is set on crops; fall back to the open original's uuid.
|
|
140
|
+
const parentUuid = crop.parentUuid ?? (original === null || original === void 0 ? void 0 : original.uuid);
|
|
141
|
+
if (!parentUuid) return;
|
|
142
|
+
if (await deleteMediaFile(crop.uuid)) {
|
|
143
|
+
onDeleted(parentUuid, crop.uuid);
|
|
144
|
+
}
|
|
145
|
+
};
|
|
131
146
|
return _jsxs(Drawer, {
|
|
132
147
|
size: "medium",
|
|
133
148
|
isOpen: isOpen,
|
|
@@ -136,7 +151,7 @@ export const CreateVariantDrawer = ({
|
|
|
136
151
|
children: _jsxs(DrawerTitle, {
|
|
137
152
|
children: [_jsx("span", {
|
|
138
153
|
children: lang({
|
|
139
|
-
message: 'SMART.MEDIA_STORE.
|
|
154
|
+
message: 'SMART.MEDIA_STORE.CREATE_CROP.TITLE'
|
|
140
155
|
})
|
|
141
156
|
}), _jsx(Button, {
|
|
142
157
|
view: "ghost",
|
|
@@ -146,7 +161,7 @@ export const CreateVariantDrawer = ({
|
|
|
146
161
|
size: "small"
|
|
147
162
|
}),
|
|
148
163
|
"aria-label": lang({
|
|
149
|
-
message: 'SMART.MEDIA_STORE.
|
|
164
|
+
message: 'SMART.MEDIA_STORE.CREATE_CROP.CLOSE'
|
|
150
165
|
}),
|
|
151
166
|
onClick: onClose
|
|
152
167
|
})]
|
|
@@ -157,7 +172,7 @@ export const CreateVariantDrawer = ({
|
|
|
157
172
|
children: [_jsxs("div", {
|
|
158
173
|
children: [_jsx(FieldLabel, {
|
|
159
174
|
children: lang({
|
|
160
|
-
message: 'SMART.MEDIA_STORE.
|
|
175
|
+
message: 'SMART.MEDIA_STORE.CREATE_CROP.RATIO_LABEL'
|
|
161
176
|
})
|
|
162
177
|
}), _jsx(Chips, {
|
|
163
178
|
children: RATIOS.map(item => _jsx(Chip, {
|
|
@@ -170,7 +185,7 @@ export const CreateVariantDrawer = ({
|
|
|
170
185
|
}), _jsxs("div", {
|
|
171
186
|
children: [_jsx(FieldLabel, {
|
|
172
187
|
children: lang({
|
|
173
|
-
message: 'SMART.MEDIA_STORE.
|
|
188
|
+
message: 'SMART.MEDIA_STORE.CREATE_CROP.SIZE_LABEL'
|
|
174
189
|
})
|
|
175
190
|
}), _jsxs(Columns, {
|
|
176
191
|
"$sizes": "auto 1fr",
|
|
@@ -182,14 +197,14 @@ export const CreateVariantDrawer = ({
|
|
|
182
197
|
"$active": dimension === 'width',
|
|
183
198
|
onClick: () => setDimension('width'),
|
|
184
199
|
children: lang({
|
|
185
|
-
message: 'SMART.MEDIA_STORE.
|
|
200
|
+
message: 'SMART.MEDIA_STORE.CREATE_CROP.WIDTH'
|
|
186
201
|
})
|
|
187
202
|
}), _jsx(SegmentItem, {
|
|
188
203
|
type: "button",
|
|
189
204
|
"$active": dimension === 'height',
|
|
190
205
|
onClick: () => setDimension('height'),
|
|
191
206
|
children: lang({
|
|
192
|
-
message: 'SMART.MEDIA_STORE.
|
|
207
|
+
message: 'SMART.MEDIA_STORE.CREATE_CROP.HEIGHT'
|
|
193
208
|
})
|
|
194
209
|
})]
|
|
195
210
|
}), _jsx(NativeInput, {
|
|
@@ -216,7 +231,7 @@ export const CreateVariantDrawer = ({
|
|
|
216
231
|
}), isJpeg && _jsxs("div", {
|
|
217
232
|
children: [_jsx(FieldLabel, {
|
|
218
233
|
children: lang({
|
|
219
|
-
message: 'SMART.MEDIA_STORE.
|
|
234
|
+
message: 'SMART.MEDIA_STORE.CREATE_CROP.QUALITY_LABEL'
|
|
220
235
|
})
|
|
221
236
|
}), _jsx(NativeInput, {
|
|
222
237
|
type: "number",
|
|
@@ -228,7 +243,7 @@ export const CreateVariantDrawer = ({
|
|
|
228
243
|
})]
|
|
229
244
|
}), ratioExists && _jsx(Text, {
|
|
230
245
|
children: lang({
|
|
231
|
-
message: 'SMART.MEDIA_STORE.
|
|
246
|
+
message: 'SMART.MEDIA_STORE.CREATE_CROP.RATIO_EXISTS'
|
|
232
247
|
})
|
|
233
248
|
}), _jsx(Button, {
|
|
234
249
|
color: "primary",
|
|
@@ -237,29 +252,53 @@ export const CreateVariantDrawer = ({
|
|
|
237
252
|
isLoading: isCreating,
|
|
238
253
|
onClick: () => void handleCreate(),
|
|
239
254
|
children: lang({
|
|
240
|
-
message: 'SMART.MEDIA_STORE.
|
|
255
|
+
message: 'SMART.MEDIA_STORE.CREATE_CROP.CREATE_BUTTON'
|
|
241
256
|
})
|
|
242
257
|
}), _jsxs("div", {
|
|
243
258
|
children: [_jsx(FieldLabel, {
|
|
244
259
|
children: lang({
|
|
245
|
-
message: 'SMART.MEDIA_STORE.
|
|
260
|
+
message: 'SMART.MEDIA_STORE.CREATE_CROP.EXISTING_LABEL'
|
|
246
261
|
})
|
|
247
|
-
}),
|
|
262
|
+
}), existingCrops.length === 0 ? _jsx(Text, {
|
|
248
263
|
children: lang({
|
|
249
|
-
message: 'SMART.MEDIA_STORE.
|
|
264
|
+
message: 'SMART.MEDIA_STORE.CREATE_CROP.NO_CROPS'
|
|
250
265
|
})
|
|
251
|
-
}) : _jsx(
|
|
252
|
-
children:
|
|
266
|
+
}) : _jsx(CropStrip, {
|
|
267
|
+
children: existingCrops.map(crop => _jsxs(CropThumb, {
|
|
253
268
|
children: [_jsx(TileImage, {
|
|
254
|
-
src: getThumbUrl(
|
|
255
|
-
alt:
|
|
256
|
-
}), _jsx(
|
|
257
|
-
children:
|
|
269
|
+
src: getThumbUrl(crop),
|
|
270
|
+
alt: crop.filename
|
|
271
|
+
}), _jsx(CropThumbDelete, {
|
|
272
|
+
children: _jsx(Button, {
|
|
273
|
+
color: "secondary",
|
|
274
|
+
size: "compact",
|
|
275
|
+
icon: _jsx(TrashIcon, {
|
|
276
|
+
size: "small",
|
|
277
|
+
view: "lined"
|
|
278
|
+
}),
|
|
279
|
+
"aria-label": lang({
|
|
280
|
+
message: 'SMART.MEDIA_STORE.CREATE_CROP.DELETE_HINT'
|
|
281
|
+
}),
|
|
282
|
+
onClick: () => setPendingDeleteCrop(crop)
|
|
283
|
+
})
|
|
284
|
+
}), _jsx(CropThumbCaption, {
|
|
285
|
+
children: getRatioLabel(crop) || crop.filename
|
|
258
286
|
})]
|
|
259
|
-
},
|
|
287
|
+
}, crop.uuid))
|
|
260
288
|
})]
|
|
261
289
|
})]
|
|
262
290
|
})
|
|
291
|
+
}), _jsx(ModalConfirm, {
|
|
292
|
+
isOpen: pendingDeleteCrop !== null,
|
|
293
|
+
title: lang({
|
|
294
|
+
message: 'SMART.MEDIA_STORE.CREATE_CROP.DELETE_CONFIRM.TITLE'
|
|
295
|
+
}),
|
|
296
|
+
confirmButtonText: lang({
|
|
297
|
+
message: 'SMART.MEDIA_STORE.CREATE_CROP.DELETE_CONFIRM.BUTTON'
|
|
298
|
+
}),
|
|
299
|
+
confirmButtonColor: "danger",
|
|
300
|
+
onAccept: () => void handleDeleteCrop(),
|
|
301
|
+
onCancel: () => setPendingDeleteCrop(null)
|
|
263
302
|
})]
|
|
264
303
|
});
|
|
265
304
|
};
|
|
@@ -3,6 +3,6 @@ import type { MediaStoreProps } from './types';
|
|
|
3
3
|
/**
|
|
4
4
|
* Controlled media gallery: the parent owns the current page (and page size)
|
|
5
5
|
* and is notified of navigation via `onPageChange`. For a self-contained
|
|
6
|
-
*
|
|
6
|
+
* version that keeps the page in internal state, use `MediaStorePaginated`.
|
|
7
7
|
*/
|
|
8
8
|
export declare const MediaStore: FC<MediaStoreProps>;
|
package/MediaStore/MediaStore.js
CHANGED
|
@@ -1,19 +1,20 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
2
|
import { useEffect, useRef, useState } from 'react';
|
|
3
|
-
import { Button, DropdownMenuButton, DropdownMenuItem, DropdownMenuSection, useToast } from '@pushwoosh/dumb-components';
|
|
3
|
+
import { Button, DropdownMenuButton, DropdownMenuItem, DropdownMenuSection, ModalConfirm, useToast } from '@pushwoosh/dumb-components';
|
|
4
4
|
import { Horizontal, useAsync } from '@pushwoosh/kit-helpers';
|
|
5
|
-
import { CheckIcon, EditIcon, UploadIcon } from '@pushwoosh/kit-icons';
|
|
5
|
+
import { CheckIcon, EditIcon, TrashIcon, UploadIcon } from '@pushwoosh/kit-icons';
|
|
6
6
|
import { Text } from '@pushwoosh/kit-typography';
|
|
7
7
|
import { useSPAContext } from '@pushwoosh/spa-context';
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
8
|
+
import { CreateCropDrawer } from './CreateCropDrawer';
|
|
9
|
+
import { useDeleteMediaFile } from './hooks';
|
|
10
|
+
import { formatBytes, getRatioLabel, getThumbUrl } from './mediaFile.utils';
|
|
10
11
|
import { useLang } from './polyglot';
|
|
11
|
-
import { Dropzone, EmptyState, Grid, HiddenInput, Tile, TileActions, TileImage,
|
|
12
|
+
import { CropBadge, Dropzone, EmptyState, Grid, HiddenInput, Tile, TileActions, TileImage, UsageLine } from './styled';
|
|
12
13
|
const UPLOAD_PATH = '/api/media/upload';
|
|
13
14
|
/**
|
|
14
15
|
* Controlled media gallery: the parent owns the current page (and page size)
|
|
15
16
|
* and is notified of navigation via `onPageChange`. For a self-contained
|
|
16
|
-
*
|
|
17
|
+
* version that keeps the page in internal state, use `MediaStorePaginated`.
|
|
17
18
|
*/
|
|
18
19
|
export const MediaStore = ({
|
|
19
20
|
application,
|
|
@@ -34,18 +35,23 @@ export const MediaStore = ({
|
|
|
34
35
|
const {
|
|
35
36
|
openToast
|
|
36
37
|
} = useToast();
|
|
38
|
+
const deleteMediaFile = useDeleteMediaFile(application);
|
|
37
39
|
const uploadInputRef = useRef(null);
|
|
38
40
|
// `uploadFiles` resolves asynchronously; read the latest page from the ref so
|
|
39
41
|
// the post-upload decision isn't made on a value captured at click time.
|
|
40
42
|
const pageRef = useRef(page);
|
|
41
43
|
pageRef.current = page;
|
|
42
|
-
const [
|
|
43
|
-
// Bumped to force a reload of the current page
|
|
44
|
-
// the page prop
|
|
44
|
+
const [cropsByUuid, setCropsByUuid] = useState({});
|
|
45
|
+
// Bumped to force a reload of the current page when the originals list changes
|
|
46
|
+
// without the page prop changing (upload, delete of an original).
|
|
45
47
|
const [reloadToken, setReloadToken] = useState(0);
|
|
48
|
+
// Bumped on changes that affect storage usage but not the originals list
|
|
49
|
+
// (crop create/delete), so the usage line refreshes without refetching files.
|
|
50
|
+
const [usageToken, setUsageToken] = useState(0);
|
|
46
51
|
const [isUploading, setIsUploading] = useState(false);
|
|
47
52
|
const [isDragOver, setIsDragOver] = useState(false);
|
|
48
|
-
const [
|
|
53
|
+
const [createCropFor, setCreateCropFor] = useState(null);
|
|
54
|
+
const [pendingDelete, setPendingDelete] = useState(null);
|
|
49
55
|
const listAsync = useAsync(async () => {
|
|
50
56
|
const response = await rpcGateway.mediaFiles.List({
|
|
51
57
|
application,
|
|
@@ -58,11 +64,17 @@ export const MediaStore = ({
|
|
|
58
64
|
total: response.total
|
|
59
65
|
};
|
|
60
66
|
}, [rpcGateway, application, page, perPage, reloadToken]);
|
|
67
|
+
const usageAsync = useAsync(() => rpcGateway.mediaFiles.GetUsage({
|
|
68
|
+
application
|
|
69
|
+
}), [rpcGateway, application, reloadToken, usageToken]);
|
|
61
70
|
const {
|
|
62
71
|
loading
|
|
63
72
|
} = listAsync;
|
|
64
73
|
const files = ((_listAsync$value = listAsync.value) === null || _listAsync$value === void 0 ? void 0 : _listAsync$value.files) ?? [];
|
|
65
74
|
const total = ((_listAsync$value2 = listAsync.value) === null || _listAsync$value2 === void 0 ? void 0 : _listAsync$value2.total) ?? 0;
|
|
75
|
+
// Read inside the async delete handler to avoid a stale-closure decision.
|
|
76
|
+
const filesCountRef = useRef(files.length);
|
|
77
|
+
filesCountRef.current = files.length;
|
|
66
78
|
useEffect(() => {
|
|
67
79
|
if (listAsync.error) {
|
|
68
80
|
openToast({
|
|
@@ -73,15 +85,15 @@ export const MediaStore = ({
|
|
|
73
85
|
});
|
|
74
86
|
}
|
|
75
87
|
}, [listAsync.error, openToast, lang]);
|
|
76
|
-
// The List of originals carries no
|
|
77
|
-
// resolves, fetch each one's
|
|
78
|
-
// "N
|
|
88
|
+
// The List of originals carries no crop count, so once a page of originals
|
|
89
|
+
// resolves, fetch each one's crops in the background and let the
|
|
90
|
+
// "N crops" badge appear per file as soon as its request resolves. The
|
|
79
91
|
// cleanup flag drops results of a page that has since been replaced.
|
|
80
92
|
useEffect(() => {
|
|
81
93
|
var _listAsync$value3;
|
|
82
94
|
const originals = (_listAsync$value3 = listAsync.value) === null || _listAsync$value3 === void 0 ? void 0 : _listAsync$value3.files;
|
|
83
95
|
if (!originals) return undefined;
|
|
84
|
-
|
|
96
|
+
setCropsByUuid({});
|
|
85
97
|
let cancelled = false;
|
|
86
98
|
originals.forEach(file => {
|
|
87
99
|
void (async () => {
|
|
@@ -91,7 +103,7 @@ export const MediaStore = ({
|
|
|
91
103
|
parentUuid: file.uuid
|
|
92
104
|
});
|
|
93
105
|
if (cancelled) return;
|
|
94
|
-
|
|
106
|
+
setCropsByUuid(prev => ({
|
|
95
107
|
...prev,
|
|
96
108
|
[file.uuid]: response.files ?? []
|
|
97
109
|
}));
|
|
@@ -129,13 +141,13 @@ export const MediaStore = ({
|
|
|
129
141
|
}),
|
|
130
142
|
type: 'success'
|
|
131
143
|
});
|
|
132
|
-
//
|
|
133
|
-
//
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
} else {
|
|
144
|
+
// Surface the freshly uploaded files on the first page. Bump the reload
|
|
145
|
+
// token unconditionally (refetches page 0 / refreshes usage); also
|
|
146
|
+
// navigate when we're not already there.
|
|
147
|
+
if (pageRef.current !== 0) {
|
|
137
148
|
onPageChange(0);
|
|
138
149
|
}
|
|
150
|
+
setReloadToken(token => token + 1);
|
|
139
151
|
}
|
|
140
152
|
if (failed > 0) {
|
|
141
153
|
openToast({
|
|
@@ -152,15 +164,42 @@ export const MediaStore = ({
|
|
|
152
164
|
setIsUploading(false);
|
|
153
165
|
}
|
|
154
166
|
};
|
|
155
|
-
const
|
|
156
|
-
|
|
167
|
+
const handleCropCreated = (parentUuid, crop) => {
|
|
168
|
+
setCropsByUuid(prev => ({
|
|
157
169
|
...prev,
|
|
158
|
-
[parentUuid]: [...(prev[parentUuid] ?? []),
|
|
170
|
+
[parentUuid]: [...(prev[parentUuid] ?? []), crop]
|
|
159
171
|
}));
|
|
172
|
+
setUsageToken(token => token + 1);
|
|
173
|
+
};
|
|
174
|
+
const handleCropDeleted = (parentUuid, cropUuid) => {
|
|
175
|
+
setCropsByUuid(prev => ({
|
|
176
|
+
...prev,
|
|
177
|
+
[parentUuid]: (prev[parentUuid] ?? []).filter(crop => crop.uuid !== cropUuid)
|
|
178
|
+
}));
|
|
179
|
+
setUsageToken(token => token + 1);
|
|
180
|
+
};
|
|
181
|
+
const handleDeleteConfirmed = async () => {
|
|
182
|
+
const file = pendingDelete;
|
|
183
|
+
setPendingDelete(null);
|
|
184
|
+
if (!file) return;
|
|
185
|
+
if (!(await deleteMediaFile(file.uuid))) return;
|
|
186
|
+
// Deleting an original drops its crops too. If it was the last item on a
|
|
187
|
+
// non-first page, step back so we don't land on an empty page; the page
|
|
188
|
+
// change refetches the list. Otherwise refetch the current page.
|
|
189
|
+
if (filesCountRef.current === 1 && pageRef.current > 0) {
|
|
190
|
+
onPageChange(pageRef.current - 1);
|
|
191
|
+
}
|
|
192
|
+
setReloadToken(token => token + 1);
|
|
160
193
|
};
|
|
161
194
|
const totalPages = Math.ceil(total / perPage);
|
|
162
195
|
return _jsxs(_Fragment, {
|
|
163
|
-
children: [
|
|
196
|
+
children: [usageAsync.value && _jsx(UsageLine, {
|
|
197
|
+
children: lang({
|
|
198
|
+
message: 'SMART.MEDIA_STORE.USAGE',
|
|
199
|
+
count: usageAsync.value.totalCount,
|
|
200
|
+
size: formatBytes(usageAsync.value.totalSize)
|
|
201
|
+
})
|
|
202
|
+
}), _jsxs(Dropzone, {
|
|
164
203
|
type: "button",
|
|
165
204
|
"$dragOver": isDragOver,
|
|
166
205
|
disabled: isUploading,
|
|
@@ -203,18 +242,18 @@ export const MediaStore = ({
|
|
|
203
242
|
}), !loading && files.length > 0 && _jsxs(_Fragment, {
|
|
204
243
|
children: [_jsx(Grid, {
|
|
205
244
|
children: files.map(file => {
|
|
206
|
-
const
|
|
245
|
+
const crops = cropsByUuid[file.uuid] ?? [];
|
|
207
246
|
return _jsxs(Tile, {
|
|
208
247
|
children: [_jsx(TileImage, {
|
|
209
248
|
src: getThumbUrl(file),
|
|
210
249
|
alt: file.filename
|
|
211
|
-
}),
|
|
250
|
+
}), crops.length > 0 && _jsx(CropBadge, {
|
|
212
251
|
children: lang({
|
|
213
|
-
message: 'SMART.MEDIA_STORE.
|
|
214
|
-
count:
|
|
252
|
+
message: 'SMART.MEDIA_STORE.CROPS_BADGE',
|
|
253
|
+
count: crops.length
|
|
215
254
|
})
|
|
216
255
|
}), _jsxs(TileActions, {
|
|
217
|
-
children: [
|
|
256
|
+
children: [crops.length > 0 ? _jsx(DropdownMenuButton, {
|
|
218
257
|
color: "secondary",
|
|
219
258
|
size: "compact",
|
|
220
259
|
icon: _jsx(CheckIcon, {
|
|
@@ -227,10 +266,10 @@ export const MediaStore = ({
|
|
|
227
266
|
children: lang({
|
|
228
267
|
message: 'SMART.MEDIA_STORE.SELECT_ORIGINAL'
|
|
229
268
|
})
|
|
230
|
-
}),
|
|
231
|
-
onClick: () => onSelect(
|
|
232
|
-
children: getRatioLabel(
|
|
233
|
-
},
|
|
269
|
+
}), crops.map(crop => _jsx(DropdownMenuItem, {
|
|
270
|
+
onClick: () => onSelect(crop.url),
|
|
271
|
+
children: getRatioLabel(crop) || crop.filename
|
|
272
|
+
}, crop.uuid))]
|
|
234
273
|
})
|
|
235
274
|
}) : _jsx(Button, {
|
|
236
275
|
color: "secondary",
|
|
@@ -250,9 +289,20 @@ export const MediaStore = ({
|
|
|
250
289
|
view: "lined"
|
|
251
290
|
}),
|
|
252
291
|
"aria-label": lang({
|
|
253
|
-
message: 'SMART.MEDIA_STORE.
|
|
292
|
+
message: 'SMART.MEDIA_STORE.CREATE_CROP_HINT'
|
|
293
|
+
}),
|
|
294
|
+
onClick: () => setCreateCropFor(file)
|
|
295
|
+
}), _jsx(Button, {
|
|
296
|
+
color: "secondary",
|
|
297
|
+
size: "compact",
|
|
298
|
+
icon: _jsx(TrashIcon, {
|
|
299
|
+
size: "small",
|
|
300
|
+
view: "lined"
|
|
301
|
+
}),
|
|
302
|
+
"aria-label": lang({
|
|
303
|
+
message: 'SMART.MEDIA_STORE.DELETE_HINT'
|
|
254
304
|
}),
|
|
255
|
-
onClick: () =>
|
|
305
|
+
onClick: () => setPendingDelete(file)
|
|
256
306
|
})]
|
|
257
307
|
})]
|
|
258
308
|
}, file.uuid);
|
|
@@ -286,13 +336,28 @@ export const MediaStore = ({
|
|
|
286
336
|
})
|
|
287
337
|
})]
|
|
288
338
|
})]
|
|
289
|
-
}), _jsx(
|
|
339
|
+
}), _jsx(CreateCropDrawer, {
|
|
290
340
|
application: application,
|
|
291
|
-
original:
|
|
292
|
-
isOpen:
|
|
293
|
-
|
|
294
|
-
onClose: () =>
|
|
295
|
-
onCreated:
|
|
341
|
+
original: createCropFor,
|
|
342
|
+
isOpen: createCropFor !== null,
|
|
343
|
+
existingCrops: createCropFor ? cropsByUuid[createCropFor.uuid] ?? [] : [],
|
|
344
|
+
onClose: () => setCreateCropFor(null),
|
|
345
|
+
onCreated: handleCropCreated,
|
|
346
|
+
onDeleted: handleCropDeleted
|
|
347
|
+
}), _jsx(ModalConfirm, {
|
|
348
|
+
isOpen: pendingDelete !== null,
|
|
349
|
+
title: lang({
|
|
350
|
+
message: 'SMART.MEDIA_STORE.DELETE_CONFIRM.TITLE'
|
|
351
|
+
}),
|
|
352
|
+
description: lang({
|
|
353
|
+
message: 'SMART.MEDIA_STORE.DELETE_CONFIRM.DESCRIPTION'
|
|
354
|
+
}),
|
|
355
|
+
confirmButtonText: lang({
|
|
356
|
+
message: 'SMART.MEDIA_STORE.DELETE_CONFIRM.BUTTON'
|
|
357
|
+
}),
|
|
358
|
+
confirmButtonColor: "danger",
|
|
359
|
+
onAccept: () => void handleDeleteConfirmed(),
|
|
360
|
+
onCancel: () => setPendingDelete(null)
|
|
296
361
|
})]
|
|
297
362
|
});
|
|
298
363
|
};
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Returns a function that deletes a media file (original or crop) and shows the
|
|
3
|
+
* success/error toast. Resolves to `true` on success so the caller can run its
|
|
4
|
+
* own post-delete routing (refetch, state update, pagination), `false` on
|
|
5
|
+
* failure. Both `MediaStore` and `CreateCropDrawer` share this.
|
|
6
|
+
*/
|
|
7
|
+
export declare function useDeleteMediaFile(application: string): (uuid: string) => Promise<boolean>;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { useToast } from '@pushwoosh/dumb-components';
|
|
2
|
+
import { usePersistentFunction } from '@pushwoosh/kit-helpers';
|
|
3
|
+
import { useSPAContext } from '@pushwoosh/spa-context';
|
|
4
|
+
import { useLang } from './polyglot';
|
|
5
|
+
/**
|
|
6
|
+
* Returns a function that deletes a media file (original or crop) and shows the
|
|
7
|
+
* success/error toast. Resolves to `true` on success so the caller can run its
|
|
8
|
+
* own post-delete routing (refetch, state update, pagination), `false` on
|
|
9
|
+
* failure. Both `MediaStore` and `CreateCropDrawer` share this.
|
|
10
|
+
*/
|
|
11
|
+
export function useDeleteMediaFile(application) {
|
|
12
|
+
const {
|
|
13
|
+
rpcGateway
|
|
14
|
+
} = useSPAContext();
|
|
15
|
+
const {
|
|
16
|
+
openToast
|
|
17
|
+
} = useToast();
|
|
18
|
+
const lang = useLang();
|
|
19
|
+
return usePersistentFunction(async uuid => {
|
|
20
|
+
try {
|
|
21
|
+
await rpcGateway.mediaFiles.Delete({
|
|
22
|
+
application,
|
|
23
|
+
uuid
|
|
24
|
+
});
|
|
25
|
+
openToast({
|
|
26
|
+
title: lang({
|
|
27
|
+
message: 'SMART.MEDIA_STORE.DELETE_SUCCESS'
|
|
28
|
+
}),
|
|
29
|
+
type: 'success'
|
|
30
|
+
});
|
|
31
|
+
return true;
|
|
32
|
+
} catch (error) {
|
|
33
|
+
openToast({
|
|
34
|
+
title: error.message || lang({
|
|
35
|
+
message: 'SMART.MEDIA_STORE.DELETE_ERROR'
|
|
36
|
+
}),
|
|
37
|
+
type: 'error'
|
|
38
|
+
});
|
|
39
|
+
return false;
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
}
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import type { MediaFile } from '@pushwoosh/rpc-v2-http-api-data/media_files';
|
|
2
2
|
/** Thumbnail URL from image meta, falling back to the full-size URL. */
|
|
3
3
|
export declare const getThumbUrl: (file: MediaFile) => string;
|
|
4
|
-
/** Aspect-ratio label (e.g. "16:9") for a
|
|
4
|
+
/** Aspect-ratio label (e.g. "16:9") for a crop; empty for originals. */
|
|
5
5
|
export declare const getRatioLabel: (file: MediaFile) => string;
|
|
6
|
+
/** Human-readable byte size, e.g. 4404019 -> "4.2 MB". */
|
|
7
|
+
export declare const formatBytes: (bytes: number) => string;
|
|
@@ -3,8 +3,19 @@ export const getThumbUrl = file => {
|
|
|
3
3
|
var _file$meta;
|
|
4
4
|
return ((_file$meta = file.meta) === null || _file$meta === void 0 ? void 0 : _file$meta.type) === 'imageMeta' ? file.meta.data.thumbnailUrl : file.url;
|
|
5
5
|
};
|
|
6
|
-
/** Aspect-ratio label (e.g. "16:9") for a
|
|
6
|
+
/** Aspect-ratio label (e.g. "16:9") for a crop; empty for originals. */
|
|
7
7
|
export const getRatioLabel = file => {
|
|
8
8
|
var _file$meta2;
|
|
9
9
|
return ((_file$meta2 = file.meta) === null || _file$meta2 === void 0 ? void 0 : _file$meta2.type) === 'imageMeta' ? file.meta.data.ratio : '';
|
|
10
|
+
};
|
|
11
|
+
const BYTE_UNITS = ['B', 'KB', 'MB', 'GB', 'TB'];
|
|
12
|
+
/** Human-readable byte size, e.g. 4404019 -> "4.2 MB". */
|
|
13
|
+
export const formatBytes = bytes => {
|
|
14
|
+
if (bytes === 0) return '0 B';
|
|
15
|
+
if (!Number.isFinite(bytes) || bytes < 0) return '—';
|
|
16
|
+
const exp = Math.min(Math.floor(Math.log(bytes) / Math.log(1024)), BYTE_UNITS.length - 1);
|
|
17
|
+
const value = bytes / 1024 ** exp;
|
|
18
|
+
// Whole numbers for bytes and for values >= 100; one decimal otherwise.
|
|
19
|
+
const rounded = exp === 0 || value >= 100 ? Math.round(value) : Math.round(value * 10) / 10;
|
|
20
|
+
return `${rounded} ${BYTE_UNITS[exp]}`;
|
|
10
21
|
};
|
|
@@ -9,20 +9,30 @@ export const messages = {
|
|
|
9
9
|
'SMART.MEDIA_STORE.LOAD_ERROR': 'Failed to load media files',
|
|
10
10
|
'SMART.MEDIA_STORE.UPLOAD_ERROR': 'Failed to upload image',
|
|
11
11
|
'SMART.MEDIA_STORE.UPLOAD_SUCCESS': 'Image uploaded',
|
|
12
|
-
'SMART.MEDIA_STORE.
|
|
12
|
+
'SMART.MEDIA_STORE.USAGE': '{{count}} files · {{size}}',
|
|
13
|
+
'SMART.MEDIA_STORE.CROPS_BADGE': '{{count}} cr.',
|
|
13
14
|
'SMART.MEDIA_STORE.SELECT_ORIGINAL': 'Original',
|
|
14
|
-
'SMART.MEDIA_STORE.
|
|
15
|
-
'SMART.MEDIA_STORE.
|
|
16
|
-
'SMART.MEDIA_STORE.
|
|
17
|
-
'SMART.MEDIA_STORE.
|
|
18
|
-
'SMART.MEDIA_STORE.
|
|
19
|
-
'SMART.MEDIA_STORE.
|
|
20
|
-
'SMART.MEDIA_STORE.
|
|
21
|
-
'SMART.MEDIA_STORE.
|
|
22
|
-
'SMART.MEDIA_STORE.
|
|
23
|
-
'SMART.MEDIA_STORE.
|
|
24
|
-
'SMART.MEDIA_STORE.
|
|
25
|
-
'SMART.MEDIA_STORE.
|
|
26
|
-
'SMART.MEDIA_STORE.
|
|
27
|
-
'SMART.MEDIA_STORE.
|
|
15
|
+
'SMART.MEDIA_STORE.CREATE_CROP_HINT': 'Create crops',
|
|
16
|
+
'SMART.MEDIA_STORE.DELETE_HINT': 'Delete',
|
|
17
|
+
'SMART.MEDIA_STORE.DELETE_SUCCESS': 'Deleted',
|
|
18
|
+
'SMART.MEDIA_STORE.DELETE_ERROR': 'Failed to delete',
|
|
19
|
+
'SMART.MEDIA_STORE.DELETE_CONFIRM.TITLE': 'Delete this file?',
|
|
20
|
+
'SMART.MEDIA_STORE.DELETE_CONFIRM.DESCRIPTION': 'Deleting the original also removes all of its crops. This can’t be undone.',
|
|
21
|
+
'SMART.MEDIA_STORE.DELETE_CONFIRM.BUTTON': 'Delete',
|
|
22
|
+
'SMART.MEDIA_STORE.CREATE_CROP.TITLE': 'Create crop',
|
|
23
|
+
'SMART.MEDIA_STORE.CREATE_CROP.CLOSE': 'Close',
|
|
24
|
+
'SMART.MEDIA_STORE.CREATE_CROP.RATIO_LABEL': 'Aspect ratio',
|
|
25
|
+
'SMART.MEDIA_STORE.CREATE_CROP.SIZE_LABEL': 'Size',
|
|
26
|
+
'SMART.MEDIA_STORE.CREATE_CROP.WIDTH': 'Width',
|
|
27
|
+
'SMART.MEDIA_STORE.CREATE_CROP.HEIGHT': 'Height',
|
|
28
|
+
'SMART.MEDIA_STORE.CREATE_CROP.QUALITY_LABEL': 'JPEG quality (1–100)',
|
|
29
|
+
'SMART.MEDIA_STORE.CREATE_CROP.CREATE_BUTTON': 'Create crop',
|
|
30
|
+
'SMART.MEDIA_STORE.CREATE_CROP.RATIO_EXISTS': 'A crop with this aspect ratio already exists',
|
|
31
|
+
'SMART.MEDIA_STORE.CREATE_CROP.EXISTING_LABEL': 'Existing crops',
|
|
32
|
+
'SMART.MEDIA_STORE.CREATE_CROP.NO_CROPS': 'No crops yet',
|
|
33
|
+
'SMART.MEDIA_STORE.CREATE_CROP.SUCCESS': 'Crop created',
|
|
34
|
+
'SMART.MEDIA_STORE.CREATE_CROP.ERROR': 'Failed to create crop',
|
|
35
|
+
'SMART.MEDIA_STORE.CREATE_CROP.DELETE_HINT': 'Delete crop',
|
|
36
|
+
'SMART.MEDIA_STORE.CREATE_CROP.DELETE_CONFIRM.TITLE': 'Delete this crop?',
|
|
37
|
+
'SMART.MEDIA_STORE.CREATE_CROP.DELETE_CONFIRM.BUTTON': 'Delete'
|
|
28
38
|
};
|
|
@@ -9,20 +9,30 @@ export const messages = {
|
|
|
9
9
|
'SMART.MEDIA_STORE.LOAD_ERROR': 'Не удалось загрузить медиафайлы',
|
|
10
10
|
'SMART.MEDIA_STORE.UPLOAD_ERROR': 'Не удалось загрузить изображение',
|
|
11
11
|
'SMART.MEDIA_STORE.UPLOAD_SUCCESS': 'Изображение загружено',
|
|
12
|
-
'SMART.MEDIA_STORE.
|
|
12
|
+
'SMART.MEDIA_STORE.USAGE': '{{count}} файлов · {{size}}',
|
|
13
|
+
'SMART.MEDIA_STORE.CROPS_BADGE': '{{count}} кр.',
|
|
13
14
|
'SMART.MEDIA_STORE.SELECT_ORIGINAL': 'Оригинал',
|
|
14
|
-
'SMART.MEDIA_STORE.
|
|
15
|
-
'SMART.MEDIA_STORE.
|
|
16
|
-
'SMART.MEDIA_STORE.
|
|
17
|
-
'SMART.MEDIA_STORE.
|
|
18
|
-
'SMART.MEDIA_STORE.
|
|
19
|
-
'SMART.MEDIA_STORE.
|
|
20
|
-
'SMART.MEDIA_STORE.
|
|
21
|
-
'SMART.MEDIA_STORE.
|
|
22
|
-
'SMART.MEDIA_STORE.
|
|
23
|
-
'SMART.MEDIA_STORE.
|
|
24
|
-
'SMART.MEDIA_STORE.
|
|
25
|
-
'SMART.MEDIA_STORE.
|
|
26
|
-
'SMART.MEDIA_STORE.
|
|
27
|
-
'SMART.MEDIA_STORE.
|
|
15
|
+
'SMART.MEDIA_STORE.CREATE_CROP_HINT': 'Создать кропы',
|
|
16
|
+
'SMART.MEDIA_STORE.DELETE_HINT': 'Удалить',
|
|
17
|
+
'SMART.MEDIA_STORE.DELETE_SUCCESS': 'Удалено',
|
|
18
|
+
'SMART.MEDIA_STORE.DELETE_ERROR': 'Не удалось удалить',
|
|
19
|
+
'SMART.MEDIA_STORE.DELETE_CONFIRM.TITLE': 'Удалить файл?',
|
|
20
|
+
'SMART.MEDIA_STORE.DELETE_CONFIRM.DESCRIPTION': 'Удаление оригинала также удалит все его кропы. Действие необратимо.',
|
|
21
|
+
'SMART.MEDIA_STORE.DELETE_CONFIRM.BUTTON': 'Удалить',
|
|
22
|
+
'SMART.MEDIA_STORE.CREATE_CROP.TITLE': 'Создание кропа',
|
|
23
|
+
'SMART.MEDIA_STORE.CREATE_CROP.CLOSE': 'Закрыть',
|
|
24
|
+
'SMART.MEDIA_STORE.CREATE_CROP.RATIO_LABEL': 'Соотношение сторон',
|
|
25
|
+
'SMART.MEDIA_STORE.CREATE_CROP.SIZE_LABEL': 'Размер',
|
|
26
|
+
'SMART.MEDIA_STORE.CREATE_CROP.WIDTH': 'Ширина',
|
|
27
|
+
'SMART.MEDIA_STORE.CREATE_CROP.HEIGHT': 'Высота',
|
|
28
|
+
'SMART.MEDIA_STORE.CREATE_CROP.QUALITY_LABEL': 'Качество JPEG (1–100)',
|
|
29
|
+
'SMART.MEDIA_STORE.CREATE_CROP.CREATE_BUTTON': 'Создать кроп',
|
|
30
|
+
'SMART.MEDIA_STORE.CREATE_CROP.RATIO_EXISTS': 'Кроп с таким соотношением сторон уже существует',
|
|
31
|
+
'SMART.MEDIA_STORE.CREATE_CROP.EXISTING_LABEL': 'Созданные кропы',
|
|
32
|
+
'SMART.MEDIA_STORE.CREATE_CROP.NO_CROPS': 'Кропов пока нет',
|
|
33
|
+
'SMART.MEDIA_STORE.CREATE_CROP.SUCCESS': 'Кроп создан',
|
|
34
|
+
'SMART.MEDIA_STORE.CREATE_CROP.ERROR': 'Не удалось создать кроп',
|
|
35
|
+
'SMART.MEDIA_STORE.CREATE_CROP.DELETE_HINT': 'Удалить кроп',
|
|
36
|
+
'SMART.MEDIA_STORE.CREATE_CROP.DELETE_CONFIRM.TITLE': 'Удалить кроп?',
|
|
37
|
+
'SMART.MEDIA_STORE.CREATE_CROP.DELETE_CONFIRM.BUTTON': 'Удалить'
|
|
28
38
|
};
|
|
@@ -10,22 +10,35 @@ export type MediaStoreMessages = {
|
|
|
10
10
|
'SMART.MEDIA_STORE.LOAD_ERROR': NoParams;
|
|
11
11
|
'SMART.MEDIA_STORE.UPLOAD_ERROR': NoParams;
|
|
12
12
|
'SMART.MEDIA_STORE.UPLOAD_SUCCESS': NoParams;
|
|
13
|
-
'SMART.MEDIA_STORE.
|
|
13
|
+
'SMART.MEDIA_STORE.USAGE': {
|
|
14
|
+
count: number;
|
|
15
|
+
size: string;
|
|
16
|
+
};
|
|
17
|
+
'SMART.MEDIA_STORE.CROPS_BADGE': {
|
|
14
18
|
count: number;
|
|
15
19
|
};
|
|
16
20
|
'SMART.MEDIA_STORE.SELECT_ORIGINAL': NoParams;
|
|
17
|
-
'SMART.MEDIA_STORE.
|
|
18
|
-
'SMART.MEDIA_STORE.
|
|
19
|
-
'SMART.MEDIA_STORE.
|
|
20
|
-
'SMART.MEDIA_STORE.
|
|
21
|
-
'SMART.MEDIA_STORE.
|
|
22
|
-
'SMART.MEDIA_STORE.
|
|
23
|
-
'SMART.MEDIA_STORE.
|
|
24
|
-
'SMART.MEDIA_STORE.
|
|
25
|
-
'SMART.MEDIA_STORE.
|
|
26
|
-
'SMART.MEDIA_STORE.
|
|
27
|
-
'SMART.MEDIA_STORE.
|
|
28
|
-
'SMART.MEDIA_STORE.
|
|
29
|
-
'SMART.MEDIA_STORE.
|
|
30
|
-
'SMART.MEDIA_STORE.
|
|
21
|
+
'SMART.MEDIA_STORE.CREATE_CROP_HINT': NoParams;
|
|
22
|
+
'SMART.MEDIA_STORE.DELETE_HINT': NoParams;
|
|
23
|
+
'SMART.MEDIA_STORE.DELETE_SUCCESS': NoParams;
|
|
24
|
+
'SMART.MEDIA_STORE.DELETE_ERROR': NoParams;
|
|
25
|
+
'SMART.MEDIA_STORE.DELETE_CONFIRM.TITLE': NoParams;
|
|
26
|
+
'SMART.MEDIA_STORE.DELETE_CONFIRM.DESCRIPTION': NoParams;
|
|
27
|
+
'SMART.MEDIA_STORE.DELETE_CONFIRM.BUTTON': NoParams;
|
|
28
|
+
'SMART.MEDIA_STORE.CREATE_CROP.TITLE': NoParams;
|
|
29
|
+
'SMART.MEDIA_STORE.CREATE_CROP.CLOSE': NoParams;
|
|
30
|
+
'SMART.MEDIA_STORE.CREATE_CROP.RATIO_LABEL': NoParams;
|
|
31
|
+
'SMART.MEDIA_STORE.CREATE_CROP.SIZE_LABEL': NoParams;
|
|
32
|
+
'SMART.MEDIA_STORE.CREATE_CROP.WIDTH': NoParams;
|
|
33
|
+
'SMART.MEDIA_STORE.CREATE_CROP.HEIGHT': NoParams;
|
|
34
|
+
'SMART.MEDIA_STORE.CREATE_CROP.QUALITY_LABEL': NoParams;
|
|
35
|
+
'SMART.MEDIA_STORE.CREATE_CROP.CREATE_BUTTON': NoParams;
|
|
36
|
+
'SMART.MEDIA_STORE.CREATE_CROP.RATIO_EXISTS': NoParams;
|
|
37
|
+
'SMART.MEDIA_STORE.CREATE_CROP.EXISTING_LABEL': NoParams;
|
|
38
|
+
'SMART.MEDIA_STORE.CREATE_CROP.NO_CROPS': NoParams;
|
|
39
|
+
'SMART.MEDIA_STORE.CREATE_CROP.SUCCESS': NoParams;
|
|
40
|
+
'SMART.MEDIA_STORE.CREATE_CROP.ERROR': NoParams;
|
|
41
|
+
'SMART.MEDIA_STORE.CREATE_CROP.DELETE_HINT': NoParams;
|
|
42
|
+
'SMART.MEDIA_STORE.CREATE_CROP.DELETE_CONFIRM.TITLE': NoParams;
|
|
43
|
+
'SMART.MEDIA_STORE.CREATE_CROP.DELETE_CONFIRM.BUTTON': NoParams;
|
|
31
44
|
};
|
package/MediaStore/styled.d.ts
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
export declare const Grid: import("styled-components").StyledComponent<"div", any, {}, never>;
|
|
2
2
|
export declare const Tile: import("styled-components").StyledComponent<"div", any, {}, never>;
|
|
3
3
|
export declare const TileImage: import("styled-components").StyledComponent<"img", any, {}, never>;
|
|
4
|
-
export declare const
|
|
4
|
+
export declare const CropBadge: import("styled-components").StyledComponent<"span", any, {}, never>;
|
|
5
5
|
export declare const TileActions: import("styled-components").StyledComponent<"div", any, {}, never>;
|
|
6
6
|
export declare const EmptyState: import("styled-components").StyledComponent<"div", any, {}, never>;
|
|
7
7
|
export declare const Dropzone: import("styled-components").StyledComponent<"button", any, {
|
|
8
8
|
$dragOver?: boolean;
|
|
9
9
|
}, never>;
|
|
10
10
|
export declare const HiddenInput: import("styled-components").StyledComponent<"input", any, {}, never>;
|
|
11
|
+
export declare const UsageLine: import("styled-components").StyledComponent<"div", any, {}, never>;
|
|
11
12
|
export declare const PreviewStage: import("styled-components").StyledComponent<"div", any, {}, never>;
|
|
12
13
|
export declare const PreviewFrame: import("styled-components").StyledComponent<"div", any, {
|
|
13
14
|
$ratio: number;
|
|
@@ -22,6 +23,7 @@ export declare const Segment: import("styled-components").StyledComponent<"div",
|
|
|
22
23
|
export declare const SegmentItem: import("styled-components").StyledComponent<"button", any, {
|
|
23
24
|
$active?: boolean;
|
|
24
25
|
}, never>;
|
|
25
|
-
export declare const
|
|
26
|
-
export declare const
|
|
27
|
-
export declare const
|
|
26
|
+
export declare const CropStrip: import("styled-components").StyledComponent<"div", any, {}, never>;
|
|
27
|
+
export declare const CropThumb: import("styled-components").StyledComponent<"div", any, {}, never>;
|
|
28
|
+
export declare const CropThumbDelete: import("styled-components").StyledComponent<"div", any, {}, never>;
|
|
29
|
+
export declare const CropThumbCaption: import("styled-components").StyledComponent<"div", any, {}, never>;
|
package/MediaStore/styled.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import styled from 'styled-components';
|
|
2
|
-
import { Color, FontWeight } from '@pushwoosh/kit-constants';
|
|
2
|
+
import { Color, FontSize, FontWeight } from '@pushwoosh/kit-constants';
|
|
3
3
|
export const Grid = styled.div.withConfig({
|
|
4
4
|
displayName: "Grid",
|
|
5
5
|
componentId: "sc-1d9kmgw-0"
|
|
@@ -12,8 +12,8 @@ export const TileImage = styled.img.withConfig({
|
|
|
12
12
|
displayName: "TileImage",
|
|
13
13
|
componentId: "sc-1d9kmgw-2"
|
|
14
14
|
})(["width:100%;aspect-ratio:1;object-fit:cover;display:block;"]);
|
|
15
|
-
export const
|
|
16
|
-
displayName: "
|
|
15
|
+
export const CropBadge = styled.span.withConfig({
|
|
16
|
+
displayName: "CropBadge",
|
|
17
17
|
componentId: "sc-1d9kmgw-3"
|
|
18
18
|
})(["position:absolute;top:6px;right:6px;font-size:10px;font-weight:", ";line-height:1;color:", ";background:", ";border-radius:20px;padding:3px 7px;pointer-events:none;"], FontWeight.BOLD, Color.CLEAR, Color.PRIMARY);
|
|
19
19
|
export const TileActions = styled.div.withConfig({
|
|
@@ -36,32 +36,36 @@ export const HiddenInput = styled.input.withConfig({
|
|
|
36
36
|
displayName: "HiddenInput",
|
|
37
37
|
componentId: "sc-1d9kmgw-7"
|
|
38
38
|
})(["display:none;"]);
|
|
39
|
-
|
|
39
|
+
export const UsageLine = styled.div.withConfig({
|
|
40
|
+
displayName: "UsageLine",
|
|
41
|
+
componentId: "sc-1d9kmgw-8"
|
|
42
|
+
})(["margin-bottom:8px;text-align:right;font-size:", ";color:", ";"], FontSize.SMALL, Color.LOCKED);
|
|
43
|
+
/* ---------- CreateCropDrawer ---------- */
|
|
40
44
|
export const PreviewStage = styled.div.withConfig({
|
|
41
45
|
displayName: "PreviewStage",
|
|
42
|
-
componentId: "sc-1d9kmgw-
|
|
46
|
+
componentId: "sc-1d9kmgw-9"
|
|
43
47
|
})(["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);
|
|
44
48
|
export const PreviewFrame = styled.div.withConfig({
|
|
45
49
|
displayName: "PreviewFrame",
|
|
46
|
-
componentId: "sc-1d9kmgw-
|
|
50
|
+
componentId: "sc-1d9kmgw-10"
|
|
47
51
|
})(["aspect-ratio:", ";max-width:100%;max-height:336px;overflow:hidden;border-radius:4px;"], ({
|
|
48
52
|
$ratio
|
|
49
53
|
}) => $ratio);
|
|
50
54
|
export const PreviewImage = styled.img.withConfig({
|
|
51
55
|
displayName: "PreviewImage",
|
|
52
|
-
componentId: "sc-1d9kmgw-
|
|
56
|
+
componentId: "sc-1d9kmgw-11"
|
|
53
57
|
})(["width:100%;height:100%;object-fit:cover;display:block;"]);
|
|
54
58
|
export const FieldLabel = styled.div.withConfig({
|
|
55
59
|
displayName: "FieldLabel",
|
|
56
|
-
componentId: "sc-1d9kmgw-
|
|
60
|
+
componentId: "sc-1d9kmgw-12"
|
|
57
61
|
})(["font-size:11px;font-weight:600;text-transform:uppercase;letter-spacing:0.4px;color:", ";margin-bottom:6px;"], Color.LOCKED);
|
|
58
62
|
export const Chips = styled.div.withConfig({
|
|
59
63
|
displayName: "Chips",
|
|
60
|
-
componentId: "sc-1d9kmgw-
|
|
64
|
+
componentId: "sc-1d9kmgw-13"
|
|
61
65
|
})(["display:flex;flex-wrap:wrap;gap:7px;"]);
|
|
62
66
|
export const Chip = styled.button.withConfig({
|
|
63
67
|
displayName: "Chip",
|
|
64
|
-
componentId: "sc-1d9kmgw-
|
|
68
|
+
componentId: "sc-1d9kmgw-14"
|
|
65
69
|
})(["font:inherit;font-size:13px;cursor:pointer;padding:6px 12px;border-radius:20px;border:1px solid ", ";background:", ";color:", ";transition:background 0.12s,border-color 0.12s;"], ({
|
|
66
70
|
$active
|
|
67
71
|
}) => $active ? Color.PRIMARY : Color.DIVIDER, ({
|
|
@@ -71,25 +75,29 @@ export const Chip = styled.button.withConfig({
|
|
|
71
75
|
}) => $active ? Color.CLEAR : Color.MAIN);
|
|
72
76
|
export const Segment = styled.div.withConfig({
|
|
73
77
|
displayName: "Segment",
|
|
74
|
-
componentId: "sc-1d9kmgw-
|
|
78
|
+
componentId: "sc-1d9kmgw-15"
|
|
75
79
|
})(["display:inline-flex;border:1px solid ", ";border-radius:6px;overflow:hidden;"], Color.DIVIDER);
|
|
76
80
|
export const SegmentItem = styled.button.withConfig({
|
|
77
81
|
displayName: "SegmentItem",
|
|
78
|
-
componentId: "sc-1d9kmgw-
|
|
82
|
+
componentId: "sc-1d9kmgw-16"
|
|
79
83
|
})(["font:inherit;font-size:13px;cursor:pointer;padding:7px 14px;border:none;background:", ";color:", ";& + &{border-left:1px solid ", ";}"], ({
|
|
80
84
|
$active
|
|
81
85
|
}) => $active ? Color.PRIMARY : Color.CLEAR, ({
|
|
82
86
|
$active
|
|
83
87
|
}) => $active ? Color.CLEAR : Color.MAIN, Color.DIVIDER);
|
|
84
|
-
export const
|
|
85
|
-
displayName: "
|
|
86
|
-
componentId: "sc-1d9kmgw-16"
|
|
87
|
-
})(["display:flex;gap:10px;flex-wrap:wrap;align-items:flex-start;"]);
|
|
88
|
-
export const VariantThumb = styled.div.withConfig({
|
|
89
|
-
displayName: "VariantThumb",
|
|
88
|
+
export const CropStrip = styled.div.withConfig({
|
|
89
|
+
displayName: "CropStrip",
|
|
90
90
|
componentId: "sc-1d9kmgw-17"
|
|
91
|
-
})(["
|
|
92
|
-
export const
|
|
93
|
-
displayName: "
|
|
91
|
+
})(["display:flex;gap:10px;flex-wrap:wrap;align-items:flex-start;"]);
|
|
92
|
+
export const CropThumb = styled.div.withConfig({
|
|
93
|
+
displayName: "CropThumb",
|
|
94
94
|
componentId: "sc-1d9kmgw-18"
|
|
95
|
+
})(["position:relative;width:88px;border:1px solid ", ";border-radius:6px;overflow:hidden;background:", ";"], Color.DIVIDER, Color.SOFT_LIGHT);
|
|
96
|
+
export const CropThumbDelete = styled.div.withConfig({
|
|
97
|
+
displayName: "CropThumbDelete",
|
|
98
|
+
componentId: "sc-1d9kmgw-19"
|
|
99
|
+
})(["position:absolute;top:4px;right:4px;opacity:0;transition:opacity 0.15s;", ":hover &{opacity:1;}@media (hover:none){opacity:1;}"], CropThumb);
|
|
100
|
+
export const CropThumbCaption = styled.div.withConfig({
|
|
101
|
+
displayName: "CropThumbCaption",
|
|
102
|
+
componentId: "sc-1d9kmgw-20"
|
|
95
103
|
})(["font-size:10px;text-align:center;padding:3px 2px;color:", ";"], Color.LOCKED);
|
package/MediaStore/types.d.ts
CHANGED
|
@@ -7,7 +7,7 @@ export type MediaStoreProps = {
|
|
|
7
7
|
perPage: number;
|
|
8
8
|
/** Called when the user requests a different page. */
|
|
9
9
|
onPageChange: (page: number) => void;
|
|
10
|
-
/** Called with the chosen file URL (original or
|
|
10
|
+
/** Called with the chosen file URL (original or crop). */
|
|
11
11
|
onSelect: (url: string) => void;
|
|
12
12
|
};
|
|
13
13
|
export type MediaStorePaginatedProps = {
|
|
@@ -17,6 +17,6 @@ export type MediaStorePaginatedProps = {
|
|
|
17
17
|
initialPage?: number;
|
|
18
18
|
/** Page size. Defaults to 30. */
|
|
19
19
|
perPage?: number;
|
|
20
|
-
/** Called with the chosen file URL (original or
|
|
20
|
+
/** Called with the chosen file URL (original or crop). */
|
|
21
21
|
onSelect: (url: string) => void;
|
|
22
22
|
};
|
package/package.json
CHANGED
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
"@pushwoosh/kit-helpers": "^4.9.1",
|
|
27
27
|
"@pushwoosh/kit-icons": "^2.1.6",
|
|
28
28
|
"@pushwoosh/kit-typography": "^1.7.3",
|
|
29
|
-
"@pushwoosh/rpc-v2-http-api-data": "^0.1.
|
|
29
|
+
"@pushwoosh/rpc-v2-http-api-data": "^0.1.980",
|
|
30
30
|
"@pushwoosh/spa-context": "^1.1.2",
|
|
31
31
|
"idb-keyval": "^6.2.2",
|
|
32
32
|
"react-hook-form": "^7.68.0",
|
|
@@ -49,5 +49,5 @@
|
|
|
49
49
|
"react": "^16.14.0 || ^18.0.0",
|
|
50
50
|
"react-dom": "^16.14.0 || ^18.0.0"
|
|
51
51
|
},
|
|
52
|
-
"version": "1.1.
|
|
52
|
+
"version": "1.1.3"
|
|
53
53
|
}
|
|
@@ -1,12 +0,0 @@
|
|
|
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 {};
|