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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (143) hide show
  1. package/README.md +72 -59
  2. package/dist/cli.js +530 -339
  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
@@ -0,0 +1,69 @@
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 DashError 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">DashError codes</Text>
22
+ <Text variant="caption" tone="muted">isDashError(error) tells a Dash call's error from any other, and isDashError(error, 'timeout') also checks its code.</Text>
23
+ <View variant="card" gap="none">
24
+ <DetailRow label="cancelled" value="The person closed it, or the call was cancelled" />
25
+ <DetailRow label="timeout" value="It took longer than the timeout" />
26
+ <DetailRow label="denied" value="Not allowed, such as a missing permission" />
27
+ <DetailRow label="invalid" value="The call's options are wrong" />
28
+ <DetailRow label="unsupported" value="This Dash can't do it" />
29
+ <DetailRow label="missing" value="There's nothing there, such as a file that isn't saved" />
30
+ <DetailRow label="failed" value="Anything else went wrong" />
31
+ </View>
32
+ <View direction="row" wrap gap="small">
33
+ <Button variant="secondary" data-call="invalid" onClick="show">Pick a date between bad limits</Button>
34
+ <Button variant="secondary" data-call="denied" onClick="show">Reach an unlisted host</Button>
35
+ <Button variant="secondary" data-call="missing" onClick="show">Read a file never saved</Button>
36
+ </View>
37
+ </View>
38
+
39
+ <View gap="small">
40
+ <Text variant="subtitle">supports</Text>
41
+ <Text variant="caption" tone="muted">Each call's docs say which Dash API version it came in, and dot.json's dash.minApiVersion says which one the dot needs. To use a newer call and still open on older Dash, check this.dash.supports first.</Text>
42
+ <View variant="card" gap="none">
43
+ <DetailRow label="This Dash offers" value="Dash API {{ apiVersions }}" />
44
+ <DetailRow label="contacts.pickPhone" value="{{ picksPhones ? 'Supported' : 'Not on this Dash' }}" />
45
+ </View>
46
+ </View>
47
+
48
+ <View gap="small">
49
+ <Text variant="subtitle">dash.dot.close</Text>
50
+ <Text variant="caption" tone="muted">Closes the dot, as going back from its first page does, such as once a payment is done.</Text>
51
+ <Button variant="secondary" onClick="closeDot">Close the showcase</Button>
52
+ </View>
53
+
54
+ <View gap="small">
55
+ <Text variant="subtitle">The last call</Text>
56
+ <View variant="card" gap="none">
57
+ <If condition="{{ code }}">
58
+ <DetailRow label="error.code" value="{{ code }}" weight="bold" />
59
+ <DetailRow label="error.message" value="{{ message }}" />
60
+ </If>
61
+ <ElseIf condition="{{ waiting }}">
62
+ <Text tone="muted">Waiting…</Text>
63
+ </ElseIf>
64
+ <Else>
65
+ <Text tone="muted">Use a button above</Text>
66
+ </Else>
67
+ </View>
68
+ </View>
69
+ </View>
@@ -0,0 +1,30 @@
1
+ import { errorMessage, unlessCancelled } from '@monimesl/dot-runtime';
2
+
3
+ import state from './state';
4
+
5
+ export default definePage({
6
+ initialState: state,
7
+ configs: { navigationBar: { title: 'Contacts' } },
8
+ actions: {
9
+ async choose() {
10
+ try {
11
+ const contact = await unlessCancelled(this.dash.contacts.pickPhone());
12
+ if (contact) {
13
+ this.setState({ contact, note: '' });
14
+ }
15
+ } catch (error) {
16
+ this.setState({ note: errorMessage(error) });
17
+ }
18
+ },
19
+ async call(state) {
20
+ if (!state.contact) {
21
+ return;
22
+ }
23
+ try {
24
+ await this.dash.system.dial(state.contact.phone);
25
+ } catch (error) {
26
+ this.setState({ note: errorMessage(error) });
27
+ }
28
+ },
29
+ },
30
+ });
@@ -0,0 +1,6 @@
1
+ import type { PickedContact } from '@monimesl/dot-runtime';
2
+
3
+ export default {
4
+ contact: null as PickedContact | null,
5
+ note: '',
6
+ };
@@ -0,0 +1,33 @@
1
+ <View>
2
+ <Text variant="title">dash.contacts</Text>
3
+ <Text>The phone's own contacts. The dot sees only the one the person chooses.</Text>
4
+
5
+ <View gap="small">
6
+ <Text variant="subtitle">pickPhone</Text>
7
+ <Text variant="caption" tone="muted">The person chooses one phone number, such as who to send money to. It needs the contacts permission in dot.json.</Text>
8
+ <View variant="card" gap="medium">
9
+ <If condition="{{ contact }}">
10
+ <View direction="row" align="center" gap="medium">
11
+ <Avatar name="{{ contact.name }}" />
12
+ <View grow gap="none">
13
+ <Text weight="medium">{{ contact.name || 'No name' }}</Text>
14
+ <Text variant="caption" tone="muted">{{ contact.phone }}</Text>
15
+ </View>
16
+ </View>
17
+ <View direction="row" gap="small">
18
+ <Button variant="secondary" onClick="choose">Choose another</Button>
19
+ <Button variant="secondary" onClick="call">Call</Button>
20
+ </View>
21
+ </If>
22
+ <Else>
23
+ <EmptyState icon="users" title="No one chosen" text="Choose a contact to send money to.">
24
+ <Button onClick="choose">Choose a contact</Button>
25
+ </EmptyState>
26
+ </Else>
27
+ </View>
28
+ </View>
29
+
30
+ <If condition="{{ note }}">
31
+ <Notice tone="danger">{{ note }}</Notice>
32
+ </If>
33
+ </View>
@@ -0,0 +1,38 @@
1
+ import { errorMessage } from '@monimesl/dot-runtime';
2
+
3
+ import state from './state';
4
+
5
+ export default definePage({
6
+ initialState: state,
7
+ configs: { navigationBar: { title: 'Crypto' } },
8
+ mutations: {
9
+ setText: (_state, event: ValueEvent) => ({ text: event.detail.value }),
10
+ setKey: (_state, event: ValueEvent) => ({ key: event.detail.value }),
11
+ // dash.utils answers at once, so even a mutation can use it.
12
+ encode: (state) => ({ encoded: dash.utils.base64.encode(state.text) }),
13
+ },
14
+ actions: {
15
+ async hash(state) {
16
+ try {
17
+ const sha256 = await this.dash.crypto.sha256(state.text);
18
+ const hmac = await this.dash.crypto.hmac({
19
+ algorithm: 'sha256',
20
+ key: state.key,
21
+ data: state.text,
22
+ });
23
+ this.setState({ sha256, hmac, note: '' });
24
+ } catch (error) {
25
+ this.setState({ note: errorMessage(error) });
26
+ }
27
+ },
28
+ async makeRandom() {
29
+ try {
30
+ const random = await this.dash.crypto.randomBytes(16);
31
+ const uuid = await this.dash.crypto.randomUUID();
32
+ this.setState({ random, uuid, note: '' });
33
+ } catch (error) {
34
+ this.setState({ note: errorMessage(error) });
35
+ }
36
+ },
37
+ },
38
+ });
@@ -0,0 +1,10 @@
1
+ export default {
2
+ text: 'Salone Sounds Live, Freetown',
3
+ key: 'shared secret',
4
+ sha256: '',
5
+ hmac: '',
6
+ random: '',
7
+ uuid: '',
8
+ encoded: '',
9
+ note: '',
10
+ };
@@ -0,0 +1,48 @@
1
+ <View>
2
+ <Text variant="title">dash.crypto</Text>
3
+ <Text>Hashes, signatures and random values, worked out on the phone. Each gives hex, or base64 with { as: 'base64' }.</Text>
4
+
5
+ <View variant="card" gap="medium">
6
+ <Input label="Text" value="{{ text }}" onChange="setText" />
7
+ <Input label="HMAC key" value="{{ key }}" onChange="setKey" />
8
+ </View>
9
+
10
+ <View gap="small">
11
+ <Text variant="subtitle">sha256 and hmac</Text>
12
+ <Text variant="caption" tone="muted">sha512 works the same way. hmac signs with a key, such as to check a message from the dot's own server.</Text>
13
+ <Button variant="secondary" onClick="hash">Hash and sign the text</Button>
14
+ <If condition="{{ sha256 }}">
15
+ <View variant="card" gap="none">
16
+ <DetailRow label="sha256" value="{{ sha256 }}" />
17
+ <DetailRow label="hmac sha256" value="{{ hmac }}" />
18
+ </View>
19
+ </If>
20
+ </View>
21
+
22
+ <View gap="small">
23
+ <Text variant="subtitle">randomBytes and randomUUID</Text>
24
+ <Text variant="caption" tone="muted">From the phone's secure random source, such as for a nonce.</Text>
25
+ <Button variant="secondary" onClick="makeRandom">Make random values</Button>
26
+ <If condition="{{ uuid }}">
27
+ <View variant="card" gap="none">
28
+ <DetailRow label="16 random bytes" value="{{ random }}" />
29
+ <DetailRow label="randomUUID" value="{{ uuid }}" />
30
+ </View>
31
+ </If>
32
+ </View>
33
+
34
+ <View gap="small">
35
+ <Text variant="subtitle">dash.utils</Text>
36
+ <Text variant="caption" tone="muted">Not Dash calls: dash.utils.base64 and dash.utils.hex run in the dot and answer at once, without await, for UTF-8 text.</Text>
37
+ <Button variant="secondary" onClick="encode">Encode the text</Button>
38
+ <If condition="{{ encoded }}">
39
+ <View variant="card" gap="none">
40
+ <DetailRow label="utils.base64.encode" value="{{ encoded }}" />
41
+ </View>
42
+ </If>
43
+ </View>
44
+
45
+ <If condition="{{ note }}">
46
+ <Notice tone="danger">{{ note }}</Notice>
47
+ </If>
48
+ </View>
@@ -5,13 +5,14 @@ const PICTURE = 'pictures/artwork.jpg';
5
5
 
6
6
  export default definePage({
7
7
  initialState: state,
8
+ configs: { navigationBar: { title: 'Files' } },
8
9
  hooks: {
9
10
  async onShow() {
10
11
  await this.actions.refresh();
11
12
  },
12
13
  },
13
14
  mutations: {
14
- setDraft: (_state, event: DotEvent<{ value: string }>) => ({ draft: event.detail.value }),
15
+ setDraft: (_state, event: ValueEvent) => ({ draft: event.detail.value }),
15
16
  },
16
17
  actions: {
17
18
  async refresh() {
@@ -29,11 +30,15 @@ export default definePage({
29
30
  time: new Date(Number(entry.name)).toLocaleTimeString('en-GB'),
30
31
  }))
31
32
  .reverse();
33
+ const documents = entries.filter(
34
+ (entry) => entry.kind === 'file' && entry.path.startsWith('documents/'),
35
+ );
32
36
  const { used, quota } = await this.dash.storage.file.usage();
33
37
  const kept = await this.dash.storage.file.stat(PICTURE);
34
38
  this.setState({
35
39
  songs,
36
40
  archives,
41
+ documents,
37
42
  usage: `${used} bytes used of ${quota / 1024 / 1024} MB`,
38
43
  picture: kept ? (await this.dash.storage.file.open(PICTURE)).url : '',
39
44
  });
@@ -50,7 +55,7 @@ export default definePage({
50
55
  await this.actions.refresh();
51
56
  },
52
57
  // Copies an archived setlist back, in place of the current one.
53
- async copyBack(_state, event: DotEvent<unknown, { path: string }>) {
58
+ async copyBack(_state, event: DataEvent<{ path: string }>) {
54
59
  await this.dash.storage.file.copy(event.data.path, 'setlist', { overwrite: true });
55
60
  await this.actions.refresh();
56
61
  },
@@ -1,3 +1,5 @@
1
+ import type { FileEntry } from '@monimesl/dot-runtime';
2
+
1
3
  export interface Archive {
2
4
  path: string;
3
5
  // When it was archived, from its folder's name.
@@ -16,4 +18,6 @@ export default {
16
18
  archives: [] as Archive[],
17
19
  usage: '',
18
20
  picture: '',
21
+ // Files saved into the dot's documents folder, such as a ticket from the dash.media page.
22
+ documents: [] as FileEntry[],
19
23
  };
@@ -0,0 +1,78 @@
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">save</Text>
46
+ <Text variant="caption" tone="muted">A picked photo or file, or a download, goes when the dot closes. save copies it into the dot's folder to keep it. These are the tickets kept on the dash.media page.</Text>
47
+ <View variant="card" gap="small">
48
+ <For each="{{ documents }}" item="document" key="path">
49
+ <View gap="none">
50
+ <Text>{{ document.name }}</Text>
51
+ <Text variant="caption" tone="muted">{{ document.path }}</Text>
52
+ </View>
53
+ </For>
54
+ <If condition="{{ documents.length === 0 }}">
55
+ <Text variant="caption" tone="muted">None yet. On the dash.media page, attach a ticket and tap Keep it.</Text>
56
+ </If>
57
+ </View>
58
+ </View>
59
+
60
+ <View gap="small">
61
+ <Text variant="subtitle">stat and open</Text>
62
+ <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>
63
+ <View variant="card" gap="small">
64
+ <If condition="{{ picture }}">
65
+ <Image src="{{ picture }}" alt="Kept artwork" width="64" height="64" />
66
+ <Button variant="secondary" onClick="forgetPicture">Forget it</Button>
67
+ </If>
68
+ <Else>
69
+ <Text variant="caption" tone="muted">None yet. On the dash.network page, search, download the artwork and tap Keep.</Text>
70
+ </Else>
71
+ </View>
72
+ </View>
73
+
74
+ <View gap="small">
75
+ <Text variant="subtitle">usage</Text>
76
+ <Text>{{ usage }}</Text>
77
+ </View>
78
+ </View>
@@ -0,0 +1,65 @@
1
+ import type { PhotoSource } from '@monimesl/dot-runtime';
2
+ import { errorMessage, unlessCancelled } from '@monimesl/dot-runtime';
3
+
4
+ import state from './state';
5
+
6
+ export default definePage({
7
+ initialState: state,
8
+ configs: { navigationBar: { title: 'Photos, files and scanning' } },
9
+ // unlessCancelled gives undefined when the person closes the picker or scanner.
10
+ actions: {
11
+ async pickPhoto(_state, event: DataEvent<{ source: PhotoSource }>) {
12
+ try {
13
+ const photo = await unlessCancelled(
14
+ this.dash.media.pickPhoto({ source: event.data.source }),
15
+ );
16
+ if (!photo) {
17
+ return;
18
+ }
19
+ const details = `${photo.name}, ${Math.round(photo.size / 1024)} KB as a JPEG`;
20
+ this.setState({ photo, photoDetails: details, note: '' });
21
+ } catch (error) {
22
+ this.setState({ note: errorMessage(error) });
23
+ }
24
+ },
25
+ async pickTicket() {
26
+ try {
27
+ const ticket = await unlessCancelled(
28
+ this.dash.media.pickFile({ types: ['application/pdf', 'image/*'] }),
29
+ );
30
+ if (!ticket) {
31
+ return;
32
+ }
33
+ const ticketSize = `${Math.round(ticket.size / 1024)} KB`;
34
+ this.setState({ ticket, ticketSize, ticketKept: false, note: '' });
35
+ } catch (error) {
36
+ this.setState({ note: errorMessage(error) });
37
+ }
38
+ },
39
+ // A picked file goes when the dot closes, so it's saved into the dot's own folder.
40
+ async keepTicket(state) {
41
+ if (!state.ticket) {
42
+ return;
43
+ }
44
+ try {
45
+ await this.dash.storage.file.createFolder('documents');
46
+ await this.dash.storage.file.save(state.ticket, `documents/${state.ticket.name}`, {
47
+ overwrite: true,
48
+ });
49
+ this.setState({ ticketKept: true, note: '' });
50
+ } catch (error) {
51
+ this.setState({ note: errorMessage(error) });
52
+ }
53
+ },
54
+ async scanTicket() {
55
+ try {
56
+ const scanned = await unlessCancelled(this.dash.media.scanCode());
57
+ if (scanned) {
58
+ this.setState({ scanned: scanned.text, note: '' });
59
+ }
60
+ } catch (error) {
61
+ this.setState({ note: errorMessage(error) });
62
+ }
63
+ },
64
+ },
65
+ });
@@ -0,0 +1,12 @@
1
+ import type { DotFile } from '@monimesl/dot-runtime';
2
+
3
+ export default {
4
+ photo: null as DotFile | null,
5
+ photoDetails: '',
6
+ ticket: null as DotFile | null,
7
+ ticketSize: '',
8
+ // Whether the ticket was saved into the dot's documents folder.
9
+ ticketKept: false,
10
+ scanned: '',
11
+ note: '',
12
+ };
@@ -0,0 +1,53 @@
1
+ <View>
2
+ <Text variant="title">dash.media</Text>
3
+ <Text>Photos, files and codes from the phone. 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">pickFile</Text>
22
+ <Text variant="caption" tone="muted">Lets the person choose a file from the phone, of at most 25 MB, with the files permission. types narrows what they can choose, here a PDF or a picture. Like a photo, it goes when the dot closes, so keep it with dash.storage.file.save.</Text>
23
+ <View variant="card" gap="small">
24
+ <Button variant="secondary" onClick="pickTicket">Attach your concert ticket</Button>
25
+ <If condition="{{ ticket }}">
26
+ <DetailRow label="Name" value="{{ ticket.name }}" />
27
+ <DetailRow label="Size" value="{{ ticketSize }}" />
28
+ <DetailRow label="Type" value="{{ ticket.contentType }}" />
29
+ <If condition="{{ ticketKept }}">
30
+ <Text variant="caption" tone="success">Kept in documents/. The dash.storage.file page lists it.</Text>
31
+ </If>
32
+ <Else>
33
+ <Button variant="secondary" onClick="keepTicket">Keep it</Button>
34
+ </Else>
35
+ </If>
36
+ </View>
37
+ </View>
38
+
39
+ <View gap="small">
40
+ <Text variant="subtitle">scanCode</Text>
41
+ <Text variant="caption" tone="muted">Opens Dash's scanner until it reads a QR code. It needs the camera permission.</Text>
42
+ <View variant="card" gap="small">
43
+ <Button variant="secondary" onClick="scanTicket">Scan a ticket's QR code</Button>
44
+ <If condition="{{ scanned }}">
45
+ <Text>{{ scanned }}</Text>
46
+ </If>
47
+ </View>
48
+ </View>
49
+
50
+ <If condition="{{ note }}">
51
+ <Notice tone="danger">{{ note }}</Notice>
52
+ </If>
53
+ </View>