@siegesailor/terminal-sigma 1.2.0 → 1.4.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.
- package/README.md +84 -31
- package/build/app.js +252 -99
- package/build/app.js.map +1 -1
- package/build/components/autocomplete-input.js +57 -0
- package/build/components/autocomplete-input.js.map +1 -0
- package/build/components/character.js +16 -6
- package/build/components/character.js.map +1 -1
- package/build/components/choice.js +16 -4
- package/build/components/choice.js.map +1 -1
- package/build/components/entry-form.js +43 -11
- package/build/components/entry-form.js.map +1 -1
- package/build/components/logs-view.js +9 -3
- package/build/components/logs-view.js.map +1 -1
- package/build/components/multi-choice.js +45 -0
- package/build/components/multi-choice.js.map +1 -0
- package/build/components/today-panel.js +14 -8
- package/build/components/today-panel.js.map +1 -1
- package/build/content.js +207 -65
- package/build/content.js.map +1 -1
- package/build/foods.js +159 -0
- package/build/foods.js.map +1 -0
- package/build/i18n.js +262 -36
- package/build/i18n.js.map +1 -1
- package/build/pixel-art.js +476 -306
- package/build/pixel-art.js.map +1 -1
- package/build/progress.js +33 -0
- package/build/progress.js.map +1 -1
- package/build/quotes.js +404 -220
- package/build/quotes.js.map +1 -1
- package/package.json +1 -1
- package/build/components/menu-grid.js +0 -43
- package/build/components/menu-grid.js.map +0 -1
package/build/app.js
CHANGED
|
@@ -3,18 +3,20 @@ import zod from "zod";
|
|
|
3
3
|
import React from "react";
|
|
4
4
|
import { ThemeProvider } from "@inkjs/ui";
|
|
5
5
|
import { Box, Text, useApp, useInput, useStdout, useWindowSize } from "ink";
|
|
6
|
+
import AutocompleteInput from "./components/autocomplete-input.js";
|
|
6
7
|
import Character from "./components/character.js";
|
|
7
8
|
import Choice from "./components/choice.js";
|
|
8
9
|
import EntryForm from "./components/entry-form.js";
|
|
9
10
|
import Footer from "./components/footer.js";
|
|
10
11
|
import LogsView, { LogRow } from "./components/logs-view.js";
|
|
11
|
-
import
|
|
12
|
+
import MultiChoice from "./components/multi-choice.js";
|
|
12
13
|
import TodayPanel from "./components/today-panel.js";
|
|
13
|
-
import { characterWidth, dietFieldsOf, layoutOf,
|
|
14
|
+
import { categoryOptionsOf, characterWidth, dietFieldsOf, intervalOptionsOf, layoutOf, menuEntriesOf, menuListWidth, menuOrder, petStateOf, profileFieldsOf, quoteOptionsOf, sectionsOf, workoutFieldsOf, } from "./content.js";
|
|
14
15
|
import { adviceOf, targetsOf } from "./health.js";
|
|
15
16
|
import { formatNumber, genders, healthGoals, intensities, languages, messagesOf, workoutActivities, workStyles, } from "./i18n.js";
|
|
17
|
+
import { lookOf } from "./pixel-art.js";
|
|
16
18
|
import { experienceOf, experiencePerMeal, levelOf, logsOf, saveProgress, todayOf, workoutExperience, } from "./progress.js";
|
|
17
|
-
import { fetchQuote, hasQuoteApi, localizedQuote,
|
|
19
|
+
import { bundledQuotes, defaultQuoteSettings, fetchQuote, hasQuoteApi, localizedQuote, pickBundled, quoteCategories, quoteIntervals, } from "./quotes.js";
|
|
18
20
|
import { paletteContext, themeNames, themes, uiThemeOf, } from "./theme.js";
|
|
19
21
|
import { advanceTimer, formatClock, idleTimer, remainingOf, toggleTimer, } from "./timer.js";
|
|
20
22
|
export const optionsSchema = zod.object({
|
|
@@ -35,6 +37,29 @@ const languageOptions = languages.map((language) => ({
|
|
|
35
37
|
value: language,
|
|
36
38
|
}));
|
|
37
39
|
const pick = (options, value, fallback) => options.find((option) => option === value) ?? fallback;
|
|
40
|
+
// Appends an event, so every action lands in the logs with the change it made.
|
|
41
|
+
const withEvent = (progress, action, detail) => ({
|
|
42
|
+
...progress,
|
|
43
|
+
events: [
|
|
44
|
+
...progress.events,
|
|
45
|
+
{
|
|
46
|
+
at: new Date().toISOString(),
|
|
47
|
+
action,
|
|
48
|
+
...(detail === undefined ? {} : { detail }),
|
|
49
|
+
},
|
|
50
|
+
],
|
|
51
|
+
});
|
|
52
|
+
// Names to suggest for the author and work filters.
|
|
53
|
+
const quoteNames = (field) => [
|
|
54
|
+
...new Set(bundledQuotes.flatMap((quote) => field === "author"
|
|
55
|
+
? [
|
|
56
|
+
quote.author,
|
|
57
|
+
...Object.values(quote.translations ?? {}).map((text) => text.author),
|
|
58
|
+
]
|
|
59
|
+
: quote.work
|
|
60
|
+
? [quote.work]
|
|
61
|
+
: [])),
|
|
62
|
+
];
|
|
38
63
|
function Titled({ title, children, }) {
|
|
39
64
|
return (React.createElement(paletteContext.Consumer, null, (palette) => (React.createElement(Box, { flexDirection: "column" },
|
|
40
65
|
React.createElement(Text, { bold: true, color: palette.accent }, title),
|
|
@@ -51,14 +76,20 @@ export default function App({ options, file, initialProgress }) {
|
|
|
51
76
|
const [now, setNow] = React.useState(Date.now);
|
|
52
77
|
const durations = React.useMemo(() => ({ focus: options.focus, break: options.break }), [options.focus, options.break]);
|
|
53
78
|
const [timer, setTimer] = React.useState(() => idleTimer(durations));
|
|
54
|
-
const
|
|
79
|
+
const quoteSettings = progress.quotes ?? defaultQuoteSettings;
|
|
80
|
+
const [quote, setQuote] = React.useState(() => pickBundled(quoteSettings, Date.now()));
|
|
55
81
|
const [view, setView] = React.useState("menu");
|
|
82
|
+
const [focusedMenu, setFocusedMenu] = React.useState("quotes");
|
|
83
|
+
const [quoteEditor, setQuoteEditor] = React.useState();
|
|
84
|
+
const [quoteOption, setQuoteOption] = React.useState("draw");
|
|
56
85
|
const [flash, setFlash] = React.useState();
|
|
57
86
|
const [draftActivity, setDraftActivity] = React.useState();
|
|
58
87
|
const language = progress.language ?? "en";
|
|
59
88
|
const messages = messagesOf(language);
|
|
60
89
|
const themeName = progress.theme ?? "ember";
|
|
61
|
-
|
|
90
|
+
// While the theme picker is open, the focused theme previews live.
|
|
91
|
+
const [previewTheme, setPreviewTheme] = React.useState();
|
|
92
|
+
const palette = themes[previewTheme ?? themeName];
|
|
62
93
|
const uiTheme = React.useMemo(() => uiThemeOf(palette), [palette]);
|
|
63
94
|
const name = options.name ?? progress.profile?.name ?? "Rook Sigma";
|
|
64
95
|
const [message, setMessage] = React.useState(() => ({
|
|
@@ -70,10 +101,9 @@ export default function App({ options, file, initialProgress }) {
|
|
|
70
101
|
};
|
|
71
102
|
// Saves first and only then shows the change, so the screen never claims
|
|
72
103
|
// something the file does not hold.
|
|
73
|
-
const
|
|
104
|
+
const commit = React.useCallback((next, text, tone = "info") => {
|
|
74
105
|
try {
|
|
75
106
|
saveProgress(file, next);
|
|
76
|
-
return true;
|
|
77
107
|
}
|
|
78
108
|
catch (error) {
|
|
79
109
|
setMessage({
|
|
@@ -82,26 +112,27 @@ export default function App({ options, file, initialProgress }) {
|
|
|
82
112
|
});
|
|
83
113
|
return false;
|
|
84
114
|
}
|
|
115
|
+
setProgress(next);
|
|
116
|
+
if (text) {
|
|
117
|
+
setMessage({ text, tone });
|
|
118
|
+
}
|
|
119
|
+
return true;
|
|
85
120
|
}, [file]);
|
|
86
121
|
const record = React.useCallback((update, text, activity) => {
|
|
87
|
-
|
|
88
|
-
if (!save(next)) {
|
|
89
|
-
return;
|
|
90
|
-
}
|
|
122
|
+
let next = update(progress);
|
|
91
123
|
const after = levelOf(experienceOf(next));
|
|
92
124
|
const leveledUp = after > levelOf(experienceOf(progress));
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
}, [progress, save]);
|
|
125
|
+
if (leveledUp) {
|
|
126
|
+
next = withEvent(next, "levelUp", String(after));
|
|
127
|
+
}
|
|
128
|
+
const { form } = messagesOf(next.language ?? "en");
|
|
129
|
+
if (commit(next, leveledUp ? `${text} ${form.levelUp(after)}` : text, "success")) {
|
|
130
|
+
setFlash({
|
|
131
|
+
activity: leveledUp ? "levelUp" : activity,
|
|
132
|
+
until: Date.now() + flashMilliseconds,
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
}, [progress, commit]);
|
|
105
136
|
React.useEffect(() => {
|
|
106
137
|
const interval = setInterval(() => {
|
|
107
138
|
setNow(Date.now());
|
|
@@ -110,6 +141,14 @@ export default function App({ options, file, initialProgress }) {
|
|
|
110
141
|
clearInterval(interval);
|
|
111
142
|
};
|
|
112
143
|
}, []);
|
|
144
|
+
// Opening the app is an action too.
|
|
145
|
+
const hasOpened = React.useRef(false);
|
|
146
|
+
React.useEffect(() => {
|
|
147
|
+
if (!hasOpened.current) {
|
|
148
|
+
hasOpened.current = true;
|
|
149
|
+
commit(withEvent(progress, "appOpened"));
|
|
150
|
+
}
|
|
151
|
+
}, [commit, progress]);
|
|
113
152
|
React.useEffect(() => {
|
|
114
153
|
const { timer: next, finished } = advanceTimer(timer, now, durations);
|
|
115
154
|
if (!finished) {
|
|
@@ -119,7 +158,7 @@ export default function App({ options, file, initialProgress }) {
|
|
|
119
158
|
setTimer(next);
|
|
120
159
|
stdout.write("\u0007");
|
|
121
160
|
if (finished === "break") {
|
|
122
|
-
|
|
161
|
+
commit(withEvent(progress, "breakOver"), words.breakOver);
|
|
123
162
|
return;
|
|
124
163
|
}
|
|
125
164
|
record((current) => ({
|
|
@@ -129,11 +168,11 @@ export default function App({ options, file, initialProgress }) {
|
|
|
129
168
|
{ at: new Date(now).toISOString(), minutes: durations.focus },
|
|
130
169
|
],
|
|
131
170
|
}), words.focusComplete(durations.focus, durations.break), "idle");
|
|
132
|
-
}, [now, timer, durations, record, stdout, language]);
|
|
171
|
+
}, [now, timer, durations, record, commit, progress, stdout, language]);
|
|
133
172
|
React.useEffect(() => {
|
|
134
173
|
let isMounted = true;
|
|
135
174
|
const refresh = async () => {
|
|
136
|
-
const next = await fetchQuote();
|
|
175
|
+
const next = await fetchQuote(quoteSettings);
|
|
137
176
|
if (isMounted) {
|
|
138
177
|
setQuote(next);
|
|
139
178
|
}
|
|
@@ -142,17 +181,24 @@ export default function App({ options, file, initialProgress }) {
|
|
|
142
181
|
if (hasQuoteApi()) {
|
|
143
182
|
void refresh();
|
|
144
183
|
}
|
|
184
|
+
if (!quoteSettings.autoRefresh) {
|
|
185
|
+
return () => {
|
|
186
|
+
isMounted = false;
|
|
187
|
+
};
|
|
188
|
+
}
|
|
145
189
|
const interval = setInterval(() => {
|
|
146
190
|
void refresh();
|
|
147
|
-
},
|
|
191
|
+
}, quoteSettings.interval * 1000);
|
|
148
192
|
return () => {
|
|
149
193
|
isMounted = false;
|
|
150
194
|
clearInterval(interval);
|
|
151
195
|
};
|
|
152
|
-
}, []);
|
|
196
|
+
}, [quoteSettings]);
|
|
153
197
|
const backToMenu = (text) => {
|
|
154
198
|
setView("menu");
|
|
199
|
+
setQuoteEditor(undefined);
|
|
155
200
|
setDraftActivity(undefined);
|
|
201
|
+
setPreviewTheme(undefined);
|
|
156
202
|
if (text) {
|
|
157
203
|
say(text);
|
|
158
204
|
}
|
|
@@ -161,20 +207,19 @@ export default function App({ options, file, initialProgress }) {
|
|
|
161
207
|
if (view === "menu" && input === "q") {
|
|
162
208
|
exit();
|
|
163
209
|
}
|
|
210
|
+
else if (view === "quotes" && quoteEditor && key.escape) {
|
|
211
|
+
setQuoteEditor(undefined);
|
|
212
|
+
}
|
|
164
213
|
else if (view !== "menu" && key.escape) {
|
|
165
214
|
const isForm = ["diet", "workout", "profile"].includes(view);
|
|
166
215
|
backToMenu(isForm ? messages.form.cancelled : undefined);
|
|
167
216
|
}
|
|
168
217
|
}, { isActive: progress.language !== undefined });
|
|
169
|
-
const saveSetting = (next, text) => {
|
|
170
|
-
if (save(next)) {
|
|
171
|
-
setProgress(next);
|
|
172
|
-
backToMenu(text);
|
|
173
|
-
}
|
|
174
|
-
};
|
|
175
218
|
const chooseLanguage = (value) => {
|
|
176
219
|
const next = pick(languages, value, "en");
|
|
177
|
-
|
|
220
|
+
if (commit(withEvent({ ...progress, language: next }, "languageChanged", next))) {
|
|
221
|
+
backToMenu(messagesOf(next).languageChanged);
|
|
222
|
+
}
|
|
178
223
|
};
|
|
179
224
|
const providers = (children) => (React.createElement(paletteContext.Provider, { value: palette },
|
|
180
225
|
React.createElement(ThemeProvider, { theme: uiTheme }, children)));
|
|
@@ -191,27 +236,20 @@ export default function App({ options, file, initialProgress }) {
|
|
|
191
236
|
timer.remaining === durations.focus * 60_000;
|
|
192
237
|
const clock = formatClock(remainingOf(timer, now));
|
|
193
238
|
const phaseLabel = messages.timer[timer.phase];
|
|
194
|
-
const selectMenu = (index) => {
|
|
195
|
-
const item = menuOrder[index];
|
|
196
|
-
if (item === "timer" && isIdle) {
|
|
197
|
-
setTimer(toggleTimer(timer, Date.now()));
|
|
198
|
-
say(messages.timer.started);
|
|
199
|
-
}
|
|
200
|
-
else if (item === "quotes") {
|
|
201
|
-
void fetchQuote().then(setQuote);
|
|
202
|
-
say(messages.quoteDrawn);
|
|
203
|
-
}
|
|
204
|
-
else if (item) {
|
|
205
|
-
setView(item);
|
|
206
|
-
}
|
|
207
|
-
};
|
|
208
239
|
const chooseTimerAction = (action) => {
|
|
209
|
-
if (action === "toggle") {
|
|
210
|
-
|
|
211
|
-
|
|
240
|
+
if (action === "start" || action === "toggle") {
|
|
241
|
+
const [event, text, detail] = action === "start"
|
|
242
|
+
? ["timerStarted", messages.timer.started, String(durations.focus)]
|
|
243
|
+
: isRunning
|
|
244
|
+
? ["timerPaused", messages.timer.paused, clock]
|
|
245
|
+
: ["timerResumed", messages.timer.resumed];
|
|
246
|
+
setTimer(toggleTimer(timer, now));
|
|
247
|
+
commit(withEvent(progress, event, detail));
|
|
248
|
+
backToMenu(text);
|
|
212
249
|
}
|
|
213
250
|
else if (action === "stop") {
|
|
214
251
|
setTimer(idleTimer(durations));
|
|
252
|
+
commit(withEvent(progress, "timerStopped", clock));
|
|
215
253
|
backToMenu(messages.timer.stopped);
|
|
216
254
|
}
|
|
217
255
|
else {
|
|
@@ -253,7 +291,7 @@ export default function App({ options, file, initialProgress }) {
|
|
|
253
291
|
record((current) => ({ ...current, workouts: [...current.workouts, workout] }), messages.form.workoutLogged(messages.workoutActivity[workout.activity], workout.minutes, workoutExperience(workout)), workoutAnimation(workout.activity));
|
|
254
292
|
};
|
|
255
293
|
const submitProfile = (values) => {
|
|
256
|
-
|
|
294
|
+
const next = withEvent({
|
|
257
295
|
...progress,
|
|
258
296
|
profile: {
|
|
259
297
|
name: values.name ?? name,
|
|
@@ -264,11 +302,64 @@ export default function App({ options, file, initialProgress }) {
|
|
|
264
302
|
workStyle: pick(workStyles, values.workStyle, "desk"),
|
|
265
303
|
goal: pick(healthGoals, values.goal, "maintain"),
|
|
266
304
|
},
|
|
267
|
-
},
|
|
305
|
+
}, "profileSaved");
|
|
306
|
+
if (commit(next)) {
|
|
307
|
+
backToMenu(messages.form.profileSaved);
|
|
308
|
+
}
|
|
268
309
|
};
|
|
269
310
|
const chooseTheme = (value) => {
|
|
270
311
|
const next = pick(themeNames, value, "ember");
|
|
271
|
-
|
|
312
|
+
if (commit(withEvent({ ...progress, theme: next }, "themeChanged", next))) {
|
|
313
|
+
backToMenu(messages.themes.changed(messages.themes.names[next]));
|
|
314
|
+
}
|
|
315
|
+
};
|
|
316
|
+
const drawQuote = (settings, base) => {
|
|
317
|
+
void fetchQuote(settings).then(setQuote);
|
|
318
|
+
return withEvent(base, "quoteDrawn");
|
|
319
|
+
};
|
|
320
|
+
// Saves 1 quote setting, logs it as "setting:value", and draws a quote that
|
|
321
|
+
// follows the new filters.
|
|
322
|
+
const updateQuotes = (patch, detail) => {
|
|
323
|
+
const settings = { ...quoteSettings, ...patch };
|
|
324
|
+
const next = withEvent({ ...progress, quotes: settings }, "quoteSettingsChanged", detail);
|
|
325
|
+
if (commit(next, messages.quotes.changed)) {
|
|
326
|
+
setQuoteEditor(undefined);
|
|
327
|
+
setQuote(pickBundled(settings, Date.now()));
|
|
328
|
+
}
|
|
329
|
+
};
|
|
330
|
+
const chooseQuoteOption = (value) => {
|
|
331
|
+
setQuoteOption(value);
|
|
332
|
+
switch (value) {
|
|
333
|
+
case "draw": {
|
|
334
|
+
commit(drawQuote(quoteSettings, progress), messages.quoteDrawn);
|
|
335
|
+
break;
|
|
336
|
+
}
|
|
337
|
+
case "autoRefresh": {
|
|
338
|
+
const isOn = !quoteSettings.autoRefresh;
|
|
339
|
+
updateQuotes({ autoRefresh: isOn }, `autoRefresh:${isOn ? "on" : "off"}`);
|
|
340
|
+
break;
|
|
341
|
+
}
|
|
342
|
+
case "mode": {
|
|
343
|
+
const mode = quoteSettings.mode === "random" ? "daily" : "random";
|
|
344
|
+
updateQuotes({ mode }, `mode:${mode}`);
|
|
345
|
+
break;
|
|
346
|
+
}
|
|
347
|
+
case "back": {
|
|
348
|
+
backToMenu();
|
|
349
|
+
break;
|
|
350
|
+
}
|
|
351
|
+
default: {
|
|
352
|
+
setQuoteEditor(pick(["interval", "categories", "excluded", "author", "work"], value, "interval"));
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
};
|
|
356
|
+
const openMenu = (value) => {
|
|
357
|
+
const item = pick(menuOrder, value, "quotes");
|
|
358
|
+
setFocusedMenu(item);
|
|
359
|
+
setView(item);
|
|
360
|
+
if (item === "theme") {
|
|
361
|
+
setPreviewTheme(themeName);
|
|
362
|
+
}
|
|
272
363
|
};
|
|
273
364
|
const experience = experienceOf(progress);
|
|
274
365
|
const level = levelOf(experience);
|
|
@@ -277,6 +368,7 @@ export default function App({ options, file, initialProgress }) {
|
|
|
277
368
|
const columns = size.columns || 100;
|
|
278
369
|
const rows = size.rows || 40;
|
|
279
370
|
const layout = layoutOf(columns, rows);
|
|
371
|
+
const pet = petStateOf(progress, now);
|
|
280
372
|
const activity = flash && flash.until > now
|
|
281
373
|
? flash.activity
|
|
282
374
|
: view === "diet"
|
|
@@ -286,48 +378,92 @@ export default function App({ options, file, initialProgress }) {
|
|
|
286
378
|
: isRunning
|
|
287
379
|
? { focus: "focus", break: "break" }[timer.phase]
|
|
288
380
|
: "idle";
|
|
381
|
+
let activityLabel = messages.activity[activity];
|
|
382
|
+
if (activity === "idle") {
|
|
383
|
+
activityLabel = messages.mood[pet.mood];
|
|
384
|
+
}
|
|
385
|
+
else if (activity === "levelUp") {
|
|
386
|
+
activityLabel = `${messages.activity.levelUp} ${messages.level(level)}`;
|
|
387
|
+
}
|
|
289
388
|
let timerSignal = isRunning ? clock : messages.signal.paused;
|
|
290
389
|
if (isIdle) {
|
|
291
390
|
timerSignal = messages.signal.ready;
|
|
292
391
|
}
|
|
293
|
-
const
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
392
|
+
const entries = menuEntriesOf(messages, {
|
|
393
|
+
timerSignal,
|
|
394
|
+
progress,
|
|
395
|
+
quotes: quoteSettings,
|
|
396
|
+
now,
|
|
397
|
+
logs: logs.length,
|
|
398
|
+
theme: themeName,
|
|
399
|
+
focus: durations.focus,
|
|
400
|
+
rest: durations.break,
|
|
401
|
+
});
|
|
402
|
+
const focusedEntry = entries.find((entry) => entry.key === focusedMenu) ?? entries[0];
|
|
403
|
+
const isTyping = view === "diet" ||
|
|
404
|
+
(view === "quotes" && (quoteEditor === "author" || quoteEditor === "work"));
|
|
405
|
+
let hint = messages.hints.select;
|
|
406
|
+
if (view === "menu") {
|
|
407
|
+
hint = messages.hints.menu;
|
|
408
|
+
}
|
|
409
|
+
else if (view === "logs") {
|
|
410
|
+
hint = messages.hints.logs;
|
|
411
|
+
}
|
|
412
|
+
else if (isTyping) {
|
|
413
|
+
hint = messages.hints.suggest;
|
|
414
|
+
}
|
|
415
|
+
else if (view === "workout" || view === "profile") {
|
|
416
|
+
hint = messages.hints.form;
|
|
417
|
+
}
|
|
418
|
+
const shownQuote = localizedQuote(quote, language);
|
|
419
|
+
const quoteEditors = {
|
|
420
|
+
interval: (React.createElement(Choice, { initialValue: String(quoteSettings.interval), options: intervalOptionsOf(messages), onSelect: (value) => {
|
|
421
|
+
const interval = quoteIntervals.find((option) => String(option) === value) ?? 1500;
|
|
422
|
+
updateQuotes({ interval }, `interval:${interval}`);
|
|
423
|
+
} })),
|
|
424
|
+
categories: (React.createElement(MultiChoice, { doneLabel: messages.quotes.done, initial: quoteSettings.categories, options: categoryOptionsOf(messages), onDone: (selected) => {
|
|
425
|
+
const categories = quoteCategories.filter((category) => selected.includes(category));
|
|
426
|
+
updateQuotes({ categories }, `categories:${categories.join(",")}`);
|
|
427
|
+
} })),
|
|
428
|
+
excluded: (React.createElement(MultiChoice, { doneLabel: messages.quotes.done, initial: quoteSettings.excluded, options: categoryOptionsOf(messages), onDone: (selected) => {
|
|
429
|
+
const excluded = quoteCategories.filter((category) => selected.includes(category));
|
|
430
|
+
updateQuotes({ excluded }, `excluded:${excluded.join(",")}`);
|
|
431
|
+
} })),
|
|
432
|
+
author: (React.createElement(AutocompleteInput, { initialValue: quoteSettings.author, placeholder: messages.quotes.authorPrompt, suggest: (text) => quoteNames("author")
|
|
433
|
+
.filter((author) => author.toLowerCase().includes(text.trim().toLowerCase()))
|
|
434
|
+
.slice(0, 5)
|
|
435
|
+
.map((label) => ({ label })), onSubmit: (text) => {
|
|
436
|
+
updateQuotes({ author: text.trim() }, `author:${text.trim()}`);
|
|
437
|
+
} })),
|
|
438
|
+
work: (React.createElement(AutocompleteInput, { initialValue: quoteSettings.work, placeholder: messages.quotes.workPrompt, suggest: (text) => quoteNames("work")
|
|
439
|
+
.filter((work) => work.toLowerCase().includes(text.trim().toLowerCase()))
|
|
440
|
+
.slice(0, 5)
|
|
441
|
+
.map((label) => ({ label })), onSubmit: (text) => {
|
|
442
|
+
updateQuotes({ work: text.trim() }, `work:${text.trim()}`);
|
|
443
|
+
} })),
|
|
303
444
|
};
|
|
445
|
+
const timerOptions = isIdle
|
|
446
|
+
? [{ label: messages.timer.start, value: "start" }]
|
|
447
|
+
: [
|
|
448
|
+
{
|
|
449
|
+
label: isRunning ? messages.timer.pause : messages.timer.resume,
|
|
450
|
+
value: "toggle",
|
|
451
|
+
},
|
|
452
|
+
{ label: messages.timer.stop, value: "stop" },
|
|
453
|
+
];
|
|
304
454
|
const panels = {
|
|
305
|
-
|
|
306
|
-
React.createElement(
|
|
307
|
-
|
|
308
|
-
progress,
|
|
309
|
-
now,
|
|
310
|
-
logs: logs.length,
|
|
311
|
-
theme: themeName,
|
|
312
|
-
focus: durations.focus,
|
|
313
|
-
rest: durations.break,
|
|
314
|
-
}), onSelect: selectMenu }),
|
|
315
|
-
React.createElement(Box, { flexDirection: "column", marginTop: 1 },
|
|
316
|
-
React.createElement(Text, { bold: true, color: palette.soft }, messages.recent.title),
|
|
317
|
-
logs.length === 0 ? (React.createElement(Text, { dimColor: true }, messages.recent.empty)) : null,
|
|
318
|
-
logs.slice(0, recentCount).map((log) => (React.createElement(LogRow, { key: `${log.kind}-${log.at}`, language: language, log: log, messages: messages })))))),
|
|
455
|
+
quotes: (React.createElement(Titled, { title: messages.menu.quotes },
|
|
456
|
+
React.createElement(Text, { dimColor: true, italic: true, wrap: "truncate-end" }, `“${shownQuote.quote}” — ${shownQuote.author}`),
|
|
457
|
+
React.createElement(Box, { marginTop: 1 }, quoteEditor ? (React.createElement(Box, { key: quoteEditor }, quoteEditors[quoteEditor])) : (React.createElement(Choice, { key: "options", initialValue: quoteOption, options: quoteOptionsOf(messages, quoteSettings), onSelect: chooseQuoteOption }))))),
|
|
319
458
|
timer: (React.createElement(Titled, { title: messages.menu.timer },
|
|
320
459
|
React.createElement(Choice, { options: [
|
|
321
|
-
|
|
322
|
-
label: isRunning ? messages.timer.pause : messages.timer.resume,
|
|
323
|
-
value: "toggle",
|
|
324
|
-
},
|
|
325
|
-
{ label: messages.timer.stop, value: "stop" },
|
|
460
|
+
...timerOptions,
|
|
326
461
|
{ label: messages.timer.back, value: "back" },
|
|
327
462
|
], onSelect: chooseTimerAction }))),
|
|
328
463
|
health: (React.createElement(HealthPanel, { language: language, messages: messages, palette: palette, progress: progress, today: today, onSelect: chooseHealthAction })),
|
|
329
|
-
|
|
330
|
-
|
|
464
|
+
logs: (React.createElement(LogsView, { height: Math.max(rows - 9, 6), language: language, logs: logs, messages: messages })),
|
|
465
|
+
diet: (React.createElement(EntryForm, { fields: dietFieldsOf(messages, language, progress.meals), messages: messages, title: messages.form.dietTitle, onSubmit: submitMeal })),
|
|
466
|
+
workout: (React.createElement(EntryForm, { fields: workoutFieldsOf(messages, progress.workouts), messages: messages, title: messages.form.workoutTitle, onSubmit: submitWorkout, onValues: (values) => {
|
|
331
467
|
setDraftActivity(values.activity);
|
|
332
468
|
} })),
|
|
333
469
|
profile: (React.createElement(EntryForm, { fields: profileFieldsOf(messages, progress.profile, name), messages: messages, title: messages.form.profileTitle, onSubmit: submitProfile })),
|
|
@@ -336,28 +472,45 @@ export default function App({ options, file, initialProgress }) {
|
|
|
336
472
|
label: messages.themes.names[option].padEnd(8),
|
|
337
473
|
value: option,
|
|
338
474
|
hint: React.createElement(Swatches, { palette: themes[option] }),
|
|
339
|
-
})),
|
|
475
|
+
})), onFocus: (value) => {
|
|
476
|
+
setPreviewTheme(pick(themeNames, value, themeName));
|
|
477
|
+
}, onSelect: chooseTheme }))),
|
|
340
478
|
language: (React.createElement(Titled, { title: messages.menu.language },
|
|
341
479
|
React.createElement(Choice, { initialValue: language, options: languageOptions, onSelect: chooseLanguage }))),
|
|
342
480
|
};
|
|
343
|
-
const
|
|
344
|
-
|
|
481
|
+
const preview = focusedEntry ? (React.createElement(Box, { flexDirection: "column" },
|
|
482
|
+
React.createElement(Text, { bold: true, color: palette.soft }, focusedEntry.status),
|
|
483
|
+
React.createElement(Box, { marginTop: 1 },
|
|
484
|
+
React.createElement(Text, { dimColor: true }, focusedEntry.description)),
|
|
485
|
+
React.createElement(Box, { marginTop: 1 },
|
|
486
|
+
React.createElement(Text, { dimColor: true, italic: true }, messages.describe.open)))) : null;
|
|
487
|
+
const todayPanel = (React.createElement(TodayPanel, { isCompact: layout.isTodayCompact, sections: sectionsOf(language, palette, progress, now), title: messages.today.title, width: layout.todayWidth }));
|
|
488
|
+
const recentBlock = (React.createElement(Box, { borderColor: palette.border, borderStyle: "round", flexDirection: "column", flexShrink: 0, paddingX: 1 },
|
|
489
|
+
React.createElement(Text, { bold: true, color: palette.soft }, messages.recent.title),
|
|
490
|
+
logs.length === 0 ? React.createElement(Text, { dimColor: true }, messages.recent.empty) : null,
|
|
491
|
+
logs.slice(0, recentCount).map((log) => (React.createElement(LogRow, { key: `${log.kind}-${log.at}`, language: language, log: log, messages: messages })))));
|
|
345
492
|
return providers(React.createElement(Box, { flexDirection: "column", height: size.rows ? rows : undefined, overflow: "hidden", width: columns },
|
|
346
493
|
React.createElement(Box, { borderColor: palette.accent, borderStyle: "round", flexDirection: "column", flexShrink: 0, paddingX: 1 },
|
|
347
494
|
React.createElement(Box, { justifyContent: "space-between" },
|
|
348
495
|
React.createElement(Text, { bold: true, color: palette.accent }, "\u273B Terminal Sigma"),
|
|
349
496
|
React.createElement(Text, { dimColor: true }, new Date(now).toTimeString().slice(0, 8))),
|
|
350
497
|
React.createElement(Text, { dimColor: true, italic: true, wrap: "truncate-end" }, `“${shownQuote.quote}” — ${shownQuote.author}`)),
|
|
351
|
-
|
|
352
|
-
React.createElement(
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
498
|
+
React.createElement(Box, { flexDirection: layout.stacked ? "column-reverse" : "row", flexGrow: 1 },
|
|
499
|
+
layout.showCharacter ? (React.createElement(Character, { activity: activity, badges: pet.badges, label: activityLabel, level: messages.level(level), levelNumber: level, look: lookOf(progress.profile), mood: pet.mood, name: name, width: characterWidth })) : null,
|
|
500
|
+
layout.showToday && layout.todayBeside ? todayPanel : null,
|
|
501
|
+
React.createElement(Box, { flexDirection: "column", width: layout.panelWidth },
|
|
502
|
+
React.createElement(Box, { flexDirection: layout.isMenuSplit ? "row" : "column", flexGrow: 1 },
|
|
503
|
+
React.createElement(Box, { borderColor: view === "menu" ? palette.accent : palette.border, borderStyle: "round", display: view === "logs" ? "none" : "flex", flexShrink: 0, paddingX: 1, width: layout.isMenuSplit ? menuListWidth : undefined },
|
|
504
|
+
React.createElement(Choice, { isNumbered: true, initialValue: focusedMenu, isActive: view === "menu", options: entries.map((entry) => ({
|
|
505
|
+
label: entry.label,
|
|
506
|
+
value: entry.key,
|
|
507
|
+
})), onFocus: (value) => {
|
|
508
|
+
setFocusedMenu(pick(menuOrder, value, "quotes"));
|
|
509
|
+
}, onSelect: openMenu })),
|
|
510
|
+
React.createElement(Box, { borderColor: view === "menu" ? palette.border : palette.accent, borderStyle: "round", flexDirection: "column", flexGrow: 1, overflow: "hidden", paddingX: 1 }, view === "menu" ? preview : panels[view])),
|
|
511
|
+
layout.showRecent && view !== "logs" ? recentBlock : null)),
|
|
512
|
+
layout.showToday && !layout.todayBeside ? todayPanel : null,
|
|
513
|
+
React.createElement(Footer, { hint: hint, message: isIdle || isRunning
|
|
361
514
|
? message.text
|
|
362
515
|
: `${messages.timer.pausedAt(phaseLabel, clock)} · ${message.text}`, status: isRunning ? messages.timer.left(phaseLabel, clock) : undefined, tone: message.tone })));
|
|
363
516
|
}
|