@quantabit/media-sdk 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,783 @@
1
+ import React, { useState, useRef, useEffect, useCallback } from 'react';
2
+ import { BaseApiClient } from '@quantabit/sdk-config';
3
+
4
+ function ImageViewer({
5
+ src,
6
+ alt = '',
7
+ zoom = true,
8
+ className = ''
9
+ }) {
10
+ const [fullscreen, setFullscreen] = useState(false);
11
+ const [scale, setScale] = useState(1);
12
+ return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement("img", {
13
+ src: src,
14
+ alt: alt,
15
+ onClick: () => zoom && setFullscreen(true),
16
+ className: `qmd-img ${className}`,
17
+ style: {
18
+ maxWidth: '100%',
19
+ borderRadius: 8,
20
+ cursor: zoom ? 'zoom-in' : 'default',
21
+ transition: 'all 0.2s'
22
+ }
23
+ }), fullscreen && /*#__PURE__*/React.createElement("div", {
24
+ onClick: () => {
25
+ setFullscreen(false);
26
+ setScale(1);
27
+ },
28
+ style: {
29
+ position: 'fixed',
30
+ inset: 0,
31
+ zIndex: 99999,
32
+ background: 'rgba(0,0,0,0.9)',
33
+ display: 'flex',
34
+ alignItems: 'center',
35
+ justifyContent: 'center',
36
+ cursor: 'zoom-out'
37
+ }
38
+ }, /*#__PURE__*/React.createElement("img", {
39
+ src: src,
40
+ alt: alt,
41
+ style: {
42
+ maxWidth: '90vw',
43
+ maxHeight: '90vh',
44
+ objectFit: 'contain',
45
+ transform: `scale(${scale})`,
46
+ transition: 'transform 0.2s'
47
+ },
48
+ onClick: e => {
49
+ e.stopPropagation();
50
+ setScale(s => s >= 2 ? 1 : s + 0.5);
51
+ }
52
+ }), /*#__PURE__*/React.createElement("button", {
53
+ onClick: () => {
54
+ setFullscreen(false);
55
+ setScale(1);
56
+ },
57
+ style: {
58
+ position: 'absolute',
59
+ top: 20,
60
+ right: 20,
61
+ border: 'none',
62
+ background: 'rgba(255,255,255,0.1)',
63
+ color: '#fff',
64
+ width: 36,
65
+ height: 36,
66
+ borderRadius: '50%',
67
+ cursor: 'pointer',
68
+ fontSize: 18
69
+ }
70
+ }, "\u2715")));
71
+ }
72
+
73
+ function VideoPlayer({
74
+ src,
75
+ poster,
76
+ autoplay = false,
77
+ controls = true,
78
+ width = '100%',
79
+ aspectRatio = '16/9',
80
+ className = ''
81
+ }) {
82
+ const ref = useRef(null);
83
+ const [playing, setPlaying] = useState(autoplay);
84
+ const toggle = () => {
85
+ if (ref.current) {
86
+ if (ref.current.paused) {
87
+ ref.current.play();
88
+ setPlaying(true);
89
+ } else {
90
+ ref.current.pause();
91
+ setPlaying(false);
92
+ }
93
+ }
94
+ };
95
+ return /*#__PURE__*/React.createElement("div", {
96
+ className: `qmd-video ${className}`,
97
+ style: {
98
+ position: 'relative',
99
+ width,
100
+ aspectRatio,
101
+ borderRadius: 12,
102
+ overflow: 'hidden',
103
+ background: '#000'
104
+ }
105
+ }, /*#__PURE__*/React.createElement("video", {
106
+ ref: ref,
107
+ src: src,
108
+ poster: poster,
109
+ autoPlay: autoplay,
110
+ controls: controls,
111
+ playsInline: true,
112
+ style: {
113
+ width: '100%',
114
+ height: '100%',
115
+ objectFit: 'cover'
116
+ },
117
+ onPlay: () => setPlaying(true),
118
+ onPause: () => setPlaying(false)
119
+ }), !controls && /*#__PURE__*/React.createElement("button", {
120
+ onClick: toggle,
121
+ style: {
122
+ position: 'absolute',
123
+ inset: 0,
124
+ border: 'none',
125
+ background: 'transparent',
126
+ cursor: 'pointer',
127
+ display: 'flex',
128
+ alignItems: 'center',
129
+ justifyContent: 'center'
130
+ }
131
+ }, !playing && /*#__PURE__*/React.createElement("div", {
132
+ style: {
133
+ width: 60,
134
+ height: 60,
135
+ borderRadius: '50%',
136
+ background: 'rgba(0,0,0,0.6)',
137
+ display: 'flex',
138
+ alignItems: 'center',
139
+ justifyContent: 'center'
140
+ }
141
+ }, /*#__PURE__*/React.createElement("div", {
142
+ style: {
143
+ width: 0,
144
+ height: 0,
145
+ borderLeft: '18px solid #fff',
146
+ borderTop: '12px solid transparent',
147
+ borderBottom: '12px solid transparent',
148
+ marginLeft: 4
149
+ }
150
+ }))));
151
+ }
152
+
153
+ function AudioPlayer({
154
+ src,
155
+ title,
156
+ artist,
157
+ cover,
158
+ className = ''
159
+ }) {
160
+ const ref = useRef(null);
161
+ const [playing, setPlaying] = useState(false);
162
+ const [progress, setProgress] = useState(0);
163
+ const [duration, setDuration] = useState(0);
164
+ const toggle = () => {
165
+ if (ref.current) {
166
+ if (playing) ref.current.pause();else ref.current.play();
167
+ }
168
+ };
169
+ const fmt = s => {
170
+ const m = Math.floor(s / 60);
171
+ return m + ':' + String(Math.floor(s % 60)).padStart(2, '0');
172
+ };
173
+ useEffect(() => {
174
+ const a = ref.current;
175
+ if (!a) return;
176
+ const onTime = () => setProgress(a.currentTime);
177
+ const onMeta = () => setDuration(a.duration);
178
+ a.addEventListener('timeupdate', onTime);
179
+ a.addEventListener('loadedmetadata', onMeta);
180
+ return () => {
181
+ a.removeEventListener('timeupdate', onTime);
182
+ a.removeEventListener('loadedmetadata', onMeta);
183
+ };
184
+ }, []);
185
+ return /*#__PURE__*/React.createElement("div", {
186
+ className: `qmd-audio ${className}`,
187
+ style: {
188
+ display: 'flex',
189
+ alignItems: 'center',
190
+ gap: 12,
191
+ padding: '12px 16px',
192
+ borderRadius: 14,
193
+ border: '1px solid #e4e4e7',
194
+ background: '#fff'
195
+ }
196
+ }, /*#__PURE__*/React.createElement("audio", {
197
+ ref: ref,
198
+ src: src,
199
+ onPlay: () => setPlaying(true),
200
+ onPause: () => setPlaying(false)
201
+ }), cover && /*#__PURE__*/React.createElement("img", {
202
+ src: cover,
203
+ alt: "",
204
+ style: {
205
+ width: 44,
206
+ height: 44,
207
+ borderRadius: 8,
208
+ objectFit: 'cover',
209
+ flexShrink: 0
210
+ }
211
+ }), /*#__PURE__*/React.createElement("div", {
212
+ style: {
213
+ flex: 1
214
+ }
215
+ }, title && /*#__PURE__*/React.createElement("div", {
216
+ style: {
217
+ fontSize: 13,
218
+ fontWeight: 600,
219
+ color: '#18181b'
220
+ }
221
+ }, title), artist && /*#__PURE__*/React.createElement("div", {
222
+ style: {
223
+ fontSize: 11,
224
+ color: '#71717a'
225
+ }
226
+ }, artist), /*#__PURE__*/React.createElement("div", {
227
+ style: {
228
+ display: 'flex',
229
+ alignItems: 'center',
230
+ gap: 6,
231
+ marginTop: 4
232
+ }
233
+ }, /*#__PURE__*/React.createElement("span", {
234
+ style: {
235
+ fontSize: 10,
236
+ color: '#a1a1aa',
237
+ width: 30
238
+ }
239
+ }, fmt(progress)), /*#__PURE__*/React.createElement("div", {
240
+ onClick: e => {
241
+ const r = e.currentTarget.getBoundingClientRect();
242
+ const pct = (e.clientX - r.left) / r.width;
243
+ ref.current.currentTime = pct * duration;
244
+ },
245
+ style: {
246
+ flex: 1,
247
+ height: 4,
248
+ borderRadius: 2,
249
+ background: '#e4e4e7',
250
+ cursor: 'pointer',
251
+ position: 'relative'
252
+ }
253
+ }, /*#__PURE__*/React.createElement("div", {
254
+ style: {
255
+ height: '100%',
256
+ width: duration ? `${progress / duration * 100}%` : '0%',
257
+ background: '#3b82f6',
258
+ borderRadius: 2,
259
+ transition: 'width 0.1s'
260
+ }
261
+ })), /*#__PURE__*/React.createElement("span", {
262
+ style: {
263
+ fontSize: 10,
264
+ color: '#a1a1aa',
265
+ width: 30
266
+ }
267
+ }, fmt(duration)))), /*#__PURE__*/React.createElement("button", {
268
+ onClick: toggle,
269
+ style: {
270
+ width: 36,
271
+ height: 36,
272
+ borderRadius: '50%',
273
+ border: 'none',
274
+ background: '#3b82f6',
275
+ color: '#fff',
276
+ cursor: 'pointer',
277
+ display: 'flex',
278
+ alignItems: 'center',
279
+ justifyContent: 'center',
280
+ flexShrink: 0
281
+ }
282
+ }, playing ? '⏸' : '▶'));
283
+ }
284
+
285
+ function Gallery({
286
+ images = [],
287
+ columns = 3,
288
+ gap = 8,
289
+ className = ''
290
+ }) {
291
+ const [active, setActive] = useState(null);
292
+ return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement("div", {
293
+ className: `qmd-gallery ${className}`,
294
+ style: {
295
+ display: 'grid',
296
+ gridTemplateColumns: `repeat(${columns},1fr)`,
297
+ gap
298
+ }
299
+ }, images.map((img, i) => {
300
+ const src = typeof img === 'string' ? img : img.src;
301
+ const alt = img.alt || '';
302
+ return /*#__PURE__*/React.createElement("img", {
303
+ key: i,
304
+ src: src,
305
+ alt: alt,
306
+ onClick: () => setActive(i),
307
+ style: {
308
+ width: '100%',
309
+ aspectRatio: '1',
310
+ objectFit: 'cover',
311
+ borderRadius: 8,
312
+ cursor: 'pointer',
313
+ transition: 'all 0.2s'
314
+ },
315
+ className: "qmd-gallery-item"
316
+ });
317
+ })), active != null && /*#__PURE__*/React.createElement("div", {
318
+ onClick: () => setActive(null),
319
+ style: {
320
+ position: 'fixed',
321
+ inset: 0,
322
+ zIndex: 99999,
323
+ background: 'rgba(0,0,0,0.9)',
324
+ display: 'flex',
325
+ alignItems: 'center',
326
+ justifyContent: 'center'
327
+ }
328
+ }, /*#__PURE__*/React.createElement("button", {
329
+ onClick: e => {
330
+ e.stopPropagation();
331
+ setActive(a => a > 0 ? a - 1 : images.length - 1);
332
+ },
333
+ style: {
334
+ position: 'absolute',
335
+ left: 20,
336
+ border: 'none',
337
+ background: 'rgba(255,255,255,0.1)',
338
+ color: '#fff',
339
+ width: 40,
340
+ height: 40,
341
+ borderRadius: '50%',
342
+ cursor: 'pointer',
343
+ fontSize: 18
344
+ }
345
+ }, "\u2039"), /*#__PURE__*/React.createElement("img", {
346
+ src: typeof images[active] === 'string' ? images[active] : images[active].src,
347
+ alt: "",
348
+ style: {
349
+ maxWidth: '85vw',
350
+ maxHeight: '85vh',
351
+ objectFit: 'contain',
352
+ borderRadius: 8
353
+ }
354
+ }), /*#__PURE__*/React.createElement("button", {
355
+ onClick: e => {
356
+ e.stopPropagation();
357
+ setActive(a => (a + 1) % images.length);
358
+ },
359
+ style: {
360
+ position: 'absolute',
361
+ right: 20,
362
+ border: 'none',
363
+ background: 'rgba(255,255,255,0.1)',
364
+ color: '#fff',
365
+ width: 40,
366
+ height: 40,
367
+ borderRadius: '50%',
368
+ cursor: 'pointer',
369
+ fontSize: 18
370
+ }
371
+ }, "\u203A"), /*#__PURE__*/React.createElement("button", {
372
+ onClick: () => setActive(null),
373
+ style: {
374
+ position: 'absolute',
375
+ top: 20,
376
+ right: 20,
377
+ border: 'none',
378
+ background: 'rgba(255,255,255,0.1)',
379
+ color: '#fff',
380
+ width: 36,
381
+ height: 36,
382
+ borderRadius: '50%',
383
+ cursor: 'pointer',
384
+ fontSize: 18
385
+ }
386
+ }, "\u2715"), /*#__PURE__*/React.createElement("div", {
387
+ style: {
388
+ position: 'absolute',
389
+ bottom: 20,
390
+ color: '#fff',
391
+ fontSize: 13
392
+ }
393
+ }, active + 1, " / ", images.length)));
394
+ }
395
+
396
+ /**
397
+ * Media SDK - API 客户端
398
+ * 媒体服务后端接口封装
399
+ *
400
+ * 使用 BaseApiClient 基类简化代码
401
+ */
402
+
403
+
404
+ /**
405
+ * 媒体 API 客户端
406
+ */
407
+ class MediaApiClient extends BaseApiClient {
408
+ constructor(config = {}) {
409
+ super('/media', config);
410
+ }
411
+
412
+ // ============ 上传 ============
413
+
414
+ /**
415
+ * 获取上传凭证
416
+ * @param {Object} options - 上传选项
417
+ */
418
+ async getUploadToken(options = {}) {
419
+ return this.post('/upload/token', options);
420
+ }
421
+
422
+ /**
423
+ * 上传图片
424
+ * @param {File} file - 文件对象
425
+ * @param {Object} options - 上传选项
426
+ * @param {Function} onProgress - 进度回调
427
+ */
428
+ async uploadImage(file, options = {}, onProgress = null) {
429
+ return this._upload('/upload/image', file, options, onProgress);
430
+ }
431
+
432
+ /**
433
+ * 上传视频
434
+ * @param {File} file - 文件对象
435
+ * @param {Object} options - 上传选项
436
+ * @param {Function} onProgress - 进度回调
437
+ */
438
+ async uploadVideo(file, options = {}, onProgress = null) {
439
+ return this._upload('/upload/video', file, options, onProgress);
440
+ }
441
+
442
+ /**
443
+ * 上传音频
444
+ * @param {File} file - 文件对象
445
+ * @param {Object} options - 上传选项
446
+ * @param {Function} onProgress - 进度回调
447
+ */
448
+ async uploadAudio(file, options = {}, onProgress = null) {
449
+ return this._upload('/upload/audio', file, options, onProgress);
450
+ }
451
+
452
+ /**
453
+ * 内部上传方法
454
+ */
455
+ async _upload(path, file, options, onProgress) {
456
+ const formData = new FormData();
457
+ formData.append('file', file);
458
+ Object.entries(options).forEach(([key, value]) => {
459
+ formData.append(key, value);
460
+ });
461
+ return new Promise((resolve, reject) => {
462
+ const xhr = new XMLHttpRequest();
463
+ const token = this.getToken();
464
+ if (onProgress) {
465
+ xhr.upload.onprogress = e => {
466
+ if (e.lengthComputable) {
467
+ onProgress(e.loaded / e.total);
468
+ }
469
+ };
470
+ }
471
+ xhr.onload = () => {
472
+ if (xhr.status >= 200 && xhr.status < 300) {
473
+ resolve(JSON.parse(xhr.responseText));
474
+ } else {
475
+ reject(new Error(`Upload failed: ${xhr.status}`));
476
+ }
477
+ };
478
+ xhr.onerror = () => reject(new Error('Upload failed'));
479
+ xhr.open('POST', `${this.baseUrl}${path}`);
480
+ if (token) xhr.setRequestHeader('Authorization', `Bearer ${token}`);
481
+ xhr.send(formData);
482
+ });
483
+ }
484
+
485
+ // ============ 媒体查询 ============
486
+
487
+ /**
488
+ * 获取媒体列表
489
+ * @param {Object} params - 查询参数
490
+ */
491
+ async getMediaList(params = {}) {
492
+ return this.get('/list', params);
493
+ }
494
+
495
+ /**
496
+ * 获取媒体详情
497
+ * @param {string} mediaId - 媒体 ID
498
+ */
499
+ async getMedia(mediaId) {
500
+ return this.get(`/${mediaId}`);
501
+ }
502
+
503
+ /**
504
+ * 删除媒体
505
+ * @param {string} mediaId - 媒体 ID
506
+ */
507
+ async deleteMedia(mediaId) {
508
+ return this.delete(`/${mediaId}`);
509
+ }
510
+
511
+ /**
512
+ * 批量删除媒体
513
+ * @param {string[]} mediaIds - 媒体 ID 列表
514
+ */
515
+ async batchDeleteMedia(mediaIds) {
516
+ return this.post('/batch-delete', {
517
+ media_ids: mediaIds
518
+ });
519
+ }
520
+
521
+ // ============ 图片处理 ============
522
+
523
+ /**
524
+ * 获取图片缩略图 URL
525
+ * @param {string} mediaId - 媒体 ID
526
+ * @param {Object} options - 处理选项
527
+ */
528
+ async getThumbnailUrl(mediaId, options = {}) {
529
+ return this.get(`/${mediaId}/thumbnail`, options);
530
+ }
531
+
532
+ /**
533
+ * 裁剪图片
534
+ * @param {string} mediaId - 媒体 ID
535
+ * @param {Object} cropOptions - 裁剪参数
536
+ */
537
+ async cropImage(mediaId, cropOptions) {
538
+ return this.post(`/${mediaId}/crop`, cropOptions);
539
+ }
540
+
541
+ /**
542
+ * 压缩图片
543
+ * @param {string} mediaId - 媒体 ID
544
+ * @param {Object} compressOptions - 压缩参数
545
+ */
546
+ async compressImage(mediaId, compressOptions) {
547
+ return this.post(`/${mediaId}/compress`, compressOptions);
548
+ }
549
+
550
+ // ============ 视频处理 ============
551
+
552
+ /**
553
+ * 获取视频封面
554
+ * @param {string} mediaId - 媒体 ID
555
+ * @param {number} time - 时间点(秒)
556
+ */
557
+ async getVideoCover(mediaId, time = 0) {
558
+ return this.get(`/${mediaId}/cover`, {
559
+ time
560
+ });
561
+ }
562
+
563
+ /**
564
+ * 转码视频
565
+ * @param {string} mediaId - 媒体 ID
566
+ * @param {Object} transcodeOptions - 转码参数
567
+ */
568
+ async transcodeVideo(mediaId, transcodeOptions) {
569
+ return this.post(`/${mediaId}/transcode`, transcodeOptions);
570
+ }
571
+
572
+ /**
573
+ * 获取转码状态
574
+ * @param {string} mediaId - 媒体 ID
575
+ */
576
+ async getTranscodeStatus(mediaId) {
577
+ return this.get(`/${mediaId}/transcode/status`);
578
+ }
579
+
580
+ // ============ 存储统计 ============
581
+
582
+ /**
583
+ * 获取存储使用情况
584
+ */
585
+ async getStorageUsage() {
586
+ return this.get('/usage');
587
+ }
588
+
589
+ /**
590
+ * 获取配额信息
591
+ */
592
+ async getQuota() {
593
+ return this.get('/quota');
594
+ }
595
+ }
596
+
597
+ // 创建默认实例
598
+ const mediaApi = new MediaApiClient();
599
+
600
+ /**
601
+ * Media SDK - React Hooks
602
+ */
603
+
604
+ function useMedia() {
605
+ const [media, setMedia] = useState([]);
606
+ const [uploading, setUploading] = useState(false);
607
+ const [progress, setProgress] = useState(0);
608
+ const [error, setError] = useState(null);
609
+ const fetchMedia = useCallback(async (params = {}) => {
610
+ try {
611
+ const result = await mediaApi.getMedia(params);
612
+ setMedia(result.data || []);
613
+ } catch (err) {
614
+ setError(err.message);
615
+ }
616
+ }, []);
617
+ const uploadMedia = useCallback(async (file, options = {}) => {
618
+ setUploading(true);
619
+ setProgress(0);
620
+ try {
621
+ const result = await mediaApi.uploadMedia(file, {
622
+ ...options,
623
+ onProgress: p => setProgress(p)
624
+ });
625
+ await fetchMedia();
626
+ return result;
627
+ } catch (err) {
628
+ setError(err.message);
629
+ throw err;
630
+ } finally {
631
+ setUploading(false);
632
+ setProgress(0);
633
+ }
634
+ }, [fetchMedia]);
635
+ const deleteMedia = useCallback(async mediaId => {
636
+ await mediaApi.deleteMedia(mediaId);
637
+ await fetchMedia();
638
+ }, [fetchMedia]);
639
+ return {
640
+ media,
641
+ uploading,
642
+ progress,
643
+ error,
644
+ fetchMedia,
645
+ uploadMedia,
646
+ deleteMedia
647
+ };
648
+ }
649
+
650
+ /**
651
+ * Media SDK - 国际化
652
+ */
653
+
654
+ const messages = {
655
+ zh: {
656
+ 'media.title': '媒体库',
657
+ 'media.upload': '上传',
658
+ 'media.delete': '删除',
659
+ 'media.preview': '预览',
660
+ 'media.download': '下载',
661
+ 'media.types.image': '图片',
662
+ 'media.types.video': '视频',
663
+ 'media.types.audio': '音频',
664
+ 'media.types.document': '文档',
665
+ 'media.status.uploading': '上传中...',
666
+ 'media.status.uploading_progress': '上传中 {progress}%',
667
+ 'media.status.success': '上传成功',
668
+ 'media.status.failed': '上传失败',
669
+ 'media.empty': '暂无媒体文件',
670
+ 'media.usage': '存储使用',
671
+ 'media.limit': '存储限制',
672
+ 'media.select_file': '选择文件',
673
+ 'loading': '加载中...'
674
+ },
675
+ en: {
676
+ 'media.title': 'Media Library',
677
+ 'media.upload': 'Upload',
678
+ 'media.delete': 'Delete',
679
+ 'media.preview': 'Preview',
680
+ 'media.download': 'Download',
681
+ 'media.types.image': 'Image',
682
+ 'media.types.video': 'Video',
683
+ 'media.types.audio': 'Audio',
684
+ 'media.types.document': 'Document',
685
+ 'media.status.uploading': 'Uploading...',
686
+ 'media.status.uploading_progress': 'Uploading {progress}%',
687
+ 'media.status.success': 'Upload successful',
688
+ 'media.status.failed': 'Upload failed',
689
+ 'media.empty': 'No media files',
690
+ 'media.usage': 'Storage Used',
691
+ 'media.limit': 'Storage Limit',
692
+ 'media.select_file': 'Select File',
693
+ 'loading': 'Loading...'
694
+ },
695
+ ja: {
696
+ 'media.title': 'メディアライブラリ',
697
+ 'media.upload': 'アップロード',
698
+ 'media.delete': '削除',
699
+ 'media.preview': 'プレビュー',
700
+ 'media.download': 'ダウンロード',
701
+ 'media.types.image': '画像',
702
+ 'media.types.video': '動画',
703
+ 'media.types.audio': '音声',
704
+ 'media.types.document': 'ドキュメント',
705
+ 'media.status.uploading': 'アップロード中...',
706
+ 'media.status.uploading_progress': 'アップロード中 {progress}%',
707
+ 'media.status.success': 'アップロード成功',
708
+ 'media.status.failed': 'アップロード失敗',
709
+ 'media.empty': 'メディアファイルがありません',
710
+ 'media.usage': 'ストレージ使用量',
711
+ 'media.limit': 'ストレージ制限',
712
+ 'media.select_file': 'ファイルを選択',
713
+ 'loading': '読み込み中...'
714
+ },
715
+ ko: {
716
+ 'media.title': '미디어 라이브러리',
717
+ 'media.upload': '업로드',
718
+ 'media.delete': '삭제',
719
+ 'media.preview': '미리보기',
720
+ 'media.download': '다운로드',
721
+ 'media.types.image': '이미지',
722
+ 'media.types.video': '비디오',
723
+ 'media.types.audio': '오디오',
724
+ 'media.types.document': '문서',
725
+ 'media.status.uploading': '업로드 중...',
726
+ 'media.status.uploading_progress': '업로드 중 {progress}%',
727
+ 'media.status.success': '업로드 성공',
728
+ 'media.status.failed': '업로드 실패',
729
+ 'media.empty': '미디어 파일이 없습니다',
730
+ 'media.usage': '스토리지 사용량',
731
+ 'media.limit': '스토리지 제한',
732
+ 'media.select_file': '파일 선택',
733
+ 'loading': '로딩 중...'
734
+ }
735
+ };
736
+ let currentLanguage = 'en';
737
+ function t(key, params = {}) {
738
+ let text = messages[currentLanguage]?.[key] || messages.en?.[key] || key;
739
+ Object.entries(params).forEach(([k, v]) => {
740
+ text = text.replace(new RegExp(`\\{${k}\\}`, 'g'), v);
741
+ });
742
+ return text;
743
+ }
744
+
745
+ /**
746
+ * Media SDK - 组件
747
+ */
748
+
749
+ function MediaGallery({
750
+ onSelect
751
+ }) {
752
+ const {
753
+ media,
754
+ loading,
755
+ deleteMedia
756
+ } = useMedia();
757
+ if (loading) return /*#__PURE__*/React.createElement("div", {
758
+ className: "eco-loading"
759
+ }, t('loading'));
760
+ return /*#__PURE__*/React.createElement("div", {
761
+ className: "eco-media-gallery"
762
+ }, media.map(item => /*#__PURE__*/React.createElement("div", {
763
+ key: item.id,
764
+ className: "eco-media-item",
765
+ onClick: () => onSelect?.(item)
766
+ }, item.type === 'image' ? /*#__PURE__*/React.createElement("img", {
767
+ src: item.url,
768
+ alt: item.name
769
+ }) : /*#__PURE__*/React.createElement("div", {
770
+ className: "eco-media-icon"
771
+ }, item.type === 'video' ? '🎬' : '📄'), /*#__PURE__*/React.createElement("div", {
772
+ className: "eco-media-name"
773
+ }, item.name), /*#__PURE__*/React.createElement("button", {
774
+ className: "eco-btn eco-btn-sm eco-btn-danger",
775
+ onClick: e => {
776
+ e.stopPropagation();
777
+ deleteMedia(item.id);
778
+ }
779
+ }, t('media.delete')))));
780
+ }
781
+
782
+ export { AudioPlayer, Gallery, ImageViewer, MediaGallery, VideoPlayer, useMedia };
783
+ //# sourceMappingURL=index.esm.js.map