@monimesl/dot-cli 0.1.0-alpha.9 → 1.0.0-beta.1

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 (143) hide show
  1. package/README.md +72 -59
  2. package/dist/cli.js +539 -341
  3. package/dist/cli.js.map +1 -1
  4. package/dist/{server-BfPAnsSf.js → server-DMnWDdJ6.js} +570 -478
  5. package/dist/server-DMnWDdJ6.js.map +1 -0
  6. package/dist/templates/showcase/dot.ts +53 -16
  7. package/dist/templates/showcase/pages/basics/bars/controller.ts +47 -0
  8. package/dist/templates/showcase/pages/basics/bars/state.ts +7 -0
  9. package/dist/templates/showcase/pages/basics/bars/view.dml +36 -0
  10. package/dist/templates/showcase/pages/{text → basics/bindings}/controller.ts +5 -0
  11. package/dist/templates/showcase/pages/basics/bindings/state.ts +14 -0
  12. package/dist/templates/showcase/pages/basics/bindings/view.dml +41 -0
  13. package/dist/templates/showcase/pages/basics/colors/controller.ts +112 -0
  14. package/dist/templates/showcase/pages/basics/colors/palette.ts +52 -0
  15. package/dist/templates/showcase/pages/basics/colors/state.ts +17 -0
  16. package/dist/templates/showcase/pages/basics/colors/view.dml +142 -0
  17. package/dist/templates/showcase/pages/basics/concert/controller.ts +16 -0
  18. package/dist/templates/showcase/pages/basics/concert/state.ts +7 -0
  19. package/dist/templates/showcase/pages/basics/concert/view.dml +20 -0
  20. package/dist/templates/showcase/pages/basics/control-flow/controller.ts +18 -0
  21. package/dist/templates/showcase/pages/basics/control-flow/state.ts +16 -0
  22. package/dist/templates/showcase/pages/basics/control-flow/view.dml +55 -0
  23. package/dist/templates/showcase/pages/basics/events/controller.ts +43 -0
  24. package/dist/templates/showcase/pages/basics/events/state.ts +11 -0
  25. package/dist/templates/showcase/pages/basics/events/view.dml +51 -0
  26. package/dist/templates/showcase/pages/basics/hooks/controller.ts +43 -0
  27. package/dist/templates/showcase/pages/basics/hooks/state.ts +10 -0
  28. package/dist/templates/showcase/pages/basics/hooks/view.dml +37 -0
  29. package/dist/templates/showcase/pages/basics/navigation/controller.ts +41 -0
  30. package/dist/templates/showcase/pages/basics/navigation/state.ts +11 -0
  31. package/dist/templates/showcase/pages/basics/navigation/view.dml +48 -0
  32. package/dist/templates/showcase/pages/basics/pick-genre/controller.ts +18 -0
  33. package/dist/templates/showcase/pages/basics/pick-genre/state.ts +3 -0
  34. package/dist/templates/showcase/pages/basics/pick-genre/view.dml +10 -0
  35. package/dist/templates/showcase/pages/basics/share/concerts.ts +31 -0
  36. package/dist/templates/showcase/pages/basics/share/controller.ts +12 -0
  37. package/dist/templates/showcase/pages/basics/share/state.ts +5 -0
  38. package/dist/templates/showcase/pages/basics/share/view.dml +21 -0
  39. package/dist/templates/showcase/pages/{state → basics/state}/controller.ts +1 -0
  40. package/dist/templates/showcase/pages/basics/state/view.dml +46 -0
  41. package/dist/templates/showcase/pages/{carousel → components/carousel}/controller.ts +2 -1
  42. package/dist/templates/showcase/pages/{carousel → components/carousel}/state.ts +5 -5
  43. package/dist/templates/showcase/pages/{carousel → components/carousel}/view.dml +7 -5
  44. package/dist/templates/showcase/pages/{feedback → components/feedback}/controller.ts +13 -2
  45. package/dist/templates/showcase/pages/components/feedback/state.ts +41 -0
  46. package/dist/templates/showcase/pages/{feedback → components/feedback}/view.dml +38 -9
  47. package/dist/templates/showcase/pages/{forms → components/forms}/controller.ts +23 -19
  48. package/dist/templates/showcase/pages/{forms → components/forms}/state.ts +6 -3
  49. package/dist/templates/showcase/pages/components/forms/view.dml +97 -0
  50. package/dist/templates/showcase/pages/components/icons/controller.ts +15 -0
  51. package/dist/templates/showcase/pages/components/icons/state.ts +75 -0
  52. package/dist/templates/showcase/pages/components/icons/view.dml +36 -0
  53. package/dist/templates/showcase/pages/{images → components/images}/controller.ts +4 -1
  54. package/dist/templates/showcase/pages/components/images/view.dml +83 -0
  55. package/dist/templates/showcase/pages/{layout → components/layout}/controller.ts +3 -2
  56. package/dist/templates/showcase/pages/components/layout/view.dml +143 -0
  57. package/dist/templates/showcase/pages/components/lists/controller.ts +18 -0
  58. package/dist/templates/showcase/pages/{lists → components/lists}/state.ts +43 -30
  59. package/dist/templates/showcase/pages/components/lists/view.dml +88 -0
  60. package/dist/templates/showcase/pages/{payments → components/money}/controller.ts +7 -8
  61. package/dist/templates/showcase/pages/components/money/view.dml +54 -0
  62. package/dist/templates/showcase/pages/components/reading/controller.ts +6 -0
  63. package/dist/templates/showcase/pages/components/reading/state.ts +23 -0
  64. package/dist/templates/showcase/pages/components/reading/view.dml +42 -0
  65. package/dist/templates/showcase/pages/{lists → components/sheets}/controller.ts +10 -11
  66. package/dist/templates/showcase/pages/components/sheets/state.ts +26 -0
  67. package/dist/templates/showcase/pages/components/sheets/view.dml +38 -0
  68. package/dist/templates/showcase/pages/components/styles/controller.ts +6 -0
  69. package/dist/templates/showcase/pages/components/styles/state.ts +3 -0
  70. package/dist/templates/showcase/pages/components/styles/view.dml +101 -0
  71. package/dist/templates/showcase/pages/components/tabs/controller.ts +41 -0
  72. package/dist/templates/showcase/pages/{filters → components/tabs}/state.ts +32 -23
  73. package/dist/templates/showcase/pages/components/tabs/view.dml +57 -0
  74. package/dist/templates/showcase/pages/components/text/controller.ts +10 -0
  75. package/dist/templates/showcase/pages/components/text/state.ts +3 -0
  76. package/dist/templates/showcase/pages/components/text/view.dml +95 -0
  77. package/dist/templates/showcase/pages/dash/account/controller.ts +32 -0
  78. package/dist/templates/showcase/pages/dash/account/state.ts +9 -0
  79. package/dist/templates/showcase/pages/dash/account/view.dml +33 -0
  80. package/dist/templates/showcase/pages/{cache → dash/cache}/controller.ts +9 -4
  81. package/dist/templates/showcase/pages/dash/cache/view.dml +48 -0
  82. package/dist/templates/showcase/pages/dash/calls/controller.ts +67 -0
  83. package/dist/templates/showcase/pages/dash/calls/state.ts +9 -0
  84. package/dist/templates/showcase/pages/dash/calls/view.dml +69 -0
  85. package/dist/templates/showcase/pages/dash/contacts/controller.ts +30 -0
  86. package/dist/templates/showcase/pages/dash/contacts/state.ts +6 -0
  87. package/dist/templates/showcase/pages/dash/contacts/view.dml +33 -0
  88. package/dist/templates/showcase/pages/dash/crypto/controller.ts +38 -0
  89. package/dist/templates/showcase/pages/dash/crypto/state.ts +10 -0
  90. package/dist/templates/showcase/pages/dash/crypto/view.dml +48 -0
  91. package/dist/templates/showcase/pages/{files → dash/files}/controller.ts +7 -2
  92. package/dist/templates/showcase/pages/{files → dash/files}/state.ts +4 -0
  93. package/dist/templates/showcase/pages/dash/files/view.dml +78 -0
  94. package/dist/templates/showcase/pages/dash/media/controller.ts +65 -0
  95. package/dist/templates/showcase/pages/dash/media/state.ts +12 -0
  96. package/dist/templates/showcase/pages/dash/media/view.dml +53 -0
  97. package/dist/templates/showcase/pages/{network → dash/network}/controller.ts +84 -20
  98. package/dist/templates/showcase/pages/{network → dash/network}/state.ts +14 -1
  99. package/dist/templates/showcase/pages/dash/network/view.dml +108 -0
  100. package/dist/templates/showcase/pages/dash/payments/cases.ts +445 -0
  101. package/dist/templates/showcase/pages/dash/payments/controller.ts +149 -0
  102. package/dist/templates/showcase/pages/dash/payments/state.ts +33 -0
  103. package/dist/templates/showcase/pages/dash/payments/view.dml +71 -0
  104. package/dist/templates/showcase/pages/{secrets → dash/secrets}/controller.ts +2 -1
  105. package/dist/templates/showcase/pages/dash/secrets/view.dml +26 -0
  106. package/dist/templates/showcase/pages/dash/system/controller.ts +89 -0
  107. package/dist/templates/showcase/pages/dash/system/state.ts +13 -0
  108. package/dist/templates/showcase/pages/dash/system/view.dml +96 -0
  109. package/dist/templates/showcase/pages/dash/ui/controller.ts +70 -0
  110. package/dist/templates/showcase/pages/dash/ui/state.ts +8 -0
  111. package/dist/templates/showcase/pages/dash/ui/view.dml +31 -0
  112. package/dist/templates/showcase/pages/index/controller.ts +1 -1
  113. package/dist/templates/showcase/pages/index/state.ts +175 -60
  114. package/dist/templates/showcase/pages/index/view.dml +6 -3
  115. package/package.json +6 -6
  116. package/dist/server-BfPAnsSf.js.map +0 -1
  117. package/dist/templates/showcase/pages/cache/view.dml +0 -36
  118. package/dist/templates/showcase/pages/device/controller.ts +0 -80
  119. package/dist/templates/showcase/pages/device/state.ts +0 -7
  120. package/dist/templates/showcase/pages/device/view.dml +0 -49
  121. package/dist/templates/showcase/pages/feedback/state.ts +0 -8
  122. package/dist/templates/showcase/pages/files/view.dml +0 -50
  123. package/dist/templates/showcase/pages/filters/controller.ts +0 -27
  124. package/dist/templates/showcase/pages/filters/view.dml +0 -44
  125. package/dist/templates/showcase/pages/forms/view.dml +0 -49
  126. package/dist/templates/showcase/pages/images/view.dml +0 -53
  127. package/dist/templates/showcase/pages/layout/view.dml +0 -103
  128. package/dist/templates/showcase/pages/lists/view.dml +0 -59
  129. package/dist/templates/showcase/pages/navigation/controller.ts +0 -65
  130. package/dist/templates/showcase/pages/navigation/state.ts +0 -17
  131. package/dist/templates/showcase/pages/navigation/view.dml +0 -34
  132. package/dist/templates/showcase/pages/network/view.dml +0 -67
  133. package/dist/templates/showcase/pages/payments/view.dml +0 -45
  134. package/dist/templates/showcase/pages/secrets/view.dml +0 -24
  135. package/dist/templates/showcase/pages/state/view.dml +0 -33
  136. package/dist/templates/showcase/pages/text/state.ts +0 -8
  137. package/dist/templates/showcase/pages/text/view.dml +0 -58
  138. /package/dist/templates/showcase/pages/{state → basics/state}/state.ts +0 -0
  139. /package/dist/templates/showcase/pages/{images → components/images}/state.ts +0 -0
  140. /package/dist/templates/showcase/pages/{layout → components/layout}/state.ts +0 -0
  141. /package/dist/templates/showcase/pages/{payments → components/money}/state.ts +0 -0
  142. /package/dist/templates/showcase/pages/{cache → dash/cache}/state.ts +0 -0
  143. /package/dist/templates/showcase/pages/{secrets → dash/secrets}/state.ts +0 -0
@@ -1,3 +1,5 @@
1
+ import { debounce, errorMessage, latest, unlessCancelled } from '@monimesl/dot-runtime';
2
+
1
3
  import type { Release } from './state';
2
4
  import state from './state';
3
5
 
@@ -14,6 +16,10 @@ interface CatalogAnswer {
14
16
  }[];
15
17
  }
16
18
 
19
+ interface ArtistAnswer {
20
+ results: { artistId: number; artistName: string; primaryGenreName?: string }[];
21
+ }
22
+
17
23
  interface Echo {
18
24
  json: unknown;
19
25
  files: Record<string, string>;
@@ -23,27 +29,76 @@ interface Echo {
23
29
  const PAGE = 10;
24
30
  const MOST = 50;
25
31
 
26
- function searchUrl(artist: string, limit = PAGE): string {
27
- const term = encodeURIComponent(artist);
28
- return `https://itunes.apple.com/search?entity=song&limit=${limit}&term=${term}`;
32
+ // Songs or artists whose names match `term`, at most `limit` of them.
33
+ function catalogUrl(entity: 'song' | 'musicArtist', term: string, limit: number): string {
34
+ return `https://itunes.apple.com/search?entity=${entity}&limit=${limit}&term=${encodeURIComponent(term)}`;
29
35
  }
30
36
 
37
+ // For searching as the person types: a pause in typing, and only the newest search's answer.
38
+ const typingPause = debounce(400);
39
+ const artistSearch = latest();
40
+
31
41
  export default definePage({
32
42
  initialState: state,
43
+ configs: { navigationBar: { title: 'Network' } },
33
44
  mutations: {
34
- setArtist: (_state, event: DotEvent<{ value: string }>) => ({ artist: event.detail.value }),
45
+ setArtist: (_state, event: ValueEvent) => ({ artist: event.detail.value }),
35
46
  },
36
47
  actions: {
48
+ async suggest(_state, event: ValueEvent) {
49
+ const typed = event.detail.value;
50
+ this.setState({ typed });
51
+ if (!typed.trim()) {
52
+ this.setState({ suggestions: [] });
53
+ return;
54
+ }
55
+ // False while the person is still typing: a later keystroke runs this again.
56
+ if (!(await typingPause(this.dash))) {
57
+ return;
58
+ }
59
+ try {
60
+ const answer = await unlessCancelled(
61
+ artistSearch(
62
+ this.dash.network.http.request<ArtistAnswer>({
63
+ url: catalogUrl('musicArtist', typed, 5),
64
+ as: 'json',
65
+ }),
66
+ ),
67
+ );
68
+ // Undefined when a newer search took its place.
69
+ if (answer) {
70
+ this.setState({
71
+ suggestions: answer.body.results.map((artist) => ({
72
+ id: artist.artistId,
73
+ name: artist.artistName,
74
+ genre: artist.primaryGenreName ?? '',
75
+ })),
76
+ });
77
+ }
78
+ } catch (error) {
79
+ this.setState({ note: errorMessage(error) });
80
+ }
81
+ },
82
+ async checkStatus() {
83
+ try {
84
+ const { online, type } = await this.dash.network.status();
85
+ this.setState({ connection: online ? `Online, by ${type}` : 'Offline' });
86
+ } catch (error) {
87
+ this.setState({ connection: errorMessage(error) });
88
+ }
89
+ },
37
90
  async searchAsText(state) {
38
91
  try {
39
- const response = await this.dash.network.http.request({ url: searchUrl(state.artist) });
92
+ const response = await this.dash.network.http.request({
93
+ url: catalogUrl('song', state.artist, PAGE),
94
+ });
40
95
  this.setState({
41
96
  raw: response.ok
42
97
  ? `${response.body.trim().slice(0, 90)}…`
43
98
  : `Apple answered ${response.status}`,
44
99
  });
45
100
  } catch (error) {
46
- this.setState({ note: (error as Error).message });
101
+ this.setState({ note: errorMessage(error) });
47
102
  }
48
103
  },
49
104
  async search() {
@@ -62,7 +117,7 @@ export default definePage({
62
117
  this.setState({ searching: true, note: '' });
63
118
  try {
64
119
  const { body } = await this.dash.network.http.request<CatalogAnswer>(
65
- { url: searchUrl(state.artist, state.limit), as: 'json' },
120
+ { url: catalogUrl('song', state.artist, state.limit), as: 'json' },
66
121
  { timeout: 10_000 },
67
122
  );
68
123
  const releases: Release[] = body.results.map((result) => ({
@@ -74,9 +129,10 @@ export default definePage({
74
129
  }));
75
130
  this.setState({ releases });
76
131
  } catch (error) {
77
- this.setState({ note: (error as Error).message });
132
+ this.setState({ note: errorMessage(error) });
133
+ } finally {
134
+ this.setState({ searching: false });
78
135
  }
79
- this.setState({ searching: false });
80
136
  },
81
137
  async sendJson(state) {
82
138
  try {
@@ -88,7 +144,7 @@ export default definePage({
88
144
  });
89
145
  this.setState({ echo: JSON.stringify(body.json) });
90
146
  } catch (error) {
91
- this.setState({ note: (error as Error).message });
147
+ this.setState({ note: errorMessage(error) });
92
148
  }
93
149
  },
94
150
  async download(state) {
@@ -103,48 +159,56 @@ export default definePage({
103
159
  name: 'artwork.jpg',
104
160
  });
105
161
  if (response.ok) {
106
- const { id, url, size } = response.file;
107
- this.setState({ picture: { id, url, size }, kept: false });
162
+ this.setState({ picture: response.file, kept: false });
108
163
  } else {
109
164
  this.setState({ note: `Apple answered ${response.status}` });
110
165
  }
111
166
  } catch (error) {
112
- this.setState({ note: (error as Error).message });
167
+ this.setState({ note: errorMessage(error) });
113
168
  }
114
169
  },
115
170
  async upload(state) {
171
+ if (!state.picture) {
172
+ return;
173
+ }
116
174
  try {
117
175
  const { body } = await this.dash.network.http.upload<Echo>({
118
176
  url: 'https://httpbin.org/post',
119
177
  fields: { artist: state.artist },
120
- files: [{ field: 'artwork', file: state.picture.id }],
178
+ files: [{ field: 'artwork', file: state.picture }],
121
179
  as: 'json',
122
180
  });
123
181
  this.setState({ uploaded: Object.keys(body.files).join(', ') });
124
182
  } catch (error) {
125
- this.setState({ note: (error as Error).message });
183
+ this.setState({ note: errorMessage(error) });
126
184
  }
127
185
  },
128
186
  async sharePicture(state) {
187
+ if (!state.picture) {
188
+ return;
189
+ }
129
190
  try {
130
- await this.dash.system.share({ file: state.picture.id });
191
+ await this.dash.system.share({ file: state.picture });
131
192
  } catch (error) {
132
- this.setState({ note: (error as Error).message });
193
+ this.setState({ note: errorMessage(error) });
133
194
  }
134
195
  },
135
196
  async keep(state) {
197
+ if (!state.picture) {
198
+ return;
199
+ }
136
200
  try {
137
- await this.dash.storage.file.save(state.picture.id, PICTURE, { overwrite: true });
201
+ await this.dash.storage.file.save(state.picture, PICTURE, { overwrite: true });
138
202
  this.setState({ kept: true });
139
203
  } catch (error) {
140
- this.setState({ note: (error as Error).message });
204
+ this.setState({ note: errorMessage(error) });
141
205
  }
142
206
  },
143
207
  async tryUnlistedHost() {
144
208
  try {
145
209
  await this.dash.network.http.request({ url: 'https://example.com' });
146
210
  } catch (error) {
147
- this.setState({ note: (error as Error).message });
211
+ this.setState({ note: errorMessage(error) });
148
212
  }
149
213
  },
150
214
  },
@@ -1,3 +1,11 @@
1
+ import type { DotFile } from '@monimesl/dot-runtime';
2
+
3
+ export interface Suggestion {
4
+ id: number;
5
+ name: string;
6
+ genre: string;
7
+ }
8
+
1
9
  export interface Release {
2
10
  id: number;
3
11
  title: string;
@@ -7,6 +15,10 @@ export interface Release {
7
15
  }
8
16
 
9
17
  export default {
18
+ connection: 'Not checked yet',
19
+ // What the person typed to search as they type, and the artists found.
20
+ typed: '',
21
+ suggestions: [] as Suggestion[],
10
22
  artist: 'Drizilik',
11
23
  // The start of Apple's answer, as the text it came as.
12
24
  raw: '',
@@ -15,7 +27,8 @@ export default {
15
27
  limit: 10,
16
28
  searching: false,
17
29
  echo: '',
18
- picture: { id: '', url: '', size: 0 },
30
+ // The downloaded artwork, in a temporary file until the dot closes.
31
+ picture: null as DotFile | null,
19
32
  uploaded: '',
20
33
  kept: false,
21
34
  note: '',
@@ -0,0 +1,108 @@
1
+ <View>
2
+ <Text variant="title">dash.network</Text>
3
+ <Text>Whether the phone is online, and the dot's requests, which Dash sends 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">status</Text>
7
+ <Text variant="caption" tone="muted">Whether the phone is online now, and how: wifi, cellular, other or none. Online doesn't promise a server answers.</Text>
8
+ <View variant="card" direction="row" align="center" gap="medium">
9
+ <View grow>
10
+ <Text>{{ connection }}</Text>
11
+ </View>
12
+ <Button variant="secondary" onClick="checkStatus">Check</Button>
13
+ </View>
14
+ </View>
15
+
16
+ <View gap="small">
17
+ <Text variant="subtitle">http.request</Text>
18
+ <Text variant="caption" tone="muted">The answer comes as text, or as JSON with as: 'json'. timeout gives up after a while.</Text>
19
+ <View variant="card" gap="small">
20
+ <Input label="Artist" placeholder="Drizilik" value="{{ artist }}" maxLength="40" onChange="setArtist" onSubmit="search" />
21
+ <View direction="row" wrap gap="small">
22
+ <Button onClick="search" loading="{{ searching }}" disabled="{{ !artist }}">Search</Button>
23
+ <Button variant="secondary" onClick="searchAsText" disabled="{{ !artist }}">Read as text</Button>
24
+ </View>
25
+ <If condition="{{ raw }}">
26
+ <Text variant="caption" tone="muted">The answer as text starts with: {{ raw }}</Text>
27
+ </If>
28
+ </View>
29
+
30
+ <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>
31
+ <If condition="{{ releases.length > 0 }}">
32
+ <List onEndReached="loadMore">
33
+ <For each="{{ releases }}" item="release" key="id">
34
+ <ListItem image="{{ release.artwork }}" title="{{ release.title }}" subtitle="{{ release.album }} · {{ release.year }}" />
35
+ </For>
36
+ </List>
37
+ </If>
38
+ <Else>
39
+ <View variant="card">
40
+ <EmptyState icon="search" title="No songs yet" text="Search for an artist, such as Drizilik, Kao Denero or Emmerson." />
41
+ </View>
42
+ </Else>
43
+ </View>
44
+
45
+ <View gap="small">
46
+ <Text variant="subtitle">Searching as you type: debounce and latest</Text>
47
+ <Text variant="caption" tone="muted">debounce waits for a pause in typing before searching, and latest cancels a search still running when a newer one starts, so an older answer can't replace a newer one.</Text>
48
+ <View variant="card" gap="small">
49
+ <Input type="search" placeholder="Type an artist, such as Emmerson" clearable value="{{ typed }}" onChange="suggest" />
50
+ <For each="{{ suggestions }}" item="artist" key="id">
51
+ <View direction="row" align="center" gap="medium">
52
+ <Avatar name="{{ artist.name }}" size="small" />
53
+ <View gap="none">
54
+ <Text>{{ artist.name }}</Text>
55
+ <Text variant="caption" tone="muted">{{ artist.genre }}</Text>
56
+ </View>
57
+ </View>
58
+ </For>
59
+ </View>
60
+ </View>
61
+
62
+ <View gap="small">
63
+ <Text variant="subtitle">Sending JSON</Text>
64
+ <Text variant="caption" tone="muted">method: 'POST' with a body. httpbin sends back what it got.</Text>
65
+ <View variant="card" gap="small">
66
+ <Button variant="secondary" onClick="sendJson">Send the first song as JSON</Button>
67
+ <If condition="{{ echo }}">
68
+ <Text>httpbin got {{ echo }}</Text>
69
+ </If>
70
+ </View>
71
+ </View>
72
+
73
+ <View gap="small">
74
+ <Text variant="subtitle">http.download and http.upload</Text>
75
+ <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>
76
+ <View variant="card" gap="small">
77
+ <View direction="row" wrap gap="small">
78
+ <Button variant="secondary" onClick="download" disabled="{{ releases.length === 0 }}">Download</Button>
79
+ <Button variant="secondary" onClick="upload" disabled="{{ !picture }}">Upload</Button>
80
+ <Button variant="secondary" onClick="sharePicture" disabled="{{ !picture }}">Share</Button>
81
+ <Button variant="secondary" onClick="keep" disabled="{{ !picture || kept }}">Keep</Button>
82
+ </View>
83
+ <If condition="{{ picture }}">
84
+ <View direction="row" align="center" gap="medium">
85
+ <Image src="{{ picture.url }}" alt="Artwork" width="64" height="64" />
86
+ <Text variant="caption" tone="muted">{{ picture.size }} bytes, in a temporary file until the dot closes</Text>
87
+ </View>
88
+ </If>
89
+ <If condition="{{ uploaded }}">
90
+ <Text>httpbin got the file field "{{ uploaded }}"</Text>
91
+ </If>
92
+ <If condition="{{ kept }}">
93
+ <Text>Saved in the dot's folder. See the dash.storage.file page.</Text>
94
+ </If>
95
+ </View>
96
+ </View>
97
+
98
+ <View gap="small">
99
+ <Text variant="subtitle">Hosts dot.json doesn't list</Text>
100
+ <Text variant="caption" tone="muted">Dash turns the request down.</Text>
101
+ <View variant="card" gap="small">
102
+ <Button variant="secondary" onClick="tryUnlistedHost">Reach example.com</Button>
103
+ <If condition="{{ note }}">
104
+ <Text variant="caption" tone="muted">{{ note }}</Text>
105
+ </If>
106
+ </View>
107
+ </View>
108
+ </View>