adaptorex
Version:
Connect all your live interactive storytelling devices and software
276 lines (239 loc) • 8.32 kB
JavaScript
/**
* Organize access to adaptor:ex data collections
*
* data is a core plugin and will be included with every game
*
* @requires plugin
* @requires file
*
* @module data/data
* @copyright Lasse Marburg 2022
* @license MIT
*/
const plugin = require('../plugin.js')
var schema = require('./schema.json')
const file = require('../../file.js')
const path = require('path')
/** @typedef {import('../../types').AdaptorAction} Action */
/**
* install request API for all custom collections the game contains
*/
class Data extends plugin.Plugin {
constructor() {
super(schema)
this.name = "data"
this.core = true
this.collection_apis = []
}
/**
* create request API for each custom database
*
* collect files in file directory and watch for changes.
*/
setup(config, game) {
return new Promise((resolve, reject) => {
super.setup(config, game)
.then(result => {
this.addTemplate("addItem", (payload, action) => {
const title = `data ${action.name}`
let subtitle = `Add new ${payload.collection} Item`
const body = []
if(payload.reference) {
subtitle += ` as ${payload.reference}`
}
if (payload.variables) {
body.push({text: JSON.stringify(payload.variables)})
}
return {title, subtitle, body}
})
this.addTemplate("getItem", (payload, action) => {
const title = `data ${action.name}`
let subtitle = `Get Item from ${payload.collection} as '${payload.reference}'`
if(payload.multiple && payload.limit != 1) {
subtitle = `Get Item(s) from ${payload.collection} as '${payload.reference}'`
}
const body = [{ text: payload.query}]
if(payload.sort && payload.sort.length) {
let txt = ""
for(let s of payload.sort) {
if(s.direction == 1) {
txt += ` ${s.by} ⬆️`
} else {
txt += ` ${s.by} ⬇️`
}
}
body.push({text:`sort by:${txt}`})
}
if(payload.limit && payload.limit > 1 && payload.multiple) {
body.push({text:`limit to ${payload.limit} Items`})
}
if(payload.skip) {
if(payload.skip == 1) {
body.push({text:`skip first Item`})
} else {
body.push({text:`skip first ${payload.skip} Items`})
}
}
return {title, subtitle, body}
})
log.debug(this.name, "load assets filenames from " + this.game.files)
this.collectFiles(this.game.files)
file.watchDir(this.game.files, (e, file) => {
this.collectFiles(this.game.files)
log.debug(this.name, "change in file directory: " + e + " " + file)
this.schemaUpdate(this.schema)
})
for(let coll of this.game.setup.collections) {
this.schema.definitions.collection.enum.push(coll)
this.schema.definitions.collection.options.enum_titles.push(coll)
this.schema.definitions.data_collection.enum.push(coll)
}
this.game.status.on("collection_added", data => {
let updt = false
if(!this.schema.definitions.collection.enum.includes(data.collection)) {
this.schema.definitions.collection.enum.push(data.collection)
if(data.plugin) {
this.schema.definitions.collection.options.enum_titles.push(data.plugin + " " + data.name)
} else {
this.schema.definitions.collection.options.enum_titles.push(data.name)
}
updt = true
}
if(data.type == "data" && !this.schema.definitions.data_collection.enum.includes(data.collection)) {
this.schema.definitions.data_collection.enum.push(data.collection)
updt = true
}
if(updt) {
this.schemaUpdate(this.schema)
}
})
return resolve(this.schema)
})
.catch(err => {
return reject(err)
})
})
}
/**
* Collects all file paths from a directory and stores them in the data schema definitions under `files.anyOf[1].enum`.
*
* Clears the existing file enum array in the schema, walks through the specified directory to gather file paths, and then populates the enum array with those paths.
*
* @param {string} dir - The base directory to collect file paths from. All file paths will be relative to this directory.
* @returns {string[]} - The updated `enum` array containing the relative file paths.
*
* @example
* const fileList = this.collectFiles('./my-directory');
* log.info(fileList);
* // Output: ['file1.png', 'sub-dir/file2.mp3']
*/
collectFiles(dir) {
this.schema.definitions.files.anyOf[1].enum = []
this.walkDirectory(dir, dir)
return this.schema.definitions.files.anyOf[1].enum
}
/**
* Recursively walks through a directory and returns a list of relative file paths in Unix-style format.
*
* @param {string} dir - The current directory being processed (absolute or relative path).
* @param {string} baseDir - The base directory from which all file paths should be relative.
*/
walkDirectory(dir, baseDir) {
const files = file.ls(dir)
files.forEach((f) => {
const filePath = path.posix.join(dir, f)
if(file.isDir(filePath)) {
return this.walkDirectory(filePath, baseDir)
}
this.schema.definitions.files.anyOf[1].enum.push(path.posix.relative(baseDir, filePath))
})
}
/**
* @type {Action}
*
* Call database update on an item
*/
async update(data, session) {
await session.variables.update(data.item, data.update, {multiple: data.multiple || false})
}
/**
* @type {Action}
*
* Set value of variable
*/
async set(data, session) {
await session.variables.set(data.variable, data.value, {multiple: data.multiple || false})
}
/**
* @type {Action}
*
* Increase numeric value of variable
*/
async increase(data, session) {
await session.variables.edit("inc", data.variable, data.value, {multiple: data.multiple || false})
}
/**
* @type {Action}
*
* Add element to variable list
*/
async push(data, session) {
if(data.duplicates) {
await session.variables.push(data.variable, data.value, {multiple: data.multiple || false})
} else {
await session.variables.add(data.variable, data.value, {multiple: data.multiple || false})
}
}
/**
* @type {Action}
*
* Remove Element from variable list
*/
async pull(data, session) {
await session.variables.pull(data.variable, data.value, {multiple: data.multiple || false})
}
/**
* @type {Action}
*
* Create a new Item in one of the existing data collections
*/
async addItem(data, session) {
await session.variables.create(data.collection, data.variables, data.reference)
}
/**
* @type {Action}
*
* Remove variable from item document or delete the whole document.
*/
async delete(data, session) {
await session.variables.delete(data.variable, {multiple: data.multiple || false})
}
/**
* @type {Action}
*
* create new reference to current session in an external document.
*
* document can than be accessed during session via name reference.
*/
async getItem(data, session) {
if(!adaptor.hasOwnProperties(data, ["collection", "query", "reference"])) {
throw new adaptor.InvalidError("can not load reference. collection, query or reference property missing.")
}
let options = {multiple: data.multiple}
if(data.sort && data.sort.length) {
options.sort = {}
for(let s of data.sort) {
options.sort[s.by] = s.direction
}
}
if(data.limit) {
options.limit = parseInt(data.limit)
}
if(data.skip) {
options.skip = parseInt(data.skip)
}
await session.createReference(data["collection"], data.query, data["reference"], options)
session.log(`create ${data.collection} reference "${data.reference}"`)
}
}
module.exports.Plugin = Data