@umituz/react-native-design-system 4.23.101 → 4.23.103
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/package.json +1 -1
- package/src/media/index.ts +4 -33
- package/src/media/infrastructure/utils/PermissionManager.ts +1 -1
- package/src/media/infrastructure/utils/media-collection-utils.ts +20 -6
- package/src/media/infrastructure/utils/mediaPickerMappers.ts +1 -1
- package/src/media/presentation/hooks/useMedia.ts +2 -2
- package/src/storage/infrastructure/repositories/BaseStorageOperations.ts +4 -4
- package/src/media/domain/entities/MediaAttachments.ts +0 -101
- package/src/media/infrastructure/services/MediaGenerationService.ts +0 -80
- package/src/media/infrastructure/services/MediaOptimizerService.ts +0 -32
- package/src/media/infrastructure/services/MediaUploadService.ts +0 -60
- package/src/media/infrastructure/services/MediaValidationService.ts +0 -61
- package/src/media/infrastructure/services/MultimediaFlashcardService.ts +0 -96
- package/src/media/presentation/hooks/multimedia.types.ts +0 -53
- package/src/media/presentation/hooks/useCardMultimediaFlashcard.ts +0 -102
- package/src/media/presentation/hooks/useMediaGeneration.ts +0 -18
- package/src/media/presentation/hooks/useMediaUpload.ts +0 -84
- package/src/media/presentation/hooks/useMediaValidation.ts +0 -93
- package/src/media/presentation/hooks/useMultimediaFlashcard.ts +0 -101
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@umituz/react-native-design-system",
|
|
3
|
-
"version": "4.23.
|
|
3
|
+
"version": "4.23.103",
|
|
4
4
|
"description": "Universal design system for React Native apps - Consolidated package with atoms, molecules, organisms, theme, typography, responsive, safe area, exception, infinite scroll, UUID, image, timezone, offline, onboarding, and loading utilities",
|
|
5
5
|
"main": "./src/index.ts",
|
|
6
6
|
"types": "./src/index.ts",
|
package/src/media/index.ts
CHANGED
|
@@ -1,16 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @umituz/react-native-media -
|
|
2
|
+
* @umituz/react-native-media - Public API
|
|
3
3
|
*
|
|
4
4
|
* Media picking capabilities for React Native apps
|
|
5
|
-
* Includes multimedia flashcard support
|
|
6
5
|
*
|
|
7
6
|
* Usage:
|
|
8
7
|
* import {
|
|
9
8
|
* useMedia,
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
* type
|
|
13
|
-
* type MultimediaFlashcard,
|
|
9
|
+
* MediaPickerService,
|
|
10
|
+
* type MediaAsset,
|
|
11
|
+
* type MediaPickerResult,
|
|
14
12
|
* } from '@umituz/react-native-media';
|
|
15
13
|
*/
|
|
16
14
|
|
|
@@ -90,30 +88,3 @@ export {
|
|
|
90
88
|
saveVideoToGallery,
|
|
91
89
|
type SaveToGalleryResult,
|
|
92
90
|
} from "./infrastructure/utils/file-media-utils";
|
|
93
|
-
|
|
94
|
-
// Media Attachment Types (Clean, no aliases)
|
|
95
|
-
export type {
|
|
96
|
-
MediaAttachmentType,
|
|
97
|
-
MediaPosition,
|
|
98
|
-
MediaAttachment,
|
|
99
|
-
MultimediaFlashcard,
|
|
100
|
-
MediaGenerationRequest,
|
|
101
|
-
MediaGenerationResult,
|
|
102
|
-
MediaUploadProgress,
|
|
103
|
-
MediaCompressionOptions,
|
|
104
|
-
MediaValidation,
|
|
105
|
-
MediaFile,
|
|
106
|
-
CreateMultimediaCardData,
|
|
107
|
-
} from "./domain/entities/MediaAttachments";
|
|
108
|
-
|
|
109
|
-
// Media Hooks
|
|
110
|
-
export { useMediaUpload } from "./presentation/hooks/useMediaUpload";
|
|
111
|
-
export { useMediaGeneration } from "./presentation/hooks/useMediaGeneration";
|
|
112
|
-
export { useMediaValidation } from "./presentation/hooks/useMediaValidation";
|
|
113
|
-
export { useMultimediaFlashcard } from "./presentation/hooks/useMultimediaFlashcard";
|
|
114
|
-
export type {
|
|
115
|
-
UseMediaUploadResult,
|
|
116
|
-
UseMediaGenerationResult,
|
|
117
|
-
UseMediaValidationResult,
|
|
118
|
-
UseMultimediaFlashcardResult,
|
|
119
|
-
} from "./presentation/hooks/multimedia.types";
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import * as ImagePicker from "expo-image-picker";
|
|
8
|
-
import { MediaLibraryPermission } from "../../domain/entities/
|
|
8
|
+
import { MediaLibraryPermission } from "../../domain/entities/Media";
|
|
9
9
|
import { mapPermissionStatus } from "./mediaPickerMappers";
|
|
10
10
|
|
|
11
11
|
/**
|
|
@@ -1,14 +1,28 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Media Collection Utilities
|
|
3
|
+
* Generic utilities for working with media collections
|
|
4
|
+
*/
|
|
5
5
|
|
|
6
6
|
type MediaType = "image" | "audio" | "video";
|
|
7
7
|
|
|
8
8
|
const FILE_SIZE_UNITS = ["Bytes", "KB", "MB", "GB"] as const;
|
|
9
9
|
|
|
10
|
+
/**
|
|
11
|
+
* Interface for media items with type property
|
|
12
|
+
*/
|
|
13
|
+
interface MediaWithType {
|
|
14
|
+
type: MediaType;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Interface for media items with file size
|
|
19
|
+
*/
|
|
20
|
+
interface MediaWithSize {
|
|
21
|
+
fileSize: number;
|
|
22
|
+
}
|
|
23
|
+
|
|
10
24
|
export function extractMediaTypes(
|
|
11
|
-
media: readonly
|
|
25
|
+
media: readonly MediaWithType[]
|
|
12
26
|
): MediaType[] {
|
|
13
27
|
const types = new Set<MediaType>();
|
|
14
28
|
media.forEach((m) => types.add(m.type));
|
|
@@ -16,7 +30,7 @@ export function extractMediaTypes(
|
|
|
16
30
|
}
|
|
17
31
|
|
|
18
32
|
export function calculateTotalSize(
|
|
19
|
-
media: readonly
|
|
33
|
+
media: readonly MediaWithSize[]
|
|
20
34
|
): number {
|
|
21
35
|
return media.reduce((total, m) => total + m.fileSize, 0);
|
|
22
36
|
}
|
|
@@ -12,8 +12,8 @@ import type {
|
|
|
12
12
|
MediaPickerOptions,
|
|
13
13
|
MediaPickerResult,
|
|
14
14
|
CameraOptions,
|
|
15
|
-
} from "../../domain/entities/
|
|
16
|
-
import { MediaLibraryPermission } from "../../domain/entities/
|
|
15
|
+
} from "../../domain/entities/Media";
|
|
16
|
+
import { MediaLibraryPermission } from "../../domain/entities/Media";
|
|
17
17
|
|
|
18
18
|
/**
|
|
19
19
|
* useMedia hook for complete media workflow
|
|
@@ -37,7 +37,7 @@ export class BaseStorageOperations {
|
|
|
37
37
|
return failure(new StorageDeserializationError(key, parseError), defaultValue);
|
|
38
38
|
}
|
|
39
39
|
} catch (_error) {
|
|
40
|
-
return failure(new StorageReadError(key,
|
|
40
|
+
return failure(new StorageReadError(key, _error), defaultValue);
|
|
41
41
|
}
|
|
42
42
|
}
|
|
43
43
|
|
|
@@ -56,7 +56,7 @@ export class BaseStorageOperations {
|
|
|
56
56
|
await AsyncStorage.setItem(key, serialized);
|
|
57
57
|
return success(value);
|
|
58
58
|
} catch (_error) {
|
|
59
|
-
return failure(new StorageWriteError(key,
|
|
59
|
+
return failure(new StorageWriteError(key, _error));
|
|
60
60
|
}
|
|
61
61
|
}
|
|
62
62
|
|
|
@@ -68,7 +68,7 @@ export class BaseStorageOperations {
|
|
|
68
68
|
await AsyncStorage.removeItem(key);
|
|
69
69
|
return success(undefined);
|
|
70
70
|
} catch (_error) {
|
|
71
|
-
return failure(new StorageDeleteError(key,
|
|
71
|
+
return failure(new StorageDeleteError(key, _error));
|
|
72
72
|
}
|
|
73
73
|
}
|
|
74
74
|
|
|
@@ -92,7 +92,7 @@ export class BaseStorageOperations {
|
|
|
92
92
|
await AsyncStorage.clear();
|
|
93
93
|
return success(undefined);
|
|
94
94
|
} catch (_error) {
|
|
95
|
-
return failure(new StorageDeleteError('ALL_KEYS',
|
|
95
|
+
return failure(new StorageDeleteError('ALL_KEYS', _error));
|
|
96
96
|
}
|
|
97
97
|
}
|
|
98
98
|
}
|
|
@@ -1,101 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Media Attachments Types
|
|
3
|
-
* Types for media attachments in flashcards and content
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
export type MediaAttachmentType = "image" | "audio" | "video";
|
|
7
|
-
export type MediaPosition = "front" | "back" | "both";
|
|
8
|
-
|
|
9
|
-
export interface MediaAttachment {
|
|
10
|
-
id: string;
|
|
11
|
-
type: MediaAttachmentType;
|
|
12
|
-
position: MediaPosition;
|
|
13
|
-
url: string;
|
|
14
|
-
localPath?: string;
|
|
15
|
-
filename: string;
|
|
16
|
-
fileSize: number;
|
|
17
|
-
mimeType: string;
|
|
18
|
-
duration?: number;
|
|
19
|
-
thumbnailUrl?: string;
|
|
20
|
-
caption?: string;
|
|
21
|
-
isDownloaded: boolean;
|
|
22
|
-
createdAt: string;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
export interface MultimediaFlashcard {
|
|
26
|
-
id: string;
|
|
27
|
-
front: string;
|
|
28
|
-
back: string;
|
|
29
|
-
difficulty: "easy" | "medium" | "hard";
|
|
30
|
-
tags: string[];
|
|
31
|
-
createdAt?: string;
|
|
32
|
-
updatedAt?: string;
|
|
33
|
-
media: MediaAttachment[];
|
|
34
|
-
hasMedia: boolean;
|
|
35
|
-
mediaType: MediaAttachmentType[];
|
|
36
|
-
isDownloaded: boolean;
|
|
37
|
-
estimatedSize: number;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
export interface MediaGenerationRequest {
|
|
41
|
-
type: "text_to_image" | "text_to_audio" | "image_search";
|
|
42
|
-
input: {
|
|
43
|
-
text?: string;
|
|
44
|
-
prompt?: string;
|
|
45
|
-
language?: string;
|
|
46
|
-
voice?: "male" | "female" | "neutral";
|
|
47
|
-
style?: "realistic" | "cartoon" | "artistic";
|
|
48
|
-
};
|
|
49
|
-
options: {
|
|
50
|
-
maxResults?: number;
|
|
51
|
-
quality?: "low" | "medium" | "high";
|
|
52
|
-
format?: "jpeg" | "png" | "mp3" | "wav";
|
|
53
|
-
};
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
export interface MediaGenerationResult {
|
|
57
|
-
success: boolean;
|
|
58
|
-
attachments: MediaAttachment[];
|
|
59
|
-
creditsUsed: number;
|
|
60
|
-
processingTime: number;
|
|
61
|
-
error?: string;
|
|
62
|
-
requestId: string;
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
export interface MediaUploadProgress {
|
|
66
|
-
fileId: string;
|
|
67
|
-
progress: number;
|
|
68
|
-
status: "uploading" | "processing" | "completed" | "error";
|
|
69
|
-
error?: string;
|
|
70
|
-
url?: string;
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
export interface MediaCompressionOptions {
|
|
74
|
-
quality: number;
|
|
75
|
-
maxWidth?: number;
|
|
76
|
-
maxHeight?: number;
|
|
77
|
-
maxFileSize?: number;
|
|
78
|
-
format?: "jpeg" | "png" | "webp";
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
export interface MediaFile {
|
|
82
|
-
name: string;
|
|
83
|
-
type: string;
|
|
84
|
-
size: number;
|
|
85
|
-
uri?: string;
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
export interface CreateMultimediaCardData {
|
|
89
|
-
front: string;
|
|
90
|
-
back: string;
|
|
91
|
-
difficulty?: "easy" | "medium" | "hard";
|
|
92
|
-
tags?: string[];
|
|
93
|
-
media?: MediaAttachment[];
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
export interface MediaValidation {
|
|
97
|
-
isValid: boolean;
|
|
98
|
-
errors: string[];
|
|
99
|
-
warnings: string[];
|
|
100
|
-
recommendations: string[];
|
|
101
|
-
}
|
|
@@ -1,80 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Media Generation Service
|
|
3
|
-
* Handles AI media generation (text-to-image, text-to-audio)
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import type {
|
|
7
|
-
MediaAttachment,
|
|
8
|
-
MediaGenerationRequest,
|
|
9
|
-
MediaGenerationResult,
|
|
10
|
-
MediaType,
|
|
11
|
-
MediaPosition,
|
|
12
|
-
} from "../../domain/entities/MediaAttachments";
|
|
13
|
-
|
|
14
|
-
export class MediaGenerationService {
|
|
15
|
-
/**
|
|
16
|
-
* Generate media from AI (text-to-image, text-to-audio, etc.)
|
|
17
|
-
*/
|
|
18
|
-
async generateMedia(
|
|
19
|
-
request: MediaGenerationRequest,
|
|
20
|
-
): Promise<MediaGenerationResult> {
|
|
21
|
-
try {
|
|
22
|
-
const startTime = Date.now();
|
|
23
|
-
|
|
24
|
-
// Simulate AI generation
|
|
25
|
-
await new Promise((resolve) => setTimeout(resolve, 3000));
|
|
26
|
-
|
|
27
|
-
const attachments: MediaAttachment[] = [];
|
|
28
|
-
|
|
29
|
-
switch (request.type) {
|
|
30
|
-
case "text_to_image":
|
|
31
|
-
for (let i = 0; i < (request.options.maxResults || 1); i++) {
|
|
32
|
-
attachments.push({
|
|
33
|
-
id: `ai_img_${Date.now()}_${i}`,
|
|
34
|
-
type: "image" as MediaType,
|
|
35
|
-
position: "both" as MediaPosition,
|
|
36
|
-
url: `https://picsum.photos/400/300?random=${Date.now() + i}`,
|
|
37
|
-
filename: `ai_generated_${i}.jpg`,
|
|
38
|
-
fileSize: 150000,
|
|
39
|
-
mimeType: "image/jpeg",
|
|
40
|
-
isDownloaded: false,
|
|
41
|
-
createdAt: new Date().toISOString(),
|
|
42
|
-
});
|
|
43
|
-
}
|
|
44
|
-
break;
|
|
45
|
-
|
|
46
|
-
case "text_to_audio":
|
|
47
|
-
attachments.push({
|
|
48
|
-
id: `ai_audio_${Date.now()}`,
|
|
49
|
-
type: "audio" as MediaType,
|
|
50
|
-
position: "back" as MediaPosition,
|
|
51
|
-
url: `https://example.com/audio_${Date.now()}.mp3`,
|
|
52
|
-
filename: `ai_generated_${Date.now()}.mp3`,
|
|
53
|
-
fileSize: 80000,
|
|
54
|
-
mimeType: "audio/mp3",
|
|
55
|
-
duration: 10,
|
|
56
|
-
isDownloaded: false,
|
|
57
|
-
createdAt: new Date().toISOString(),
|
|
58
|
-
});
|
|
59
|
-
break;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
return {
|
|
63
|
-
success: true,
|
|
64
|
-
attachments,
|
|
65
|
-
creditsUsed: request.type === "text_to_image" ? 5 : 3,
|
|
66
|
-
processingTime: Date.now() - startTime,
|
|
67
|
-
requestId: `req_${Date.now()}`,
|
|
68
|
-
};
|
|
69
|
-
} catch (error) {
|
|
70
|
-
return {
|
|
71
|
-
success: false,
|
|
72
|
-
attachments: [],
|
|
73
|
-
creditsUsed: 0,
|
|
74
|
-
processingTime: 0,
|
|
75
|
-
error: error instanceof Error ? error.message : "Unknown error",
|
|
76
|
-
requestId: "",
|
|
77
|
-
};
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
}
|
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Media Optimizer Service
|
|
3
|
-
* Handles media optimization and deletion
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import type {
|
|
7
|
-
MediaAttachment,
|
|
8
|
-
MediaCompressionOptions,
|
|
9
|
-
} from "../../domain/entities/MediaAttachments";
|
|
10
|
-
|
|
11
|
-
export class MediaOptimizerService {
|
|
12
|
-
/**
|
|
13
|
-
* Optimize media file
|
|
14
|
-
*/
|
|
15
|
-
async optimizeMedia(
|
|
16
|
-
attachment: MediaAttachment,
|
|
17
|
-
options: MediaCompressionOptions,
|
|
18
|
-
): Promise<MediaAttachment> {
|
|
19
|
-
return {
|
|
20
|
-
...attachment,
|
|
21
|
-
fileSize: Math.floor(attachment.fileSize * options.quality),
|
|
22
|
-
url: `${attachment.url}?optimized=true`,
|
|
23
|
-
};
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
/**
|
|
27
|
-
* Delete media attachment
|
|
28
|
-
*/
|
|
29
|
-
async deleteMedia(_attachmentId: string): Promise<void> {
|
|
30
|
-
// Mock implementation
|
|
31
|
-
}
|
|
32
|
-
}
|
|
@@ -1,60 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Media Upload Service
|
|
3
|
-
* Handles media upload, download, and URL operations
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import type {
|
|
7
|
-
MediaAttachment,
|
|
8
|
-
MediaCompressionOptions,
|
|
9
|
-
MediaFile,
|
|
10
|
-
} from "../../domain/entities/MediaAttachments";
|
|
11
|
-
import { generateThumbnail, getMediaDuration } from "../utils/file-media-utils";
|
|
12
|
-
import { getMediaTypeFromMime } from "../utils/mime-type-detector";
|
|
13
|
-
|
|
14
|
-
export class MediaUploadService {
|
|
15
|
-
/**
|
|
16
|
-
* Upload media file with optional compression
|
|
17
|
-
*/
|
|
18
|
-
async uploadMedia(
|
|
19
|
-
file: MediaFile,
|
|
20
|
-
_options?: MediaCompressionOptions,
|
|
21
|
-
): Promise<MediaAttachment> {
|
|
22
|
-
try {
|
|
23
|
-
// Simulate upload process
|
|
24
|
-
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
25
|
-
|
|
26
|
-
const attachment: MediaAttachment = {
|
|
27
|
-
id: `media_${Date.now()}`,
|
|
28
|
-
type: getMediaTypeFromMime(file.type),
|
|
29
|
-
position: "both",
|
|
30
|
-
url: `https://storage.example.com/media/${Date.now()}_${file.name}`,
|
|
31
|
-
filename: file.name,
|
|
32
|
-
fileSize: file.size || 100000,
|
|
33
|
-
mimeType: file.type,
|
|
34
|
-
duration: await getMediaDuration(file),
|
|
35
|
-
thumbnailUrl: generateThumbnail(file),
|
|
36
|
-
caption: "",
|
|
37
|
-
isDownloaded: true,
|
|
38
|
-
createdAt: new Date().toISOString(),
|
|
39
|
-
};
|
|
40
|
-
|
|
41
|
-
return attachment;
|
|
42
|
-
} catch (error) {
|
|
43
|
-
throw new Error(`Failed to upload media: ${error}`);
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
/**
|
|
48
|
-
* Get media URL
|
|
49
|
-
*/
|
|
50
|
-
async getMediaUrl(attachmentId: string): Promise<string> {
|
|
51
|
-
return `https://storage.example.com/media/${attachmentId}`;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
/**
|
|
55
|
-
* Download media to local storage
|
|
56
|
-
*/
|
|
57
|
-
async downloadMedia(attachmentId: string): Promise<string> {
|
|
58
|
-
return `/local/storage/${attachmentId}`;
|
|
59
|
-
}
|
|
60
|
-
}
|
|
@@ -1,61 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Media Validation Service
|
|
3
|
-
* Handles media file validation before upload
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import { formatFileSize } from "../utils/media-collection-utils";
|
|
7
|
-
import type { MediaValidation, MediaFile } from "../../domain/entities/MediaAttachments";
|
|
8
|
-
|
|
9
|
-
export class MediaValidationService {
|
|
10
|
-
/**
|
|
11
|
-
* Validate media file before upload
|
|
12
|
-
*/
|
|
13
|
-
async validateMedia(file: MediaFile): Promise<MediaValidation> {
|
|
14
|
-
try {
|
|
15
|
-
const errors: string[] = [];
|
|
16
|
-
const warnings: string[] = [];
|
|
17
|
-
const recommendations: string[] = [];
|
|
18
|
-
|
|
19
|
-
// File size validation
|
|
20
|
-
const maxSize = 50 * 1024 * 1024; // 50MB
|
|
21
|
-
if (file.size > maxSize) {
|
|
22
|
-
errors.push(
|
|
23
|
-
`File size (${formatFileSize(file.size)}) exceeds maximum allowed size (${formatFileSize(maxSize)})`,
|
|
24
|
-
);
|
|
25
|
-
} else if (file.size > 10 * 1024 * 1024) {
|
|
26
|
-
warnings.push(`Large file size may impact performance`);
|
|
27
|
-
recommendations.push("Consider compressing file");
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
// File type validation
|
|
31
|
-
const supportedTypes = [
|
|
32
|
-
"image/jpeg",
|
|
33
|
-
"image/png",
|
|
34
|
-
"image/webp",
|
|
35
|
-
"audio/mp3",
|
|
36
|
-
"audio/wav",
|
|
37
|
-
"audio/m4a",
|
|
38
|
-
"video/mp4",
|
|
39
|
-
"video/mov",
|
|
40
|
-
];
|
|
41
|
-
|
|
42
|
-
if (!supportedTypes.includes(file.type)) {
|
|
43
|
-
errors.push(`Unsupported file type: ${file.type}`);
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
return {
|
|
47
|
-
isValid: errors.length === 0,
|
|
48
|
-
errors,
|
|
49
|
-
warnings,
|
|
50
|
-
recommendations,
|
|
51
|
-
};
|
|
52
|
-
} catch (error) {
|
|
53
|
-
return {
|
|
54
|
-
isValid: false,
|
|
55
|
-
errors: [error instanceof Error ? error.message : "Validation failed"],
|
|
56
|
-
warnings: [],
|
|
57
|
-
recommendations: [],
|
|
58
|
-
};
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
}
|
|
@@ -1,96 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Multimedia Flashcard Service
|
|
3
|
-
* Media attachments for flashcards - Main entry point
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import type {
|
|
7
|
-
MediaAttachment,
|
|
8
|
-
MediaFile,
|
|
9
|
-
MediaGenerationRequest,
|
|
10
|
-
MediaGenerationResult,
|
|
11
|
-
MediaCompressionOptions,
|
|
12
|
-
MediaValidation,
|
|
13
|
-
} from "../../domain/entities/MediaAttachments";
|
|
14
|
-
import { MediaUploadService } from "./MediaUploadService";
|
|
15
|
-
import { MediaGenerationService } from "./MediaGenerationService";
|
|
16
|
-
import { MediaValidationService } from "./MediaValidationService";
|
|
17
|
-
import { MediaOptimizerService } from "./MediaOptimizerService";
|
|
18
|
-
|
|
19
|
-
export class MultimediaFlashcardService {
|
|
20
|
-
private static instance: MultimediaFlashcardService;
|
|
21
|
-
private uploadService: MediaUploadService;
|
|
22
|
-
private generationService: MediaGenerationService;
|
|
23
|
-
private validationService: MediaValidationService;
|
|
24
|
-
private optimizerService: MediaOptimizerService;
|
|
25
|
-
|
|
26
|
-
private constructor() {
|
|
27
|
-
this.uploadService = new MediaUploadService();
|
|
28
|
-
this.generationService = new MediaGenerationService();
|
|
29
|
-
this.validationService = new MediaValidationService();
|
|
30
|
-
this.optimizerService = new MediaOptimizerService();
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
static getInstance(): MultimediaFlashcardService {
|
|
34
|
-
if (!MultimediaFlashcardService.instance) {
|
|
35
|
-
MultimediaFlashcardService.instance = new MultimediaFlashcardService();
|
|
36
|
-
}
|
|
37
|
-
return MultimediaFlashcardService.instance;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
/**
|
|
41
|
-
* Upload media file with optional compression
|
|
42
|
-
*/
|
|
43
|
-
async uploadMedia(
|
|
44
|
-
file: MediaFile,
|
|
45
|
-
options?: MediaCompressionOptions,
|
|
46
|
-
): Promise<MediaAttachment> {
|
|
47
|
-
return this.uploadService.uploadMedia(file, options);
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
/**
|
|
51
|
-
* Generate media from AI (text-to-image, text-to-audio, etc.)
|
|
52
|
-
*/
|
|
53
|
-
async generateMedia(
|
|
54
|
-
request: MediaGenerationRequest,
|
|
55
|
-
): Promise<MediaGenerationResult> {
|
|
56
|
-
return this.generationService.generateMedia(request);
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
/**
|
|
60
|
-
* Validate media file before upload
|
|
61
|
-
*/
|
|
62
|
-
async validateMedia(file: MediaFile): Promise<MediaValidation> {
|
|
63
|
-
return this.validationService.validateMedia(file);
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
/**
|
|
67
|
-
* Optimize media file
|
|
68
|
-
*/
|
|
69
|
-
async optimizeMedia(
|
|
70
|
-
attachment: MediaAttachment,
|
|
71
|
-
options: MediaCompressionOptions,
|
|
72
|
-
): Promise<MediaAttachment> {
|
|
73
|
-
return this.optimizerService.optimizeMedia(attachment, options);
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
/**
|
|
77
|
-
* Delete media attachment
|
|
78
|
-
*/
|
|
79
|
-
async deleteMedia(attachmentId: string): Promise<void> {
|
|
80
|
-
return this.optimizerService.deleteMedia(attachmentId);
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
/**
|
|
84
|
-
* Get media URL
|
|
85
|
-
*/
|
|
86
|
-
async getMediaUrl(attachmentId: string): Promise<string> {
|
|
87
|
-
return this.uploadService.getMediaUrl(attachmentId);
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
/**
|
|
91
|
-
* Download media to local storage
|
|
92
|
-
*/
|
|
93
|
-
async downloadMedia(attachmentId: string): Promise<string> {
|
|
94
|
-
return this.uploadService.downloadMedia(attachmentId);
|
|
95
|
-
}
|
|
96
|
-
}
|
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Multimedia Flashcard Hook Types
|
|
3
|
-
* Type definitions for multimedia hooks
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import type {
|
|
7
|
-
MediaAttachment,
|
|
8
|
-
MediaFile,
|
|
9
|
-
MediaGenerationRequest,
|
|
10
|
-
MediaGenerationResult,
|
|
11
|
-
MediaCompressionOptions,
|
|
12
|
-
MediaValidation,
|
|
13
|
-
MediaUploadProgress,
|
|
14
|
-
MultimediaFlashcard,
|
|
15
|
-
CreateMultimediaCardData,
|
|
16
|
-
} from "../../domain/entities/MediaAttachments";
|
|
17
|
-
|
|
18
|
-
export interface UseMediaUploadResult {
|
|
19
|
-
uploadMedia: (
|
|
20
|
-
file: MediaFile,
|
|
21
|
-
options?: MediaCompressionOptions,
|
|
22
|
-
) => Promise<MediaAttachment>;
|
|
23
|
-
isUploading: boolean;
|
|
24
|
-
uploadProgress: MediaUploadProgress | null;
|
|
25
|
-
error: string | null;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
export interface UseMediaGenerationResult {
|
|
29
|
-
generateMedia: (
|
|
30
|
-
request: MediaGenerationRequest,
|
|
31
|
-
) => Promise<MediaGenerationResult>;
|
|
32
|
-
isGenerating: boolean;
|
|
33
|
-
generationResult: MediaGenerationResult | null;
|
|
34
|
-
error: string | null;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
export interface UseMediaValidationResult {
|
|
38
|
-
validateMedia: (file: MediaFile) => Promise<MediaValidation>;
|
|
39
|
-
isValidating: boolean;
|
|
40
|
-
validation: MediaValidation | null;
|
|
41
|
-
error: string | null;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
export interface UseMultimediaFlashcardResult {
|
|
45
|
-
createMultimediaCard: (cardData: CreateMultimediaCardData) => Promise<MultimediaFlashcard>;
|
|
46
|
-
updateMedia: (
|
|
47
|
-
cardId: string,
|
|
48
|
-
media: MediaAttachment[],
|
|
49
|
-
) => Promise<MultimediaFlashcard>;
|
|
50
|
-
deleteMedia: (attachmentId: string) => Promise<void>;
|
|
51
|
-
isProcessing: boolean;
|
|
52
|
-
error: string | null;
|
|
53
|
-
}
|
|
@@ -1,102 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Card Multimedia Flashcard Hooks
|
|
3
|
-
* Main hook and exports for card multimedia functionality
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import { useState, useCallback } from "react";
|
|
7
|
-
import { calculateTotalSize, extractMediaTypes } from "../../infrastructure/utils/media-collection-utils";
|
|
8
|
-
import type { UseCardMultimediaFlashcardResult } from "./card-multimedia.types";
|
|
9
|
-
import type {
|
|
10
|
-
MediaAttachment as CardMediaAttachment,
|
|
11
|
-
MultimediaFlashcard as CardMultimediaFlashcard,
|
|
12
|
-
CreateMultimediaCardData as CreateCardMultimediaData,
|
|
13
|
-
} from "../../domain/entities/MediaAttachments";
|
|
14
|
-
|
|
15
|
-
// Export individual hooks
|
|
16
|
-
export { useCardMediaUpload } from "./useCardMediaUpload";
|
|
17
|
-
export { useCardMediaGeneration } from "./useCardMediaGeneration";
|
|
18
|
-
export { useCardMediaValidation } from "./useCardMediaValidation";
|
|
19
|
-
|
|
20
|
-
// Export types
|
|
21
|
-
export type {
|
|
22
|
-
UseCardMediaUploadResult,
|
|
23
|
-
UseCardMediaGenerationResult,
|
|
24
|
-
UseCardMediaValidationResult,
|
|
25
|
-
UseCardMultimediaFlashcardResult,
|
|
26
|
-
} from "./card-multimedia.types";
|
|
27
|
-
|
|
28
|
-
/**
|
|
29
|
-
* Main hook for card multimedia flashcard operations
|
|
30
|
-
*/
|
|
31
|
-
export const useCardMultimediaFlashcard =
|
|
32
|
-
(): UseCardMultimediaFlashcardResult => {
|
|
33
|
-
const [isProcessing, setIsProcessing] = useState(false);
|
|
34
|
-
const [error, setError] = useState<string | null>(null);
|
|
35
|
-
|
|
36
|
-
const createCardMultimedia = useCallback(
|
|
37
|
-
async (cardData: CreateCardMultimediaData): Promise<CardMultimediaFlashcard> => {
|
|
38
|
-
try {
|
|
39
|
-
setIsProcessing(true);
|
|
40
|
-
setError(null);
|
|
41
|
-
|
|
42
|
-
// Simulate card creation
|
|
43
|
-
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
44
|
-
|
|
45
|
-
const card: CardMultimediaFlashcard = {
|
|
46
|
-
id: `card_multimedia_${Date.now()}`,
|
|
47
|
-
front: cardData.front || "",
|
|
48
|
-
back: cardData.back || "",
|
|
49
|
-
difficulty: cardData.difficulty || "medium",
|
|
50
|
-
tags: cardData.tags || [],
|
|
51
|
-
media: cardData.media || [],
|
|
52
|
-
hasMedia: (cardData.media || []).length > 0,
|
|
53
|
-
mediaType: extractMediaTypes(cardData.media || []),
|
|
54
|
-
isDownloaded: (cardData.media || []).every(
|
|
55
|
-
(m: CardMediaAttachment) => m.isDownloaded,
|
|
56
|
-
),
|
|
57
|
-
estimatedSize: calculateTotalSize(cardData.media || []),
|
|
58
|
-
createdAt: new Date().toISOString(),
|
|
59
|
-
};
|
|
60
|
-
|
|
61
|
-
return card;
|
|
62
|
-
} catch (err) {
|
|
63
|
-
const errorMessage =
|
|
64
|
-
err instanceof Error ? err.message : "Card creation failed";
|
|
65
|
-
setError(errorMessage);
|
|
66
|
-
setIsProcessing(false);
|
|
67
|
-
throw err;
|
|
68
|
-
} finally {
|
|
69
|
-
setIsProcessing(false);
|
|
70
|
-
}
|
|
71
|
-
},
|
|
72
|
-
[],
|
|
73
|
-
);
|
|
74
|
-
|
|
75
|
-
const updateCardMedia = useCallback(
|
|
76
|
-
async (
|
|
77
|
-
_cardId: string,
|
|
78
|
-
_media: CardMediaAttachment[],
|
|
79
|
-
): Promise<CardMultimediaFlashcard> => {
|
|
80
|
-
// Mock implementation
|
|
81
|
-
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
82
|
-
return {} as CardMultimediaFlashcard;
|
|
83
|
-
},
|
|
84
|
-
[],
|
|
85
|
-
);
|
|
86
|
-
|
|
87
|
-
const deleteCardMedia = useCallback(
|
|
88
|
-
async (_attachmentId: string): Promise<void> => {
|
|
89
|
-
// Mock implementation
|
|
90
|
-
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
91
|
-
},
|
|
92
|
-
[],
|
|
93
|
-
);
|
|
94
|
-
|
|
95
|
-
return {
|
|
96
|
-
createCardMultimedia,
|
|
97
|
-
updateCardMedia,
|
|
98
|
-
deleteCardMedia,
|
|
99
|
-
isProcessing,
|
|
100
|
-
error,
|
|
101
|
-
};
|
|
102
|
-
};
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Media Generation Hook
|
|
3
|
-
* Hook for generating media with AI
|
|
4
|
-
* Now a thin wrapper around useGenericMediaGeneration
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
import { useGenericMediaGeneration } from "../../infrastructure/hooks/useGenericMediaGeneration";
|
|
8
|
-
import type { UseMediaGenerationResult } from "./multimedia.types";
|
|
9
|
-
import type {
|
|
10
|
-
MediaAttachment,
|
|
11
|
-
MediaGenerationRequest,
|
|
12
|
-
} from "../../domain/entities/MediaAttachments";
|
|
13
|
-
|
|
14
|
-
export const useMediaGeneration = (): UseMediaGenerationResult => {
|
|
15
|
-
return useGenericMediaGeneration<MediaAttachment, MediaGenerationRequest>(
|
|
16
|
-
(baseAttachment) => baseAttachment as MediaAttachment
|
|
17
|
-
);
|
|
18
|
-
};
|
|
@@ -1,84 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Media Upload Hook
|
|
3
|
-
* Hook for uploading media files
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import { useState, useCallback } from "react";
|
|
7
|
-
import { generateThumbnail, getMediaDuration } from "../../infrastructure/utils/file-media-utils";
|
|
8
|
-
import { getMediaTypeFromMime } from "../../infrastructure/utils/mime-type-detector";
|
|
9
|
-
import type { UseMediaUploadResult } from "./multimedia.types";
|
|
10
|
-
import type {
|
|
11
|
-
MediaAttachment,
|
|
12
|
-
MediaCompressionOptions,
|
|
13
|
-
MediaFile,
|
|
14
|
-
MediaUploadProgress,
|
|
15
|
-
} from "../../domain/entities/MediaAttachments";
|
|
16
|
-
|
|
17
|
-
export const useMediaUpload = (): UseMediaUploadResult => {
|
|
18
|
-
const [isUploading, setIsUploading] = useState(false);
|
|
19
|
-
const [uploadProgress, setUploadProgress] =
|
|
20
|
-
useState<MediaUploadProgress | null>(null);
|
|
21
|
-
const [error, setError] = useState<string | null>(null);
|
|
22
|
-
|
|
23
|
-
const uploadMedia = useCallback(
|
|
24
|
-
async (file: MediaFile, _options?: MediaCompressionOptions) => {
|
|
25
|
-
try {
|
|
26
|
-
setIsUploading(true);
|
|
27
|
-
setError(null);
|
|
28
|
-
|
|
29
|
-
// Simulate upload progress
|
|
30
|
-
setUploadProgress({
|
|
31
|
-
fileId: `upload_${Date.now()}`,
|
|
32
|
-
progress: 0,
|
|
33
|
-
status: "uploading",
|
|
34
|
-
});
|
|
35
|
-
|
|
36
|
-
// Simulate progress updates
|
|
37
|
-
for (let i = 1; i <= 100; i += 10) {
|
|
38
|
-
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
39
|
-
setUploadProgress((prev) => (prev ? { ...prev, progress: i } : null));
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
const attachment: MediaAttachment = {
|
|
43
|
-
id: `media_${Date.now()}`,
|
|
44
|
-
type: getMediaTypeFromMime(file.type),
|
|
45
|
-
position: "both",
|
|
46
|
-
url: `https://storage.example.com/media/${Date.now()}_${file.name}`,
|
|
47
|
-
filename: file.name,
|
|
48
|
-
fileSize: file.size || 100000,
|
|
49
|
-
mimeType: file.type,
|
|
50
|
-
duration: await getMediaDuration(file),
|
|
51
|
-
thumbnailUrl: generateThumbnail(file),
|
|
52
|
-
caption: "",
|
|
53
|
-
isDownloaded: true,
|
|
54
|
-
createdAt: new Date().toISOString(),
|
|
55
|
-
};
|
|
56
|
-
|
|
57
|
-
setUploadProgress({
|
|
58
|
-
fileId: `upload_${Date.now()}`,
|
|
59
|
-
progress: 100,
|
|
60
|
-
status: "completed",
|
|
61
|
-
url: attachment.url,
|
|
62
|
-
});
|
|
63
|
-
|
|
64
|
-
return attachment;
|
|
65
|
-
} catch (err) {
|
|
66
|
-
const errorMessage =
|
|
67
|
-
err instanceof Error ? err.message : "Upload failed";
|
|
68
|
-
setError(errorMessage);
|
|
69
|
-
setIsUploading(false);
|
|
70
|
-
throw err;
|
|
71
|
-
} finally {
|
|
72
|
-
setIsUploading(false);
|
|
73
|
-
}
|
|
74
|
-
},
|
|
75
|
-
[],
|
|
76
|
-
);
|
|
77
|
-
|
|
78
|
-
return {
|
|
79
|
-
uploadMedia,
|
|
80
|
-
isUploading,
|
|
81
|
-
uploadProgress,
|
|
82
|
-
error,
|
|
83
|
-
};
|
|
84
|
-
};
|
|
@@ -1,93 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Media Validation Hook
|
|
3
|
-
* Hook for validating media files
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import { useState, useCallback } from "react";
|
|
7
|
-
import { formatFileSize } from "../../infrastructure/utils/media-collection-utils";
|
|
8
|
-
import type { UseMediaValidationResult } from "./multimedia.types";
|
|
9
|
-
import type { MediaValidation, MediaFile } from "../../domain/entities/MediaAttachments";
|
|
10
|
-
|
|
11
|
-
export const useMediaValidation = (): UseMediaValidationResult => {
|
|
12
|
-
const [isValidating, setIsValidating] = useState(false);
|
|
13
|
-
const [validation, setValidation] = useState<MediaValidation | null>(
|
|
14
|
-
null,
|
|
15
|
-
);
|
|
16
|
-
const [error, setError] = useState<string | null>(null);
|
|
17
|
-
|
|
18
|
-
const validateMedia = useCallback(
|
|
19
|
-
async (file: MediaFile): Promise<MediaValidation> => {
|
|
20
|
-
try {
|
|
21
|
-
setIsValidating(true);
|
|
22
|
-
setError(null);
|
|
23
|
-
|
|
24
|
-
// Simulate validation
|
|
25
|
-
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
26
|
-
|
|
27
|
-
const errors: string[] = [];
|
|
28
|
-
const warnings: string[] = [];
|
|
29
|
-
const recommendations: string[] = [];
|
|
30
|
-
|
|
31
|
-
// File size validation
|
|
32
|
-
const maxSize = 50 * 1024 * 1024; // 50MB
|
|
33
|
-
if (file.size > maxSize) {
|
|
34
|
-
errors.push(
|
|
35
|
-
`File size (${formatFileSize(file.size)}) exceeds maximum allowed size (${formatFileSize(maxSize)})`,
|
|
36
|
-
);
|
|
37
|
-
} else if (file.size > 10 * 1024 * 1024) {
|
|
38
|
-
// 10MB
|
|
39
|
-
warnings.push(`Large file size may impact performance`);
|
|
40
|
-
recommendations.push("Consider compressing file");
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
// File type validation
|
|
44
|
-
const supportedTypes = [
|
|
45
|
-
"image/jpeg",
|
|
46
|
-
"image/png",
|
|
47
|
-
"image/webp",
|
|
48
|
-
"audio/mp3",
|
|
49
|
-
"audio/wav",
|
|
50
|
-
"audio/m4a",
|
|
51
|
-
"video/mp4",
|
|
52
|
-
"video/mov",
|
|
53
|
-
];
|
|
54
|
-
|
|
55
|
-
if (!supportedTypes.includes(file.type)) {
|
|
56
|
-
errors.push(`Unsupported file type: ${file.type}`);
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
const result: MediaValidation = {
|
|
60
|
-
isValid: errors.length === 0,
|
|
61
|
-
errors,
|
|
62
|
-
warnings,
|
|
63
|
-
recommendations,
|
|
64
|
-
};
|
|
65
|
-
|
|
66
|
-
setValidation(result);
|
|
67
|
-
return result;
|
|
68
|
-
} catch (err) {
|
|
69
|
-
const errorMessage =
|
|
70
|
-
err instanceof Error ? err.message : "Validation failed";
|
|
71
|
-
setError(errorMessage);
|
|
72
|
-
setIsValidating(false);
|
|
73
|
-
|
|
74
|
-
return {
|
|
75
|
-
isValid: false,
|
|
76
|
-
errors: [errorMessage],
|
|
77
|
-
warnings: [],
|
|
78
|
-
recommendations: [],
|
|
79
|
-
};
|
|
80
|
-
} finally {
|
|
81
|
-
setIsValidating(false);
|
|
82
|
-
}
|
|
83
|
-
},
|
|
84
|
-
[],
|
|
85
|
-
);
|
|
86
|
-
|
|
87
|
-
return {
|
|
88
|
-
validateMedia,
|
|
89
|
-
isValidating,
|
|
90
|
-
validation,
|
|
91
|
-
error,
|
|
92
|
-
};
|
|
93
|
-
};
|
|
@@ -1,101 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Multimedia Flashcard Hooks
|
|
3
|
-
* Main hook and exports for multimedia functionality
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import { useState, useCallback } from "react";
|
|
7
|
-
import { calculateTotalSize, extractMediaTypes } from "../../infrastructure/utils/media-collection-utils";
|
|
8
|
-
import type { UseMultimediaFlashcardResult } from "./multimedia.types";
|
|
9
|
-
import type {
|
|
10
|
-
MediaAttachment,
|
|
11
|
-
MultimediaFlashcard,
|
|
12
|
-
CreateMultimediaCardData,
|
|
13
|
-
} from "../../domain/entities/MediaAttachments";
|
|
14
|
-
|
|
15
|
-
// Export individual hooks
|
|
16
|
-
export { useMediaUpload } from "./useMediaUpload";
|
|
17
|
-
export { useMediaGeneration } from "./useMediaGeneration";
|
|
18
|
-
export { useMediaValidation } from "./useMediaValidation";
|
|
19
|
-
|
|
20
|
-
// Export types
|
|
21
|
-
export type {
|
|
22
|
-
UseMediaUploadResult,
|
|
23
|
-
UseMediaGenerationResult,
|
|
24
|
-
UseMediaValidationResult,
|
|
25
|
-
UseMultimediaFlashcardResult,
|
|
26
|
-
} from "./multimedia.types";
|
|
27
|
-
|
|
28
|
-
/**
|
|
29
|
-
* Main hook for multimedia flashcard operations
|
|
30
|
-
*/
|
|
31
|
-
export const useMultimediaFlashcard = (): UseMultimediaFlashcardResult => {
|
|
32
|
-
const [isProcessing, setIsProcessing] = useState(false);
|
|
33
|
-
const [error, setError] = useState<string | null>(null);
|
|
34
|
-
|
|
35
|
-
const createMultimediaCard = useCallback(
|
|
36
|
-
async (cardData: CreateMultimediaCardData): Promise<MultimediaFlashcard> => {
|
|
37
|
-
try {
|
|
38
|
-
setIsProcessing(true);
|
|
39
|
-
setError(null);
|
|
40
|
-
|
|
41
|
-
// Simulate card creation
|
|
42
|
-
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
43
|
-
|
|
44
|
-
const card: MultimediaFlashcard = {
|
|
45
|
-
id: `card_${Date.now()}`,
|
|
46
|
-
front: cardData.front || "",
|
|
47
|
-
back: cardData.back || "",
|
|
48
|
-
difficulty: cardData.difficulty || "medium",
|
|
49
|
-
tags: cardData.tags || [],
|
|
50
|
-
media: cardData.media || [],
|
|
51
|
-
hasMedia: (cardData.media || []).length > 0,
|
|
52
|
-
mediaType: extractMediaTypes(cardData.media || []),
|
|
53
|
-
isDownloaded: (cardData.media || []).every(
|
|
54
|
-
(m: MediaAttachment) => m.isDownloaded,
|
|
55
|
-
),
|
|
56
|
-
estimatedSize: calculateTotalSize(cardData.media || []),
|
|
57
|
-
createdAt: new Date().toISOString(),
|
|
58
|
-
};
|
|
59
|
-
|
|
60
|
-
return card;
|
|
61
|
-
} catch (err) {
|
|
62
|
-
const errorMessage =
|
|
63
|
-
err instanceof Error ? err.message : "Card creation failed";
|
|
64
|
-
setError(errorMessage);
|
|
65
|
-
setIsProcessing(false);
|
|
66
|
-
throw err;
|
|
67
|
-
} finally {
|
|
68
|
-
setIsProcessing(false);
|
|
69
|
-
}
|
|
70
|
-
},
|
|
71
|
-
[],
|
|
72
|
-
);
|
|
73
|
-
|
|
74
|
-
const updateMedia = useCallback(
|
|
75
|
-
async (
|
|
76
|
-
_cardId: string,
|
|
77
|
-
_media: MediaAttachment[],
|
|
78
|
-
): Promise<MultimediaFlashcard> => {
|
|
79
|
-
// Mock implementation
|
|
80
|
-
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
81
|
-
return {} as MultimediaFlashcard;
|
|
82
|
-
},
|
|
83
|
-
[],
|
|
84
|
-
);
|
|
85
|
-
|
|
86
|
-
const deleteMedia = useCallback(
|
|
87
|
-
async (_attachmentId: string): Promise<void> => {
|
|
88
|
-
// Mock implementation
|
|
89
|
-
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
90
|
-
},
|
|
91
|
-
[],
|
|
92
|
-
);
|
|
93
|
-
|
|
94
|
-
return {
|
|
95
|
-
createMultimediaCard,
|
|
96
|
-
updateMedia,
|
|
97
|
-
deleteMedia,
|
|
98
|
-
isProcessing,
|
|
99
|
-
error,
|
|
100
|
-
};
|
|
101
|
-
};
|