@yodaos-pkg/ink 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +648 -0
- package/dist/README.md +648 -0
- package/dist/env.js +336 -0
- package/dist/index.d.ts +115 -0
- package/dist/index.js +476 -0
- package/dist/package.json +20 -0
- package/dist/pkg/README.md +34 -0
- package/dist/pkg/ink_web.d.ts +77 -0
- package/dist/pkg/ink_web.js +1384 -0
- package/dist/pkg/ink_web_bg.wasm +0 -0
- package/dist/pkg/ink_web_bg.wasm.d.ts +29 -0
- package/dist/pkg/package.json +3 -0
- package/package.json +24 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,476 @@
|
|
|
1
|
+
import initInkWasm, {
|
|
2
|
+
InkWebView as RawInkWebView,
|
|
3
|
+
get_version as getInkVersionFromWasm,
|
|
4
|
+
} from './pkg/ink_web.js';
|
|
5
|
+
|
|
6
|
+
let bindingsPromise;
|
|
7
|
+
|
|
8
|
+
function getDefaultScaleFactor() {
|
|
9
|
+
return Number(globalThis.devicePixelRatio || 1);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function ensureFetch(fetchImpl) {
|
|
13
|
+
const resolved = fetchImpl || globalThis.fetch;
|
|
14
|
+
if (typeof resolved !== 'function') {
|
|
15
|
+
throw new Error('A fetch implementation is required for VFS loading.');
|
|
16
|
+
}
|
|
17
|
+
return resolved;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function trimTrailingSlash(value) {
|
|
21
|
+
return value.replace(/\/+$/, '');
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function ensureLeadingSlash(value) {
|
|
25
|
+
if (!value) {
|
|
26
|
+
return '/';
|
|
27
|
+
}
|
|
28
|
+
return value.startsWith('/') ? value : `/${value}`;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function normalizeBaseUrl(baseUrl) {
|
|
32
|
+
if (typeof baseUrl !== 'string' || !baseUrl.trim()) {
|
|
33
|
+
throw new Error('`baseUrl` must be a non-empty string.');
|
|
34
|
+
}
|
|
35
|
+
return trimTrailingSlash(baseUrl.trim());
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function cloneUint8Array(value) {
|
|
39
|
+
if (value instanceof Uint8Array) {
|
|
40
|
+
return new Uint8Array(value);
|
|
41
|
+
}
|
|
42
|
+
if (ArrayBuffer.isView(value)) {
|
|
43
|
+
return new Uint8Array(value.buffer.slice(value.byteOffset, value.byteOffset + value.byteLength));
|
|
44
|
+
}
|
|
45
|
+
if (value instanceof ArrayBuffer) {
|
|
46
|
+
return new Uint8Array(value.slice(0));
|
|
47
|
+
}
|
|
48
|
+
return null;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function toBundleBytes(filePath, value) {
|
|
52
|
+
if (typeof value === 'string') {
|
|
53
|
+
return new TextEncoder().encode(value);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const cloned = cloneUint8Array(value);
|
|
57
|
+
if (cloned) {
|
|
58
|
+
return cloned;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
throw new TypeError(
|
|
62
|
+
`Unsupported bundle file payload for \`${filePath}\`. Expected string, ArrayBuffer, or Uint8Array.`,
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function getEntries(files) {
|
|
67
|
+
if (files instanceof Map) {
|
|
68
|
+
return Array.from(files.entries());
|
|
69
|
+
}
|
|
70
|
+
if (Array.isArray(files)) {
|
|
71
|
+
return files;
|
|
72
|
+
}
|
|
73
|
+
if (files && typeof files === 'object') {
|
|
74
|
+
return Object.entries(files);
|
|
75
|
+
}
|
|
76
|
+
throw new TypeError('`files` must be a Map, entry array, or plain object.');
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export function normalizeBundleFiles(files) {
|
|
80
|
+
const normalized = new Map();
|
|
81
|
+
for (const [filePath, value] of getEntries(files)) {
|
|
82
|
+
if (typeof filePath !== 'string' || !filePath) {
|
|
83
|
+
throw new TypeError('Bundle file paths must be non-empty strings.');
|
|
84
|
+
}
|
|
85
|
+
normalized.set(filePath, toBundleBytes(filePath, value));
|
|
86
|
+
}
|
|
87
|
+
return normalized;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export function serializeQuery(query) {
|
|
91
|
+
if (query == null) {
|
|
92
|
+
return null;
|
|
93
|
+
}
|
|
94
|
+
if (typeof query === 'string') {
|
|
95
|
+
return query;
|
|
96
|
+
}
|
|
97
|
+
return JSON.stringify(query);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
async function buildHttpError(response, bodyReader = (res) => res.text()) {
|
|
101
|
+
let details = response.statusText;
|
|
102
|
+
try {
|
|
103
|
+
details = await bodyReader(response);
|
|
104
|
+
} catch {
|
|
105
|
+
// Keep the default status text when the body cannot be read.
|
|
106
|
+
}
|
|
107
|
+
const message = `Request failed with ${response.status} ${response.statusText}: ${String(details).slice(0, 200)}`;
|
|
108
|
+
return new Error(message);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export function createVfsUrls({ baseUrl, appId }) {
|
|
112
|
+
if (typeof appId !== 'string' || !appId.trim()) {
|
|
113
|
+
throw new Error('`appId` must be a non-empty string.');
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const normalizedBaseUrl = normalizeBaseUrl(baseUrl);
|
|
117
|
+
const encodedAppId = encodeURIComponent(appId.trim());
|
|
118
|
+
|
|
119
|
+
return {
|
|
120
|
+
manifestUrl: `${normalizedBaseUrl}/apps/${encodedAppId}/manifest`,
|
|
121
|
+
fileUrl(filePath) {
|
|
122
|
+
if (typeof filePath !== 'string' || !filePath) {
|
|
123
|
+
throw new Error('`filePath` must be a non-empty string.');
|
|
124
|
+
}
|
|
125
|
+
const segments = filePath
|
|
126
|
+
.split('/')
|
|
127
|
+
.filter(Boolean)
|
|
128
|
+
.map((segment) => encodeURIComponent(segment));
|
|
129
|
+
return `${normalizedBaseUrl}/apps/${encodedAppId}/files/${segments.join('/')}`;
|
|
130
|
+
},
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
export async function loadBundleFromVfs({
|
|
135
|
+
appId,
|
|
136
|
+
baseUrl,
|
|
137
|
+
fetch: fetchImpl,
|
|
138
|
+
signal,
|
|
139
|
+
headers,
|
|
140
|
+
}) {
|
|
141
|
+
const resolvedFetch = ensureFetch(fetchImpl);
|
|
142
|
+
const { manifestUrl, fileUrl } = createVfsUrls({ baseUrl, appId });
|
|
143
|
+
|
|
144
|
+
const manifestResponse = await resolvedFetch(manifestUrl, {
|
|
145
|
+
method: 'GET',
|
|
146
|
+
headers,
|
|
147
|
+
signal,
|
|
148
|
+
});
|
|
149
|
+
if (!manifestResponse.ok) {
|
|
150
|
+
throw await buildHttpError(manifestResponse);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
const manifest = await manifestResponse.json();
|
|
154
|
+
if (!manifest || !Array.isArray(manifest.files)) {
|
|
155
|
+
throw new Error('VFS manifest must include a `files` array.');
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
const files = new Map();
|
|
159
|
+
await Promise.all(
|
|
160
|
+
manifest.files.map(async (entry) => {
|
|
161
|
+
if (!entry || typeof entry.path !== 'string' || !entry.path) {
|
|
162
|
+
throw new Error('Each VFS manifest entry must include a non-empty `path`.');
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
const response = await resolvedFetch(fileUrl(entry.path), {
|
|
166
|
+
method: 'GET',
|
|
167
|
+
headers,
|
|
168
|
+
signal,
|
|
169
|
+
});
|
|
170
|
+
if (!response.ok) {
|
|
171
|
+
throw await buildHttpError(response);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
files.set(entry.path, new Uint8Array(await response.arrayBuffer()));
|
|
175
|
+
}),
|
|
176
|
+
);
|
|
177
|
+
|
|
178
|
+
return {
|
|
179
|
+
appId: manifest.appId || appId,
|
|
180
|
+
manifest,
|
|
181
|
+
files,
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function normalizeKeyboardCode(event) {
|
|
186
|
+
return event.code || event.key || 'Unidentified';
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
function getPointerPosition(canvas, event) {
|
|
190
|
+
const rect = canvas.getBoundingClientRect();
|
|
191
|
+
return {
|
|
192
|
+
x: event.clientX - rect.left,
|
|
193
|
+
y: event.clientY - rect.top,
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
function ensureAnimationFrameApi() {
|
|
198
|
+
if (typeof globalThis.requestAnimationFrame !== 'function') {
|
|
199
|
+
return {
|
|
200
|
+
request(callback) {
|
|
201
|
+
return setTimeout(() => callback(Date.now()), 16);
|
|
202
|
+
},
|
|
203
|
+
cancel(handle) {
|
|
204
|
+
clearTimeout(handle);
|
|
205
|
+
},
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
return {
|
|
210
|
+
request(callback) {
|
|
211
|
+
return globalThis.requestAnimationFrame(callback);
|
|
212
|
+
},
|
|
213
|
+
cancel(handle) {
|
|
214
|
+
globalThis.cancelAnimationFrame(handle);
|
|
215
|
+
},
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
export async function initInk(options = {}) {
|
|
220
|
+
if (!bindingsPromise) {
|
|
221
|
+
const initInput = options.wasmUrl || options.moduleOrPath || new URL('./pkg/ink_web_bg.wasm', import.meta.url);
|
|
222
|
+
bindingsPromise = initInkWasm(initInput).then(() => ({
|
|
223
|
+
InkWebView: RawInkWebView,
|
|
224
|
+
getInkVersion: getInkVersionFromWasm,
|
|
225
|
+
}));
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
return bindingsPromise;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
export function getInkVersion() {
|
|
232
|
+
return getInkVersionFromWasm();
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
export class InkView {
|
|
236
|
+
static async create(options) {
|
|
237
|
+
const config = options || {};
|
|
238
|
+
const width = Number(config.width);
|
|
239
|
+
const height = Number(config.height);
|
|
240
|
+
|
|
241
|
+
if (!Number.isFinite(width) || !Number.isFinite(height)) {
|
|
242
|
+
throw new Error('`width` and `height` are required numeric values.');
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
const bindings = await initInk(config.wasm);
|
|
246
|
+
const rawView = new bindings.InkWebView(width, height, Number(config.scaleFactor || getDefaultScaleFactor()));
|
|
247
|
+
const view = new InkView(rawView, config);
|
|
248
|
+
|
|
249
|
+
if (config.canvas) {
|
|
250
|
+
view.bindCanvas(config.canvas);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
return view;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
#rawView;
|
|
257
|
+
#canvas;
|
|
258
|
+
#animationFrameApi;
|
|
259
|
+
#frameHandle;
|
|
260
|
+
#autoRender;
|
|
261
|
+
#domCleanup;
|
|
262
|
+
|
|
263
|
+
constructor(rawView, options = {}) {
|
|
264
|
+
this.#rawView = rawView;
|
|
265
|
+
this.#canvas = options.canvas || null;
|
|
266
|
+
this.#animationFrameApi = ensureAnimationFrameApi();
|
|
267
|
+
this.#frameHandle = null;
|
|
268
|
+
this.#autoRender = false;
|
|
269
|
+
this.#domCleanup = null;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
bindCanvas(canvas) {
|
|
273
|
+
if (!canvas || typeof canvas.getBoundingClientRect !== 'function') {
|
|
274
|
+
throw new TypeError('`canvas` must be an HTMLCanvasElement-like object.');
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
this.#rawView.bindCanvas(canvas);
|
|
278
|
+
this.#canvas = canvas;
|
|
279
|
+
return this;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
bindDomEvents(options = {}) {
|
|
283
|
+
const canvas = options.canvas || this.#canvas;
|
|
284
|
+
if (!canvas) {
|
|
285
|
+
throw new Error('bindDomEvents requires a bound canvas.');
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
if (this.#domCleanup) {
|
|
289
|
+
this.#domCleanup();
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
const keyboardTarget = options.keyboardTarget || globalThis.window || canvas;
|
|
293
|
+
const focusTarget = options.focusTarget || canvas;
|
|
294
|
+
const listeners = [];
|
|
295
|
+
const addListener = (target, type, listener, listenerOptions) => {
|
|
296
|
+
if (!target || typeof target.addEventListener !== 'function') {
|
|
297
|
+
return;
|
|
298
|
+
}
|
|
299
|
+
target.addEventListener(type, listener, listenerOptions);
|
|
300
|
+
listeners.push(() => target.removeEventListener(type, listener, listenerOptions));
|
|
301
|
+
};
|
|
302
|
+
|
|
303
|
+
if (focusTarget && typeof focusTarget.setAttribute === 'function' && focusTarget.tabIndex < 0) {
|
|
304
|
+
focusTarget.setAttribute('tabindex', '0');
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
addListener(canvas, 'pointerdown', (event) => {
|
|
308
|
+
const { x, y } = getPointerPosition(canvas, event);
|
|
309
|
+
this.notifyUserInteraction();
|
|
310
|
+
if (focusTarget && typeof focusTarget.focus === 'function') {
|
|
311
|
+
focusTarget.focus();
|
|
312
|
+
}
|
|
313
|
+
this.#rawView.dispatchPointer('pointerdown', x, y, event.pointerId || 0, event.button || 0, 0, 0);
|
|
314
|
+
this.requestRender();
|
|
315
|
+
});
|
|
316
|
+
addListener(canvas, 'pointermove', (event) => {
|
|
317
|
+
const { x, y } = getPointerPosition(canvas, event);
|
|
318
|
+
this.#rawView.dispatchPointer('pointermove', x, y, event.pointerId || 0, event.button || 0, 0, 0);
|
|
319
|
+
this.requestRender();
|
|
320
|
+
});
|
|
321
|
+
addListener(canvas, 'pointerup', (event) => {
|
|
322
|
+
const { x, y } = getPointerPosition(canvas, event);
|
|
323
|
+
this.#rawView.dispatchPointer('pointerup', x, y, event.pointerId || 0, event.button || 0, 0, 0);
|
|
324
|
+
this.requestRender();
|
|
325
|
+
});
|
|
326
|
+
addListener(canvas, 'pointercancel', (event) => {
|
|
327
|
+
const { x, y } = getPointerPosition(canvas, event);
|
|
328
|
+
this.#rawView.dispatchPointer('touchcancel', x, y, event.pointerId || 0, event.button || 0, 0, 0);
|
|
329
|
+
this.requestRender();
|
|
330
|
+
});
|
|
331
|
+
addListener(
|
|
332
|
+
canvas,
|
|
333
|
+
'wheel',
|
|
334
|
+
(event) => {
|
|
335
|
+
const { x, y } = getPointerPosition(canvas, event);
|
|
336
|
+
if (options.preventWheelDefault !== false && typeof event.preventDefault === 'function') {
|
|
337
|
+
event.preventDefault();
|
|
338
|
+
}
|
|
339
|
+
this.#rawView.dispatchPointer('mousewheel', x, y, 0, 0, event.deltaX || 0, event.deltaY || 0);
|
|
340
|
+
this.requestRender();
|
|
341
|
+
},
|
|
342
|
+
{ passive: false },
|
|
343
|
+
);
|
|
344
|
+
|
|
345
|
+
addListener(keyboardTarget, 'keydown', (event) => {
|
|
346
|
+
this.#rawView.dispatchInput('keydown', normalizeKeyboardCode(event), Number(event.timeStamp || Date.now()));
|
|
347
|
+
this.requestRender();
|
|
348
|
+
});
|
|
349
|
+
addListener(keyboardTarget, 'keyup', (event) => {
|
|
350
|
+
this.#rawView.dispatchInput('keyup', normalizeKeyboardCode(event), Number(event.timeStamp || Date.now()));
|
|
351
|
+
this.requestRender();
|
|
352
|
+
});
|
|
353
|
+
addListener(focusTarget, 'focus', () => {
|
|
354
|
+
this.focus();
|
|
355
|
+
});
|
|
356
|
+
addListener(focusTarget, 'blur', () => {
|
|
357
|
+
this.blur();
|
|
358
|
+
});
|
|
359
|
+
|
|
360
|
+
this.#domCleanup = () => {
|
|
361
|
+
for (const dispose of listeners.splice(0)) {
|
|
362
|
+
dispose();
|
|
363
|
+
}
|
|
364
|
+
this.#domCleanup = null;
|
|
365
|
+
};
|
|
366
|
+
|
|
367
|
+
return this.#domCleanup;
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
openBundle({ appId, files, initialPage = null, query = null }) {
|
|
371
|
+
if (typeof appId !== 'string' || !appId.trim()) {
|
|
372
|
+
throw new Error('`appId` must be a non-empty string.');
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
this.#rawView.openBundle(appId, normalizeBundleFiles(files), initialPage, serializeQuery(query));
|
|
376
|
+
this.requestRender();
|
|
377
|
+
return this;
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
async openFromVfs({ appId, baseUrl, initialPage = null, query = null, fetch, signal, headers }) {
|
|
381
|
+
const bundle = await loadBundleFromVfs({
|
|
382
|
+
appId,
|
|
383
|
+
baseUrl,
|
|
384
|
+
fetch,
|
|
385
|
+
signal,
|
|
386
|
+
headers,
|
|
387
|
+
});
|
|
388
|
+
this.openBundle({
|
|
389
|
+
appId: bundle.appId,
|
|
390
|
+
files: bundle.files,
|
|
391
|
+
initialPage,
|
|
392
|
+
query,
|
|
393
|
+
});
|
|
394
|
+
return bundle;
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
resize(width, height, scaleFactor = getDefaultScaleFactor()) {
|
|
398
|
+
this.#rawView.resize(Number(width), Number(height), Number(scaleFactor));
|
|
399
|
+
this.requestRender();
|
|
400
|
+
return this;
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
focus() {
|
|
404
|
+
this.#rawView.focus();
|
|
405
|
+
return this;
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
blur() {
|
|
409
|
+
this.#rawView.blur();
|
|
410
|
+
return this;
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
notifyUserInteraction() {
|
|
414
|
+
this.#rawView.notifyUserInteraction();
|
|
415
|
+
return this;
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
render() {
|
|
419
|
+
const hasMoreWork = Boolean(this.#rawView.render());
|
|
420
|
+
if (hasMoreWork || this.#autoRender) {
|
|
421
|
+
this.requestRender();
|
|
422
|
+
}
|
|
423
|
+
return hasMoreWork;
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
requestRender() {
|
|
427
|
+
if (this.#frameHandle != null) {
|
|
428
|
+
return this;
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
this.#frameHandle = this.#animationFrameApi.request(() => {
|
|
432
|
+
this.#frameHandle = null;
|
|
433
|
+
this.render();
|
|
434
|
+
});
|
|
435
|
+
return this;
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
startRendering() {
|
|
439
|
+
this.#autoRender = true;
|
|
440
|
+
this.requestRender();
|
|
441
|
+
return this;
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
stopRendering() {
|
|
445
|
+
this.#autoRender = false;
|
|
446
|
+
if (this.#frameHandle != null) {
|
|
447
|
+
this.#animationFrameApi.cancel(this.#frameHandle);
|
|
448
|
+
this.#frameHandle = null;
|
|
449
|
+
}
|
|
450
|
+
return this;
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
isRunning() {
|
|
454
|
+
return Boolean(this.#rawView.isRunning());
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
destroy() {
|
|
458
|
+
this.stopRendering();
|
|
459
|
+
if (this.#domCleanup) {
|
|
460
|
+
this.#domCleanup();
|
|
461
|
+
}
|
|
462
|
+
this.#rawView.destroy();
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
export function createInkView(options) {
|
|
467
|
+
return InkView.create(options);
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
export function createFrameworkBindings() {
|
|
471
|
+
return {
|
|
472
|
+
ensureLeadingSlash,
|
|
473
|
+
initInk,
|
|
474
|
+
createInkView,
|
|
475
|
+
};
|
|
476
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@yodaos-pkg/ink",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Ink Web SDK for browser rendering and VFS-backed app loading",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "index.js",
|
|
7
|
+
"types": "index.d.ts",
|
|
8
|
+
"files": [
|
|
9
|
+
"*"
|
|
10
|
+
],
|
|
11
|
+
"repository": {
|
|
12
|
+
"type": "git",
|
|
13
|
+
"url": "https://github.com/jsar-project/ink"
|
|
14
|
+
},
|
|
15
|
+
"license": "ISC",
|
|
16
|
+
"publishConfig": {
|
|
17
|
+
"access": "public",
|
|
18
|
+
"registry": "https://registry.npmjs.com/"
|
|
19
|
+
}
|
|
20
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# ink-web
|
|
2
|
+
|
|
3
|
+
`ink-web` provides WebAssembly (Wasm) bindings.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
This package compiles the Ink engine into Wasm, enabling it to run in modern web browsers:
|
|
8
|
+
|
|
9
|
+
- **Wasm Bindings**: Utilizes `wasm-bindgen` to implement interoperability between Rust code and Web APIs.
|
|
10
|
+
- **Web Rendering**: Bridges Ink's drawing instructions to Web Canvas or WebGL.
|
|
11
|
+
|
|
12
|
+
## Running Locally
|
|
13
|
+
|
|
14
|
+
1. Build the demo:
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
./build.sh
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
2. Start the bundled Node static server and proxy:
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
npm run dev --prefix playground
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
3. Open:
|
|
27
|
+
|
|
28
|
+
```text
|
|
29
|
+
http://localhost:8080
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
The local server also exposes a same-origin `/__proxy` endpoint for external HTTP resources so
|
|
33
|
+
the demo can load remote images and other browser-fetched assets without direct cross-origin
|
|
34
|
+
requests from the page.
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/* tslint:disable */
|
|
2
|
+
/* eslint-disable */
|
|
3
|
+
|
|
4
|
+
export class InkWebView {
|
|
5
|
+
free(): void;
|
|
6
|
+
[Symbol.dispose](): void;
|
|
7
|
+
bindCanvas(canvas: HTMLCanvasElement): void;
|
|
8
|
+
blur(): void;
|
|
9
|
+
destroy(): void;
|
|
10
|
+
dispatchInput(event_type: string, code: string, timestamp: number): void;
|
|
11
|
+
dispatchPointer(event_type: string, x: number, y: number, id: number, button: number, delta_x: number, delta_y: number): void;
|
|
12
|
+
focus(): void;
|
|
13
|
+
isRunning(): boolean;
|
|
14
|
+
constructor(width: number, height: number, scale_factor: number);
|
|
15
|
+
notifyUserInteraction(): void;
|
|
16
|
+
openBundle(app_id: string, files: Map<any, any>, initial_page?: string | null, query_json?: string | null): void;
|
|
17
|
+
render(): boolean;
|
|
18
|
+
resize(width: number, height: number, scale_factor: number): void;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function get_version(): string;
|
|
22
|
+
|
|
23
|
+
export function main(): void;
|
|
24
|
+
|
|
25
|
+
export type InitInput = RequestInfo | URL | Response | BufferSource | WebAssembly.Module;
|
|
26
|
+
|
|
27
|
+
export interface InitOutput {
|
|
28
|
+
readonly memory: WebAssembly.Memory;
|
|
29
|
+
readonly __wbg_inkwebview_free: (a: number, b: number) => void;
|
|
30
|
+
readonly get_version: (a: number) => void;
|
|
31
|
+
readonly inkwebview_bindCanvas: (a: number, b: number, c: number) => void;
|
|
32
|
+
readonly inkwebview_blur: (a: number) => void;
|
|
33
|
+
readonly inkwebview_destroy: (a: number) => void;
|
|
34
|
+
readonly inkwebview_dispatchInput: (a: number, b: number, c: number, d: number, e: number, f: number, g: number) => void;
|
|
35
|
+
readonly inkwebview_dispatchPointer: (a: number, b: number, c: number, d: number, e: number, f: number, g: number, h: number, i: number, j: number) => void;
|
|
36
|
+
readonly inkwebview_focus: (a: number) => void;
|
|
37
|
+
readonly inkwebview_isRunning: (a: number) => number;
|
|
38
|
+
readonly inkwebview_new: (a: number, b: number, c: number) => number;
|
|
39
|
+
readonly inkwebview_notifyUserInteraction: (a: number) => void;
|
|
40
|
+
readonly inkwebview_openBundle: (a: number, b: number, c: number, d: number, e: number, f: number, g: number, h: number, i: number) => void;
|
|
41
|
+
readonly inkwebview_render: (a: number, b: number) => void;
|
|
42
|
+
readonly inkwebview_resize: (a: number, b: number, c: number, d: number) => void;
|
|
43
|
+
readonly main: () => void;
|
|
44
|
+
readonly __wasm_bindgen_func_elem_8675: (a: number, b: number) => void;
|
|
45
|
+
readonly __wasm_bindgen_func_elem_12871: (a: number, b: number) => void;
|
|
46
|
+
readonly __wasm_bindgen_func_elem_8855: (a: number, b: number, c: number) => void;
|
|
47
|
+
readonly __wasm_bindgen_func_elem_12872: (a: number, b: number, c: number) => void;
|
|
48
|
+
readonly __wasm_bindgen_func_elem_8856: (a: number, b: number) => void;
|
|
49
|
+
readonly __wbindgen_export: (a: number, b: number) => number;
|
|
50
|
+
readonly __wbindgen_export2: (a: number, b: number, c: number, d: number) => number;
|
|
51
|
+
readonly __wbindgen_export3: (a: number) => void;
|
|
52
|
+
readonly __wbindgen_export4: (a: number, b: number, c: number) => void;
|
|
53
|
+
readonly __wbindgen_add_to_stack_pointer: (a: number) => number;
|
|
54
|
+
readonly __wbindgen_start: () => void;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export type SyncInitInput = BufferSource | WebAssembly.Module;
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Instantiates the given `module`, which can either be bytes or
|
|
61
|
+
* a precompiled `WebAssembly.Module`.
|
|
62
|
+
*
|
|
63
|
+
* @param {{ module: SyncInitInput }} module - Passing `SyncInitInput` directly is deprecated.
|
|
64
|
+
*
|
|
65
|
+
* @returns {InitOutput}
|
|
66
|
+
*/
|
|
67
|
+
export function initSync(module: { module: SyncInitInput } | SyncInitInput): InitOutput;
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* If `module_or_path` is {RequestInfo} or {URL}, makes a request and
|
|
71
|
+
* for everything else, calls `WebAssembly.instantiate` directly.
|
|
72
|
+
*
|
|
73
|
+
* @param {{ module_or_path: InitInput | Promise<InitInput> }} module_or_path - Passing `InitInput` directly is deprecated.
|
|
74
|
+
*
|
|
75
|
+
* @returns {Promise<InitOutput>}
|
|
76
|
+
*/
|
|
77
|
+
export default function __wbg_init (module_or_path?: { module_or_path: InitInput | Promise<InitInput> } | InitInput | Promise<InitInput>): Promise<InitOutput>;
|