Skip to content

Commit

Permalink
feat: add formatState function to select and toggle
Browse files Browse the repository at this point in the history
defaults to prefixEmoji
allows for fancy things like 'lamp: on' and 'lamp: off'

BREAKING CHANGE: select multiselect is renamed to showFalseEmoji
  • Loading branch information
EdJoPaTo committed May 24, 2020
1 parent 5b80c9c commit e7cff1a
Show file tree
Hide file tree
Showing 4 changed files with 46 additions and 35 deletions.
10 changes: 4 additions & 6 deletions source/buttons/select.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ test('empty choices no buttons', async t => {

test('is set creates false button', async t => {
const func = generateSelectButtons('pre', ['a'], {
prefixTrue: 'T',
isSet: () => true,
set: () => {
t.fail('no need to call set on keyboard creation')
Expand All @@ -25,7 +24,7 @@ test('is set creates false button', async t => {

const buttons = await func(undefined)
t.deepEqual(buttons, [[{
text: 'T a',
text: ' a',
relativePath: 'preF:a'
}]])
})
Expand All @@ -45,10 +44,9 @@ test('is not set creates true button', async t => {
}]])
})

test('multiselect also prefixes currently false buttons', async t => {
test('showFalseEmoji also prefixes currently false buttons', async t => {
const func = generateSelectButtons('pre', ['a'], {
multiselect: true,
prefixFalse: 'F',
showFalseEmoji: true,
isSet: () => false,
set: () => {
t.fail('no need to call set on keyboard creation')
Expand All @@ -57,7 +55,7 @@ test('multiselect also prefixes currently false buttons', async t => {

const buttons = await func(undefined)
t.deepEqual(buttons, [[{
text: 'F a',
text: '🚫 a',
relativePath: 'preT:a'
}]])
})
Expand Down
14 changes: 7 additions & 7 deletions source/buttons/select.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,19 @@ import {CallbackButtonTemplate} from '../keyboard'
import {ConstOrPromise, ConstOrContextFunc} from '../generic-types'
import {getChoiceKeysFromChoices} from '../choices/understand-choices'
import {ManyChoicesOptions, Choices, createChoiceTextFunction, generateChoicesPaginationButtons} from '../choices'
import {PrefixOptions, prefixEmoji} from '../prefix'
import {prefixEmoji} from '../prefix'

import {getButtonsOfPage, getButtonsAsRows} from './align'

export type ChoiceIsSetFunc<Context> = (context: Context, key: string) => ConstOrPromise<boolean>
export type ChoiceSetFunc<Context> = (context: Context, key: string, newState: boolean) => ConstOrPromise<void>
export type FormatStateFunction<Context> = (context: Context, textResult: string, state: boolean, key: string) => ConstOrPromise<string>

export interface SelectOptions<Context> extends ManyChoicesOptions<Context>, PrefixOptions {
readonly multiselect?: boolean;
export interface SelectOptions<Context> extends ManyChoicesOptions<Context> {
readonly showFalseEmoji?: boolean;
readonly isSet: ChoiceIsSetFunc<Context>;
readonly set: ChoiceSetFunc<Context>;
readonly formatState?: FormatStateFunction<Context>;
}

export function generateSelectButtons<Context>(actionPrefix: string, choices: ConstOrContextFunc<Context, Choices>, options: SelectOptions<Context>): (context: Context) => Promise<CallbackButtonTemplate[][]> {
Expand All @@ -24,16 +26,14 @@ export function generateSelectButtons<Context>(actionPrefix: string, choices: Co
const choicesConstant = typeof choices === 'function' ? await choices(context) : choices
const choiceKeys = getChoiceKeysFromChoices(choicesConstant)
const textFunction = createChoiceTextFunction(choicesConstant, options.buttonText)
const formatFunction: FormatStateFunction<Context> = options.formatState ?? ((_, textResult, state) => prefixEmoji(textResult, state, {hideFalseEmoji: !options.showFalseEmoji}))
const currentPage = await options.getCurrentPage?.(context)
const keysOfPage = getButtonsOfPage(choiceKeys, options.columns, options.maxRows, currentPage)
const buttonsOfPage = await Promise.all(keysOfPage
.map(async key => {
const textResult = await textFunction(context, key)
const state = await options.isSet(context, key)
const text = prefixEmoji(textResult, state, {
hideFalseEmoji: !options.multiselect,
...options
})
const text = await formatFunction(context, textResult, state, key)

const dropinLetter = state ? 'F' : 'T'
const relativePath = actionPrefix + dropinLetter + ':' + key
Expand Down
29 changes: 29 additions & 0 deletions source/buttons/toggle.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import {CallbackButtonTemplate} from '../keyboard'
import {ContextPathFunc, ConstOrPromise, ConstOrContextPathFunc} from '../generic-types'
import {prefixEmoji} from '../prefix'

import {SingleButtonOptions} from './basic'

export type FormatStateFunction<Context> = (context: Context, text: string, state: boolean, path: string) => ConstOrPromise<string>

export interface ToggleOptions<Context> extends SingleButtonOptions<Context> {
readonly set: (context: Context, newState: boolean, path: string) => ConstOrPromise<void>;
readonly isSet: ContextPathFunc<Context, boolean>;
readonly formatState?: FormatStateFunction<Context>;
}

export function generateToggleButton<Context>(text: ConstOrContextPathFunc<Context, string>, actionPrefix: string, options: ToggleOptions<Context>): ContextPathFunc<Context, CallbackButtonTemplate | undefined> {
const formatFunction: FormatStateFunction<Context> = options.formatState ?? ((_, text, state) => prefixEmoji(text, state))
return async (context, path) => {
if (options.hide && await options.hide(context)) {
return undefined
}

const textResult = typeof text === 'function' ? await text(context, path) : text
const state = await options.isSet(context, path)
return {
text: await formatFunction(context, textResult, state, path),
relativePath: actionPrefix + ':' + (state ? 'false' : 'true')
}
}
}
28 changes: 6 additions & 22 deletions source/menu-template.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,20 +7,15 @@ import {ensureTriggerChild} from './path'
import {Keyboard, ButtonTemplate, CallbackButtonTemplate, ButtonTemplateRow, InlineKeyboard} from './keyboard'
import {MenuLike, Submenu} from './menu-like'
import {PaginationOptions, createPaginationChoices, SetPageFunction} from './buttons/pagination'
import {prefixEmoji, PrefixOptions} from './prefix'
import {SelectOptions, generateSelectButtons} from './buttons/select'
import {SingleButtonOptions} from './buttons/basic'
import {SubmenuOptions, ChooseIntoSubmenuOptions} from './buttons/submenu'
import {ToggleOptions, generateToggleButton} from './buttons/toggle'

export interface InteractionOptions<Context> extends SingleButtonOptions<Context> {
readonly do: ActionFunc<Context>;
}

export interface ToggleOptions<Context> extends SingleButtonOptions<Context>, PrefixOptions {
readonly set: (context: Context, newState: boolean) => Promise<unknown> | void;
readonly isSet: ContextFunc<Context, boolean>;
}

export class MenuTemplate<Context> {
private readonly _body: ContextPathFunc<Context, Body>
private readonly _keyboard: Keyboard<Context> = new Keyboard()
Expand Down Expand Up @@ -244,34 +239,23 @@ export class MenuTemplate<Context> {
toggle(text: ConstOrContextPathFunc<Context, string>, actionPrefix: string, options: ToggleOptions<Context>): void {
this._actions.add(
new RegExp(actionPrefix + ':true$'),
async context => {
await options.set(context, true)
async (context, path) => {
await options.set(context, true, path)
return '.'
},
options.hide
)

this._actions.add(
new RegExp(actionPrefix + ':false$'),
async context => {
await options.set(context, false)
async (context, path) => {
await options.set(context, false, path)
return '.'
},
options.hide
)

this._keyboard.add(Boolean(options.joinLastRow), async (context, path): Promise<CallbackButtonTemplate | undefined> => {
if (options.hide && await options.hide(context)) {
return undefined
}

const textResult = typeof text === 'function' ? await text(context, path) : text
const state = await options.isSet(context)
return {
text: prefixEmoji(textResult, state, options),
relativePath: actionPrefix + ':' + (state ? 'false' : 'true')
}
})
this._keyboard.add(Boolean(options.joinLastRow), generateToggleButton(text, actionPrefix, options))
}
}

Expand Down

0 comments on commit e7cff1a

Please sign in to comment.