@monimesl/dot-cli 0.1.0-alpha.15 → 0.1.0-alpha.18

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.
Files changed (109) hide show
  1. package/dist/cli.js +17 -10
  2. package/dist/cli.js.map +1 -1
  3. package/dist/{server-Dbrgkelo.js → server-uIyYmYnp.js} +7 -2
  4. package/dist/{server-Dbrgkelo.js.map → server-uIyYmYnp.js.map} +1 -1
  5. package/dist/templates/showcase/dot.ts +42 -16
  6. package/dist/templates/showcase/pages/basics/bars/controller.ts +36 -0
  7. package/dist/templates/showcase/pages/basics/bars/state.ts +6 -0
  8. package/dist/templates/showcase/pages/basics/bars/view.dml +28 -0
  9. package/dist/templates/showcase/pages/{text → basics/bindings}/controller.ts +4 -0
  10. package/dist/templates/showcase/pages/basics/bindings/state.ts +14 -0
  11. package/dist/templates/showcase/pages/basics/bindings/view.dml +41 -0
  12. package/dist/templates/showcase/pages/basics/control-flow/controller.ts +17 -0
  13. package/dist/templates/showcase/pages/basics/control-flow/state.ts +16 -0
  14. package/dist/templates/showcase/pages/basics/control-flow/view.dml +55 -0
  15. package/dist/templates/showcase/pages/basics/events/controller.ts +42 -0
  16. package/dist/templates/showcase/pages/basics/events/state.ts +11 -0
  17. package/dist/templates/showcase/pages/basics/events/view.dml +51 -0
  18. package/dist/templates/showcase/pages/basics/hooks/controller.ts +36 -0
  19. package/dist/templates/showcase/pages/basics/hooks/state.ts +10 -0
  20. package/dist/templates/showcase/pages/basics/hooks/view.dml +32 -0
  21. package/dist/templates/showcase/pages/basics/navigation/controller.ts +33 -0
  22. package/dist/templates/showcase/pages/basics/navigation/state.ts +9 -0
  23. package/dist/templates/showcase/pages/basics/navigation/view.dml +37 -0
  24. package/dist/templates/showcase/pages/basics/state/view.dml +46 -0
  25. package/dist/templates/showcase/pages/{carousel → components/carousel}/view.dml +7 -5
  26. package/dist/templates/showcase/pages/{feedback → components/feedback}/view.dml +18 -9
  27. package/dist/templates/showcase/pages/{forms → components/forms}/controller.ts +4 -1
  28. package/dist/templates/showcase/pages/{forms → components/forms}/state.ts +3 -0
  29. package/dist/templates/showcase/pages/components/forms/view.dml +90 -0
  30. package/dist/templates/showcase/pages/components/icons/controller.ts +14 -0
  31. package/dist/templates/showcase/pages/components/icons/state.ts +75 -0
  32. package/dist/templates/showcase/pages/components/icons/view.dml +36 -0
  33. package/dist/templates/showcase/pages/components/images/view.dml +70 -0
  34. package/dist/templates/showcase/pages/components/layout/view.dml +143 -0
  35. package/dist/templates/showcase/pages/components/lists/controller.ts +17 -0
  36. package/dist/templates/showcase/pages/{lists → components/lists}/state.ts +43 -30
  37. package/dist/templates/showcase/pages/components/lists/view.dml +70 -0
  38. package/dist/templates/showcase/pages/{payments → components/money}/view.dml +17 -8
  39. package/dist/templates/showcase/pages/components/reading/controller.ts +5 -0
  40. package/dist/templates/showcase/pages/components/reading/state.ts +23 -0
  41. package/dist/templates/showcase/pages/components/reading/view.dml +42 -0
  42. package/dist/templates/showcase/pages/{lists → components/sheets}/controller.ts +8 -10
  43. package/dist/templates/showcase/pages/components/sheets/state.ts +26 -0
  44. package/dist/templates/showcase/pages/components/sheets/view.dml +38 -0
  45. package/dist/templates/showcase/pages/components/styles/controller.ts +5 -0
  46. package/dist/templates/showcase/pages/components/styles/state.ts +3 -0
  47. package/dist/templates/showcase/pages/components/styles/view.dml +101 -0
  48. package/dist/templates/showcase/pages/{filters → components/tabs}/controller.ts +1 -0
  49. package/dist/templates/showcase/pages/{filters → components/tabs}/state.ts +1 -0
  50. package/dist/templates/showcase/pages/components/tabs/view.dml +56 -0
  51. package/dist/templates/showcase/pages/components/text/controller.ts +9 -0
  52. package/dist/templates/showcase/pages/components/text/state.ts +3 -0
  53. package/dist/templates/showcase/pages/components/text/view.dml +95 -0
  54. package/dist/templates/showcase/pages/{cache → dash/cache}/controller.ts +4 -0
  55. package/dist/templates/showcase/pages/dash/cache/view.dml +48 -0
  56. package/dist/templates/showcase/pages/dash/calls/controller.ts +48 -0
  57. package/dist/templates/showcase/pages/dash/calls/state.ts +6 -0
  58. package/dist/templates/showcase/pages/dash/calls/view.dml +51 -0
  59. package/dist/templates/showcase/pages/{files → dash/files}/controller.ts +23 -1
  60. package/dist/templates/showcase/pages/{files → dash/files}/state.ts +8 -0
  61. package/dist/templates/showcase/pages/dash/files/view.dml +79 -0
  62. package/dist/templates/showcase/pages/dash/media/controller.ts +32 -0
  63. package/dist/templates/showcase/pages/dash/media/state.ts +12 -0
  64. package/dist/templates/showcase/pages/dash/media/view.dml +34 -0
  65. package/dist/templates/showcase/pages/dash/network/view.dml +80 -0
  66. package/dist/templates/showcase/pages/dash/secrets/view.dml +26 -0
  67. package/dist/templates/showcase/pages/{device → dash/system}/controller.ts +11 -30
  68. package/dist/templates/showcase/pages/dash/system/state.ts +9 -0
  69. package/dist/templates/showcase/pages/dash/system/view.dml +59 -0
  70. package/dist/templates/showcase/pages/dash/ui/controller.ts +69 -0
  71. package/dist/templates/showcase/pages/dash/ui/state.ts +8 -0
  72. package/dist/templates/showcase/pages/dash/ui/view.dml +31 -0
  73. package/dist/templates/showcase/pages/index/state.ts +137 -60
  74. package/dist/templates/showcase/pages/index/view.dml +6 -3
  75. package/package.json +6 -6
  76. package/dist/templates/showcase/pages/cache/view.dml +0 -36
  77. package/dist/templates/showcase/pages/device/state.ts +0 -7
  78. package/dist/templates/showcase/pages/device/view.dml +0 -49
  79. package/dist/templates/showcase/pages/files/view.dml +0 -50
  80. package/dist/templates/showcase/pages/filters/view.dml +0 -44
  81. package/dist/templates/showcase/pages/forms/view.dml +0 -49
  82. package/dist/templates/showcase/pages/images/view.dml +0 -53
  83. package/dist/templates/showcase/pages/layout/view.dml +0 -103
  84. package/dist/templates/showcase/pages/lists/view.dml +0 -59
  85. package/dist/templates/showcase/pages/navigation/controller.ts +0 -65
  86. package/dist/templates/showcase/pages/navigation/state.ts +0 -17
  87. package/dist/templates/showcase/pages/navigation/view.dml +0 -34
  88. package/dist/templates/showcase/pages/network/view.dml +0 -67
  89. package/dist/templates/showcase/pages/secrets/view.dml +0 -24
  90. package/dist/templates/showcase/pages/state/view.dml +0 -33
  91. package/dist/templates/showcase/pages/text/state.ts +0 -8
  92. package/dist/templates/showcase/pages/text/view.dml +0 -58
  93. /package/dist/templates/showcase/pages/{state → basics/state}/controller.ts +0 -0
  94. /package/dist/templates/showcase/pages/{state → basics/state}/state.ts +0 -0
  95. /package/dist/templates/showcase/pages/{carousel → components/carousel}/controller.ts +0 -0
  96. /package/dist/templates/showcase/pages/{carousel → components/carousel}/state.ts +0 -0
  97. /package/dist/templates/showcase/pages/{feedback → components/feedback}/controller.ts +0 -0
  98. /package/dist/templates/showcase/pages/{feedback → components/feedback}/state.ts +0 -0
  99. /package/dist/templates/showcase/pages/{images → components/images}/controller.ts +0 -0
  100. /package/dist/templates/showcase/pages/{images → components/images}/state.ts +0 -0
  101. /package/dist/templates/showcase/pages/{layout → components/layout}/controller.ts +0 -0
  102. /package/dist/templates/showcase/pages/{layout → components/layout}/state.ts +0 -0
  103. /package/dist/templates/showcase/pages/{payments → components/money}/controller.ts +0 -0
  104. /package/dist/templates/showcase/pages/{payments → components/money}/state.ts +0 -0
  105. /package/dist/templates/showcase/pages/{cache → dash/cache}/state.ts +0 -0
  106. /package/dist/templates/showcase/pages/{network → dash/network}/controller.ts +0 -0
  107. /package/dist/templates/showcase/pages/{network → dash/network}/state.ts +0 -0
  108. /package/dist/templates/showcase/pages/{secrets → dash/secrets}/controller.ts +0 -0
  109. /package/dist/templates/showcase/pages/{secrets → dash/secrets}/state.ts +0 -0
@@ -0,0 +1,95 @@
1
+ <View>
2
+ <Text variant="title">Text and links</Text>
3
+ <Text>Text shows words. Its variant sets the size, and the theme sets the font and colours.</Text>
4
+
5
+ <View gap="small">
6
+ <Text variant="subtitle">variant</Text>
7
+ <View variant="card" gap="small">
8
+ <Text variant="display" numeric>SLE 1,250</Text>
9
+ <Text variant="title">Palm-wine, a title</Text>
10
+ <Text variant="subtitle">S. E. Rogie, a subtitle</Text>
11
+ <Text variant="lead">A lead opens an article. It's a little bigger than body text.</Text>
12
+ <Text variant="body">Guitar music for a calm evening. This is body text, the default.</Text>
13
+ <Text variant="caption">Recorded in Freetown, in a caption</Text>
14
+ </View>
15
+ </View>
16
+
17
+ <View gap="small">
18
+ <Text variant="subtitle">tone</Text>
19
+ <View variant="card" direction="row" wrap gap="medium">
20
+ <Text tone="default">default</Text>
21
+ <Text tone="muted">muted</Text>
22
+ <Text tone="info">info</Text>
23
+ <Text tone="success">success</Text>
24
+ <Text tone="warning">warning</Text>
25
+ <Text tone="danger">danger</Text>
26
+ </View>
27
+ </View>
28
+
29
+ <View gap="small">
30
+ <Text variant="subtitle">weight, italic and decoration</Text>
31
+ <View variant="card" gap="small">
32
+ <View direction="row" wrap gap="medium">
33
+ <Text weight="regular">regular</Text>
34
+ <Text weight="medium">medium</Text>
35
+ <Text weight="bold">bold</Text>
36
+ <Text italic>italic</Text>
37
+ </View>
38
+ <View direction="row" wrap gap="medium">
39
+ <Text decoration="underline">underline</Text>
40
+ <Text decoration="strikethrough">strikethrough</Text>
41
+ <Text decoration="none">none</Text>
42
+ </View>
43
+ </View>
44
+ </View>
45
+
46
+ <View gap="small">
47
+ <Text variant="subtitle">align and numeric</Text>
48
+ <Text variant="caption" tone="muted">numeric gives every digit the same width, so amounts line up.</Text>
49
+ <View variant="card" gap="small">
50
+ <Text align="start">At the start</Text>
51
+ <Text align="center">In the center</Text>
52
+ <Text align="end">At the end</Text>
53
+ <Text align="end" numeric>SLE 1,111.11</Text>
54
+ <Text align="end" numeric>SLE 8,888.88</Text>
55
+ </View>
56
+ </View>
57
+
58
+ <View gap="small">
59
+ <Text variant="subtitle">maxLines</Text>
60
+ <Text variant="caption" tone="muted">Cuts the text off after this many lines.</Text>
61
+ <View variant="card">
62
+ <Text maxLines="2">Sierra Leone's Refugee All Stars came together in refugee camps in Guinea, recorded their first album, Living Like a Refugee, back in Freetown, and carried Salone music to stages around the world. Two lines are allowed here, so the rest is cut off.</Text>
63
+ </View>
64
+ </View>
65
+
66
+ <View gap="small">
67
+ <Text variant="subtitle">leading and selectable</Text>
68
+ <Text variant="caption" tone="muted">leading moves the lines closer or further apart. selectable lets the person copy the text: hold your finger on the last one.</Text>
69
+ <View variant="card" gap="medium">
70
+ <Text leading="tight">Tight lines. Palm-wine music grew in the bars of Freetown, played on guitar and sung in Krio.</Text>
71
+ <Text leading="normal">Normal lines. Palm-wine music grew in the bars of Freetown, played on guitar and sung in Krio.</Text>
72
+ <Text leading="relaxed" selectable>Relaxed lines, and you can select them. Palm-wine music grew in the bars of Freetown, played on guitar and sung in Krio.</Text>
73
+ </View>
74
+ </View>
75
+
76
+ <View gap="small">
77
+ <Text variant="subtitle">Text inside text</Text>
78
+ <View variant="card">
79
+ <Text>Text can hold <Text weight="bold">bold</Text> and <Text tone="danger">red</Text> words.</Text>
80
+ </View>
81
+ </View>
82
+
83
+ <View gap="small">
84
+ <Text variant="subtitle">Link</Text>
85
+ <Text variant="caption" tone="muted">With href, a link opens a web page in the phone's browser. With onTap, it runs a handler.</Text>
86
+ <View variant="card" gap="small">
87
+ <Text>Read about <Link href="https://en.wikipedia.org/wiki/Music_of_Sierra_Leone">the music of Sierra Leone</Link>.</Text>
88
+ <View direction="row" gap="medium">
89
+ <Link onTap="like">Like the song</Link>
90
+ <Link tone="danger" onTap="unlike">Take it back</Link>
91
+ </View>
92
+ <Text tone="muted">{{ likes }} {{ likes === 1 ? 'like' : 'likes' }}</Text>
93
+ </View>
94
+ </View>
95
+ </View>
@@ -37,6 +37,10 @@ export default definePage({
37
37
  await this.dash.storage.cache.set(GENRE, event.detail.value);
38
38
  await this.actions.refresh();
39
39
  },
40
+ async forgetGenre() {
41
+ await this.dash.storage.cache.remove(GENRE);
42
+ await this.actions.refresh();
43
+ },
40
44
  async clear() {
41
45
  await this.dash.storage.cache.clear();
42
46
  await this.actions.refresh();
@@ -0,0 +1,48 @@
1
+ <View>
2
+ <Text variant="title">dash.storage.cache</Text>
3
+ <Text>The cache keeps small values by name, such as text, numbers or JSON. A value can expire after a while, or stay until it's removed.</Text>
4
+
5
+ <View gap="small">
6
+ <Text variant="subtitle">set with a ttl, keys and get</Text>
7
+ <Text variant="caption" tone="muted">Tap an artist. ttl: 30 keeps it for 30 seconds.</Text>
8
+ <List>
9
+ <For each="{{ artists }}" item="artist" key="id">
10
+ <ListItem icon="microphone" title="{{ artist.name }}" data-id="{{ artist.id }}" data-name="{{ artist.name }}" onTap="view" />
11
+ </For>
12
+ </List>
13
+ <View variant="card" gap="small">
14
+ <If condition="{{ recent.length > 0 }}">
15
+ <For each="{{ recent }}" item="name" key="*this">
16
+ <Text>{{ name }}</Text>
17
+ </For>
18
+ </If>
19
+ <Else>
20
+ <Text variant="caption" tone="muted">No artists viewed in the last 30 seconds</Text>
21
+ </Else>
22
+ <Button variant="secondary" onClick="refresh">Check again</Button>
23
+ </View>
24
+ </View>
25
+
26
+ <View gap="small">
27
+ <Text variant="subtitle">set without a ttl, and exists</Text>
28
+ <Text variant="caption" tone="muted">Your favourite genre stays until it's removed, even after the dot closes.</Text>
29
+ <View variant="card">
30
+ <Select label="Genre" placeholder="Choose a genre" value="{{ genre }}" onChange="setGenre">
31
+ <Option value="Afropop" label="Afropop" />
32
+ <Option value="Palm-wine" label="Palm-wine" />
33
+ <Option value="Gumbe" label="Gumbe" />
34
+ </Select>
35
+ </View>
36
+ </View>
37
+
38
+ <View gap="small">
39
+ <Text variant="subtitle">remove and clear</Text>
40
+ <View variant="card" gap="small">
41
+ <Text>Keys in the cache: {{ keys || 'none' }}</Text>
42
+ <View direction="row" wrap gap="small">
43
+ <Button variant="secondary" onClick="forgetGenre" disabled="{{ !genre }}">Forget the genre</Button>
44
+ <Button variant="secondary" onClick="clear" disabled="{{ !keys }}">Clear the cache</Button>
45
+ </View>
46
+ </View>
47
+ </View>
48
+ </View>
@@ -0,0 +1,48 @@
1
+ import type { DashRequest } from '@monimesl/dot-runtime';
2
+ import { HostError } from '@monimesl/dot-runtime';
3
+
4
+ import state from './state';
5
+
6
+ // The long wait, kept so the Cancel button can stop it.
7
+ let wait: DashRequest<void> | null = null;
8
+
9
+ export default definePage({
10
+ initialState: state,
11
+ actions: {
12
+ // Makes the call a button names, and shows how it ended.
13
+ async show(_state, event: DotEvent<unknown, { call: string }>) {
14
+ this.setState({ code: '', message: '' });
15
+ try {
16
+ switch (event.data.call) {
17
+ case 'timeout':
18
+ // Gives up after 1 second on a wait of 5.
19
+ await this.dash.system.delay(5000, { timeout: 1000 });
20
+ break;
21
+ case 'cancelled':
22
+ wait = this.dash.system.delay(10_000);
23
+ this.setState({ waiting: true });
24
+ await wait;
25
+ break;
26
+ case 'invalid':
27
+ await this.dash.ui.pickDate({ min: '2026-12-31', max: '2026-01-01' });
28
+ break;
29
+ case 'denied':
30
+ await this.dash.network.http.request({ url: 'https://example.com' });
31
+ break;
32
+ }
33
+ this.setState({ code: 'none', message: 'It worked' });
34
+ } catch (error) {
35
+ if (error instanceof HostError) {
36
+ this.setState({ code: error.code, message: error.message });
37
+ } else {
38
+ this.setState({ code: 'not a HostError', message: (error as Error).message });
39
+ }
40
+ }
41
+ wait = null;
42
+ this.setState({ waiting: false });
43
+ },
44
+ cancelWait() {
45
+ wait?.cancel();
46
+ },
47
+ },
48
+ });
@@ -0,0 +1,6 @@
1
+ export default {
2
+ waiting: false,
3
+ // The last call's outcome: the HostError's code and message, or how it ended.
4
+ code: '',
5
+ message: '',
6
+ };
@@ -0,0 +1,51 @@
1
+ <View>
2
+ <Text variant="title">Calls and errors</Text>
3
+ <Text>Every this.dash call goes to Dash and comes back later, so await it. When it fails, it throws a HostError with a code that says why.</Text>
4
+
5
+ <View gap="small">
6
+ <Text variant="subtitle">timeout</Text>
7
+ <Text variant="caption" tone="muted">The last argument of a call can set a timeout, in milliseconds. This waits 5 seconds with a timeout of 1.</Text>
8
+ <Button variant="secondary" data-call="timeout" onClick="show">Wait too long</Button>
9
+ </View>
10
+
11
+ <View gap="small">
12
+ <Text variant="subtitle">cancel</Text>
13
+ <Text variant="caption" tone="muted">A call can be cancelled while it runs. Calls still running when the page closes are cancelled for you.</Text>
14
+ <View direction="row" gap="small">
15
+ <Button variant="secondary" data-call="cancelled" onClick="show" disabled="{{ waiting }}">Wait 10 seconds</Button>
16
+ <Button variant="secondary" onClick="cancelWait" disabled="{{ !waiting }}">Cancel</Button>
17
+ </View>
18
+ </View>
19
+
20
+ <View gap="small">
21
+ <Text variant="subtitle">HostError codes</Text>
22
+ <View variant="card" gap="none">
23
+ <DetailRow label="cancelled" value="The person closed it, or the call was cancelled" />
24
+ <DetailRow label="timeout" value="It took longer than the timeout" />
25
+ <DetailRow label="denied" value="Not allowed, such as a missing permission" />
26
+ <DetailRow label="invalid" value="The call's options are wrong" />
27
+ <DetailRow label="unsupported" value="This Dash can't do it" />
28
+ <DetailRow label="failed" value="Anything else went wrong" />
29
+ </View>
30
+ <View direction="row" wrap gap="small">
31
+ <Button variant="secondary" data-call="invalid" onClick="show">Pick a date between bad limits</Button>
32
+ <Button variant="secondary" data-call="denied" onClick="show">Reach an unlisted host</Button>
33
+ </View>
34
+ </View>
35
+
36
+ <View gap="small">
37
+ <Text variant="subtitle">The last call</Text>
38
+ <View variant="card" gap="none">
39
+ <If condition="{{ code }}">
40
+ <DetailRow label="error.code" value="{{ code }}" weight="bold" />
41
+ <DetailRow label="error.message" value="{{ message }}" />
42
+ </If>
43
+ <ElseIf condition="{{ waiting }}">
44
+ <Text tone="muted">Waiting…</Text>
45
+ </ElseIf>
46
+ <Else>
47
+ <Text tone="muted">Use a button above</Text>
48
+ </Else>
49
+ </View>
50
+ </View>
51
+ </View>
@@ -1,4 +1,6 @@
1
- import type { Archive, Song } from './state';
1
+ import { HostError } from '@monimesl/dot-runtime';
2
+
3
+ import type { Archive, Document, Song } from './state';
2
4
  import state from './state';
3
5
 
4
6
  const PICTURE = 'pictures/artwork.jpg';
@@ -29,11 +31,15 @@ export default definePage({
29
31
  time: new Date(Number(entry.name)).toLocaleTimeString('en-GB'),
30
32
  }))
31
33
  .reverse();
34
+ const documents: Document[] = entries
35
+ .filter((entry) => entry.kind === 'file' && entry.path.startsWith('documents/'))
36
+ .map((entry) => ({ path: entry.path, name: entry.name }));
32
37
  const { used, quota } = await this.dash.storage.file.usage();
33
38
  const kept = await this.dash.storage.file.stat(PICTURE);
34
39
  this.setState({
35
40
  songs,
36
41
  archives,
42
+ documents,
37
43
  usage: `${used} bytes used of ${quota / 1024 / 1024} MB`,
38
44
  picture: kept ? (await this.dash.storage.file.open(PICTURE)).url : '',
39
45
  });
@@ -58,6 +64,22 @@ export default definePage({
58
64
  await this.dash.storage.file.remove('setlist', { recursive: true });
59
65
  await this.actions.refresh();
60
66
  },
67
+ // A picked file goes when the dot closes, so it's saved into the dot's own folder.
68
+ async attach() {
69
+ try {
70
+ const file = await this.dash.storage.file.pick({ types: ['application/pdf', 'image/*'] });
71
+ await this.dash.storage.file.createFolder('documents');
72
+ await this.dash.storage.file.save(file, `documents/${file.name}`, { overwrite: true });
73
+ this.setState({ attachNote: '' });
74
+ await this.actions.refresh();
75
+ } catch (error) {
76
+ // Closing the picker or scanner isn't a problem to show.
77
+ const closedByPerson = error instanceof HostError && error.code === 'cancelled';
78
+ if (!closedByPerson) {
79
+ this.setState({ attachNote: (error as Error).message });
80
+ }
81
+ }
82
+ },
61
83
  async forgetPicture() {
62
84
  await this.dash.storage.file.remove(PICTURE);
63
85
  await this.actions.refresh();
@@ -4,6 +4,11 @@ export interface Archive {
4
4
  time: string;
5
5
  }
6
6
 
7
+ export interface Document {
8
+ path: string;
9
+ name: string;
10
+ }
11
+
7
12
  export interface Song {
8
13
  path: string;
9
14
  text: string;
@@ -16,4 +21,7 @@ export default {
16
21
  archives: [] as Archive[],
17
22
  usage: '',
18
23
  picture: '',
24
+ // Files the person attached, kept in the dot's documents folder.
25
+ documents: [] as Document[],
26
+ attachNote: '',
19
27
  };
@@ -0,0 +1,79 @@
1
+ <View>
2
+ <Text variant="title">dash.storage.file</Text>
3
+ <Text>Each dot has its own folder on the phone. Other dots can't see it, and its files stay after the dot closes.</Text>
4
+
5
+ <View gap="small">
6
+ <Text variant="subtitle">write, read and list</Text>
7
+ <Text variant="caption" tone="muted">A setlist, where each song is a text file. Add some, then close and reopen the dot: they are still here.</Text>
8
+ <View variant="card" gap="small">
9
+ <Input label="Song" placeholder="Exodus, by Drizilik" value="{{ draft }}" maxLength="80" onChange="setDraft" onSubmit="save" />
10
+ <View direction="row" gap="small" wrap>
11
+ <Button onClick="save" disabled="{{ !draft }}">Add to the setlist</Button>
12
+ <Button variant="secondary" onClick="archive" disabled="{{ songs.length === 0 }}">Archive it</Button>
13
+ <Button variant="secondary" onClick="clearSetlist" disabled="{{ songs.length === 0 }}">Clear it</Button>
14
+ </View>
15
+ <For each="{{ songs }}" item="song" key="path">
16
+ <View gap="none">
17
+ <Text>{{ song.text }}</Text>
18
+ <Text variant="caption" tone="muted">{{ song.path }}</Text>
19
+ </View>
20
+ </For>
21
+ <If condition="{{ songs.length === 0 }}">
22
+ <Text variant="caption" tone="muted">No songs yet</Text>
23
+ </If>
24
+ </View>
25
+ </View>
26
+
27
+ <View gap="small">
28
+ <Text variant="subtitle">createFolder, move, copy and remove</Text>
29
+ <Text variant="caption" tone="muted">Archive it moves the setlist folder into archive/. Tap an archived setlist to copy it back. Clear it removes the folder and all in it.</Text>
30
+ <View variant="card" gap="small">
31
+ <If condition="{{ archives.length > 0 }}">
32
+ <List>
33
+ <For each="{{ archives }}" item="archive" key="path">
34
+ <ListItem icon="playlist" title="Archived at {{ archive.time }}" subtitle="{{ archive.path }}" value="Copy back" data-path="{{ archive.path }}" onTap="copyBack" />
35
+ </For>
36
+ </List>
37
+ </If>
38
+ <Else>
39
+ <Text variant="caption" tone="muted">None yet. Add songs, then archive the setlist.</Text>
40
+ </Else>
41
+ </View>
42
+ </View>
43
+
44
+ <View gap="small">
45
+ <Text variant="subtitle">pick and save</Text>
46
+ <Text variant="caption" tone="muted">pick lets the person choose a file from the phone, with the files permission. A picked file goes when the dot closes, so save copies it into the dot's folder.</Text>
47
+ <View variant="card" gap="small">
48
+ <Button variant="secondary" onClick="attach">Attach your concert ticket</Button>
49
+ <For each="{{ documents }}" item="document" key="path">
50
+ <View gap="none">
51
+ <Text>{{ document.name }}</Text>
52
+ <Text variant="caption" tone="muted">{{ document.path }}</Text>
53
+ </View>
54
+ </For>
55
+ <If condition="{{ attachNote }}">
56
+ <Text variant="caption" tone="muted">{{ attachNote }}</Text>
57
+ </If>
58
+ </View>
59
+ </View>
60
+
61
+ <View gap="small">
62
+ <Text variant="subtitle">stat and open</Text>
63
+ <Text variant="caption" tone="muted">stat tells whether a file is there. open gives an address to show it with. This is the artwork kept on the dash.network page.</Text>
64
+ <View variant="card" gap="small">
65
+ <If condition="{{ picture }}">
66
+ <Image src="{{ picture }}" alt="Kept artwork" width="64" height="64" />
67
+ <Button variant="secondary" onClick="forgetPicture">Forget it</Button>
68
+ </If>
69
+ <Else>
70
+ <Text variant="caption" tone="muted">None yet. On the dash.network page, search, download the artwork and tap Keep.</Text>
71
+ </Else>
72
+ </View>
73
+ </View>
74
+
75
+ <View gap="small">
76
+ <Text variant="subtitle">usage</Text>
77
+ <Text>{{ usage }}</Text>
78
+ </View>
79
+ </View>
@@ -0,0 +1,32 @@
1
+ import { HostError } from '@monimesl/dot-runtime';
2
+
3
+ import state from './state';
4
+
5
+ // Closing a picker or scanner isn't a problem to show.
6
+ function problemToShow(error: unknown): string {
7
+ const closedByPerson = error instanceof HostError && error.code === 'cancelled';
8
+ return closedByPerson ? '' : (error as Error).message;
9
+ }
10
+
11
+ export default definePage({
12
+ initialState: state,
13
+ actions: {
14
+ async pickPhoto(_state, event: DotEvent<unknown, { source: 'library' | 'camera' }>) {
15
+ try {
16
+ const photo = await this.dash.media.pickPhoto({ source: event.data.source });
17
+ const details = `${photo.name}, ${Math.round(photo.size / 1024)} KB as a JPEG`;
18
+ this.setState({ photo, photoDetails: details, note: '' });
19
+ } catch (error) {
20
+ this.setState({ note: problemToShow(error) });
21
+ }
22
+ },
23
+ async scanTicket() {
24
+ try {
25
+ const { text } = await this.dash.media.scanCode();
26
+ this.setState({ scanned: text, note: '' });
27
+ } catch (error) {
28
+ this.setState({ note: problemToShow(error) });
29
+ }
30
+ },
31
+ },
32
+ });
@@ -0,0 +1,12 @@
1
+ export interface Photo {
2
+ name: string;
3
+ size: number;
4
+ url: string;
5
+ }
6
+
7
+ export default {
8
+ photo: null as Photo | null,
9
+ photoDetails: '',
10
+ scanned: '',
11
+ note: '',
12
+ };
@@ -0,0 +1,34 @@
1
+ <View>
2
+ <Text variant="title">dash.media</Text>
3
+ <Text>Photos and codes from the camera. Each needs a permission in dot.json, which Dash asks the person for the first time.</Text>
4
+
5
+ <View gap="small">
6
+ <Text variant="subtitle">pickPhoto</Text>
7
+ <Text variant="caption" tone="muted">source="library" needs the photoLibrary permission, and source="camera" the camera one. The photo comes back as a JPEG file.</Text>
8
+ <View variant="card" gap="small">
9
+ <View direction="row" wrap gap="small">
10
+ <Button variant="secondary" data-source="library" onClick="pickPhoto">Choose a photo</Button>
11
+ <Button variant="secondary" data-source="camera" onClick="pickPhoto">Take a photo</Button>
12
+ </View>
13
+ <If condition="{{ photo }}">
14
+ <Image src="{{ photo.url }}" alt="The photo you picked" aspect="4:3" fit="contain" />
15
+ <Text variant="caption" tone="muted">{{ photoDetails }}. It goes when the dot closes, unless the dot saves it with dash.storage.file.save.</Text>
16
+ </If>
17
+ </View>
18
+ </View>
19
+
20
+ <View gap="small">
21
+ <Text variant="subtitle">scanCode</Text>
22
+ <Text variant="caption" tone="muted">Opens Dash's scanner until it reads a QR code. It needs the camera permission.</Text>
23
+ <View variant="card" gap="small">
24
+ <Button variant="secondary" onClick="scanTicket">Scan a ticket's QR code</Button>
25
+ <If condition="{{ scanned }}">
26
+ <Text>{{ scanned }}</Text>
27
+ </If>
28
+ </View>
29
+ </View>
30
+
31
+ <If condition="{{ note }}">
32
+ <Notice tone="danger">{{ note }}</Notice>
33
+ </If>
34
+ </View>
@@ -0,0 +1,80 @@
1
+ <View>
2
+ <Text variant="title">dash.network.http</Text>
3
+ <Text>Dash sends the dot's requests, only to the hosts listed in dot.json. This page searches Apple's music catalog.</Text>
4
+
5
+ <View gap="small">
6
+ <Text variant="subtitle">request</Text>
7
+ <Text variant="caption" tone="muted">The answer comes as text, or as JSON with as: 'json'. timeout gives up after a while.</Text>
8
+ <View variant="card" gap="small">
9
+ <Input label="Artist" placeholder="Drizilik" value="{{ artist }}" maxLength="40" onChange="setArtist" onSubmit="search" />
10
+ <View direction="row" wrap gap="small">
11
+ <Button onClick="search" loading="{{ searching }}" disabled="{{ !artist }}">Search</Button>
12
+ <Button variant="secondary" onClick="searchAsText" disabled="{{ !artist }}">Read as text</Button>
13
+ </View>
14
+ <If condition="{{ raw }}">
15
+ <Text variant="caption" tone="muted">The answer as text starts with: {{ raw }}</Text>
16
+ </If>
17
+ </View>
18
+
19
+ <Text variant="caption" tone="muted">The same answer read as JSON, with artwork. Near the end, the list loads 10 more, up to 50. {{ releases.length }} so far.</Text>
20
+ <If condition="{{ releases.length > 0 }}">
21
+ <List onEndReached="loadMore">
22
+ <For each="{{ releases }}" item="release" key="id">
23
+ <ListItem image="{{ release.artwork }}" title="{{ release.title }}" subtitle="{{ release.album }} · {{ release.year }}" />
24
+ </For>
25
+ </List>
26
+ </If>
27
+ <Else>
28
+ <View variant="card">
29
+ <EmptyState icon="search" title="No songs yet" text="Search for an artist, such as Drizilik, Kao Denero or Emmerson." />
30
+ </View>
31
+ </Else>
32
+ </View>
33
+
34
+ <View gap="small">
35
+ <Text variant="subtitle">Sending JSON</Text>
36
+ <Text variant="caption" tone="muted">method: 'POST' with a body. httpbin sends back what it got.</Text>
37
+ <View variant="card" gap="small">
38
+ <Button variant="secondary" onClick="sendJson">Send the first song as JSON</Button>
39
+ <If condition="{{ echo }}">
40
+ <Text>httpbin got {{ echo }}</Text>
41
+ </If>
42
+ </View>
43
+ </View>
44
+
45
+ <View gap="small">
46
+ <Text variant="subtitle">download and upload</Text>
47
+ <Text variant="caption" tone="muted">download saves the answer as a file. upload sends files with form fields. Then share the file, or keep it in the dot's folder.</Text>
48
+ <View variant="card" gap="small">
49
+ <View direction="row" wrap gap="small">
50
+ <Button variant="secondary" onClick="download" disabled="{{ releases.length === 0 }}">Download</Button>
51
+ <Button variant="secondary" onClick="upload" disabled="{{ !picture.id }}">Upload</Button>
52
+ <Button variant="secondary" onClick="sharePicture" disabled="{{ !picture.id }}">Share</Button>
53
+ <Button variant="secondary" onClick="keep" disabled="{{ !picture.id || kept }}">Keep</Button>
54
+ </View>
55
+ <If condition="{{ picture.url }}">
56
+ <View direction="row" align="center" gap="medium">
57
+ <Image src="{{ picture.url }}" alt="Artwork" width="64" height="64" />
58
+ <Text variant="caption" tone="muted">{{ picture.size }} bytes, in a temporary file until the dot closes</Text>
59
+ </View>
60
+ </If>
61
+ <If condition="{{ uploaded }}">
62
+ <Text>httpbin got the file field "{{ uploaded }}"</Text>
63
+ </If>
64
+ <If condition="{{ kept }}">
65
+ <Text>Saved in the dot's folder. See the dash.storage.file page.</Text>
66
+ </If>
67
+ </View>
68
+ </View>
69
+
70
+ <View gap="small">
71
+ <Text variant="subtitle">Hosts dot.json doesn't list</Text>
72
+ <Text variant="caption" tone="muted">Dash turns the request down.</Text>
73
+ <View variant="card" gap="small">
74
+ <Button variant="secondary" onClick="tryUnlistedHost">Reach example.com</Button>
75
+ <If condition="{{ note }}">
76
+ <Text variant="caption" tone="muted">{{ note }}</Text>
77
+ </If>
78
+ </View>
79
+ </View>
80
+ </View>
@@ -0,0 +1,26 @@
1
+ <View>
2
+ <Text variant="title">dash.storage.secret</Text>
3
+ <Text>Secrets hold sign-in tokens from the dot's own server. Dash keeps them encrypted on the phone. A dot can keep up to 8, of up to 4 KB each.</Text>
4
+
5
+ <View gap="small">
6
+ <Text variant="subtitle">set, get, remove and clear</Text>
7
+ <Text variant="caption" tone="muted">A sign-in that stays. Sign out removes the token, and Forget every secret clears them all.</Text>
8
+ <View variant="card" gap="small">
9
+ <If condition="{{ token }}">
10
+ <Text>Signed in as {{ signedInAs }}</Text>
11
+ <Text variant="caption" tone="muted">Session token {{ token }}</Text>
12
+ <View direction="row" gap="small">
13
+ <Button variant="secondary" onClick="signOut">Sign out</Button>
14
+ <Button variant="secondary" onClick="forgetAll">Forget every secret</Button>
15
+ </View>
16
+ </If>
17
+ <Else>
18
+ <Input label="Name" placeholder="Mariama" value="{{ name }}" maxLength="40" onChange="setName" onSubmit="signIn" />
19
+ <Button onClick="signIn" disabled="{{ !name }}">Sign in</Button>
20
+ <Text variant="caption" tone="muted">Sign in, then close and reopen the dot. You stay signed in.</Text>
21
+ </Else>
22
+ </View>
23
+ </View>
24
+
25
+ <Notice tone="warning" title="Keep keys off the phone">Only the token is a secret here: your name is in the cache. Never put API keys in a dot's code or .env, since anyone with the dot can read them.</Notice>
26
+ </View>