Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion lib/consts.dart
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,8 @@ enum ImportFormat {
curl("cURL"),
postman("Postman Collection v2.1"),
insomnia("Insomnia v4"),
har("Har v1.2");
har("Har v1.2"),
hurl("Hurl");

const ImportFormat(this.label);
final String label;
Expand Down
47 changes: 41 additions & 6 deletions lib/importer/import_dialog.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:apidash/providers/providers.dart';
import 'package:apidash/widgets/widgets.dart';
import 'package:apidash/consts.dart';
import 'package:apidash_core/apidash_core.dart';
import 'importer.dart';

void importToCollectionPane(
Expand All @@ -28,27 +30,60 @@ void importToCollectionPane(
(content) {
kImporter
.getHttpRequestModelList(importFormatType, content)
.then((importedRequestModels) {
.then((importedRequestModels) async {
if (importedRequestModels != null) {
if (importedRequestModels.isEmpty) {
sm.showSnackBar(
getSnackBar("No requests imported", small: false));
if (!context.mounted) return;
Navigator.of(context).pop();
} else {
for (var model in importedRequestModels.reversed) {
// Determine if we should show selection dialog
// Currently only Hurl format supports selective import
final shouldShowSelectionDialog =
importFormatType == ImportFormat.hurl;

List<(String?, HttpRequestModel)> requestsToImport;

if (shouldShowSelectionDialog && context.mounted) {
// Show selection dialog for Hurl files
final selectedRequests = await showRequestSelectionDialog(
context: context,
requests: importedRequestModels,
);

// User cancelled or didn't select anything
if (selectedRequests == null || selectedRequests.isEmpty) {
if (!context.mounted) return;
Navigator.of(context).pop();
return;
}

requestsToImport = selectedRequests;
} else {
// Import all requests directly for other formats
requestsToImport = importedRequestModels;
}

// Import the requests
for (var model in requestsToImport.reversed) {
ref
.read(collectionStateNotifierProvider.notifier)
.addRequestModel(
model.$2,
name: model.$1,
);
}

// Show success message
sm.showSnackBar(getSnackBar(
"Successfully imported ${importedRequestModels.length} requests",
"Successfully imported ${requestsToImport.length} request${requestsToImport.length == 1 ? '' : 's'}",
small: false));

// Close the import dialog
if (!context.mounted) return;
Navigator.of(context).pop();
}
// Solves - Do not use BuildContexts across async gaps
if (!context.mounted) return;
Navigator.of(context).pop();
} else {
var err = "Unable to parse ${file.name}";
sm.showSnackBar(getSnackBar(err, small: false));
Expand Down
1 change: 1 addition & 0 deletions lib/importer/importer.dart
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ class Importer {
ImportFormat.postman => PostmanIO().getHttpRequestModelList(content),
ImportFormat.insomnia => InsomniaIO().getHttpRequestModelList(content),
ImportFormat.har => HarParserIO().getHttpRequestModelList(content),
ImportFormat.hurl => HurlIO().getHttpRequestModelList(content),
};
}
}
Expand Down
3 changes: 3 additions & 0 deletions lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ void main() async {
WidgetsFlutterBinding.ensureInitialized();
await Stac.initialize();

// Initialize Rust library for Hurl parser
await RustLib.init();

//Load all LLMs
// await LLMManager.fetchAvailableLLMs();
await ModelManager.fetchAvailableModels();
Expand Down
133 changes: 133 additions & 0 deletions lib/widgets/dialog_request_selection.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
import 'package:apidash_core/apidash_core.dart';
import 'package:apidash_design_system/apidash_design_system.dart';
import 'package:flutter/material.dart';

/// Shows a dialog to let user select which requests to import
Future<List<(String?, HttpRequestModel)>?> showRequestSelectionDialog({
required BuildContext context,
required List<(String?, HttpRequestModel)> requests,
}) async {
if (requests.isEmpty) {
return [];
}

// Track which requests are selected (all selected by default)
final selectedIndices = List.generate(requests.length, (index) => index).toSet();

return showDialog<List<(String?, HttpRequestModel)>?>(
context: context,
builder: (context) {
return StatefulBuilder(
builder: (context, setState) {
final selectedCount = selectedIndices.length;
final totalCount = requests.length;

return AlertDialog(
title: const Text('Select Requests to Import'),
content: SizedBox(
width: 600,
height: 400,
child: Column(
children: [
// Select All / Deselect All row
Row(
children: [
Checkbox(
value: selectedCount == totalCount,
tristate: true,
onChanged: (value) {
setState(() {
if (selectedCount == totalCount) {
selectedIndices.clear();
} else {
selectedIndices.addAll(
List.generate(requests.length, (i) => i),
);
}
});
},
),
Text(
'Select All ($selectedCount of $totalCount)',
style: kTextStyleButton,
),
],
),
kVSpacer8,
const Divider(),
kVSpacer8,
// List of requests
Expanded(
child: ListView.builder(
itemCount: requests.length,
itemBuilder: (context, index) {
final (name, request) = requests[index];
final isSelected = selectedIndices.contains(index);
final displayName = name ?? request.url;

return CheckboxListTile(
value: isSelected,
onChanged: (value) {
setState(() {
if (value == true) {
selectedIndices.add(index);
} else {
selectedIndices.remove(index);
}
});
},
title: Text(
displayName,
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
subtitle: Text(
request.method.name.toUpperCase(),
style: kCodeStyle.copyWith(fontSize: 12),
),
secondary: Container(
padding: kP8,
decoration: BoxDecoration(
color: kColorStatusCodeDefault.withValues(alpha: 0.1),
borderRadius: kBorderRadius8,
),
child: Text(
request.method.name.toUpperCase(),
style: kCodeStyle.copyWith(
fontSize: 11,
fontWeight: FontWeight.bold,
),
),
),
);
},
),
),
],
),
),
actions: [
TextButton(
onPressed: () {
Navigator.of(context).pop(null);
},
child: const Text('Cancel'),
),
FilledButton(
onPressed: selectedCount == 0
? null
: () {
final selectedRequests = selectedIndices
.map((index) => requests[index])
.toList();
Navigator.of(context).pop(selectedRequests);
},
child: Text('Import $selectedCount Request${selectedCount == 1 ? '' : 's'}'),
),
],
);
},
);
},
);
}
1 change: 1 addition & 0 deletions lib/widgets/widgets.dart
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ export 'dialog_history_retention.dart';
export 'dialog_import.dart';
export 'dialog_ok_cancel.dart';
export 'dialog_rename.dart';
export 'dialog_request_selection.dart';
export 'dialog_text.dart';
export 'drag_and_drop_area.dart';
export 'dropdown_codegen.dart';
Expand Down
1 change: 1 addition & 0 deletions packages/apidash_core/lib/apidash_core.dart
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,6 @@ export 'utils/utils.dart';
export 'package:freezed_annotation/freezed_annotation.dart';
export 'package:genai/genai.dart';
export 'package:curl_parser/curl_parser.dart' show Curl;
export 'package:hurl/hurl.dart' show RustLib;
export 'package:openapi_spec/openapi_spec.dart'
show OpenApi, Operation, ParameterHeader;
Loading