Mari's guided journal software.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

112 lines
4.6 KiB

import {InquireFunction, ShowFunction} from "../Inquire";
import {ExpandChoiceOptions, Separator, SeparatorOptions} from "inquirer";
import {merge} from "../../utils/Merge";
import {Entry, EntryJTD} from "../../datatypes/Entry";
import {editYaml} from "../../schemata/YAMLPrompt";
const VIEW = ".View"
const DONE = ".Done"
export interface EntryMainMenuChoice<PropertyT extends string & keyof EntryMainMenuOptions> {
readonly type: "mainMenu"
readonly property: PropertyT
choice(input: EntryMainMenuOptions[PropertyT]): ExpandChoiceOptions & { value: PropertyT }
onSelected(input: EntryMainMenuOptions[PropertyT]): Promise<EntryMainMenuOptions[PropertyT]>
}
function getChoice<PropertyT extends string & keyof EntryMainMenuOptions>(choice: EntryMainMenuChoice<PropertyT>, entry: EntryMainMenuOptions): ExpandChoiceOptions & {value: PropertyT} {
return choice.choice(entry[choice.property])
}
async function onSelected<PropertyT extends string & keyof EntryMainMenuOptions>(choice: EntryMainMenuChoice<PropertyT>, entry: EntryMainMenuOptions): Promise<Partial<EntryMainMenuOptions>> {
return {
[choice.property]: await choice.onSelected(entry[choice.property])
}
}
export function makeEntryMainMenuChoice<PropertyT extends string & keyof EntryMainMenuOptions, OptionsT, OutputT>({property, key, name, short, injected, toOptions, toProperty}: {
property: PropertyT,
key: string,
name: string|((value: EntryMainMenuOptions[PropertyT]) => string),
short?: string|((value: EntryMainMenuOptions[PropertyT]) => string),
injected: (options: OptionsT) => Promise<OutputT>,
toOptions: (input: EntryMainMenuOptions[PropertyT]) => OptionsT,
toProperty: (output: OutputT) => EntryMainMenuOptions[PropertyT]
}): {
(options: OptionsT): Promise<OutputT>,
mainMenu: EntryMainMenuChoice<PropertyT>
} {
return Object.assign(injected, {
"mainMenu": {
type: "mainMenu" as const,
property,
choice(input: EntryMainMenuOptions[PropertyT]) {
return {
name: typeof name === "string" ? name : name(input),
short: typeof short === "string" ? short : typeof short === "function" ? short(input) : typeof name === "string" ? name : name(input),
value: property as PropertyT,
key,
}
},
async onSelected(input: EntryMainMenuOptions[PropertyT]): Promise<EntryMainMenuOptions[PropertyT]> {
const output = await injected(toOptions(input))
return toProperty(output)
},
}
})
}
export interface EntryMainMenuOptions extends Omit<Entry, "finishedAt"> {}
export interface EntryMainMenuDependencies {
readonly inquire: InquireFunction
readonly choices: readonly (EntryMainMenuChoice<any>|SeparatorOptions)[]
readonly showError: ShowFunction
}
export function entryMainMenuPrompt(deps: EntryMainMenuDependencies): (options: EntryMainMenuOptions) => Promise<Entry> {
return (options) => promptForEntryMainMenu(options, deps)
}
export async function promptForEntryMainMenu(entry: EntryMainMenuOptions, deps: EntryMainMenuDependencies): Promise<Entry> {
const {
inquire,
choices,
showError,
} = deps
const result = await inquire({
type: "expand",
message: "Anything else you want to add?",
pageSize: 999,
choices: [
...choices.map((choice) => choice.type === "separator" ? choice : getChoice(choice, entry)),
new Separator(),
{name: "View and edit this entry as yaml", value: VIEW, key: "v"},
{name: "Save and upload this entry", value: DONE, key: "q"},
new Separator(),
]
})
function continueWith(update: Partial<EntryMainMenuOptions>): Promise<Entry> {
const data = merge(update, entry)
return promptForEntryMainMenu(data, deps)
}
if (result === DONE) {
return {
...entry,
finishedAt: new Date(),
}
} else if (result === VIEW) {
const updated = await editYaml({
schema: EntryJTD,
currentValue: {...entry, finishedAt: new Date()},
name: "the current entry",
inquire,
showError,
})
const withoutFinishedAt = {...updated, finishedAt: undefined}
return continueWith(withoutFinishedAt)
} else {
const selectedChoice = choices.find((choice) => choice.type === "mainMenu" && choice.property === result) as EntryMainMenuChoice<any>
return continueWith(await onSelected(selectedChoice, entry))
}
}