@monimesl/dot-cli 0.1.0-alpha.2 → 0.1.0-alpha.3
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/dist/cli.js +151 -35
- package/dist/cli.js.map +1 -1
- package/dist/server-z7M4FLjf.js +10886 -0
- package/dist/server-z7M4FLjf.js.map +1 -0
- package/dist/template/.env +3 -3
- package/dist/template/dot.ts +14 -1
- package/dist/template/pages/device/controller.ts +70 -0
- package/dist/template/pages/device/state.ts +7 -0
- package/dist/template/pages/device/view.dml +44 -0
- package/dist/template/pages/feedback/controller.ts +16 -0
- package/dist/template/pages/feedback/state.ts +7 -0
- package/dist/template/pages/feedback/view.dml +87 -0
- package/dist/template/pages/files/controller.ts +47 -0
- package/dist/template/pages/files/state.ts +11 -0
- package/dist/template/pages/files/view.dml +35 -0
- package/dist/template/pages/forms/controller.ts +44 -0
- package/dist/template/pages/forms/state.ts +15 -0
- package/dist/template/pages/forms/view.dml +39 -0
- package/dist/template/pages/images/controller.ts +15 -0
- package/dist/template/pages/images/state.ts +7 -0
- package/dist/template/pages/images/view.dml +38 -0
- package/dist/template/pages/index/controller.ts +2 -59
- package/dist/template/pages/index/state.ts +93 -12
- package/dist/template/pages/index/view.dml +17 -63
- package/dist/template/pages/layout/controller.ts +23 -0
- package/dist/template/pages/layout/state.ts +12 -0
- package/dist/template/pages/layout/view.dml +63 -0
- package/dist/template/pages/lists/controller.ts +36 -0
- package/dist/template/pages/lists/state.ts +96 -0
- package/dist/template/pages/lists/view.dml +59 -0
- package/dist/template/pages/navigation/controller.ts +42 -0
- package/dist/template/pages/navigation/state.ts +13 -0
- package/dist/template/pages/navigation/view.dml +24 -0
- package/dist/template/pages/network/controller.ts +107 -0
- package/dist/template/pages/network/state.ts +15 -0
- package/dist/template/pages/network/view.dml +56 -0
- package/dist/template/pages/state/controller.ts +22 -0
- package/dist/template/pages/state/state.ts +10 -0
- package/dist/template/pages/state/view.dml +33 -0
- package/dist/template/pages/text/controller.ts +10 -0
- package/dist/template/pages/text/state.ts +7 -0
- package/dist/template/pages/text/view.dml +49 -0
- package/package.json +10 -6
- package/dist/template/pages/help/tips/controller.ts +0 -30
- package/dist/template/pages/help/tips/state.ts +0 -11
- package/dist/template/pages/help/tips/view.dml +0 -15
package/dist/template/.env
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
# Public configuration for the dot.
|
|
2
|
-
#
|
|
3
|
-
|
|
1
|
+
# Public configuration for the dot. Anyone with the dot can read these variables: never put a
|
|
2
|
+
# secret here. Read them in the service as `env.GREETING`.
|
|
3
|
+
GREETING=This message comes from the dot's .env file.
|
package/dist/template/dot.ts
CHANGED
|
@@ -1,6 +1,19 @@
|
|
|
1
1
|
import { logger } from '@monimesl/dot-runtime';
|
|
2
2
|
|
|
3
3
|
export default defineDot({
|
|
4
|
-
pages: [
|
|
4
|
+
pages: [
|
|
5
|
+
'index',
|
|
6
|
+
'layout',
|
|
7
|
+
'text',
|
|
8
|
+
'images',
|
|
9
|
+
'lists',
|
|
10
|
+
'forms',
|
|
11
|
+
'feedback',
|
|
12
|
+
'state',
|
|
13
|
+
'navigation',
|
|
14
|
+
'device',
|
|
15
|
+
'network',
|
|
16
|
+
'files',
|
|
17
|
+
],
|
|
5
18
|
plugins: [logger()],
|
|
6
19
|
});
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import state from './state';
|
|
2
|
+
|
|
3
|
+
export default definePage({
|
|
4
|
+
initialState: state,
|
|
5
|
+
mutations: {
|
|
6
|
+
clearToast: () => ({ toast: '' }),
|
|
7
|
+
},
|
|
8
|
+
actions: {
|
|
9
|
+
async greet() {
|
|
10
|
+
try {
|
|
11
|
+
const { action } = await this.dash.ui.alert({
|
|
12
|
+
title: 'Hello',
|
|
13
|
+
message: env.GREETING,
|
|
14
|
+
actions: [
|
|
15
|
+
{ id: 'nice', label: 'Nice' },
|
|
16
|
+
{ id: 'close', label: 'Close', role: 'cancel' },
|
|
17
|
+
],
|
|
18
|
+
});
|
|
19
|
+
this.setState({ choice: action });
|
|
20
|
+
} catch (error) {
|
|
21
|
+
this.setState({ note: (error as Error).message });
|
|
22
|
+
}
|
|
23
|
+
},
|
|
24
|
+
async confirmDelete() {
|
|
25
|
+
try {
|
|
26
|
+
const { action } = await this.dash.ui.alert({
|
|
27
|
+
title: 'Delete everything?',
|
|
28
|
+
message: "This can't be undone.",
|
|
29
|
+
actions: [
|
|
30
|
+
{ id: 'keep', label: 'Keep', role: 'cancel' },
|
|
31
|
+
{ id: 'delete', label: 'Delete', role: 'destructive' },
|
|
32
|
+
],
|
|
33
|
+
});
|
|
34
|
+
this.setState({ choice: action });
|
|
35
|
+
} catch (error) {
|
|
36
|
+
this.setState({ note: (error as Error).message });
|
|
37
|
+
}
|
|
38
|
+
},
|
|
39
|
+
async vibrate(_state, event: DotEvent<unknown, { style: 'light' | 'success' | 'error' }>) {
|
|
40
|
+
await this.dash.system.vibrate({ style: event.data.style });
|
|
41
|
+
},
|
|
42
|
+
async copyAccount() {
|
|
43
|
+
await this.dash.system.copyText('AC-2041');
|
|
44
|
+
this.setState({ toast: 'Account number copied' });
|
|
45
|
+
},
|
|
46
|
+
async shareLink() {
|
|
47
|
+
try {
|
|
48
|
+
await this.dash.system.share({ title: 'Dot', url: 'https://github.com/monime-lab/dot' });
|
|
49
|
+
} catch (error) {
|
|
50
|
+
this.setState({ note: (error as Error).message });
|
|
51
|
+
}
|
|
52
|
+
},
|
|
53
|
+
async sendCode() {
|
|
54
|
+
for (let seconds = 10; seconds > 0; seconds--) {
|
|
55
|
+
this.setState({ resendIn: seconds });
|
|
56
|
+
await this.dash.system.delay(1000);
|
|
57
|
+
}
|
|
58
|
+
this.setState({ resendIn: 0 });
|
|
59
|
+
},
|
|
60
|
+
async toggleTorch(state) {
|
|
61
|
+
const next = state.torch === 'on' ? 'off' : 'on';
|
|
62
|
+
try {
|
|
63
|
+
await this.dash.system.torch(next);
|
|
64
|
+
this.setState({ torch: next, note: '' });
|
|
65
|
+
} catch (error) {
|
|
66
|
+
this.setState({ note: (error as Error).message });
|
|
67
|
+
}
|
|
68
|
+
},
|
|
69
|
+
},
|
|
70
|
+
});
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
<View>
|
|
2
|
+
<Text variant="title">Alerts and device</Text>
|
|
3
|
+
<Text>Dash draws these, not the dot: a dot calls them through the global dash object.</Text>
|
|
4
|
+
|
|
5
|
+
<Text variant="caption" tone="muted">dash.ui.alert</Text>
|
|
6
|
+
<View variant="card" gap="small">
|
|
7
|
+
<View direction="row" wrap gap="small">
|
|
8
|
+
<Button onClick="greet">Say hello</Button>
|
|
9
|
+
<Button variant="secondary" onClick="confirmDelete">Ask to delete</Button>
|
|
10
|
+
</View>
|
|
11
|
+
<If condition="{{ choice }}">
|
|
12
|
+
<Text>You chose "{{ choice }}"</Text>
|
|
13
|
+
</If>
|
|
14
|
+
<Text variant="caption" tone="muted">The hello message is GREETING in the dot's .env file.</Text>
|
|
15
|
+
</View>
|
|
16
|
+
|
|
17
|
+
<Text variant="caption" tone="muted">dash.system.vibrate</Text>
|
|
18
|
+
<View variant="card" direction="row" wrap gap="small">
|
|
19
|
+
<Button variant="secondary" data-style="light" onClick="vibrate">Light</Button>
|
|
20
|
+
<Button variant="secondary" data-style="success" onClick="vibrate">Success</Button>
|
|
21
|
+
<Button variant="secondary" data-style="error" onClick="vibrate">Error</Button>
|
|
22
|
+
</View>
|
|
23
|
+
|
|
24
|
+
<Text variant="caption" tone="muted">dash.system.copyText and dash.system.share</Text>
|
|
25
|
+
<View variant="card" direction="row" wrap gap="small">
|
|
26
|
+
<Button variant="secondary" onClick="copyAccount">Copy AC-2041</Button>
|
|
27
|
+
<Button variant="secondary" onClick="shareLink">Share a link</Button>
|
|
28
|
+
</View>
|
|
29
|
+
|
|
30
|
+
<Text variant="caption" tone="muted">dash.system.delay</Text>
|
|
31
|
+
<View variant="card">
|
|
32
|
+
<Button variant="secondary" onClick="sendCode" disabled="{{ resendIn > 0 }}">{{ resendIn > 0 ? 'Resend in ' + resendIn + 's' : 'Send a code' }}</Button>
|
|
33
|
+
</View>
|
|
34
|
+
|
|
35
|
+
<Text variant="caption" tone="muted">dash.system.torch</Text>
|
|
36
|
+
<View variant="card" gap="small">
|
|
37
|
+
<Button variant="secondary" onClick="toggleTorch">{{ torch === 'on' ? 'Turn the torch off' : 'Turn the torch on' }}</Button>
|
|
38
|
+
<If condition="{{ note }}">
|
|
39
|
+
<Text variant="caption" tone="muted">{{ note }}</Text>
|
|
40
|
+
</If>
|
|
41
|
+
</View>
|
|
42
|
+
|
|
43
|
+
<Toast message="{{ toast }}" tone="success" onHide="clearToast" />
|
|
44
|
+
</View>
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import state from './state';
|
|
2
|
+
|
|
3
|
+
export default definePage({
|
|
4
|
+
initialState: state,
|
|
5
|
+
mutations: {
|
|
6
|
+
advance: (state) => ({ progress: Math.min(100, state.progress + 20) }),
|
|
7
|
+
restart: () => ({ progress: 0 }),
|
|
8
|
+
toggleBusy: (state) => ({ busy: !state.busy }),
|
|
9
|
+
toggleLoaded: (state) => ({ loaded: !state.loaded }),
|
|
10
|
+
showToast: (_state, event: DotEvent<unknown, { message: string; tone: string }>) => ({
|
|
11
|
+
toast: event.data.message,
|
|
12
|
+
toastTone: event.data.tone,
|
|
13
|
+
}),
|
|
14
|
+
clearToast: () => ({ toast: '' }),
|
|
15
|
+
},
|
|
16
|
+
});
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
<View>
|
|
2
|
+
<Text variant="title">Feedback</Text>
|
|
3
|
+
<Text>Show that something is happening, how far along it is, and how it went.</Text>
|
|
4
|
+
|
|
5
|
+
<View gap="small">
|
|
6
|
+
<Text variant="caption" tone="muted">Spinners, while something loads</Text>
|
|
7
|
+
<View variant="card" direction="row" gap="large" align="center">
|
|
8
|
+
<Spinner size="small" />
|
|
9
|
+
<Spinner />
|
|
10
|
+
<Spinner size="large" />
|
|
11
|
+
</View>
|
|
12
|
+
</View>
|
|
13
|
+
|
|
14
|
+
<View gap="small">
|
|
15
|
+
<Text variant="caption" tone="muted">Skeletons, in the shape of what's loading</Text>
|
|
16
|
+
<View variant="card">
|
|
17
|
+
<If condition="{{ loaded }}">
|
|
18
|
+
<View direction="row" align="center" gap="large">
|
|
19
|
+
<Image src="statics/logo.png" alt="Logo" width="40" height="40" shape="circle" />
|
|
20
|
+
<View gap="none">
|
|
21
|
+
<Text weight="medium">Dot Showcase</Text>
|
|
22
|
+
<Text variant="caption" tone="muted">Loaded, in place of the skeleton</Text>
|
|
23
|
+
</View>
|
|
24
|
+
</View>
|
|
25
|
+
<Image src="statics/logo.png" alt="Banner" width="320" height="80" fit="contain" />
|
|
26
|
+
</If>
|
|
27
|
+
<Else>
|
|
28
|
+
<View direction="row" align="center" gap="large">
|
|
29
|
+
<Skeleton shape="circle" />
|
|
30
|
+
<Skeleton lines="2" />
|
|
31
|
+
</View>
|
|
32
|
+
<Skeleton shape="box" height="80" />
|
|
33
|
+
</Else>
|
|
34
|
+
<Switch label="Loaded" checked="{{ loaded }}" onChange="toggleLoaded" />
|
|
35
|
+
</View>
|
|
36
|
+
</View>
|
|
37
|
+
|
|
38
|
+
<View gap="small">
|
|
39
|
+
<Text variant="caption" tone="muted">Toasts, which go away on their own</Text>
|
|
40
|
+
<View direction="row" gap="small">
|
|
41
|
+
<Button variant="secondary" data-message="Saved" data-tone="success" onClick="showToast">Saved</Button>
|
|
42
|
+
<Button variant="secondary" data-message="You're offline" data-tone="warning" onClick="showToast">Offline</Button>
|
|
43
|
+
</View>
|
|
44
|
+
</View>
|
|
45
|
+
|
|
46
|
+
<View gap="small">
|
|
47
|
+
<Text variant="caption" tone="muted">A progress bar, from 0 to 100</Text>
|
|
48
|
+
<View variant="card">
|
|
49
|
+
<ProgressBar value="{{ progress }}" />
|
|
50
|
+
<Text>{{ progress }}% done</Text>
|
|
51
|
+
<View direction="row" gap="small">
|
|
52
|
+
<Button variant="secondary" onClick="advance" disabled="{{ progress >= 100 }}">Advance</Button>
|
|
53
|
+
<Button variant="secondary" onClick="restart" disabled="{{ progress === 0 }}">Restart</Button>
|
|
54
|
+
</View>
|
|
55
|
+
</View>
|
|
56
|
+
</View>
|
|
57
|
+
|
|
58
|
+
<View gap="small">
|
|
59
|
+
<Text variant="caption" tone="muted">A button that's busy ignores taps</Text>
|
|
60
|
+
<View variant="card">
|
|
61
|
+
<Button loading="{{ busy }}">{{ busy ? 'Paying…' : 'Pay Le 50,000' }}</Button>
|
|
62
|
+
<Switch label="Busy" checked="{{ busy }}" onChange="toggleBusy" />
|
|
63
|
+
</View>
|
|
64
|
+
</View>
|
|
65
|
+
|
|
66
|
+
<View gap="small">
|
|
67
|
+
<Text variant="caption" tone="muted">Notices, in the flow of the page</Text>
|
|
68
|
+
<Notice title="Heads up">Your meter number is on the bill.</Notice>
|
|
69
|
+
<Notice tone="info" title="New">You can now pay water bills.</Notice>
|
|
70
|
+
<Notice tone="success" title="Paid">Le 50,000 for electricity.</Notice>
|
|
71
|
+
<Notice tone="warning" title="Almost out">Your wallet has Le 2,000 left.</Notice>
|
|
72
|
+
<Notice tone="danger" title="Payment failed">The bank didn't answer. Try again.</Notice>
|
|
73
|
+
</View>
|
|
74
|
+
|
|
75
|
+
<View gap="small">
|
|
76
|
+
<Text variant="caption" tone="muted">Badges, for a status</Text>
|
|
77
|
+
<View direction="row" wrap gap="small">
|
|
78
|
+
<Badge>Draft</Badge>
|
|
79
|
+
<Badge tone="info">New</Badge>
|
|
80
|
+
<Badge tone="success">Paid</Badge>
|
|
81
|
+
<Badge tone="warning">Pending</Badge>
|
|
82
|
+
<Badge tone="danger">Failed</Badge>
|
|
83
|
+
</View>
|
|
84
|
+
</View>
|
|
85
|
+
|
|
86
|
+
<Toast message="{{ toast }}" tone="{{ toastTone }}" onHide="clearToast" />
|
|
87
|
+
</View>
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import type { Note } from './state';
|
|
2
|
+
import state from './state';
|
|
3
|
+
|
|
4
|
+
const PICTURE = 'pictures/octocat.png';
|
|
5
|
+
|
|
6
|
+
export default definePage({
|
|
7
|
+
initialState: state,
|
|
8
|
+
hooks: {
|
|
9
|
+
async onShow() {
|
|
10
|
+
await this.actions.refresh();
|
|
11
|
+
},
|
|
12
|
+
},
|
|
13
|
+
mutations: {
|
|
14
|
+
setDraft: (_state, event: DotEvent<{ value: string }>) => ({ draft: event.detail.value }),
|
|
15
|
+
},
|
|
16
|
+
actions: {
|
|
17
|
+
async refresh() {
|
|
18
|
+
const entries = await this.dash.file.list('', { recursive: true });
|
|
19
|
+
const notes: Note[] = [];
|
|
20
|
+
for (const entry of entries) {
|
|
21
|
+
if (entry.kind === 'file' && entry.path.startsWith('notes/')) {
|
|
22
|
+
notes.push({ path: entry.path, text: await this.dash.file.read(entry.path) });
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
const { used, quota } = await this.dash.file.usage();
|
|
26
|
+
const kept = await this.dash.file.stat(PICTURE);
|
|
27
|
+
this.setState({
|
|
28
|
+
notes,
|
|
29
|
+
usage: `${used} bytes used of ${quota / 1024 / 1024} MB`,
|
|
30
|
+
picture: kept ? (await this.dash.file.open(PICTURE)).url : '',
|
|
31
|
+
});
|
|
32
|
+
},
|
|
33
|
+
async save(state) {
|
|
34
|
+
await this.dash.file.write(`notes/${Date.now()}.txt`, state.draft);
|
|
35
|
+
this.setState({ draft: '' });
|
|
36
|
+
await this.actions.refresh();
|
|
37
|
+
},
|
|
38
|
+
async clearNotes() {
|
|
39
|
+
await this.dash.file.remove('notes', { recursive: true });
|
|
40
|
+
await this.actions.refresh();
|
|
41
|
+
},
|
|
42
|
+
async forgetPicture() {
|
|
43
|
+
await this.dash.file.remove(PICTURE);
|
|
44
|
+
await this.actions.refresh();
|
|
45
|
+
},
|
|
46
|
+
},
|
|
47
|
+
});
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
<View>
|
|
2
|
+
<Text variant="title">Files</Text>
|
|
3
|
+
<Text>A dot has a folder of its own that no other dot sees. What it keeps there lasts between runs.</Text>
|
|
4
|
+
|
|
5
|
+
<Text variant="caption" tone="muted">Notes, written as text files</Text>
|
|
6
|
+
<View variant="card" gap="small">
|
|
7
|
+
<Input label="Note" placeholder="Buy rice" value="{{ draft }}" maxLength="80" onChange="setDraft" onSubmit="save" />
|
|
8
|
+
<View direction="row" gap="small">
|
|
9
|
+
<Button onClick="save" disabled="{{ !draft }}">Save</Button>
|
|
10
|
+
<Button variant="secondary" onClick="clearNotes" disabled="{{ notes.length === 0 }}">Delete all</Button>
|
|
11
|
+
</View>
|
|
12
|
+
<For each="{{ notes }}" item="note" key="path">
|
|
13
|
+
<View gap="none">
|
|
14
|
+
<Text>{{ note.text }}</Text>
|
|
15
|
+
<Text variant="caption" tone="muted">{{ note.path }}</Text>
|
|
16
|
+
</View>
|
|
17
|
+
</For>
|
|
18
|
+
<If condition="{{ notes.length === 0 }}">
|
|
19
|
+
<Text variant="caption" tone="muted">No notes yet</Text>
|
|
20
|
+
</If>
|
|
21
|
+
</View>
|
|
22
|
+
|
|
23
|
+
<Text variant="caption" tone="muted">A picture kept from the Network page</Text>
|
|
24
|
+
<View variant="card" gap="small">
|
|
25
|
+
<If condition="{{ picture }}">
|
|
26
|
+
<Image src="{{ picture }}" alt="Kept picture" width="64" height="64" />
|
|
27
|
+
<Button variant="secondary" onClick="forgetPicture">Forget it</Button>
|
|
28
|
+
</If>
|
|
29
|
+
<Else>
|
|
30
|
+
<Text variant="caption" tone="muted">None yet: download one on the Network page and tap Keep.</Text>
|
|
31
|
+
</Else>
|
|
32
|
+
</View>
|
|
33
|
+
|
|
34
|
+
<Text variant="caption" tone="muted">{{ usage }}</Text>
|
|
35
|
+
</View>
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import state from './state';
|
|
2
|
+
|
|
3
|
+
type Typed = DotEvent<{ value: string }>;
|
|
4
|
+
type Toggled = DotEvent<{ checked: boolean }>;
|
|
5
|
+
|
|
6
|
+
export default definePage({
|
|
7
|
+
initialState: state,
|
|
8
|
+
mutations: {
|
|
9
|
+
setAccount: (_state, event: Typed) => ({ account: event.detail.value }),
|
|
10
|
+
setName: (_state, event: Typed) => ({ name: event.detail.value }),
|
|
11
|
+
setEmail: (_state, event: Typed) => ({ email: event.detail.value }),
|
|
12
|
+
setPhone: (_state, event: Typed) => ({ phone: event.detail.value }),
|
|
13
|
+
setAmount: (_state, event: Typed) => ({ amount: event.detail.value }),
|
|
14
|
+
setNote: (_state, event: Typed) => ({ note: event.detail.value }),
|
|
15
|
+
setPlan: (_state, event: Typed) => ({ plan: event.detail.value }),
|
|
16
|
+
setReceipts: (_state, event: Toggled) => ({ receipts: event.detail.checked }),
|
|
17
|
+
setAgreed: (_state, event: Toggled) => ({ agreed: event.detail.checked }),
|
|
18
|
+
},
|
|
19
|
+
actions: {
|
|
20
|
+
async send(state) {
|
|
21
|
+
const errors = {
|
|
22
|
+
phone: state.phone.length < 8 ? 'Enter a phone number, such as 076 123456' : '',
|
|
23
|
+
amount: Number(state.amount) >= 1000 ? '' : 'Send at least Le 1,000',
|
|
24
|
+
};
|
|
25
|
+
this.setState({ errors, sent: '', failure: '' });
|
|
26
|
+
if (errors.phone || errors.amount) {
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
this.setState({ sending: true });
|
|
30
|
+
try {
|
|
31
|
+
const { account, name, email, phone, amount, note, plan, receipts } = state;
|
|
32
|
+
await this.dash.network.http.request({
|
|
33
|
+
url: 'https://httpbin.org/post',
|
|
34
|
+
method: 'POST',
|
|
35
|
+
body: { account, name, email, phone, amount: Number(amount), note, plan, receipts },
|
|
36
|
+
});
|
|
37
|
+
this.setState({ sent: `Le ${amount} to ${phone}` });
|
|
38
|
+
} catch (error) {
|
|
39
|
+
this.setState({ failure: (error as Error).message });
|
|
40
|
+
}
|
|
41
|
+
this.setState({ sending: false });
|
|
42
|
+
},
|
|
43
|
+
},
|
|
44
|
+
});
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
<View>
|
|
2
|
+
<Text variant="title">Forms</Text>
|
|
3
|
+
<Text>Each change reaches the controller, which keeps the value in state and checks it when the form is sent.</Text>
|
|
4
|
+
|
|
5
|
+
<View variant="card">
|
|
6
|
+
<Select label="Pay from" placeholder="Choose an account" value="{{ account }}" onChange="setAccount">
|
|
7
|
+
<Option value="savings" label="Savings · Le 1,250,000" />
|
|
8
|
+
<Option value="wallet" label="Mobile wallet · Le 84,500" />
|
|
9
|
+
<Option value="card" label="Debit card · Le 310,000" />
|
|
10
|
+
</Select>
|
|
11
|
+
<Input label="Name" placeholder="Ada Lovelace" value="{{ name }}" maxLength="40" onChange="setName" />
|
|
12
|
+
<Input label="Email" type="email" placeholder="ada@example.com" hint="For the receipt" value="{{ email }}" onChange="setEmail" />
|
|
13
|
+
<Input label="Phone" type="phone" placeholder="076 123456" value="{{ phone }}" error="{{ errors.phone }}" onChange="setPhone" />
|
|
14
|
+
<Input label="Amount" type="number" prefix="Le" placeholder="0" value="{{ amount }}" error="{{ errors.amount }}" onChange="setAmount" />
|
|
15
|
+
<TextArea label="Note" placeholder="What it's for" rows="3" maxLength="140" hint="{{ note.length }} of 140" value="{{ note }}" onChange="setNote" />
|
|
16
|
+
</View>
|
|
17
|
+
|
|
18
|
+
<View variant="card">
|
|
19
|
+
<RadioGroup label="How often" value="{{ plan }}" onChange="setPlan">
|
|
20
|
+
<Radio value="once" label="Just once" />
|
|
21
|
+
<Radio value="monthly" label="Every month" />
|
|
22
|
+
<Radio value="yearly" label="Every year" />
|
|
23
|
+
</RadioGroup>
|
|
24
|
+
<Divider />
|
|
25
|
+
<Switch label="Send me receipts" checked="{{ receipts }}" onChange="setReceipts" />
|
|
26
|
+
<Checkbox label="I agree to the terms" checked="{{ agreed }}" onChange="setAgreed" />
|
|
27
|
+
</View>
|
|
28
|
+
|
|
29
|
+
<If condition="{{ sent }}">
|
|
30
|
+
<Notice tone="success" title="Sent">{{ sent }}</Notice>
|
|
31
|
+
</If>
|
|
32
|
+
<If condition="{{ failure }}">
|
|
33
|
+
<Notice tone="danger" title="Not sent">{{ failure }}</Notice>
|
|
34
|
+
</If>
|
|
35
|
+
|
|
36
|
+
<Footer>
|
|
37
|
+
<Button onClick="send" loading="{{ sending }}" disabled="{{ !agreed }}">Send</Button>
|
|
38
|
+
</Footer>
|
|
39
|
+
</View>
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import state from './state';
|
|
2
|
+
|
|
3
|
+
export default definePage({
|
|
4
|
+
initialState: state,
|
|
5
|
+
mutations: {
|
|
6
|
+
grow: (state) => ({ size: Math.min(state.size + 16, 128) }),
|
|
7
|
+
shrink: (state) => ({ size: Math.max(state.size - 16, 16) }),
|
|
8
|
+
logoLoaded: (_state, event: DotEvent<{ width: number; height: number }>) => ({
|
|
9
|
+
loaded: `${event.detail.width} × ${event.detail.height}`,
|
|
10
|
+
}),
|
|
11
|
+
missingFailed: (_state, event: DotEvent<{ message: string }>) => ({
|
|
12
|
+
failure: event.detail.message,
|
|
13
|
+
}),
|
|
14
|
+
},
|
|
15
|
+
});
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
<View>
|
|
2
|
+
<Text variant="title">Images</Text>
|
|
3
|
+
<Text>Pictures in the dot's statics/ folder ship with it. The build checks each one.</Text>
|
|
4
|
+
|
|
5
|
+
<View variant="card" direction="row" align="end" gap="large">
|
|
6
|
+
<Image src="statics/logo.png" alt="Small logo" width="24" height="24" />
|
|
7
|
+
<Image src="statics/logo.png" alt="Medium logo" width="48" height="48" />
|
|
8
|
+
<Image src="statics/logo.png" alt="Large logo" width="96" height="96" />
|
|
9
|
+
</View>
|
|
10
|
+
|
|
11
|
+
<Text variant="caption" tone="muted">Shapes, and how a picture fills its box</Text>
|
|
12
|
+
<View variant="card" direction="row" align="center" gap="large">
|
|
13
|
+
<Image src="statics/logo.png" alt="Rounded" width="56" height="56" />
|
|
14
|
+
<Image src="statics/logo.png" alt="Square" width="56" height="56" shape="square" />
|
|
15
|
+
<Image src="statics/logo.png" alt="Circle" width="56" height="56" shape="circle" />
|
|
16
|
+
<Image src="statics/logo.png" alt="Contained" width="96" height="56" fit="contain" />
|
|
17
|
+
</View>
|
|
18
|
+
|
|
19
|
+
<Text variant="caption" tone="muted">Sizes can come from state</Text>
|
|
20
|
+
<View variant="card" gap="small">
|
|
21
|
+
<Image src="statics/logo.png" alt="Logo" width="{{ size }}" height="{{ size }}" onLoad="logoLoaded" />
|
|
22
|
+
<Text>{{ size }} × {{ size }} on screen, {{ loaded }} in the file</Text>
|
|
23
|
+
<View direction="row" gap="small">
|
|
24
|
+
<Button variant="secondary" onClick="shrink" disabled="{{ size <= 16 }}">Smaller</Button>
|
|
25
|
+
<Button variant="secondary" onClick="grow" disabled="{{ size >= 128 }}">Bigger</Button>
|
|
26
|
+
</View>
|
|
27
|
+
</View>
|
|
28
|
+
|
|
29
|
+
<Text variant="caption" tone="muted">An image that fails says why</Text>
|
|
30
|
+
<View variant="card">
|
|
31
|
+
<If condition="{{ failure }}">
|
|
32
|
+
<Text>{{ failure }}</Text>
|
|
33
|
+
</If>
|
|
34
|
+
<Else>
|
|
35
|
+
<Image src="{{ missing }}" alt="" width="48" height="48" onError="missingFailed" />
|
|
36
|
+
</Else>
|
|
37
|
+
</View>
|
|
38
|
+
</View>
|
|
@@ -2,66 +2,9 @@ import state from './state';
|
|
|
2
2
|
|
|
3
3
|
export default definePage({
|
|
4
4
|
initialState: state,
|
|
5
|
-
hooks: {
|
|
6
|
-
onShow() {
|
|
7
|
-
this.setState((current) => ({ visits: current.visits + 1 }));
|
|
8
|
-
},
|
|
9
|
-
},
|
|
10
|
-
mutations: {
|
|
11
|
-
increment: (state) => {
|
|
12
|
-
const count = state.count + 1;
|
|
13
|
-
return { count, taps: [{ id: count }, ...state.taps] };
|
|
14
|
-
},
|
|
15
|
-
reset: () => ({ count: 0, taps: [] }),
|
|
16
|
-
rename: (_state, event: DotEvent<{ value: string }>) => ({ title: event.detail.value }),
|
|
17
|
-
logoLoaded: (_state, event: DotEvent<{ width: number; height: number }>) => ({
|
|
18
|
-
logoStatus: 'loaded' as const,
|
|
19
|
-
logoSize: `${event.detail.width}x${event.detail.height}`,
|
|
20
|
-
}),
|
|
21
|
-
logoFailed: () => ({ logoStatus: 'failed' as const }),
|
|
22
|
-
},
|
|
23
5
|
actions: {
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
this.mutations.increment();
|
|
27
|
-
}
|
|
28
|
-
},
|
|
29
|
-
async sayHello(state) {
|
|
30
|
-
try {
|
|
31
|
-
const { action } = await dash.ui.alert(
|
|
32
|
-
{
|
|
33
|
-
title: `Hello from ${state.title}`,
|
|
34
|
-
message: env.DOT_PUBLIC_GREETING,
|
|
35
|
-
actions: [
|
|
36
|
-
{ id: 'nice', label: 'Nice' },
|
|
37
|
-
{ id: 'close', label: 'Close', role: 'cancel' },
|
|
38
|
-
],
|
|
39
|
-
},
|
|
40
|
-
{ timeout: 30_000 },
|
|
41
|
-
);
|
|
42
|
-
this.setState({ dashNote: `You chose "${action}"` });
|
|
43
|
-
} catch (error) {
|
|
44
|
-
this.setState({ dashNote: errorMessage(error) });
|
|
45
|
-
}
|
|
46
|
-
},
|
|
47
|
-
async vibrate() {
|
|
48
|
-
await dash.system.vibrate({ style: 'success' });
|
|
49
|
-
},
|
|
50
|
-
async toggleTorch(state) {
|
|
51
|
-
const next = state.torch === 'on' ? 'off' : 'on';
|
|
52
|
-
try {
|
|
53
|
-
await dash.system.torch(next);
|
|
54
|
-
this.setState({ torch: next, dashNote: `Torch ${next}` });
|
|
55
|
-
} catch (error) {
|
|
56
|
-
this.setState({ dashNote: errorMessage(error) });
|
|
57
|
-
}
|
|
58
|
-
},
|
|
59
|
-
openTips(state) {
|
|
60
|
-
this.navigation.push('help/tips', { count: String(state.count), trail: 'Home', depth: '1' });
|
|
6
|
+
open(_state, event: DotEvent<unknown, { page: PageId }>) {
|
|
7
|
+
this.navigation.push(event.data.page);
|
|
61
8
|
},
|
|
62
9
|
},
|
|
63
10
|
});
|
|
64
|
-
|
|
65
|
-
function errorMessage(error: unknown): string {
|
|
66
|
-
return error instanceof Error ? error.message : String(error);
|
|
67
|
-
}
|