@pushwoosh/smart-components-kit 1.4.0 → 1.5.1

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.
@@ -0,0 +1,14 @@
1
+ import { type FC } from 'react';
2
+ import type { GetCanvaStatusResponse } from '@pushwoosh/rpc-v2-http-api-data/canva';
3
+ import type { MediaFile } from '@pushwoosh/rpc-v2-http-api-data/media_files';
4
+ type CanvaImportDrawerProps = {
5
+ application: string;
6
+ isOpen: boolean;
7
+ status: GetCanvaStatusResponse;
8
+ onStatusChange: () => void;
9
+ onClose: () => void;
10
+ onImported: (files: MediaFile[]) => void;
11
+ };
12
+ /** Drawer that connects the user's Canva and imports pages of a chosen design into the library. */
13
+ export declare const CanvaImportDrawer: FC<CanvaImportDrawerProps>;
14
+ export {};
@@ -0,0 +1,386 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useEffect, useRef, useState } from 'react';
3
+ import { Button, Chip, Drawer, DrawerBody, DrawerHeader, DrawerTitle, NativeInput, useToast } from '@pushwoosh/dumb-components';
4
+ import { FlexContainer, useAsync, Vertical } from '@pushwoosh/kit-helpers';
5
+ import { CloseIcon } from '@pushwoosh/kit-icons';
6
+ import { Text } from '@pushwoosh/kit-typography';
7
+ import { useSPAContext } from '@pushwoosh/spa-context';
8
+ import { ConsentClosedError, connectCanva, disconnectCanva, PopupBlockedError, UnsafeReturnPageError } from './canva';
9
+ import { togglePages } from './canvaPages';
10
+ import { useLang } from './polyglot';
11
+ import { Chips, DesignCaption, DesignTile, EmptyState, FieldLabel, Grid, Segment, SegmentItem, TileImage } from './styles';
12
+ const MAX_PAGES = 10;
13
+ const SEARCH_DELAY_MS = 400;
14
+ const POLL_INTERVAL_MS = 1500;
15
+ const IMPORT_TIMEOUT_MS = 10 * 60 * 1000;
16
+ const errorMessage = (error, fallback) => error instanceof Error && error.message ? error.message : fallback;
17
+ const sleep = ms => new Promise(resolve => {
18
+ window.setTimeout(resolve, ms);
19
+ });
20
+ /** Drawer that connects the user's Canva and imports pages of a chosen design into the library. */
21
+ export const CanvaImportDrawer = ({
22
+ application,
23
+ isOpen,
24
+ status,
25
+ onStatusChange,
26
+ onClose,
27
+ onImported
28
+ }) => {
29
+ const lang = useLang();
30
+ const {
31
+ rpcGateway
32
+ } = useSPAContext();
33
+ const {
34
+ openToast
35
+ } = useToast();
36
+ const [query, setQuery] = useState('');
37
+ const [debouncedQuery, setDebouncedQuery] = useState('');
38
+ const [designs, setDesigns] = useState([]);
39
+ const [continuation, setContinuation] = useState('');
40
+ const [selected, setSelected] = useState(null);
41
+ const [pages, setPages] = useState([1]);
42
+ const [format, setFormat] = useState('CANVA_IMPORT_FORMAT_PNG');
43
+ const [isConnecting, setIsConnecting] = useState(false);
44
+ const [isLoadingMore, setIsLoadingMore] = useState(false);
45
+ const [progress, setProgress] = useState(null);
46
+ const runRef = useRef(0);
47
+ const queryRef = useRef('');
48
+ const isOpenRef = useRef(isOpen);
49
+ isOpenRef.current = isOpen;
50
+ queryRef.current = debouncedQuery;
51
+ const isReady = status.connected && status.tokenValid;
52
+ useEffect(() => {
53
+ const timer = window.setTimeout(() => setDebouncedQuery(query.trim()), SEARCH_DELAY_MS);
54
+ return () => window.clearTimeout(timer);
55
+ }, [query]);
56
+ const designsAsync = useAsync(async () => {
57
+ if (!isOpen || !isReady) return null;
58
+ return rpcGateway.canva.ListDesigns({
59
+ query: debouncedQuery
60
+ });
61
+ }, [rpcGateway, isOpen, isReady, debouncedQuery]);
62
+ useEffect(() => {
63
+ setDesigns(designsAsync.value?.designs ?? []);
64
+ setContinuation(designsAsync.value?.continuation ?? '');
65
+ }, [designsAsync.value]);
66
+ useEffect(() => {
67
+ if (designsAsync.error) {
68
+ openToast({
69
+ title: errorMessage(designsAsync.error, lang({
70
+ message: 'SMART.MEDIA_STORAGE.CANVA.ERROR'
71
+ })),
72
+ type: 'error'
73
+ });
74
+ }
75
+ }, [designsAsync.error, openToast, lang]);
76
+ useEffect(() => () => {
77
+ runRef.current += 1;
78
+ }, []);
79
+ const loadMore = async () => {
80
+ if (isLoadingMore) return;
81
+ const queryAtStart = debouncedQuery;
82
+ setIsLoadingMore(true);
83
+ try {
84
+ const page = await rpcGateway.canva.ListDesigns({
85
+ query: queryAtStart,
86
+ continuation
87
+ });
88
+ if (queryRef.current !== queryAtStart) return;
89
+ setDesigns(prev => {
90
+ const seen = new Set(prev.map(design => design.id));
91
+ return [...prev, ...(page.designs ?? []).filter(design => !seen.has(design.id))];
92
+ });
93
+ setContinuation(page.continuation ?? '');
94
+ } catch (error) {
95
+ openToast({
96
+ title: errorMessage(error, lang({
97
+ message: 'SMART.MEDIA_STORAGE.CANVA.ERROR'
98
+ })),
99
+ type: 'error'
100
+ });
101
+ } finally {
102
+ setIsLoadingMore(false);
103
+ }
104
+ };
105
+ const handleConnect = async () => {
106
+ setIsConnecting(true);
107
+ try {
108
+ await connectCanva(rpcGateway);
109
+ onStatusChange();
110
+ } catch (error) {
111
+ if (!(error instanceof ConsentClosedError)) {
112
+ const title = error instanceof PopupBlockedError ? lang({
113
+ message: 'SMART.MEDIA_STORAGE.CANVA.POPUP_BLOCKED'
114
+ }) : lang({
115
+ message: 'SMART.MEDIA_STORAGE.CANVA.CONNECT_FAILED'
116
+ });
117
+ if (error instanceof UnsafeReturnPageError) {
118
+ console.error('Canva connect refused: /robots.txt on this origin is not served as plain text');
119
+ }
120
+ openToast({
121
+ title,
122
+ type: 'error'
123
+ });
124
+ }
125
+ } finally {
126
+ setIsConnecting(false);
127
+ }
128
+ };
129
+ const handleDisconnect = async () => {
130
+ try {
131
+ await disconnectCanva(rpcGateway);
132
+ onStatusChange();
133
+ } catch (error) {
134
+ openToast({
135
+ title: errorMessage(error, lang({
136
+ message: 'SMART.MEDIA_STORAGE.CANVA.ERROR'
137
+ })),
138
+ type: 'error'
139
+ });
140
+ }
141
+ };
142
+ const selectDesign = design => {
143
+ setSelected(design);
144
+ setPages([1]);
145
+ };
146
+ const togglePage = page => {
147
+ setPages(prev => togglePages(prev, page, MAX_PAGES));
148
+ };
149
+ const handleImport = async () => {
150
+ if (!selected) return;
151
+ runRef.current += 1;
152
+ const run = runRef.current;
153
+ setProgress(lang({
154
+ message: 'SMART.MEDIA_STORAGE.CANVA.EXPORTING'
155
+ }));
156
+ try {
157
+ const {
158
+ importId
159
+ } = await rpcGateway.canva.StartImport({
160
+ application,
161
+ designId: selected.id,
162
+ pages,
163
+ format
164
+ });
165
+ const startedAt = Date.now();
166
+ for (;;) {
167
+ await sleep(POLL_INTERVAL_MS);
168
+ if (runRef.current !== run) return;
169
+ if (Date.now() - startedAt > IMPORT_TIMEOUT_MS) {
170
+ throw new Error(lang({
171
+ message: 'SMART.MEDIA_STORAGE.CANVA.ERROR'
172
+ }));
173
+ }
174
+ const state = await rpcGateway.canva.GetImport({
175
+ importId
176
+ });
177
+ if (runRef.current !== run) return;
178
+ if (state.state === 'CANVA_IMPORT_STATE_FAILED') {
179
+ throw new Error(state.error);
180
+ }
181
+ if (state.state === 'CANVA_IMPORT_STATE_DONE') {
182
+ openToast({
183
+ title: lang({
184
+ message: 'SMART.MEDIA_STORAGE.CANVA.SUCCESS'
185
+ }),
186
+ type: 'success'
187
+ });
188
+ onImported(state.files ?? []);
189
+ if (isOpenRef.current) onClose();
190
+ return;
191
+ }
192
+ if (state.state === 'CANVA_IMPORT_STATE_UPLOADING') {
193
+ setProgress(lang({
194
+ message: 'SMART.MEDIA_STORAGE.CANVA.IMPORTING',
195
+ done: state.pagesDone,
196
+ total: state.pagesTotal
197
+ }));
198
+ }
199
+ }
200
+ } catch (error) {
201
+ if (runRef.current === run) {
202
+ openToast({
203
+ title: errorMessage(error, lang({
204
+ message: 'SMART.MEDIA_STORAGE.CANVA.ERROR'
205
+ })),
206
+ type: 'error'
207
+ });
208
+ }
209
+ } finally {
210
+ if (runRef.current === run) setProgress(null);
211
+ }
212
+ };
213
+ const pageCount = selected ? Math.max(selected.pageCount, 1) : 0;
214
+ return _jsxs(Drawer, {
215
+ size: "medium",
216
+ isOpen: isOpen,
217
+ onClose: onClose,
218
+ children: [_jsx(DrawerHeader, {
219
+ children: _jsxs(DrawerTitle, {
220
+ children: [_jsx("span", {
221
+ children: lang({
222
+ message: 'SMART.MEDIA_STORAGE.CANVA.TITLE'
223
+ })
224
+ }), _jsx(Button, {
225
+ view: "ghost",
226
+ color: "secondary",
227
+ size: "compact",
228
+ icon: _jsx(CloseIcon, {
229
+ size: "small"
230
+ }),
231
+ "aria-label": lang({
232
+ message: 'SMART.MEDIA_STORAGE.CANVA.CLOSE'
233
+ }),
234
+ onClick: onClose
235
+ })]
236
+ })
237
+ }), _jsxs(DrawerBody, {
238
+ children: [!isReady && _jsxs(Vertical, {
239
+ "$gap": 12,
240
+ children: [_jsx(Text, {
241
+ children: status.connected ? lang({
242
+ message: 'SMART.MEDIA_STORAGE.CANVA.RECONNECT_HINT'
243
+ }) : lang({
244
+ message: 'SMART.MEDIA_STORAGE.CANVA.CONNECT_HINT'
245
+ })
246
+ }), _jsx("div", {
247
+ children: _jsx(Button, {
248
+ color: "primary",
249
+ isLoading: isConnecting,
250
+ isDisabled: isConnecting,
251
+ onClick: () => void handleConnect(),
252
+ children: lang({
253
+ message: 'SMART.MEDIA_STORAGE.CANVA.CONNECT'
254
+ })
255
+ })
256
+ })]
257
+ }), isReady && _jsxs(Vertical, {
258
+ "$gap": 16,
259
+ children: [_jsxs(FlexContainer, {
260
+ "$alignItems": "center",
261
+ "$gap": 8,
262
+ children: [_jsx(Text, {
263
+ children: lang({
264
+ message: 'SMART.MEDIA_STORAGE.CANVA.CONNECTED_AS',
265
+ name: status.identity
266
+ })
267
+ }), _jsx(Button, {
268
+ view: "ghost",
269
+ color: "secondary",
270
+ size: "compact",
271
+ onClick: () => void handleDisconnect(),
272
+ children: lang({
273
+ message: 'SMART.MEDIA_STORAGE.CANVA.DISCONNECT'
274
+ })
275
+ })]
276
+ }), _jsx(NativeInput, {
277
+ value: query,
278
+ placeholder: lang({
279
+ message: 'SMART.MEDIA_STORAGE.CANVA.SEARCH'
280
+ }),
281
+ "aria-label": lang({
282
+ message: 'SMART.MEDIA_STORAGE.CANVA.SEARCH'
283
+ }),
284
+ onChange: event => setQuery(event.target.value)
285
+ }), !designsAsync.loading && designs.length === 0 && _jsx(EmptyState, {
286
+ children: lang({
287
+ message: 'SMART.MEDIA_STORAGE.CANVA.EMPTY'
288
+ })
289
+ }), _jsx(Grid, {
290
+ children: designs.map(design => _jsxs(DesignTile, {
291
+ type: "button",
292
+ "$selected": selected?.id === design.id,
293
+ disabled: progress !== null,
294
+ onClick: () => selectDesign(design),
295
+ children: [_jsx(TileImage, {
296
+ src: design.thumbnailUrl,
297
+ alt: "",
298
+ loading: "lazy"
299
+ }), _jsxs(DesignCaption, {
300
+ children: [_jsx("span", {
301
+ children: design.title || lang({
302
+ message: 'SMART.MEDIA_STORAGE.CANVA.UNTITLED'
303
+ })
304
+ }), _jsx("span", {
305
+ children: lang({
306
+ message: 'SMART.MEDIA_STORAGE.CANVA.PAGES',
307
+ count: Math.max(design.pageCount, 1)
308
+ })
309
+ })]
310
+ })]
311
+ }, design.id))
312
+ }), continuation && _jsx("div", {
313
+ children: _jsx(Button, {
314
+ view: "ghost",
315
+ color: "secondary",
316
+ isLoading: isLoadingMore,
317
+ isDisabled: isLoadingMore,
318
+ onClick: () => void loadMore(),
319
+ children: lang({
320
+ message: 'SMART.MEDIA_STORAGE.CANVA.LOAD_MORE'
321
+ })
322
+ })
323
+ }), selected && _jsxs(Vertical, {
324
+ "$gap": 12,
325
+ children: [pageCount > 1 && _jsxs("div", {
326
+ children: [_jsx(FieldLabel, {
327
+ children: lang({
328
+ message: 'SMART.MEDIA_STORAGE.CANVA.PAGES_LABEL',
329
+ max: MAX_PAGES
330
+ })
331
+ }), _jsx(Chips, {
332
+ children: Array.from({
333
+ length: pageCount
334
+ }, (_, index) => index + 1).map(page => _jsx(Chip, {
335
+ "$selected": pages.includes(page),
336
+ "aria-pressed": pages.includes(page),
337
+ onClick: () => togglePage(page),
338
+ children: page
339
+ }, page))
340
+ })]
341
+ }), _jsxs("div", {
342
+ children: [_jsx(FieldLabel, {
343
+ children: lang({
344
+ message: 'SMART.MEDIA_STORAGE.CANVA.FORMAT_LABEL'
345
+ })
346
+ }), _jsxs(Segment, {
347
+ role: "radiogroup",
348
+ "aria-label": lang({
349
+ message: 'SMART.MEDIA_STORAGE.CANVA.FORMAT_LABEL'
350
+ }),
351
+ children: [_jsx(SegmentItem, {
352
+ type: "button",
353
+ role: "radio",
354
+ "aria-checked": format === 'CANVA_IMPORT_FORMAT_PNG',
355
+ "$active": format === 'CANVA_IMPORT_FORMAT_PNG',
356
+ onClick: () => setFormat('CANVA_IMPORT_FORMAT_PNG'),
357
+ children: "PNG"
358
+ }), _jsx(SegmentItem, {
359
+ type: "button",
360
+ role: "radio",
361
+ "aria-checked": format === 'CANVA_IMPORT_FORMAT_JPG',
362
+ "$active": format === 'CANVA_IMPORT_FORMAT_JPG',
363
+ onClick: () => setFormat('CANVA_IMPORT_FORMAT_JPG'),
364
+ children: "JPG"
365
+ })]
366
+ })]
367
+ }), _jsxs(FlexContainer, {
368
+ "$alignItems": "center",
369
+ "$gap": 12,
370
+ children: [_jsx(Button, {
371
+ color: "primary",
372
+ isLoading: progress !== null,
373
+ isDisabled: progress !== null,
374
+ onClick: () => void handleImport(),
375
+ children: lang({
376
+ message: 'SMART.MEDIA_STORAGE.CANVA.IMPORT'
377
+ })
378
+ }), progress && _jsx(Text, {
379
+ children: progress
380
+ })]
381
+ })]
382
+ })]
383
+ })]
384
+ })]
385
+ });
386
+ };
@@ -5,11 +5,12 @@ import { FlexContainer, useAsync } from '@pushwoosh/kit-helpers';
5
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 { CanvaImportDrawer } from './CanvaImportDrawer';
8
9
  import { CreateCropDrawer } from './CreateCropDrawer';
9
10
  import { useDeleteMediaFile, useDeleteMediaFiles } from './hooks';
10
11
  import { formatBytes, getCropCaption, getSourceLabel, getThumbUrl, hasVersions } from './mediaFile.utils';
11
12
  import { Lang, useLang } from './polyglot';
12
- import { CountBadge, Dropzone, EmptyState, Grid, HiddenInput, SelectionBar, SelectionCount, SourceBadge, Tile, TileActions, TileBadges, TileCheckbox, TileImage, UsageLine, WarningBadge } from './styles';
13
+ import { CountBadge, Dropzone, DropzoneRow, EmptyState, Grid, HiddenInput, SelectionBar, SelectionCount, SourceBadge, Tile, TileActions, TileBadges, TileCheckbox, TileImage, UsageLine, WarningBadge } from './styles';
13
14
  const UPLOAD_PATH = '/api/media/upload';
14
15
  const ALLOWED_IMAGE_EXTENSIONS = ['.jpg', '.jpeg', '.png', '.gif', '.webp'];
15
16
  const isAllowedImage = file => {
@@ -80,6 +81,10 @@ export const MediaStorage = ({
80
81
  const usageAsync = useAsync(() => rpcGateway.mediaFiles.GetUsage({
81
82
  application
82
83
  }), [rpcGateway, application, reloadToken, usageToken]);
84
+ const [canvaToken, setCanvaToken] = useState(0);
85
+ const [isCanvaOpen, setIsCanvaOpen] = useState(false);
86
+ const canvaStatusAsync = useAsync(() => rpcGateway.canva.GetStatus({}), [rpcGateway, canvaToken]);
87
+ const canvaStatus = canvaStatusAsync.value;
83
88
  const {
84
89
  loading
85
90
  } = listAsync;
@@ -242,28 +247,36 @@ export const MediaStorage = ({
242
247
  const totalPages = Math.ceil(total / perPage);
243
248
  const showPagination = !loading && files.length > 0 && totalPages > 1;
244
249
  return _jsxs(_Fragment, {
245
- children: [_jsxs(Dropzone, {
246
- type: "button",
247
- "$dragOver": isDragOver,
248
- disabled: isUploading,
249
- onClick: () => uploadInputRef.current?.click(),
250
- onDragOver: event => {
251
- event.preventDefault();
252
- setIsDragOver(true);
253
- },
254
- onDragLeave: () => setIsDragOver(false),
255
- onDrop: event => {
256
- event.preventDefault();
257
- setIsDragOver(false);
258
- void uploadFiles(Array.from(event.dataTransfer.files));
259
- },
260
- children: [_jsx(UploadIcon, {
261
- size: "medium",
262
- view: "lined"
263
- }), isUploading ? lang({
264
- message: 'SMART.MEDIA_STORAGE.UPLOADING'
265
- }) : lang({
266
- message: 'SMART.MEDIA_STORAGE.DROPZONE_HINT'
250
+ children: [_jsxs(DropzoneRow, {
251
+ children: [_jsxs(Dropzone, {
252
+ type: "button",
253
+ "$dragOver": isDragOver,
254
+ disabled: isUploading,
255
+ onClick: () => uploadInputRef.current?.click(),
256
+ onDragOver: event => {
257
+ event.preventDefault();
258
+ setIsDragOver(true);
259
+ },
260
+ onDragLeave: () => setIsDragOver(false),
261
+ onDrop: event => {
262
+ event.preventDefault();
263
+ setIsDragOver(false);
264
+ void uploadFiles(Array.from(event.dataTransfer.files));
265
+ },
266
+ children: [_jsx(UploadIcon, {
267
+ size: "medium",
268
+ view: "lined"
269
+ }), isUploading ? lang({
270
+ message: 'SMART.MEDIA_STORAGE.UPLOADING'
271
+ }) : lang({
272
+ message: 'SMART.MEDIA_STORAGE.DROPZONE_HINT'
273
+ })]
274
+ }), canvaStatus?.available && _jsx(Button, {
275
+ color: "secondary",
276
+ onClick: () => setIsCanvaOpen(true),
277
+ children: lang({
278
+ message: 'SMART.MEDIA_STORAGE.CANVA.BUTTON'
279
+ })
267
280
  })]
268
281
  }), _jsx(HiddenInput, {
269
282
  ref: uploadInputRef,
@@ -438,6 +451,18 @@ export const MediaStorage = ({
438
451
  size: formatBytes(usageAsync.value.totalSize)
439
452
  })
440
453
  })]
454
+ }), canvaStatus?.available && _jsx(CanvaImportDrawer, {
455
+ application: application,
456
+ isOpen: isCanvaOpen,
457
+ status: canvaStatus,
458
+ onStatusChange: () => setCanvaToken(token => token + 1),
459
+ onClose: () => setIsCanvaOpen(false),
460
+ onImported: () => {
461
+ if (pageRef.current !== 0) {
462
+ onPageChange(0);
463
+ }
464
+ setReloadToken(token => token + 1);
465
+ }
441
466
  }), _jsx(CreateCropDrawer, {
442
467
  application: application,
443
468
  original: createCropFor,
@@ -0,0 +1,18 @@
1
+ import type { API as RpcGatewayAPI } from '@pushwoosh/rpc-v2-http-api-data';
2
+ /** Error raised when the browser refused to open the Canva consent window. */
3
+ export declare class PopupBlockedError extends Error {
4
+ }
5
+ /** Consent result read from the pop-up once it is back on our origin; null while it is still elsewhere. */
6
+ export declare const readConsentReturn: (location: Pick<Location, "origin" | "hash">, origin: string) => URLSearchParams | null;
7
+ /** Error raised when the user closed the consent pop-up without finishing. */
8
+ export declare class ConsentClosedError extends Error {
9
+ }
10
+ /** Error raised when the return page is not plain static text: the code must not land in an app. */
11
+ export declare class UnsafeReturnPageError extends Error {
12
+ }
13
+ /** True when the response is served as plain text, i.e. no app bundle runs on that page. */
14
+ export declare const isStaticText: (contentType: string | null) => boolean;
15
+ /** Connects the user's Canva in a pop-up that returns to a static text file on this origin. */
16
+ export declare const connectCanva: (rpcGateway: RpcGatewayAPI) => Promise<void>;
17
+ /** Revokes and forgets the signed-in user's Canva connection. */
18
+ export declare const disconnectCanva: (rpcGateway: RpcGatewayAPI) => Promise<unknown>;
@@ -0,0 +1,86 @@
1
+ const PROVIDER = 'canva';
2
+ const POLL_INTERVAL_MS = 500;
3
+ const CONNECT_TIMEOUT_MS = 10 * 60 * 1000;
4
+ /** Error raised when the browser refused to open the Canva consent window. */
5
+ export class PopupBlockedError extends Error {}
6
+ /** Consent result read from the pop-up once it is back on our origin; null while it is still elsewhere. */
7
+ export const readConsentReturn = (location, origin) => {
8
+ if (location.origin !== origin) return null;
9
+ const params = new URLSearchParams(location.hash.replace(/^#/, ''));
10
+ return params.get('oauth_provider') === PROVIDER ? params : null;
11
+ };
12
+ /** Error raised when the user closed the consent pop-up without finishing. */
13
+ export class ConsentClosedError extends Error {}
14
+ const waitForReturn = (popup, origin) => new Promise((resolve, reject) => {
15
+ const startedAt = Date.now();
16
+ const timer = window.setInterval(() => {
17
+ if (popup.closed) {
18
+ window.clearInterval(timer);
19
+ reject(new ConsentClosedError());
20
+ return;
21
+ }
22
+ if (Date.now() - startedAt > CONNECT_TIMEOUT_MS) {
23
+ window.clearInterval(timer);
24
+ popup.close();
25
+ reject(new Error('timeout'));
26
+ return;
27
+ }
28
+ let params = null;
29
+ try {
30
+ params = readConsentReturn(popup.location, origin);
31
+ } catch {
32
+ return;
33
+ }
34
+ if (!params) return;
35
+ window.clearInterval(timer);
36
+ popup.close();
37
+ resolve(params);
38
+ }, POLL_INTERVAL_MS);
39
+ });
40
+ /** Error raised when the return page is not plain static text: the code must not land in an app. */
41
+ export class UnsafeReturnPageError extends Error {}
42
+ /** True when the response is served as plain text, i.e. no app bundle runs on that page. */
43
+ export const isStaticText = contentType => (contentType ?? '').toLowerCase().startsWith('text/plain');
44
+ const ensureStaticReturn = async url => {
45
+ const response = await fetch(url, {
46
+ method: 'HEAD',
47
+ cache: 'no-store',
48
+ credentials: 'omit'
49
+ });
50
+ if (!response.ok || !isStaticText(response.headers.get('content-type'))) throw new UnsafeReturnPageError();
51
+ };
52
+ /** Connects the user's Canva in a pop-up that returns to a static text file on this origin. */
53
+ export const connectCanva = async rpcGateway => {
54
+ const {
55
+ origin
56
+ } = window.location;
57
+ const returnTo = `${origin}/robots.txt`;
58
+ const popup = window.open('', 'pushwoosh-canva-connect', 'width=640,height=760');
59
+ if (!popup) throw new PopupBlockedError();
60
+ try {
61
+ await ensureStaticReturn(returnTo);
62
+ const {
63
+ authUrl
64
+ } = await rpcGateway.integrations.GetOAuthAuthURL({
65
+ provider: PROVIDER,
66
+ returnTo
67
+ });
68
+ popup.location.href = authUrl;
69
+ } catch (error) {
70
+ popup.close();
71
+ throw error;
72
+ }
73
+ const params = await waitForReturn(popup, origin);
74
+ if (params.get('oauth_status') !== 'pending') {
75
+ throw new Error(params.get('oauth_error') || 'Canva consent was not given');
76
+ }
77
+ await rpcGateway.integrations.CompleteOAuthAuthorization({
78
+ provider: PROVIDER,
79
+ state: params.get('oauth_state') ?? '',
80
+ code: params.get('oauth_code') ?? ''
81
+ });
82
+ };
83
+ /** Revokes and forgets the signed-in user's Canva connection. */
84
+ export const disconnectCanva = rpcGateway => rpcGateway.integrations.DeleteOAuthIntegration({
85
+ provider: PROVIDER
86
+ });
@@ -0,0 +1,2 @@
1
+ /** Next page selection after a click: at least one page stays selected, at most `max`, in order. */
2
+ export declare const togglePages: (selected: number[], page: number, max: number) => number[];
@@ -0,0 +1,7 @@
1
+ /** Next page selection after a click: at least one page stays selected, at most `max`, in order. */
2
+ export const togglePages = (selected, page, max) => {
3
+ if (selected.includes(page)) {
4
+ return selected.length > 1 ? selected.filter(item => item !== page) : selected;
5
+ }
6
+ return selected.length < max ? [...selected, page].sort((a, b) => a - b) : selected;
7
+ };
@@ -1,3 +1,4 @@
1
1
  export { MediaStorage } from './MediaStorage';
2
2
  export { MediaStoragePaginated } from './MediaStoragePaginated';
3
3
  export type { MediaStorageProps, MediaStoragePaginatedProps } from './types';
4
+ export { connectCanva, ConsentClosedError, disconnectCanva, PopupBlockedError, UnsafeReturnPageError, } from './canva';
@@ -1,2 +1,3 @@
1
1
  export { MediaStorage } from './MediaStorage';
2
- export { MediaStoragePaginated } from './MediaStoragePaginated';
2
+ export { MediaStoragePaginated } from './MediaStoragePaginated';
3
+ export { connectCanva, ConsentClosedError, disconnectCanva, PopupBlockedError, UnsafeReturnPageError } from './canva';
@@ -47,5 +47,27 @@ export const messages = {
47
47
  'SMART.MEDIA_STORAGE.CREATE_CROP.ERROR': 'تعذّر إنشاء الاقتصاص',
48
48
  'SMART.MEDIA_STORAGE.CREATE_CROP.DELETE_HINT': 'حذف الاقتصاص',
49
49
  'SMART.MEDIA_STORAGE.CREATE_CROP.DELETE_CONFIRM.TITLE': 'حذف هذا الاقتصاص؟',
50
- 'SMART.MEDIA_STORAGE.CREATE_CROP.DELETE_CONFIRM.BUTTON': 'حذف'
50
+ 'SMART.MEDIA_STORAGE.CREATE_CROP.DELETE_CONFIRM.BUTTON': 'حذف',
51
+ 'SMART.MEDIA_STORAGE.CANVA.BUTTON': 'استيراد من Canva',
52
+ 'SMART.MEDIA_STORAGE.CANVA.TITLE': 'استيراد من Canva',
53
+ 'SMART.MEDIA_STORAGE.CANVA.CLOSE': 'إغلاق',
54
+ 'SMART.MEDIA_STORAGE.CANVA.CONNECT_HINT': 'اربط حساب Canva لاستيراد تصميماتك.',
55
+ 'SMART.MEDIA_STORAGE.CANVA.RECONNECT_HINT': 'لم يعد Canva يقبل هذا الاتصال. اربط الحساب مجددًا.',
56
+ 'SMART.MEDIA_STORAGE.CANVA.CONNECT': 'ربط Canva',
57
+ 'SMART.MEDIA_STORAGE.CANVA.CONNECTED_AS': 'متصل باسم {{name}}',
58
+ 'SMART.MEDIA_STORAGE.CANVA.DISCONNECT': 'قطع الاتصال',
59
+ 'SMART.MEDIA_STORAGE.CANVA.SEARCH': 'البحث في التصميمات',
60
+ 'SMART.MEDIA_STORAGE.CANVA.EMPTY': 'لم يتم العثور على تصميمات',
61
+ 'SMART.MEDIA_STORAGE.CANVA.UNTITLED': 'تصميم بلا عنوان',
62
+ 'SMART.MEDIA_STORAGE.CANVA.PAGES': '{{count}} ص',
63
+ 'SMART.MEDIA_STORAGE.CANVA.LOAD_MORE': 'تحميل المزيد',
64
+ 'SMART.MEDIA_STORAGE.CANVA.PAGES_LABEL': 'الصفحات (حتى {{max}})',
65
+ 'SMART.MEDIA_STORAGE.CANVA.FORMAT_LABEL': 'التنسيق',
66
+ 'SMART.MEDIA_STORAGE.CANVA.IMPORT': 'استيراد',
67
+ 'SMART.MEDIA_STORAGE.CANVA.IMPORTING': 'جارٍ استيراد {{done}} من {{total}}…',
68
+ 'SMART.MEDIA_STORAGE.CANVA.EXPORTING': 'يقوم Canva بتجهيز التصميم…',
69
+ 'SMART.MEDIA_STORAGE.CANVA.SUCCESS': 'تم الاستيراد من Canva',
70
+ 'SMART.MEDIA_STORAGE.CANVA.POPUP_BLOCKED': 'اسمح بالنوافذ المنبثقة لهذا الموقع لربط Canva',
71
+ 'SMART.MEDIA_STORAGE.CANVA.CONNECT_FAILED': 'تعذّر ربط Canva',
72
+ 'SMART.MEDIA_STORAGE.CANVA.ERROR': 'تعذّر الاستيراد من Canva'
51
73
  };
@@ -47,5 +47,27 @@ export const messages = {
47
47
  'SMART.MEDIA_STORAGE.CREATE_CROP.ERROR': 'ক্রপ তৈরি করা যায়নি',
48
48
  'SMART.MEDIA_STORAGE.CREATE_CROP.DELETE_HINT': 'ক্রপ মুছুন',
49
49
  'SMART.MEDIA_STORAGE.CREATE_CROP.DELETE_CONFIRM.TITLE': 'এই ক্রপটি মুছবেন?',
50
- 'SMART.MEDIA_STORAGE.CREATE_CROP.DELETE_CONFIRM.BUTTON': 'মুছুন'
50
+ 'SMART.MEDIA_STORAGE.CREATE_CROP.DELETE_CONFIRM.BUTTON': 'মুছুন',
51
+ 'SMART.MEDIA_STORAGE.CANVA.BUTTON': 'Canva থেকে ইমপোর্ট',
52
+ 'SMART.MEDIA_STORAGE.CANVA.TITLE': 'Canva থেকে ইমপোর্ট',
53
+ 'SMART.MEDIA_STORAGE.CANVA.CLOSE': 'বন্ধ করুন',
54
+ 'SMART.MEDIA_STORAGE.CANVA.CONNECT_HINT': 'আপনার ডিজাইন ইমপোর্ট করতে Canva অ্যাকাউন্ট সংযুক্ত করুন।',
55
+ 'SMART.MEDIA_STORAGE.CANVA.RECONNECT_HINT': 'Canva আর এই সংযোগ গ্রহণ করছে না। আবার সংযুক্ত করুন।',
56
+ 'SMART.MEDIA_STORAGE.CANVA.CONNECT': 'Canva সংযুক্ত করুন',
57
+ 'SMART.MEDIA_STORAGE.CANVA.CONNECTED_AS': '{{name}} হিসেবে সংযুক্ত',
58
+ 'SMART.MEDIA_STORAGE.CANVA.DISCONNECT': 'সংযোগ বিচ্ছিন্ন করুন',
59
+ 'SMART.MEDIA_STORAGE.CANVA.SEARCH': 'ডিজাইন খুঁজুন',
60
+ 'SMART.MEDIA_STORAGE.CANVA.EMPTY': 'কোনো ডিজাইন পাওয়া যায়নি',
61
+ 'SMART.MEDIA_STORAGE.CANVA.UNTITLED': 'শিরোনামহীন ডিজাইন',
62
+ 'SMART.MEDIA_STORAGE.CANVA.PAGES': '{{count}} পৃ.',
63
+ 'SMART.MEDIA_STORAGE.CANVA.LOAD_MORE': 'আরও লোড করুন',
64
+ 'SMART.MEDIA_STORAGE.CANVA.PAGES_LABEL': 'পৃষ্ঠা (সর্বোচ্চ {{max}})',
65
+ 'SMART.MEDIA_STORAGE.CANVA.FORMAT_LABEL': 'ফরম্যাট',
66
+ 'SMART.MEDIA_STORAGE.CANVA.IMPORT': 'ইমপোর্ট',
67
+ 'SMART.MEDIA_STORAGE.CANVA.IMPORTING': '{{total}}-এর মধ্যে {{done}} ইমপোর্ট হচ্ছে…',
68
+ 'SMART.MEDIA_STORAGE.CANVA.EXPORTING': 'Canva ডিজাইন প্রস্তুত করছে…',
69
+ 'SMART.MEDIA_STORAGE.CANVA.SUCCESS': 'Canva থেকে ইমপোর্ট হয়েছে',
70
+ 'SMART.MEDIA_STORAGE.CANVA.POPUP_BLOCKED': 'Canva সংযুক্ত করতে এই সাইটের জন্য পপ-আপ অনুমতি দিন',
71
+ 'SMART.MEDIA_STORAGE.CANVA.CONNECT_FAILED': 'Canva সংযুক্ত করা যায়নি',
72
+ 'SMART.MEDIA_STORAGE.CANVA.ERROR': 'Canva থেকে ইমপোর্ট ব্যর্থ হয়েছে'
51
73
  };
@@ -47,5 +47,27 @@ export const messages = {
47
47
  'SMART.MEDIA_STORAGE.CREATE_CROP.ERROR': 'Zuschnitt konnte nicht erstellt werden',
48
48
  'SMART.MEDIA_STORAGE.CREATE_CROP.DELETE_HINT': 'Zuschnitt löschen',
49
49
  'SMART.MEDIA_STORAGE.CREATE_CROP.DELETE_CONFIRM.TITLE': 'Diesen Zuschnitt löschen?',
50
- 'SMART.MEDIA_STORAGE.CREATE_CROP.DELETE_CONFIRM.BUTTON': 'Löschen'
50
+ 'SMART.MEDIA_STORAGE.CREATE_CROP.DELETE_CONFIRM.BUTTON': 'Löschen',
51
+ 'SMART.MEDIA_STORAGE.CANVA.BUTTON': 'Aus Canva importieren',
52
+ 'SMART.MEDIA_STORAGE.CANVA.TITLE': 'Aus Canva importieren',
53
+ 'SMART.MEDIA_STORAGE.CANVA.CLOSE': 'Schließen',
54
+ 'SMART.MEDIA_STORAGE.CANVA.CONNECT_HINT': 'Verbinden Sie Ihr Canva-Konto, um Ihre Designs zu importieren.',
55
+ 'SMART.MEDIA_STORAGE.CANVA.RECONNECT_HINT': 'Canva akzeptiert diese Verbindung nicht mehr. Verbinden Sie erneut.',
56
+ 'SMART.MEDIA_STORAGE.CANVA.CONNECT': 'Canva verbinden',
57
+ 'SMART.MEDIA_STORAGE.CANVA.CONNECTED_AS': 'Verbunden als {{name}}',
58
+ 'SMART.MEDIA_STORAGE.CANVA.DISCONNECT': 'Trennen',
59
+ 'SMART.MEDIA_STORAGE.CANVA.SEARCH': 'Designs suchen',
60
+ 'SMART.MEDIA_STORAGE.CANVA.EMPTY': 'Keine Designs gefunden',
61
+ 'SMART.MEDIA_STORAGE.CANVA.UNTITLED': 'Unbenanntes Design',
62
+ 'SMART.MEDIA_STORAGE.CANVA.PAGES': '{{count}} S.',
63
+ 'SMART.MEDIA_STORAGE.CANVA.LOAD_MORE': 'Mehr laden',
64
+ 'SMART.MEDIA_STORAGE.CANVA.PAGES_LABEL': 'Seiten (bis zu {{max}})',
65
+ 'SMART.MEDIA_STORAGE.CANVA.FORMAT_LABEL': 'Format',
66
+ 'SMART.MEDIA_STORAGE.CANVA.IMPORT': 'Importieren',
67
+ 'SMART.MEDIA_STORAGE.CANVA.IMPORTING': 'Importiere {{done}} von {{total}}…',
68
+ 'SMART.MEDIA_STORAGE.CANVA.EXPORTING': 'Canva bereitet das Design vor…',
69
+ 'SMART.MEDIA_STORAGE.CANVA.SUCCESS': 'Aus Canva importiert',
70
+ 'SMART.MEDIA_STORAGE.CANVA.POPUP_BLOCKED': 'Erlauben Sie Pop-ups für diese Website, um Canva zu verbinden',
71
+ 'SMART.MEDIA_STORAGE.CANVA.CONNECT_FAILED': 'Canva konnte nicht verbunden werden',
72
+ 'SMART.MEDIA_STORAGE.CANVA.ERROR': 'Canva-Import fehlgeschlagen'
51
73
  };
@@ -47,5 +47,27 @@ export const messages = {
47
47
  'SMART.MEDIA_STORAGE.CREATE_CROP.ERROR': 'Failed to create crop',
48
48
  'SMART.MEDIA_STORAGE.CREATE_CROP.DELETE_HINT': 'Delete crop',
49
49
  'SMART.MEDIA_STORAGE.CREATE_CROP.DELETE_CONFIRM.TITLE': 'Delete this crop?',
50
- 'SMART.MEDIA_STORAGE.CREATE_CROP.DELETE_CONFIRM.BUTTON': 'Delete'
50
+ 'SMART.MEDIA_STORAGE.CREATE_CROP.DELETE_CONFIRM.BUTTON': 'Delete',
51
+ 'SMART.MEDIA_STORAGE.CANVA.BUTTON': 'Import from Canva',
52
+ 'SMART.MEDIA_STORAGE.CANVA.TITLE': 'Import from Canva',
53
+ 'SMART.MEDIA_STORAGE.CANVA.CLOSE': 'Close',
54
+ 'SMART.MEDIA_STORAGE.CANVA.CONNECT_HINT': 'Connect your Canva account to import your designs.',
55
+ 'SMART.MEDIA_STORAGE.CANVA.RECONNECT_HINT': 'Canva no longer accepts this connection. Connect again.',
56
+ 'SMART.MEDIA_STORAGE.CANVA.CONNECT': 'Connect Canva',
57
+ 'SMART.MEDIA_STORAGE.CANVA.CONNECTED_AS': 'Connected as {{name}}',
58
+ 'SMART.MEDIA_STORAGE.CANVA.DISCONNECT': 'Disconnect',
59
+ 'SMART.MEDIA_STORAGE.CANVA.SEARCH': 'Search designs',
60
+ 'SMART.MEDIA_STORAGE.CANVA.EMPTY': 'No designs found',
61
+ 'SMART.MEDIA_STORAGE.CANVA.UNTITLED': 'Untitled design',
62
+ 'SMART.MEDIA_STORAGE.CANVA.PAGES': '{{count}} p.',
63
+ 'SMART.MEDIA_STORAGE.CANVA.LOAD_MORE': 'Load more',
64
+ 'SMART.MEDIA_STORAGE.CANVA.PAGES_LABEL': 'Pages (up to {{max}})',
65
+ 'SMART.MEDIA_STORAGE.CANVA.FORMAT_LABEL': 'Format',
66
+ 'SMART.MEDIA_STORAGE.CANVA.IMPORT': 'Import',
67
+ 'SMART.MEDIA_STORAGE.CANVA.IMPORTING': 'Importing {{done}} of {{total}}…',
68
+ 'SMART.MEDIA_STORAGE.CANVA.EXPORTING': 'Canva is preparing the design…',
69
+ 'SMART.MEDIA_STORAGE.CANVA.SUCCESS': 'Imported from Canva',
70
+ 'SMART.MEDIA_STORAGE.CANVA.POPUP_BLOCKED': 'Allow pop-ups for this site to connect Canva',
71
+ 'SMART.MEDIA_STORAGE.CANVA.CONNECT_FAILED': 'Could not connect Canva',
72
+ 'SMART.MEDIA_STORAGE.CANVA.ERROR': 'Canva import failed'
51
73
  };
@@ -47,5 +47,27 @@ export const messages = {
47
47
  'SMART.MEDIA_STORAGE.CREATE_CROP.ERROR': 'No se pudo crear el recorte',
48
48
  'SMART.MEDIA_STORAGE.CREATE_CROP.DELETE_HINT': 'Eliminar recorte',
49
49
  'SMART.MEDIA_STORAGE.CREATE_CROP.DELETE_CONFIRM.TITLE': '¿Eliminar este recorte?',
50
- 'SMART.MEDIA_STORAGE.CREATE_CROP.DELETE_CONFIRM.BUTTON': 'Eliminar'
50
+ 'SMART.MEDIA_STORAGE.CREATE_CROP.DELETE_CONFIRM.BUTTON': 'Eliminar',
51
+ 'SMART.MEDIA_STORAGE.CANVA.BUTTON': 'Importar desde Canva',
52
+ 'SMART.MEDIA_STORAGE.CANVA.TITLE': 'Importar desde Canva',
53
+ 'SMART.MEDIA_STORAGE.CANVA.CLOSE': 'Cerrar',
54
+ 'SMART.MEDIA_STORAGE.CANVA.CONNECT_HINT': 'Conecta tu cuenta de Canva para importar tus diseños.',
55
+ 'SMART.MEDIA_STORAGE.CANVA.RECONNECT_HINT': 'Canva ya no acepta esta conexión. Vuelve a conectar.',
56
+ 'SMART.MEDIA_STORAGE.CANVA.CONNECT': 'Conectar Canva',
57
+ 'SMART.MEDIA_STORAGE.CANVA.CONNECTED_AS': 'Conectado como {{name}}',
58
+ 'SMART.MEDIA_STORAGE.CANVA.DISCONNECT': 'Desconectar',
59
+ 'SMART.MEDIA_STORAGE.CANVA.SEARCH': 'Buscar diseños',
60
+ 'SMART.MEDIA_STORAGE.CANVA.EMPTY': 'No se encontraron diseños',
61
+ 'SMART.MEDIA_STORAGE.CANVA.UNTITLED': 'Diseño sin título',
62
+ 'SMART.MEDIA_STORAGE.CANVA.PAGES': '{{count}} pág.',
63
+ 'SMART.MEDIA_STORAGE.CANVA.LOAD_MORE': 'Cargar más',
64
+ 'SMART.MEDIA_STORAGE.CANVA.PAGES_LABEL': 'Páginas (hasta {{max}})',
65
+ 'SMART.MEDIA_STORAGE.CANVA.FORMAT_LABEL': 'Formato',
66
+ 'SMART.MEDIA_STORAGE.CANVA.IMPORT': 'Importar',
67
+ 'SMART.MEDIA_STORAGE.CANVA.IMPORTING': 'Importando {{done}} de {{total}}…',
68
+ 'SMART.MEDIA_STORAGE.CANVA.EXPORTING': 'Canva está preparando el diseño…',
69
+ 'SMART.MEDIA_STORAGE.CANVA.SUCCESS': 'Importado desde Canva',
70
+ 'SMART.MEDIA_STORAGE.CANVA.POPUP_BLOCKED': 'Permite las ventanas emergentes de este sitio para conectar Canva',
71
+ 'SMART.MEDIA_STORAGE.CANVA.CONNECT_FAILED': 'No se pudo conectar Canva',
72
+ 'SMART.MEDIA_STORAGE.CANVA.ERROR': 'No se pudo importar desde Canva'
51
73
  };
@@ -47,5 +47,27 @@ export const messages = {
47
47
  'SMART.MEDIA_STORAGE.CREATE_CROP.ERROR': 'Échec de la création du recadrage',
48
48
  'SMART.MEDIA_STORAGE.CREATE_CROP.DELETE_HINT': 'Supprimer le recadrage',
49
49
  'SMART.MEDIA_STORAGE.CREATE_CROP.DELETE_CONFIRM.TITLE': 'Supprimer ce recadrage ?',
50
- 'SMART.MEDIA_STORAGE.CREATE_CROP.DELETE_CONFIRM.BUTTON': 'Supprimer'
50
+ 'SMART.MEDIA_STORAGE.CREATE_CROP.DELETE_CONFIRM.BUTTON': 'Supprimer',
51
+ 'SMART.MEDIA_STORAGE.CANVA.BUTTON': 'Importer depuis Canva',
52
+ 'SMART.MEDIA_STORAGE.CANVA.TITLE': 'Importer depuis Canva',
53
+ 'SMART.MEDIA_STORAGE.CANVA.CLOSE': 'Fermer',
54
+ 'SMART.MEDIA_STORAGE.CANVA.CONNECT_HINT': 'Connectez votre compte Canva pour importer vos designs.',
55
+ 'SMART.MEDIA_STORAGE.CANVA.RECONNECT_HINT': 'Canva n’accepte plus cette connexion. Reconnectez-vous.',
56
+ 'SMART.MEDIA_STORAGE.CANVA.CONNECT': 'Connecter Canva',
57
+ 'SMART.MEDIA_STORAGE.CANVA.CONNECTED_AS': 'Connecté en tant que {{name}}',
58
+ 'SMART.MEDIA_STORAGE.CANVA.DISCONNECT': 'Déconnecter',
59
+ 'SMART.MEDIA_STORAGE.CANVA.SEARCH': 'Rechercher des designs',
60
+ 'SMART.MEDIA_STORAGE.CANVA.EMPTY': 'Aucun design trouvé',
61
+ 'SMART.MEDIA_STORAGE.CANVA.UNTITLED': 'Design sans titre',
62
+ 'SMART.MEDIA_STORAGE.CANVA.PAGES': '{{count}} p.',
63
+ 'SMART.MEDIA_STORAGE.CANVA.LOAD_MORE': 'Charger plus',
64
+ 'SMART.MEDIA_STORAGE.CANVA.PAGES_LABEL': 'Pages (jusqu’à {{max}})',
65
+ 'SMART.MEDIA_STORAGE.CANVA.FORMAT_LABEL': 'Format',
66
+ 'SMART.MEDIA_STORAGE.CANVA.IMPORT': 'Importer',
67
+ 'SMART.MEDIA_STORAGE.CANVA.IMPORTING': 'Importation {{done}} sur {{total}}…',
68
+ 'SMART.MEDIA_STORAGE.CANVA.EXPORTING': 'Canva prépare le design…',
69
+ 'SMART.MEDIA_STORAGE.CANVA.SUCCESS': 'Importé depuis Canva',
70
+ 'SMART.MEDIA_STORAGE.CANVA.POPUP_BLOCKED': 'Autorisez les fenêtres pop-up pour ce site afin de connecter Canva',
71
+ 'SMART.MEDIA_STORAGE.CANVA.CONNECT_FAILED': 'Impossible de connecter Canva',
72
+ 'SMART.MEDIA_STORAGE.CANVA.ERROR': 'Échec de l’importation depuis Canva'
51
73
  };
@@ -47,5 +47,27 @@ export const messages = {
47
47
  'SMART.MEDIA_STORAGE.CREATE_CROP.ERROR': 'יצירת החיתוך נכשלה',
48
48
  'SMART.MEDIA_STORAGE.CREATE_CROP.DELETE_HINT': 'מחיקת חיתוך',
49
49
  'SMART.MEDIA_STORAGE.CREATE_CROP.DELETE_CONFIRM.TITLE': 'למחוק את החיתוך הזה?',
50
- 'SMART.MEDIA_STORAGE.CREATE_CROP.DELETE_CONFIRM.BUTTON': 'מחיקה'
50
+ 'SMART.MEDIA_STORAGE.CREATE_CROP.DELETE_CONFIRM.BUTTON': 'מחיקה',
51
+ 'SMART.MEDIA_STORAGE.CANVA.BUTTON': 'ייבוא מ-Canva',
52
+ 'SMART.MEDIA_STORAGE.CANVA.TITLE': 'ייבוא מ-Canva',
53
+ 'SMART.MEDIA_STORAGE.CANVA.CLOSE': 'סגירה',
54
+ 'SMART.MEDIA_STORAGE.CANVA.CONNECT_HINT': 'חברו את חשבון Canva כדי לייבא את העיצובים שלכם.',
55
+ 'SMART.MEDIA_STORAGE.CANVA.RECONNECT_HINT': 'Canva כבר לא מקבל את החיבור הזה. התחברו מחדש.',
56
+ 'SMART.MEDIA_STORAGE.CANVA.CONNECT': 'חיבור Canva',
57
+ 'SMART.MEDIA_STORAGE.CANVA.CONNECTED_AS': 'מחובר בתור {{name}}',
58
+ 'SMART.MEDIA_STORAGE.CANVA.DISCONNECT': 'ניתוק',
59
+ 'SMART.MEDIA_STORAGE.CANVA.SEARCH': 'חיפוש עיצובים',
60
+ 'SMART.MEDIA_STORAGE.CANVA.EMPTY': 'לא נמצאו עיצובים',
61
+ 'SMART.MEDIA_STORAGE.CANVA.UNTITLED': 'עיצוב ללא שם',
62
+ 'SMART.MEDIA_STORAGE.CANVA.PAGES': '{{count}} עמ׳',
63
+ 'SMART.MEDIA_STORAGE.CANVA.LOAD_MORE': 'טעינת עוד',
64
+ 'SMART.MEDIA_STORAGE.CANVA.PAGES_LABEL': 'עמודים (עד {{max}})',
65
+ 'SMART.MEDIA_STORAGE.CANVA.FORMAT_LABEL': 'פורמט',
66
+ 'SMART.MEDIA_STORAGE.CANVA.IMPORT': 'ייבוא',
67
+ 'SMART.MEDIA_STORAGE.CANVA.IMPORTING': 'מייבא {{done}} מתוך {{total}}…',
68
+ 'SMART.MEDIA_STORAGE.CANVA.EXPORTING': 'Canva מכין את העיצוב…',
69
+ 'SMART.MEDIA_STORAGE.CANVA.SUCCESS': 'יובא מ-Canva',
70
+ 'SMART.MEDIA_STORAGE.CANVA.POPUP_BLOCKED': 'אפשרו חלונות קופצים לאתר הזה כדי לחבר את Canva',
71
+ 'SMART.MEDIA_STORAGE.CANVA.CONNECT_FAILED': 'לא ניתן לחבר את Canva',
72
+ 'SMART.MEDIA_STORAGE.CANVA.ERROR': 'הייבוא מ-Canva נכשל'
51
73
  };
@@ -47,5 +47,27 @@ export const messages = {
47
47
  'SMART.MEDIA_STORAGE.CREATE_CROP.ERROR': 'क्रॉप नहीं बनाया जा सका',
48
48
  'SMART.MEDIA_STORAGE.CREATE_CROP.DELETE_HINT': 'क्रॉप हटाएँ',
49
49
  'SMART.MEDIA_STORAGE.CREATE_CROP.DELETE_CONFIRM.TITLE': 'यह क्रॉप हटाएँ?',
50
- 'SMART.MEDIA_STORAGE.CREATE_CROP.DELETE_CONFIRM.BUTTON': 'हटाएँ'
50
+ 'SMART.MEDIA_STORAGE.CREATE_CROP.DELETE_CONFIRM.BUTTON': 'हटाएँ',
51
+ 'SMART.MEDIA_STORAGE.CANVA.BUTTON': 'Canva से इम्पोर्ट करें',
52
+ 'SMART.MEDIA_STORAGE.CANVA.TITLE': 'Canva से इम्पोर्ट करें',
53
+ 'SMART.MEDIA_STORAGE.CANVA.CLOSE': 'बंद करें',
54
+ 'SMART.MEDIA_STORAGE.CANVA.CONNECT_HINT': 'अपने डिज़ाइन इम्पोर्ट करने के लिए Canva खाता कनेक्ट करें।',
55
+ 'SMART.MEDIA_STORAGE.CANVA.RECONNECT_HINT': 'Canva अब यह कनेक्शन स्वीकार नहीं करता। फिर से कनेक्ट करें।',
56
+ 'SMART.MEDIA_STORAGE.CANVA.CONNECT': 'Canva कनेक्ट करें',
57
+ 'SMART.MEDIA_STORAGE.CANVA.CONNECTED_AS': '{{name}} के रूप में कनेक्टेड',
58
+ 'SMART.MEDIA_STORAGE.CANVA.DISCONNECT': 'डिस्कनेक्ट करें',
59
+ 'SMART.MEDIA_STORAGE.CANVA.SEARCH': 'डिज़ाइन खोजें',
60
+ 'SMART.MEDIA_STORAGE.CANVA.EMPTY': 'कोई डिज़ाइन नहीं मिला',
61
+ 'SMART.MEDIA_STORAGE.CANVA.UNTITLED': 'बिना नाम का डिज़ाइन',
62
+ 'SMART.MEDIA_STORAGE.CANVA.PAGES': '{{count}} पृ.',
63
+ 'SMART.MEDIA_STORAGE.CANVA.LOAD_MORE': 'और लोड करें',
64
+ 'SMART.MEDIA_STORAGE.CANVA.PAGES_LABEL': 'पृष्ठ (अधिकतम {{max}})',
65
+ 'SMART.MEDIA_STORAGE.CANVA.FORMAT_LABEL': 'फ़ॉर्मेट',
66
+ 'SMART.MEDIA_STORAGE.CANVA.IMPORT': 'इम्पोर्ट करें',
67
+ 'SMART.MEDIA_STORAGE.CANVA.IMPORTING': '{{total}} में से {{done}} इम्पोर्ट हो रहे हैं…',
68
+ 'SMART.MEDIA_STORAGE.CANVA.EXPORTING': 'Canva डिज़ाइन तैयार कर रहा है…',
69
+ 'SMART.MEDIA_STORAGE.CANVA.SUCCESS': 'Canva से इम्पोर्ट हो गया',
70
+ 'SMART.MEDIA_STORAGE.CANVA.POPUP_BLOCKED': 'Canva कनेक्ट करने के लिए इस साइट के पॉप-अप की अनुमति दें',
71
+ 'SMART.MEDIA_STORAGE.CANVA.CONNECT_FAILED': 'Canva कनेक्ट नहीं हो सका',
72
+ 'SMART.MEDIA_STORAGE.CANVA.ERROR': 'Canva से इम्पोर्ट विफल रहा'
51
73
  };
@@ -47,5 +47,27 @@ export const messages = {
47
47
  'SMART.MEDIA_STORAGE.CREATE_CROP.ERROR': 'トリミングの作成に失敗しました',
48
48
  'SMART.MEDIA_STORAGE.CREATE_CROP.DELETE_HINT': 'トリミングを削除',
49
49
  'SMART.MEDIA_STORAGE.CREATE_CROP.DELETE_CONFIRM.TITLE': 'このトリミングを削除しますか?',
50
- 'SMART.MEDIA_STORAGE.CREATE_CROP.DELETE_CONFIRM.BUTTON': '削除'
50
+ 'SMART.MEDIA_STORAGE.CREATE_CROP.DELETE_CONFIRM.BUTTON': '削除',
51
+ 'SMART.MEDIA_STORAGE.CANVA.BUTTON': 'Canvaからインポート',
52
+ 'SMART.MEDIA_STORAGE.CANVA.TITLE': 'Canvaからインポート',
53
+ 'SMART.MEDIA_STORAGE.CANVA.CLOSE': '閉じる',
54
+ 'SMART.MEDIA_STORAGE.CANVA.CONNECT_HINT': 'デザインをインポートするにはCanvaアカウントを接続してください。',
55
+ 'SMART.MEDIA_STORAGE.CANVA.RECONNECT_HINT': 'Canvaはこの接続を受け付けなくなりました。もう一度接続してください。',
56
+ 'SMART.MEDIA_STORAGE.CANVA.CONNECT': 'Canvaを接続',
57
+ 'SMART.MEDIA_STORAGE.CANVA.CONNECTED_AS': '{{name}}として接続中',
58
+ 'SMART.MEDIA_STORAGE.CANVA.DISCONNECT': '接続を解除',
59
+ 'SMART.MEDIA_STORAGE.CANVA.SEARCH': 'デザインを検索',
60
+ 'SMART.MEDIA_STORAGE.CANVA.EMPTY': 'デザインが見つかりません',
61
+ 'SMART.MEDIA_STORAGE.CANVA.UNTITLED': '無題のデザイン',
62
+ 'SMART.MEDIA_STORAGE.CANVA.PAGES': '{{count}}ページ',
63
+ 'SMART.MEDIA_STORAGE.CANVA.LOAD_MORE': 'さらに読み込む',
64
+ 'SMART.MEDIA_STORAGE.CANVA.PAGES_LABEL': 'ページ(最大{{max}})',
65
+ 'SMART.MEDIA_STORAGE.CANVA.FORMAT_LABEL': '形式',
66
+ 'SMART.MEDIA_STORAGE.CANVA.IMPORT': 'インポート',
67
+ 'SMART.MEDIA_STORAGE.CANVA.IMPORTING': '{{total}}件中{{done}}件をインポート中…',
68
+ 'SMART.MEDIA_STORAGE.CANVA.EXPORTING': 'Canvaがデザインを準備しています…',
69
+ 'SMART.MEDIA_STORAGE.CANVA.SUCCESS': 'Canvaからインポートしました',
70
+ 'SMART.MEDIA_STORAGE.CANVA.POPUP_BLOCKED': 'Canvaを接続するには、このサイトのポップアップを許可してください',
71
+ 'SMART.MEDIA_STORAGE.CANVA.CONNECT_FAILED': 'Canvaを接続できませんでした',
72
+ 'SMART.MEDIA_STORAGE.CANVA.ERROR': 'Canvaからのインポートに失敗しました'
51
73
  };
@@ -47,5 +47,27 @@ export const messages = {
47
47
  'SMART.MEDIA_STORAGE.CREATE_CROP.ERROR': '크롭을 생성하지 못했습니다',
48
48
  'SMART.MEDIA_STORAGE.CREATE_CROP.DELETE_HINT': '크롭 삭제',
49
49
  'SMART.MEDIA_STORAGE.CREATE_CROP.DELETE_CONFIRM.TITLE': '이 크롭을 삭제하시겠습니까?',
50
- 'SMART.MEDIA_STORAGE.CREATE_CROP.DELETE_CONFIRM.BUTTON': '삭제'
50
+ 'SMART.MEDIA_STORAGE.CREATE_CROP.DELETE_CONFIRM.BUTTON': '삭제',
51
+ 'SMART.MEDIA_STORAGE.CANVA.BUTTON': 'Canva에서 가져오기',
52
+ 'SMART.MEDIA_STORAGE.CANVA.TITLE': 'Canva에서 가져오기',
53
+ 'SMART.MEDIA_STORAGE.CANVA.CLOSE': '닫기',
54
+ 'SMART.MEDIA_STORAGE.CANVA.CONNECT_HINT': '디자인을 가져오려면 Canva 계정을 연결하세요.',
55
+ 'SMART.MEDIA_STORAGE.CANVA.RECONNECT_HINT': 'Canva가 더 이상 이 연결을 허용하지 않습니다. 다시 연결하세요.',
56
+ 'SMART.MEDIA_STORAGE.CANVA.CONNECT': 'Canva 연결',
57
+ 'SMART.MEDIA_STORAGE.CANVA.CONNECTED_AS': '{{name}}(으)로 연결됨',
58
+ 'SMART.MEDIA_STORAGE.CANVA.DISCONNECT': '연결 해제',
59
+ 'SMART.MEDIA_STORAGE.CANVA.SEARCH': '디자인 검색',
60
+ 'SMART.MEDIA_STORAGE.CANVA.EMPTY': '디자인을 찾을 수 없습니다',
61
+ 'SMART.MEDIA_STORAGE.CANVA.UNTITLED': '제목 없는 디자인',
62
+ 'SMART.MEDIA_STORAGE.CANVA.PAGES': '{{count}}쪽',
63
+ 'SMART.MEDIA_STORAGE.CANVA.LOAD_MORE': '더 보기',
64
+ 'SMART.MEDIA_STORAGE.CANVA.PAGES_LABEL': '페이지(최대 {{max}}개)',
65
+ 'SMART.MEDIA_STORAGE.CANVA.FORMAT_LABEL': '형식',
66
+ 'SMART.MEDIA_STORAGE.CANVA.IMPORT': '가져오기',
67
+ 'SMART.MEDIA_STORAGE.CANVA.IMPORTING': '{{total}}개 중 {{done}}개 가져오는 중…',
68
+ 'SMART.MEDIA_STORAGE.CANVA.EXPORTING': 'Canva에서 디자인을 준비하고 있습니다…',
69
+ 'SMART.MEDIA_STORAGE.CANVA.SUCCESS': 'Canva에서 가져왔습니다',
70
+ 'SMART.MEDIA_STORAGE.CANVA.POPUP_BLOCKED': 'Canva를 연결하려면 이 사이트의 팝업을 허용하세요',
71
+ 'SMART.MEDIA_STORAGE.CANVA.CONNECT_FAILED': 'Canva를 연결할 수 없습니다',
72
+ 'SMART.MEDIA_STORAGE.CANVA.ERROR': 'Canva에서 가져오지 못했습니다'
51
73
  };
@@ -47,5 +47,27 @@ export const messages = {
47
47
  'SMART.MEDIA_STORAGE.CREATE_CROP.ERROR': 'Falha ao criar o recorte',
48
48
  'SMART.MEDIA_STORAGE.CREATE_CROP.DELETE_HINT': 'Excluir recorte',
49
49
  'SMART.MEDIA_STORAGE.CREATE_CROP.DELETE_CONFIRM.TITLE': 'Excluir este recorte?',
50
- 'SMART.MEDIA_STORAGE.CREATE_CROP.DELETE_CONFIRM.BUTTON': 'Excluir'
50
+ 'SMART.MEDIA_STORAGE.CREATE_CROP.DELETE_CONFIRM.BUTTON': 'Excluir',
51
+ 'SMART.MEDIA_STORAGE.CANVA.BUTTON': 'Importar do Canva',
52
+ 'SMART.MEDIA_STORAGE.CANVA.TITLE': 'Importar do Canva',
53
+ 'SMART.MEDIA_STORAGE.CANVA.CLOSE': 'Fechar',
54
+ 'SMART.MEDIA_STORAGE.CANVA.CONNECT_HINT': 'Conecte sua conta do Canva para importar seus designs.',
55
+ 'SMART.MEDIA_STORAGE.CANVA.RECONNECT_HINT': 'O Canva não aceita mais esta conexão. Conecte novamente.',
56
+ 'SMART.MEDIA_STORAGE.CANVA.CONNECT': 'Conectar Canva',
57
+ 'SMART.MEDIA_STORAGE.CANVA.CONNECTED_AS': 'Conectado como {{name}}',
58
+ 'SMART.MEDIA_STORAGE.CANVA.DISCONNECT': 'Desconectar',
59
+ 'SMART.MEDIA_STORAGE.CANVA.SEARCH': 'Pesquisar designs',
60
+ 'SMART.MEDIA_STORAGE.CANVA.EMPTY': 'Nenhum design encontrado',
61
+ 'SMART.MEDIA_STORAGE.CANVA.UNTITLED': 'Design sem título',
62
+ 'SMART.MEDIA_STORAGE.CANVA.PAGES': '{{count}} pág.',
63
+ 'SMART.MEDIA_STORAGE.CANVA.LOAD_MORE': 'Carregar mais',
64
+ 'SMART.MEDIA_STORAGE.CANVA.PAGES_LABEL': 'Páginas (até {{max}})',
65
+ 'SMART.MEDIA_STORAGE.CANVA.FORMAT_LABEL': 'Formato',
66
+ 'SMART.MEDIA_STORAGE.CANVA.IMPORT': 'Importar',
67
+ 'SMART.MEDIA_STORAGE.CANVA.IMPORTING': 'Importando {{done}} de {{total}}…',
68
+ 'SMART.MEDIA_STORAGE.CANVA.EXPORTING': 'O Canva está preparando o design…',
69
+ 'SMART.MEDIA_STORAGE.CANVA.SUCCESS': 'Importado do Canva',
70
+ 'SMART.MEDIA_STORAGE.CANVA.POPUP_BLOCKED': 'Permita pop-ups neste site para conectar o Canva',
71
+ 'SMART.MEDIA_STORAGE.CANVA.CONNECT_FAILED': 'Não foi possível conectar o Canva',
72
+ 'SMART.MEDIA_STORAGE.CANVA.ERROR': 'Falha ao importar do Canva'
51
73
  };
@@ -47,5 +47,27 @@ export const messages = {
47
47
  'SMART.MEDIA_STORAGE.CREATE_CROP.ERROR': 'Не удалось создать кроп',
48
48
  'SMART.MEDIA_STORAGE.CREATE_CROP.DELETE_HINT': 'Удалить кроп',
49
49
  'SMART.MEDIA_STORAGE.CREATE_CROP.DELETE_CONFIRM.TITLE': 'Удалить кроп?',
50
- 'SMART.MEDIA_STORAGE.CREATE_CROP.DELETE_CONFIRM.BUTTON': 'Удалить'
50
+ 'SMART.MEDIA_STORAGE.CREATE_CROP.DELETE_CONFIRM.BUTTON': 'Удалить',
51
+ 'SMART.MEDIA_STORAGE.CANVA.BUTTON': 'Импорт из Canva',
52
+ 'SMART.MEDIA_STORAGE.CANVA.TITLE': 'Импорт из Canva',
53
+ 'SMART.MEDIA_STORAGE.CANVA.CLOSE': 'Закрыть',
54
+ 'SMART.MEDIA_STORAGE.CANVA.CONNECT_HINT': 'Подключите аккаунт Canva, чтобы импортировать свои дизайны.',
55
+ 'SMART.MEDIA_STORAGE.CANVA.RECONNECT_HINT': 'Canva больше не принимает это подключение. Подключите заново.',
56
+ 'SMART.MEDIA_STORAGE.CANVA.CONNECT': 'Подключить Canva',
57
+ 'SMART.MEDIA_STORAGE.CANVA.CONNECTED_AS': 'Подключено: {{name}}',
58
+ 'SMART.MEDIA_STORAGE.CANVA.DISCONNECT': 'Отключить',
59
+ 'SMART.MEDIA_STORAGE.CANVA.SEARCH': 'Поиск дизайнов',
60
+ 'SMART.MEDIA_STORAGE.CANVA.EMPTY': 'Дизайны не найдены',
61
+ 'SMART.MEDIA_STORAGE.CANVA.UNTITLED': 'Дизайн без названия',
62
+ 'SMART.MEDIA_STORAGE.CANVA.PAGES': '{{count}} стр.',
63
+ 'SMART.MEDIA_STORAGE.CANVA.LOAD_MORE': 'Показать ещё',
64
+ 'SMART.MEDIA_STORAGE.CANVA.PAGES_LABEL': 'Страницы (до {{max}})',
65
+ 'SMART.MEDIA_STORAGE.CANVA.FORMAT_LABEL': 'Формат',
66
+ 'SMART.MEDIA_STORAGE.CANVA.IMPORT': 'Импортировать',
67
+ 'SMART.MEDIA_STORAGE.CANVA.IMPORTING': 'Импорт {{done}} из {{total}}…',
68
+ 'SMART.MEDIA_STORAGE.CANVA.EXPORTING': 'Canva готовит дизайн…',
69
+ 'SMART.MEDIA_STORAGE.CANVA.SUCCESS': 'Импортировано из Canva',
70
+ 'SMART.MEDIA_STORAGE.CANVA.POPUP_BLOCKED': 'Разрешите всплывающие окна для этого сайта, чтобы подключить Canva',
71
+ 'SMART.MEDIA_STORAGE.CANVA.CONNECT_FAILED': 'Не удалось подключить Canva',
72
+ 'SMART.MEDIA_STORAGE.CANVA.ERROR': 'Не удалось импортировать из Canva'
51
73
  };
@@ -47,5 +47,27 @@ export const messages = {
47
47
  'SMART.MEDIA_STORAGE.CREATE_CROP.ERROR': 'สร้างครอปไม่สำเร็จ',
48
48
  'SMART.MEDIA_STORAGE.CREATE_CROP.DELETE_HINT': 'ลบครอป',
49
49
  'SMART.MEDIA_STORAGE.CREATE_CROP.DELETE_CONFIRM.TITLE': 'ลบครอปนี้ใช่ไหม',
50
- 'SMART.MEDIA_STORAGE.CREATE_CROP.DELETE_CONFIRM.BUTTON': 'ลบ'
50
+ 'SMART.MEDIA_STORAGE.CREATE_CROP.DELETE_CONFIRM.BUTTON': 'ลบ',
51
+ 'SMART.MEDIA_STORAGE.CANVA.BUTTON': 'นำเข้าจาก Canva',
52
+ 'SMART.MEDIA_STORAGE.CANVA.TITLE': 'นำเข้าจาก Canva',
53
+ 'SMART.MEDIA_STORAGE.CANVA.CLOSE': 'ปิด',
54
+ 'SMART.MEDIA_STORAGE.CANVA.CONNECT_HINT': 'เชื่อมต่อบัญชี Canva เพื่อนำเข้าดีไซน์ของคุณ',
55
+ 'SMART.MEDIA_STORAGE.CANVA.RECONNECT_HINT': 'Canva ไม่ยอมรับการเชื่อมต่อนี้แล้ว โปรดเชื่อมต่อใหม่',
56
+ 'SMART.MEDIA_STORAGE.CANVA.CONNECT': 'เชื่อมต่อ Canva',
57
+ 'SMART.MEDIA_STORAGE.CANVA.CONNECTED_AS': 'เชื่อมต่อในชื่อ {{name}}',
58
+ 'SMART.MEDIA_STORAGE.CANVA.DISCONNECT': 'ยกเลิกการเชื่อมต่อ',
59
+ 'SMART.MEDIA_STORAGE.CANVA.SEARCH': 'ค้นหาดีไซน์',
60
+ 'SMART.MEDIA_STORAGE.CANVA.EMPTY': 'ไม่พบดีไซน์',
61
+ 'SMART.MEDIA_STORAGE.CANVA.UNTITLED': 'ดีไซน์ไม่มีชื่อ',
62
+ 'SMART.MEDIA_STORAGE.CANVA.PAGES': '{{count}} หน้า',
63
+ 'SMART.MEDIA_STORAGE.CANVA.LOAD_MORE': 'โหลดเพิ่ม',
64
+ 'SMART.MEDIA_STORAGE.CANVA.PAGES_LABEL': 'หน้า (สูงสุด {{max}})',
65
+ 'SMART.MEDIA_STORAGE.CANVA.FORMAT_LABEL': 'รูปแบบ',
66
+ 'SMART.MEDIA_STORAGE.CANVA.IMPORT': 'นำเข้า',
67
+ 'SMART.MEDIA_STORAGE.CANVA.IMPORTING': 'กำลังนำเข้า {{done}} จาก {{total}}…',
68
+ 'SMART.MEDIA_STORAGE.CANVA.EXPORTING': 'Canva กำลังเตรียมดีไซน์…',
69
+ 'SMART.MEDIA_STORAGE.CANVA.SUCCESS': 'นำเข้าจาก Canva แล้ว',
70
+ 'SMART.MEDIA_STORAGE.CANVA.POPUP_BLOCKED': 'อนุญาตป๊อปอัปสำหรับเว็บไซต์นี้เพื่อเชื่อมต่อ Canva',
71
+ 'SMART.MEDIA_STORAGE.CANVA.CONNECT_FAILED': 'ไม่สามารถเชื่อมต่อ Canva ได้',
72
+ 'SMART.MEDIA_STORAGE.CANVA.ERROR': 'นำเข้าจาก Canva ไม่สำเร็จ'
51
73
  };
@@ -47,5 +47,27 @@ export const messages = {
47
47
  'SMART.MEDIA_STORAGE.CREATE_CROP.ERROR': '创建裁剪图失败',
48
48
  'SMART.MEDIA_STORAGE.CREATE_CROP.DELETE_HINT': '删除裁剪图',
49
49
  'SMART.MEDIA_STORAGE.CREATE_CROP.DELETE_CONFIRM.TITLE': '删除此裁剪图?',
50
- 'SMART.MEDIA_STORAGE.CREATE_CROP.DELETE_CONFIRM.BUTTON': '删除'
50
+ 'SMART.MEDIA_STORAGE.CREATE_CROP.DELETE_CONFIRM.BUTTON': '删除',
51
+ 'SMART.MEDIA_STORAGE.CANVA.BUTTON': '从 Canva 导入',
52
+ 'SMART.MEDIA_STORAGE.CANVA.TITLE': '从 Canva 导入',
53
+ 'SMART.MEDIA_STORAGE.CANVA.CLOSE': '关闭',
54
+ 'SMART.MEDIA_STORAGE.CANVA.CONNECT_HINT': '连接你的 Canva 账号以导入设计。',
55
+ 'SMART.MEDIA_STORAGE.CANVA.RECONNECT_HINT': 'Canva 不再接受此连接,请重新连接。',
56
+ 'SMART.MEDIA_STORAGE.CANVA.CONNECT': '连接 Canva',
57
+ 'SMART.MEDIA_STORAGE.CANVA.CONNECTED_AS': '已连接:{{name}}',
58
+ 'SMART.MEDIA_STORAGE.CANVA.DISCONNECT': '断开连接',
59
+ 'SMART.MEDIA_STORAGE.CANVA.SEARCH': '搜索设计',
60
+ 'SMART.MEDIA_STORAGE.CANVA.EMPTY': '未找到设计',
61
+ 'SMART.MEDIA_STORAGE.CANVA.UNTITLED': '未命名设计',
62
+ 'SMART.MEDIA_STORAGE.CANVA.PAGES': '{{count}} 页',
63
+ 'SMART.MEDIA_STORAGE.CANVA.LOAD_MORE': '加载更多',
64
+ 'SMART.MEDIA_STORAGE.CANVA.PAGES_LABEL': '页面(最多 {{max}} 页)',
65
+ 'SMART.MEDIA_STORAGE.CANVA.FORMAT_LABEL': '格式',
66
+ 'SMART.MEDIA_STORAGE.CANVA.IMPORT': '导入',
67
+ 'SMART.MEDIA_STORAGE.CANVA.IMPORTING': '正在导入第 {{done}} / {{total}} 页…',
68
+ 'SMART.MEDIA_STORAGE.CANVA.EXPORTING': 'Canva 正在准备设计…',
69
+ 'SMART.MEDIA_STORAGE.CANVA.SUCCESS': '已从 Canva 导入',
70
+ 'SMART.MEDIA_STORAGE.CANVA.POPUP_BLOCKED': '请允许此网站的弹出窗口以连接 Canva',
71
+ 'SMART.MEDIA_STORAGE.CANVA.CONNECT_FAILED': '无法连接 Canva',
72
+ 'SMART.MEDIA_STORAGE.CANVA.ERROR': '从 Canva 导入失败'
51
73
  };
@@ -64,4 +64,35 @@ export type MediaStorageMessages = {
64
64
  'SMART.MEDIA_STORAGE.CREATE_CROP.DELETE_HINT': NoParams;
65
65
  'SMART.MEDIA_STORAGE.CREATE_CROP.DELETE_CONFIRM.TITLE': NoParams;
66
66
  'SMART.MEDIA_STORAGE.CREATE_CROP.DELETE_CONFIRM.BUTTON': NoParams;
67
+ 'SMART.MEDIA_STORAGE.CANVA.BUTTON': NoParams;
68
+ 'SMART.MEDIA_STORAGE.CANVA.TITLE': NoParams;
69
+ 'SMART.MEDIA_STORAGE.CANVA.CLOSE': NoParams;
70
+ 'SMART.MEDIA_STORAGE.CANVA.CONNECT_HINT': NoParams;
71
+ 'SMART.MEDIA_STORAGE.CANVA.RECONNECT_HINT': NoParams;
72
+ 'SMART.MEDIA_STORAGE.CANVA.CONNECT': NoParams;
73
+ 'SMART.MEDIA_STORAGE.CANVA.CONNECTED_AS': {
74
+ name: string;
75
+ };
76
+ 'SMART.MEDIA_STORAGE.CANVA.DISCONNECT': NoParams;
77
+ 'SMART.MEDIA_STORAGE.CANVA.SEARCH': NoParams;
78
+ 'SMART.MEDIA_STORAGE.CANVA.EMPTY': NoParams;
79
+ 'SMART.MEDIA_STORAGE.CANVA.UNTITLED': NoParams;
80
+ 'SMART.MEDIA_STORAGE.CANVA.PAGES': {
81
+ count: number;
82
+ };
83
+ 'SMART.MEDIA_STORAGE.CANVA.LOAD_MORE': NoParams;
84
+ 'SMART.MEDIA_STORAGE.CANVA.PAGES_LABEL': {
85
+ max: number;
86
+ };
87
+ 'SMART.MEDIA_STORAGE.CANVA.FORMAT_LABEL': NoParams;
88
+ 'SMART.MEDIA_STORAGE.CANVA.IMPORT': NoParams;
89
+ 'SMART.MEDIA_STORAGE.CANVA.IMPORTING': {
90
+ done: number;
91
+ total: number;
92
+ };
93
+ 'SMART.MEDIA_STORAGE.CANVA.EXPORTING': NoParams;
94
+ 'SMART.MEDIA_STORAGE.CANVA.SUCCESS': NoParams;
95
+ 'SMART.MEDIA_STORAGE.CANVA.POPUP_BLOCKED': NoParams;
96
+ 'SMART.MEDIA_STORAGE.CANVA.CONNECT_FAILED': NoParams;
97
+ 'SMART.MEDIA_STORAGE.CANVA.ERROR': NoParams;
67
98
  };
@@ -33,3 +33,8 @@ export declare const CropStrip: import("styled-components").StyledComponent<"div
33
33
  export declare const CropThumb: import("styled-components").StyledComponent<"div", any, {}, never>;
34
34
  export declare const CropThumbDelete: import("styled-components").StyledComponent<"div", any, {}, never>;
35
35
  export declare const CropThumbCaption: import("styled-components").StyledComponent<"div", any, {}, never>;
36
+ export declare const DesignTile: import("styled-components").StyledComponent<"button", any, {
37
+ $selected?: boolean;
38
+ }, never>;
39
+ export declare const DesignCaption: import("styled-components").StyledComponent<"span", any, {}, never>;
40
+ export declare const DropzoneRow: import("styled-components").StyledComponent<"div", any, {}, never>;
@@ -123,4 +123,20 @@ export const CropThumbDelete = styled.div.withConfig({
123
123
  export const CropThumbCaption = styled.div.withConfig({
124
124
  displayName: "CropThumbCaption",
125
125
  componentId: "sc-uu3cbo-26"
126
- })(["font-size:10px;text-align:center;padding:3px 2px;color:", ";"], Color.LOCKED);
126
+ })(["font-size:10px;text-align:center;padding:3px 2px;color:", ";"], Color.LOCKED);
127
+ export const DesignTile = styled.button.withConfig({
128
+ displayName: "DesignTile",
129
+ componentId: "sc-uu3cbo-27"
130
+ })(["display:flex;flex-direction:column;padding:0;font:inherit;text-align:left;cursor:pointer;background:", ";border-radius:6px;overflow:hidden;border:1px solid ", ";box-shadow:", ";transition:border-color 0.15s,box-shadow 0.15s;&:hover:not(:disabled){border-color:", ";}&:disabled{cursor:default;}"], Color.CLEAR, ({
131
+ $selected
132
+ }) => $selected ? Color.PRIMARY : Color.DIVIDER, ({
133
+ $selected
134
+ }) => $selected ? `0 0 0 1px ${Color.PRIMARY}` : 'none', Color.PRIMARY);
135
+ export const DesignCaption = styled.span.withConfig({
136
+ displayName: "DesignCaption",
137
+ componentId: "sc-uu3cbo-28"
138
+ })(["display:flex;justify-content:space-between;gap:6px;padding:6px 8px;font-size:", ";color:", ";span:first-child{overflow:hidden;white-space:nowrap;text-overflow:ellipsis;}span:last-child{flex-shrink:0;color:", ";}"], FontSize.SMALL, Color.MAIN, Color.LOCKED);
139
+ export const DropzoneRow = styled.div.withConfig({
140
+ displayName: "DropzoneRow",
141
+ componentId: "sc-uu3cbo-29"
142
+ })(["display:flex;align-items:stretch;gap:12px;& > :first-child{flex:1;}"]);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pushwoosh/smart-components-kit",
3
- "version": "1.4.0",
3
+ "version": "1.5.1",
4
4
  "description": "Stateful, data-connected React components shared by Pushwoosh micro-frontends",
5
5
  "repository": {
6
6
  "type": "git",
@@ -17,12 +17,12 @@
17
17
  },
18
18
  "dependencies": {
19
19
  "@floating-ui/react-dom": "^2.1.2",
20
- "@pushwoosh/dumb-components": "^2.9.13",
20
+ "@pushwoosh/dumb-components": "^2.9.17",
21
21
  "@pushwoosh/kit-constants": "^2.1.0",
22
22
  "@pushwoosh/kit-helpers": "^4.12.7",
23
23
  "@pushwoosh/kit-icons": "^2.3.3",
24
24
  "@pushwoosh/kit-typography": "^1.8.7",
25
- "@pushwoosh/rpc-v2-http-api-data": "^0.2.376",
25
+ "@pushwoosh/rpc-v2-http-api-data": "^0.2.383",
26
26
  "@pushwoosh/spa-context": "^1.8.1",
27
27
  "idb-keyval": "^6.2.2",
28
28
  "jwt-decode": "^4.0.0",