@monimesl/dot-cli 0.1.0-alpha.16 → 0.1.0-alpha.19

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 +16 -9
  2. package/dist/cli.js.map +1 -1
  3. package/dist/templates/showcase/dot.ts +32 -17
  4. package/dist/templates/showcase/pages/basics/bars/controller.ts +36 -0
  5. package/dist/templates/showcase/pages/basics/bars/state.ts +6 -0
  6. package/dist/templates/showcase/pages/basics/bars/view.dml +28 -0
  7. package/dist/templates/showcase/pages/{text → basics/bindings}/controller.ts +5 -0
  8. package/dist/templates/showcase/pages/basics/bindings/state.ts +14 -0
  9. package/dist/templates/showcase/pages/basics/bindings/view.dml +41 -0
  10. package/dist/templates/showcase/pages/basics/control-flow/controller.ts +18 -0
  11. package/dist/templates/showcase/pages/basics/control-flow/state.ts +16 -0
  12. package/dist/templates/showcase/pages/basics/control-flow/view.dml +55 -0
  13. package/dist/templates/showcase/pages/basics/events/controller.ts +43 -0
  14. package/dist/templates/showcase/pages/basics/events/state.ts +11 -0
  15. package/dist/templates/showcase/pages/basics/events/view.dml +51 -0
  16. package/dist/templates/showcase/pages/basics/hooks/controller.ts +37 -0
  17. package/dist/templates/showcase/pages/basics/hooks/state.ts +10 -0
  18. package/dist/templates/showcase/pages/basics/hooks/view.dml +32 -0
  19. package/dist/templates/showcase/pages/basics/navigation/controller.ts +34 -0
  20. package/dist/templates/showcase/pages/basics/navigation/state.ts +9 -0
  21. package/dist/templates/showcase/pages/basics/navigation/view.dml +37 -0
  22. package/dist/templates/showcase/pages/{state → basics/state}/controller.ts +1 -0
  23. package/dist/templates/showcase/pages/basics/state/view.dml +46 -0
  24. package/dist/templates/showcase/pages/{carousel → components/carousel}/controller.ts +1 -0
  25. package/dist/templates/showcase/pages/{carousel → components/carousel}/view.dml +7 -5
  26. package/dist/templates/showcase/pages/{feedback → components/feedback}/controller.ts +1 -0
  27. package/dist/templates/showcase/pages/{feedback → components/feedback}/view.dml +18 -9
  28. package/dist/templates/showcase/pages/{forms → components/forms}/controller.ts +5 -1
  29. package/dist/templates/showcase/pages/{forms → components/forms}/state.ts +3 -0
  30. package/dist/templates/showcase/pages/components/forms/view.dml +90 -0
  31. package/dist/templates/showcase/pages/components/icons/controller.ts +15 -0
  32. package/dist/templates/showcase/pages/components/icons/state.ts +75 -0
  33. package/dist/templates/showcase/pages/components/icons/view.dml +36 -0
  34. package/dist/templates/showcase/pages/{images → components/images}/controller.ts +1 -0
  35. package/dist/templates/showcase/pages/components/images/view.dml +70 -0
  36. package/dist/templates/showcase/pages/{layout → components/layout}/controller.ts +1 -0
  37. package/dist/templates/showcase/pages/components/layout/view.dml +143 -0
  38. package/dist/templates/showcase/pages/components/lists/controller.ts +18 -0
  39. package/dist/templates/showcase/pages/{lists → components/lists}/state.ts +43 -30
  40. package/dist/templates/showcase/pages/components/lists/view.dml +70 -0
  41. package/dist/templates/showcase/pages/{payments → components/money}/controller.ts +1 -0
  42. package/dist/templates/showcase/pages/{payments → components/money}/view.dml +17 -8
  43. package/dist/templates/showcase/pages/components/reading/controller.ts +6 -0
  44. package/dist/templates/showcase/pages/components/reading/state.ts +23 -0
  45. package/dist/templates/showcase/pages/components/reading/view.dml +42 -0
  46. package/dist/templates/showcase/pages/{lists → components/sheets}/controller.ts +9 -10
  47. package/dist/templates/showcase/pages/components/sheets/state.ts +26 -0
  48. package/dist/templates/showcase/pages/components/sheets/view.dml +38 -0
  49. package/dist/templates/showcase/pages/{styles → components/styles}/controller.ts +1 -0
  50. package/dist/templates/showcase/pages/components/styles/view.dml +101 -0
  51. package/dist/templates/showcase/pages/{filters → components/tabs}/controller.ts +2 -0
  52. package/dist/templates/showcase/pages/{filters → components/tabs}/state.ts +1 -0
  53. package/dist/templates/showcase/pages/components/tabs/view.dml +56 -0
  54. package/dist/templates/showcase/pages/components/text/controller.ts +10 -0
  55. package/dist/templates/showcase/pages/components/text/state.ts +3 -0
  56. package/dist/templates/showcase/pages/components/text/view.dml +95 -0
  57. package/dist/templates/showcase/pages/{cache → dash/cache}/controller.ts +5 -0
  58. package/dist/templates/showcase/pages/dash/cache/view.dml +48 -0
  59. package/dist/templates/showcase/pages/dash/calls/controller.ts +49 -0
  60. package/dist/templates/showcase/pages/dash/calls/state.ts +6 -0
  61. package/dist/templates/showcase/pages/dash/calls/view.dml +51 -0
  62. package/dist/templates/showcase/pages/{files → dash/files}/controller.ts +1 -0
  63. package/dist/templates/showcase/pages/dash/files/view.dml +79 -0
  64. package/dist/templates/showcase/pages/dash/media/controller.ts +33 -0
  65. package/dist/templates/showcase/pages/dash/media/state.ts +8 -0
  66. package/dist/templates/showcase/pages/dash/media/view.dml +34 -0
  67. package/dist/templates/showcase/pages/{network → dash/network}/controller.ts +14 -5
  68. package/dist/templates/showcase/pages/{network → dash/network}/state.ts +4 -1
  69. package/dist/templates/showcase/pages/dash/network/view.dml +80 -0
  70. package/dist/templates/showcase/pages/{secrets → dash/secrets}/controller.ts +1 -0
  71. package/dist/templates/showcase/pages/dash/secrets/view.dml +26 -0
  72. package/dist/templates/showcase/pages/dash/system/controller.ts +62 -0
  73. package/dist/templates/showcase/pages/dash/system/state.ts +9 -0
  74. package/dist/templates/showcase/pages/dash/system/view.dml +59 -0
  75. package/dist/templates/showcase/pages/dash/ui/controller.ts +70 -0
  76. package/dist/templates/showcase/pages/dash/ui/state.ts +8 -0
  77. package/dist/templates/showcase/pages/dash/ui/view.dml +31 -0
  78. package/dist/templates/showcase/pages/index/state.ts +135 -64
  79. package/dist/templates/showcase/pages/index/view.dml +6 -3
  80. package/package.json +6 -6
  81. package/dist/templates/showcase/pages/cache/view.dml +0 -36
  82. package/dist/templates/showcase/pages/device/controller.ts +0 -105
  83. package/dist/templates/showcase/pages/device/state.ts +0 -16
  84. package/dist/templates/showcase/pages/device/view.dml +0 -69
  85. package/dist/templates/showcase/pages/files/view.dml +0 -64
  86. package/dist/templates/showcase/pages/filters/view.dml +0 -44
  87. package/dist/templates/showcase/pages/forms/view.dml +0 -49
  88. package/dist/templates/showcase/pages/images/view.dml +0 -53
  89. package/dist/templates/showcase/pages/layout/view.dml +0 -103
  90. package/dist/templates/showcase/pages/lists/view.dml +0 -59
  91. package/dist/templates/showcase/pages/navigation/controller.ts +0 -65
  92. package/dist/templates/showcase/pages/navigation/state.ts +0 -17
  93. package/dist/templates/showcase/pages/navigation/view.dml +0 -34
  94. package/dist/templates/showcase/pages/network/view.dml +0 -67
  95. package/dist/templates/showcase/pages/secrets/view.dml +0 -24
  96. package/dist/templates/showcase/pages/state/view.dml +0 -33
  97. package/dist/templates/showcase/pages/styles/view.dml +0 -61
  98. package/dist/templates/showcase/pages/text/state.ts +0 -8
  99. package/dist/templates/showcase/pages/text/view.dml +0 -58
  100. /package/dist/templates/showcase/pages/{state → basics/state}/state.ts +0 -0
  101. /package/dist/templates/showcase/pages/{carousel → components/carousel}/state.ts +0 -0
  102. /package/dist/templates/showcase/pages/{feedback → components/feedback}/state.ts +0 -0
  103. /package/dist/templates/showcase/pages/{images → components/images}/state.ts +0 -0
  104. /package/dist/templates/showcase/pages/{layout → components/layout}/state.ts +0 -0
  105. /package/dist/templates/showcase/pages/{payments → components/money}/state.ts +0 -0
  106. /package/dist/templates/showcase/pages/{styles → components/styles}/state.ts +0 -0
  107. /package/dist/templates/showcase/pages/{cache → dash/cache}/state.ts +0 -0
  108. /package/dist/templates/showcase/pages/{files → dash/files}/state.ts +0 -0
  109. /package/dist/templates/showcase/pages/{secrets → dash/secrets}/state.ts +0 -0
@@ -0,0 +1,49 @@
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
+ configs: { navigationBar: { title: 'Calls and errors' } },
12
+ actions: {
13
+ // Makes the call a button names, and shows how it ended.
14
+ async show(_state, event: DotEvent<unknown, { call: string }>) {
15
+ this.setState({ code: '', message: '' });
16
+ try {
17
+ switch (event.data.call) {
18
+ case 'timeout':
19
+ // Gives up after 1 second on a wait of 5.
20
+ await this.dash.system.delay(5000, { timeout: 1000 });
21
+ break;
22
+ case 'cancelled':
23
+ wait = this.dash.system.delay(10_000);
24
+ this.setState({ waiting: true });
25
+ await wait;
26
+ break;
27
+ case 'invalid':
28
+ await this.dash.ui.pickDate({ min: '2026-12-31', max: '2026-01-01' });
29
+ break;
30
+ case 'denied':
31
+ await this.dash.network.http.request({ url: 'https://example.com' });
32
+ break;
33
+ }
34
+ this.setState({ code: 'none', message: 'It worked' });
35
+ } catch (error) {
36
+ if (error instanceof HostError) {
37
+ this.setState({ code: error.code, message: error.message });
38
+ } else {
39
+ this.setState({ code: 'not a HostError', message: (error as Error).message });
40
+ }
41
+ }
42
+ wait = null;
43
+ this.setState({ waiting: false });
44
+ },
45
+ cancelWait() {
46
+ wait?.cancel();
47
+ },
48
+ },
49
+ });
@@ -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>
@@ -7,6 +7,7 @@ const PICTURE = 'pictures/artwork.jpg';
7
7
 
8
8
  export default definePage({
9
9
  initialState: state,
10
+ configs: { navigationBar: { title: 'dash.storage.file' } },
10
11
  hooks: {
11
12
  async onShow() {
12
13
  await this.actions.refresh();
@@ -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,33 @@
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
+ configs: { navigationBar: { title: 'dash.media' } },
14
+ actions: {
15
+ async pickPhoto(_state, event: DotEvent<unknown, { source: 'library' | 'camera' }>) {
16
+ try {
17
+ const photo = await this.dash.media.pickPhoto({ source: event.data.source });
18
+ const details = `${photo.name}, ${Math.round(photo.size / 1024)} KB as a JPEG`;
19
+ this.setState({ photo, photoDetails: details, note: '' });
20
+ } catch (error) {
21
+ this.setState({ note: problemToShow(error) });
22
+ }
23
+ },
24
+ async scanTicket() {
25
+ try {
26
+ const { text } = await this.dash.media.scanCode();
27
+ this.setState({ scanned: text, note: '' });
28
+ } catch (error) {
29
+ this.setState({ note: problemToShow(error) });
30
+ }
31
+ },
32
+ },
33
+ });
@@ -0,0 +1,8 @@
1
+ import type { DotFile } from '@monimesl/dot-runtime';
2
+
3
+ export default {
4
+ photo: null as DotFile | null,
5
+ photoDetails: '',
6
+ scanned: '',
7
+ note: '',
8
+ };
@@ -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>
@@ -30,6 +30,7 @@ function searchUrl(artist: string, limit = PAGE): string {
30
30
 
31
31
  export default definePage({
32
32
  initialState: state,
33
+ configs: { navigationBar: { title: 'dash.network.http' } },
33
34
  mutations: {
34
35
  setArtist: (_state, event: DotEvent<{ value: string }>) => ({ artist: event.detail.value }),
35
36
  },
@@ -103,8 +104,7 @@ export default definePage({
103
104
  name: 'artwork.jpg',
104
105
  });
105
106
  if (response.ok) {
106
- const { id, url, size } = response.file;
107
- this.setState({ picture: { id, url, size }, kept: false });
107
+ this.setState({ picture: response.file, kept: false });
108
108
  } else {
109
109
  this.setState({ note: `Apple answered ${response.status}` });
110
110
  }
@@ -113,11 +113,14 @@ export default definePage({
113
113
  }
114
114
  },
115
115
  async upload(state) {
116
+ if (!state.picture) {
117
+ return;
118
+ }
116
119
  try {
117
120
  const { body } = await this.dash.network.http.upload<Echo>({
118
121
  url: 'https://httpbin.org/post',
119
122
  fields: { artist: state.artist },
120
- files: [{ field: 'artwork', file: state.picture.id }],
123
+ files: [{ field: 'artwork', file: state.picture }],
121
124
  as: 'json',
122
125
  });
123
126
  this.setState({ uploaded: Object.keys(body.files).join(', ') });
@@ -126,15 +129,21 @@ export default definePage({
126
129
  }
127
130
  },
128
131
  async sharePicture(state) {
132
+ if (!state.picture) {
133
+ return;
134
+ }
129
135
  try {
130
- await this.dash.system.share({ file: state.picture.id });
136
+ await this.dash.system.share({ file: state.picture });
131
137
  } catch (error) {
132
138
  this.setState({ note: (error as Error).message });
133
139
  }
134
140
  },
135
141
  async keep(state) {
142
+ if (!state.picture) {
143
+ return;
144
+ }
136
145
  try {
137
- await this.dash.storage.file.save(state.picture.id, PICTURE, { overwrite: true });
146
+ await this.dash.storage.file.save(state.picture, PICTURE, { overwrite: true });
138
147
  this.setState({ kept: true });
139
148
  } catch (error) {
140
149
  this.setState({ note: (error as Error).message });
@@ -1,3 +1,5 @@
1
+ import type { DotFile } from '@monimesl/dot-runtime';
2
+
1
3
  export interface Release {
2
4
  id: number;
3
5
  title: string;
@@ -15,7 +17,8 @@ export default {
15
17
  limit: 10,
16
18
  searching: false,
17
19
  echo: '',
18
- picture: { id: '', url: '', size: 0 },
20
+ // The downloaded artwork, in a temporary file until the dot closes.
21
+ picture: null as DotFile | null,
19
22
  uploaded: '',
20
23
  kept: false,
21
24
  note: '',
@@ -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 }}">Upload</Button>
52
+ <Button variant="secondary" onClick="sharePicture" disabled="{{ !picture }}">Share</Button>
53
+ <Button variant="secondary" onClick="keep" disabled="{{ !picture || kept }}">Keep</Button>
54
+ </View>
55
+ <If condition="{{ picture }}">
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>
@@ -5,6 +5,7 @@ const NAME = 'session-name';
5
5
 
6
6
  export default definePage({
7
7
  initialState: state,
8
+ configs: { navigationBar: { title: 'dash.storage.secret' } },
8
9
  hooks: {
9
10
  async onShow() {
10
11
  await this.actions.refresh();
@@ -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>
@@ -0,0 +1,62 @@
1
+ import type { VibrationStyle } from '@monimesl/dot-runtime';
2
+
3
+ import state from './state';
4
+
5
+ export default definePage({
6
+ initialState: state,
7
+ configs: { navigationBar: { title: 'dash.system' } },
8
+ mutations: {
9
+ clearToast: () => ({ toast: '' }),
10
+ },
11
+ actions: {
12
+ async vibrate(_state, event: DotEvent<unknown, { style: VibrationStyle }>) {
13
+ await this.dash.system.vibrate({ style: event.data.style });
14
+ },
15
+ async copySong() {
16
+ await this.dash.system.copyText('Exodus, by Drizilik');
17
+ this.setState({ toast: 'Song copied' });
18
+ },
19
+ async shareText() {
20
+ try {
21
+ await this.dash.system.share({
22
+ text: 'Salone Sounds Live, Freetown, 24 October. See you there!',
23
+ });
24
+ } catch (error) {
25
+ this.setState({ note: (error as Error).message });
26
+ }
27
+ },
28
+ async shareLink() {
29
+ try {
30
+ await this.dash.system.share({
31
+ title: 'Music of Sierra Leone',
32
+ url: 'https://en.wikipedia.org/wiki/Music_of_Sierra_Leone',
33
+ });
34
+ } catch (error) {
35
+ this.setState({ note: (error as Error).message });
36
+ }
37
+ },
38
+ async openDrizilik() {
39
+ try {
40
+ await this.dash.system.openUrl('https://drizilik.com/');
41
+ } catch (error) {
42
+ this.setState({ note: (error as Error).message });
43
+ }
44
+ },
45
+ async countDown() {
46
+ for (let seconds = 10; seconds > 0; seconds--) {
47
+ this.setState({ showIn: seconds });
48
+ await this.dash.system.delay(1000);
49
+ }
50
+ this.setState({ showIn: 0, toast: "It's showtime" });
51
+ },
52
+ async toggleTorch(state) {
53
+ const next = state.torch === 'on' ? 'off' : 'on';
54
+ try {
55
+ await this.dash.system.torch(next);
56
+ this.setState({ torch: next, note: '' });
57
+ } catch (error) {
58
+ this.setState({ note: (error as Error).message });
59
+ }
60
+ },
61
+ },
62
+ });
@@ -0,0 +1,9 @@
1
+ import type { VibrationStyle } from '@monimesl/dot-runtime';
2
+
3
+ export default {
4
+ styles: ['light', 'medium', 'heavy', 'success', 'warning', 'error'] as VibrationStyle[],
5
+ torch: 'off' as 'on' | 'off',
6
+ showIn: 0,
7
+ note: '',
8
+ toast: '',
9
+ };
@@ -0,0 +1,59 @@
1
+ <View>
2
+ <Text variant="title">dash.system</Text>
3
+ <Text>The phone's own features: vibration, the clipboard, sharing, the browser, timers and the torch.</Text>
4
+
5
+ <View gap="small">
6
+ <Text variant="subtitle">vibrate</Text>
7
+ <Text variant="caption" tone="muted">A short buzz, in a style that suits what happened.</Text>
8
+ <View variant="card" direction="row" wrap gap="small">
9
+ <For each="{{ styles }}" item="style" key="*this">
10
+ <Button variant="secondary" data-style="{{ style }}" onClick="vibrate">{{ style }}</Button>
11
+ </For>
12
+ </View>
13
+ </View>
14
+
15
+ <View gap="small">
16
+ <Text variant="subtitle">copyText</Text>
17
+ <View variant="card">
18
+ <Button variant="secondary" onClick="copySong">Copy a song's name</Button>
19
+ </View>
20
+ </View>
21
+
22
+ <View gap="small">
23
+ <Text variant="subtitle">share</Text>
24
+ <Text variant="caption" tone="muted">Opens the phone's share sheet with text, a link or a file. The dash.network page shares a file.</Text>
25
+ <View variant="card" direction="row" wrap gap="small">
26
+ <Button variant="secondary" onClick="shareText">Share text</Button>
27
+ <Button variant="secondary" onClick="shareLink">Share a link</Button>
28
+ </View>
29
+ </View>
30
+
31
+ <View gap="small">
32
+ <Text variant="subtitle">openUrl</Text>
33
+ <Text variant="caption" tone="muted">Opens a web page in the phone's browser.</Text>
34
+ <View variant="card">
35
+ <Button variant="secondary" onClick="openDrizilik">Open Drizilik's website</Button>
36
+ </View>
37
+ </View>
38
+
39
+ <View gap="small">
40
+ <Text variant="subtitle">delay</Text>
41
+ <Text variant="caption" tone="muted">Waits for a number of milliseconds. Dash keeps time, so it's cancelled when the page closes.</Text>
42
+ <View variant="card">
43
+ <Button variant="secondary" onClick="countDown" disabled="{{ showIn > 0 }}">{{ showIn > 0 ? 'Showtime in ' + showIn + 's' : 'Count down to showtime' }}</Button>
44
+ </View>
45
+ </View>
46
+
47
+ <View gap="small">
48
+ <Text variant="subtitle">torch</Text>
49
+ <Text variant="caption" tone="muted">Turns the camera's light on or off.</Text>
50
+ <View variant="card">
51
+ <Button variant="secondary" onClick="toggleTorch">{{ torch === 'on' ? 'Turn the torch off' : 'Lights up for the slow song' }}</Button>
52
+ </View>
53
+ </View>
54
+
55
+ <If condition="{{ note }}">
56
+ <Notice tone="danger">{{ note }}</Notice>
57
+ </If>
58
+ <Toast message="{{ toast }}" tone="success" onHide="clearToast" />
59
+ </View>