Skip to content

Commit

Permalink
Add Open in IDX feature
Browse files Browse the repository at this point in the history
  • Loading branch information
johnpryan committed Jun 12, 2024
1 parent 0748858 commit d958acf
Show file tree
Hide file tree
Showing 10 changed files with 145 additions and 15 deletions.
30 changes: 30 additions & 0 deletions pkgs/dart_services/lib/src/common_server.dart
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,36 @@ class CommonServerApi {
return ok(version().toJson());
}

@Route.post('$apiPrefix/openInIDX')
Future<Response> openInIdx(Request request, String apiVersion) async {
final code = api.OpenInIdxRequest.fromJson(await request.readAsJson()).code;
final idxUrl = Uri.parse('https://idx.google.com/run.api');

final data = {
'project[files][lib/main.dart]': code,
'project[settings]': '{"baselineEnvironment": "flutter"}',
};
try {
final response = await http.post(
idxUrl,
body: data,
headers: {'Content-Type': 'application/x-www-form-urlencoded'},
);

if (response.statusCode == 302) {
return ok(api.OpenInIdxResponse(idxUrl: response.headers['location']!)
.toJson());
} else {
return Response.internalServerError(
body:
'Failed to read response from IDX server. Response: $response');
}
} catch (error) {
return Response.internalServerError(
body: 'Failed to read response from IDX server. Error: $error');
}
}

static final String? geminiApiKey = Platform.environment['GEMINI_API_KEY'];
http.Client? geminiHttpClient;

Expand Down
5 changes: 5 additions & 0 deletions pkgs/dart_services/lib/src/common_server.g.dart

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

34 changes: 34 additions & 0 deletions pkgs/dartpad_shared/lib/model.dart
Original file line number Diff line number Diff line change
Expand Up @@ -392,6 +392,40 @@ class GeminiResponse {
String toString() => 'GeminiResponse[response=$response]';
}

@JsonSerializable()
class OpenInIdxRequest {
final String code;

OpenInIdxRequest({
required this.code,
});

factory OpenInIdxRequest.fromJson(Map<String, dynamic> json) =>
_$OpenInIdxRequestFromJson(json);

Map<String, dynamic> toJson() => _$OpenInIdxRequestToJson(this);

@override
String toString() => 'OpenInIdxRequest [${code.substring(0, 10)} (...)';
}

@JsonSerializable()
class OpenInIdxResponse {
final String idxUrl;

OpenInIdxResponse({
required this.idxUrl,
});

factory OpenInIdxResponse.fromJson(Map<String, dynamic> json) =>
_$OpenInIdxResponseFromJson(json);

Map<String, dynamic> toJson() => _$OpenInIdxResponseToJson(this);

@override
String toString() => 'OpenInIdxResponse [$idxUrl]';
}

@JsonSerializable()
class PackageInfo {
final String name;
Expand Down
20 changes: 20 additions & 0 deletions pkgs/dartpad_shared/lib/model.g.dart

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions pkgs/dartpad_shared/lib/services.dart
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,9 @@ class ServicesClient {
Future<CompileDDCResponse> compileDDC(CompileRequest request) =>
_requestPost('compileDDC', request.toJson(), CompileDDCResponse.fromJson);

Future<OpenInIdxResponse> openInIdx(OpenInIdxRequest request) =>
_requestPost('openInIDX', request.toJson(), OpenInIdxResponse.fromJson);

/// Note: this API is experimental and could change or be removed at any time.
@experimental
Future<GeminiResponse> gemini(SourceRequest request) =>
Expand Down
Binary file added pkgs/dartpad_ui/assets/idx_192.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
64 changes: 50 additions & 14 deletions pkgs/dartpad_ui/lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import 'package:flutter/material.dart';
import 'package:flutter_web_plugins/url_strategy.dart' show usePathUrlStrategy;
import 'package:go_router/go_router.dart';
import 'package:google_fonts/google_fonts.dart';
import 'package:http/http.dart' as http;
import 'package:pointer_interceptor/pointer_interceptor.dart';
import 'package:provider/provider.dart';
import 'package:split_view/split_view.dart';
Expand Down Expand Up @@ -703,20 +704,7 @@ class DartPadAppBar extends StatelessWidget implements PreferredSizeWidget {
actions: [
// Hide the Install SDK button when the screen width is too small.
if (constraints.maxWidth > smallScreenWidth)
TextButton(
onPressed: () {
url_launcher.launchUrl(
Uri.parse('https://docs.flutter.dev/get-started/install'),
);
},
child: const Row(
children: [
Text('Install SDK'),
SizedBox(width: denseSpacing),
Icon(Icons.launch, size: 18),
],
),
),
ContinueInMenu(openInIdx: _openInIDX,),
const SizedBox(width: denseSpacing),
_BrightnessButton(
handleBrightnessChange: widget.handleBrightnessChanged,
Expand All @@ -732,6 +720,13 @@ class DartPadAppBar extends StatelessWidget implements PreferredSizeWidget {
Size get preferredSize => bottom == null
? const Size(double.infinity, 56.0)
: const Size(double.infinity, 112.0);

Future<void> _openInIDX() async {
final code = appModel.sourceCodeController.text;
final request = OpenInIdxRequest(code: code);
final response = await appServices.services.openInIdx(request);
url_launcher.launchUrl(Uri.parse(response.idxUrl));
}
}

class EditorWithButtons extends StatelessWidget {
Expand Down Expand Up @@ -1157,6 +1152,47 @@ class OverflowMenu extends StatelessWidget {
}
}

class ContinueInMenu extends StatelessWidget {
VoidCallback openInIdx;
ContinueInMenu({super.key, required this.openInIdx});

@override
Widget build(BuildContext context) {
return MenuAnchor(
builder: (context, MenuController controller, Widget? child) {
return TextButton(
child: const Text('Continue in...'),
onPressed: () => controller.toggleMenuState(),
);
},
menuChildren: [
...[
MenuItemButton(
trailingIcon: const Logo(type: 'idx'),
onPressed: () {
openInIdx();
},
child: const Padding(
padding: EdgeInsets.fromLTRB(0, 0, 32, 0),
child: Text('IDX'),
),
),
MenuItemButton(
trailingIcon: const Icon(Icons.launch),
onPressed: () {
url_launcher.launchUrl(Uri.parse('https://docs.flutter.dev/get-started/install'));
},
child: const Padding(
padding: EdgeInsets.fromLTRB(0, 0, 32, 0),
child: Text('Install SDK'),
),
)].map((widget) => PointerInterceptor(child: widget))
],
);
}
}


class KeyBindingsTable extends StatelessWidget {
final List<(String, List<ShortcutActivator>)> bindings;

Expand Down
1 change: 1 addition & 0 deletions pkgs/dartpad_ui/lib/widgets.dart
Original file line number Diff line number Diff line change
Expand Up @@ -261,6 +261,7 @@ final class Logo extends StatelessWidget {
'flutter' => 'assets/flutter_logo_192.png',
'flame' => 'assets/flame_logo_192.png',
'gemini' => 'assets/gemini_sparkle_192.png',
'idx' => 'assets/idx_192.png',
_ => 'assets/dart_logo_192.png',
};
return Image.asset(assetPath, width: width);
Expand Down
1 change: 1 addition & 0 deletions pkgs/dartpad_ui/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ flutter:
- assets/flame_logo_192.png
- assets/flutter_logo_192.png
- assets/gemini_sparkle_192.png
- assets/idx_192.png
- assets/RobotoMono-Bold.ttf
- assets/RobotoMono-Regular.ttf

Expand Down
2 changes: 1 addition & 1 deletion pkgs/samples/lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ class MyApp extends StatelessWidget {
title: 'Flutter Demo',
debugShowCheckedModeBanner: false,
theme: ThemeData(
primarySwatch: Colors.blue,
colorSchemeSeed: Colors.blue,
),
home: const MyHomePage(title: 'Flutter Demo Home Page'),
);
Expand Down

0 comments on commit d958acf

Please sign in to comment.