feat(dispatcher): scene transition hooks + ToScene when exiting
This commit is contained in:
parent
8e04c13b60
commit
3968a35654
3 changed files with 135 additions and 13 deletions
59
packages/dispatcher/src/context/scene-transition.ts
Normal file
59
packages/dispatcher/src/context/scene-transition.ts
Normal file
|
@ -0,0 +1,59 @@
|
|||
import { MtTypeAssertionError } from '@mtcute/core'
|
||||
import { makeInspectable } from '@mtcute/core/utils.js'
|
||||
|
||||
import { BusinessMessageContext } from './business-message.js'
|
||||
import { CallbackQueryContext, InlineCallbackQueryContext } from './callback-query.js'
|
||||
import { MessageContext } from './message.js'
|
||||
import { UpdateContextType } from './parse.js'
|
||||
|
||||
/** Update which is dispatched whenever scene is entered or exited */
|
||||
export class SceneTransitionContext {
|
||||
constructor(
|
||||
/** Name of the previous scene, if any */
|
||||
readonly previousScene: string | null,
|
||||
/** Update, handler for which triggered the transition */
|
||||
readonly update: UpdateContextType,
|
||||
) {}
|
||||
|
||||
/** Get {@link update}, asserting it is a message-related update */
|
||||
get message(): MessageContext {
|
||||
if (this.update instanceof MessageContext) {
|
||||
return this.update
|
||||
}
|
||||
|
||||
throw new MtTypeAssertionError('SceneTransitionContext.message', 'message', this.update._name)
|
||||
}
|
||||
|
||||
/** Get {@link update}, asserting it is a business message-related update */
|
||||
get businessMessage(): BusinessMessageContext {
|
||||
if (this.update instanceof BusinessMessageContext) {
|
||||
return this.update
|
||||
}
|
||||
|
||||
throw new MtTypeAssertionError('SceneTransitionContext.businessMessage', 'business message', this.update._name)
|
||||
}
|
||||
|
||||
/** Get {@link update}, asserting it is a callback query update */
|
||||
get callbackQuery(): CallbackQueryContext {
|
||||
if (this.update instanceof CallbackQueryContext) {
|
||||
return this.update
|
||||
}
|
||||
|
||||
throw new MtTypeAssertionError('SceneTransitionContext.callbackQuery', 'callback query', this.update._name)
|
||||
}
|
||||
|
||||
/** Get {@link update}, asserting it is an inline callback query update */
|
||||
get inlineCallbackQuery(): InlineCallbackQueryContext {
|
||||
if (this.update instanceof InlineCallbackQueryContext) {
|
||||
return this.update
|
||||
}
|
||||
|
||||
throw new MtTypeAssertionError(
|
||||
'SceneTransitionContext.inlineCallbackQuery',
|
||||
'inline callback query',
|
||||
this.update._name,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
makeInspectable(SceneTransitionContext)
|
|
@ -39,6 +39,7 @@ import {
|
|||
PreCheckoutQueryContext,
|
||||
} from './context/index.js'
|
||||
import { _parsedUpdateToContext, UpdateContextType } from './context/parse.js'
|
||||
import { SceneTransitionContext } from './context/scene-transition.js'
|
||||
import { filters, UpdateFilter } from './filters/index.js'
|
||||
// begin-codegen-imports
|
||||
import {
|
||||
|
@ -143,6 +144,11 @@ export class Dispatcher<State extends object = never> {
|
|||
state?: UpdateState<State>,
|
||||
) => MaybePromise<void>
|
||||
|
||||
private _sceneTransitionHandler?: (
|
||||
update: SceneTransitionContext,
|
||||
state: UpdateState<State>,
|
||||
) => MaybePromise<PropagationAction | void>
|
||||
|
||||
protected constructor(client?: TelegramClient, params?: DispatcherParams) {
|
||||
this.dispatchRawUpdate = this.dispatchRawUpdate.bind(this)
|
||||
this.dispatchUpdate = this.dispatchUpdate.bind(this)
|
||||
|
@ -462,7 +468,10 @@ export class Dispatcher<State extends object = never> {
|
|||
(update.name === 'new_message' ||
|
||||
update.name === 'edit_message' ||
|
||||
update.name === 'callback_query' ||
|
||||
update.name === 'message_group')
|
||||
update.name === 'message_group' ||
|
||||
update.name === 'new_business_message' ||
|
||||
update.name === 'edit_business_message' ||
|
||||
update.name === 'business_message_group')
|
||||
) {
|
||||
if (!parsedContext) parsedContext = _parsedUpdateToContext(this._client, update)
|
||||
const key = await this._stateKeyDelegate!(parsedContext as any)
|
||||
|
@ -521,6 +530,40 @@ export class Dispatcher<State extends object = never> {
|
|||
handled = true
|
||||
} else continue
|
||||
|
||||
if (parsedState && this._scenes) {
|
||||
// check if scene transition was made
|
||||
const newScene = parsedState.scene
|
||||
|
||||
if (parsedScene !== newScene) {
|
||||
const nextDp = newScene ? this._scenes.get(newScene) : this._parent
|
||||
|
||||
if (!nextDp) {
|
||||
throw new MtArgumentError(`Scene ${newScene} not found`)
|
||||
}
|
||||
|
||||
if (nextDp._sceneTransitionHandler) {
|
||||
const transition = new SceneTransitionContext(parsedScene, parsedContext)
|
||||
const transitionResult = await nextDp._sceneTransitionHandler?.(
|
||||
transition,
|
||||
parsedState,
|
||||
)
|
||||
|
||||
switch (transitionResult) {
|
||||
case 'stop':
|
||||
return true
|
||||
case 'continue':
|
||||
continue
|
||||
case 'scene': {
|
||||
const scene = parsedState.scene
|
||||
const dp = scene ? nextDp._scenes!.get(scene)! : nextDp._parent!
|
||||
|
||||
return dp._dispatchUpdateNowImpl(update, undefined, scene, true)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
switch (result) {
|
||||
case 'continue':
|
||||
continue
|
||||
|
@ -535,18 +578,10 @@ export class Dispatcher<State extends object = never> {
|
|||
throw new MtArgumentError('Cannot use ToScene without state')
|
||||
}
|
||||
|
||||
const scene = parsedState['_scene']
|
||||
const scene = parsedState.scene
|
||||
const dp = scene ? this._scenes!.get(scene)! : this._parent!
|
||||
|
||||
if (!scene) {
|
||||
throw new MtArgumentError('Cannot use ToScene without entering a scene')
|
||||
}
|
||||
|
||||
return this._scenes!.get(scene)!._dispatchUpdateNowImpl(
|
||||
update,
|
||||
undefined,
|
||||
scene,
|
||||
true,
|
||||
)
|
||||
return dp._dispatchUpdateNowImpl(update, undefined, scene, true)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -739,6 +774,7 @@ export class Dispatcher<State extends object = never> {
|
|||
child._client = this._client
|
||||
child._storage = this._storage
|
||||
child._deps = this._deps
|
||||
child._scenes = this._scenes
|
||||
child._stateKeyDelegate = this._stateKeyDelegate
|
||||
child._customStorage ??= this._customStorage
|
||||
child._customStateKeyDelegate ??= this._customStateKeyDelegate
|
||||
|
@ -1071,6 +1107,32 @@ export class Dispatcher<State extends object = never> {
|
|||
this._addKnownHandler('raw', filter, handler, group)
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a scene transition handler
|
||||
*
|
||||
* This handler is called whenever a scene transition occurs
|
||||
* in the context of the scene that is being entered,
|
||||
* and before any of the its own handlers are called,
|
||||
* and can be used to customize the transition behavior:
|
||||
* - `Stop` to prevent dispatching the update any further **even if ToScene/ToRoot was used**
|
||||
* - `Continue` same as Stop, but still dispatch the update to children
|
||||
* - `ToScene` to prevent the transition and dispatch the update to the scene entered in the transition handler
|
||||
*
|
||||
* > **Note**: if multiple `state.enter()` calls were made within the same update,
|
||||
* > this handler will only be called for the last one.
|
||||
*
|
||||
* @param handler Raw update handler
|
||||
* @param group Handler group index
|
||||
*/
|
||||
onSceneTransition(
|
||||
handler:
|
||||
| ((ctx: SceneTransitionContext, state: UpdateState<State>) => MaybePromise<PropagationAction | void>)
|
||||
| null,
|
||||
): void {
|
||||
if (handler) this._sceneTransitionHandler = handler
|
||||
else this._sceneTransitionHandler = undefined
|
||||
}
|
||||
|
||||
// begin-codegen
|
||||
|
||||
/**
|
||||
|
|
|
@ -11,7 +11,8 @@
|
|||
*
|
||||
* `Continue`: Continue propagating the event inside the same handler group.
|
||||
*
|
||||
* `ToScene`: Used after using `state.enter()` to dispatch the update to the scene
|
||||
* `ToScene`: Used after using `state.enter()` to dispatch the update to the scene,
|
||||
* or after `state.exit()` to dispatch the update to the root dispatcher.
|
||||
*/
|
||||
export enum PropagationAction {
|
||||
Stop = 'stop',
|
||||
|
|
Loading…
Reference in a new issue