@vk-io/scenes
Version:
Scenes for the library vk-io
386 lines (378 loc) • 10.2 kB
JavaScript
'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;