mirror of
https://github.com/foss42/apidash.git
synced 2025-06-03 16:27:06 +08:00
Merge branch 'main' of https://github.com/foss42/apidash into add-go-http-codegen
This commit is contained in:
@ -53,7 +53,9 @@ class DartHttpCodeGen {
|
||||
declareVar('uri').assign(refer('Uri.parse').call([literalString(url)]));
|
||||
|
||||
Expression? dataExp;
|
||||
if (kMethodsWithBody.contains(method) && (body?.isNotEmpty ?? false)) {
|
||||
if (kMethodsWithBody.contains(method) &&
|
||||
(body?.isNotEmpty ?? false) &&
|
||||
contentType != ContentType.formdata) {
|
||||
final strContent = CodeExpression(Code('r\'\'\'$body\'\'\''));
|
||||
dataExp = declareVar('body', type: refer('String')).assign(strContent);
|
||||
if (!hasContentTypeHeader) {
|
||||
|
@ -7,7 +7,7 @@ import 'package:apidash/consts.dart';
|
||||
|
||||
class KotlinOkHttpCodeGen {
|
||||
final String kTemplateStart = """import okhttp3.OkHttpClient
|
||||
import okhttp3.Request{{importForQuery}}{{importForBody}}
|
||||
import okhttp3.Request{{importForQuery}}{{importForBody}}{{importForFormData}}
|
||||
|
||||
fun main() {
|
||||
val client = OkHttpClient()
|
||||
@ -23,6 +23,10 @@ import okhttp3.HttpUrl.Companion.toHttpUrl""";
|
||||
import okhttp3.RequestBody.Companion.toRequestBody
|
||||
import okhttp3.MediaType.Companion.toMediaType""";
|
||||
|
||||
final String kStringImportForFormData = """
|
||||
|
||||
import okhttp3.MultipartBody""";
|
||||
|
||||
final String kTemplateUrl = '''
|
||||
|
||||
val url = "{{url}}"
|
||||
@ -77,6 +81,7 @@ import okhttp3.MediaType.Companion.toMediaType""";
|
||||
String result = "";
|
||||
bool hasQuery = false;
|
||||
bool hasBody = false;
|
||||
bool hasFormData = false;
|
||||
|
||||
String url = requestModel.url;
|
||||
if (!url.contains("://") && url.isNotEmpty) {
|
||||
@ -109,6 +114,7 @@ import okhttp3.MediaType.Companion.toMediaType""";
|
||||
var method = requestModel.method;
|
||||
var requestBody = requestModel.requestBody;
|
||||
if (requestModel.isFormDataRequest) {
|
||||
hasFormData = true;
|
||||
var formDataTemplate = jj.Template(kFormDataBody);
|
||||
|
||||
result += formDataTemplate.render({
|
||||
@ -128,7 +134,8 @@ import okhttp3.MediaType.Companion.toMediaType""";
|
||||
var templateStart = jj.Template(kTemplateStart);
|
||||
var stringStart = templateStart.render({
|
||||
"importForQuery": hasQuery ? kStringImportForQuery : "",
|
||||
"importForBody": hasBody ? kStringImportForBody : ""
|
||||
"importForBody": hasBody ? kStringImportForBody : "",
|
||||
"importForFormData": hasFormData ? kStringImportForFormData : ""
|
||||
});
|
||||
|
||||
result = stringStart + result;
|
||||
|
@ -130,7 +130,9 @@ body = b'\r\n'.join(dataList)
|
||||
|
||||
var method = requestModel.method;
|
||||
var requestBody = requestModel.requestBody;
|
||||
if (kMethodsWithBody.contains(method) && requestBody != null) {
|
||||
if (kMethodsWithBody.contains(method) &&
|
||||
requestBody != null &&
|
||||
!requestModel.isFormDataRequest) {
|
||||
var contentLength = utf8.encode(requestBody).length;
|
||||
if (contentLength > 0) {
|
||||
hasBody = true;
|
||||
|
@ -140,7 +140,7 @@ print('Response Body:', response.text)
|
||||
hasJsonBody = true;
|
||||
var templateBody = jj.Template(kTemplateJson);
|
||||
result += templateBody.render({"body": requestBody});
|
||||
} else {
|
||||
} else if (!requestModel.isFormDataRequest) {
|
||||
hasBody = true;
|
||||
var templateBody = jj.Template(kTemplateBody);
|
||||
result += templateBody.render({"body": requestBody});
|
||||
|
@ -500,7 +500,8 @@ const kRaiseIssue =
|
||||
const kCsvError =
|
||||
"There seems to be an issue rendering this CSV. Please raise an issue in API Dash GitHub repo so that we can resolve it.";
|
||||
|
||||
const kHintTextUrlCard = "Enter API endpoint like api.foss42.com/country/codes";
|
||||
const kHintTextUrlCard =
|
||||
"Enter API endpoint like api.apidash.dev/country/codes";
|
||||
const kLabelPlusNew = "+ New";
|
||||
const kLabelSend = "Send";
|
||||
const kLabelSending = "Sending..";
|
||||
|
@ -29,6 +29,7 @@ class RequestModel {
|
||||
this.responseStatus,
|
||||
this.message,
|
||||
this.responseModel,
|
||||
this.isWorking = false,
|
||||
});
|
||||
|
||||
final String id;
|
||||
@ -47,6 +48,7 @@ class RequestModel {
|
||||
final int? responseStatus;
|
||||
final String? message;
|
||||
final ResponseModel? responseModel;
|
||||
final bool isWorking;
|
||||
|
||||
List<NameValueModel>? get enabledRequestHeaders =>
|
||||
getEnabledRows(requestHeaders, isHeaderEnabledList);
|
||||
@ -106,6 +108,7 @@ class RequestModel {
|
||||
int? responseStatus,
|
||||
String? message,
|
||||
ResponseModel? responseModel,
|
||||
bool? isWorking,
|
||||
}) {
|
||||
var headers = requestHeaders ?? this.requestHeaders;
|
||||
var params = requestParams ?? this.requestParams;
|
||||
@ -129,6 +132,7 @@ class RequestModel {
|
||||
responseStatus: responseStatus ?? this.responseStatus,
|
||||
message: message ?? this.message,
|
||||
responseModel: responseModel ?? this.responseModel,
|
||||
isWorking: isWorking ?? this.isWorking,
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -156,11 +156,20 @@ class CollectionStateNotifier
|
||||
}
|
||||
|
||||
Future<void> sendRequest(String id) async {
|
||||
ref.read(sentRequestIdStateProvider.notifier).state = id;
|
||||
ref.read(codePaneVisibleStateProvider.notifier).state = false;
|
||||
final defaultUriScheme =
|
||||
ref.read(settingsProvider.select((value) => value.defaultUriScheme));
|
||||
final defaultUriScheme = ref.read(
|
||||
settingsProvider.select(
|
||||
(value) => value.defaultUriScheme,
|
||||
),
|
||||
);
|
||||
|
||||
RequestModel requestModel = state![id]!;
|
||||
|
||||
// set current model's isWorking to true and update state
|
||||
var map = {...state!};
|
||||
map[id] = requestModel.copyWith(isWorking: true);
|
||||
state = map;
|
||||
|
||||
(http.Response?, Duration?, String?)? responseRec = await request(
|
||||
requestModel,
|
||||
defaultUriScheme: defaultUriScheme,
|
||||
@ -172,6 +181,7 @@ class CollectionStateNotifier
|
||||
newRequestModel = requestModel.copyWith(
|
||||
responseStatus: -1,
|
||||
message: responseRec.$3,
|
||||
isWorking: false,
|
||||
);
|
||||
} else {
|
||||
final responseModel = baseResponseModel.fromResponse(
|
||||
@ -183,10 +193,12 @@ class CollectionStateNotifier
|
||||
responseStatus: statusCode,
|
||||
message: kResponseCodeReasons[statusCode],
|
||||
responseModel: responseModel,
|
||||
isWorking: false,
|
||||
);
|
||||
}
|
||||
ref.read(sentRequestIdStateProvider.notifier).state = null;
|
||||
var map = {...state!};
|
||||
|
||||
// update state with response data
|
||||
map = {...state!};
|
||||
map[id] = newRequestModel;
|
||||
state = map;
|
||||
}
|
||||
|
@ -3,7 +3,6 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
|
||||
final navRailIndexStateProvider = StateProvider<int>((ref) => 0);
|
||||
final selectedIdEditStateProvider = StateProvider<String?>((ref) => null);
|
||||
final sentRequestIdStateProvider = StateProvider<String?>((ref) => null);
|
||||
final codePaneVisibleStateProvider = StateProvider<bool>((ref) => false);
|
||||
final saveDataStateProvider = StateProvider<bool>((ref) => false);
|
||||
final clearDataStateProvider = StateProvider<bool>((ref) => false);
|
||||
|
@ -9,13 +9,14 @@ class ResponsePane extends ConsumerWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final selectedId = ref.watch(selectedIdStateProvider);
|
||||
final sentRequestId = ref.watch(sentRequestIdStateProvider);
|
||||
final isWorking = ref.watch(
|
||||
selectedRequestModelProvider.select((value) => value?.isWorking)) ??
|
||||
false;
|
||||
final responseStatus = ref.watch(
|
||||
selectedRequestModelProvider.select((value) => value?.responseStatus));
|
||||
final message = ref
|
||||
.watch(selectedRequestModelProvider.select((value) => value?.message));
|
||||
if (sentRequestId != null && sentRequestId == selectedId) {
|
||||
if (isWorking) {
|
||||
return const SendingWidget();
|
||||
}
|
||||
if (responseStatus == null) {
|
||||
|
@ -1,4 +1,6 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:apidash/providers/collection_providers.dart';
|
||||
import 'package:apidash/consts.dart';
|
||||
import 'details_card/details_card.dart';
|
||||
import 'url_card.dart';
|
||||
@ -10,6 +12,7 @@ class RequestEditor extends StatelessWidget {
|
||||
Widget build(BuildContext context) {
|
||||
return const Column(
|
||||
children: [
|
||||
RequestEditorTopBar(),
|
||||
EditorPaneRequestURLCard(),
|
||||
kVSpacer10,
|
||||
Expanded(
|
||||
@ -19,3 +22,91 @@ class RequestEditor extends StatelessWidget {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class RequestEditorTopBar extends ConsumerWidget {
|
||||
const RequestEditorTopBar({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final id = ref.watch(selectedIdStateProvider);
|
||||
final name =
|
||||
ref.watch(selectedRequestModelProvider.select((value) => value?.name));
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
left: 12.0,
|
||||
top: 4.0,
|
||||
right: 8.0,
|
||||
bottom: 4.0,
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Text(
|
||||
name ?? "",
|
||||
style: Theme.of(context).textTheme.bodyMedium,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
maxLines: 1,
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
width: 6,
|
||||
),
|
||||
SizedBox(
|
||||
width: 90,
|
||||
height: 24,
|
||||
child: FilledButton.tonalIcon(
|
||||
style: const ButtonStyle(
|
||||
padding: MaterialStatePropertyAll(EdgeInsets.zero),
|
||||
),
|
||||
onPressed: () {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) {
|
||||
final controller =
|
||||
TextEditingController(text: name ?? "");
|
||||
controller.selection = TextSelection(
|
||||
baseOffset: 0, extentOffset: controller.text.length);
|
||||
return AlertDialog(
|
||||
title: const Text('Rename Request'),
|
||||
content: TextField(
|
||||
autofocus: true,
|
||||
controller: controller,
|
||||
decoration:
|
||||
const InputDecoration(hintText: "Enter new name"),
|
||||
),
|
||||
actions: <Widget>[
|
||||
OutlinedButton(
|
||||
onPressed: () {
|
||||
Navigator.pop(context);
|
||||
},
|
||||
child: const Text('CANCEL')),
|
||||
FilledButton(
|
||||
onPressed: () {
|
||||
final val = controller.text.trim();
|
||||
ref
|
||||
.read(collectionStateNotifierProvider
|
||||
.notifier)
|
||||
.update(id!, name: val);
|
||||
Navigator.pop(context);
|
||||
controller.dispose();
|
||||
},
|
||||
child: const Text('OK')),
|
||||
],
|
||||
);
|
||||
});
|
||||
},
|
||||
icon: const Icon(
|
||||
Icons.edit,
|
||||
size: 12,
|
||||
),
|
||||
label: Text(
|
||||
"Rename",
|
||||
style: Theme.of(context).textTheme.bodySmall,
|
||||
),
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -81,6 +81,11 @@ class URLTextField extends ConsumerWidget {
|
||||
.read(collectionStateNotifierProvider.notifier)
|
||||
.update(selectedId, url: value);
|
||||
},
|
||||
onFieldSubmitted: (value) {
|
||||
ref
|
||||
.read(collectionStateNotifierProvider.notifier)
|
||||
.sendRequest(selectedId);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -93,10 +98,11 @@ class SendButton extends ConsumerWidget {
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final selectedId = ref.watch(selectedIdStateProvider);
|
||||
final sentRequestId = ref.watch(sentRequestIdStateProvider);
|
||||
final isWorking = ref.watch(
|
||||
selectedRequestModelProvider.select((value) => value?.isWorking));
|
||||
|
||||
return SendRequestButton(
|
||||
selectedId: selectedId,
|
||||
sentRequestId: sentRequestId,
|
||||
isWorking: isWorking ?? false,
|
||||
onTap: () {
|
||||
ref
|
||||
.read(collectionStateNotifierProvider.notifier)
|
||||
|
@ -65,7 +65,7 @@ class SettingsPage extends ConsumerWidget {
|
||||
hoverColor: kColorTransparent,
|
||||
title: const Text('Default URI Scheme'),
|
||||
subtitle: Text(
|
||||
'api.foss42.com → ${settings.defaultUriScheme}://api.foss42.com'),
|
||||
'api.apidash.dev → ${settings.defaultUriScheme}://api.apidash.dev'),
|
||||
trailing: DropdownMenu(
|
||||
onSelected: (value) {
|
||||
ref
|
||||
|
@ -43,7 +43,7 @@ String getShortPath(String path) {
|
||||
var f = p.split(path);
|
||||
if (f.length > 2) {
|
||||
f = f.sublist(f.length - 2);
|
||||
return ".../${p.joinAll(f)}";
|
||||
return p.join("...", p.joinAll(f));
|
||||
}
|
||||
return path;
|
||||
}
|
||||
|
@ -112,7 +112,9 @@ Map<String, dynamic> requestModelToHARJsonRequest(
|
||||
|
||||
var method = requestModel.method;
|
||||
var requestBody = requestModel.requestBody;
|
||||
if (kMethodsWithBody.contains(method) && requestBody != null) {
|
||||
if (kMethodsWithBody.contains(method) &&
|
||||
requestBody != null &&
|
||||
!requestModel.isFormDataRequest) {
|
||||
var contentLength = utf8.encode(requestBody).length;
|
||||
if (contentLength > 0) {
|
||||
hasBody = true;
|
||||
|
@ -47,36 +47,37 @@ class CopyButton extends StatelessWidget {
|
||||
class SendRequestButton extends StatelessWidget {
|
||||
const SendRequestButton({
|
||||
super.key,
|
||||
required this.selectedId,
|
||||
required this.sentRequestId,
|
||||
required this.isWorking,
|
||||
required this.onTap,
|
||||
});
|
||||
|
||||
final String? selectedId;
|
||||
final String? sentRequestId;
|
||||
final bool isWorking;
|
||||
final void Function() onTap;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
bool disable = sentRequestId != null;
|
||||
return FilledButton(
|
||||
onPressed: disable ? null : onTap,
|
||||
onPressed: isWorking ? null : onTap,
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Text(
|
||||
disable
|
||||
? (selectedId == sentRequestId ? kLabelSending : kLabelBusy)
|
||||
: kLabelSend,
|
||||
style: kTextStyleButton,
|
||||
),
|
||||
if (!disable) kHSpacer10,
|
||||
if (!disable)
|
||||
const Icon(
|
||||
size: 16,
|
||||
Icons.send,
|
||||
),
|
||||
],
|
||||
children: isWorking
|
||||
? const [
|
||||
Text(
|
||||
kLabelSending,
|
||||
style: kTextStyleButton,
|
||||
),
|
||||
]
|
||||
: const [
|
||||
Text(
|
||||
kLabelSend,
|
||||
style: kTextStyleButton,
|
||||
),
|
||||
kHSpacer10,
|
||||
Icon(
|
||||
size: 16,
|
||||
Icons.send,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
@ -97,6 +97,9 @@ class SidebarRequestCard extends StatelessWidget {
|
||||
onTapOutsideNameEditor?.call();
|
||||
//FocusScope.of(context).unfocus();
|
||||
},
|
||||
onFieldSubmitted: (value) {
|
||||
onTapOutsideNameEditor?.call();
|
||||
},
|
||||
onChanged: onChangedNameEditor,
|
||||
decoration: const InputDecoration(
|
||||
isCollapsed: true,
|
||||
|
@ -46,32 +46,34 @@ class _FormDataFieldState extends State<FormDataField> {
|
||||
color: colorScheme.onSurface,
|
||||
),
|
||||
decoration: InputDecoration(
|
||||
hintStyle: kCodeStyle.copyWith(
|
||||
color: colorScheme.outline.withOpacity(
|
||||
hintStyle: kCodeStyle.copyWith(
|
||||
color: colorScheme.outline.withOpacity(
|
||||
kHintOpacity,
|
||||
),
|
||||
),
|
||||
hintText: widget.hintText,
|
||||
contentPadding: const EdgeInsets.only(bottom: 16),
|
||||
focusedBorder: UnderlineInputBorder(
|
||||
borderSide: BorderSide(
|
||||
color: colorScheme.primary.withOpacity(
|
||||
kHintOpacity,
|
||||
),
|
||||
),
|
||||
hintText: widget.hintText,
|
||||
focusedBorder: UnderlineInputBorder(
|
||||
borderSide: BorderSide(
|
||||
color: colorScheme.primary.withOpacity(
|
||||
kHintOpacity,
|
||||
),
|
||||
),
|
||||
),
|
||||
enabledBorder: UnderlineInputBorder(
|
||||
borderSide: BorderSide(
|
||||
color: colorScheme.surfaceVariant,
|
||||
),
|
||||
enabledBorder: UnderlineInputBorder(
|
||||
borderSide: BorderSide(
|
||||
color: colorScheme.surfaceVariant,
|
||||
),
|
||||
),
|
||||
suffixIcon: DropdownButtonFormData(
|
||||
formDataType: widget.formDataType,
|
||||
onChanged: (p0) {
|
||||
if (widget.onFormDataTypeChanged != null) {
|
||||
widget.onFormDataTypeChanged!(p0);
|
||||
}
|
||||
},
|
||||
)),
|
||||
),
|
||||
suffixIcon: DropdownButtonFormData(
|
||||
formDataType: widget.formDataType,
|
||||
onChanged: (p0) {
|
||||
if (widget.onFormDataTypeChanged != null) {
|
||||
widget.onFormDataTypeChanged!(p0);
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
onChanged: widget.onChanged,
|
||||
),
|
||||
),
|
||||
|
@ -9,9 +9,12 @@ class CustomMarkdown extends StatelessWidget {
|
||||
super.key,
|
||||
required this.data,
|
||||
this.padding = const EdgeInsets.all(16.0),
|
||||
this.onTapLink,
|
||||
});
|
||||
|
||||
final String data;
|
||||
final EdgeInsets padding;
|
||||
final void Function(String text, String? href, String title)? onTapLink;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@ -25,9 +28,10 @@ class CustomMarkdown extends StatelessWidget {
|
||||
data: data,
|
||||
selectable: true,
|
||||
extensionSet: md.ExtensionSet.gitHubFlavored,
|
||||
onTapLink: (text, href, title) {
|
||||
launchUrl(Uri.parse(href ?? ""));
|
||||
},
|
||||
onTapLink: onTapLink ??
|
||||
(text, href, title) {
|
||||
launchUrl(Uri.parse(href ?? ""));
|
||||
},
|
||||
builders: {
|
||||
"inlineButton": InlineButton(),
|
||||
},
|
||||
|
@ -7,11 +7,13 @@ class URLField extends StatelessWidget {
|
||||
required this.selectedId,
|
||||
this.initialValue,
|
||||
this.onChanged,
|
||||
this.onFieldSubmitted,
|
||||
});
|
||||
|
||||
final String selectedId;
|
||||
final String? initialValue;
|
||||
final void Function(String)? onChanged;
|
||||
final void Function(String)? onFieldSubmitted;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@ -29,6 +31,7 @@ class URLField extends StatelessWidget {
|
||||
border: InputBorder.none,
|
||||
),
|
||||
onChanged: onChanged,
|
||||
onFieldSubmitted: onFieldSubmitted,
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -95,7 +98,6 @@ class JsonSearchField extends StatelessWidget {
|
||||
controller: controller,
|
||||
onChanged: onChanged,
|
||||
style: kCodeStyle,
|
||||
cursorHeight: 18,
|
||||
decoration: const InputDecoration(
|
||||
isDense: true,
|
||||
border: InputBorder.none,
|
||||
|
Reference in New Issue
Block a user