parent
548f6766e4
commit
b1f03c0cf4
@ -0,0 +1,5 @@ |
||||
{ |
||||
"flags": { |
||||
"gulpfile": "gulpfile.cjs" |
||||
} |
||||
} |
@ -0,0 +1,96 @@ |
||||
import {schema} from "../schemata/SchemaData.js"; |
||||
|
||||
export enum SleepQuality { |
||||
ACIDIC = "Acidic", |
||||
RESTLESS = "Restless", |
||||
NIGHTMARISH = "Nightmarish", |
||||
TIMESKIP = "Timeskip", |
||||
DREAMLESS = "Dreamless", |
||||
DREAMY = "Dreamy", |
||||
ECSTASY = "Ecstasy", |
||||
} |
||||
export const SLEEP_QUALITIES: SleepQuality[] = [SleepQuality.ACIDIC, SleepQuality.RESTLESS, SleepQuality.NIGHTMARISH, SleepQuality.TIMESKIP, SleepQuality.DREAMLESS, SleepQuality.DREAMY, SleepQuality.ECSTASY] |
||||
|
||||
export type SleepQualityJTD = typeof SleepQualityJTD |
||||
export const SleepQualityJTD = schema({ |
||||
schema: { |
||||
enum: SLEEP_QUALITIES |
||||
}, |
||||
key: "sleepQuality", |
||||
references: [], |
||||
}) |
||||
|
||||
export enum WakeQuality { |
||||
AGONIZED = "Agonized", |
||||
EXHAUSTED = "Exhausted", |
||||
DROWSY = "Drowsy", |
||||
RESTED = "Rested", |
||||
ENERGIZED = "Energized", |
||||
} |
||||
export const WAKE_QUALITIES: WakeQuality[] = [WakeQuality.AGONIZED, WakeQuality.EXHAUSTED, WakeQuality.DROWSY, WakeQuality.RESTED, WakeQuality.ENERGIZED] |
||||
|
||||
export type WakeQualityJTD = typeof WakeQualityJTD |
||||
export const WakeQualityJTD = schema({ |
||||
schema: { |
||||
enum: WAKE_QUALITIES |
||||
}, |
||||
key: "wakeQuality", |
||||
references: [], |
||||
}) |
||||
|
||||
export interface SleepRecord { |
||||
readonly sleepAt?: Date |
||||
readonly sleepQuality?: SleepQuality |
||||
readonly interruptions?: number |
||||
readonly wakeAt?: Date |
||||
readonly wakeQuality?: WakeQuality |
||||
readonly dreams?: string |
||||
} |
||||
export type SleepRecordJTD = typeof SleepRecordJTD |
||||
export const SleepRecordJTD = schema({ |
||||
schema: { |
||||
optionalProperties: { |
||||
sleepAt: { type: "timestamp" }, |
||||
sleepQuality: SleepQualityJTD.reference, |
||||
interruptions: { type: "uint32" }, |
||||
wakeAt: { type: "timestamp" }, |
||||
wakeQuality: WakeQualityJTD.reference, |
||||
dreams: { type: "string" }, |
||||
} |
||||
}, |
||||
typeHint: null as SleepRecord|null, |
||||
key: "sleepRecord", |
||||
references: [SleepQualityJTD, WakeQualityJTD], |
||||
}) |
||||
|
||||
export type SleepRecordListJTD = typeof SleepRecordListJTD |
||||
export const SleepRecordListJTD = schema({ |
||||
schema: { |
||||
elements: SleepRecordJTD.reference |
||||
}, |
||||
typeHint: null as (readonly SleepRecord[])|null, |
||||
key: "sleepRecords", |
||||
references: [SleepRecordJTD, ...SleepRecordJTD.requiredReferences], |
||||
}) |
||||
|
||||
const TimeFormat = Intl.DateTimeFormat([], { |
||||
dateStyle: undefined, |
||||
timeStyle: undefined, |
||||
year: undefined, |
||||
month: undefined, |
||||
day: undefined, |
||||
weekday: undefined, |
||||
hour: "numeric", |
||||
minute: "2-digit", |
||||
second: undefined, |
||||
fractionalSecondDigits: undefined, |
||||
timeZone: undefined, |
||||
hour12: true, |
||||
hourCycle: "h12", |
||||
}) |
||||
|
||||
export function sleepRecordToString(record: SleepRecord) { |
||||
const sleepAt = record.sleepAt === undefined ? null : TimeFormat.format(record.sleepAt) |
||||
const wakeAt = record.wakeAt === undefined ? null : TimeFormat.format(record.wakeAt) |
||||
return `${sleepAt ?? "?:??"} (${record.sleepQuality ?? "???"}) -${record.interruptions ?? "?"}-> ${wakeAt ?? "?:??"} (${record.wakeQuality ?? "???"})${typeof record.dreams === "string" ? " with dream journal" : ""}` |
||||
} |
@ -1,13 +1,15 @@ |
||||
import {addEntryCommand} from "./commands/AddEntry"; |
||||
import {updateEmpathyGuideCommand} from "./commands/UpdateEmpathyGuide"; |
||||
import yargs from "yargs" |
||||
import {addEntryCommand} from "./commands/AddEntry.js"; |
||||
import {updateEmpathyGuideCommand} from "./commands/UpdateEmpathyGuide.js"; |
||||
import {Argv, default as yargs} from "yargs" |
||||
|
||||
yargs |
||||
const Yargs = yargs as unknown as {(): Argv} |
||||
|
||||
Yargs() |
||||
.scriptName("mari-status-bar") |
||||
.command(addEntryCommand()) |
||||
.command(updateEmpathyGuideCommand()) |
||||
.demandCommand() |
||||
.parseAsync() |
||||
.catch((err) => { |
||||
.catch((err: unknown) => { |
||||
console.error(err) |
||||
}) |
@ -0,0 +1,196 @@ |
||||
import { |
||||
DateQuestion, |
||||
EditorQuestion, |
||||
ListQuestion, |
||||
NumberQuestion, |
||||
} from "inquirer"; |
||||
import {Separator} from "../Inquire.js"; |
||||
import {InquireFunction} from "../Inquire.js"; |
||||
import {SleepQuality, SleepRecord, sleepRecordToString, WakeQuality} from "../../datatypes/SleepRecord.js"; |
||||
import {DateTime} from "luxon"; |
||||
import chalk from "chalk"; |
||||
import {EntryMainMenuChoice, makeEntryMainMenuChoice} from "./EntryMainMenuPrompt.js"; |
||||
|
||||
export interface SleepRecordPromptOptions extends Partial<Omit<EditorQuestion, "name"|"type"|"default"> & Omit<DateQuestion, "name"|"type"|"default"> & Omit<ListQuestion, "name"|"type"|"default"> & Omit<NumberQuestion, "name"|"type"|"default">>{ |
||||
default?: SleepRecord, |
||||
} |
||||
|
||||
export interface SleepRecordPromptDependencies { |
||||
readonly inquire: InquireFunction<EditorQuestion, string> & InquireFunction<DateQuestion, Date|null> & InquireFunction<ListQuestion & {default: SleepQuality|undefined}, SleepQuality|undefined> & InquireFunction<ListQuestion & {default: WakeQuality|undefined}, WakeQuality|undefined> & InquireFunction<NumberQuestion, number> |
||||
} |
||||
|
||||
export function sleepRecordPrompt(deps: SleepRecordPromptDependencies): { |
||||
(options: SleepRecordPromptOptions): Promise<SleepRecord|null>, |
||||
mainMenu: EntryMainMenuChoice<"sleepRecords">, |
||||
} { |
||||
return makeEntryMainMenuChoice({ |
||||
property: "sleepRecords", |
||||
name: (input) => typeof input !== "object" || input.length === 0 ? `Add sleep record` : `Change sleep record ${chalk.dim(`(currently ${chalk.greenBright(sleepRecordToString(input[0]))})`)}`, |
||||
key: "z", |
||||
injected: (options) => promptForSleepRecord(options, deps), |
||||
toOptions: (value) => typeof value !== "object" || value.length === 0 ? {} : {default: value[0]}, |
||||
toProperty: (value) => value === null ? undefined : [value], |
||||
}) |
||||
} |
||||
|
||||
export async function promptForSleepRecord(options: SleepRecordPromptOptions, {inquire}: SleepRecordPromptDependencies): Promise<SleepRecord | null> { |
||||
const oldBedTime = options.default?.sleepAt |
||||
const newBedTime = await inquire({ |
||||
...options, |
||||
type: "date", |
||||
message: "About when did you get to sleep?", |
||||
clearable: true, |
||||
startCleared: false, |
||||
default: oldBedTime ?? DateTime.local().set({hour: 23, minute: 0, second: 0, millisecond: 0}).minus({days: 1}).toJSDate(), |
||||
format: { |
||||
year: undefined, |
||||
weekday: "short", |
||||
month: "short", |
||||
day: "numeric", |
||||
hour: "numeric", |
||||
minute: "numeric", |
||||
second: undefined, |
||||
fractionalSecondDigits: undefined, |
||||
timeZoneName: "short", |
||||
}, |
||||
startingDatePart: "hour", |
||||
deltas: { |
||||
weekday: 1, |
||||
hour: [1, 5, 10], |
||||
minute: [15, 5, 1], |
||||
default: 0 |
||||
} |
||||
}) |
||||
const oldSleepQuality = options.default?.sleepQuality |
||||
const newSleepQuality = await inquire({ |
||||
...options, |
||||
type: "list", |
||||
message: `How'd you sleep?`, |
||||
choices: [ |
||||
{ |
||||
value: SleepQuality.ACIDIC, |
||||
name: `Acidic ${chalk.dim("(extremely poor/extremely restless/heavy acid reflux)")}` |
||||
}, |
||||
{ |
||||
value: SleepQuality.RESTLESS, |
||||
name: `Restless ${chalk.dim("(very poor/very restless/fever dreamy)")}` |
||||
}, |
||||
{ |
||||
value: SleepQuality.NIGHTMARISH, |
||||
name: `Nightmarish ${chalk.dim("(poor/restless/plagued with nightmares)")}` |
||||
}, |
||||
{ |
||||
value: SleepQuality.TIMESKIP, |
||||
name: `Timeskip ${chalk.dim("(neutral/passage of time not recognized)")}` |
||||
}, |
||||
{ |
||||
value: SleepQuality.DREAMLESS, |
||||
name: `Dreamless ${chalk.dim("(good/peaceful/no dreams had or remembered)")}` |
||||
}, |
||||
{ |
||||
value: SleepQuality.DREAMY, |
||||
name: `Dreamy ${chalk.dim("(very good/peaceful/neutral to somewhat pleasant dreams)")}` |
||||
}, |
||||
{ |
||||
value: SleepQuality.ECSTASY, |
||||
name: `Ecstasy ${chalk.dim("(extremely good/very peaceful/very pleasant dreams)")}` |
||||
}, |
||||
new Separator(), |
||||
{ |
||||
value: undefined, |
||||
name: `Uncertain` |
||||
}, |
||||
], |
||||
default: oldSleepQuality ?? SleepQuality.TIMESKIP, |
||||
pageSize: 999, |
||||
}) |
||||
const oldInterruptions = options.default?.interruptions |
||||
const newInterruptionsRaw = await inquire({ |
||||
...options, |
||||
type: "number", |
||||
message: "How many times did you end up waking up during the sleep?", |
||||
default: oldInterruptions ?? -1, |
||||
}) |
||||
const newInterruptions = newInterruptionsRaw < 0 ? undefined : Math.round(newInterruptionsRaw) |
||||
const oldWakeTime = options.default?.wakeAt |
||||
const newWakeTime = await inquire({ |
||||
...options, |
||||
type: "date", |
||||
message: "Around when did you get up?", |
||||
clearable: true, |
||||
startCleared: false, |
||||
default: oldWakeTime ?? DateTime.local().set({hour: 7, minute: 0, second: 0, millisecond: 0}).toJSDate(), |
||||
format: { |
||||
year: undefined, |
||||
weekday: "short", |
||||
month: "short", |
||||
day: "numeric", |
||||
hour: "numeric", |
||||
minute: "numeric", |
||||
second: undefined, |
||||
fractionalSecondDigits: undefined, |
||||
timeZoneName: "short", |
||||
}, |
||||
startingDatePart: "hour", |
||||
deltas: { |
||||
hour: [1, 5, 10], |
||||
minute: [15, 5, 1], |
||||
default: 0 |
||||
} |
||||
}) |
||||
const oldWakeQuality = options.default?.wakeQuality |
||||
const newWakeQuality = await inquire({ |
||||
...options, |
||||
type: "list", |
||||
message: `How'd you feel when you got up?`, |
||||
choices: [ |
||||
{ |
||||
value: WakeQuality.AGONIZED, |
||||
name: `Agonized ${chalk.dim("(in physical pain/barely able or unable to get out of bed)")}` |
||||
}, |
||||
{ |
||||
value: WakeQuality.EXHAUSTED, |
||||
name: `Exhausted ${chalk.dim("(very tired/not wanting to get out of bed)")}` |
||||
}, |
||||
{ |
||||
value: WakeQuality.DROWSY, |
||||
name: `Drowsy ${chalk.dim("(a bit sleepy but willing to get out of bed)")}` |
||||
}, |
||||
{ |
||||
value: WakeQuality.RESTED, |
||||
name: `Rested ${chalk.dim("(well rested and ready to get going)")}` |
||||
}, |
||||
{ |
||||
value: WakeQuality.ENERGIZED, |
||||
name: `Energized ${chalk.dim("(thoroughly recharged and eager to get up and running)")}` |
||||
}, |
||||
new Separator(), |
||||
{ |
||||
value: undefined, |
||||
name: `Uncertain` |
||||
}, |
||||
], |
||||
default: oldWakeQuality ?? WakeQuality.DROWSY, |
||||
pageSize: 999, |
||||
}) |
||||
const oldDreamJournal = options.default?.dreams |
||||
const newDreamJournalRaw = (await inquire({ |
||||
...options, |
||||
type: "editor", |
||||
message: typeof oldDreamJournal === "string" ? `Edit dream journal for this sleep session:` : `Type up dream journal for this sleep session:`, |
||||
default: oldDreamJournal, |
||||
})).trimEnd() |
||||
const newDreamJournal = newDreamJournalRaw === "" ? undefined : newDreamJournalRaw |
||||
const result: SleepRecord = { |
||||
dreams: newDreamJournal, |
||||
interruptions: newInterruptions, |
||||
sleepAt: newBedTime ?? undefined, |
||||
sleepQuality: newSleepQuality, |
||||
wakeAt: newWakeTime ?? undefined, |
||||
wakeQuality: newWakeQuality, |
||||
} |
||||
return ( |
||||
Object.keys(result).map((key: keyof SleepRecord) => result[key]).some(value => (value !== undefined)) |
||||
? result |
||||
: null) |
||||
} |
@ -0,0 +1,386 @@ |
||||
/* |
||||
* Adapted from https://github.com/haversnail/inquirer-date-prompt:
|
||||
* MIT License |
||||
* |
||||
* Copyright (c) 2021 Alex Havermale |
||||
* |
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy |
||||
* of this software and associated documentation files (the "Software"), to deal |
||||
* in the Software without restriction, including without limitation the rights |
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
||||
* copies of the Software, and to permit persons to whom the Software is |
||||
* furnished to do so, subject to the following conditions: |
||||
* |
||||
* The above copyright notice and this permission notice shall be included in all |
||||
* copies or substantial portions of the Software. |
||||
* |
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE |
||||
* SOFTWARE. |
||||
*/ |
||||
|
||||
import chalk from "chalk"; |
||||
import {DateTime, DurationObjectUnits} from "luxon"; |
||||
import inquirer, {Answers, DateQuestion, Question} from "inquirer"; |
||||
import {Interface as ReadLineInterface, Key} from "readline"; |
||||
import Prompt from "inquirer/lib/prompts/base.js"; |
||||
import observe from "inquirer/lib/utils/events.js"; |
||||
import {map, takeUntil} from "rxjs/operators/index.js"; |
||||
import cliCursor from "cli-cursor" |
||||
import SuccessfulPromptStateData = inquirer.prompts.SuccessfulPromptStateData; |
||||
import FailedPromptStateData = inquirer.prompts.FailedPromptStateData; |
||||
|
||||
declare module "inquirer" { |
||||
|
||||
export interface DateQuestionOptions<AnswerT extends Answers> { |
||||
/** |
||||
* Transforms the value to display to the user. |
||||
* |
||||
* @param date |
||||
* The currently selected date in string format. |
||||
* |
||||
* @param answers |
||||
* The answers provided by the users. |
||||
* |
||||
* @param flags |
||||
* Additional information about the value. |
||||
* |
||||
* @returns |
||||
* The value to display to the user. |
||||
*/ |
||||
transformer?( |
||||
date: string, |
||||
answers: AnswerT, |
||||
flags: { isDirty?: boolean; isCleared?: boolean; isFinal?: boolean }, |
||||
): string | Promise<string>; |
||||
|
||||
/** |
||||
* A Boolean value indicating whether the prompt is clearable. |
||||
* If `true`, pressing `backspace` or `delete` will replace the current value with `null`. |
||||
*/ |
||||
clearable?: boolean; |
||||
|
||||
/** |
||||
* A Boolean value indicating whether the prompt should start cleared even though a default is provided. |
||||
* If `true`, the value will start off null, but clearing it will change to the default date. |
||||
*/ |
||||
startCleared?: boolean; |
||||
|
||||
/** |
||||
* A specific locale to use when formatting the date. |
||||
* If no locale is provided, it will default to the user's current locale. |
||||
* @see the {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/DateTimeFormat|Intl.DateTimeFormat} docs for more info.
|
||||
*/ |
||||
locale?: string; |
||||
|
||||
/** |
||||
* A set of options for customizing the date format. |
||||
* @see the {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/DateTimeFormat|Intl.DateTimeFormat} docs for more info.
|
||||
*/ |
||||
format?: Intl.DateTimeFormatOptions; |
||||
/** The date part that should be highlighted when the prompt is shown. */ |
||||
startingDatePart?: Intl.DateTimeFormatPartTypes |
||||
/** The amount by which each value should change. If null, the value will not be selectable. */ |
||||
deltas?: { |
||||
[key in Intl.DateTimeFormatPartTypes|"default"]?: number|[number]|[number,number]|[number,number,number]|[number,number,number,number]|0 |
||||
} |
||||
} |
||||
|
||||
export interface DateQuestion<AnswerT extends Answers = Answers> extends Question<AnswerT>, DateQuestionOptions<AnswerT> { |
||||
/** @inheritDoc */ |
||||
type: "date" |
||||
/** If the default is not provided or null, the value will be the present date at the time that the prompt is shown. */ |
||||
default?: Date|null |
||||
} |
||||
|
||||
export interface QuestionMap { |
||||
["date"]: DateQuestion |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* A lookup object that maps each date part type to the corresponding field of a duration. |
||||
*/ |
||||
const offsetLookup: Partial<Record<Intl.DateTimeFormatPartTypes, keyof DurationObjectUnits>> = { |
||||
year: "years", |
||||
month: "months", |
||||
day: "days", |
||||
hour: "hours", |
||||
minute: "minutes", |
||||
second: "seconds", |
||||
weekday: "days", |
||||
}; |
||||
|
||||
/** |
||||
* Returns the index of the _last_ element in the array where predicate is true, and -1 otherwise. |
||||
*/ |
||||
function findLastIndex<T>(array: T[], predicate: (value: T, index: number, obj: T[]) => boolean) { |
||||
let l = array.length; |
||||
while (l--) { |
||||
if (predicate(array[l], l, array)) return l; |
||||
} |
||||
return -1; |
||||
} |
||||
|
||||
/** |
||||
* Represents a date prompt. |
||||
*/ |
||||
export class DateInput<AnswerT extends Answers = Answers, QuestionT extends DateQuestion<AnswerT> = DateQuestion> extends Prompt<QuestionT> { |
||||
date: DateTime |
||||
readonly transformer: DateQuestion["transformer"] |
||||
readonly clearable: boolean |
||||
readonly format: Intl.DateTimeFormatOptions |
||||
readonly deltas: NonNullable<DateQuestion["deltas"]> |
||||
done: ((state: unknown) => void)|null = null |
||||
isDirty: boolean |
||||
isCleared: boolean |
||||
cursorIndex: number |
||||
firstEditableIndex: number |
||||
lastEditableIndex: number |
||||
|
||||
constructor(questions: QuestionT, rl: ReadLineInterface, answers: AnswerT) { |
||||
super(questions, rl, answers); |
||||
// Set the format object based on the user's specified options:
|
||||
const { transformer, clearable, startCleared, locale, format = {}, default: date, deltas, startingDatePart } = this.opt; |
||||
this.transformer = transformer |
||||
this.deltas = deltas ?? {} |
||||
this.clearable = clearable ?? false |
||||
this.format = { |
||||
year: "numeric", |
||||
month: "numeric", |
||||
day: "numeric", |
||||
hour: "numeric", |
||||
minute: "numeric", |
||||
...format, |
||||
}; |
||||
// Set the date object with either the default value or the current date:
|
||||
this.date = DateTime.fromJSDate(date ?? new Date()); |
||||
if (typeof locale === "string") { |
||||
this.date = this.date.setLocale(locale) |
||||
} |
||||
// Clear the default value option (so it won't be printed by the Prompt class):
|
||||
this.opt.default = null; |
||||
this.isDirty = false; |
||||
this.isCleared = startCleared ?? false; |
||||
// Set the first and last indices of the editable date parts:
|
||||
this.firstEditableIndex = this.dateParts.findIndex((part) => this.isDatePartEditable(part.type)); |
||||
this.lastEditableIndex = findLastIndex(this.dateParts, (part) => this.isDatePartEditable(part.type)); |
||||
// Set the cursor index to the first editable part:
|
||||
this.cursorIndex = !!startingDatePart && this.isDatePartEditable(startingDatePart) ? this.dateParts.findIndex((part) => part.type === startingDatePart) : this.firstEditableIndex; |
||||
} |
||||
|
||||
// Called by parent class:
|
||||
_run(cb: DateInput["done"]) { |
||||
this.done = cb; |
||||
|
||||
// Observe events:
|
||||
const events = observe(this.rl); |
||||
const submit = events.line.pipe(map(() => (this.isCleared ? null : this.date.toJSDate()))); |
||||
const validation = this.handleSubmitEvents(submit); |
||||
validation.success.forEach(this.onEnd.bind(this)); |
||||
validation.error.forEach(this.onError.bind(this)); |
||||
events.keypress.pipe(takeUntil(validation.success)).forEach(this.onKeypress.bind(this)); |
||||
|
||||
// Init the prompt:
|
||||
cliCursor.hide(); |
||||
this.render(); |
||||
|
||||
return this; |
||||
} |
||||
|
||||
/** |
||||
* Renders the prompt. |
||||
* @param {string} [error] |
||||
*/ |
||||
render(error?: string) { |
||||
let message = this.getQuestion(); // The question portion of the output, including any prefix and suffix
|
||||
|
||||
const { isDirty, isCleared } = this; |
||||
const isFinal = this.status === "answered"; |
||||
|
||||
if (!isCleared) { |
||||
const dateString = this.dateParts |
||||
.map(({ value }, index) => |
||||
isFinal |
||||
? chalk.cyan(value) |
||||
: index === this.cursorIndex |
||||
? chalk.inverse(value) |
||||
: !isDirty |
||||
? chalk.dim(value) |
||||
: value, |
||||
) |
||||
.join(""); |
||||
|
||||
// Apply the transformer function if one was provided:
|
||||
message += this.opt.transformer |
||||
? this.opt.transformer(dateString, this.answers as AnswerT, { isDirty, isCleared, isFinal }) |
||||
: dateString; |
||||
|
||||
// Display info on how to clear if the prompt is clearable:
|
||||
if (this.opt.clearable && !isFinal) { |
||||
message += chalk.dim(" (<delete> to clear) "); |
||||
} |
||||
} |
||||
|
||||
const bottomContent = error ? chalk.red(">> ") + error : ""; |
||||
|
||||
// Render the final message:
|
||||
this.screen.render(message, bottomContent); |
||||
} |
||||
|
||||
/** |
||||
* The end event handler. |
||||
*/ |
||||
onEnd({ value }: SuccessfulPromptStateData<Date|null>) { |
||||
this.status = "answered"; |
||||
|
||||
// Re-render prompt
|
||||
this.render(); |
||||
|
||||
this.screen.done(); |
||||
cliCursor.show(); |
||||
if (this.done !== null) { |
||||
this.done(value); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* The error event handler. |
||||
*/ |
||||
onError({ isValid }: FailedPromptStateData) { |
||||
this.render(isValid || undefined); |
||||
} |
||||
|
||||
/** |
||||
* The array of date part objects according to the user's specified format. |
||||
*/ |
||||
get dateParts() { |
||||
return this.date.toLocaleParts(this.format); |
||||
} |
||||
|
||||
/** |
||||
* The currently selected date part. |
||||
*/ |
||||
get currentDatePart() { |
||||
return this.dateParts[this.cursorIndex]; |
||||
} |
||||
|
||||
isDatePartEditable(part: Intl.DateTimeFormatPartTypes): boolean { |
||||
return offsetLookup.hasOwnProperty(part) && ((this.deltas[part] ?? this.deltas["default"] ?? 1) !== 0) |
||||
} |
||||
|
||||
/** |
||||
* A Boolean value indicating whether the currently selected date part is editable. |
||||
*/ |
||||
get isCurrentDatePartEditable() { |
||||
return this.isDatePartEditable(this.currentDatePart.type); |
||||
} |
||||
|
||||
/** |
||||
* Moves the cursor index to the right. |
||||
*/ |
||||
incrementCursorIndex() { |
||||
if (this.cursorIndex < this.lastEditableIndex) { |
||||
this.cursorIndex++; |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Moves the cursor index to the left. |
||||
*/ |
||||
decrementCursorIndex() { |
||||
if (this.cursorIndex > this.firstEditableIndex) { |
||||
this.cursorIndex--; |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Shifts the currently selected date part to the specified offset value. |
||||
* The default value is `0`. |
||||
* @param {number} offset |
||||
*/ |
||||
shiftDatePartValue(offset = 0) { |
||||
const { type } = this.currentDatePart; |
||||
const duration: DurationObjectUnits = {} |
||||
const offsetProperty = offsetLookup[type] |
||||
if (offset !== 0 && typeof offsetProperty === "string") { |
||||
duration[offsetProperty] = offset |
||||
// Set the input as "dirty" now that the initial date is being changed:
|
||||
this.isDirty = true; |
||||
this.date = this.date.plus(duration) |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Increments the currently selected date part by one. |
||||
*/ |
||||
incrementDatePartValueBy(value = 1) { |
||||
this.shiftDatePartValue(value); |
||||
} |
||||
|
||||
/** |
||||
* Decrements the currently selected date part by one. |
||||
*/ |
||||
decrementDatePartValueBy(value = 1) { |
||||
this.shiftDatePartValue(-1 * value); |
||||
} |
||||
|
||||
/** |
||||
* The keypress event handler. |
||||
*/ |
||||
onKeypress({ key }: {key: Key}) { |
||||
// Reset cleared state if any other key is pressed:
|
||||
if (this.isCleared) { |
||||
this.isCleared = false; |
||||
this.isDirty = true; |
||||
return |
||||
} |
||||
// Calculate the amount to increment/decrement by based on modifiers:
|
||||
const deltas = this.deltas[this.currentDatePart.type] ?? this.deltas["default"] ?? [1, 10, 100] |
||||
const amount = ((): number => { |
||||
if (typeof deltas === "number") { |
||||
return deltas |
||||
} else { |
||||
switch (deltas.length) { |
||||
case 1: |
||||
return deltas[0] |
||||
case 2: |
||||
return (key.shift || key.meta) ? deltas[1] : deltas[0] |
||||
case 3: |
||||
return (key.shift || key.meta) ? (key.shift && key.meta) ? deltas[2] : deltas[1] : deltas[0] |
||||
case 4: |
||||
return key.shift ? key.meta ? deltas[3] : deltas[1] : key.meta ? deltas[2] : deltas[0] |
||||
} |
||||
} |
||||
})() |
||||
|
||||
switch (key.name) { |
||||
case "right": |
||||
do { |
||||
this.incrementCursorIndex(); |
||||
} while (!this.isCurrentDatePartEditable); // increments the cursor index until it hits an editable value
|
||||
break; |
||||
case "left": |
||||
do { |
||||
this.decrementCursorIndex(); |
||||
} while (!this.isCurrentDatePartEditable); // decrements the cursor index until it hits an editable value
|
||||
break; |
||||
case "up": |
||||
this.incrementDatePartValueBy(amount); |
||||
break; |
||||
case "down": |
||||
this.decrementDatePartValueBy(amount); |
||||
break; |
||||
case "delete": |
||||
case "backspace": |
||||
if (this.clearable) this.isCleared = true; |
||||
break; |
||||
} |
||||
|
||||
this.render(); |
||||
} |
||||
} |
@ -1,8 +1,10 @@ |
||||
import {registerPrompt} from "inquirer"; |
||||
import {MultiTextInput} from "./MultiTextInput"; |
||||
import {HierarchicalCheckboxInput} from "./HierarchicalCheckboxInput"; |
||||
import {registerPrompt} from "../Inquire.js"; |
||||
import {MultiTextInput} from "./MultiTextInput.js"; |
||||
import {HierarchicalCheckboxInput} from "./HierarchicalCheckboxInput.js"; |
||||
import {DateInput} from "./DateInput.js"; |
||||
|
||||
export function registerPrompts() { |
||||
registerPrompt("multitext", MultiTextInput) |
||||
registerPrompt("hierarchical-checkbox", HierarchicalCheckboxInput) |
||||
registerPrompt("date", DateInput) |
||||
} |
Loading…
Reference in new issue