UNPKG

@vk-io/scenes

Version:

Scenes for the library vk-io

386 lines (378 loc) 10.2 kB
'use strict'; exports.LastAction = void 0; (function (LastAction) { LastAction["NONE"] = "none"; LastAction["ENTER"] = "enter"; LastAction["LEAVE"] = "leave"; })(exports.LastAction || (exports.LastAction = {})); class SceneContext { constructor(options) { /** * Is the scene canceled, used in leaveHandler() * * ```ts * ctx.scene.leave({ * canceled: true * }); * ``` */ this.canceled = false; this.lastAction = exports.LastAction.NONE; /** * Controlled behavior leave */ this.leaving = false; this.context = options.context; this.repository = options.repository; this.sessionKey = options.sessionKey; this.updateSession(); } /** * Returns current scene */ get current() { return this.repository.get(this.session.current); } /** * Enter to scene * * ```ts * ctx.scene.enter('signup'); * ctx.scene.enter('signup', { * silent: true, * state: { * username: 'Super_Developer' * } * }); * ``` */ async enter(slug, options = {}) { var _a; const scene = this.repository.strictGet(slug); const isCurrent = ((_a = this.current) === null || _a === void 0 ? void 0 : _a.slug) === scene.slug; if (!isCurrent) { if (!this.leaving) { await this.leave({ silent: options.silent }); } if (this.leaving) { this.leaving = false; this.reset(); } } this.lastAction = exports.LastAction.ENTER; this.session.current = scene.slug; Object.assign(this.state, options.state || {}); if (options.silent) { return; } await scene.enterHandler(this.context); } /** * Reenter to current scene * * ```ts * ctx.scene.reenter(); * ``` */ async reenter() { const { current } = this; if (!current) { throw new Error('There is no active scene to enter'); } await this.enter(current.slug); } /** * Leave from current scene * * ```ts * ctx.scene.leave(); * ctx.scene.leave({ * silent: true, * canceled: true * }); * ``` */ async leave(options = {}) { var _a; const { current } = this; if (!current) { return; } this.leaving = true; this.lastAction = exports.LastAction.LEAVE; if (!options.silent) { this.canceled = (_a = options.canceled) !== null && _a !== void 0 ? _a : false; await current.leaveHandler(this.context); } if (this.leaving) { this.reset(); } this.leaving = false; this.canceled = false; } /** * Reset state/session */ reset() { // eslint-disable-next-line no-underscore-dangle delete this.context.session.__scene; this.updateSession(); } /** * Updates session and state is lazy */ updateSession() { // eslint-disable-next-line no-underscore-dangle this.session = new Proxy(this.context[this.sessionKey].__scene || {}, { set: (target, prop, value) => { target[prop] = value; // eslint-disable-next-line no-underscore-dangle this.context[this.sessionKey].__scene = target; return true; } }); this.state = new Proxy(this.session.state || {}, { set: (target, prop, value) => { target[prop] = value; this.session.state = target; return true; } }); } } class StepSceneContext { constructor(options) { this.stepChanged = false; this.context = options.context; this.steps = options.steps; } /** * The first enter to the handler */ get firstTime() { const { firstTime = true } = this.context.scene.session; return firstTime; } /** * Returns current stepId */ get stepId() { return this.context.scene.session.stepId || 0; } /** * Sets current stepId */ set stepId(stepId) { const { session } = this.context.scene; session.stepId = stepId; session.firstTime = true; this.stepChanged = true; } /** * Returns current handler */ get current() { return this.steps[this.stepId]; } /** * Reenter current step handler * * ```ts * ctx.scene.step.reenter(); * ``` */ async reenter() { const { current } = this; if (!current) { await this.context.scene.leave(); return; } this.stepChanged = false; await current(this.context); if (this.context.scene.lastAction !== exports.LastAction.LEAVE && !this.stepChanged) { this.context.scene.session.firstTime = false; } } /** * The go method goes to a specific step * * ```ts * ctx.scene.step.go(3); * ctx.scene.step.go(3, { * silent: true * }); * ``` */ go(stepId, { silent = false } = {}) { this.stepId = stepId; if (silent) { return Promise.resolve(); } return this.reenter(); } /** * Move to the next handler * * ```ts * ctx.scene.step.next(); * ctx.scene.step.next({ * silent: true * }); * ``` */ next(options) { return this.go(this.stepId + 1, options); } /** * Move to the previous handler * * ```ts * ctx.scene.step.previous(); * ctx.scene.step.previous({ * silent: true * }); * ``` */ previous(options) { return this.go(this.stepId - 1, options); } } class StepScene { constructor(slug, rawOptions) { const options = Array.isArray(rawOptions) ? { steps: rawOptions } : rawOptions; this.slug = slug; this.steps = options.steps; // eslint-disable-next-line @typescript-eslint/no-empty-function this.onEnterHandler = options.enterHandler || (() => { }); // eslint-disable-next-line @typescript-eslint/no-empty-function this.onLeaveHandler = options.leaveHandler || (() => { }); } async enterHandler(context) { context.scene.step = new StepSceneContext({ context, // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore steps: this.steps }); await this.onEnterHandler(context); if (context.scene.lastAction !== exports.LastAction.LEAVE) { await context.scene.step.reenter(); } } leaveHandler(context) { return Promise.resolve(this.onLeaveHandler(context)); } } class CacheRepository { constructor({ sortingValues } = {}) { this.collection = new Map(); this.keys = []; this.values = []; this.sortingValues = sortingValues; } /** * Checks has value by key */ has(key) { return this.collection.has(key); } /** * Sets value by key */ set(key, value) { this.collection.set(key, value); this.keys = [...this.collection.keys()]; this.values = [...this.collection.values()]; if (this.sortingValues) { this.values.sort(this.sortingValues); } } /** * Returns value by key */ get(key) { return this.collection.get(key); } /** * Sets value by key else error if exits */ strictSet(key, value) { if (this.collection.has(key)) { throw new Error(`Value by ${key} already exists`); } return this.set(key, value); } /** * Returns value by key else error */ strictGet(key) { const value = this.get(key); if (!value) { throw new Error(`Value by ${key} not found`); } return value; } /** * Returns iterator */ [Symbol.iterator]() { return this.collection[Symbol.iterator](); } } class SceneManager { constructor({ scenes, sessionKey = 'session' } = {}) { this.repository = new CacheRepository(); this.sessionKey = sessionKey; if (scenes) { this.addScenes(scenes); } } /** * Checks for has a scene */ hasScene(slug) { return this.repository.has(slug); } /** * Adds scenes to the repository */ addScenes(scenes) { for (const scene of scenes) { this.repository.set(scene.slug, scene); } return this; } /** * Returns the middleware for embedding */ get middleware() { return (context, next) => { context.scene = new SceneContext({ context, sessionKey: this.sessionKey, repository: this.repository }); return next(); }; } /** * Returns the middleware for intercept */ // eslint-disable-next-line class-methods-use-this get middlewareIntercept() { return (context, next) => { if (!context.scene.current) { return next(); } return context.scene.reenter(); }; } } exports.SceneContext = SceneContext; exports.SceneManager = SceneManager; exports.StepScene = StepScene; exports.StepSceneContext = StepSceneContext;