Skip to content

Commit a647d27

Browse files
committed
✨ feat: add canvas and linkedom support with example screen implementation
- Add canvas.bundle.mjs and linkedom.bundle.mjs assets - Implement ExampleScreen for demonstration - Update routing and home screen integration
1 parent ed34995 commit a647d27

5 files changed

Lines changed: 194 additions & 0 deletions

File tree

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
/* esm.sh - canvas@3.2.0 */
2+
var i=Object.create;var m=Object.defineProperty;var l=Object.getOwnPropertyDescriptor;var g=Object.getOwnPropertyNames;var d=Object.getPrototypeOf,f=Object.prototype.hasOwnProperty;var I=(e,a)=>()=>(a||e((a={exports:{}}).exports,a),a.exports);var w=(e,a,t,o)=>{if(a&&typeof a=="object"||typeof a=="function")for(let n of g(a))!f.call(e,n)&&n!==t&&m(e,n,{get:()=>a[n],enumerable:!(o=l(a,n))||o.enumerable});return e};var D=(e,a,t)=>(t=e!=null?i(d(e)):{},w(a||!e||!e.__esModule?m(t,"default",{value:e,enumerable:!0}):t,e));var s=I(r=>{r.createCanvas=function(e,a){return Object.assign(document.createElement("canvas"),{width:e,height:a})};r.createImageData=function(e,a,t){switch(arguments.length){case 0:return new ImageData;case 1:return new ImageData(e);case 2:return new ImageData(e,a);default:return new ImageData(e,a,t)}};r.loadImage=function(e,a){return new Promise(function(t,o){let n=Object.assign(document.createElement("img"),a);function u(){n.onload=null,n.onerror=null}n.onload=function(){u(),t(n)},n.onerror=function(){u(),o(new Error('Failed to load the image "'+e+'"'))},n.src=e})}});var c=D(s()),{createCanvas:p,createImageData:v,loadImage:E}=c,b=c.default??c;export{p as createCanvas,v as createImageData,b as default,E as loadImage};
3+
//# sourceMappingURL=canvas.bundle.mjs.map

example/assets/examples/linkedom.bundle.mjs

Lines changed: 21 additions & 0 deletions
Large diffs are not rendered by default.

example/lib/app/home_screen.dart

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -495,6 +495,15 @@ class _HomeScreenState extends State<HomeScreen> {
495495
Navigator.pushNamed(context, '/js-actions-test');
496496
},
497497
),
498+
const Divider(),
499+
ListTile(
500+
leading: const Icon(Icons.science),
501+
title: const Text('Example Screen'),
502+
onTap: () {
503+
Navigator.pop(context);
504+
Navigator.pushNamed(context, '/example');
505+
},
506+
),
498507
],
499508
),
500509
);

example/lib/app/router.dart

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import 'package:flutter/material.dart';
33
import '../screens/api_overview_screen.dart';
44
import '../screens/engine_api_screen.dart';
55
import '../screens/error_api_screen.dart';
6+
import '../screens/example_screen.dart';
67
import '../screens/js_actions_test_screen.dart';
78
import '../screens/playground_screen.dart';
89
import '../screens/runtime_api_screen.dart';
@@ -49,6 +50,10 @@ class AppRouter {
4950
return MaterialPageRoute(
5051
builder: (_) => const SourceApiScreen(),
5152
);
53+
case '/example':
54+
return MaterialPageRoute(
55+
builder: (_) => const ExampleScreen(),
56+
);
5257
default:
5358
return MaterialPageRoute(
5459
builder: (_) => const HomeScreen(),
Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
import 'dart:async';
2+
3+
import 'package:fjs/fjs.dart';
4+
import 'package:flutter/material.dart';
5+
import 'package:flutter/services.dart';
6+
7+
class MyEngine {
8+
MyEngine._(this._engine);
9+
10+
final JsEngine _engine;
11+
12+
static late final MyEngine _instance;
13+
static bool _initialized = false;
14+
15+
factory MyEngine() {
16+
if (!_initialized) {
17+
throw StateError('MyEngine is not initialized. Call initialize() first.');
18+
}
19+
return _instance;
20+
}
21+
22+
static Future<void> initialize({
23+
FutureOr<JsValue?> Function(JsValue)? bridgeCall,
24+
}) async {
25+
if (_initialized) {
26+
return;
27+
}
28+
29+
final linkedom =
30+
await rootBundle.load('assets/examples/linkedom.bundle.mjs');
31+
final canvas = await rootBundle.load('assets/examples/canvas.bundle.mjs');
32+
33+
final rt = await JsAsyncRuntime.withOptions(
34+
builtin: JsBuiltinOptions(
35+
console: true,
36+
fetch: true,
37+
timers: true,
38+
url: true,
39+
),
40+
additional: [
41+
JsModule(
42+
name: 'canvas',
43+
source: JsCode.bytes(canvas.buffer.asUint8List())),
44+
JsModule(
45+
name: 'linkedom',
46+
source: JsCode.bytes(linkedom.buffer.asUint8List())),
47+
]);
48+
final context = await JsAsyncContext.from(rt: rt);
49+
final engine = JsEngine(context);
50+
await engine.init(bridgeCall: bridgeCall);
51+
_instance = MyEngine._(engine);
52+
_initialized = true;
53+
}
54+
55+
void _assertInitialized() {
56+
if (!_initialized) {
57+
throw StateError('MyEngine is not initialized. Call initialize() first.');
58+
}
59+
}
60+
61+
Future<JsValue> evaluateModule(String taskId, String code) async {
62+
_assertInitialized();
63+
await _engine
64+
.evaluateModule(JsModule(name: taskId, source: JsCode.code(code)));
65+
final result = await _engine.eval(JsCode.code('''
66+
(()=>{
67+
const result = globalThis['$taskId'];
68+
delete globalThis['$taskId'];
69+
return result;
70+
})()
71+
'''));
72+
return result;
73+
}
74+
75+
Future<JsValue> evaluate(String code) async {
76+
_assertInitialized();
77+
return _engine.eval(JsCode.code(code));
78+
}
79+
}
80+
81+
Future<void> test1MyEngine() async {
82+
await MyEngine.initialize(bridgeCall: (value) async {
83+
print('Bridge call with value: $value');
84+
return JsValue.string('Response from Dart');
85+
});
86+
87+
final engine = MyEngine();
88+
final code = '''
89+
await (async () => {
90+
const {parseHTML} = await import('linkedom');
91+
const html = await fetch("https://example.com").then((res) => res.text());
92+
console.log("Fetched HTML:", html);
93+
const {document} = parseHTML(html);
94+
95+
return document.toString();
96+
})()
97+
''';
98+
final result = await engine.evaluate(code);
99+
100+
print('Evaluation result: $result');
101+
}
102+
103+
Future<void> test2MyEngine() async {
104+
await MyEngine.initialize(bridgeCall: (value) async {
105+
print('Bridge call with value: $value');
106+
return JsValue.string('Response from Dart');
107+
});
108+
109+
final engine = MyEngine();
110+
final taskId = DateTime.timestamp().microsecondsSinceEpoch.toRadixString(36);
111+
final code = '''
112+
import {parseHTML} from 'linkedom';
113+
114+
globalThis['$taskId'] = await (async () => {
115+
const html = await fetch("https://example.com").then((res) => res.text());
116+
console.log("Fetched HTML:", html);
117+
const {document} = parseHTML(html);
118+
119+
return document.toString();
120+
})();
121+
''';
122+
print(code);
123+
final result = await engine.evaluateModule(taskId, code);
124+
125+
print('Evaluation result: $result');
126+
}
127+
128+
class ExampleScreen extends StatelessWidget {
129+
const ExampleScreen({super.key});
130+
131+
@override
132+
Widget build(BuildContext context) {
133+
return Scaffold(
134+
body: Center(
135+
child: Column(
136+
mainAxisSize: MainAxisSize.min,
137+
spacing: 24.0,
138+
children: [
139+
ElevatedButton(
140+
onPressed: () async {
141+
await test1MyEngine();
142+
},
143+
child: const Text('Run MyEngine Test 1'),
144+
),
145+
ElevatedButton(
146+
onPressed: () async {
147+
await test2MyEngine();
148+
},
149+
child: const Text('Run MyEngine Test 2'),
150+
),
151+
],
152+
),
153+
),
154+
);
155+
}
156+
}

0 commit comments

Comments
 (0)