@flusys/ng-storage 1.0.0-beta → 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/README.md +209 -5
- package/fesm2022/flusys-ng-storage-file-manager-form.component-Bv8ER8J0.mjs +484 -0
- package/fesm2022/flusys-ng-storage-file-manager-form.component-Bv8ER8J0.mjs.map +1 -0
- package/fesm2022/flusys-ng-storage-file-manager-list.component-McM1zSlE.mjs +304 -0
- package/fesm2022/flusys-ng-storage-file-manager-list.component-McM1zSlE.mjs.map +1 -0
- package/fesm2022/flusys-ng-storage-folder-form.component-fRFiMFev.mjs +155 -0
- package/fesm2022/flusys-ng-storage-folder-form.component-fRFiMFev.mjs.map +1 -0
- package/fesm2022/flusys-ng-storage-folder-list.component-C4xHI8SO.mjs +194 -0
- package/fesm2022/flusys-ng-storage-folder-list.component-C4xHI8SO.mjs.map +1 -0
- package/fesm2022/{flusys-ng-storage-storage-config-form.component-Bc95TVD0.mjs → flusys-ng-storage-storage-config-form.component-DEMexjmg.mjs} +242 -292
- package/fesm2022/flusys-ng-storage-storage-config-form.component-DEMexjmg.mjs.map +1 -0
- package/fesm2022/flusys-ng-storage-storage-config-list.component-iJez-rII.mjs +187 -0
- package/fesm2022/flusys-ng-storage-storage-config-list.component-iJez-rII.mjs.map +1 -0
- package/fesm2022/flusys-ng-storage.mjs +363 -199
- package/fesm2022/flusys-ng-storage.mjs.map +1 -1
- package/package.json +9 -9
- package/types/flusys-ng-storage.d.ts +150 -127
- package/fesm2022/flusys-ng-storage-file-manager-form.component-DVZsFYrj.mjs +0 -715
- package/fesm2022/flusys-ng-storage-file-manager-form.component-DVZsFYrj.mjs.map +0 -1
- package/fesm2022/flusys-ng-storage-file-manager-list.component-CwnyNoYb.mjs +0 -475
- package/fesm2022/flusys-ng-storage-file-manager-list.component-CwnyNoYb.mjs.map +0 -1
- package/fesm2022/flusys-ng-storage-folder-form.component-DH-KCz21.mjs +0 -253
- package/fesm2022/flusys-ng-storage-folder-form.component-DH-KCz21.mjs.map +0 -1
- package/fesm2022/flusys-ng-storage-folder-list.component-iIys4NvL.mjs +0 -267
- package/fesm2022/flusys-ng-storage-folder-list.component-iIys4NvL.mjs.map +0 -1
- package/fesm2022/flusys-ng-storage-storage-config-form.component-Bc95TVD0.mjs.map +0 -1
- package/fesm2022/flusys-ng-storage-storage-config-list.component-Bfomqyr3.mjs +0 -328
- package/fesm2022/flusys-ng-storage-storage-config-list.component-Bfomqyr3.mjs.map +0 -1
|
@@ -0,0 +1,484 @@
|
|
|
1
|
+
import * as i0 from '@angular/core';
|
|
2
|
+
import { inject, signal, computed, effect, ChangeDetectionStrategy, Component } from '@angular/core';
|
|
3
|
+
import { toSignal } from '@angular/core/rxjs-interop';
|
|
4
|
+
import { form, required, FormField } from '@angular/forms/signals';
|
|
5
|
+
import { ActivatedRoute } from '@angular/router';
|
|
6
|
+
import { AngularModule, PrimeModule } from '@flusys/ng-shared';
|
|
7
|
+
import { firstValueFrom } from 'rxjs';
|
|
8
|
+
import { BaseStoragePage, FileManagerApiService, UploadService, StorageConfigApiService, FolderApiService, formatFileSize, getProviderLabel } from './flusys-ng-storage.mjs';
|
|
9
|
+
import * as i1 from '@angular/forms';
|
|
10
|
+
import * as i1$1 from 'primeng/button';
|
|
11
|
+
import * as i3 from 'primeng/fileupload';
|
|
12
|
+
import * as i3$1 from 'primeng/inputtext';
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* File Manager Form Component
|
|
16
|
+
* Upload and edit file metadata
|
|
17
|
+
*/
|
|
18
|
+
class FileManagerFormComponent extends BaseStoragePage {
|
|
19
|
+
route = inject(ActivatedRoute);
|
|
20
|
+
fileService = inject(FileManagerApiService);
|
|
21
|
+
uploadService = inject(UploadService);
|
|
22
|
+
configService = inject(StorageConfigApiService);
|
|
23
|
+
folderService = inject(FolderApiService);
|
|
24
|
+
routeParams = toSignal(this.route.paramMap);
|
|
25
|
+
initialized = false;
|
|
26
|
+
isLoadingConfigs = signal(false, ...(ngDevMode ? [{ debugName: "isLoadingConfigs" }] : []));
|
|
27
|
+
isLoadingFolders = signal(false, ...(ngDevMode ? [{ debugName: "isLoadingFolders" }] : []));
|
|
28
|
+
existingFile = signal(null, ...(ngDevMode ? [{ debugName: "existingFile" }] : []));
|
|
29
|
+
isEditMode = computed(() => !!this.existingFile(), ...(ngDevMode ? [{ debugName: "isEditMode" }] : []));
|
|
30
|
+
selectedFile = signal(null, ...(ngDevMode ? [{ debugName: "selectedFile" }] : []));
|
|
31
|
+
availableConfigs = signal([], ...(ngDevMode ? [{ debugName: "availableConfigs" }] : []));
|
|
32
|
+
availableFolders = signal([], ...(ngDevMode ? [{ debugName: "availableFolders" }] : []));
|
|
33
|
+
/** Utility functions exposed to template */
|
|
34
|
+
formatFileSize = formatFileSize;
|
|
35
|
+
getProviderLabel = getProviderLabel;
|
|
36
|
+
/** Form model */
|
|
37
|
+
formModel = signal({
|
|
38
|
+
id: '',
|
|
39
|
+
name: '',
|
|
40
|
+
isPrivate: false,
|
|
41
|
+
folderId: '',
|
|
42
|
+
storageConfigId: '',
|
|
43
|
+
}, ...(ngDevMode ? [{ debugName: "formModel" }] : []));
|
|
44
|
+
/** Form with validation */
|
|
45
|
+
fileForm = form(this.formModel, (f) => {
|
|
46
|
+
required(f.name, { message: 'File name is required' });
|
|
47
|
+
});
|
|
48
|
+
/** Check if form is valid */
|
|
49
|
+
isFormValid = computed(() => this.formModel().name.trim().length > 0, ...(ngDevMode ? [{ debugName: "isFormValid" }] : []));
|
|
50
|
+
constructor() {
|
|
51
|
+
super();
|
|
52
|
+
effect(() => {
|
|
53
|
+
const params = this.routeParams();
|
|
54
|
+
if (!params || this.initialized)
|
|
55
|
+
return;
|
|
56
|
+
this.initialized = true;
|
|
57
|
+
const fileId = params.get('id');
|
|
58
|
+
if (fileId) {
|
|
59
|
+
this.loadFile(fileId);
|
|
60
|
+
}
|
|
61
|
+
else {
|
|
62
|
+
this.loadStorageConfigs();
|
|
63
|
+
}
|
|
64
|
+
this.loadFolders();
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
async loadStorageConfigs() {
|
|
68
|
+
this.isLoadingConfigs.set(true);
|
|
69
|
+
try {
|
|
70
|
+
const response = await firstValueFrom(this.configService.getAll('', {
|
|
71
|
+
pagination: { currentPage: 0, pageSize: 100 },
|
|
72
|
+
filter: {},
|
|
73
|
+
select: [],
|
|
74
|
+
sort: {},
|
|
75
|
+
}));
|
|
76
|
+
if (response.success && response.data) {
|
|
77
|
+
this.availableConfigs.set(response.data);
|
|
78
|
+
if (response.data.length === 1) {
|
|
79
|
+
this.formModel.update((m) => ({ ...m, storageConfigId: response.data[0].id }));
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
catch {
|
|
84
|
+
this.showError('Failed to load storage configurations.');
|
|
85
|
+
}
|
|
86
|
+
finally {
|
|
87
|
+
this.isLoadingConfigs.set(false);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
async loadFolders() {
|
|
91
|
+
this.isLoadingFolders.set(true);
|
|
92
|
+
try {
|
|
93
|
+
const response = await firstValueFrom(this.folderService.getAll('', {
|
|
94
|
+
pagination: { currentPage: 0, pageSize: 100 },
|
|
95
|
+
filter: {},
|
|
96
|
+
select: [],
|
|
97
|
+
sort: {},
|
|
98
|
+
}));
|
|
99
|
+
if (response.success && response.data) {
|
|
100
|
+
this.availableFolders.set(response.data);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
catch {
|
|
104
|
+
// Silently fail - folders are optional
|
|
105
|
+
}
|
|
106
|
+
finally {
|
|
107
|
+
this.isLoadingFolders.set(false);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
async loadFile(id) {
|
|
111
|
+
this.isLoading.set(true);
|
|
112
|
+
try {
|
|
113
|
+
const response = await this.fileService.findByIdAsync(id);
|
|
114
|
+
if (response.success && response.data) {
|
|
115
|
+
const file = response.data;
|
|
116
|
+
this.existingFile.set(file);
|
|
117
|
+
this.formModel.set({
|
|
118
|
+
id: file.id,
|
|
119
|
+
name: file.name,
|
|
120
|
+
isPrivate: file.isPrivate ?? false,
|
|
121
|
+
folderId: file.folderId ?? '',
|
|
122
|
+
storageConfigId: '',
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
catch {
|
|
127
|
+
this.showError('Failed to load file.');
|
|
128
|
+
this.navigateTo(['/storage/files']);
|
|
129
|
+
}
|
|
130
|
+
finally {
|
|
131
|
+
this.isLoading.set(false);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
onFileSelect(event) {
|
|
135
|
+
const file = event.files?.[0] || event.currentFiles?.[0];
|
|
136
|
+
if (file) {
|
|
137
|
+
this.selectedFile.set(file);
|
|
138
|
+
this.formModel.update((m) => ({ ...m, name: file.name }));
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
async onSubmit() {
|
|
142
|
+
if (!this.isFormValid())
|
|
143
|
+
return;
|
|
144
|
+
this.isLoading.set(true);
|
|
145
|
+
try {
|
|
146
|
+
const formValue = this.formModel();
|
|
147
|
+
if (this.isEditMode()) {
|
|
148
|
+
await this.fileService.updateAsync({
|
|
149
|
+
id: formValue.id,
|
|
150
|
+
name: formValue.name,
|
|
151
|
+
isPrivate: formValue.isPrivate,
|
|
152
|
+
folderId: formValue.folderId || null,
|
|
153
|
+
});
|
|
154
|
+
this.showSuccess('File updated successfully.');
|
|
155
|
+
}
|
|
156
|
+
else {
|
|
157
|
+
const file = this.selectedFile();
|
|
158
|
+
if (!file) {
|
|
159
|
+
this.showError('Please select a file to upload.');
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
if (!formValue.storageConfigId) {
|
|
163
|
+
this.showError('Please select a storage configuration.');
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
const uploadResponse = await firstValueFrom(this.uploadService.uploadSingleFile(file, {
|
|
167
|
+
compress: true,
|
|
168
|
+
quality: 85,
|
|
169
|
+
storageConfigId: formValue.storageConfigId,
|
|
170
|
+
}));
|
|
171
|
+
if (uploadResponse.success && uploadResponse.data) {
|
|
172
|
+
const uploadedFileInfo = uploadResponse.data;
|
|
173
|
+
await this.fileService.createFileAsync({
|
|
174
|
+
name: formValue.name,
|
|
175
|
+
key: uploadedFileInfo.key,
|
|
176
|
+
contentType: file.type || 'application/octet-stream',
|
|
177
|
+
size: Math.round(file.size / 1024).toString(),
|
|
178
|
+
isPrivate: formValue.isPrivate,
|
|
179
|
+
folderId: formValue.folderId || undefined,
|
|
180
|
+
storageConfigId: formValue.storageConfigId,
|
|
181
|
+
});
|
|
182
|
+
this.showSuccess('File uploaded successfully.');
|
|
183
|
+
}
|
|
184
|
+
else {
|
|
185
|
+
throw new Error('Upload failed');
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
this.navigateTo(['/storage/files']);
|
|
189
|
+
}
|
|
190
|
+
catch (error) {
|
|
191
|
+
this.showError(error?.message || `Failed to ${this.isEditMode() ? 'update' : 'upload'} file.`);
|
|
192
|
+
}
|
|
193
|
+
finally {
|
|
194
|
+
this.isLoading.set(false);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
onCancel() {
|
|
198
|
+
this.navigateTo(['/storage/files']);
|
|
199
|
+
}
|
|
200
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: FileManagerFormComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
201
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.3", type: FileManagerFormComponent, isStandalone: true, selector: "lib-file-manager-form", usesInheritance: true, ngImport: i0, template: `
|
|
202
|
+
<div class="card">
|
|
203
|
+
<div class="mb-4">
|
|
204
|
+
<h3 class="text-lg sm:text-xl font-semibold">{{ isEditMode() ? 'Edit File' : 'Upload File' }}</h3>
|
|
205
|
+
@if (showCompanyInfo()) {
|
|
206
|
+
<p class="text-sm text-muted-color mt-1">Company: {{ currentCompanyName() }}</p>
|
|
207
|
+
}
|
|
208
|
+
</div>
|
|
209
|
+
|
|
210
|
+
<form (ngSubmit)="onSubmit()">
|
|
211
|
+
@if (!isEditMode()) {
|
|
212
|
+
<div class="grid grid-cols-1 md:grid-cols-2 gap-4 mb-4">
|
|
213
|
+
<div class="field">
|
|
214
|
+
<label for="storageConfig" class="block font-medium mb-2 text-sm sm:text-base">Storage Configuration *</label>
|
|
215
|
+
<select id="storageConfig" class="p-select-native w-full" [formField]="fileForm.storageConfigId">
|
|
216
|
+
<option value="">Select storage configuration...</option>
|
|
217
|
+
@for (config of availableConfigs(); track config.id) {
|
|
218
|
+
<option [value]="config.id">{{ config.name }} ({{ getProviderLabel(config.storage) }})</option>
|
|
219
|
+
}
|
|
220
|
+
</select>
|
|
221
|
+
@if (availableConfigs().length === 0 && !isLoadingConfigs()) {
|
|
222
|
+
<small class="text-warn block mt-2">
|
|
223
|
+
<i class="pi pi-exclamation-triangle mr-1"></i>
|
|
224
|
+
No storage configurations found. Please create one first.
|
|
225
|
+
</small>
|
|
226
|
+
}
|
|
227
|
+
</div>
|
|
228
|
+
|
|
229
|
+
<div class="field">
|
|
230
|
+
<label for="folder" class="block font-medium mb-2 text-sm sm:text-base">Folder (Optional)</label>
|
|
231
|
+
<select id="folder" class="p-select-native w-full" [formField]="fileForm.folderId">
|
|
232
|
+
<option value="">No folder (root level)</option>
|
|
233
|
+
@for (folder of availableFolders(); track folder.id) {
|
|
234
|
+
<option [value]="folder.id">{{ folder.name }}</option>
|
|
235
|
+
}
|
|
236
|
+
</select>
|
|
237
|
+
@if (availableFolders().length === 0 && !isLoadingFolders()) {
|
|
238
|
+
<small class="text-muted-color block mt-2">
|
|
239
|
+
<i class="pi pi-info-circle mr-1"></i>
|
|
240
|
+
No folders available. File will be uploaded to root level.
|
|
241
|
+
</small>
|
|
242
|
+
}
|
|
243
|
+
</div>
|
|
244
|
+
</div>
|
|
245
|
+
} @else {
|
|
246
|
+
<div class="field mb-4">
|
|
247
|
+
<label for="folder" class="block font-medium mb-2 text-sm sm:text-base">Folder (Optional)</label>
|
|
248
|
+
<select id="folder" class="p-select-native w-full" [formField]="fileForm.folderId">
|
|
249
|
+
<option value="">No folder (root level)</option>
|
|
250
|
+
@for (folder of availableFolders(); track folder.id) {
|
|
251
|
+
<option [value]="folder.id">{{ folder.name }}</option>
|
|
252
|
+
}
|
|
253
|
+
</select>
|
|
254
|
+
@if (availableFolders().length === 0 && !isLoadingFolders()) {
|
|
255
|
+
<small class="text-muted-color block mt-2">
|
|
256
|
+
<i class="pi pi-info-circle mr-1"></i>
|
|
257
|
+
No folders available.
|
|
258
|
+
</small>
|
|
259
|
+
}
|
|
260
|
+
</div>
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
@if (!isEditMode()) {
|
|
264
|
+
<div class="field mb-4">
|
|
265
|
+
<label class="block font-medium mb-2 text-sm sm:text-base">Select File *</label>
|
|
266
|
+
<p-fileupload
|
|
267
|
+
#fileUpload
|
|
268
|
+
[customUpload]="true"
|
|
269
|
+
(onSelect)="onFileSelect($event)"
|
|
270
|
+
[multiple]="false"
|
|
271
|
+
[maxFileSize]="50000000"
|
|
272
|
+
[showUploadButton]="false"
|
|
273
|
+
[showCancelButton]="false"
|
|
274
|
+
chooseLabel="Choose File"
|
|
275
|
+
chooseIcon="pi pi-upload"
|
|
276
|
+
accept="*/*"
|
|
277
|
+
class="w-full"
|
|
278
|
+
>
|
|
279
|
+
<ng-template #content let-files>
|
|
280
|
+
@if (files && files.length > 0) {
|
|
281
|
+
<div class="flex flex-col gap-2">
|
|
282
|
+
@for (file of files; track file.name) {
|
|
283
|
+
<div class="flex items-center gap-3 p-3 border border-surface rounded-lg bg-surface-ground">
|
|
284
|
+
<i class="pi pi-file text-xl sm:text-2xl text-primary"></i>
|
|
285
|
+
<div class="flex-1 min-w-0">
|
|
286
|
+
<div class="font-medium truncate">{{ file.name }}</div>
|
|
287
|
+
<div class="text-sm text-muted-color">{{ formatFileSize(file.size) }}</div>
|
|
288
|
+
</div>
|
|
289
|
+
<p-button icon="pi pi-times" [text]="true" severity="danger" size="small" (onClick)="fileUpload.clear()" />
|
|
290
|
+
</div>
|
|
291
|
+
}
|
|
292
|
+
</div>
|
|
293
|
+
} @else {
|
|
294
|
+
<div class="flex items-center justify-center flex-col p-6 sm:p-8 border-2 border-dashed border-surface rounded-lg">
|
|
295
|
+
<i class="pi pi-cloud-upload text-3xl sm:text-4xl text-muted-color mb-3"></i>
|
|
296
|
+
<p class="text-muted-color text-center text-sm sm:text-base">Drag and drop file here or click to browse</p>
|
|
297
|
+
<p class="text-xs sm:text-sm text-muted-color mt-2">Maximum file size: 50MB</p>
|
|
298
|
+
</div>
|
|
299
|
+
}
|
|
300
|
+
</ng-template>
|
|
301
|
+
</p-fileupload>
|
|
302
|
+
@if (!selectedFile() && !isEditMode()) {
|
|
303
|
+
<small class="text-muted-color">Please select a file to upload</small>
|
|
304
|
+
}
|
|
305
|
+
</div>
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
|
309
|
+
<div class="field md:col-span-2">
|
|
310
|
+
<label for="name" class="block font-medium mb-2 text-sm sm:text-base">File Name *</label>
|
|
311
|
+
<input pInputText id="name" [formField]="fileForm.name" [placeholder]="isEditMode() ? 'Enter file name' : 'Will be auto-filled from selected file'" class="w-full" />
|
|
312
|
+
</div>
|
|
313
|
+
|
|
314
|
+
<div class="field">
|
|
315
|
+
<label class="p-checkbox-native flex items-center gap-2">
|
|
316
|
+
<input type="checkbox" [formField]="fileForm.isPrivate" />
|
|
317
|
+
<span>Private File</span>
|
|
318
|
+
</label>
|
|
319
|
+
<small class="text-muted-color block mt-2">
|
|
320
|
+
{{ formModel().isPrivate ? 'Only users with permission can access' : 'File is publicly accessible' }}
|
|
321
|
+
</small>
|
|
322
|
+
</div>
|
|
323
|
+
</div>
|
|
324
|
+
|
|
325
|
+
<div class="flex justify-end gap-2 mt-4">
|
|
326
|
+
<p-button label="Cancel" severity="secondary" [outlined]="true" (onClick)="onCancel()" />
|
|
327
|
+
<p-button
|
|
328
|
+
type="submit"
|
|
329
|
+
[label]="isEditMode() ? 'Update' : 'Upload'"
|
|
330
|
+
[loading]="isLoading()"
|
|
331
|
+
[disabled]="!isFormValid() || (!isEditMode() && !selectedFile()) || isLoading()"
|
|
332
|
+
/>
|
|
333
|
+
</div>
|
|
334
|
+
</form>
|
|
335
|
+
</div>
|
|
336
|
+
`, isInline: true, dependencies: [{ kind: "ngmodule", type: AngularModule }, { kind: "directive", type: i1.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1.NgSelectOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i1.ɵNgSelectMultipleOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],[formArray],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1.NgForm, selector: "form:not([ngNoForm]):not([formGroup]):not([formArray]),ng-form,[ngForm]", inputs: ["ngFormOptions"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "ngmodule", type: PrimeModule }, { kind: "component", type: i1$1.Button, selector: "p-button", inputs: ["hostName", "type", "badge", "disabled", "raised", "rounded", "text", "plain", "outlined", "link", "tabindex", "size", "variant", "style", "styleClass", "badgeClass", "badgeSeverity", "ariaLabel", "autofocus", "iconPos", "icon", "label", "loading", "loadingIcon", "severity", "buttonProps", "fluid"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "component", type: i3.FileUpload, selector: "p-fileupload, p-fileUpload", inputs: ["name", "url", "method", "multiple", "accept", "disabled", "auto", "withCredentials", "maxFileSize", "invalidFileSizeMessageSummary", "invalidFileSizeMessageDetail", "invalidFileTypeMessageSummary", "invalidFileTypeMessageDetail", "invalidFileLimitMessageDetail", "invalidFileLimitMessageSummary", "style", "styleClass", "previewWidth", "chooseLabel", "uploadLabel", "cancelLabel", "chooseIcon", "uploadIcon", "cancelIcon", "showUploadButton", "showCancelButton", "mode", "headers", "customUpload", "fileLimit", "uploadStyleClass", "cancelStyleClass", "removeStyleClass", "chooseStyleClass", "chooseButtonProps", "uploadButtonProps", "cancelButtonProps", "files"], outputs: ["onBeforeUpload", "onSend", "onUpload", "onError", "onClear", "onRemove", "onSelect", "onProgress", "uploadHandler", "onImageError", "onRemoveUploadedFile"] }, { kind: "directive", type: i3$1.InputText, selector: "[pInputText]", inputs: ["hostName", "ptInputText", "pInputTextPT", "pInputTextUnstyled", "pSize", "variant", "fluid", "invalid"] }, { kind: "directive", type: FormField, selector: "[formField]", inputs: ["formField"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
337
|
+
}
|
|
338
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: FileManagerFormComponent, decorators: [{
|
|
339
|
+
type: Component,
|
|
340
|
+
args: [{
|
|
341
|
+
selector: 'lib-file-manager-form',
|
|
342
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
343
|
+
imports: [AngularModule, PrimeModule, FormField],
|
|
344
|
+
template: `
|
|
345
|
+
<div class="card">
|
|
346
|
+
<div class="mb-4">
|
|
347
|
+
<h3 class="text-lg sm:text-xl font-semibold">{{ isEditMode() ? 'Edit File' : 'Upload File' }}</h3>
|
|
348
|
+
@if (showCompanyInfo()) {
|
|
349
|
+
<p class="text-sm text-muted-color mt-1">Company: {{ currentCompanyName() }}</p>
|
|
350
|
+
}
|
|
351
|
+
</div>
|
|
352
|
+
|
|
353
|
+
<form (ngSubmit)="onSubmit()">
|
|
354
|
+
@if (!isEditMode()) {
|
|
355
|
+
<div class="grid grid-cols-1 md:grid-cols-2 gap-4 mb-4">
|
|
356
|
+
<div class="field">
|
|
357
|
+
<label for="storageConfig" class="block font-medium mb-2 text-sm sm:text-base">Storage Configuration *</label>
|
|
358
|
+
<select id="storageConfig" class="p-select-native w-full" [formField]="fileForm.storageConfigId">
|
|
359
|
+
<option value="">Select storage configuration...</option>
|
|
360
|
+
@for (config of availableConfigs(); track config.id) {
|
|
361
|
+
<option [value]="config.id">{{ config.name }} ({{ getProviderLabel(config.storage) }})</option>
|
|
362
|
+
}
|
|
363
|
+
</select>
|
|
364
|
+
@if (availableConfigs().length === 0 && !isLoadingConfigs()) {
|
|
365
|
+
<small class="text-warn block mt-2">
|
|
366
|
+
<i class="pi pi-exclamation-triangle mr-1"></i>
|
|
367
|
+
No storage configurations found. Please create one first.
|
|
368
|
+
</small>
|
|
369
|
+
}
|
|
370
|
+
</div>
|
|
371
|
+
|
|
372
|
+
<div class="field">
|
|
373
|
+
<label for="folder" class="block font-medium mb-2 text-sm sm:text-base">Folder (Optional)</label>
|
|
374
|
+
<select id="folder" class="p-select-native w-full" [formField]="fileForm.folderId">
|
|
375
|
+
<option value="">No folder (root level)</option>
|
|
376
|
+
@for (folder of availableFolders(); track folder.id) {
|
|
377
|
+
<option [value]="folder.id">{{ folder.name }}</option>
|
|
378
|
+
}
|
|
379
|
+
</select>
|
|
380
|
+
@if (availableFolders().length === 0 && !isLoadingFolders()) {
|
|
381
|
+
<small class="text-muted-color block mt-2">
|
|
382
|
+
<i class="pi pi-info-circle mr-1"></i>
|
|
383
|
+
No folders available. File will be uploaded to root level.
|
|
384
|
+
</small>
|
|
385
|
+
}
|
|
386
|
+
</div>
|
|
387
|
+
</div>
|
|
388
|
+
} @else {
|
|
389
|
+
<div class="field mb-4">
|
|
390
|
+
<label for="folder" class="block font-medium mb-2 text-sm sm:text-base">Folder (Optional)</label>
|
|
391
|
+
<select id="folder" class="p-select-native w-full" [formField]="fileForm.folderId">
|
|
392
|
+
<option value="">No folder (root level)</option>
|
|
393
|
+
@for (folder of availableFolders(); track folder.id) {
|
|
394
|
+
<option [value]="folder.id">{{ folder.name }}</option>
|
|
395
|
+
}
|
|
396
|
+
</select>
|
|
397
|
+
@if (availableFolders().length === 0 && !isLoadingFolders()) {
|
|
398
|
+
<small class="text-muted-color block mt-2">
|
|
399
|
+
<i class="pi pi-info-circle mr-1"></i>
|
|
400
|
+
No folders available.
|
|
401
|
+
</small>
|
|
402
|
+
}
|
|
403
|
+
</div>
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
@if (!isEditMode()) {
|
|
407
|
+
<div class="field mb-4">
|
|
408
|
+
<label class="block font-medium mb-2 text-sm sm:text-base">Select File *</label>
|
|
409
|
+
<p-fileupload
|
|
410
|
+
#fileUpload
|
|
411
|
+
[customUpload]="true"
|
|
412
|
+
(onSelect)="onFileSelect($event)"
|
|
413
|
+
[multiple]="false"
|
|
414
|
+
[maxFileSize]="50000000"
|
|
415
|
+
[showUploadButton]="false"
|
|
416
|
+
[showCancelButton]="false"
|
|
417
|
+
chooseLabel="Choose File"
|
|
418
|
+
chooseIcon="pi pi-upload"
|
|
419
|
+
accept="*/*"
|
|
420
|
+
class="w-full"
|
|
421
|
+
>
|
|
422
|
+
<ng-template #content let-files>
|
|
423
|
+
@if (files && files.length > 0) {
|
|
424
|
+
<div class="flex flex-col gap-2">
|
|
425
|
+
@for (file of files; track file.name) {
|
|
426
|
+
<div class="flex items-center gap-3 p-3 border border-surface rounded-lg bg-surface-ground">
|
|
427
|
+
<i class="pi pi-file text-xl sm:text-2xl text-primary"></i>
|
|
428
|
+
<div class="flex-1 min-w-0">
|
|
429
|
+
<div class="font-medium truncate">{{ file.name }}</div>
|
|
430
|
+
<div class="text-sm text-muted-color">{{ formatFileSize(file.size) }}</div>
|
|
431
|
+
</div>
|
|
432
|
+
<p-button icon="pi pi-times" [text]="true" severity="danger" size="small" (onClick)="fileUpload.clear()" />
|
|
433
|
+
</div>
|
|
434
|
+
}
|
|
435
|
+
</div>
|
|
436
|
+
} @else {
|
|
437
|
+
<div class="flex items-center justify-center flex-col p-6 sm:p-8 border-2 border-dashed border-surface rounded-lg">
|
|
438
|
+
<i class="pi pi-cloud-upload text-3xl sm:text-4xl text-muted-color mb-3"></i>
|
|
439
|
+
<p class="text-muted-color text-center text-sm sm:text-base">Drag and drop file here or click to browse</p>
|
|
440
|
+
<p class="text-xs sm:text-sm text-muted-color mt-2">Maximum file size: 50MB</p>
|
|
441
|
+
</div>
|
|
442
|
+
}
|
|
443
|
+
</ng-template>
|
|
444
|
+
</p-fileupload>
|
|
445
|
+
@if (!selectedFile() && !isEditMode()) {
|
|
446
|
+
<small class="text-muted-color">Please select a file to upload</small>
|
|
447
|
+
}
|
|
448
|
+
</div>
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
|
452
|
+
<div class="field md:col-span-2">
|
|
453
|
+
<label for="name" class="block font-medium mb-2 text-sm sm:text-base">File Name *</label>
|
|
454
|
+
<input pInputText id="name" [formField]="fileForm.name" [placeholder]="isEditMode() ? 'Enter file name' : 'Will be auto-filled from selected file'" class="w-full" />
|
|
455
|
+
</div>
|
|
456
|
+
|
|
457
|
+
<div class="field">
|
|
458
|
+
<label class="p-checkbox-native flex items-center gap-2">
|
|
459
|
+
<input type="checkbox" [formField]="fileForm.isPrivate" />
|
|
460
|
+
<span>Private File</span>
|
|
461
|
+
</label>
|
|
462
|
+
<small class="text-muted-color block mt-2">
|
|
463
|
+
{{ formModel().isPrivate ? 'Only users with permission can access' : 'File is publicly accessible' }}
|
|
464
|
+
</small>
|
|
465
|
+
</div>
|
|
466
|
+
</div>
|
|
467
|
+
|
|
468
|
+
<div class="flex justify-end gap-2 mt-4">
|
|
469
|
+
<p-button label="Cancel" severity="secondary" [outlined]="true" (onClick)="onCancel()" />
|
|
470
|
+
<p-button
|
|
471
|
+
type="submit"
|
|
472
|
+
[label]="isEditMode() ? 'Update' : 'Upload'"
|
|
473
|
+
[loading]="isLoading()"
|
|
474
|
+
[disabled]="!isFormValid() || (!isEditMode() && !selectedFile()) || isLoading()"
|
|
475
|
+
/>
|
|
476
|
+
</div>
|
|
477
|
+
</form>
|
|
478
|
+
</div>
|
|
479
|
+
`,
|
|
480
|
+
}]
|
|
481
|
+
}], ctorParameters: () => [] });
|
|
482
|
+
|
|
483
|
+
export { FileManagerFormComponent };
|
|
484
|
+
//# sourceMappingURL=flusys-ng-storage-file-manager-form.component-Bv8ER8J0.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"flusys-ng-storage-file-manager-form.component-Bv8ER8J0.mjs","sources":["../../../projects/ng-storage/pages/file-manager/file-manager-form.component.ts"],"sourcesContent":["import { ChangeDetectionStrategy, Component, computed, effect, inject, signal } from '@angular/core';\nimport { toSignal } from '@angular/core/rxjs-interop';\nimport { form, FormField, required } from '@angular/forms/signals';\nimport { ActivatedRoute } from '@angular/router';\nimport { AngularModule, PrimeModule } from '@flusys/ng-shared';\nimport { firstValueFrom } from 'rxjs';\nimport { IFileManager } from '../../interfaces/file-manager.interface';\nimport { IFolder } from '../../interfaces/folder.interface';\nimport { IStorageConfig } from '../../interfaces/storage-config.interface';\nimport { FileManagerApiService } from '../../services/file-manager-api.service';\nimport { FolderApiService } from '../../services/folder-api.service';\nimport { StorageConfigApiService } from '../../services/storage-config-api.service';\nimport { UploadService } from '../../services/upload.service';\nimport { BaseStoragePage } from '../../utils/base-storage-page';\nimport { formatFileSize, getProviderLabel } from '../../utils/storage-utils';\n\n/** File manager form model interface */\ninterface IFileManagerFormModel {\n id: string;\n name: string;\n isPrivate: boolean;\n folderId: string;\n storageConfigId: string;\n}\n\n/**\n * File Manager Form Component\n * Upload and edit file metadata\n */\n@Component({\n selector: 'lib-file-manager-form',\n changeDetection: ChangeDetectionStrategy.OnPush,\n imports: [AngularModule, PrimeModule, FormField],\n template: `\n <div class=\"card\">\n <div class=\"mb-4\">\n <h3 class=\"text-lg sm:text-xl font-semibold\">{{ isEditMode() ? 'Edit File' : 'Upload File' }}</h3>\n @if (showCompanyInfo()) {\n <p class=\"text-sm text-muted-color mt-1\">Company: {{ currentCompanyName() }}</p>\n }\n </div>\n\n <form (ngSubmit)=\"onSubmit()\">\n @if (!isEditMode()) {\n <div class=\"grid grid-cols-1 md:grid-cols-2 gap-4 mb-4\">\n <div class=\"field\">\n <label for=\"storageConfig\" class=\"block font-medium mb-2 text-sm sm:text-base\">Storage Configuration *</label>\n <select id=\"storageConfig\" class=\"p-select-native w-full\" [formField]=\"fileForm.storageConfigId\">\n <option value=\"\">Select storage configuration...</option>\n @for (config of availableConfigs(); track config.id) {\n <option [value]=\"config.id\">{{ config.name }} ({{ getProviderLabel(config.storage) }})</option>\n }\n </select>\n @if (availableConfigs().length === 0 && !isLoadingConfigs()) {\n <small class=\"text-warn block mt-2\">\n <i class=\"pi pi-exclamation-triangle mr-1\"></i>\n No storage configurations found. Please create one first.\n </small>\n }\n </div>\n\n <div class=\"field\">\n <label for=\"folder\" class=\"block font-medium mb-2 text-sm sm:text-base\">Folder (Optional)</label>\n <select id=\"folder\" class=\"p-select-native w-full\" [formField]=\"fileForm.folderId\">\n <option value=\"\">No folder (root level)</option>\n @for (folder of availableFolders(); track folder.id) {\n <option [value]=\"folder.id\">{{ folder.name }}</option>\n }\n </select>\n @if (availableFolders().length === 0 && !isLoadingFolders()) {\n <small class=\"text-muted-color block mt-2\">\n <i class=\"pi pi-info-circle mr-1\"></i>\n No folders available. File will be uploaded to root level.\n </small>\n }\n </div>\n </div>\n } @else {\n <div class=\"field mb-4\">\n <label for=\"folder\" class=\"block font-medium mb-2 text-sm sm:text-base\">Folder (Optional)</label>\n <select id=\"folder\" class=\"p-select-native w-full\" [formField]=\"fileForm.folderId\">\n <option value=\"\">No folder (root level)</option>\n @for (folder of availableFolders(); track folder.id) {\n <option [value]=\"folder.id\">{{ folder.name }}</option>\n }\n </select>\n @if (availableFolders().length === 0 && !isLoadingFolders()) {\n <small class=\"text-muted-color block mt-2\">\n <i class=\"pi pi-info-circle mr-1\"></i>\n No folders available.\n </small>\n }\n </div>\n }\n\n @if (!isEditMode()) {\n <div class=\"field mb-4\">\n <label class=\"block font-medium mb-2 text-sm sm:text-base\">Select File *</label>\n <p-fileupload\n #fileUpload\n [customUpload]=\"true\"\n (onSelect)=\"onFileSelect($event)\"\n [multiple]=\"false\"\n [maxFileSize]=\"50000000\"\n [showUploadButton]=\"false\"\n [showCancelButton]=\"false\"\n chooseLabel=\"Choose File\"\n chooseIcon=\"pi pi-upload\"\n accept=\"*/*\"\n class=\"w-full\"\n >\n <ng-template #content let-files>\n @if (files && files.length > 0) {\n <div class=\"flex flex-col gap-2\">\n @for (file of files; track file.name) {\n <div class=\"flex items-center gap-3 p-3 border border-surface rounded-lg bg-surface-ground\">\n <i class=\"pi pi-file text-xl sm:text-2xl text-primary\"></i>\n <div class=\"flex-1 min-w-0\">\n <div class=\"font-medium truncate\">{{ file.name }}</div>\n <div class=\"text-sm text-muted-color\">{{ formatFileSize(file.size) }}</div>\n </div>\n <p-button icon=\"pi pi-times\" [text]=\"true\" severity=\"danger\" size=\"small\" (onClick)=\"fileUpload.clear()\" />\n </div>\n }\n </div>\n } @else {\n <div class=\"flex items-center justify-center flex-col p-6 sm:p-8 border-2 border-dashed border-surface rounded-lg\">\n <i class=\"pi pi-cloud-upload text-3xl sm:text-4xl text-muted-color mb-3\"></i>\n <p class=\"text-muted-color text-center text-sm sm:text-base\">Drag and drop file here or click to browse</p>\n <p class=\"text-xs sm:text-sm text-muted-color mt-2\">Maximum file size: 50MB</p>\n </div>\n }\n </ng-template>\n </p-fileupload>\n @if (!selectedFile() && !isEditMode()) {\n <small class=\"text-muted-color\">Please select a file to upload</small>\n }\n </div>\n }\n\n <div class=\"grid grid-cols-1 md:grid-cols-2 gap-4\">\n <div class=\"field md:col-span-2\">\n <label for=\"name\" class=\"block font-medium mb-2 text-sm sm:text-base\">File Name *</label>\n <input pInputText id=\"name\" [formField]=\"fileForm.name\" [placeholder]=\"isEditMode() ? 'Enter file name' : 'Will be auto-filled from selected file'\" class=\"w-full\" />\n </div>\n\n <div class=\"field\">\n <label class=\"p-checkbox-native flex items-center gap-2\">\n <input type=\"checkbox\" [formField]=\"fileForm.isPrivate\" />\n <span>Private File</span>\n </label>\n <small class=\"text-muted-color block mt-2\">\n {{ formModel().isPrivate ? 'Only users with permission can access' : 'File is publicly accessible' }}\n </small>\n </div>\n </div>\n\n <div class=\"flex justify-end gap-2 mt-4\">\n <p-button label=\"Cancel\" severity=\"secondary\" [outlined]=\"true\" (onClick)=\"onCancel()\" />\n <p-button\n type=\"submit\"\n [label]=\"isEditMode() ? 'Update' : 'Upload'\"\n [loading]=\"isLoading()\"\n [disabled]=\"!isFormValid() || (!isEditMode() && !selectedFile()) || isLoading()\"\n />\n </div>\n </form>\n </div>\n `,\n})\nexport class FileManagerFormComponent extends BaseStoragePage {\n private readonly route = inject(ActivatedRoute);\n private readonly fileService = inject(FileManagerApiService);\n private readonly uploadService = inject(UploadService);\n private readonly configService = inject(StorageConfigApiService);\n private readonly folderService = inject(FolderApiService);\n\n private readonly routeParams = toSignal(this.route.paramMap);\n private initialized = false;\n\n readonly isLoadingConfigs = signal(false);\n readonly isLoadingFolders = signal(false);\n readonly existingFile = signal<IFileManager | null>(null);\n readonly isEditMode = computed(() => !!this.existingFile());\n readonly selectedFile = signal<File | null>(null);\n readonly availableConfigs = signal<IStorageConfig[]>([]);\n readonly availableFolders = signal<IFolder[]>([]);\n\n /** Utility functions exposed to template */\n readonly formatFileSize = formatFileSize;\n readonly getProviderLabel = getProviderLabel;\n\n /** Form model */\n readonly formModel = signal<IFileManagerFormModel>({\n id: '',\n name: '',\n isPrivate: false,\n folderId: '',\n storageConfigId: '',\n });\n\n /** Form with validation */\n readonly fileForm = form(this.formModel, (f) => {\n required(f.name, { message: 'File name is required' });\n });\n\n /** Check if form is valid */\n readonly isFormValid = computed(() => this.formModel().name.trim().length > 0);\n\n constructor() {\n super();\n effect(() => {\n const params = this.routeParams();\n if (!params || this.initialized) return;\n\n this.initialized = true;\n const fileId = params.get('id');\n if (fileId) {\n this.loadFile(fileId);\n } else {\n this.loadStorageConfigs();\n }\n this.loadFolders();\n });\n }\n\n async loadStorageConfigs(): Promise<void> {\n this.isLoadingConfigs.set(true);\n try {\n const response = await firstValueFrom(\n this.configService.getAll('', {\n pagination: { currentPage: 0, pageSize: 100 },\n filter: {},\n select: [],\n sort: {},\n }),\n );\n if (response.success && response.data) {\n this.availableConfigs.set(response.data);\n if (response.data.length === 1) {\n this.formModel.update((m) => ({ ...m, storageConfigId: response.data![0].id }));\n }\n }\n } catch {\n this.showError('Failed to load storage configurations.');\n } finally {\n this.isLoadingConfigs.set(false);\n }\n }\n\n async loadFolders(): Promise<void> {\n this.isLoadingFolders.set(true);\n try {\n const response = await firstValueFrom(\n this.folderService.getAll('', {\n pagination: { currentPage: 0, pageSize: 100 },\n filter: {},\n select: [],\n sort: {},\n }),\n );\n if (response.success && response.data) {\n this.availableFolders.set(response.data);\n }\n } catch {\n // Silently fail - folders are optional\n } finally {\n this.isLoadingFolders.set(false);\n }\n }\n\n async loadFile(id: string): Promise<void> {\n this.isLoading.set(true);\n try {\n const response = await this.fileService.findByIdAsync(id);\n if (response.success && response.data) {\n const file = response.data;\n this.existingFile.set(file);\n this.formModel.set({\n id: file.id,\n name: file.name,\n isPrivate: file.isPrivate ?? false,\n folderId: file.folderId ?? '',\n storageConfigId: '',\n });\n }\n } catch {\n this.showError('Failed to load file.');\n this.navigateTo(['/storage/files']);\n } finally {\n this.isLoading.set(false);\n }\n }\n\n onFileSelect(event: { files?: File[]; currentFiles?: File[] }): void {\n const file = event.files?.[0] || event.currentFiles?.[0];\n if (file) {\n this.selectedFile.set(file);\n this.formModel.update((m) => ({ ...m, name: file.name }));\n }\n }\n\n async onSubmit(): Promise<void> {\n if (!this.isFormValid()) return;\n\n this.isLoading.set(true);\n try {\n const formValue = this.formModel();\n\n if (this.isEditMode()) {\n await this.fileService.updateAsync({\n id: formValue.id,\n name: formValue.name,\n isPrivate: formValue.isPrivate,\n folderId: formValue.folderId || null,\n });\n this.showSuccess('File updated successfully.');\n } else {\n const file = this.selectedFile();\n if (!file) {\n this.showError('Please select a file to upload.');\n return;\n }\n\n if (!formValue.storageConfigId) {\n this.showError('Please select a storage configuration.');\n return;\n }\n\n const uploadResponse = await firstValueFrom(\n this.uploadService.uploadSingleFile(file, {\n compress: true,\n quality: 85,\n storageConfigId: formValue.storageConfigId,\n }),\n );\n\n if (uploadResponse.success && uploadResponse.data) {\n const uploadedFileInfo = uploadResponse.data;\n await this.fileService.createFileAsync({\n name: formValue.name,\n key: uploadedFileInfo.key,\n contentType: file.type || 'application/octet-stream',\n size: Math.round(file.size / 1024).toString(),\n isPrivate: formValue.isPrivate,\n folderId: formValue.folderId || undefined,\n storageConfigId: formValue.storageConfigId,\n });\n this.showSuccess('File uploaded successfully.');\n } else {\n throw new Error('Upload failed');\n }\n }\n\n this.navigateTo(['/storage/files']);\n } catch (error: any) {\n this.showError(error?.message || `Failed to ${this.isEditMode() ? 'update' : 'upload'} file.`);\n } finally {\n this.isLoading.set(false);\n }\n }\n\n onCancel(): void {\n this.navigateTo(['/storage/files']);\n }\n}\n"],"names":["i2","i4"],"mappings":"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"}
|