@pushwoosh/smart-components-kit 1.1.2 → 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 +1 -0
- package/MediaStore/CreateCropDrawer.js +43 -4
- package/MediaStore/MediaStore.js +78 -13
- package/MediaStore/hooks.d.ts +7 -0
- package/MediaStore/hooks.js +42 -0
- package/MediaStore/mediaFile.utils.d.ts +2 -0
- package/MediaStore/mediaFile.utils.js +11 -0
- package/MediaStore/polyglot/messages_en.js +11 -1
- package/MediaStore/polyglot/messages_ru.js +11 -1
- package/MediaStore/polyglot/types.d.ts +13 -0
- package/MediaStore/styled.d.ts +2 -0
- package/MediaStore/styled.js +21 -13
- package/package.json +2 -2
|
@@ -7,6 +7,7 @@ type CreateCropDrawerProps = {
|
|
|
7
7
|
existingCrops: MediaFile[];
|
|
8
8
|
onClose: () => void;
|
|
9
9
|
onCreated: (parentUuid: string, crop: MediaFile) => void;
|
|
10
|
+
onDeleted: (parentUuid: string, cropUuid: string) => void;
|
|
10
11
|
};
|
|
11
12
|
export declare const CreateCropDrawer: FC<CreateCropDrawerProps>;
|
|
12
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, CropStrip, CropThumb, CropThumbCaption, FieldLabel, PreviewFrame, PreviewImage, PreviewStage, Segment, SegmentItem, TileImage } from './styled';
|
|
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',
|
|
@@ -54,7 +55,8 @@ export const CreateCropDrawer = ({
|
|
|
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 CreateCropDrawer = ({
|
|
|
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.
|
|
@@ -128,6 +132,17 @@ export const CreateCropDrawer = ({
|
|
|
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,
|
|
@@ -253,6 +268,19 @@ export const CreateCropDrawer = ({
|
|
|
253
268
|
children: [_jsx(TileImage, {
|
|
254
269
|
src: getThumbUrl(crop),
|
|
255
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
|
+
})
|
|
256
284
|
}), _jsx(CropThumbCaption, {
|
|
257
285
|
children: getRatioLabel(crop) || crop.filename
|
|
258
286
|
})]
|
|
@@ -260,6 +288,17 @@ export const CreateCropDrawer = ({
|
|
|
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
|
};
|
package/MediaStore/MediaStore.js
CHANGED
|
@@ -1,14 +1,15 @@
|
|
|
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
8
|
import { CreateCropDrawer } from './CreateCropDrawer';
|
|
9
|
-
import {
|
|
9
|
+
import { useDeleteMediaFile } from './hooks';
|
|
10
|
+
import { formatBytes, getRatioLabel, getThumbUrl } from './mediaFile.utils';
|
|
10
11
|
import { useLang } from './polyglot';
|
|
11
|
-
import { CropBadge, Dropzone, EmptyState, Grid, HiddenInput, Tile, TileActions, TileImage } from './styled';
|
|
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)
|
|
@@ -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
44
|
const [cropsByUuid, setCropsByUuid] = useState({});
|
|
43
|
-
// Bumped to force a reload of the current page
|
|
44
|
-
// the page prop
|
|
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
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({
|
|
@@ -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({
|
|
@@ -157,10 +169,37 @@ export const MediaStore = ({
|
|
|
157
169
|
...prev,
|
|
158
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,
|
|
@@ -253,6 +292,17 @@ export const MediaStore = ({
|
|
|
253
292
|
message: 'SMART.MEDIA_STORE.CREATE_CROP_HINT'
|
|
254
293
|
}),
|
|
255
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'
|
|
304
|
+
}),
|
|
305
|
+
onClick: () => setPendingDelete(file)
|
|
256
306
|
})]
|
|
257
307
|
})]
|
|
258
308
|
}, file.uuid);
|
|
@@ -292,7 +342,22 @@ export const MediaStore = ({
|
|
|
292
342
|
isOpen: createCropFor !== null,
|
|
293
343
|
existingCrops: createCropFor ? cropsByUuid[createCropFor.uuid] ?? [] : [],
|
|
294
344
|
onClose: () => setCreateCropFor(null),
|
|
295
|
-
onCreated: handleCropCreated
|
|
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
|
+
}
|
|
@@ -3,3 +3,5 @@ import type { MediaFile } from '@pushwoosh/rpc-v2-http-api-data/media_files';
|
|
|
3
3
|
export declare const getThumbUrl: (file: MediaFile) => string;
|
|
4
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;
|
|
@@ -7,4 +7,15 @@ export const getThumbUrl = file => {
|
|
|
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,9 +9,16 @@ 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.USAGE': '{{count}} files · {{size}}',
|
|
12
13
|
'SMART.MEDIA_STORE.CROPS_BADGE': '{{count}} cr.',
|
|
13
14
|
'SMART.MEDIA_STORE.SELECT_ORIGINAL': 'Original',
|
|
14
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',
|
|
15
22
|
'SMART.MEDIA_STORE.CREATE_CROP.TITLE': 'Create crop',
|
|
16
23
|
'SMART.MEDIA_STORE.CREATE_CROP.CLOSE': 'Close',
|
|
17
24
|
'SMART.MEDIA_STORE.CREATE_CROP.RATIO_LABEL': 'Aspect ratio',
|
|
@@ -24,5 +31,8 @@ export const messages = {
|
|
|
24
31
|
'SMART.MEDIA_STORE.CREATE_CROP.EXISTING_LABEL': 'Existing crops',
|
|
25
32
|
'SMART.MEDIA_STORE.CREATE_CROP.NO_CROPS': 'No crops yet',
|
|
26
33
|
'SMART.MEDIA_STORE.CREATE_CROP.SUCCESS': 'Crop created',
|
|
27
|
-
'SMART.MEDIA_STORE.CREATE_CROP.ERROR': 'Failed to create crop'
|
|
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,9 +9,16 @@ 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.USAGE': '{{count}} файлов · {{size}}',
|
|
12
13
|
'SMART.MEDIA_STORE.CROPS_BADGE': '{{count}} кр.',
|
|
13
14
|
'SMART.MEDIA_STORE.SELECT_ORIGINAL': 'Оригинал',
|
|
14
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': 'Удалить',
|
|
15
22
|
'SMART.MEDIA_STORE.CREATE_CROP.TITLE': 'Создание кропа',
|
|
16
23
|
'SMART.MEDIA_STORE.CREATE_CROP.CLOSE': 'Закрыть',
|
|
17
24
|
'SMART.MEDIA_STORE.CREATE_CROP.RATIO_LABEL': 'Соотношение сторон',
|
|
@@ -24,5 +31,8 @@ export const messages = {
|
|
|
24
31
|
'SMART.MEDIA_STORE.CREATE_CROP.EXISTING_LABEL': 'Созданные кропы',
|
|
25
32
|
'SMART.MEDIA_STORE.CREATE_CROP.NO_CROPS': 'Кропов пока нет',
|
|
26
33
|
'SMART.MEDIA_STORE.CREATE_CROP.SUCCESS': 'Кроп создан',
|
|
27
|
-
'SMART.MEDIA_STORE.CREATE_CROP.ERROR': 'Не удалось создать кроп'
|
|
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,11 +10,21 @@ 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.USAGE': {
|
|
14
|
+
count: number;
|
|
15
|
+
size: string;
|
|
16
|
+
};
|
|
13
17
|
'SMART.MEDIA_STORE.CROPS_BADGE': {
|
|
14
18
|
count: number;
|
|
15
19
|
};
|
|
16
20
|
'SMART.MEDIA_STORE.SELECT_ORIGINAL': NoParams;
|
|
17
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;
|
|
18
28
|
'SMART.MEDIA_STORE.CREATE_CROP.TITLE': NoParams;
|
|
19
29
|
'SMART.MEDIA_STORE.CREATE_CROP.CLOSE': NoParams;
|
|
20
30
|
'SMART.MEDIA_STORE.CREATE_CROP.RATIO_LABEL': NoParams;
|
|
@@ -28,4 +38,7 @@ export type MediaStoreMessages = {
|
|
|
28
38
|
'SMART.MEDIA_STORE.CREATE_CROP.NO_CROPS': NoParams;
|
|
29
39
|
'SMART.MEDIA_STORE.CREATE_CROP.SUCCESS': NoParams;
|
|
30
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
|
@@ -8,6 +8,7 @@ export declare const Dropzone: import("styled-components").StyledComponent<"butt
|
|
|
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;
|
|
@@ -24,4 +25,5 @@ export declare const SegmentItem: import("styled-components").StyledComponent<"b
|
|
|
24
25
|
}, never>;
|
|
25
26
|
export declare const CropStrip: import("styled-components").StyledComponent<"div", any, {}, never>;
|
|
26
27
|
export declare const CropThumb: import("styled-components").StyledComponent<"div", any, {}, never>;
|
|
28
|
+
export declare const CropThumbDelete: import("styled-components").StyledComponent<"div", any, {}, never>;
|
|
27
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"
|
|
@@ -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
|
+
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);
|
|
39
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,11 +75,11 @@ 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, ({
|
|
@@ -83,13 +87,17 @@ export const SegmentItem = styled.button.withConfig({
|
|
|
83
87
|
}) => $active ? Color.CLEAR : Color.MAIN, Color.DIVIDER);
|
|
84
88
|
export const CropStrip = styled.div.withConfig({
|
|
85
89
|
displayName: "CropStrip",
|
|
86
|
-
componentId: "sc-1d9kmgw-
|
|
90
|
+
componentId: "sc-1d9kmgw-17"
|
|
87
91
|
})(["display:flex;gap:10px;flex-wrap:wrap;align-items:flex-start;"]);
|
|
88
92
|
export const CropThumb = styled.div.withConfig({
|
|
89
93
|
displayName: "CropThumb",
|
|
90
|
-
componentId: "sc-1d9kmgw-
|
|
91
|
-
})(["width:88px;border:1px solid ", ";border-radius:6px;overflow:hidden;background:", ";"], Color.DIVIDER, Color.SOFT_LIGHT);
|
|
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);
|
|
92
100
|
export const CropThumbCaption = styled.div.withConfig({
|
|
93
101
|
displayName: "CropThumbCaption",
|
|
94
|
-
componentId: "sc-1d9kmgw-
|
|
102
|
+
componentId: "sc-1d9kmgw-20"
|
|
95
103
|
})(["font-size:10px;text-align:center;padding:3px 2px;color:", ";"], Color.LOCKED);
|
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
|
}
|