@monimesl/dot-cli 1.0.0-beta.2 → 1.0.0-beta.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +22 -9
- package/dist/cli.js +13 -11
- package/dist/cli.js.map +1 -1
- package/dist/templates/showcase/.env +2 -2
- package/dist/templates/showcase/dot.ts +2 -1
- package/dist/templates/showcase/pages/basics/bars/controller.ts +2 -2
- package/dist/templates/showcase/pages/basics/bars/view.dml +2 -1
- package/dist/templates/showcase/pages/basics/bindings/state.ts +2 -2
- package/dist/templates/showcase/pages/basics/bindings/view.dml +2 -2
- package/dist/templates/showcase/pages/basics/colors/controller.ts +1 -1
- package/dist/templates/showcase/pages/basics/concert/view.dml +1 -1
- package/dist/templates/showcase/pages/basics/control-flow/controller.ts +1 -1
- package/dist/templates/showcase/pages/basics/control-flow/view.dml +1 -1
- package/dist/templates/showcase/pages/basics/events/controller.ts +1 -1
- package/dist/templates/showcase/pages/basics/events/view.dml +1 -1
- package/dist/templates/showcase/pages/basics/hooks/controller.ts +2 -1
- package/dist/templates/showcase/pages/basics/hooks/view.dml +2 -2
- package/dist/templates/showcase/pages/basics/navigation/controller.ts +1 -0
- package/dist/templates/showcase/pages/basics/navigation/view.dml +1 -1
- package/dist/templates/showcase/pages/basics/pick-genre/controller.ts +3 -3
- package/dist/templates/showcase/pages/basics/share/view.dml +2 -2
- package/dist/templates/showcase/pages/basics/state/controller.ts +9 -0
- package/dist/templates/showcase/pages/basics/state/state.ts +3 -0
- package/dist/templates/showcase/pages/components/carousel/view.dml +2 -2
- package/dist/templates/showcase/pages/components/feedback/controller.ts +2 -1
- package/dist/templates/showcase/pages/components/feedback/state.ts +2 -2
- package/dist/templates/showcase/pages/components/feedback/view.dml +2 -2
- package/dist/templates/showcase/pages/components/forms/controller.ts +2 -2
- package/dist/templates/showcase/pages/components/forms/state.ts +1 -1
- package/dist/templates/showcase/pages/components/forms/view.dml +5 -5
- package/dist/templates/showcase/pages/components/icons/controller.ts +1 -0
- package/dist/templates/showcase/pages/components/icons/state.ts +1 -1
- package/dist/templates/showcase/pages/components/images/controller.ts +1 -1
- package/dist/templates/showcase/pages/components/images/state.ts +1 -1
- package/dist/templates/showcase/pages/components/images/view.dml +4 -4
- package/dist/templates/showcase/pages/components/layout/controller.ts +3 -1
- package/dist/templates/showcase/pages/components/layout/view.dml +5 -5
- package/dist/templates/showcase/pages/components/lists/view.dml +7 -7
- package/dist/templates/showcase/pages/components/money/controller.ts +1 -1
- package/dist/templates/showcase/pages/components/money/view.dml +2 -2
- package/dist/templates/showcase/pages/components/reading/state.ts +2 -2
- package/dist/templates/showcase/pages/components/sheets/controller.ts +1 -1
- package/dist/templates/showcase/pages/components/sheets/view.dml +1 -1
- package/dist/templates/showcase/pages/components/styles/view.dml +3 -3
- package/dist/templates/showcase/pages/components/tabs/state.ts +2 -2
- package/dist/templates/showcase/pages/components/tabs/view.dml +2 -2
- package/dist/templates/showcase/pages/components/text/view.dml +3 -3
- package/dist/templates/showcase/pages/dash/account/controller.ts +3 -0
- package/dist/templates/showcase/pages/dash/account/view.dml +2 -2
- package/dist/templates/showcase/pages/dash/cache/controller.ts +3 -0
- package/dist/templates/showcase/pages/dash/calls/controller.ts +3 -2
- package/dist/templates/showcase/pages/dash/contacts/controller.ts +1 -0
- package/dist/templates/showcase/pages/dash/crypto/controller.ts +2 -0
- package/dist/templates/showcase/pages/dash/crypto/view.dml +1 -1
- package/dist/templates/showcase/pages/dash/files/controller.ts +6 -1
- package/dist/templates/showcase/pages/dash/files/view.dml +1 -1
- package/dist/templates/showcase/pages/dash/media/controller.ts +7 -1
- package/dist/templates/showcase/pages/dash/media/state.ts +2 -0
- package/dist/templates/showcase/pages/dash/network/controller.ts +5 -1
- package/dist/templates/showcase/pages/dash/network/view.dml +1 -1
- package/dist/templates/showcase/pages/dash/payments/cases.ts +3 -3
- package/dist/templates/showcase/pages/dash/payments/controller.ts +6 -0
- package/dist/templates/showcase/pages/dash/payments/view.dml +1 -1
- package/dist/templates/showcase/pages/dash/secrets/controller.ts +3 -0
- package/dist/templates/showcase/pages/dash/system/controller.ts +5 -0
- package/dist/templates/showcase/pages/dash/system/view.dml +4 -4
- package/dist/templates/showcase/pages/dash/ui/controller.ts +6 -1
- package/dist/templates/showcase/pages/index/controller.ts +1 -0
- package/dist/templates/showcase/pages/index/state.ts +3 -3
- package/dist/templates/starter/.env +2 -2
- package/dist/templates/starter/dot.ts +1 -0
- package/dist/templates/starter/pages/index/controller.ts +2 -0
- package/dist/templates/starter/pages/index/state.ts +2 -0
- package/dist/templates/starter/pages/index/view.dml +2 -1
- package/package.json +6 -6
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
//
|
|
2
|
-
//
|
|
1
|
+
// An article's body in Markdown, as a news app would get it from its server. It's written as
|
|
2
|
+
// joined lines so that the built code keeps it on one line.
|
|
3
3
|
const body = [
|
|
4
4
|
"## What's on sale",
|
|
5
5
|
'',
|
|
@@ -7,7 +7,7 @@ export default definePage({
|
|
|
7
7
|
openRelease: (state, event: DataEvent<{ id: string }>) => ({
|
|
8
8
|
selected: state.releases.find((release) => release.id === event.data.id) ?? null,
|
|
9
9
|
}),
|
|
10
|
-
// A
|
|
10
|
+
// A Sheet doesn't close itself. The page sets open back to false.
|
|
11
11
|
closeRelease: () => ({ selected: null }),
|
|
12
12
|
openRating: () => ({ rating: true }),
|
|
13
13
|
closeRating: () => ({ rating: false }),
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
|
|
15
15
|
<View gap="small">
|
|
16
16
|
<Text variant="subtitle">A sheet with a form</Text>
|
|
17
|
-
<Text variant="caption" tone="muted">Any component can go in a sheet. This one has no close icon, and more room
|
|
17
|
+
<Text variant="caption" tone="muted">Any component can go in a sheet. This one has no close icon, and padding="large" gives it more room.</Text>
|
|
18
18
|
<View variant="card" direction="row" align="center" gap="small">
|
|
19
19
|
<View grow>
|
|
20
20
|
<Text>{{ saved ? 'You gave ' + saved + ' stars' : 'Not rated yet' }}</Text>
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
<View>
|
|
2
2
|
<Text variant="title">Styles</Text>
|
|
3
|
-
<Text>A View
|
|
3
|
+
<Text>A View can set its colour, outline, corners and size. Its colours come from the theme, so a dot looks right in light and dark.</Text>
|
|
4
4
|
|
|
5
5
|
<View gap="small">
|
|
6
6
|
<Text variant="subtitle">background</Text>
|
|
7
|
-
<Text variant="caption" tone="muted">
|
|
7
|
+
<Text variant="caption" tone="muted">On a primary background, give text tone="onPrimary" so it reads.</Text>
|
|
8
8
|
<View background="primary" radius="large" padding="large" gap="small">
|
|
9
9
|
<Text variant="caption" tone="onPrimary">Your balance</Text>
|
|
10
10
|
<Text variant="display" tone="onPrimary" numeric>{{ balance }}</Text>
|
|
@@ -40,7 +40,7 @@
|
|
|
40
40
|
|
|
41
41
|
<View gap="small">
|
|
42
42
|
<Text variant="subtitle">Theme colours</Text>
|
|
43
|
-
<Text variant="caption" tone="muted">The theme in dot.ts can set primary, background, surface, text, muted, border and subtle, for light and dark
|
|
43
|
+
<Text variant="caption" tone="muted">The theme in dot.ts can set primary, background, surface, text, muted, border and subtle, for light and dark. Left out, they're Dash's. The theme can also name its own colours, such as gold and ink here. Use them as a background or a text tone.</Text>
|
|
44
44
|
<View direction="row" gap="small" align="center">
|
|
45
45
|
<View background="gold" radius="full" paddingTop="small" paddingBottom="small" paddingStart="medium" paddingEnd="medium">
|
|
46
46
|
<Text weight="medium" tone="ink">Gold member</Text>
|
|
@@ -40,7 +40,6 @@ const releases: Release[] = [
|
|
|
40
40
|
release('nack', 'Nack', 'The Therapist', 2022, 'song', 'Afropop'),
|
|
41
41
|
release('hannah', 'Hannah', 'Donii Major', 2021, 'song', 'R&B'),
|
|
42
42
|
release('shukubly', 'Shukubly', 'Drizilik', 2018, 'album', 'Kriofusion'),
|
|
43
|
-
release('en-yay-sah', 'En Yay Sah', 'Janka Nabay and the Bubu Gang', 2012, 'album', 'Bubu'),
|
|
44
43
|
release('red-album', 'Red Album', 'LAJ', 2013, 'album', 'Hip hop'),
|
|
45
44
|
release(
|
|
46
45
|
'ghetto-struggle',
|
|
@@ -50,6 +49,7 @@ const releases: Release[] = [
|
|
|
50
49
|
'song',
|
|
51
50
|
'Hip hop',
|
|
52
51
|
),
|
|
52
|
+
release('en-yay-sah', 'En Yay Sah', 'Janka Nabay and the Bubu Gang', 2012, 'album', 'Bubu'),
|
|
53
53
|
release('borbor-bele', 'Borbor Bele', 'Emmerson', 2004, 'album', 'Afropop'),
|
|
54
54
|
release('corruption', 'Corruption', 'Daddy Saj', 2003, 'album', 'Hip hop'),
|
|
55
55
|
release('cry-africa', 'Cry Africa', 'Vicky Fornah', 1999, 'song', 'Gospel'),
|
|
@@ -60,7 +60,7 @@ const releases: Release[] = [
|
|
|
60
60
|
|
|
61
61
|
export default {
|
|
62
62
|
releases,
|
|
63
|
-
// The releases the tab and the
|
|
63
|
+
// The releases left after the tab, the genre and the search filter them.
|
|
64
64
|
shown: releases,
|
|
65
65
|
tab: 'all',
|
|
66
66
|
albums: releases.filter((item) => item.kind === 'album').length,
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
<View>
|
|
2
2
|
<Text variant="title">Tabs and chips</Text>
|
|
3
|
-
<Text>Tabs change what the page shows. Chips and a search narrow it down. Try Songs with Palm-wine
|
|
3
|
+
<Text>Tabs change what the page shows. Chips and a search narrow it down. Try Songs with Palm-wine. Nothing matches, so an EmptyState shows.</Text>
|
|
4
4
|
|
|
5
5
|
<View gap="small">
|
|
6
6
|
<Text variant="subtitle">Tabs and Chip</Text>
|
|
7
|
-
<Text variant="caption" tone="muted">
|
|
7
|
+
<Text variant="caption" tone="muted">Tabs sends onChange with the chosen tab's value in event.detail.value. A Tab's badge shows a count. A Chip is selected only when the page says so. An Input with type="search" and clearable makes a search box.</Text>
|
|
8
8
|
<Tabs variant="underline" value="{{ tab }}" onChange="chooseTab">
|
|
9
9
|
<Tab value="all" label="All" />
|
|
10
10
|
<Tab value="album" label="Albums" badge="{{ albums }}" />
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
<View>
|
|
2
2
|
<Text variant="title">Text and links</Text>
|
|
3
|
-
<Text>Text shows words. Its variant sets the size, and
|
|
3
|
+
<Text>Text shows words. Its variant sets the size and weight, and its tone sets the colour from the theme.</Text>
|
|
4
4
|
|
|
5
5
|
<View gap="small">
|
|
6
6
|
<Text variant="subtitle">variant</Text>
|
|
@@ -65,7 +65,7 @@
|
|
|
65
65
|
|
|
66
66
|
<View gap="small">
|
|
67
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
|
|
68
|
+
<Text variant="caption" tone="muted">leading moves the lines closer or further apart. selectable lets the person select and copy the text. Hold your finger on the last one.</Text>
|
|
69
69
|
<View variant="card" gap="medium">
|
|
70
70
|
<Text leading="tight">Tight lines. Palm-wine music grew in the bars of Freetown, played on guitar and sung in Krio.</Text>
|
|
71
71
|
<Text leading="normal">Normal lines. Palm-wine music grew in the bars of Freetown, played on guitar and sung in Krio.</Text>
|
|
@@ -82,7 +82,7 @@
|
|
|
82
82
|
|
|
83
83
|
<View gap="small">
|
|
84
84
|
<Text variant="subtitle">Link</Text>
|
|
85
|
-
<Text variant="caption" tone="muted">With href, a
|
|
85
|
+
<Text variant="caption" tone="muted">With an https href, a Link opens the web page in the phone's browser. Without one, onTap runs a handler.</Text>
|
|
86
86
|
<View variant="card" gap="small">
|
|
87
87
|
<Text>Read about <Link href="https://en.wikipedia.org/wiki/Music_of_Sierra_Leone">the music of Sierra Leone</Link>.</Text>
|
|
88
88
|
<View direction="row" gap="medium">
|
|
@@ -9,6 +9,8 @@ export default definePage({
|
|
|
9
9
|
setNonce: (_state, event: ValueEvent) => ({ nonce: event.detail.value }),
|
|
10
10
|
},
|
|
11
11
|
actions: {
|
|
12
|
+
// A nonce longer than 128 characters rejects with 'invalid'. If no one is signed in to Dash, or
|
|
13
|
+
// Dash can't reach Monime, it rejects with 'failed'.
|
|
12
14
|
async signIn(state) {
|
|
13
15
|
try {
|
|
14
16
|
const { fullName, alias, accountIdToken } = await this.dash.account.signIn({
|
|
@@ -20,6 +22,7 @@ export default definePage({
|
|
|
20
22
|
this.setState({ note: errorMessage(error) });
|
|
21
23
|
}
|
|
22
24
|
},
|
|
25
|
+
// Rejects with 'unsupported' until Dash has OpenID Connect.
|
|
23
26
|
async signInWithOidc() {
|
|
24
27
|
try {
|
|
25
28
|
const { id } = await this.dash.account.signIn({ mode: 'oidc', scopes: ['profile'] });
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
<View gap="small">
|
|
6
6
|
<Text variant="subtitle">signIn</Text>
|
|
7
|
-
<Text variant="caption" tone="muted">With mode "simple", Dash says who the person is: their full name, the phone number or email address they use Dash with
|
|
7
|
+
<Text variant="caption" tone="muted">With mode "simple", Dash says who the person is: their full name, and the phone number or email address they use Dash with. It also gives a token that names them in this dot. Monime makes it from the dot's id, the person and the nonce, so the same nonce gives the same token on any phone. The token names the person, but proves nothing to the dot's server.</Text>
|
|
8
8
|
<View variant="card" gap="medium">
|
|
9
9
|
<Input label="Nonce" value="{{ nonce }}" onChange="setNonce" />
|
|
10
10
|
<Button onClick="signIn">Sign in with Dash</Button>
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
|
|
21
21
|
<View gap="small">
|
|
22
22
|
<Text variant="subtitle">signIn with OpenID Connect</Text>
|
|
23
|
-
<Text variant="caption" tone="muted">With mode "oidc", Dash will give an ID token the dot's server checks with Monime's keys, and only the details the person agrees to share. It isn't ready yet, so
|
|
23
|
+
<Text variant="caption" tone="muted">With mode "oidc", Dash will give an ID token the dot's server checks with Monime's keys, and only the details the person agrees to share. It isn't ready yet, so the call fails with unsupported.</Text>
|
|
24
24
|
<Button variant="secondary" onClick="signInWithOidc">Sign in with OpenID Connect</Button>
|
|
25
25
|
<If condition="{{ oidcNote }}">
|
|
26
26
|
<Notice tone="info">{{ oidcNote }}</Notice>
|
|
@@ -12,11 +12,14 @@ export default definePage({
|
|
|
12
12
|
await this.actions.refresh();
|
|
13
13
|
},
|
|
14
14
|
},
|
|
15
|
+
// Values are JSON: text, numbers, true or false, lists and objects. Each can be up to 1 MB,
|
|
16
|
+
// and the cache up to 64 MB. A full cache rejects with 'failed'.
|
|
15
17
|
actions: {
|
|
16
18
|
async refresh() {
|
|
17
19
|
const keys = await this.dash.storage.cache.keys();
|
|
18
20
|
const recent: string[] = [];
|
|
19
21
|
for (const key of keys.filter((each) => each.startsWith(RECENT))) {
|
|
22
|
+
// get gives null for a key that's missing or expired.
|
|
20
23
|
recent.push((await this.dash.storage.cache.get<string>(key)) ?? '');
|
|
21
24
|
}
|
|
22
25
|
const hasGenre = await this.dash.storage.cache.exists(GENRE);
|
|
@@ -3,7 +3,7 @@ import { errorMessage, isDashError } from '@monimesl/dot-runtime';
|
|
|
3
3
|
|
|
4
4
|
import state from './state';
|
|
5
5
|
|
|
6
|
-
// The long wait, kept so the Cancel button can stop it.
|
|
6
|
+
// The long wait, kept so the Cancel button can stop it. cancel() makes it reject with 'cancelled'.
|
|
7
7
|
let wait: DashRequest<void> | null = null;
|
|
8
8
|
|
|
9
9
|
export default definePage({
|
|
@@ -27,7 +27,8 @@ export default definePage({
|
|
|
27
27
|
try {
|
|
28
28
|
switch (event.data.call) {
|
|
29
29
|
case 'timeout':
|
|
30
|
-
//
|
|
30
|
+
// Every call takes { timeout } last, except payments.request, where Dash decides.
|
|
31
|
+
// This gives up after 1 second on a wait of 5.
|
|
31
32
|
await this.dash.system.delay(5000, { timeout: 1000 });
|
|
32
33
|
break;
|
|
33
34
|
case 'cancelled':
|
|
@@ -6,6 +6,7 @@ export default definePage({
|
|
|
6
6
|
initialState: state,
|
|
7
7
|
configs: { navigationBar: { title: 'Contacts' } },
|
|
8
8
|
actions: {
|
|
9
|
+
// Resolves with { name, phone }, the number as the contact has it, such as "076 123456".
|
|
9
10
|
async choose() {
|
|
10
11
|
try {
|
|
11
12
|
const contact = await unlessCancelled(this.dash.contacts.pickPhone());
|
|
@@ -12,6 +12,7 @@ export default definePage({
|
|
|
12
12
|
encode: (state) => ({ encoded: dash.utils.base64.encode(state.text) }),
|
|
13
13
|
},
|
|
14
14
|
actions: {
|
|
15
|
+
// Text goes in as UTF-8. Binary data goes in as { base64: '…' }.
|
|
15
16
|
async hash(state) {
|
|
16
17
|
try {
|
|
17
18
|
const sha256 = await this.dash.crypto.sha256(state.text);
|
|
@@ -25,6 +26,7 @@ export default definePage({
|
|
|
25
26
|
this.setState({ note: errorMessage(error) });
|
|
26
27
|
}
|
|
27
28
|
},
|
|
29
|
+
// randomBytes takes a count from 1 to 1024.
|
|
28
30
|
async makeRandom() {
|
|
29
31
|
try {
|
|
30
32
|
const random = await this.dash.crypto.randomBytes(16);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
<View>
|
|
2
2
|
<Text variant="title">dash.crypto</Text>
|
|
3
|
-
<Text>Hashes, signatures and random values, worked out on the phone.
|
|
3
|
+
<Text>Hashes, signatures and random values, worked out on the phone. They come as hex, or as base64 with { as: 'base64' }. randomUUID gives a UUID.</Text>
|
|
4
4
|
|
|
5
5
|
<View variant="card" gap="medium">
|
|
6
6
|
<Input label="Text" value="{{ text }}" onChange="setText" />
|
|
@@ -14,6 +14,8 @@ export default definePage({
|
|
|
14
14
|
mutations: {
|
|
15
15
|
setDraft: (_state, event: ValueEvent) => ({ draft: event.detail.value }),
|
|
16
16
|
},
|
|
17
|
+
// Paths are inside the dot's folder, and '' is the folder itself. A call on a path with nothing
|
|
18
|
+
// there rejects with 'missing', while stat gives null.
|
|
17
19
|
actions: {
|
|
18
20
|
async refresh() {
|
|
19
21
|
const entries = await this.dash.storage.file.list('', { recursive: true });
|
|
@@ -33,6 +35,7 @@ export default definePage({
|
|
|
33
35
|
const documents = entries.filter(
|
|
34
36
|
(entry) => entry.kind === 'file' && entry.path.startsWith('documents/'),
|
|
35
37
|
);
|
|
38
|
+
// In bytes: what the dot's files take, and the most they may take.
|
|
36
39
|
const { used, quota } = await this.dash.storage.file.usage();
|
|
37
40
|
const kept = await this.dash.storage.file.stat(PICTURE);
|
|
38
41
|
this.setState({
|
|
@@ -54,11 +57,13 @@ export default definePage({
|
|
|
54
57
|
await this.dash.storage.file.move('setlist', `archive/${Date.now()}`);
|
|
55
58
|
await this.actions.refresh();
|
|
56
59
|
},
|
|
57
|
-
// Copies an archived setlist back, in place of the current one.
|
|
60
|
+
// Copies an archived setlist back, in place of the current one. Without overwrite, a path
|
|
61
|
+
// that's taken rejects with 'failed'.
|
|
58
62
|
async copyBack(_state, event: DataEvent<{ path: string }>) {
|
|
59
63
|
await this.dash.storage.file.copy(event.data.path, 'setlist', { overwrite: true });
|
|
60
64
|
await this.actions.refresh();
|
|
61
65
|
},
|
|
66
|
+
// A folder with files in it needs recursive.
|
|
62
67
|
async clearSetlist() {
|
|
63
68
|
await this.dash.storage.file.remove('setlist', { recursive: true });
|
|
64
69
|
await this.actions.refresh();
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
<View>
|
|
2
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>
|
|
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. Like the cache and secrets, it isn't backed up, so keep what matters on the dot's server too.</Text>
|
|
4
4
|
|
|
5
5
|
<View gap="small">
|
|
6
6
|
<Text variant="subtitle">write, read and list</Text>
|
|
@@ -6,8 +6,12 @@ import state from './state';
|
|
|
6
6
|
export default definePage({
|
|
7
7
|
initialState: state,
|
|
8
8
|
configs: { navigationBar: { title: 'Photos, files and scanning' } },
|
|
9
|
-
//
|
|
9
|
+
// Each call here needs a permission in dot.json. Dash asks the person the first time. If dot.json
|
|
10
|
+
// doesn't list it, or the person says no, the call rejects with 'denied'. unlessCancelled gives
|
|
11
|
+
// undefined when the person closes the picker or scanner.
|
|
10
12
|
actions: {
|
|
13
|
+
// Resolves with a DotFile: a JPEG of at most 2048 pixels on its longest side, without its
|
|
14
|
+
// location. A phone without a camera rejects with 'unsupported'.
|
|
11
15
|
async pickPhoto(_state, event: DataEvent<{ source: PhotoSource }>) {
|
|
12
16
|
try {
|
|
13
17
|
const photo = await unlessCancelled(
|
|
@@ -22,6 +26,7 @@ export default definePage({
|
|
|
22
26
|
this.setState({ note: errorMessage(error) });
|
|
23
27
|
}
|
|
24
28
|
},
|
|
29
|
+
// A file larger than 25 MB rejects with 'invalid'.
|
|
25
30
|
async pickTicket() {
|
|
26
31
|
try {
|
|
27
32
|
const ticket = await unlessCancelled(
|
|
@@ -51,6 +56,7 @@ export default definePage({
|
|
|
51
56
|
this.setState({ note: errorMessage(error) });
|
|
52
57
|
}
|
|
53
58
|
},
|
|
59
|
+
// Resolves with { text, format }. Only QR codes for now.
|
|
54
60
|
async scanTicket() {
|
|
55
61
|
try {
|
|
56
62
|
const scanned = await unlessCancelled(this.dash.media.scanCode());
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import type { DotFile } from '@monimesl/dot-runtime';
|
|
2
2
|
|
|
3
3
|
export default {
|
|
4
|
+
// A DotFile has a name, a size in bytes, a contentType and a url to show it with. It's temporary,
|
|
5
|
+
// so it goes when the dot closes.
|
|
4
6
|
photo: null as DotFile | null,
|
|
5
7
|
photoDetails: '',
|
|
6
8
|
ticket: null as DotFile | null,
|
|
@@ -87,6 +87,8 @@ export default definePage({
|
|
|
87
87
|
this.setState({ connection: errorMessage(error) });
|
|
88
88
|
}
|
|
89
89
|
},
|
|
90
|
+
// Like fetch, it resolves with any answer, even a 404, so check ok. It rejects only when no
|
|
91
|
+
// answer comes, and with 'denied' for a host that network.hosts in dot.json doesn't list.
|
|
90
92
|
async searchAsText(state) {
|
|
91
93
|
try {
|
|
92
94
|
const response = await this.dash.network.http.request({
|
|
@@ -113,6 +115,7 @@ export default definePage({
|
|
|
113
115
|
this.setState({ limit: state.limit + PAGE });
|
|
114
116
|
await this.actions.load();
|
|
115
117
|
},
|
|
118
|
+
// With as: 'json', an answer that isn't JSON rejects with 'failed'.
|
|
116
119
|
async load(state) {
|
|
117
120
|
this.setState({ searching: true, note: '' });
|
|
118
121
|
try {
|
|
@@ -153,7 +156,8 @@ export default definePage({
|
|
|
153
156
|
return;
|
|
154
157
|
}
|
|
155
158
|
try {
|
|
156
|
-
//
|
|
159
|
+
// Resolves with ok and a temporary file for a 2xx answer of up to 256 MB. The catalog's
|
|
160
|
+
// artwork comes in any size its address names.
|
|
157
161
|
const response = await this.dash.network.http.download({
|
|
158
162
|
url: release.artwork.replace('100x100bb', '300x300bb'),
|
|
159
163
|
name: 'artwork.jpg',
|
|
@@ -97,7 +97,7 @@
|
|
|
97
97
|
|
|
98
98
|
<View gap="small">
|
|
99
99
|
<Text variant="subtitle">Hosts dot.json doesn't list</Text>
|
|
100
|
-
<Text variant="caption" tone="muted">Dash turns the request down.</Text>
|
|
100
|
+
<Text variant="caption" tone="muted">Dash turns the request down, with the code denied.</Text>
|
|
101
101
|
<View variant="card" gap="small">
|
|
102
102
|
<Button variant="secondary" onClick="tryUnlistedHost">Reach example.com</Button>
|
|
103
103
|
<If condition="{{ note }}">
|
|
@@ -26,8 +26,8 @@ export interface CaseGroup {
|
|
|
26
26
|
cases: PaymentCase[];
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
-
// SLE 0.50 a ticket, in minor units as Monime counts money
|
|
30
|
-
// little: the two tickets the page starts with come to SLE 1.00.
|
|
29
|
+
// SLE 0.50 a ticket, in minor units as Monime counts money. In Dash the money is real, so the
|
|
30
|
+
// cases cost little: the two tickets the page starts with come to SLE 1.00.
|
|
31
31
|
const TICKET_PRICE = 50;
|
|
32
32
|
const SAMPLE_PHONE_NUMBER = '+23276123456';
|
|
33
33
|
const SAMPLE_ACCOUNT_ID = 'fac-k6VgVTfSmLruHpiQgMg4DUeySZx';
|
|
@@ -299,7 +299,7 @@ export const GROUPS: CaseGroup[] = [
|
|
|
299
299
|
id: 'invalid',
|
|
300
300
|
title: 'Refused before Dash',
|
|
301
301
|
subtitle:
|
|
302
|
-
'Every host checks these the same way
|
|
302
|
+
'Every host checks these the same way. Each rejects as invalid, with a message that starts as shown.',
|
|
303
303
|
cases: [
|
|
304
304
|
{
|
|
305
305
|
id: 'no-items',
|
|
@@ -52,6 +52,9 @@ export default definePage({
|
|
|
52
52
|
phoneNumber: state.phoneNumber,
|
|
53
53
|
financialAccountId: state.financialAccountId,
|
|
54
54
|
};
|
|
55
|
+
// Once the person picks a method, it resolves with how the payment ended: completed,
|
|
56
|
+
// pending, expired or cancelled. Before that, it rejects with 'cancelled' if the sheet
|
|
57
|
+
// closes, and with 'invalid' or 'denied' if Monime won't take the order.
|
|
55
58
|
const request = this.dash.payments.request(paymentCase.options(input, reference));
|
|
56
59
|
const cancelAfter = paymentCase.cancelAfterMilliseconds;
|
|
57
60
|
if (cancelAfter !== undefined) {
|
|
@@ -76,6 +79,7 @@ export default definePage({
|
|
|
76
79
|
runs: added(current.runs, paymentCase.title, outcome),
|
|
77
80
|
}));
|
|
78
81
|
},
|
|
82
|
+
// get resolves with the payment for a reference, or undefined if there is none.
|
|
79
83
|
async checkPayment(state) {
|
|
80
84
|
const title = 'Check the last payment';
|
|
81
85
|
const found = await lookedUp(this.dash.payments.get(state.reference));
|
|
@@ -126,6 +130,8 @@ function resultOutcome(result: PaymentResult): string {
|
|
|
126
130
|
return `${result.status} by ${result.method}, ${moneyText(result.amount)}`;
|
|
127
131
|
}
|
|
128
132
|
|
|
133
|
+
// isDashError(error) is true for an error from a Dash call, so it has a code. Give it codes, such
|
|
134
|
+
// as isDashError(error, 'invalid'), to check for those only.
|
|
129
135
|
function errorOutcome(error: unknown): string {
|
|
130
136
|
if (isDashError(error)) {
|
|
131
137
|
return `${error.code}: ${error.message}`;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
<View>
|
|
2
2
|
<Text variant="title">dash.payments</Text>
|
|
3
|
-
<Text>Takes a payment for an order through Dash's own payment sheet. The person picks a linked account, a card or mobile money, and the money goes to the Space that owns the dot. It needs the payments permission in dot.json. Each case below says what should happen. In Dash the money is real, so the orders cost a few cents.</Text>
|
|
3
|
+
<Text>Takes a payment for an order through Dash's own payment sheet. The person picks a linked account, a card or mobile money, and the money goes to the Space that owns the dot. It needs the payments permission in dot.json, which Dash doesn't ask the person for. Each case below says what should happen. In Dash the money is real, so the orders cost a few cents.</Text>
|
|
4
4
|
|
|
5
5
|
<View gap="small">
|
|
6
6
|
<Text variant="subtitle">The order</Text>
|
|
@@ -16,6 +16,7 @@ export default definePage({
|
|
|
16
16
|
},
|
|
17
17
|
actions: {
|
|
18
18
|
async refresh() {
|
|
19
|
+
// null if there's no secret by that name.
|
|
19
20
|
const token = await this.dash.storage.secret.get(SESSION);
|
|
20
21
|
const signedInAs = await this.dash.storage.cache.get<string>(NAME);
|
|
21
22
|
this.setState({
|
|
@@ -23,6 +24,8 @@ export default definePage({
|
|
|
23
24
|
signedInAs: signedInAs ?? '',
|
|
24
25
|
});
|
|
25
26
|
},
|
|
27
|
+
// A value over 4 KB rejects with 'invalid'. A 33rd secret rejects with 'failed', so remove
|
|
28
|
+
// one first.
|
|
26
29
|
async signIn(state) {
|
|
27
30
|
// A real dot gets its token from its own API, once the person signs in there.
|
|
28
31
|
const token = `demo-${Math.random().toString(36).slice(2)}${Date.now().toString(36)}`;
|
|
@@ -37,6 +37,7 @@ export default definePage({
|
|
|
37
37
|
this.setState({ note: errorMessage(error) });
|
|
38
38
|
}
|
|
39
39
|
},
|
|
40
|
+
// Only https addresses. Anything else rejects with 'invalid'.
|
|
40
41
|
async openDrizilik() {
|
|
41
42
|
try {
|
|
42
43
|
await this.dash.system.openUrl('https://drizilik.com/');
|
|
@@ -44,6 +45,7 @@ export default definePage({
|
|
|
44
45
|
this.setState({ note: errorMessage(error) });
|
|
45
46
|
}
|
|
46
47
|
},
|
|
48
|
+
// A dot's logic has no timers, so Dash keeps the time.
|
|
47
49
|
async countDown() {
|
|
48
50
|
for (let seconds = 10; seconds > 0; seconds--) {
|
|
49
51
|
this.setState({ showIn: seconds });
|
|
@@ -68,6 +70,8 @@ export default definePage({
|
|
|
68
70
|
this.setState({ note: errorMessage(error) });
|
|
69
71
|
}
|
|
70
72
|
},
|
|
73
|
+
// A number may have digits, spaces, dashes, +, * and #. Anything else rejects with 'invalid'.
|
|
74
|
+
// For a USSD code, Dash asks the person first, and rejects with 'cancelled' if they say no.
|
|
71
75
|
async dialNumber(state) {
|
|
72
76
|
try {
|
|
73
77
|
await this.dash.system.dial(state.number);
|
|
@@ -76,6 +80,7 @@ export default definePage({
|
|
|
76
80
|
this.setState({ note: errorMessage(error) });
|
|
77
81
|
}
|
|
78
82
|
},
|
|
83
|
+
// A phone without a torch rejects with 'unsupported'.
|
|
79
84
|
async toggleTorch(state) {
|
|
80
85
|
const torchOn = !state.torchOn;
|
|
81
86
|
try {
|
|
@@ -58,7 +58,7 @@
|
|
|
58
58
|
|
|
59
59
|
<View gap="small">
|
|
60
60
|
<Text variant="subtitle">openUrl</Text>
|
|
61
|
-
<Text variant="caption" tone="muted">Opens
|
|
61
|
+
<Text variant="caption" tone="muted">Opens an https web page in the phone's browser.</Text>
|
|
62
62
|
<View variant="card">
|
|
63
63
|
<Button variant="secondary" onClick="openDrizilik">Open Drizilik's website</Button>
|
|
64
64
|
</View>
|
|
@@ -66,7 +66,7 @@
|
|
|
66
66
|
|
|
67
67
|
<View gap="small">
|
|
68
68
|
<Text variant="subtitle">delay</Text>
|
|
69
|
-
<Text variant="caption" tone="muted">Waits for a number of milliseconds.
|
|
69
|
+
<Text variant="caption" tone="muted">Waits for a number of milliseconds. Like any call still running, it stops when the page closes.</Text>
|
|
70
70
|
<View variant="card">
|
|
71
71
|
<Button variant="secondary" onClick="countDown" disabled="{{ showIn > 0 }}">{{ showIn > 0 ? 'Showtime in ' + showIn + 's' : 'Count down to showtime' }}</Button>
|
|
72
72
|
</View>
|
|
@@ -74,7 +74,7 @@
|
|
|
74
74
|
|
|
75
75
|
<View gap="small">
|
|
76
76
|
<Text variant="subtitle">dial</Text>
|
|
77
|
-
<Text variant="caption" tone="muted">Opens the phone's dialer with a number ready, or a USSD code such as *715#. The person taps Call.</Text>
|
|
77
|
+
<Text variant="caption" tone="muted">Opens the phone's dialer with a number ready, or a USSD code such as *715#. The person taps Call. For a code, Dash asks them first.</Text>
|
|
78
78
|
<View variant="card">
|
|
79
79
|
<Input label="Number or code" type="phone" value="{{ number }}" onChange="setNumber" onSubmit="dialNumber" />
|
|
80
80
|
<Button variant="secondary" onClick="dialNumber" disabled="{{ !number }}">Open the dialer</Button>
|
|
@@ -83,7 +83,7 @@
|
|
|
83
83
|
|
|
84
84
|
<View gap="small">
|
|
85
85
|
<Text variant="subtitle">torch</Text>
|
|
86
|
-
<Text variant="caption" tone="muted">Turns the camera's light on or off.</Text>
|
|
86
|
+
<Text variant="caption" tone="muted">Turns the camera's light on or off, on a phone that has one.</Text>
|
|
87
87
|
<View variant="card">
|
|
88
88
|
<Button variant="secondary" onClick="toggleTorch">{{ torchOn ? 'Turn the torch off' : 'Lights up for the slow song' }}</Button>
|
|
89
89
|
</View>
|
|
@@ -5,9 +5,13 @@ import state from './state';
|
|
|
5
5
|
export default definePage({
|
|
6
6
|
initialState: state,
|
|
7
7
|
configs: { navigationBar: { title: 'Dialogs and pickers' } },
|
|
8
|
+
// A this.dash call goes to Dash and answers later, so await it. If it fails, it throws a
|
|
9
|
+
// DashError with a code that says why, such as 'cancelled' or 'denied'. errorMessage(error)
|
|
10
|
+
// gives text to show for any error. The Calls and errors page lists every code.
|
|
8
11
|
actions: {
|
|
9
12
|
async greet() {
|
|
10
13
|
try {
|
|
14
|
+
// Resolves with { action }: the id of the button tapped.
|
|
11
15
|
const { action } = await this.dash.ui.alert({
|
|
12
16
|
title: 'Kushé',
|
|
13
17
|
message: 'Kushé means hello in Krio, and tenki means thanks.',
|
|
@@ -36,7 +40,8 @@ export default definePage({
|
|
|
36
40
|
this.setState({ note: errorMessage(error) });
|
|
37
41
|
}
|
|
38
42
|
},
|
|
39
|
-
//
|
|
43
|
+
// Closing a picker rejects with 'cancelled'. That isn't a problem, so unlessCancelled turns it
|
|
44
|
+
// into undefined. Any other error still throws. A min after max rejects with 'invalid'.
|
|
40
45
|
async chooseNight(state) {
|
|
41
46
|
try {
|
|
42
47
|
const night = await unlessCancelled(
|
|
@@ -3,6 +3,7 @@ import state from './state';
|
|
|
3
3
|
export default definePage({
|
|
4
4
|
initialState: state,
|
|
5
5
|
actions: {
|
|
6
|
+
// Each row's data-page names its page. The Events and Navigation pages show how this works.
|
|
6
7
|
open(_state, event: DataEvent<{ page: PageId }>) {
|
|
7
8
|
this.navigation.push(event.data.page);
|
|
8
9
|
},
|
|
@@ -40,7 +40,7 @@ export default {
|
|
|
40
40
|
{
|
|
41
41
|
page: 'basics/events',
|
|
42
42
|
title: 'Events',
|
|
43
|
-
covers: 'onTap, onChange, event.detail, data
|
|
43
|
+
covers: 'onClick, onTap, onChange, event.detail, event.data',
|
|
44
44
|
icon: 'bolt',
|
|
45
45
|
},
|
|
46
46
|
{
|
|
@@ -216,7 +216,7 @@ export default {
|
|
|
216
216
|
{
|
|
217
217
|
page: 'dash/files',
|
|
218
218
|
title: 'dash.storage.file',
|
|
219
|
-
covers: 'write, read, list, move, copy, remove, save, stat, open, usage',
|
|
219
|
+
covers: 'write, read, list, createFolder, move, copy, remove, save, stat, open, usage',
|
|
220
220
|
icon: 'file',
|
|
221
221
|
},
|
|
222
222
|
{
|
|
@@ -234,7 +234,7 @@ export default {
|
|
|
234
234
|
{
|
|
235
235
|
page: 'dash/calls',
|
|
236
236
|
title: 'Calls and errors',
|
|
237
|
-
covers: 'timeout, cancel, DashError, isDashError',
|
|
237
|
+
covers: 'timeout, cancel, DashError, isDashError, supports, dash.dot.close',
|
|
238
238
|
icon: 'warning',
|
|
239
239
|
},
|
|
240
240
|
],
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
# Public
|
|
2
|
-
#
|
|
1
|
+
# Public settings for the dot. Code reads each one from `env`, such as `env.GREETING`.
|
|
2
|
+
# They're built into the dot, so anyone who has it can read them. Never put a secret here.
|
|
3
3
|
GREETING=Hello from your dot
|
|
@@ -2,6 +2,8 @@ import state from './state';
|
|
|
2
2
|
|
|
3
3
|
export default definePage({
|
|
4
4
|
initialState: state,
|
|
5
|
+
// A mutation gets the state and returns the fields to change, and the view follows. For steps
|
|
6
|
+
// that wait, such as a Dash call, use actions instead.
|
|
5
7
|
mutations: {
|
|
6
8
|
increment: (state) => ({ count: state.count + 1 }),
|
|
7
9
|
reset: () => ({ count: 0 }),
|
|
@@ -3,11 +3,12 @@
|
|
|
3
3
|
<Image src="statics/logo.png" alt="Logo" width="40" height="40" />
|
|
4
4
|
<Text variant="title">{{ greeting }}</Text>
|
|
5
5
|
</View>
|
|
6
|
-
<Text>Change pages/index/view.dml and save
|
|
6
|
+
<Text>Change pages/index/view.dml and save. The simulator shows the change at once.</Text>
|
|
7
7
|
|
|
8
8
|
<View variant="card" align="center" gap="medium">
|
|
9
9
|
<Text variant="title">{{ count }}</Text>
|
|
10
10
|
<Text tone="muted">{{ count === 1 ? 'tap' : 'taps' }} so far</Text>
|
|
11
|
+
<!-- onClick names the mutation or action in controller.ts to run. -->
|
|
11
12
|
<View direction="row" gap="small">
|
|
12
13
|
<Button variant="secondary" onClick="reset" disabled="{{ count === 0 }}">Reset</Button>
|
|
13
14
|
<Button onClick="increment">Tap me</Button>
|