hazo_files 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.
- package/.cursor/rules/db_schema.mdc +0 -0
- package/.cursor/rules/design.mdc +0 -0
- package/.cursor/rules/general.mdc +0 -0
- package/CHANGE_LOG.md +341 -0
- package/CLAUDE.md +926 -0
- package/README.md +929 -0
- package/SETUP_CHECKLIST.md +931 -0
- package/TECHDOC.md +325 -0
- package/dist/index.d.mts +1031 -0
- package/dist/index.d.ts +1031 -0
- package/dist/index.js +2457 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +2333 -0
- package/dist/index.mjs.map +1 -0
- package/dist/ui/index.js +4054 -0
- package/dist/ui/index.js.map +1 -0
- package/dist/ui/index.mjs +3982 -0
- package/dist/ui/index.mjs.map +1 -0
- package/docs/ADDING_MODULES.md +964 -0
- package/hazo_files_config.ini +31 -0
- package/package.json +83 -0
package/README.md
ADDED
|
@@ -0,0 +1,929 @@
|
|
|
1
|
+
# hazo_files
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/hazo_files)
|
|
4
|
+
[](https://opensource.org/licenses/MIT)
|
|
5
|
+
|
|
6
|
+
A powerful, modular file management package for Node.js and React applications with support for local filesystem and Google Drive storage. Built with TypeScript for type safety and developer experience.
|
|
7
|
+
|
|
8
|
+
## Features
|
|
9
|
+
|
|
10
|
+
- **Multiple Storage Providers**: Local filesystem and Google Drive support out of the box
|
|
11
|
+
- **Modular Architecture**: Easily add custom storage providers
|
|
12
|
+
- **Unified API**: Single consistent interface across all storage providers
|
|
13
|
+
- **React UI Components**: Drop-in FileBrowser component with folder tree, file list, and preview
|
|
14
|
+
- **Naming Rules System**: Visual configurator and utilities for generating consistent file/folder names
|
|
15
|
+
- **TypeScript**: Full type safety and IntelliSense support
|
|
16
|
+
- **OAuth Integration**: Built-in Google Drive OAuth authentication
|
|
17
|
+
- **Progress Tracking**: Upload/download progress callbacks
|
|
18
|
+
- **File Validation**: Extension filtering and file size limits
|
|
19
|
+
- **Error Handling**: Comprehensive error types and handling
|
|
20
|
+
|
|
21
|
+
## Installation
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
npm install hazo_files
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
For React UI components, ensure you have React 18+ installed:
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
npm install react react-dom
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
For the NamingRuleConfigurator component (drag-and-drop interface), also install:
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
npm install @dnd-kit/core @dnd-kit/sortable @dnd-kit/utilities
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## Quick Start
|
|
40
|
+
|
|
41
|
+
### Basic Usage (Server-side)
|
|
42
|
+
|
|
43
|
+
```typescript
|
|
44
|
+
import { createInitializedFileManager } from 'hazo_files';
|
|
45
|
+
|
|
46
|
+
// Create and initialize file manager
|
|
47
|
+
const fileManager = await createInitializedFileManager({
|
|
48
|
+
config: {
|
|
49
|
+
provider: 'local',
|
|
50
|
+
local: {
|
|
51
|
+
basePath: './files',
|
|
52
|
+
maxFileSize: 10 * 1024 * 1024, // 10MB
|
|
53
|
+
allowedExtensions: ['jpg', 'png', 'pdf', 'txt']
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
// Create a directory
|
|
59
|
+
await fileManager.createDirectory('/documents');
|
|
60
|
+
|
|
61
|
+
// Upload a file
|
|
62
|
+
await fileManager.uploadFile(
|
|
63
|
+
'./local-file.pdf',
|
|
64
|
+
'/documents/file.pdf',
|
|
65
|
+
{
|
|
66
|
+
onProgress: (progress, bytes, total) => {
|
|
67
|
+
console.log(`Upload progress: ${progress}%`);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
);
|
|
71
|
+
|
|
72
|
+
// List directory contents
|
|
73
|
+
const result = await fileManager.listDirectory('/documents');
|
|
74
|
+
if (result.success) {
|
|
75
|
+
console.log(result.data);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Download a file
|
|
79
|
+
await fileManager.downloadFile('/documents/file.pdf', './downloaded.pdf');
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
### Using Configuration File
|
|
83
|
+
|
|
84
|
+
Create `hazo_files_config.ini` in your project root:
|
|
85
|
+
|
|
86
|
+
```ini
|
|
87
|
+
[general]
|
|
88
|
+
provider = local
|
|
89
|
+
|
|
90
|
+
[local]
|
|
91
|
+
base_path = ./files
|
|
92
|
+
max_file_size = 10485760
|
|
93
|
+
allowed_extensions = jpg,png,pdf,txt
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
Then initialize without config object:
|
|
97
|
+
|
|
98
|
+
```typescript
|
|
99
|
+
import { createInitializedFileManager } from 'hazo_files';
|
|
100
|
+
|
|
101
|
+
const fileManager = await createInitializedFileManager();
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
### React UI Component
|
|
105
|
+
|
|
106
|
+
```typescript
|
|
107
|
+
import { FileBrowser } from 'hazo_files/ui';
|
|
108
|
+
import type { FileBrowserAPI } from 'hazo_files/ui';
|
|
109
|
+
|
|
110
|
+
// Create an API adapter that calls your server endpoints
|
|
111
|
+
const api: FileBrowserAPI = {
|
|
112
|
+
async listDirectory(path: string) {
|
|
113
|
+
const res = await fetch(`/api/files?action=list&path=${path}`);
|
|
114
|
+
return res.json();
|
|
115
|
+
},
|
|
116
|
+
async getFolderTree(path = '/', depth = 3) {
|
|
117
|
+
const res = await fetch(`/api/files?action=tree&path=${path}&depth=${depth}`);
|
|
118
|
+
return res.json();
|
|
119
|
+
},
|
|
120
|
+
async uploadFile(file: File, remotePath: string) {
|
|
121
|
+
const formData = new FormData();
|
|
122
|
+
formData.append('file', file);
|
|
123
|
+
formData.append('path', remotePath);
|
|
124
|
+
const res = await fetch('/api/files/upload', { method: 'POST', body: formData });
|
|
125
|
+
return res.json();
|
|
126
|
+
},
|
|
127
|
+
// ... implement other methods
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
function MyFileBrowser() {
|
|
131
|
+
return (
|
|
132
|
+
<FileBrowser
|
|
133
|
+
api={api}
|
|
134
|
+
initialPath="/"
|
|
135
|
+
showPreview={true}
|
|
136
|
+
showTree={true}
|
|
137
|
+
viewMode="grid"
|
|
138
|
+
/>
|
|
139
|
+
);
|
|
140
|
+
}
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
## Advanced Usage
|
|
144
|
+
|
|
145
|
+
### Google Drive Integration
|
|
146
|
+
|
|
147
|
+
#### 1. Set up Google Cloud Console
|
|
148
|
+
|
|
149
|
+
1. Go to [Google Cloud Console](https://console.cloud.google.com)
|
|
150
|
+
2. Create a new project or select an existing one
|
|
151
|
+
3. Enable the Google Drive API
|
|
152
|
+
4. Create OAuth 2.0 credentials
|
|
153
|
+
5. Add authorized redirect URIs (e.g., `http://localhost:3000/api/auth/callback/google`)
|
|
154
|
+
|
|
155
|
+
#### 2. Configure Environment Variables
|
|
156
|
+
|
|
157
|
+
Create `.env.local`:
|
|
158
|
+
|
|
159
|
+
```env
|
|
160
|
+
GOOGLE_DRIVE_CLIENT_ID=your-client-id.apps.googleusercontent.com
|
|
161
|
+
GOOGLE_DRIVE_CLIENT_SECRET=your-client-secret
|
|
162
|
+
GOOGLE_DRIVE_REDIRECT_URI=http://localhost:3000/api/auth/callback/google
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
#### 3. Configure hazo_files
|
|
166
|
+
|
|
167
|
+
```ini
|
|
168
|
+
[general]
|
|
169
|
+
provider = google_drive
|
|
170
|
+
|
|
171
|
+
[google_drive]
|
|
172
|
+
client_id =
|
|
173
|
+
client_secret =
|
|
174
|
+
redirect_uri = http://localhost:3000/api/auth/callback/google
|
|
175
|
+
refresh_token =
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
Environment variables will automatically override empty values.
|
|
179
|
+
|
|
180
|
+
#### 4. Implement OAuth Flow
|
|
181
|
+
|
|
182
|
+
```typescript
|
|
183
|
+
import { createFileManager, GoogleDriveModule } from 'hazo_files';
|
|
184
|
+
|
|
185
|
+
// Initialize with Google Drive
|
|
186
|
+
const fileManager = createFileManager({
|
|
187
|
+
config: {
|
|
188
|
+
provider: 'google_drive',
|
|
189
|
+
google_drive: {
|
|
190
|
+
clientId: process.env.GOOGLE_DRIVE_CLIENT_ID!,
|
|
191
|
+
clientSecret: process.env.GOOGLE_DRIVE_CLIENT_SECRET!,
|
|
192
|
+
redirectUri: process.env.GOOGLE_DRIVE_REDIRECT_URI!,
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
await fileManager.initialize();
|
|
198
|
+
|
|
199
|
+
// Get the Google Drive module to access auth methods
|
|
200
|
+
const module = fileManager.getModule() as GoogleDriveModule;
|
|
201
|
+
const auth = module.getAuth();
|
|
202
|
+
|
|
203
|
+
// Generate auth URL
|
|
204
|
+
const authUrl = auth.getAuthUrl();
|
|
205
|
+
console.log('Visit:', authUrl);
|
|
206
|
+
|
|
207
|
+
// After user authorizes, exchange code for tokens
|
|
208
|
+
const tokens = await auth.exchangeCodeForTokens(authCode);
|
|
209
|
+
|
|
210
|
+
// Authenticate the module
|
|
211
|
+
await module.authenticate(tokens);
|
|
212
|
+
|
|
213
|
+
// Now you can use the file manager
|
|
214
|
+
await fileManager.createDirectory('/MyFolder');
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
### Next.js API Route Example
|
|
218
|
+
|
|
219
|
+
```typescript
|
|
220
|
+
// app/api/files/route.ts
|
|
221
|
+
import { NextRequest, NextResponse } from 'next/server';
|
|
222
|
+
import { createInitializedFileManager } from 'hazo_files';
|
|
223
|
+
|
|
224
|
+
async function getFileManager() {
|
|
225
|
+
return createInitializedFileManager({
|
|
226
|
+
config: {
|
|
227
|
+
provider: 'local',
|
|
228
|
+
local: {
|
|
229
|
+
basePath: process.env.LOCAL_STORAGE_BASE_PATH || './files',
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
export async function GET(request: NextRequest) {
|
|
236
|
+
const { searchParams } = new URL(request.url);
|
|
237
|
+
const action = searchParams.get('action');
|
|
238
|
+
const path = searchParams.get('path') || '/';
|
|
239
|
+
|
|
240
|
+
const fm = await getFileManager();
|
|
241
|
+
|
|
242
|
+
switch (action) {
|
|
243
|
+
case 'list':
|
|
244
|
+
return NextResponse.json(await fm.listDirectory(path));
|
|
245
|
+
case 'tree':
|
|
246
|
+
const depth = parseInt(searchParams.get('depth') || '3', 10);
|
|
247
|
+
return NextResponse.json(await fm.getFolderTree(path, depth));
|
|
248
|
+
default:
|
|
249
|
+
return NextResponse.json({ success: false, error: 'Invalid action' });
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
export async function POST(request: NextRequest) {
|
|
254
|
+
const body = await request.json();
|
|
255
|
+
const { action, ...params } = body;
|
|
256
|
+
|
|
257
|
+
const fm = await getFileManager();
|
|
258
|
+
|
|
259
|
+
switch (action) {
|
|
260
|
+
case 'createDirectory':
|
|
261
|
+
return NextResponse.json(await fm.createDirectory(params.path));
|
|
262
|
+
case 'deleteFile':
|
|
263
|
+
return NextResponse.json(await fm.deleteFile(params.path));
|
|
264
|
+
case 'renameFile':
|
|
265
|
+
return NextResponse.json(await fm.renameFile(params.path, params.newName));
|
|
266
|
+
default:
|
|
267
|
+
return NextResponse.json({ success: false, error: 'Invalid action' });
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
### File Upload API Route
|
|
273
|
+
|
|
274
|
+
```typescript
|
|
275
|
+
// app/api/files/upload/route.ts
|
|
276
|
+
import { NextRequest, NextResponse } from 'next/server';
|
|
277
|
+
import { createInitializedFileManager } from 'hazo_files';
|
|
278
|
+
|
|
279
|
+
export async function POST(request: NextRequest) {
|
|
280
|
+
const formData = await request.formData();
|
|
281
|
+
const file = formData.get('file') as File;
|
|
282
|
+
const path = formData.get('path') as string;
|
|
283
|
+
|
|
284
|
+
const fm = await getFileManager();
|
|
285
|
+
|
|
286
|
+
// Convert File to Buffer
|
|
287
|
+
const arrayBuffer = await file.arrayBuffer();
|
|
288
|
+
const buffer = Buffer.from(arrayBuffer);
|
|
289
|
+
|
|
290
|
+
const result = await fm.uploadFile(buffer, path);
|
|
291
|
+
return NextResponse.json(result);
|
|
292
|
+
}
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
### Progress Tracking
|
|
296
|
+
|
|
297
|
+
```typescript
|
|
298
|
+
// Upload with progress tracking
|
|
299
|
+
await fileManager.uploadFile(
|
|
300
|
+
'./large-file.zip',
|
|
301
|
+
'/uploads/large-file.zip',
|
|
302
|
+
{
|
|
303
|
+
onProgress: (progress, bytesTransferred, totalBytes) => {
|
|
304
|
+
console.log(`Progress: ${progress.toFixed(2)}%`);
|
|
305
|
+
console.log(`${bytesTransferred} / ${totalBytes} bytes`);
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
);
|
|
309
|
+
|
|
310
|
+
// Download with progress tracking
|
|
311
|
+
await fileManager.downloadFile(
|
|
312
|
+
'/uploads/large-file.zip',
|
|
313
|
+
'./downloaded-file.zip',
|
|
314
|
+
{
|
|
315
|
+
onProgress: (progress, bytesTransferred, totalBytes) => {
|
|
316
|
+
console.log(`Download: ${progress.toFixed(2)}%`);
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
);
|
|
320
|
+
```
|
|
321
|
+
|
|
322
|
+
### File Operations
|
|
323
|
+
|
|
324
|
+
```typescript
|
|
325
|
+
// Create directory structure
|
|
326
|
+
await fileManager.createDirectory('/projects/2024/docs');
|
|
327
|
+
|
|
328
|
+
// Upload file
|
|
329
|
+
const uploadResult = await fileManager.uploadFile(
|
|
330
|
+
buffer,
|
|
331
|
+
'/projects/2024/docs/report.pdf'
|
|
332
|
+
);
|
|
333
|
+
|
|
334
|
+
// Move file
|
|
335
|
+
await fileManager.moveItem(
|
|
336
|
+
'/projects/2024/docs/report.pdf',
|
|
337
|
+
'/archive/2024/report.pdf'
|
|
338
|
+
);
|
|
339
|
+
|
|
340
|
+
// Rename file
|
|
341
|
+
await fileManager.renameFile(
|
|
342
|
+
'/archive/2024/report.pdf',
|
|
343
|
+
'annual-report.pdf'
|
|
344
|
+
);
|
|
345
|
+
|
|
346
|
+
// Copy file (convenience method)
|
|
347
|
+
await fileManager.copyFile(
|
|
348
|
+
'/archive/2024/annual-report.pdf',
|
|
349
|
+
'/backup/annual-report.pdf'
|
|
350
|
+
);
|
|
351
|
+
|
|
352
|
+
// Delete file
|
|
353
|
+
await fileManager.deleteFile('/backup/annual-report.pdf');
|
|
354
|
+
|
|
355
|
+
// Remove directory (recursive)
|
|
356
|
+
await fileManager.removeDirectory('/archive/2024', true);
|
|
357
|
+
|
|
358
|
+
// Check if file exists
|
|
359
|
+
const exists = await fileManager.exists('/projects/2024/docs');
|
|
360
|
+
|
|
361
|
+
// Get file/folder information
|
|
362
|
+
const itemResult = await fileManager.getItem('/projects/2024/docs/report.pdf');
|
|
363
|
+
if (itemResult.success && itemResult.data) {
|
|
364
|
+
console.log('File:', itemResult.data.name);
|
|
365
|
+
console.log('Size:', itemResult.data.size);
|
|
366
|
+
console.log('Modified:', itemResult.data.modifiedAt);
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
// List directory with options
|
|
370
|
+
const listResult = await fileManager.listDirectory('/projects', {
|
|
371
|
+
recursive: true,
|
|
372
|
+
includeHidden: false,
|
|
373
|
+
filter: (item) => !item.isDirectory && item.name.endsWith('.pdf')
|
|
374
|
+
});
|
|
375
|
+
```
|
|
376
|
+
|
|
377
|
+
### Working with Text Files
|
|
378
|
+
|
|
379
|
+
```typescript
|
|
380
|
+
// Write text file
|
|
381
|
+
await fileManager.writeFile('/notes/readme.txt', 'Hello, World!');
|
|
382
|
+
|
|
383
|
+
// Read text file
|
|
384
|
+
const readResult = await fileManager.readFile('/notes/readme.txt');
|
|
385
|
+
if (readResult.success) {
|
|
386
|
+
console.log(readResult.data); // "Hello, World!"
|
|
387
|
+
}
|
|
388
|
+
```
|
|
389
|
+
|
|
390
|
+
### Folder Tree
|
|
391
|
+
|
|
392
|
+
```typescript
|
|
393
|
+
// Get folder tree (3 levels deep by default)
|
|
394
|
+
const treeResult = await fileManager.getFolderTree('/projects', 3);
|
|
395
|
+
if (treeResult.success && treeResult.data) {
|
|
396
|
+
console.log(JSON.stringify(treeResult.data, null, 2));
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
// Output:
|
|
400
|
+
// [
|
|
401
|
+
// {
|
|
402
|
+
// "id": "abc123",
|
|
403
|
+
// "name": "2024",
|
|
404
|
+
// "path": "/projects/2024",
|
|
405
|
+
// "children": [
|
|
406
|
+
// {
|
|
407
|
+
// "id": "def456",
|
|
408
|
+
// "name": "docs",
|
|
409
|
+
// "path": "/projects/2024/docs",
|
|
410
|
+
// "children": []
|
|
411
|
+
// }
|
|
412
|
+
// ]
|
|
413
|
+
// }
|
|
414
|
+
// ]
|
|
415
|
+
```
|
|
416
|
+
|
|
417
|
+
## Configuration
|
|
418
|
+
|
|
419
|
+
### Configuration File (`hazo_files_config.ini`)
|
|
420
|
+
|
|
421
|
+
```ini
|
|
422
|
+
[general]
|
|
423
|
+
provider = local
|
|
424
|
+
|
|
425
|
+
[local]
|
|
426
|
+
base_path = ./files
|
|
427
|
+
allowed_extensions = jpg,png,pdf,txt,doc,docx
|
|
428
|
+
max_file_size = 10485760
|
|
429
|
+
|
|
430
|
+
[google_drive]
|
|
431
|
+
client_id = your-client-id.apps.googleusercontent.com
|
|
432
|
+
client_secret = your-client-secret
|
|
433
|
+
redirect_uri = http://localhost:3000/api/auth/callback/google
|
|
434
|
+
refresh_token =
|
|
435
|
+
access_token =
|
|
436
|
+
root_folder_id =
|
|
437
|
+
|
|
438
|
+
[naming]
|
|
439
|
+
; Supported date format tokens for naming rules
|
|
440
|
+
date_formats = YYYY,YY,MM,M,DD,D,MMM,MMMM,YYYY-MM-DD,YYYY-MMM-DD,DD-MM-YYYY,MM-DD-YYYY
|
|
441
|
+
```
|
|
442
|
+
|
|
443
|
+
### Environment Variables
|
|
444
|
+
|
|
445
|
+
The following environment variables can override configuration file values:
|
|
446
|
+
|
|
447
|
+
- `GOOGLE_DRIVE_CLIENT_ID`
|
|
448
|
+
- `GOOGLE_DRIVE_CLIENT_SECRET`
|
|
449
|
+
- `GOOGLE_DRIVE_REDIRECT_URI`
|
|
450
|
+
- `GOOGLE_DRIVE_REFRESH_TOKEN`
|
|
451
|
+
- `GOOGLE_DRIVE_ACCESS_TOKEN`
|
|
452
|
+
- `GOOGLE_DRIVE_ROOT_FOLDER_ID`
|
|
453
|
+
|
|
454
|
+
### Configuration via Code
|
|
455
|
+
|
|
456
|
+
```typescript
|
|
457
|
+
import { createInitializedFileManager } from 'hazo_files';
|
|
458
|
+
|
|
459
|
+
const fileManager = await createInitializedFileManager({
|
|
460
|
+
config: {
|
|
461
|
+
provider: 'local',
|
|
462
|
+
local: {
|
|
463
|
+
basePath: './storage',
|
|
464
|
+
allowedExtensions: ['jpg', 'png', 'gif', 'pdf'],
|
|
465
|
+
maxFileSize: 5 * 1024 * 1024 // 5MB
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
});
|
|
469
|
+
```
|
|
470
|
+
|
|
471
|
+
## UI Components
|
|
472
|
+
|
|
473
|
+
### FileBrowser Component
|
|
474
|
+
|
|
475
|
+
The `FileBrowser` is a complete, drop-in file management UI with:
|
|
476
|
+
|
|
477
|
+
- Folder tree navigation
|
|
478
|
+
- File list (grid or list view)
|
|
479
|
+
- Breadcrumb navigation
|
|
480
|
+
- File preview (images, text, PDFs)
|
|
481
|
+
- Context menus and actions
|
|
482
|
+
- Upload, download, rename, delete operations
|
|
483
|
+
- Drag-and-drop support (if implemented in API)
|
|
484
|
+
|
|
485
|
+
```typescript
|
|
486
|
+
import { FileBrowser } from 'hazo_files/ui';
|
|
487
|
+
|
|
488
|
+
<FileBrowser
|
|
489
|
+
api={api}
|
|
490
|
+
initialPath="/"
|
|
491
|
+
showPreview={true}
|
|
492
|
+
showTree={true}
|
|
493
|
+
viewMode="grid"
|
|
494
|
+
treeWidth={250}
|
|
495
|
+
previewHeight={300}
|
|
496
|
+
onError={(error) => console.error(error)}
|
|
497
|
+
onNavigate={(path) => console.log('Navigated to:', path)}
|
|
498
|
+
onSelect={(item) => console.log('Selected:', item)}
|
|
499
|
+
/>
|
|
500
|
+
```
|
|
501
|
+
|
|
502
|
+
### Individual Components
|
|
503
|
+
|
|
504
|
+
You can also use individual components:
|
|
505
|
+
|
|
506
|
+
```typescript
|
|
507
|
+
import {
|
|
508
|
+
PathBreadcrumb,
|
|
509
|
+
FolderTree,
|
|
510
|
+
FileList,
|
|
511
|
+
FilePreview,
|
|
512
|
+
FileActions
|
|
513
|
+
} from 'hazo_files/ui';
|
|
514
|
+
|
|
515
|
+
// Use individually with your own layout
|
|
516
|
+
```
|
|
517
|
+
|
|
518
|
+
### Hooks
|
|
519
|
+
|
|
520
|
+
```typescript
|
|
521
|
+
import { useFileBrowser, useFileOperations } from 'hazo_files/ui';
|
|
522
|
+
|
|
523
|
+
function MyCustomFileBrowser() {
|
|
524
|
+
const {
|
|
525
|
+
currentPath,
|
|
526
|
+
files,
|
|
527
|
+
tree,
|
|
528
|
+
selectedItem,
|
|
529
|
+
isLoading,
|
|
530
|
+
navigate,
|
|
531
|
+
refresh,
|
|
532
|
+
selectItem
|
|
533
|
+
} = useFileBrowser(api, '/');
|
|
534
|
+
|
|
535
|
+
const {
|
|
536
|
+
createFolder,
|
|
537
|
+
uploadFiles,
|
|
538
|
+
deleteItem,
|
|
539
|
+
renameItem
|
|
540
|
+
} = useFileOperations(api, currentPath);
|
|
541
|
+
|
|
542
|
+
// Build your custom UI
|
|
543
|
+
}
|
|
544
|
+
```
|
|
545
|
+
|
|
546
|
+
### Naming Rule Configurator
|
|
547
|
+
|
|
548
|
+
Build consistent file/folder naming patterns with a visual drag-and-drop interface:
|
|
549
|
+
|
|
550
|
+
```typescript
|
|
551
|
+
import { NamingRuleConfigurator } from 'hazo_files/ui';
|
|
552
|
+
import type { NamingVariable } from 'hazo_files/ui';
|
|
553
|
+
|
|
554
|
+
function NamingConfig() {
|
|
555
|
+
// Define user-specific variables
|
|
556
|
+
const userVariables: NamingVariable[] = [
|
|
557
|
+
{
|
|
558
|
+
variable_name: 'project_name',
|
|
559
|
+
description: 'Name of the project',
|
|
560
|
+
example_value: 'WebApp',
|
|
561
|
+
category: 'user'
|
|
562
|
+
},
|
|
563
|
+
{
|
|
564
|
+
variable_name: 'client_id',
|
|
565
|
+
description: 'Client identifier',
|
|
566
|
+
example_value: 'ACME',
|
|
567
|
+
category: 'user'
|
|
568
|
+
},
|
|
569
|
+
];
|
|
570
|
+
|
|
571
|
+
const handleSchemaChange = (schema) => {
|
|
572
|
+
console.log('New schema:', schema);
|
|
573
|
+
// Save to database or state
|
|
574
|
+
};
|
|
575
|
+
|
|
576
|
+
const handleExport = (schema) => {
|
|
577
|
+
// Export as JSON file
|
|
578
|
+
const blob = new Blob([JSON.stringify(schema, null, 2)], { type: 'application/json' });
|
|
579
|
+
const url = URL.createObjectURL(blob);
|
|
580
|
+
const a = document.createElement('a');
|
|
581
|
+
a.href = url;
|
|
582
|
+
a.download = 'naming-rule.json';
|
|
583
|
+
a.click();
|
|
584
|
+
};
|
|
585
|
+
|
|
586
|
+
return (
|
|
587
|
+
<NamingRuleConfigurator
|
|
588
|
+
variables={userVariables}
|
|
589
|
+
onChange={handleSchemaChange}
|
|
590
|
+
onExport={handleExport}
|
|
591
|
+
sampleFileName="proposal.pdf"
|
|
592
|
+
/>
|
|
593
|
+
);
|
|
594
|
+
}
|
|
595
|
+
```
|
|
596
|
+
|
|
597
|
+
The configurator provides:
|
|
598
|
+
- **Category Tabs**: User, Date, File, Counter variables
|
|
599
|
+
- **Drag & Drop**: Build patterns by dragging variables into file/folder patterns
|
|
600
|
+
- **Segment Reordering**: Drag segments within patterns to reorder them
|
|
601
|
+
- **Live Preview**: See generated names in real-time with example values
|
|
602
|
+
- **Undo/Redo**: Full history with keyboard shortcuts (Ctrl+Z, Ctrl+Y)
|
|
603
|
+
- **Import/Export**: Save and load naming rules as JSON
|
|
604
|
+
- **Scrollable Layout**: Works in fixed-height containers with scrollable content area
|
|
605
|
+
|
|
606
|
+
System variables included:
|
|
607
|
+
- **Date**: YYYY, YY, MM, DD, YYYY-MM-DD, MMM, MMMM, etc.
|
|
608
|
+
- **File**: original_name, extension, ext
|
|
609
|
+
- **Counter**: counter (auto-incrementing with padding)
|
|
610
|
+
|
|
611
|
+
## Naming Rules API
|
|
612
|
+
|
|
613
|
+
Generate file and folder names programmatically from naming schemas:
|
|
614
|
+
|
|
615
|
+
```typescript
|
|
616
|
+
import {
|
|
617
|
+
hazo_files_generate_file_name,
|
|
618
|
+
hazo_files_generate_folder_name,
|
|
619
|
+
createVariableSegment,
|
|
620
|
+
createLiteralSegment,
|
|
621
|
+
type NamingRuleSchema
|
|
622
|
+
} from 'hazo_files';
|
|
623
|
+
|
|
624
|
+
// Create a naming schema
|
|
625
|
+
const schema: NamingRuleSchema = {
|
|
626
|
+
version: 1,
|
|
627
|
+
filePattern: [
|
|
628
|
+
createVariableSegment('client_id'),
|
|
629
|
+
createLiteralSegment('_'),
|
|
630
|
+
createVariableSegment('project_name'),
|
|
631
|
+
createLiteralSegment('_'),
|
|
632
|
+
createVariableSegment('YYYY-MM-DD'),
|
|
633
|
+
createLiteralSegment('_'),
|
|
634
|
+
createVariableSegment('counter'),
|
|
635
|
+
],
|
|
636
|
+
folderPattern: [
|
|
637
|
+
createVariableSegment('YYYY'),
|
|
638
|
+
createLiteralSegment('/'),
|
|
639
|
+
createVariableSegment('client_id'),
|
|
640
|
+
createLiteralSegment('/'),
|
|
641
|
+
createVariableSegment('project_name'),
|
|
642
|
+
],
|
|
643
|
+
};
|
|
644
|
+
|
|
645
|
+
// Define variable values
|
|
646
|
+
const variables = {
|
|
647
|
+
client_id: 'ACME',
|
|
648
|
+
project_name: 'Website',
|
|
649
|
+
};
|
|
650
|
+
|
|
651
|
+
// Generate file name
|
|
652
|
+
const fileResult = hazo_files_generate_file_name(
|
|
653
|
+
schema,
|
|
654
|
+
variables,
|
|
655
|
+
'original-document.pdf',
|
|
656
|
+
{
|
|
657
|
+
counterValue: 42,
|
|
658
|
+
preserveExtension: true, // Keep original .pdf extension
|
|
659
|
+
date: new Date('2024-12-09'),
|
|
660
|
+
}
|
|
661
|
+
);
|
|
662
|
+
|
|
663
|
+
if (fileResult.success) {
|
|
664
|
+
console.log(fileResult.name);
|
|
665
|
+
// Output: "ACME_Website_2024-12-09_042.pdf"
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
// Generate folder path
|
|
669
|
+
const folderResult = hazo_files_generate_folder_name(schema, variables);
|
|
670
|
+
|
|
671
|
+
if (folderResult.success) {
|
|
672
|
+
console.log(folderResult.name);
|
|
673
|
+
// Output: "2024/ACME/Website"
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
// Use with FileManager
|
|
677
|
+
const uploadPath = `/${folderResult.name}/${fileResult.name}`;
|
|
678
|
+
await fileManager.uploadFile(buffer, uploadPath);
|
|
679
|
+
```
|
|
680
|
+
|
|
681
|
+
### Available System Variables
|
|
682
|
+
|
|
683
|
+
**Date Variables** (use current date unless overridden):
|
|
684
|
+
- `YYYY` - Full year (2024)
|
|
685
|
+
- `YY` - Two-digit year (24)
|
|
686
|
+
- `MM` - Month with zero padding (01-12)
|
|
687
|
+
- `M` - Month without padding (1-12)
|
|
688
|
+
- `DD` - Day with zero padding (01-31)
|
|
689
|
+
- `D` - Day without padding (1-31)
|
|
690
|
+
- `MMM` - Short month name (Jan, Feb, etc.)
|
|
691
|
+
- `MMMM` - Full month name (January, February, etc.)
|
|
692
|
+
- `YYYY-MM-DD` - ISO date format (2024-01-15)
|
|
693
|
+
- `YYYY-MMM-DD` - Date with month name (2024-Jan-15)
|
|
694
|
+
- `DD-MM-YYYY` - European format (15-01-2024)
|
|
695
|
+
- `MM-DD-YYYY` - US format (01-15-2024)
|
|
696
|
+
|
|
697
|
+
**File Metadata Variables** (from original filename):
|
|
698
|
+
- `original_name` - Filename without extension
|
|
699
|
+
- `extension` - File extension with dot (.pdf)
|
|
700
|
+
- `ext` - Extension without dot (pdf)
|
|
701
|
+
|
|
702
|
+
**Counter Variable**:
|
|
703
|
+
- `counter` - Auto-incrementing number with zero padding (001, 042, 123)
|
|
704
|
+
|
|
705
|
+
### Parsing Pattern Strings
|
|
706
|
+
|
|
707
|
+
You can also parse pattern strings directly:
|
|
708
|
+
|
|
709
|
+
```typescript
|
|
710
|
+
import { parsePatternString, patternToString } from 'hazo_files';
|
|
711
|
+
|
|
712
|
+
// Parse string to segments
|
|
713
|
+
const segments = parsePatternString('{client_id}_{YYYY-MM-DD}_{counter}');
|
|
714
|
+
console.log(segments);
|
|
715
|
+
// [
|
|
716
|
+
// { id: '...', type: 'variable', value: 'client_id' },
|
|
717
|
+
// { id: '...', type: 'literal', value: '_' },
|
|
718
|
+
// { id: '...', type: 'variable', value: 'YYYY-MM-DD' },
|
|
719
|
+
// { id: '...', type: 'literal', value: '_' },
|
|
720
|
+
// { id: '...', type: 'variable', value: 'counter' },
|
|
721
|
+
// ]
|
|
722
|
+
|
|
723
|
+
// Convert back to string
|
|
724
|
+
const patternStr = patternToString(segments);
|
|
725
|
+
// "{client_id}_{YYYY-MM-DD}_{counter}"
|
|
726
|
+
```
|
|
727
|
+
|
|
728
|
+
## API Reference
|
|
729
|
+
|
|
730
|
+
### FileManager
|
|
731
|
+
|
|
732
|
+
Main service class providing unified file operations.
|
|
733
|
+
|
|
734
|
+
#### Methods
|
|
735
|
+
|
|
736
|
+
- `initialize(config?: HazoFilesConfig): Promise<void>` - Initialize the file manager
|
|
737
|
+
- `createDirectory(path: string): Promise<OperationResult<FolderItem>>` - Create directory
|
|
738
|
+
- `removeDirectory(path: string, recursive?: boolean): Promise<OperationResult>` - Remove directory
|
|
739
|
+
- `uploadFile(source, remotePath, options?): Promise<OperationResult<FileItem>>` - Upload file
|
|
740
|
+
- `downloadFile(remotePath, localPath?, options?): Promise<OperationResult<Buffer | string>>` - Download file
|
|
741
|
+
- `moveItem(sourcePath, destinationPath, options?): Promise<OperationResult<FileSystemItem>>` - Move file/folder
|
|
742
|
+
- `deleteFile(path: string): Promise<OperationResult>` - Delete file
|
|
743
|
+
- `renameFile(path, newName, options?): Promise<OperationResult<FileItem>>` - Rename file
|
|
744
|
+
- `renameFolder(path, newName, options?): Promise<OperationResult<FolderItem>>` - Rename folder
|
|
745
|
+
- `listDirectory(path, options?): Promise<OperationResult<FileSystemItem[]>>` - List directory contents
|
|
746
|
+
- `getItem(path: string): Promise<OperationResult<FileSystemItem>>` - Get file/folder info
|
|
747
|
+
- `exists(path: string): Promise<boolean>` - Check if file/folder exists
|
|
748
|
+
- `getFolderTree(path?, depth?): Promise<OperationResult<TreeNode[]>>` - Get folder tree
|
|
749
|
+
- `writeFile(path, content, options?): Promise<OperationResult<FileItem>>` - Write text file
|
|
750
|
+
- `readFile(path: string): Promise<OperationResult<string>>` - Read text file
|
|
751
|
+
- `copyFile(sourcePath, destinationPath, options?): Promise<OperationResult<FileItem>>` - Copy file
|
|
752
|
+
- `ensureDirectory(path: string): Promise<OperationResult<FolderItem>>` - Ensure directory exists
|
|
753
|
+
|
|
754
|
+
### Types
|
|
755
|
+
|
|
756
|
+
```typescript
|
|
757
|
+
type StorageProvider = 'local' | 'google_drive';
|
|
758
|
+
|
|
759
|
+
interface FileItem {
|
|
760
|
+
id: string;
|
|
761
|
+
name: string;
|
|
762
|
+
path: string;
|
|
763
|
+
size: number;
|
|
764
|
+
mimeType: string;
|
|
765
|
+
createdAt: Date;
|
|
766
|
+
modifiedAt: Date;
|
|
767
|
+
isDirectory: false;
|
|
768
|
+
parentId?: string;
|
|
769
|
+
metadata?: Record<string, unknown>;
|
|
770
|
+
}
|
|
771
|
+
|
|
772
|
+
interface FolderItem {
|
|
773
|
+
id: string;
|
|
774
|
+
name: string;
|
|
775
|
+
path: string;
|
|
776
|
+
createdAt: Date;
|
|
777
|
+
modifiedAt: Date;
|
|
778
|
+
isDirectory: true;
|
|
779
|
+
parentId?: string;
|
|
780
|
+
children?: (FileItem | FolderItem)[];
|
|
781
|
+
metadata?: Record<string, unknown>;
|
|
782
|
+
}
|
|
783
|
+
|
|
784
|
+
interface OperationResult<T = void> {
|
|
785
|
+
success: boolean;
|
|
786
|
+
data?: T;
|
|
787
|
+
error?: string;
|
|
788
|
+
}
|
|
789
|
+
|
|
790
|
+
interface UploadOptions {
|
|
791
|
+
overwrite?: boolean;
|
|
792
|
+
onProgress?: (progress: number, bytesTransferred: number, totalBytes: number) => void;
|
|
793
|
+
metadata?: Record<string, unknown>;
|
|
794
|
+
}
|
|
795
|
+
```
|
|
796
|
+
|
|
797
|
+
See `src/types/index.ts` for complete type definitions.
|
|
798
|
+
|
|
799
|
+
## Error Handling
|
|
800
|
+
|
|
801
|
+
hazo_files provides comprehensive error types:
|
|
802
|
+
|
|
803
|
+
```typescript
|
|
804
|
+
import {
|
|
805
|
+
FileNotFoundError,
|
|
806
|
+
DirectoryNotFoundError,
|
|
807
|
+
FileExistsError,
|
|
808
|
+
DirectoryExistsError,
|
|
809
|
+
DirectoryNotEmptyError,
|
|
810
|
+
PermissionDeniedError,
|
|
811
|
+
InvalidPathError,
|
|
812
|
+
FileTooLargeError,
|
|
813
|
+
InvalidExtensionError,
|
|
814
|
+
AuthenticationError,
|
|
815
|
+
ConfigurationError,
|
|
816
|
+
OperationError
|
|
817
|
+
} from 'hazo_files';
|
|
818
|
+
|
|
819
|
+
// Use in try-catch
|
|
820
|
+
try {
|
|
821
|
+
await fileManager.uploadFile(buffer, '/files/test.exe');
|
|
822
|
+
} catch (error) {
|
|
823
|
+
if (error instanceof InvalidExtensionError) {
|
|
824
|
+
console.error('File type not allowed');
|
|
825
|
+
} else if (error instanceof FileTooLargeError) {
|
|
826
|
+
console.error('File is too large');
|
|
827
|
+
}
|
|
828
|
+
}
|
|
829
|
+
```
|
|
830
|
+
|
|
831
|
+
## Extending with Custom Storage Providers
|
|
832
|
+
|
|
833
|
+
See [docs/ADDING_MODULES.md](docs/ADDING_MODULES.md) for a complete guide on creating custom storage modules.
|
|
834
|
+
|
|
835
|
+
Quick example:
|
|
836
|
+
|
|
837
|
+
```typescript
|
|
838
|
+
import { BaseStorageModule } from 'hazo_files';
|
|
839
|
+
import type { StorageProvider, OperationResult, FileItem } from 'hazo_files';
|
|
840
|
+
|
|
841
|
+
class S3StorageModule extends BaseStorageModule {
|
|
842
|
+
readonly provider: StorageProvider = 's3' as StorageProvider;
|
|
843
|
+
|
|
844
|
+
async initialize(config: HazoFilesConfig): Promise<void> {
|
|
845
|
+
await super.initialize(config);
|
|
846
|
+
// Initialize S3 client
|
|
847
|
+
}
|
|
848
|
+
|
|
849
|
+
async uploadFile(source, remotePath, options?): Promise<OperationResult<FileItem>> {
|
|
850
|
+
// Implement S3 upload
|
|
851
|
+
}
|
|
852
|
+
|
|
853
|
+
// Implement other required methods...
|
|
854
|
+
}
|
|
855
|
+
|
|
856
|
+
// Register the module
|
|
857
|
+
import { registerModule } from 'hazo_files';
|
|
858
|
+
registerModule('s3', () => new S3StorageModule());
|
|
859
|
+
```
|
|
860
|
+
|
|
861
|
+
## Testing
|
|
862
|
+
|
|
863
|
+
The package includes a test application in `test-app/` demonstrating:
|
|
864
|
+
|
|
865
|
+
- Next.js 14+ integration
|
|
866
|
+
- API routes for file operations
|
|
867
|
+
- FileBrowser UI component usage
|
|
868
|
+
- Local storage and Google Drive switching
|
|
869
|
+
- OAuth flow implementation
|
|
870
|
+
|
|
871
|
+
To run the test app:
|
|
872
|
+
|
|
873
|
+
```bash
|
|
874
|
+
cd test-app
|
|
875
|
+
npm install
|
|
876
|
+
npm run dev
|
|
877
|
+
```
|
|
878
|
+
|
|
879
|
+
Visit `http://localhost:3000`
|
|
880
|
+
|
|
881
|
+
## Browser Compatibility
|
|
882
|
+
|
|
883
|
+
The UI components require:
|
|
884
|
+
|
|
885
|
+
- Modern browsers with ES2020+ support
|
|
886
|
+
- React 18+
|
|
887
|
+
- CSS Grid and Flexbox support
|
|
888
|
+
|
|
889
|
+
Server-side code requires Node.js 16+
|
|
890
|
+
|
|
891
|
+
## License
|
|
892
|
+
|
|
893
|
+
MIT License - see LICENSE file for details
|
|
894
|
+
|
|
895
|
+
## Contributing
|
|
896
|
+
|
|
897
|
+
Contributions are welcome! Please:
|
|
898
|
+
|
|
899
|
+
1. Fork the repository
|
|
900
|
+
2. Create a feature branch
|
|
901
|
+
3. Commit your changes with clear messages
|
|
902
|
+
4. Add tests for new functionality
|
|
903
|
+
5. Submit a pull request
|
|
904
|
+
|
|
905
|
+
## Support
|
|
906
|
+
|
|
907
|
+
- GitHub Issues: [https://github.com/pub12/hazo_files/issues](https://github.com/pub12/hazo_files/issues)
|
|
908
|
+
- Documentation: [https://github.com/pub12/hazo_files](https://github.com/pub12/hazo_files)
|
|
909
|
+
|
|
910
|
+
## Roadmap
|
|
911
|
+
|
|
912
|
+
- Amazon S3 storage module
|
|
913
|
+
- Dropbox storage module
|
|
914
|
+
- OneDrive storage module
|
|
915
|
+
- WebDAV support
|
|
916
|
+
- Advanced search and filtering
|
|
917
|
+
- Batch operations
|
|
918
|
+
- File versioning
|
|
919
|
+
- Sharing and permissions
|
|
920
|
+
|
|
921
|
+
## Credits
|
|
922
|
+
|
|
923
|
+
Created by Pubs Abayasiri
|
|
924
|
+
|
|
925
|
+
Built with:
|
|
926
|
+
- TypeScript
|
|
927
|
+
- React
|
|
928
|
+
- Google APIs (googleapis)
|
|
929
|
+
- tsup for building
|