@monimesl/dot-simulator 0.1.0-alpha.10

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 ADDED
@@ -0,0 +1,20 @@
1
+ # @monimesl/dot-simulator
2
+
3
+ The browser host for dots, used during development. `dotctl dev` starts it. It runs the service
4
+ bundle in a Worker and shows the webview in a phone-sized frame. It relays messages between them,
5
+ owns the navigation stack, and logs every message. See [docs/hosts.md](../../../docs/hosts.md).
6
+
7
+ ```ts
8
+ import { startSimulator } from '@monimesl/dot-simulator';
9
+
10
+ const simulator = await startSimulator({ dotDir: 'my-dot/dist', name: 'My dot', port: 3680 });
11
+ simulator.reload(); // restart the dot after a rebuild
12
+ simulator.error('view.dml:3:7: ...'); // show a build error over the phone
13
+ ```
14
+
15
+ - `src/server.ts` serves the simulator page, the dot's bundles and the reload event stream. It
16
+ injects the bridge into the webview's `index.html`, and runs Dash's network and storage calls.
17
+ - `src/host.ts` has `startHost()`, which does what every host does. It relays messages, owns the
18
+ navigation stack (`NavigationStack` from core) and announces it with `syncStack`.
19
+ - `src/client/` holds the browser scripts. `simulator.js` is the page, and `webview-bridge.js` and
20
+ `service-bridge.js` install `__hostTransport` in each layer.
@@ -0,0 +1,647 @@
1
+ (function() {
2
+ //#region ../../core/dist/index.js
3
+ function defineComponent(manifest) {
4
+ return {
5
+ props: {},
6
+ events: [],
7
+ ...manifest
8
+ };
9
+ }
10
+ function componentRegistry(manifests) {
11
+ const registry = {};
12
+ for (const manifest of manifests) {
13
+ if (manifest.name in registry) throw new Error(`Component <${manifest.name}> is defined twice`);
14
+ registry[manifest.name] = manifest;
15
+ }
16
+ return registry;
17
+ }
18
+ const AmountInput = defineComponent({
19
+ name: "AmountInput",
20
+ props: {
21
+ value: "number",
22
+ currency: "string",
23
+ minorUnits: "number",
24
+ unit: ["minor", "major"],
25
+ label: "string",
26
+ placeholder: "string",
27
+ hint: "string",
28
+ error: "string",
29
+ disabled: "boolean"
30
+ },
31
+ events: ["change"],
32
+ children: "none"
33
+ });
34
+ const Button = defineComponent({
35
+ name: "Button",
36
+ props: {
37
+ variant: ["primary", "secondary"],
38
+ disabled: "boolean",
39
+ loading: "boolean"
40
+ },
41
+ events: ["click"],
42
+ children: "text"
43
+ });
44
+ const SIZES = [
45
+ "small",
46
+ "medium",
47
+ "large"
48
+ ];
49
+ const SPACES = [
50
+ "none",
51
+ "small",
52
+ "medium",
53
+ "large"
54
+ ];
55
+ const PADDING_PROPS = {
56
+ padding: SPACES,
57
+ paddingTop: SPACES,
58
+ paddingStart: SPACES,
59
+ paddingEnd: SPACES,
60
+ paddingBottom: SPACES
61
+ };
62
+ const ASPECTS = [
63
+ "1:1",
64
+ "4:3",
65
+ "3:2",
66
+ "16:9",
67
+ "3:4",
68
+ "2:3",
69
+ "9:16"
70
+ ];
71
+ const TONES = [
72
+ "default",
73
+ "muted",
74
+ "info",
75
+ "success",
76
+ "warning",
77
+ "danger"
78
+ ];
79
+ const STATUS_TONES = [
80
+ "default",
81
+ "info",
82
+ "success",
83
+ "warning",
84
+ "danger"
85
+ ];
86
+ const ICON_NAMES = [
87
+ "arrow-down",
88
+ "arrow-left",
89
+ "arrow-right",
90
+ "arrow-up",
91
+ "bank",
92
+ "bell",
93
+ "bolt",
94
+ "calendar",
95
+ "card",
96
+ "cart",
97
+ "cash",
98
+ "check",
99
+ "chevron-down",
100
+ "chevron-left",
101
+ "chevron-right",
102
+ "chevron-up",
103
+ "clock",
104
+ "close",
105
+ "copy",
106
+ "download",
107
+ "edit",
108
+ "error",
109
+ "external-link",
110
+ "eye",
111
+ "eye-off",
112
+ "file",
113
+ "gift",
114
+ "globe",
115
+ "grid",
116
+ "headphones",
117
+ "heart",
118
+ "help",
119
+ "history",
120
+ "home",
121
+ "image",
122
+ "info",
123
+ "link",
124
+ "list",
125
+ "location",
126
+ "lock",
127
+ "logout",
128
+ "mail",
129
+ "microphone",
130
+ "minus",
131
+ "mobile",
132
+ "more",
133
+ "music",
134
+ "phone",
135
+ "playlist",
136
+ "plus",
137
+ "qr-code",
138
+ "receipt",
139
+ "refresh",
140
+ "search",
141
+ "send",
142
+ "settings",
143
+ "share",
144
+ "star",
145
+ "success",
146
+ "ticket",
147
+ "trash",
148
+ "upload",
149
+ "user",
150
+ "users",
151
+ "video",
152
+ "wallet",
153
+ "warning",
154
+ "water",
155
+ "wifi"
156
+ ];
157
+ const Carousel = defineComponent({
158
+ name: "Carousel",
159
+ props: {
160
+ autoplay: "boolean",
161
+ interval: "number",
162
+ loop: "boolean",
163
+ indicator: ["dots", "none"],
164
+ gap: SPACES,
165
+ effect: ["slide", "scale"],
166
+ index: "number",
167
+ ...PADDING_PROPS
168
+ },
169
+ events: ["change"],
170
+ children: "elements"
171
+ });
172
+ const Chip = defineComponent({
173
+ name: "Chip",
174
+ props: {
175
+ label: "string",
176
+ icon: ICON_NAMES,
177
+ selected: "boolean",
178
+ disabled: "boolean"
179
+ },
180
+ events: ["tap"],
181
+ children: "none"
182
+ });
183
+ const CodeInput = defineComponent({
184
+ name: "CodeInput",
185
+ props: {
186
+ value: "string",
187
+ length: "number",
188
+ label: "string",
189
+ hint: "string",
190
+ error: "string",
191
+ secure: "boolean",
192
+ disabled: "boolean"
193
+ },
194
+ events: ["change", "complete"],
195
+ children: "none"
196
+ });
197
+ const DetailRow = defineComponent({
198
+ name: "DetailRow",
199
+ props: {
200
+ label: "string",
201
+ value: "string",
202
+ tone: TONES,
203
+ weight: [
204
+ "regular",
205
+ "medium",
206
+ "bold"
207
+ ],
208
+ ...PADDING_PROPS
209
+ },
210
+ children: "none"
211
+ });
212
+ const Divider = defineComponent({
213
+ name: "Divider",
214
+ children: "none"
215
+ });
216
+ const EmptyState = defineComponent({
217
+ name: "EmptyState",
218
+ props: {
219
+ icon: ICON_NAMES,
220
+ title: "string",
221
+ text: "string",
222
+ ...PADDING_PROPS
223
+ },
224
+ children: "elements"
225
+ });
226
+ const Spinner = defineComponent({
227
+ name: "Spinner",
228
+ props: { size: SIZES },
229
+ children: "none"
230
+ });
231
+ const ProgressBar = defineComponent({
232
+ name: "ProgressBar",
233
+ props: { value: "number" },
234
+ children: "none"
235
+ });
236
+ const Notice = defineComponent({
237
+ name: "Notice",
238
+ props: {
239
+ tone: STATUS_TONES,
240
+ title: "string",
241
+ ...PADDING_PROPS
242
+ },
243
+ children: "text"
244
+ });
245
+ const Badge = defineComponent({
246
+ name: "Badge",
247
+ props: { tone: STATUS_TONES },
248
+ children: "text"
249
+ });
250
+ const Footer = defineComponent({
251
+ name: "Footer",
252
+ props: PADDING_PROPS,
253
+ children: "elements"
254
+ });
255
+ const Grid = defineComponent({
256
+ name: "Grid",
257
+ props: {
258
+ columns: "number",
259
+ gap: SPACES,
260
+ ...PADDING_PROPS
261
+ },
262
+ children: "elements"
263
+ });
264
+ const GridItem = defineComponent({
265
+ name: "GridItem",
266
+ props: {
267
+ label: "string",
268
+ icon: ICON_NAMES,
269
+ image: "string",
270
+ badge: "string"
271
+ },
272
+ events: ["tap"],
273
+ children: "none"
274
+ });
275
+ const Icon = defineComponent({
276
+ name: "Icon",
277
+ props: {
278
+ name: ICON_NAMES,
279
+ size: SIZES,
280
+ tone: TONES
281
+ },
282
+ children: "none",
283
+ inline: true
284
+ });
285
+ const Image = defineComponent({
286
+ name: "Image",
287
+ props: {
288
+ src: "string",
289
+ alt: "string",
290
+ width: "number",
291
+ height: "number",
292
+ fit: ["cover", "contain"],
293
+ shape: [
294
+ "rounded",
295
+ "square",
296
+ "circle"
297
+ ],
298
+ aspect: ASPECTS
299
+ },
300
+ events: ["load", "error"],
301
+ children: "none",
302
+ overrides: [{
303
+ prop: "aspect",
304
+ ignores: "height",
305
+ because: "the aspect sets the height"
306
+ }]
307
+ });
308
+ const Input = defineComponent({
309
+ name: "Input",
310
+ props: {
311
+ value: "string",
312
+ placeholder: "string",
313
+ label: "string",
314
+ hint: "string",
315
+ error: "string",
316
+ prefix: "string",
317
+ suffix: "string",
318
+ type: [
319
+ "text",
320
+ "number",
321
+ "email",
322
+ "phone",
323
+ "password"
324
+ ],
325
+ maxLength: "number",
326
+ disabled: "boolean"
327
+ },
328
+ events: ["change", "submit"],
329
+ children: "none"
330
+ });
331
+ const Link = defineComponent({
332
+ name: "Link",
333
+ props: {
334
+ href: "string",
335
+ tone: TONES
336
+ },
337
+ events: ["tap"],
338
+ children: "text",
339
+ inline: true
340
+ });
341
+ const List = defineComponent({
342
+ name: "List",
343
+ props: {
344
+ size: ["sm", "md"],
345
+ ...PADDING_PROPS
346
+ },
347
+ events: ["endReached"],
348
+ children: "elements"
349
+ });
350
+ const ListItem = defineComponent({
351
+ name: "ListItem",
352
+ props: {
353
+ title: "string",
354
+ subtitle: "string",
355
+ value: "string",
356
+ icon: ICON_NAMES,
357
+ image: "string",
358
+ ...PADDING_PROPS
359
+ },
360
+ events: ["tap"],
361
+ children: "elements"
362
+ });
363
+ const Sheet = defineComponent({
364
+ name: "Sheet",
365
+ props: {
366
+ open: "boolean",
367
+ title: "string",
368
+ showCloseIcon: "boolean",
369
+ ...PADDING_PROPS
370
+ },
371
+ events: ["close"],
372
+ children: "elements"
373
+ });
374
+ const Toast = defineComponent({
375
+ name: "Toast",
376
+ props: {
377
+ message: "string",
378
+ tone: STATUS_TONES,
379
+ duration: "number"
380
+ },
381
+ events: ["hide"],
382
+ children: "none"
383
+ });
384
+ const QRCode = defineComponent({
385
+ name: "QRCode",
386
+ props: {
387
+ value: "string",
388
+ size: "number",
389
+ label: "string"
390
+ },
391
+ children: "none"
392
+ });
393
+ const Select = defineComponent({
394
+ name: "Select",
395
+ props: {
396
+ value: "string",
397
+ label: "string",
398
+ placeholder: "string",
399
+ hint: "string",
400
+ error: "string",
401
+ disabled: "boolean"
402
+ },
403
+ events: ["change"],
404
+ children: "elements"
405
+ });
406
+ const Option = defineComponent({
407
+ name: "Option",
408
+ props: {
409
+ value: "string",
410
+ label: "string"
411
+ },
412
+ children: "none"
413
+ });
414
+ const Scroll = defineComponent({
415
+ name: "Scroll",
416
+ props: {
417
+ gap: SPACES,
418
+ snap: [
419
+ "none",
420
+ "start",
421
+ "center"
422
+ ],
423
+ index: "number",
424
+ ...PADDING_PROPS
425
+ },
426
+ events: ["change"],
427
+ children: "elements"
428
+ });
429
+ const Skeleton = defineComponent({
430
+ name: "Skeleton",
431
+ props: {
432
+ shape: [
433
+ "text",
434
+ "circle",
435
+ "box"
436
+ ],
437
+ lines: "number",
438
+ height: "number"
439
+ },
440
+ children: "none"
441
+ });
442
+ const Tabs = defineComponent({
443
+ name: "Tabs",
444
+ props: {
445
+ value: "string",
446
+ variant: ["underline", "segmented"],
447
+ ...PADDING_PROPS
448
+ },
449
+ events: ["change"],
450
+ children: "elements"
451
+ });
452
+ const Tab = defineComponent({
453
+ name: "Tab",
454
+ props: {
455
+ value: "string",
456
+ label: "string",
457
+ badge: "number"
458
+ },
459
+ children: "none"
460
+ });
461
+ const Text = defineComponent({
462
+ name: "Text",
463
+ props: {
464
+ variant: [
465
+ "title",
466
+ "body",
467
+ "caption"
468
+ ],
469
+ tone: TONES,
470
+ weight: [
471
+ "regular",
472
+ "medium",
473
+ "bold"
474
+ ],
475
+ align: [
476
+ "start",
477
+ "center",
478
+ "end"
479
+ ],
480
+ maxLines: "number",
481
+ ...PADDING_PROPS
482
+ },
483
+ children: "text",
484
+ inline: true
485
+ });
486
+ const TextArea = defineComponent({
487
+ name: "TextArea",
488
+ props: {
489
+ value: "string",
490
+ placeholder: "string",
491
+ label: "string",
492
+ hint: "string",
493
+ error: "string",
494
+ rows: "number",
495
+ maxLength: "number",
496
+ disabled: "boolean"
497
+ },
498
+ events: ["change"],
499
+ children: "none"
500
+ });
501
+ const Switch = defineComponent({
502
+ name: "Switch",
503
+ props: {
504
+ checked: "boolean",
505
+ label: "string",
506
+ disabled: "boolean"
507
+ },
508
+ events: ["change"],
509
+ children: "none"
510
+ });
511
+ const Checkbox = defineComponent({
512
+ name: "Checkbox",
513
+ props: {
514
+ checked: "boolean",
515
+ label: "string",
516
+ disabled: "boolean"
517
+ },
518
+ events: ["change"],
519
+ children: "none"
520
+ });
521
+ const RadioGroup = defineComponent({
522
+ name: "RadioGroup",
523
+ props: {
524
+ value: "string",
525
+ label: "string",
526
+ disabled: "boolean"
527
+ },
528
+ events: ["change"],
529
+ children: "elements"
530
+ });
531
+ const builtinComponents = {
532
+ AmountInput,
533
+ Badge,
534
+ Button,
535
+ Carousel,
536
+ Checkbox,
537
+ Chip,
538
+ CodeInput,
539
+ DetailRow,
540
+ Divider,
541
+ EmptyState,
542
+ Footer,
543
+ Grid,
544
+ GridItem,
545
+ Icon,
546
+ Image,
547
+ Input,
548
+ Link,
549
+ List,
550
+ ListItem,
551
+ Notice,
552
+ Option,
553
+ ProgressBar,
554
+ QRCode,
555
+ Radio: defineComponent({
556
+ name: "Radio",
557
+ props: {
558
+ value: "string",
559
+ label: "string",
560
+ disabled: "boolean"
561
+ },
562
+ children: "none"
563
+ }),
564
+ RadioGroup,
565
+ Scroll,
566
+ Select,
567
+ Sheet,
568
+ Skeleton,
569
+ Spinner,
570
+ Switch,
571
+ Tab,
572
+ Tabs,
573
+ Text,
574
+ TextArea,
575
+ Toast,
576
+ View: defineComponent({
577
+ name: "View",
578
+ props: {
579
+ gap: SPACES,
580
+ direction: ["column", "row"],
581
+ align: [
582
+ "stretch",
583
+ "start",
584
+ "center",
585
+ "end"
586
+ ],
587
+ justify: [
588
+ "start",
589
+ "center",
590
+ "end",
591
+ "between"
592
+ ],
593
+ wrap: "boolean",
594
+ grow: "boolean",
595
+ ...PADDING_PROPS,
596
+ variant: ["plain", "card"]
597
+ },
598
+ events: ["tap"],
599
+ children: "elements"
600
+ })
601
+ };
602
+ componentRegistry(Object.values(builtinComponents));
603
+ const LABEL = "[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?";
604
+ new RegExp(`^(?:\\*\\.${LABEL}(?:\\.${LABEL})+|${LABEL}(?:\\.${LABEL})*)$`);
605
+ function createAdaptorTransport(adapter) {
606
+ return {
607
+ post: (message) => adapter.postMessage(message),
608
+ listen(listener) {
609
+ const onMessage = (event) => listener(event.data);
610
+ adapter.addEventListener("message", onMessage);
611
+ return () => adapter.removeEventListener("message", onMessage);
612
+ }
613
+ };
614
+ }
615
+ const SERVICE_URL = `/dot/logic/dot.js`;
616
+ //#endregion
617
+ //#region src/client/hot-reload.ts
618
+ function hotReloadIn(data) {
619
+ const reload = data?.dotHotReload;
620
+ return typeof reload === "number" ? reload : void 0;
621
+ }
622
+ //#endregion
623
+ //#region src/client/wire.ts
624
+ function isWire(data) {
625
+ return typeof data?.dot === "string";
626
+ }
627
+ function wireTransport(channel) {
628
+ return {
629
+ post: (message) => channel.post({ dot: JSON.stringify(message) }),
630
+ listen: (listener) => channel.listen((data) => {
631
+ if (isWire(data)) listener(JSON.parse(data.dot));
632
+ })
633
+ };
634
+ }
635
+ //#endregion
636
+ //#region src/client/service-bridge.ts
637
+ const scope = globalThis;
638
+ globalThis.__hostTransport = wireTransport(createAdaptorTransport(scope));
639
+ scope.importScripts(SERVICE_URL);
640
+ scope.addEventListener("message", ({ data }) => {
641
+ const reload = hotReloadIn(data);
642
+ if (reload !== void 0) scope.importScripts(`${SERVICE_URL}?hot=${reload}`);
643
+ });
644
+ //#endregion
645
+ })();
646
+
647
+ //# sourceMappingURL=service-bridge.js.map