@monimesl/dot-cli 1.0.0-beta.6 → 1.0.0-beta.8
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/templates/showcase/dot.ts +16 -1
- package/dist/templates/showcase/pages/basics/theme/controller.ts +28 -0
- package/dist/templates/showcase/pages/basics/theme/state.ts +4 -0
- package/dist/templates/showcase/pages/basics/theme/view.dml +50 -0
- package/dist/templates/showcase/pages/components/styles/view.dml +37 -2
- package/dist/templates/showcase/pages/dash/cache/controller.ts +43 -17
- package/dist/templates/showcase/pages/dash/cache/state.ts +1 -0
- package/dist/templates/showcase/pages/dash/cache/view.dml +4 -0
- package/dist/templates/showcase/pages/dash/files/controller.ts +62 -32
- package/dist/templates/showcase/pages/dash/files/state.ts +1 -0
- package/dist/templates/showcase/pages/dash/files/view.dml +4 -0
- package/dist/templates/showcase/pages/dash/reminders/controller.ts +1 -1
- package/dist/templates/showcase/pages/dash/reminders/view.dml +1 -1
- package/dist/templates/showcase/pages/dash/secrets/controller.ts +35 -15
- package/dist/templates/showcase/pages/dash/secrets/state.ts +1 -0
- package/dist/templates/showcase/pages/dash/secrets/view.dml +4 -0
- package/dist/templates/showcase/pages/index/state.ts +7 -1
- package/dist/templates/showcase/statics/icons/ticket-stub.svg +1 -0
- package/package.json +6 -6
|
@@ -16,6 +16,7 @@ export default defineDot({
|
|
|
16
16
|
'basics/hooks',
|
|
17
17
|
'basics/bars',
|
|
18
18
|
'basics/colors',
|
|
19
|
+
'basics/theme',
|
|
19
20
|
'basics/share',
|
|
20
21
|
'basics/concert',
|
|
21
22
|
// What a view can show.
|
|
@@ -47,8 +48,18 @@ export default defineDot({
|
|
|
47
48
|
'dash/secrets',
|
|
48
49
|
'dash/calls',
|
|
49
50
|
],
|
|
50
|
-
//
|
|
51
|
+
// How every page looks. The Theme page explains each setting.
|
|
51
52
|
theme: {
|
|
53
|
+
// Turn one of these on to see it on every page:
|
|
54
|
+
// font: 'rounded',
|
|
55
|
+
// headingFont: 'serif',
|
|
56
|
+
// headingWeight: 'medium',
|
|
57
|
+
// textSize: 'large',
|
|
58
|
+
// shape: 'rounded',
|
|
59
|
+
// density: 'compact',
|
|
60
|
+
// elevation: 'raised',
|
|
61
|
+
// components: { Button: { size: 'large' }, View: { variants: { card: { border: 'none' } } } },
|
|
62
|
+
// The showcase's own colours, used by name on the Styles page, and the Page colours swatches.
|
|
52
63
|
colors: {
|
|
53
64
|
custom: {
|
|
54
65
|
gold: { light: '#f5c542', dark: '#d4a72c' },
|
|
@@ -57,6 +68,10 @@ export default defineDot({
|
|
|
57
68
|
...swatchColors(),
|
|
58
69
|
},
|
|
59
70
|
},
|
|
71
|
+
// The showcase's own icon, an SVG in statics/, used by name like Dot's icons.
|
|
72
|
+
icons: { 'ticket-stub': 'statics/icons/ticket-stub.svg' },
|
|
73
|
+
// A gradient between two of the theme's colours, for a View's background.
|
|
74
|
+
gradients: { goldenHour: { from: 'gold', to: 'warning', direction: 'diagonal' } },
|
|
60
75
|
},
|
|
61
76
|
// logger() writes each page load, event, navigation and state change to the console.
|
|
62
77
|
plugins: [logger()],
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import state from './state';
|
|
2
|
+
|
|
3
|
+
// Sierra Leone's green and blue, darker in light mode so text in them reads on white.
|
|
4
|
+
const FLAG_TONES = {
|
|
5
|
+
success: { light: '#178a33', dark: '#4cd471' },
|
|
6
|
+
info: { light: '#0060a8', dark: '#5aa9ff' },
|
|
7
|
+
} as const;
|
|
8
|
+
|
|
9
|
+
export default definePage({
|
|
10
|
+
initialState: state,
|
|
11
|
+
configs: { navigationBar: { title: 'Theme' } },
|
|
12
|
+
actions: {
|
|
13
|
+
// A page can't change the theme, but setColors changes its colours, tones included. Undefined
|
|
14
|
+
// puts the theme's own back.
|
|
15
|
+
setFlagTones(_state, event: CheckedEvent) {
|
|
16
|
+
const flagTones = event.detail.checked;
|
|
17
|
+
this.setState({ flagTones });
|
|
18
|
+
if (flagTones) {
|
|
19
|
+
this.setColors(FLAG_TONES);
|
|
20
|
+
} else {
|
|
21
|
+
this.setColors({
|
|
22
|
+
success: { light: undefined, dark: undefined },
|
|
23
|
+
info: { light: undefined, dark: undefined },
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
},
|
|
27
|
+
},
|
|
28
|
+
});
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
<View>
|
|
2
|
+
<Text variant="title">Theme</Text>
|
|
3
|
+
<Text>A dot has no CSS. The theme in dot.ts sets how every page looks, and a page can't change it while it runs. To see a setting below, turn it on in dot.ts, where a commented line has each one, then reload.</Text>
|
|
4
|
+
|
|
5
|
+
<View gap="small">
|
|
6
|
+
<Text variant="subtitle">colors</Text>
|
|
7
|
+
<Text variant="caption" tone="muted">primary, background, surface, text, muted, border and subtle, and the tones info, success, warning and danger, each for light and dark. A colour left out is Dash's. A page can change its own with setColors, as this switch does for two tones.</Text>
|
|
8
|
+
<View variant="card" gap="small">
|
|
9
|
+
<View direction="row" gap="small" wrap>
|
|
10
|
+
<Button tone="info">Details</Button>
|
|
11
|
+
<Button tone="success">Pay SLE 120</Button>
|
|
12
|
+
<Button variant="secondary" tone="danger">Refund</Button>
|
|
13
|
+
</View>
|
|
14
|
+
<View direction="row" gap="small" wrap>
|
|
15
|
+
<Badge tone="info">New</Badge>
|
|
16
|
+
<Badge tone="success">Paid</Badge>
|
|
17
|
+
<Badge tone="warning">Due Friday</Badge>
|
|
18
|
+
<Badge tone="danger">Failed</Badge>
|
|
19
|
+
</View>
|
|
20
|
+
<Switch label="Use the flag's green and blue" checked="{{ flagTones }}" onChange="setFlagTones" />
|
|
21
|
+
</View>
|
|
22
|
+
</View>
|
|
23
|
+
|
|
24
|
+
<View gap="small">
|
|
25
|
+
<Text variant="subtitle">shape, density and elevation</Text>
|
|
26
|
+
<Text variant="caption" tone="muted">shape is how round corners are: square, subtle, soft (the default) or rounded. density is how much room gaps and padding take: compact, regular (the default) or comfortable. elevation: "raised" gives every card and list a soft shadow in place of its hairline.</Text>
|
|
27
|
+
</View>
|
|
28
|
+
|
|
29
|
+
<View gap="small">
|
|
30
|
+
<Text variant="subtitle">font, headingFont, headingWeight and textSize</Text>
|
|
31
|
+
<Text variant="caption" tone="muted">font is one of the phone's own fonts, so nothing is downloaded: system (the default), rounded, serif or mono. headingFont and headingWeight give titles their own. textSize, small, regular or large, makes all text a tenth smaller or larger.</Text>
|
|
32
|
+
</View>
|
|
33
|
+
|
|
34
|
+
<View gap="small">
|
|
35
|
+
<Text variant="subtitle">components</Text>
|
|
36
|
+
<Text variant="caption" tone="muted">Default props for every use of a component, or for one of its variants, so a dot sets its look once. Button: { size: "large" } makes every Button large. View: { variants: { card: { elevation: "low" } } } gives only cards a shadow. A prop in the markup still wins.</Text>
|
|
37
|
+
</View>
|
|
38
|
+
|
|
39
|
+
<View gap="small">
|
|
40
|
+
<Text variant="subtitle">icons and gradients</Text>
|
|
41
|
+
<Text variant="caption" tone="muted">icons names SVGs in statics/, such as ticket-stub here. They work wherever Dot's icons do, and take the icon's tone. The build refuses an SVG with scripts or links, so export it from your design tool as plain shapes. gradients names a gradient between two of the theme's colours, for a View's background, such as goldenHour.</Text>
|
|
42
|
+
<View background="goldenHour" radius="large" padding="large" gap="small">
|
|
43
|
+
<View direction="row" align="center" gap="small">
|
|
44
|
+
<Icon name="ticket-stub" tone="ink" />
|
|
45
|
+
<Text weight="bold" tone="ink">Freetown Jazz Night</Text>
|
|
46
|
+
</View>
|
|
47
|
+
<Text variant="title" tone="ink">2 tickets</Text>
|
|
48
|
+
</View>
|
|
49
|
+
</View>
|
|
50
|
+
</View>
|
|
@@ -39,8 +39,8 @@
|
|
|
39
39
|
</View>
|
|
40
40
|
|
|
41
41
|
<View gap="small">
|
|
42
|
-
<Text variant="subtitle">
|
|
43
|
-
<Text variant="caption" tone="muted">The theme in dot.ts can
|
|
42
|
+
<Text variant="subtitle">The dot's own colours</Text>
|
|
43
|
+
<Text variant="caption" tone="muted">The theme in dot.ts can name its own colours, such as gold and ink here. Use them as a background or a text tone. The Theme page has the rest of the theme.</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>
|
|
@@ -63,6 +63,41 @@
|
|
|
63
63
|
</View>
|
|
64
64
|
</View>
|
|
65
65
|
|
|
66
|
+
<View gap="small">
|
|
67
|
+
<Text variant="subtitle">elevation</Text>
|
|
68
|
+
<Text variant="caption" tone="muted">A shadow lifts a box off the page. low suits a card, and high something that floats over others.</Text>
|
|
69
|
+
<View direction="row" gap="large" padding="medium">
|
|
70
|
+
<View grow variant="card" elevation="low" border="none" align="center">
|
|
71
|
+
<Text>low</Text>
|
|
72
|
+
</View>
|
|
73
|
+
<View grow variant="card" elevation="high" border="none" align="center">
|
|
74
|
+
<Text>high</Text>
|
|
75
|
+
</View>
|
|
76
|
+
</View>
|
|
77
|
+
</View>
|
|
78
|
+
|
|
79
|
+
<View gap="small">
|
|
80
|
+
<Text variant="subtitle">Buttons</Text>
|
|
81
|
+
<Text variant="caption" tone="muted">variant is primary, secondary, outline or ghost. tone colours a button, such as danger for one that deletes. size is small, medium or large.</Text>
|
|
82
|
+
<View direction="row" gap="small" wrap>
|
|
83
|
+
<Button>Primary</Button>
|
|
84
|
+
<Button variant="secondary">Secondary</Button>
|
|
85
|
+
<Button variant="outline">Outline</Button>
|
|
86
|
+
<Button variant="ghost">Ghost</Button>
|
|
87
|
+
</View>
|
|
88
|
+
<View direction="row" gap="small" wrap>
|
|
89
|
+
<Button tone="danger">Delete</Button>
|
|
90
|
+
<Button variant="secondary" tone="success">Mark paid</Button>
|
|
91
|
+
<Button variant="outline" tone="warning">Remind me</Button>
|
|
92
|
+
<Button variant="ghost" tone="danger">Cancel</Button>
|
|
93
|
+
</View>
|
|
94
|
+
<View direction="row" gap="small" align="center" wrap>
|
|
95
|
+
<Button size="small" variant="secondary">Small</Button>
|
|
96
|
+
<Button size="medium" variant="secondary">Medium</Button>
|
|
97
|
+
<Button size="large" variant="secondary">Large</Button>
|
|
98
|
+
</View>
|
|
99
|
+
</View>
|
|
100
|
+
|
|
66
101
|
<View gap="small">
|
|
67
102
|
<Text variant="subtitle">width and height</Text>
|
|
68
103
|
<Text variant="caption" tone="muted">In points, or fill for all the room there is.</Text>
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { errorMessage } from '@monimesl/dot-runtime';
|
|
2
|
+
|
|
1
3
|
import state from './state';
|
|
2
4
|
|
|
3
5
|
const RECENT = 'recent/';
|
|
@@ -16,37 +18,61 @@ export default definePage({
|
|
|
16
18
|
// and the cache up to 64 MB. A full cache rejects with 'failed'.
|
|
17
19
|
actions: {
|
|
18
20
|
async refresh() {
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
21
|
+
try {
|
|
22
|
+
const keys = await this.dash.storage.cache.keys();
|
|
23
|
+
const recent: string[] = [];
|
|
24
|
+
for (const key of keys.filter((each) => each.startsWith(RECENT))) {
|
|
25
|
+
// get gives null for a key that's missing or expired.
|
|
26
|
+
recent.push((await this.dash.storage.cache.get<string>(key)) ?? '');
|
|
27
|
+
}
|
|
28
|
+
const hasGenre = await this.dash.storage.cache.exists(GENRE);
|
|
29
|
+
this.setState({
|
|
30
|
+
recent,
|
|
31
|
+
genre: hasGenre ? ((await this.dash.storage.cache.get<string>(GENRE)) ?? '') : '',
|
|
32
|
+
keys: keys.join(', '),
|
|
33
|
+
});
|
|
34
|
+
} catch (error) {
|
|
35
|
+
this.setState({ note: errorMessage(error) });
|
|
24
36
|
}
|
|
25
|
-
const hasGenre = await this.dash.storage.cache.exists(GENRE);
|
|
26
|
-
this.setState({
|
|
27
|
-
recent,
|
|
28
|
-
genre: hasGenre ? ((await this.dash.storage.cache.get<string>(GENRE)) ?? '') : '',
|
|
29
|
-
keys: keys.join(', '),
|
|
30
|
-
});
|
|
31
37
|
},
|
|
32
38
|
// Each view lasts 30 seconds, then the cache forgets it.
|
|
33
39
|
async view(_state, event: DataEvent<{ id: string; name: string }>) {
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
40
|
+
try {
|
|
41
|
+
await this.dash.storage.cache.set(`${RECENT}${event.data.id}`, event.data.name, {
|
|
42
|
+
ttl: RECENT_MILLISECONDS,
|
|
43
|
+
});
|
|
44
|
+
this.setState({ note: '' });
|
|
45
|
+
} catch (error) {
|
|
46
|
+
this.setState({ note: errorMessage(error) });
|
|
47
|
+
}
|
|
37
48
|
await this.actions.refresh();
|
|
38
49
|
},
|
|
39
50
|
// With no ttl, the genre lasts until it's removed.
|
|
40
51
|
async setGenre(_state, event: ValueEvent) {
|
|
41
|
-
|
|
52
|
+
try {
|
|
53
|
+
await this.dash.storage.cache.set(GENRE, event.detail.value);
|
|
54
|
+
this.setState({ note: '' });
|
|
55
|
+
} catch (error) {
|
|
56
|
+
this.setState({ note: errorMessage(error) });
|
|
57
|
+
}
|
|
42
58
|
await this.actions.refresh();
|
|
43
59
|
},
|
|
44
60
|
async forgetGenre() {
|
|
45
|
-
|
|
61
|
+
try {
|
|
62
|
+
await this.dash.storage.cache.remove(GENRE);
|
|
63
|
+
this.setState({ note: '' });
|
|
64
|
+
} catch (error) {
|
|
65
|
+
this.setState({ note: errorMessage(error) });
|
|
66
|
+
}
|
|
46
67
|
await this.actions.refresh();
|
|
47
68
|
},
|
|
48
69
|
async clear() {
|
|
49
|
-
|
|
70
|
+
try {
|
|
71
|
+
await this.dash.storage.cache.clear();
|
|
72
|
+
this.setState({ note: '' });
|
|
73
|
+
} catch (error) {
|
|
74
|
+
this.setState({ note: errorMessage(error) });
|
|
75
|
+
}
|
|
50
76
|
await this.actions.refresh();
|
|
51
77
|
},
|
|
52
78
|
},
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { errorMessage } from '@monimesl/dot-runtime';
|
|
2
|
+
|
|
1
3
|
import type { Archive, Song } from './state';
|
|
2
4
|
import state from './state';
|
|
3
5
|
|
|
@@ -18,58 +20,86 @@ export default definePage({
|
|
|
18
20
|
// there rejects with 'missing', while stat gives null.
|
|
19
21
|
actions: {
|
|
20
22
|
async refresh() {
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
23
|
+
try {
|
|
24
|
+
const entries = await this.dash.storage.file.list('', { recursive: true });
|
|
25
|
+
const songs: Song[] = [];
|
|
26
|
+
for (const entry of entries) {
|
|
27
|
+
if (entry.kind === 'file' && entry.path.startsWith('setlist/')) {
|
|
28
|
+
songs.push({ path: entry.path, text: await this.dash.storage.file.read(entry.path) });
|
|
29
|
+
}
|
|
26
30
|
}
|
|
31
|
+
const archives: Archive[] = entries
|
|
32
|
+
.filter((entry) => entry.kind === 'folder' && /^archive\/[^/]+$/.test(entry.path))
|
|
33
|
+
.map((entry) => ({
|
|
34
|
+
path: entry.path,
|
|
35
|
+
time: new Date(Number(entry.name)).toLocaleTimeString('en-GB'),
|
|
36
|
+
}))
|
|
37
|
+
.reverse();
|
|
38
|
+
const documents = entries.filter(
|
|
39
|
+
(entry) => entry.kind === 'file' && entry.path.startsWith('documents/'),
|
|
40
|
+
);
|
|
41
|
+
// In bytes: what the dot's files take, and the most they may take.
|
|
42
|
+
const { used, quota } = await this.dash.storage.file.usage();
|
|
43
|
+
const kept = await this.dash.storage.file.stat(PICTURE);
|
|
44
|
+
this.setState({
|
|
45
|
+
songs,
|
|
46
|
+
archives,
|
|
47
|
+
documents,
|
|
48
|
+
usage: `${used} bytes used of ${quota / 1024 / 1024} MB`,
|
|
49
|
+
picture: kept ? (await this.dash.storage.file.open(PICTURE)).url : '',
|
|
50
|
+
});
|
|
51
|
+
} catch (error) {
|
|
52
|
+
this.setState({ note: errorMessage(error) });
|
|
27
53
|
}
|
|
28
|
-
const archives: Archive[] = entries
|
|
29
|
-
.filter((entry) => entry.kind === 'folder' && /^archive\/[^/]+$/.test(entry.path))
|
|
30
|
-
.map((entry) => ({
|
|
31
|
-
path: entry.path,
|
|
32
|
-
time: new Date(Number(entry.name)).toLocaleTimeString('en-GB'),
|
|
33
|
-
}))
|
|
34
|
-
.reverse();
|
|
35
|
-
const documents = entries.filter(
|
|
36
|
-
(entry) => entry.kind === 'file' && entry.path.startsWith('documents/'),
|
|
37
|
-
);
|
|
38
|
-
// In bytes: what the dot's files take, and the most they may take.
|
|
39
|
-
const { used, quota } = await this.dash.storage.file.usage();
|
|
40
|
-
const kept = await this.dash.storage.file.stat(PICTURE);
|
|
41
|
-
this.setState({
|
|
42
|
-
songs,
|
|
43
|
-
archives,
|
|
44
|
-
documents,
|
|
45
|
-
usage: `${used} bytes used of ${quota / 1024 / 1024} MB`,
|
|
46
|
-
picture: kept ? (await this.dash.storage.file.open(PICTURE)).url : '',
|
|
47
|
-
});
|
|
48
54
|
},
|
|
49
55
|
async save(state) {
|
|
50
|
-
|
|
51
|
-
|
|
56
|
+
try {
|
|
57
|
+
await this.dash.storage.file.write(`setlist/${Date.now()}.txt`, state.draft);
|
|
58
|
+
this.setState({ draft: '', note: '' });
|
|
59
|
+
} catch (error) {
|
|
60
|
+
this.setState({ note: errorMessage(error) });
|
|
61
|
+
}
|
|
52
62
|
await this.actions.refresh();
|
|
53
63
|
},
|
|
54
64
|
// Moves the whole setlist folder into the archive, under the time it was archived.
|
|
55
65
|
async archive() {
|
|
56
|
-
|
|
57
|
-
|
|
66
|
+
try {
|
|
67
|
+
await this.dash.storage.file.createFolder('archive');
|
|
68
|
+
await this.dash.storage.file.move('setlist', `archive/${Date.now()}`);
|
|
69
|
+
this.setState({ note: '' });
|
|
70
|
+
} catch (error) {
|
|
71
|
+
this.setState({ note: errorMessage(error) });
|
|
72
|
+
}
|
|
58
73
|
await this.actions.refresh();
|
|
59
74
|
},
|
|
60
75
|
// Copies an archived setlist back, in place of the current one. Without overwrite, a path
|
|
61
76
|
// that's taken rejects with 'failed'.
|
|
62
77
|
async copyBack(_state, event: DataEvent<{ path: string }>) {
|
|
63
|
-
|
|
78
|
+
try {
|
|
79
|
+
await this.dash.storage.file.copy(event.data.path, 'setlist', { overwrite: true });
|
|
80
|
+
this.setState({ note: '' });
|
|
81
|
+
} catch (error) {
|
|
82
|
+
this.setState({ note: errorMessage(error) });
|
|
83
|
+
}
|
|
64
84
|
await this.actions.refresh();
|
|
65
85
|
},
|
|
66
86
|
// A folder with files in it needs recursive.
|
|
67
87
|
async clearSetlist() {
|
|
68
|
-
|
|
88
|
+
try {
|
|
89
|
+
await this.dash.storage.file.remove('setlist', { recursive: true });
|
|
90
|
+
this.setState({ note: '' });
|
|
91
|
+
} catch (error) {
|
|
92
|
+
this.setState({ note: errorMessage(error) });
|
|
93
|
+
}
|
|
69
94
|
await this.actions.refresh();
|
|
70
95
|
},
|
|
71
96
|
async forgetPicture() {
|
|
72
|
-
|
|
97
|
+
try {
|
|
98
|
+
await this.dash.storage.file.remove(PICTURE);
|
|
99
|
+
this.setState({ note: '' });
|
|
100
|
+
} catch (error) {
|
|
101
|
+
this.setState({ note: errorMessage(error) });
|
|
102
|
+
}
|
|
73
103
|
await this.actions.refresh();
|
|
74
104
|
},
|
|
75
105
|
},
|
|
@@ -35,7 +35,7 @@ export default definePage({
|
|
|
35
35
|
this.setState({ note: errorMessage(error) });
|
|
36
36
|
}
|
|
37
37
|
},
|
|
38
|
-
// A
|
|
38
|
+
// A 13th still to come rejects with 'failed', so cancel one first.
|
|
39
39
|
async remindTomorrow() {
|
|
40
40
|
const note = await noteAfter(
|
|
41
41
|
this.dash.reminders.set({
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
<View gap="small">
|
|
6
6
|
<Text variant="subtitle">set</Text>
|
|
7
|
-
<Text variant="caption" tone="muted">Once, or repeating as transfer reminders do. It needs the reminders permission in dot.json, and a dot can have
|
|
7
|
+
<Text variant="caption" tone="muted">Once, or repeating as transfer reminders do. It needs the reminders permission in dot.json, and a dot can have 12 still to come.</Text>
|
|
8
8
|
<View direction="row" gap="small" wrap>
|
|
9
9
|
<Button onClick="remindTomorrow">Remind me tomorrow</Button>
|
|
10
10
|
<Button variant="secondary" onClick="remindWeekly">Every Friday</Button>
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { errorMessage } from '@monimesl/dot-runtime';
|
|
2
|
+
|
|
1
3
|
import state from './state';
|
|
2
4
|
|
|
3
5
|
const SESSION = 'session';
|
|
@@ -16,31 +18,49 @@ export default definePage({
|
|
|
16
18
|
},
|
|
17
19
|
actions: {
|
|
18
20
|
async refresh() {
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
21
|
+
try {
|
|
22
|
+
// null if there's no secret by that name.
|
|
23
|
+
const token = await this.dash.storage.secret.get(SESSION);
|
|
24
|
+
const signedInAs = await this.dash.storage.cache.get<string>(NAME);
|
|
25
|
+
this.setState({
|
|
26
|
+
token: token ? `${token.slice(0, 12)}…` : '',
|
|
27
|
+
signedInAs: signedInAs ?? '',
|
|
28
|
+
});
|
|
29
|
+
} catch (error) {
|
|
30
|
+
this.setState({ note: errorMessage(error) });
|
|
31
|
+
}
|
|
26
32
|
},
|
|
27
33
|
// A value over 4 KB rejects with 'invalid'. A 33rd secret rejects with 'failed', so remove
|
|
28
34
|
// one first.
|
|
29
35
|
async signIn(state) {
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
36
|
+
try {
|
|
37
|
+
// A real dot gets its token from its own API, once the person signs in there.
|
|
38
|
+
const token = `demo-${Math.random().toString(36).slice(2)}${Date.now().toString(36)}`;
|
|
39
|
+
await this.dash.storage.secret.set(SESSION, token);
|
|
40
|
+
await this.dash.storage.cache.set(NAME, state.name);
|
|
41
|
+
this.setState({ name: '', note: '' });
|
|
42
|
+
} catch (error) {
|
|
43
|
+
this.setState({ note: errorMessage(error) });
|
|
44
|
+
}
|
|
35
45
|
await this.actions.refresh();
|
|
36
46
|
},
|
|
37
47
|
async signOut() {
|
|
38
|
-
|
|
39
|
-
|
|
48
|
+
try {
|
|
49
|
+
await this.dash.storage.secret.remove(SESSION);
|
|
50
|
+
await this.dash.storage.cache.remove(NAME);
|
|
51
|
+
this.setState({ note: '' });
|
|
52
|
+
} catch (error) {
|
|
53
|
+
this.setState({ note: errorMessage(error) });
|
|
54
|
+
}
|
|
40
55
|
await this.actions.refresh();
|
|
41
56
|
},
|
|
42
57
|
async forgetAll() {
|
|
43
|
-
|
|
58
|
+
try {
|
|
59
|
+
await this.dash.storage.secret.clear();
|
|
60
|
+
this.setState({ note: '' });
|
|
61
|
+
} catch (error) {
|
|
62
|
+
this.setState({ note: errorMessage(error) });
|
|
63
|
+
}
|
|
44
64
|
await this.actions.refresh();
|
|
45
65
|
},
|
|
46
66
|
},
|
|
@@ -23,4 +23,8 @@
|
|
|
23
23
|
</View>
|
|
24
24
|
|
|
25
25
|
<Notice tone="warning" title="Keep keys off the phone">Only the token is a secret here: your name is in the cache. Never put API keys in a dot's code or .env, since anyone with the dot can read them.</Notice>
|
|
26
|
+
|
|
27
|
+
<If condition="{{ note }}">
|
|
28
|
+
<Notice tone="danger">{{ note }}</Notice>
|
|
29
|
+
</If>
|
|
26
30
|
</View>
|
|
@@ -67,6 +67,12 @@ export default {
|
|
|
67
67
|
covers: 'configs.colors, setColors, background, primary, surfaces',
|
|
68
68
|
icon: 'water',
|
|
69
69
|
},
|
|
70
|
+
{
|
|
71
|
+
page: 'basics/theme',
|
|
72
|
+
title: 'Theme',
|
|
73
|
+
covers: 'colors, shape, density, elevation, fonts, components, icons, gradients',
|
|
74
|
+
icon: 'eye',
|
|
75
|
+
},
|
|
70
76
|
{
|
|
71
77
|
page: 'basics/share',
|
|
72
78
|
title: 'Share links',
|
|
@@ -88,7 +94,7 @@ export default {
|
|
|
88
94
|
{
|
|
89
95
|
page: 'components/styles',
|
|
90
96
|
title: 'Styles',
|
|
91
|
-
covers: 'background, border, radius, sizes,
|
|
97
|
+
covers: 'background, border, radius, sizes, elevation, buttons, own colours',
|
|
92
98
|
icon: 'star',
|
|
93
99
|
},
|
|
94
100
|
{
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path fill="#000" fill-rule="evenodd" d="M5 4h14a2 2 0 0 1 2 2v3a3 3 0 0 0 0 6v3a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-3a3 3 0 0 0 0-6V6a2 2 0 0 1 2-2zm4 3v2h1.5V7zm0 4v2h1.5v-2zm0 4v2h1.5v-2z"/></svg>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@monimesl/dot-cli",
|
|
3
|
-
"version": "1.0.0-beta.
|
|
3
|
+
"version": "1.0.0-beta.8",
|
|
4
4
|
"description": "The dotctl command: build mini apps and run them in the simulator",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"repository": {
|
|
@@ -24,11 +24,11 @@
|
|
|
24
24
|
"dependencies": {
|
|
25
25
|
"typescript": "^5.9.3",
|
|
26
26
|
"uqr": "^0.1.3",
|
|
27
|
-
"@monimesl/dot-compiler": "1.0.0-beta.
|
|
28
|
-
"@monimesl/dot-core": "1.0.0-beta.
|
|
29
|
-
"@monimesl/dot-dml-parser": "1.0.0-beta.
|
|
30
|
-
"@monimesl/dot-script-parser": "1.0.0-beta.
|
|
31
|
-
"@monimesl/dot-simulator": "1.0.0-beta.
|
|
27
|
+
"@monimesl/dot-compiler": "1.0.0-beta.8",
|
|
28
|
+
"@monimesl/dot-core": "1.0.0-beta.8",
|
|
29
|
+
"@monimesl/dot-dml-parser": "1.0.0-beta.8",
|
|
30
|
+
"@monimesl/dot-script-parser": "1.0.0-beta.8",
|
|
31
|
+
"@monimesl/dot-simulator": "1.0.0-beta.8"
|
|
32
32
|
},
|
|
33
33
|
"devDependencies": {
|
|
34
34
|
"@types/node": "^22.20.4",
|