🐛 Fix file upload in site

This commit is contained in:
2025-11-21 00:24:35 +08:00
parent 7b85533184
commit fc65440420

View File

@@ -126,6 +126,8 @@ class FileUploadDialog extends HookConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final formKey = useMemoized(() => GlobalKey<FormState>());
final pathController = useTextEditingController(text: '/');
final isUploading = useState(false);
final progressStates = useState<List<Map<String, dynamic>>>(
selectedFiles
@@ -142,7 +144,7 @@ class FileUploadDialog extends HookConsumerWidget {
);
final uploadFile = useCallback((
String filePath,
String basePath,
File file,
int index,
) async {
@@ -154,7 +156,13 @@ class FileUploadDialog extends HookConsumerWidget {
siteFilesNotifierProvider((siteId: site.id, path: null)).notifier,
);
await siteFilesNotifier.uploadFile(file, filePath);
final fileName = file.path.split('/').last;
final uploadPath =
basePath.endsWith('/')
? '$basePath$fileName'
: '$basePath/$fileName';
await siteFilesNotifier.uploadFile(file, uploadPath);
progressStates.value[index]['status'] = 'completed';
progressStates.value[index]['progress'] = 1.0;
@@ -168,6 +176,8 @@ class FileUploadDialog extends HookConsumerWidget {
final uploadAllFiles = useCallback(
() async {
if (!formKey.currentState!.validate()) return;
isUploading.value = true;
// Reset all progress states
@@ -181,9 +191,7 @@ class FileUploadDialog extends HookConsumerWidget {
// Upload files sequentially (could be made parallel if needed)
for (int i = 0; i < selectedFiles.length; i++) {
final file = selectedFiles[i];
// For now, upload to root. In a real implementation, you'd get this from a form field
final uploadPath = '/${file.path.split('/').last}';
await uploadFile(uploadPath, file, i);
await uploadFile(pathController.text, file, i);
}
isUploading.value = false;
@@ -208,6 +216,8 @@ class FileUploadDialog extends HookConsumerWidget {
selectedFiles,
onUploadComplete,
context,
formKey,
pathController,
],
);
@@ -215,9 +225,40 @@ class FileUploadDialog extends HookConsumerWidget {
titleText: 'Upload Files',
child: SingleChildScrollView(
padding: const EdgeInsets.all(20),
child: Form(
key: formKey,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Upload path field
TextFormField(
controller: pathController,
decoration: const InputDecoration(
labelText: 'Upload Path',
hintText: '/ (root) or /assets/images/',
border: OutlineInputBorder(
borderRadius: BorderRadius.all(Radius.circular(12)),
),
),
validator: (value) {
if (value == null || value.isEmpty) {
return 'Please enter an upload path';
}
if (!value.startsWith('/') && value != '/') {
return 'Path must start with /';
}
if (value.contains(' ')) {
return 'Path cannot contain spaces';
}
if (value.contains('//')) {
return 'Path cannot have consecutive slashes';
}
return null;
},
onTapOutside:
(_) => FocusManager.instance.primaryFocus?.unfocus(),
),
const Gap(20),
Text(
'Ready to upload ${selectedFiles.length} file${selectedFiles.length == 1 ? '' : 's'}:',
style: Theme.of(context).textTheme.titleMedium,
@@ -323,6 +364,22 @@ class FileUploadDialog extends HookConsumerWidget {
],
),
],
// Show the final upload path when not uploading
if (!isUploading.value &&
progressState['status'] != 'uploading') ...[
const Gap(8),
Text(
'Will upload to: ${pathController.text.endsWith('/') ? pathController.text : '${pathController.text}/'}$fileName',
style: Theme.of(
context,
).textTheme.bodySmall?.copyWith(
color:
Theme.of(
context,
).colorScheme.onSurfaceVariant,
),
),
],
],
),
),
@@ -356,6 +413,7 @@ class FileUploadDialog extends HookConsumerWidget {
],
),
),
),
);
}
}
@@ -1133,7 +1191,7 @@ class _FileManagementSection extends HookConsumerWidget {
return ListView.builder(
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
padding: EdgeInsets.zero,
itemCount: files.length,
itemBuilder: (context, index) {
final file = files[index];
@@ -1177,8 +1235,12 @@ class _FileItem extends HookConsumerWidget {
final theme = Theme.of(context);
return Card(
margin: const EdgeInsets.only(bottom: 8),
color: Theme.of(context).colorScheme.surfaceContainerHigh,
elevation: 0,
child: ListTile(
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(8)),
),
leading: Icon(
file.isDirectory ? Symbols.folder : Symbols.description,
color: theme.colorScheme.primary,