@rajeev02/document 0.1.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.
- package/lib/editor/index.d.ts +134 -0
- package/lib/editor/index.d.ts.map +1 -0
- package/lib/editor/index.js +250 -0
- package/lib/editor/index.js.map +1 -0
- package/lib/index.d.ts +15 -0
- package/lib/index.d.ts.map +1 -0
- package/lib/index.js +27 -0
- package/lib/index.js.map +1 -0
- package/lib/picker/index.d.ts +62 -0
- package/lib/picker/index.d.ts.map +1 -0
- package/lib/picker/index.js +182 -0
- package/lib/picker/index.js.map +1 -0
- package/lib/signature/index.d.ts +103 -0
- package/lib/signature/index.d.ts.map +1 -0
- package/lib/signature/index.js +147 -0
- package/lib/signature/index.js.map +1 -0
- package/package.json +52 -0
- package/src/editor/index.ts +352 -0
- package/src/index.ts +44 -0
- package/src/picker/index.ts +246 -0
- package/src/signature/index.ts +227 -0
|
@@ -0,0 +1,352 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @rajeev02/document — Editor
|
|
3
|
+
* PDF annotation, highlight, text, stamps, form filling, page management
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export type AnnotationTool =
|
|
7
|
+
| "highlight"
|
|
8
|
+
| "underline"
|
|
9
|
+
| "strikethrough"
|
|
10
|
+
| "freehand"
|
|
11
|
+
| "text_note"
|
|
12
|
+
| "text_box"
|
|
13
|
+
| "rectangle"
|
|
14
|
+
| "circle"
|
|
15
|
+
| "arrow"
|
|
16
|
+
| "stamp"
|
|
17
|
+
| "eraser";
|
|
18
|
+
|
|
19
|
+
export interface Annotation {
|
|
20
|
+
id: string;
|
|
21
|
+
tool: AnnotationTool;
|
|
22
|
+
pageNumber: number;
|
|
23
|
+
/** Position relative to page (0-1 normalized) */
|
|
24
|
+
position: { x: number; y: number };
|
|
25
|
+
size?: { width: number; height: number };
|
|
26
|
+
color: string;
|
|
27
|
+
opacity: number;
|
|
28
|
+
/** Text content (for notes, text boxes) */
|
|
29
|
+
text?: string;
|
|
30
|
+
/** Font size for text */
|
|
31
|
+
fontSize?: number;
|
|
32
|
+
/** Freehand path points */
|
|
33
|
+
path?: { x: number; y: number }[];
|
|
34
|
+
/** Pen thickness for freehand/shapes */
|
|
35
|
+
strokeWidth?: number;
|
|
36
|
+
/** Stamp type */
|
|
37
|
+
stampType?: StampType;
|
|
38
|
+
createdAt: number;
|
|
39
|
+
modifiedAt: number;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export type StampType =
|
|
43
|
+
| "approved"
|
|
44
|
+
| "rejected"
|
|
45
|
+
| "draft"
|
|
46
|
+
| "confidential"
|
|
47
|
+
| "reviewed"
|
|
48
|
+
| "final"
|
|
49
|
+
| "copy"
|
|
50
|
+
| "void"
|
|
51
|
+
| "urgent"
|
|
52
|
+
| "custom";
|
|
53
|
+
|
|
54
|
+
export interface FormField {
|
|
55
|
+
id: string;
|
|
56
|
+
type: "text" | "checkbox" | "radio" | "select" | "date" | "signature";
|
|
57
|
+
label: string;
|
|
58
|
+
pageNumber: number;
|
|
59
|
+
position: { x: number; y: number };
|
|
60
|
+
size: { width: number; height: number };
|
|
61
|
+
value?: string;
|
|
62
|
+
required: boolean;
|
|
63
|
+
options?: string[];
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export interface DocumentPage {
|
|
67
|
+
pageNumber: number;
|
|
68
|
+
width: number;
|
|
69
|
+
height: number;
|
|
70
|
+
thumbnailUri?: string;
|
|
71
|
+
annotations: Annotation[];
|
|
72
|
+
rotation: number;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Document Editor Controller
|
|
77
|
+
*/
|
|
78
|
+
export class DocumentEditorController {
|
|
79
|
+
private sourceUri: string;
|
|
80
|
+
private pages: DocumentPage[] = [];
|
|
81
|
+
private annotations: Annotation[] = [];
|
|
82
|
+
private formFields: FormField[] = [];
|
|
83
|
+
private currentPage: number = 1;
|
|
84
|
+
private zoom: number = 1.0;
|
|
85
|
+
private activeTool: AnnotationTool | null = null;
|
|
86
|
+
private activeColor: string = "#FFFF00"; // Yellow highlight
|
|
87
|
+
private activeStrokeWidth: number = 2;
|
|
88
|
+
private history: Annotation[][] = [];
|
|
89
|
+
private historyIndex: number = -1;
|
|
90
|
+
private listeners: Set<(event: DocEditorEvent) => void> = new Set();
|
|
91
|
+
|
|
92
|
+
constructor(sourceUri: string, totalPages: number = 1) {
|
|
93
|
+
this.sourceUri = sourceUri;
|
|
94
|
+
for (let i = 1; i <= totalPages; i++) {
|
|
95
|
+
this.pages.push({
|
|
96
|
+
pageNumber: i,
|
|
97
|
+
width: 595,
|
|
98
|
+
height: 842,
|
|
99
|
+
annotations: [],
|
|
100
|
+
rotation: 0,
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// ---- NAVIGATION ----
|
|
106
|
+
goToPage(page: number): void {
|
|
107
|
+
this.currentPage = Math.max(1, Math.min(page, this.pages.length));
|
|
108
|
+
}
|
|
109
|
+
nextPage(): void {
|
|
110
|
+
this.goToPage(this.currentPage + 1);
|
|
111
|
+
}
|
|
112
|
+
prevPage(): void {
|
|
113
|
+
this.goToPage(this.currentPage - 1);
|
|
114
|
+
}
|
|
115
|
+
getCurrentPage(): number {
|
|
116
|
+
return this.currentPage;
|
|
117
|
+
}
|
|
118
|
+
getTotalPages(): number {
|
|
119
|
+
return this.pages.length;
|
|
120
|
+
}
|
|
121
|
+
setZoom(zoom: number): void {
|
|
122
|
+
this.zoom = Math.max(0.5, Math.min(5.0, zoom));
|
|
123
|
+
}
|
|
124
|
+
getZoom(): number {
|
|
125
|
+
return this.zoom;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// ---- TOOLS ----
|
|
129
|
+
setActiveTool(tool: AnnotationTool | null): void {
|
|
130
|
+
this.activeTool = tool;
|
|
131
|
+
}
|
|
132
|
+
getActiveTool(): AnnotationTool | null {
|
|
133
|
+
return this.activeTool;
|
|
134
|
+
}
|
|
135
|
+
setActiveColor(color: string): void {
|
|
136
|
+
this.activeColor = color;
|
|
137
|
+
}
|
|
138
|
+
setStrokeWidth(width: number): void {
|
|
139
|
+
this.activeStrokeWidth = width;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// ---- ANNOTATIONS ----
|
|
143
|
+
addAnnotation(
|
|
144
|
+
annotation: Omit<Annotation, "id" | "createdAt" | "modifiedAt">,
|
|
145
|
+
): string {
|
|
146
|
+
const id = `ann_${Date.now()}_${Math.random().toString(36).substr(2, 4)}`;
|
|
147
|
+
const full: Annotation = {
|
|
148
|
+
...annotation,
|
|
149
|
+
id,
|
|
150
|
+
createdAt: Date.now(),
|
|
151
|
+
modifiedAt: Date.now(),
|
|
152
|
+
};
|
|
153
|
+
this.annotations.push(full);
|
|
154
|
+
this.saveHistory();
|
|
155
|
+
this.emit({ type: "annotation_added", annotation: full });
|
|
156
|
+
return id;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/** Quick highlight at position on current page */
|
|
160
|
+
addHighlight(x: number, y: number, width: number, height: number): string {
|
|
161
|
+
return this.addAnnotation({
|
|
162
|
+
tool: "highlight",
|
|
163
|
+
pageNumber: this.currentPage,
|
|
164
|
+
position: { x, y },
|
|
165
|
+
size: { width, height },
|
|
166
|
+
color: this.activeColor,
|
|
167
|
+
opacity: 0.4,
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/** Add text note */
|
|
172
|
+
addTextNote(x: number, y: number, text: string): string {
|
|
173
|
+
return this.addAnnotation({
|
|
174
|
+
tool: "text_note",
|
|
175
|
+
pageNumber: this.currentPage,
|
|
176
|
+
position: { x, y },
|
|
177
|
+
text,
|
|
178
|
+
color: "#FEF3C7",
|
|
179
|
+
opacity: 1,
|
|
180
|
+
fontSize: 12,
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/** Add text box */
|
|
185
|
+
addTextBox(
|
|
186
|
+
x: number,
|
|
187
|
+
y: number,
|
|
188
|
+
width: number,
|
|
189
|
+
height: number,
|
|
190
|
+
text: string,
|
|
191
|
+
): string {
|
|
192
|
+
return this.addAnnotation({
|
|
193
|
+
tool: "text_box",
|
|
194
|
+
pageNumber: this.currentPage,
|
|
195
|
+
position: { x, y },
|
|
196
|
+
size: { width, height },
|
|
197
|
+
text,
|
|
198
|
+
color: "#000000",
|
|
199
|
+
opacity: 1,
|
|
200
|
+
fontSize: 14,
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/** Add stamp */
|
|
205
|
+
addStamp(x: number, y: number, stampType: StampType): string {
|
|
206
|
+
return this.addAnnotation({
|
|
207
|
+
tool: "stamp",
|
|
208
|
+
pageNumber: this.currentPage,
|
|
209
|
+
position: { x, y },
|
|
210
|
+
size: { width: 0.2, height: 0.06 },
|
|
211
|
+
color: stampType === "approved" ? "#10B981" : "#EF4444",
|
|
212
|
+
opacity: 0.9,
|
|
213
|
+
stampType,
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/** Add freehand drawing */
|
|
218
|
+
addFreehand(path: { x: number; y: number }[]): string {
|
|
219
|
+
return this.addAnnotation({
|
|
220
|
+
tool: "freehand",
|
|
221
|
+
pageNumber: this.currentPage,
|
|
222
|
+
position: path[0] ?? { x: 0, y: 0 },
|
|
223
|
+
path,
|
|
224
|
+
color: this.activeColor,
|
|
225
|
+
opacity: 1,
|
|
226
|
+
strokeWidth: this.activeStrokeWidth,
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
removeAnnotation(id: string): void {
|
|
231
|
+
this.annotations = this.annotations.filter((a) => a.id !== id);
|
|
232
|
+
this.saveHistory();
|
|
233
|
+
this.emit({ type: "annotation_removed", annotationId: id });
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
getAnnotations(pageNumber?: number): Annotation[] {
|
|
237
|
+
if (pageNumber)
|
|
238
|
+
return this.annotations.filter((a) => a.pageNumber === pageNumber);
|
|
239
|
+
return [...this.annotations];
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
clearAnnotations(pageNumber?: number): void {
|
|
243
|
+
if (pageNumber)
|
|
244
|
+
this.annotations = this.annotations.filter(
|
|
245
|
+
(a) => a.pageNumber !== pageNumber,
|
|
246
|
+
);
|
|
247
|
+
else this.annotations = [];
|
|
248
|
+
this.saveHistory();
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// ---- PAGE MANAGEMENT ----
|
|
252
|
+
rotatePage(pageNumber: number, degrees: number): void {
|
|
253
|
+
const page = this.pages.find((p) => p.pageNumber === pageNumber);
|
|
254
|
+
if (page) page.rotation = (page.rotation + degrees) % 360;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
deletePage(pageNumber: number): void {
|
|
258
|
+
if (this.pages.length <= 1) return;
|
|
259
|
+
this.pages = this.pages.filter((p) => p.pageNumber !== pageNumber);
|
|
260
|
+
this.annotations = this.annotations.filter(
|
|
261
|
+
(a) => a.pageNumber !== pageNumber,
|
|
262
|
+
);
|
|
263
|
+
// Renumber
|
|
264
|
+
this.pages.forEach((p, i) => {
|
|
265
|
+
p.pageNumber = i + 1;
|
|
266
|
+
});
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
reorderPages(fromIndex: number, toIndex: number): void {
|
|
270
|
+
if (
|
|
271
|
+
fromIndex < 0 ||
|
|
272
|
+
toIndex < 0 ||
|
|
273
|
+
fromIndex >= this.pages.length ||
|
|
274
|
+
toIndex >= this.pages.length
|
|
275
|
+
)
|
|
276
|
+
return;
|
|
277
|
+
const [page] = this.pages.splice(fromIndex, 1);
|
|
278
|
+
this.pages.splice(toIndex, 0, page);
|
|
279
|
+
this.pages.forEach((p, i) => {
|
|
280
|
+
p.pageNumber = i + 1;
|
|
281
|
+
});
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
// ---- FORM FILLING ----
|
|
285
|
+
setFormFieldValue(fieldId: string, value: string): void {
|
|
286
|
+
const field = this.formFields.find((f) => f.id === fieldId);
|
|
287
|
+
if (field) field.value = value;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
getFormFields(): FormField[] {
|
|
291
|
+
return [...this.formFields];
|
|
292
|
+
}
|
|
293
|
+
loadFormFields(fields: FormField[]): void {
|
|
294
|
+
this.formFields = fields;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
// ---- UNDO/REDO ----
|
|
298
|
+
undo(): boolean {
|
|
299
|
+
if (this.historyIndex <= 0) return false;
|
|
300
|
+
this.historyIndex--;
|
|
301
|
+
this.annotations = [...this.history[this.historyIndex]];
|
|
302
|
+
return true;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
redo(): boolean {
|
|
306
|
+
if (this.historyIndex >= this.history.length - 1) return false;
|
|
307
|
+
this.historyIndex++;
|
|
308
|
+
this.annotations = [...this.history[this.historyIndex]];
|
|
309
|
+
return true;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
canUndo(): boolean {
|
|
313
|
+
return this.historyIndex > 0;
|
|
314
|
+
}
|
|
315
|
+
canRedo(): boolean {
|
|
316
|
+
return this.historyIndex < this.history.length - 1;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
/** Export state for saving */
|
|
320
|
+
getEditState(): Record<string, unknown> {
|
|
321
|
+
return {
|
|
322
|
+
sourceUri: this.sourceUri,
|
|
323
|
+
annotations: this.annotations,
|
|
324
|
+
formFields: this.formFields,
|
|
325
|
+
pages: this.pages,
|
|
326
|
+
};
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
on(listener: (event: DocEditorEvent) => void): () => void {
|
|
330
|
+
this.listeners.add(listener);
|
|
331
|
+
return () => this.listeners.delete(listener);
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
private saveHistory(): void {
|
|
335
|
+
this.history = this.history.slice(0, this.historyIndex + 1);
|
|
336
|
+
this.history.push([...this.annotations]);
|
|
337
|
+
this.historyIndex = this.history.length - 1;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
private emit(event: DocEditorEvent): void {
|
|
341
|
+
for (const l of this.listeners) {
|
|
342
|
+
try {
|
|
343
|
+
l(event);
|
|
344
|
+
} catch {}
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
export type DocEditorEvent =
|
|
350
|
+
| { type: "annotation_added"; annotation: Annotation }
|
|
351
|
+
| { type: "annotation_removed"; annotationId: string }
|
|
352
|
+
| { type: "page_changed"; page: number };
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @rajeev02/document
|
|
3
|
+
* Document Picker, Editor & Signature
|
|
4
|
+
* File picker, PDF annotation, stamps, form filling, digital signatures
|
|
5
|
+
*
|
|
6
|
+
* @author Rajeev Kumar Joshi
|
|
7
|
+
* @license MIT
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
// Picker
|
|
11
|
+
export {
|
|
12
|
+
categorizeFile,
|
|
13
|
+
getMimeTypesForCategory,
|
|
14
|
+
formatFileSize,
|
|
15
|
+
getFileExtension,
|
|
16
|
+
getFileIcon,
|
|
17
|
+
getPickerPreset,
|
|
18
|
+
} from "./picker";
|
|
19
|
+
export type {
|
|
20
|
+
DocumentSource,
|
|
21
|
+
DocumentCategory,
|
|
22
|
+
PickerConfig,
|
|
23
|
+
PickedDocument,
|
|
24
|
+
} from "./picker";
|
|
25
|
+
|
|
26
|
+
// Editor
|
|
27
|
+
export { DocumentEditorController } from "./editor";
|
|
28
|
+
export type {
|
|
29
|
+
AnnotationTool,
|
|
30
|
+
Annotation,
|
|
31
|
+
StampType,
|
|
32
|
+
FormField,
|
|
33
|
+
DocumentPage,
|
|
34
|
+
DocEditorEvent,
|
|
35
|
+
} from "./editor";
|
|
36
|
+
|
|
37
|
+
// Signature
|
|
38
|
+
export { SignatureManager, getSignatureSizePresets } from "./signature";
|
|
39
|
+
export type {
|
|
40
|
+
SignatureType,
|
|
41
|
+
SignatureData,
|
|
42
|
+
SignaturePlacement,
|
|
43
|
+
SignatureConfig,
|
|
44
|
+
} from "./signature";
|
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @rajeev02/document — Picker
|
|
3
|
+
* Universal document picker: gallery, camera, files, cloud, recent, multi-select
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export type DocumentSource =
|
|
7
|
+
| "gallery"
|
|
8
|
+
| "camera"
|
|
9
|
+
| "files"
|
|
10
|
+
| "google_drive"
|
|
11
|
+
| "dropbox"
|
|
12
|
+
| "onedrive"
|
|
13
|
+
| "icloud"
|
|
14
|
+
| "recent";
|
|
15
|
+
export type DocumentCategory =
|
|
16
|
+
| "image"
|
|
17
|
+
| "video"
|
|
18
|
+
| "audio"
|
|
19
|
+
| "pdf"
|
|
20
|
+
| "document"
|
|
21
|
+
| "spreadsheet"
|
|
22
|
+
| "presentation"
|
|
23
|
+
| "archive"
|
|
24
|
+
| "any";
|
|
25
|
+
|
|
26
|
+
export interface PickerConfig {
|
|
27
|
+
/** Allowed sources */
|
|
28
|
+
sources: DocumentSource[];
|
|
29
|
+
/** Allowed file categories */
|
|
30
|
+
categories: DocumentCategory[];
|
|
31
|
+
/** Custom MIME types (e.g., ['application/pdf', 'image/jpeg']) */
|
|
32
|
+
mimeTypes?: string[];
|
|
33
|
+
/** Allow multiple selection */
|
|
34
|
+
multiple: boolean;
|
|
35
|
+
/** Max files in multi-select */
|
|
36
|
+
maxFiles?: number;
|
|
37
|
+
/** Max file size per file (bytes) */
|
|
38
|
+
maxSizeBytes?: number;
|
|
39
|
+
/** Total max size (bytes) */
|
|
40
|
+
totalMaxSizeBytes?: number;
|
|
41
|
+
/** Enable camera document scanning mode */
|
|
42
|
+
enableDocumentScan?: boolean;
|
|
43
|
+
/** Compress images before returning */
|
|
44
|
+
compressImages?: boolean;
|
|
45
|
+
/** Max image dimension after compression */
|
|
46
|
+
maxImageDimension?: number;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export interface PickedDocument {
|
|
50
|
+
id: string;
|
|
51
|
+
name: string;
|
|
52
|
+
uri: string;
|
|
53
|
+
mimeType: string;
|
|
54
|
+
sizeBytes: number;
|
|
55
|
+
category: DocumentCategory;
|
|
56
|
+
source: DocumentSource;
|
|
57
|
+
/** Image/video width */
|
|
58
|
+
width?: number;
|
|
59
|
+
/** Image/video height */
|
|
60
|
+
height?: number;
|
|
61
|
+
/** Video/audio duration ms */
|
|
62
|
+
durationMs?: number;
|
|
63
|
+
/** Thumbnail URI */
|
|
64
|
+
thumbnailUri?: string;
|
|
65
|
+
/** Last modified timestamp */
|
|
66
|
+
modifiedAt?: number;
|
|
67
|
+
/** File extension */
|
|
68
|
+
extension: string;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/** MIME type to category mapping */
|
|
72
|
+
export function categorizeFile(mimeType: string): DocumentCategory {
|
|
73
|
+
if (mimeType.startsWith("image/")) return "image";
|
|
74
|
+
if (mimeType.startsWith("video/")) return "video";
|
|
75
|
+
if (mimeType.startsWith("audio/")) return "audio";
|
|
76
|
+
if (mimeType === "application/pdf") return "pdf";
|
|
77
|
+
if (
|
|
78
|
+
mimeType.includes("word") ||
|
|
79
|
+
mimeType.includes("document") ||
|
|
80
|
+
mimeType.includes("text")
|
|
81
|
+
)
|
|
82
|
+
return "document";
|
|
83
|
+
if (
|
|
84
|
+
mimeType.includes("sheet") ||
|
|
85
|
+
mimeType.includes("excel") ||
|
|
86
|
+
mimeType.includes("csv")
|
|
87
|
+
)
|
|
88
|
+
return "spreadsheet";
|
|
89
|
+
if (mimeType.includes("presentation") || mimeType.includes("powerpoint"))
|
|
90
|
+
return "presentation";
|
|
91
|
+
if (
|
|
92
|
+
mimeType.includes("zip") ||
|
|
93
|
+
mimeType.includes("rar") ||
|
|
94
|
+
mimeType.includes("tar") ||
|
|
95
|
+
mimeType.includes("7z")
|
|
96
|
+
)
|
|
97
|
+
return "archive";
|
|
98
|
+
return "any";
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/** Get MIME types for a category */
|
|
102
|
+
export function getMimeTypesForCategory(category: DocumentCategory): string[] {
|
|
103
|
+
switch (category) {
|
|
104
|
+
case "image":
|
|
105
|
+
return [
|
|
106
|
+
"image/jpeg",
|
|
107
|
+
"image/png",
|
|
108
|
+
"image/gif",
|
|
109
|
+
"image/webp",
|
|
110
|
+
"image/heic",
|
|
111
|
+
"image/heif",
|
|
112
|
+
"image/bmp",
|
|
113
|
+
"image/svg+xml",
|
|
114
|
+
];
|
|
115
|
+
case "video":
|
|
116
|
+
return [
|
|
117
|
+
"video/mp4",
|
|
118
|
+
"video/quicktime",
|
|
119
|
+
"video/x-msvideo",
|
|
120
|
+
"video/webm",
|
|
121
|
+
"video/3gpp",
|
|
122
|
+
];
|
|
123
|
+
case "audio":
|
|
124
|
+
return [
|
|
125
|
+
"audio/mpeg",
|
|
126
|
+
"audio/wav",
|
|
127
|
+
"audio/ogg",
|
|
128
|
+
"audio/aac",
|
|
129
|
+
"audio/flac",
|
|
130
|
+
"audio/x-m4a",
|
|
131
|
+
];
|
|
132
|
+
case "pdf":
|
|
133
|
+
return ["application/pdf"];
|
|
134
|
+
case "document":
|
|
135
|
+
return [
|
|
136
|
+
"application/msword",
|
|
137
|
+
"application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
|
138
|
+
"text/plain",
|
|
139
|
+
"text/rtf",
|
|
140
|
+
"application/rtf",
|
|
141
|
+
];
|
|
142
|
+
case "spreadsheet":
|
|
143
|
+
return [
|
|
144
|
+
"application/vnd.ms-excel",
|
|
145
|
+
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
|
146
|
+
"text/csv",
|
|
147
|
+
];
|
|
148
|
+
case "presentation":
|
|
149
|
+
return [
|
|
150
|
+
"application/vnd.ms-powerpoint",
|
|
151
|
+
"application/vnd.openxmlformats-officedocument.presentationml.presentation",
|
|
152
|
+
];
|
|
153
|
+
case "archive":
|
|
154
|
+
return [
|
|
155
|
+
"application/zip",
|
|
156
|
+
"application/x-rar-compressed",
|
|
157
|
+
"application/x-7z-compressed",
|
|
158
|
+
"application/gzip",
|
|
159
|
+
];
|
|
160
|
+
case "any":
|
|
161
|
+
return ["*/*"];
|
|
162
|
+
default:
|
|
163
|
+
return ["*/*"];
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/** Format file size for display */
|
|
168
|
+
export function formatFileSize(bytes: number): string {
|
|
169
|
+
if (bytes < 1024) return `${bytes} B`;
|
|
170
|
+
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
|
|
171
|
+
if (bytes < 1024 * 1024 * 1024)
|
|
172
|
+
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
|
|
173
|
+
return `${(bytes / (1024 * 1024 * 1024)).toFixed(2)} GB`;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/** Get file extension from name or URI */
|
|
177
|
+
export function getFileExtension(nameOrUri: string): string {
|
|
178
|
+
const parts = nameOrUri.split(".");
|
|
179
|
+
return parts.length > 1
|
|
180
|
+
? parts[parts.length - 1].toLowerCase().split("?")[0]
|
|
181
|
+
: "";
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/** Get icon name for file category (for UI rendering) */
|
|
185
|
+
export function getFileIcon(category: DocumentCategory): string {
|
|
186
|
+
const icons: Record<DocumentCategory, string> = {
|
|
187
|
+
image: "image",
|
|
188
|
+
video: "film",
|
|
189
|
+
audio: "music",
|
|
190
|
+
pdf: "file-text",
|
|
191
|
+
document: "file-text",
|
|
192
|
+
spreadsheet: "table",
|
|
193
|
+
presentation: "monitor",
|
|
194
|
+
archive: "archive",
|
|
195
|
+
any: "file",
|
|
196
|
+
};
|
|
197
|
+
return icons[category] ?? "file";
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/** Default picker configs for common use cases */
|
|
201
|
+
export function getPickerPreset(
|
|
202
|
+
preset: "photo" | "video" | "document" | "kyc_document" | "any",
|
|
203
|
+
): PickerConfig {
|
|
204
|
+
switch (preset) {
|
|
205
|
+
case "photo":
|
|
206
|
+
return {
|
|
207
|
+
sources: ["gallery", "camera"],
|
|
208
|
+
categories: ["image"],
|
|
209
|
+
multiple: false,
|
|
210
|
+
compressImages: true,
|
|
211
|
+
maxImageDimension: 2048,
|
|
212
|
+
maxSizeBytes: 10 * 1024 * 1024,
|
|
213
|
+
};
|
|
214
|
+
case "video":
|
|
215
|
+
return {
|
|
216
|
+
sources: ["gallery", "camera"],
|
|
217
|
+
categories: ["video"],
|
|
218
|
+
multiple: false,
|
|
219
|
+
maxSizeBytes: 100 * 1024 * 1024,
|
|
220
|
+
};
|
|
221
|
+
case "document":
|
|
222
|
+
return {
|
|
223
|
+
sources: ["files", "google_drive", "recent"],
|
|
224
|
+
categories: ["pdf", "document", "spreadsheet"],
|
|
225
|
+
multiple: true,
|
|
226
|
+
maxFiles: 10,
|
|
227
|
+
maxSizeBytes: 25 * 1024 * 1024,
|
|
228
|
+
};
|
|
229
|
+
case "kyc_document":
|
|
230
|
+
return {
|
|
231
|
+
sources: ["camera", "gallery"],
|
|
232
|
+
categories: ["image", "pdf"],
|
|
233
|
+
multiple: false,
|
|
234
|
+
enableDocumentScan: true,
|
|
235
|
+
compressImages: true,
|
|
236
|
+
maxSizeBytes: 5 * 1024 * 1024,
|
|
237
|
+
};
|
|
238
|
+
case "any":
|
|
239
|
+
return {
|
|
240
|
+
sources: ["files", "gallery", "camera", "google_drive", "recent"],
|
|
241
|
+
categories: ["any"],
|
|
242
|
+
multiple: true,
|
|
243
|
+
maxFiles: 20,
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
}
|