class AsyncCircuit { static create(...args) { return new this(...args); } constructor() { this.middlewares = []; } hook(callback) { this.middlewares.push(callback); return this; } prehook(callback) { this.middlewares.unshift(callback); return this; } unhook(callback) { const pos = this.middlewares.indexOf(callback); if(pos !== -1) { this.middlewares.splice(pos, 1); } return this; } // Función principal para parsear el árbol parse(node) { // Si el nodo es un array (ejecutar en paralelo) if (Array.isArray(node)) { const code = node.map(cb => this.parse(cb)).join(','); return `Promise.all([${code}])`; // Convertimos todo en un Promise.all } // Si es un objeto con $type y $callbacks if (node && typeof node === 'object') { Inject_middlewares: { for(let index__middleware=0; index__middleware<this.middlewares.length; index__middleware++) { const middleware = this.middlewares[index__middleware]; const result = middleware(node); } } const { $type, $callbacks } = node; const callbacks = $callbacks.map(cb => (typeof cb === 'function' ? `(${cb.toString()})()` : this.parse(cb))); // Dependiendo del tipo, generamos el código adecuado switch ($type) { case 'parallel': return `Promise.all([\n ${callbacks.join(',')}\n ])`; // Ejecutar en paralelo case 'serie': return `(async () => {\n ${callbacks.map(cb => `await ${cb}`).join('; ')}\n })()`; // Ejecutar en serie case 'race': return `Promise.race([\n ${callbacks.join(',')}\n ])`; // Ejecutar en carrera case 'sync': return `(() => {\n ${callbacks.join(';\n ')};\n return Promise.resolve();\n })()`; // Ejecutar síncrono default: throw new Error(`Required property «$type» to be one known but found «${$type}» on «this.parse»`); } } // Si el nodo es una función, la transformamos directamente if (typeof node === 'function') { return `(${node.toString()})()`; // Convertimos la función en una llamada inmediata } throw new Error(`Required argument «node» to be of known type but found «${typeof $type}» on «this.parse»`); } // Método que ejecuta el código generado por eval async execute(node, externalScope = {}) { const code = this.parse(node); const AsyncFunction = (async function() {}).constructor; const argKeys = Object.keys(externalScope).join(", "); const argValues = Object.values(externalScope); const asyncFunction = new AsyncFunction(argKeys, code); return await asyncFunction(...argValues); // Ejecutamos el código generado con eval } }
class LswClassRegister { constructor() { this.$classes = {}; } register(id, classDefinition, forceOverwrite = false) { $ensure({id},1).type("string"); $ensure({classDefinition},1).type("function"); if(!forceOverwrite) { $ensure({$classes:this.$classes},1).to.not.have.key(id); } this.$classes[id] = classDefinition; } unregister(id, silenceError = false) { if(!silenceError) { $ensure({$classes:this.$classes},1).to.have.key(id); } delete this.$classes[id]; } instantiate(id, ...args) { $ensure({id},1).type("string"); $ensure({$classes:this.$classes},1).to.have.key(id); const clazz = this.$classes[id]; const objeto = new clazz(...args); return objeto; } async initialize(id, ...args) { $ensure({id},1).type("string"); $ensure({$classes:this.$classes},1).to.have.key(id); const clazz = this.$classes[id]; const objeto = new clazz(...args); if(typeof objeto.initialize === "function") { await objeto.initialize(); } return objeto; } }
Promise_extensions: { globalThis.Promise.prototype.chain = function (nextPromise) { return this.then(() => nextPromise); }; }
const LswCommander = class { static from(...args) { return new this(...args); } onRun(callback, args) { try { let output = undefined; if (typeof this.$beforeRun === "function") { this.$beforeRun(...args); } output = callback(...args); if (typeof this.$afterRun === "function") { this.$afterRun(...args); } return output; } catch (error) { if (typeof this.$onError === "function") { const output = this.$onError(error); if (typeof output !== "undefined") { return output; } } throw error; } } constructor(handlers) { this.$handlers = handlers; this.$beforeRun = undefined; this.$afterRun = undefined; this.$onError = undefined; this.command = (url, queryParamsExtender = {}) => { if (!url) throw new Error("URL is required"); if (typeof url !== "string") throw new Error("URL must be a string"); if (typeof this.$handlers !== "object" || this.$handlers === null) { throw new Error("Handlers must be a valid object"); } const [path, queryString] = url.split("?"); const queryParams = queryString ? Object.fromEntries(new URLSearchParams(queryString).entries()) : {}; Object.assign(queryParams, queryParamsExtender); const pathParts = path.split("/").filter(Boolean); let currentHandler = this.$handlers; for (const part of pathParts) { if (currentHandler[part] === undefined) { throw new Error(`Handler for path "${path}" not found`); } currentHandler = currentHandler[part]; } if (typeof currentHandler !== "function") { throw new Error(`Handler at path "${path}" is not a function`); } const isSpreadable = isOnlyConsecutiveNumbers(queryParams); if (isSpreadable && isSpreadable.length) { return this.onRun(currentHandler, isSpreadable); } else if (queryParams.argumentsOrder) { const args = []; const argKeys = queryParams.argumentsOrder.split(",").map(arg => arg.trim()); for (let index = 0; index < argKeys.length; index++) { const argKey = argKeys[index]; const argValue = queryParams[argKey] || null; args.push(argValue); } return this.onRun(currentHandler, args); } else { return this.onRun(currentHandler, [queryParams]); } }; } get run() { return this.command; } beforeRun(callback) { if (typeof callback !== "function") { throw new Error("Required parameter «callback» to be a function on «beforeRun»"); } this.$beforeRun = callback; } afterRun(callback) { if (typeof callback !== "function") { throw new Error("Required parameter «callback» to be a function on «afterRun»"); } this.$afterRun = callback; } onError(callback) { if (typeof callback !== "function") { throw new Error("Required parameter «callback» to be a function on «onError»"); } this.$onError = callback; } };
class PromiseMap { constructor(keys) { this.promises = new Map(); keys.forEach(key => { this.set(key); }); } static create(keys) { return new this(keys); } has(key) { return this.promises.has(key); } get(key) { if (!this.has(key)) { throw new Error(`Required argument «key» to be an existing key (not «${key}») on «PromiseMap.get»`); } return this.promises.get(key); } set(key) { if (this.has(key)) { throw new Error(`Required argument «key» to not be an existing key (not «${key}») on «PromiseMap.set»`); } let resolve, reject; const promise = new Promise((res, rej) => { resolve = res; reject = rej; }); this.promises.set(key, { promise, resolve, reject }); } unset(key) { if (!this.has(key)) { throw new Error(`Required argument «key» to be an existing key (not «${key}») on «PromiseMap.unset»`); } this.promises.delete(key); } on(key) { if (!this.has(key)) { throw new Error(`Required argument «key» to be an existing key (not «${key}») on «PromiseMap.on»`); } return this.promises.get(key).promise; } bind(key, key2) { this.on(key).then(output => this.get(key2).resolve(output)); } propagate(key) { return { to: (key2) => { this.bind(key, key2); return this.propagate(key2); } } } }
const LswConfigurer = class { constructor(parentConfigurer = null) { this.$parent = parentConfigurer; this.$state = {}; } configure(stateOptions = {}) { Object.assign(this.$state, stateOptions); } get(key, defaultValue = undefined) { let target = this; Iterating_parents: while(!!target) { if(key in target.$state) { return target.$state[key]; } target = target.$parent; } return defaultValue; } }
const LswConstants = class { static THROW_ERROR = {}; static global = new this(); constructor() { this.$values = {}; } define(key, value) { if(key in this.$values) { throw new Error(`Cannot override constant «${key}» on «LswConstants.define»`); } this.$values[key] = value; } pick(key, defaultValue = LswConstants.THROW_ERROR) { if(!(key in this.$values)) { if(defaultValue === LswConstants.THROW_ERROR) { console.log("[!] Known keys only:", Object.keys(this.$values)); throw new Error(`Could not find constant «${key}» on «LswConstants.pick»`); } } return this.$values[key] || defaultValue; } }; return LswConstants;
class LswCyclerSet { constructor(value) { this.value = value; } } class LswCyclerReturn { constructor(value) { this.value = value; } } class LswCyclerReturner { constructor(value) { if(typeof value !== "function") { throw new Error("Required argument «value» to be a function on «LswCyclerReturner.constructor»"); } this.value = value; } } class LswCycler { static Return = LswCyclerReturn; static Returner = LswCyclerReturner; static Set = LswCyclerSet; static returner(value) { return new this.Returner(value); } static return(value) { return new this.Return(value); } static set(value) { return new this.Set(value); } constructor($object, exposedProps = []) { this.$object = $object; if(exposedProps === "*") { Object.assign(this, $object); } else { for(let index=0; index<exposedProps.length; index++) { const exposedProp = exposedProps[index]; this[exposedProp] = $object[exposedProp]; } } } static from(...args) { return new this(...args); } async run(steps, parameters) { let original = []; let output = original; Iterate_cycle: for (let j = 0; j < steps.length; j++) { let step = steps[j]; let fn = this.$object[step]; if (typeof fn !== "function") { throw new Error("Required step «" + step + "» to be a function on round " + j + " on «LswCycler.run»"); } const result = await fn.call(this.$object, parameters); Apply_intercycle_signals: { if (result instanceof this.constructor.Set) { output = await result.value; } else if (result instanceof this.constructor.Return) { return result.value; } else if (result instanceof this.constructor.Returner) { return result.value(output, original); } } Append_result_if_not_changed_output: { original.push(result); } } return output; } }
class BrowsieCheckersAPI { static mustBeString(obj, method = "Browsie.mustBeString", id = "?") { if (typeof obj !== "string") { throw new Error(`Required «${id}» to be a string on «${method}»`); } } static mustBeArray(obj, method = "Browsie.mustBeArray", id = "?") { if (!Array.isArray(obj)) { throw new Error(`Required «${id}» to be an array on «${method}»`); } } static mustBeObject(obj, method = "Browsie.mustBeObject", id = "?") { if (typeof obj !== "object") { throw new Error(`Required «${id}» to be an object on «${method}»`); } } static mustBeGreaterThan(obj, comparison = 0, method = "Browsie.mustBeObject", id = "?") { if (obj <= comparison) { throw new Error(`Required «${id}» to be greater than «${comparison}» on «${method}»`); } } } class BrowsieStaticAPI extends BrowsieCheckersAPI { static openedConnections = []; static _trace = true; static trace(methodName, args = []) { // @INJECTION: from LSW const traceActivatedGlobally = (typeof Vue === "undefined") || (typeof Vue.prototype.$lsw === "undefined") || ((typeof Vue !== "undefined") && (typeof Vue.prototype.$lsw !== "undefined") && (Vue.prototype.$lsw.logger.$options.active)); if (this._trace && traceActivatedGlobally) { console.log("[browsie][" + methodName + "]", args.length + " args: " + Array.from(args).map(arg => typeof (arg)).join(", ")); } } static async listDatabases() { this.trace("Browsie.listDatabases", arguments); try { const databases = await indexedDB.databases(); console.log('Bases de datos disponibles:', databases); return databases; } catch (error) { console.error('Error al obtener las bases de datos:', error); } } static createDatabase(dbName, schemaDefinition = null, version = 1, versionUpgrades = {}) { this.trace("Browsie.createDatabase", arguments); return new Promise((resolve, reject) => { const request = indexedDB.open(dbName, version); request.onsuccess = () => { console.log(`[SUCCESS] Database "${dbName}" created/opened successfully.`); request.result.close(); resolve(request.result); }; request.onerror = (error) => { console.error(`[ERROR] Failed to create/open database "${dbName}":`, error); reject(error); }; request.onupgradeneeded = async (event) => { const db = event.target.result; console.log(`[UPGRADE] Upgrading database "${dbName}" from version ${event.oldVersion} to ${version}.`); // Si hay una definición de esquema inicial, crear los almacenes e índices if (schemaDefinition && event.oldVersion === 0) { console.log("[SCHEMA] Applying initial schema definition."); Object.keys(schemaDefinition).forEach((storeName) => { if (!db.objectStoreNames.contains(storeName)) { const objectStore = db.createObjectStore(storeName, { keyPath: "id", autoIncrement: true, }); if (!Array.isArray(schemaDefinition[storeName])) { console.log(schemaDefinition); throw new Error(`Required property «schemaDefinition.${storeName}» to be an array on «LswDatabase.createDatabase»`); } schemaDefinition[storeName].forEach((index) => { const indexName = index.replace(/^\!/, ""); objectStore.createIndex(indexName, indexName, { unique: index.startsWith("!") }); }); } }); } // Aplicar las transformaciones de esquema para cada versión for (let v = event.oldVersion + 1; v <= version; v++) { if (versionUpgrades[v]) { console.log(`[VERSION ${v}] Applying upgrade function.`); await versionUpgrades[v](db); } else { console.log(`[VERSION ${v}] No upgrade function defined.`); } } }; }); } static async exportDatabase(dbName) { this.trace("Browsie.exportDatabase", arguments); const schema = await this.getSchema(dbName); const storeIds = Object.keys(schema); const allData = {}; for(let indexStore=0; indexStore<storeIds.length; indexStore++) { const storeId = storeIds[indexStore]; const storeData = await this.getAllDataFromStore(dbName, storeId); allData[storeId] = storeData; } return allData; } static async importToDatabase(dbName, storesData = {}) { this.trace("Browsie.importToDatabase", arguments); const storeIds = Object.keys(storesData); const connection = await this.open(dbName); for(let indexStore=0; indexStore<storeIds.length; indexStore++) { const storeId = storeIds[indexStore]; const allData = storesData[storeId]; console.log(`[*] Importing store «${storeId}»`); await connection.insertMany(storeId, allData); } } // Obtener todos los datos de un store static async getAllDataFromStore(dbName, storeName) { this.trace("Browsie.getAllDataFromStore", arguments); return await new Promise((resolve, reject) => { const request = indexedDB.open(dbName); request.onsuccess = (event) => { const db = event.target.result; const transaction = db.transaction(storeName, 'readonly'); const store = transaction.objectStore(storeName); const getAllRequest = store.getAll(); getAllRequest.onsuccess = () => resolve(getAllRequest.result); getAllRequest.onerror = () => { db.close(); reject(new Error('Error al obtener los datos del store')); }; }; request.onerror = () => { reject(new Error('Error al abrir la base de datos')); }; }); } // Insertar datos en un store static async insertDataIntoStore(dbName, storeName, data) { this.trace("Browsie.insertDataIntoStore", arguments); return await new Promise((resolve, reject) => { const request = indexedDB.open(dbName); request.onsuccess = (event) => { const db = event.target.result; const transaction = db.transaction(storeName, 'readwrite'); const store = transaction.objectStore(storeName); data.forEach(item => store.add(item)); transaction.oncomplete = () => resolve(); transaction.onerror = () => { db.close(); reject(new Error('Error al insertar los datos en el store')); }; }; request.onerror = () => { reject(new Error('Error al abrir la base de datos')); }; }); } // Eliminar una base de datos static deleteDatabase(dbName) { this.trace("Browsie.deleteDatabase", arguments); return new Promise((resolve, reject) => { const request = indexedDB.deleteDatabase(dbName); request.onblocked = () => { // db.close(); reject(new Error("Error al eliminar la base de datos porque está bloqueada")); }; request.onsuccess = () => resolve(); request.onerror = () => { // db.close(); reject(new Error('Error al eliminar la base de datos')); }; }).then(() => { console.log(`[!] Base de datos «${dbName}» eliminada correctamente.`); }); } static async getSchema(dbName) { this.trace("Browsie.getSchema", arguments); let db = undefined; try { // Abrir la base de datos en modo solo lectura const request = indexedDB.open(dbName); db = await new Promise((resolve, reject) => { request.onsuccess = (event) => resolve(event.target.result); request.onerror = () => { reject(new Error('Error al abrir la base de datos')); }; }); // Construir el esquema a partir de los almacenes const schema = {}; const objectStoreNames = Array.from(db.objectStoreNames); // Lista de stores objectStoreNames.forEach(storeName => { const transaction = db.transaction(storeName, 'readonly'); const store = transaction.objectStore(storeName); const storeInfo = { keyPath: store.keyPath, autoIncrement: store.autoIncrement, indexes: [] }; // Recorrer los índices del store const indexNames = Array.from(store.indexNames); // Lista de índices indexNames.forEach(indexName => { const index = store.index(indexName); storeInfo.indexes.push({ name: index.name, keyPath: index.keyPath, unique: index.unique, multiEntry: index.multiEntry }); }); schema[storeName] = storeInfo; }); return schema; } catch (error) { console.error('Error al obtener el esquema:', error); throw error; } finally { if (db) { db.close(); } } } static async pickRow(databaseId, tableId, rowId) { this.trace("Browsie.pickRow", arguments); $ensure(databaseId).type("string"); $ensure(tableId).type("string"); $ensure(rowId).type("number"); let connection = undefined; try { connection = await this.open(databaseId); const rows = await connection.selectMany(tableId, v => v.id === rowId); if (rows.length === 1) { return rows[0]; } else if (rows.length === 0) { return undefined; } } catch (error) { throw error; } finally { try { await connection.close(); } catch (error) { console.log("Could not close connection on picking row"); } } } } class BrowsieTriggersAPI extends BrowsieStaticAPI { static globMatch = TriggersClass.globMatch; triggers = new TriggersClass(); } class BrowsieCrudAPI extends BrowsieTriggersAPI { static async open(...args) { this.trace("Browsie.open", arguments); const db = new this(...args); await db.open(); return db; } // Constructor que abre la base de datos constructor(dbName, trace = false) { super(); this.$dbName = dbName; this.$db = null; this.$innerSchema = null; this._trace = trace; } getInnerSchema() { this.constructor.trace("browsie.getInnerSchema", arguments); return this.$innerSchema; } setInnerSchema(innerSchema) { this.constructor.trace("browsie.setInnerSchema", arguments); if (!(innerSchema instanceof LswSchema)) { throw new Error(`Required parameter «innerSchema» to be an instance of LswSchema on «browsie.setInnerSchema»`); } this.$innerSchema = innerSchema; } // Abre la base de datos open() { this.constructor.trace("browsie.open", arguments); return new Promise((resolve, reject) => { const request = indexedDB.open(this.$dbName); request.onsuccess = () => { this.$db = request.result; resolve(this.$db); }; request.onerror = (error) => reject(this._expandError(error, `Error on «browsie.open» operation over database «${this.$dbName}»: `)); }); } close(...args) { this.constructor.trace("browsie.close", arguments); return this.$db.close(...args); } // Método para seleccionar 1 elemento de un store con un filtro select(store, filter = {}) { this.constructor.trace("browsie.select", arguments); this.triggers.emit(`crud.select.one.${store}`, { store, filter }); return new Promise((resolve, reject) => { const transaction = this.$db.transaction(store, 'readonly'); const objectStore = transaction.objectStore(store); const request = objectStore.getAll(); request.onsuccess = () => { const result = request.result.filter(item => { return Object.keys(filter).every(key => item[key] === filter[key]); }); resolve(result); }; request.onerror = (error) => reject(this._expandError(error, `Error on «browsie.select» operation over store «${store}»: `)); }); } // Método para insertar un solo item en un store insert(store, item) { this.constructor.trace("browsie.insert", arguments); this.triggers.emit(`crud.insert.one.${store}`, { store, item }); return new Promise((resolve, reject) => { const transaction = this.$db.transaction(store, 'readwrite'); const objectStore = transaction.objectStore(store); const request = objectStore.add(item); request.onsuccess = () => resolve(request.result); request.onerror = (error) => reject(this._expandError(error, `Error on «browsie.insert» operation over store «${store}»: `)); }); } // Método para actualizar un item en un store update(store, id, item) { this.constructor.trace("browsie.update", arguments); this.triggers.emit(`crud.update.one.${store}`, { store, id, item }); return new Promise((resolve, reject) => { const transaction = this.$db.transaction(store, 'readwrite'); const objectStore = transaction.objectStore(store); const request = objectStore.put({ ...item, id }); request.onsuccess = () => resolve(request.result); request.onerror = (error) => reject(this._expandError(error, `Error on «browsie.update» operation over store «${store}»: `)); }); } // Método tipo upsert: que cambia solo los campos que le proporcionas (hace entre 1 y 2 queries) async overwrite(store, idOrItem, item) { this.constructor.trace("browsie.overwrite", arguments); this.triggers.emit(`crud.overwrite.one.${store}`, { store, idOrItem, item }); const isId = (typeof idOrItem === "string") || (typeof idOrItem === "number"); const isItem = typeof idOrItem === "object"; let previousItem = undefined; if (isItem) { previousItem = idOrItem; } else if (isId) { const matches = await this.select(store, it => it.id === idOrItem); if (matches.length === 0) { throw new Error(`Zero rows on overwrite operator. Cannot overwrite a row that does not exist on «browsie.overwrite»`); } else if (matches.length > 1) { throw new Error(`Multiple rows on overwrite operation. Cannot overwrite multiple rows. Ensure store «${store}» is using index «id» as unique value to complete this operation`); } previousItem = matches[0]; } else { throw new Error(`Required parameter «idOrItem» to be a string or an object on «browsie.overwrite»`); } const newItem = Object.assign({}, previousItem, item); return await this.update(store, newItem.id, newItem); } // Método para eliminar un item de un store por ID delete(store, id) { this.constructor.trace("browsie.delete", arguments); this._ensureIntegrity(store, id); this.triggers.emit(`crud.delete.one.${store}`, { store, id }); return new Promise((resolve, reject) => { const transaction = this.$db.transaction(store, 'readwrite'); const objectStore = transaction.objectStore(store); const request = objectStore.delete(id); request.onsuccess = () => resolve(); request.onerror = (error) => reject(this._expandError(error, `Error on «browsie.delete» operation over store «${store}»: `)); }); } _getSchemaEntityByStoreName(store) { this.constructor.trace("browsie._ensureIntegrity", arguments); const innerSchema = this.getInnerSchema().$schema; const tableIds = Object.keys(innerSchema.hasTables); Iterating_tables: for (let indexTables = 0; indexTables < tableIds.length; indexTables++) { const tableId = tableIds[indexTables]; if (tableId === store) { return innerSchema.hasTables[tableId]; } } return undefined; } _ensureIntegrity(store, id) { this.constructor.trace("browsie._ensureIntegrity", arguments); const innerSchema = this.getInnerSchema().$schema; const tableIds = Object.keys(innerSchema.hasTables); const sourceEntity = innerSchema.hasTables[store]; const sourceEntityId = sourceEntity.hasEntityId; const boundColumns = []; Iterating_tables: for (let indexTables = 0; indexTables < tableIds.length; indexTables++) { const tableId = tableIds[indexTables]; const tableData = innerSchema.hasTables[tableId]; const columnIds = Object.keys(tableData.hasColumns); Iterating_columns: for (let indexColumns = 0; indexColumns < columnIds.length; indexColumns++) { const columnId = columnIds[indexColumns]; const columnData = tableData.hasColumns[columnId]; When_it_has_references: { if (!columnData.refersTo) { break When_it_has_references; } const { entity: schemaEntityId, property: entityColumnId, constraint = true } = columnData.refersTo; if (!constraint) { break When_it_has_references; } const isSameEntity = schemaEntityId === sourceEntityId; if (!isSameEntity) { break When_it_has_references; } boundColumns.push({ source: [store, entityColumnId], mustCheck: [tableId, columnId] }); } } } console.log(`BOUND COLUMNS to ${store}:`, boundColumns); } _expandError(errorObject, baseMessage = false) { this.constructor.trace("browsie._expandError", arguments); let error = errorObject; if (errorObject instanceof Error) { error = errorObject; } else if (errorObject.target && errorObject.target.error) { error = errorObject.target.error; } else { error = new Error(errorObject); } if (baseMessage) { const errorTemp = new Error(error.message ?? error); Object.assign(errorTemp, error); errorTemp.message = baseMessage + errorTemp.message; error = errorTemp; } return error; } // Método para seleccionar elementos de un store con un filtro select(store, filter) { this.constructor.trace("browsie.select", arguments); this.triggers.emit(`crud.select.one.${store}`, { store, filter }); return new Promise((resolve, reject) => { const transaction = this.$db.transaction(store, 'readonly'); const objectStore = transaction.objectStore(store); const request = objectStore.getAll(); request.onsuccess = () => { const result = request.result.filter(item => { try { return filter(item); } catch (error) { console.error("Error arised from filter callback on «browsie.select»"); return false; } }); resolve(result); }; request.onerror = (error) => reject(this._expandError(error, `Error on «browsie.select» operation over store «${store}»: `)); }); } selectMany(store, filterFn = i => true) { this.constructor.trace("browsie.selectMany", arguments); this.triggers.emit(`crud.select.many.${store}`, { store, filterFn }); return new Promise((resolve, reject) => { const transaction = this.$db.transaction(store, 'readonly'); const objectStore = transaction.objectStore(store); const request = objectStore.openCursor(); // Usa cursor para recorrer la BD sin cargar todo en memoria const results = []; request.onsuccess = (event) => { const cursor = event.target.result; if (cursor) { let isAccepted = undefined; try { isAccepted = filterFn(cursor.value); } catch (error) { console.error(`Silent error arised from filter callback on «browsie.selectMany» against store «${store}»`, error); } if (isAccepted) { // Aplica la función de filtro results.push(cursor.value); } cursor.continue(); // Avanza al siguiente registro } else { resolve(results); // Se terminó el recorrido } }; request.onerror = (error) => reject(this._expandError(error, `Error on «browsie.selectMany» operation over store «${store}»: `)); }); } // Método para insertar varios items en un store insertMany(store, items) { this.constructor.trace("browsie.insertMany", arguments); this.triggers.emit(`crud.insert.many.${store}`, { store, items }); this.constructor.mustBeString(store, "insertMany", "arguments[0]"); this.constructor.mustBeArray(items, "insertMany", "arguments[1]"); return new Promise((resolve, reject) => { if (items.length === 0) { return resolve(false); } const transaction = this.$db.transaction(store, 'readwrite'); const objectStore = transaction.objectStore(store); let insertedCount = 0; items.forEach(item => { const request = objectStore.add(item); request.onsuccess = () => { insertedCount++; if (insertedCount === items.length) resolve(); }; request.onerror = (error) => reject(this._expandError(error, `Error on «browsie.insertMany» operation over store «${store}» inserting «${items.length}» items: `)); }); }); } // Método para actualizar varios items en un store updateMany(store, filter, item) { this.constructor.trace("browsie.updateMany", arguments); this.triggers.emit(`crud.update.many.${store}`, { store, filter, item }); return new Promise((resolve, reject) => { const transaction = this.$db.transaction(store, 'readwrite'); const objectStore = transaction.objectStore(store); const request = objectStore.openCursor(); let updatedCount = 0; request.onsuccess = () => { const cursor = request.result; if (cursor) { if (Object.keys(filter).every(key => cursor.value[key] === filter[key])) { const updatedItem = { ...cursor.value, ...item }; const updateRequest = cursor.update(updatedItem); updateRequest.onsuccess = () => { updatedCount++; if (updatedCount === cursor.value.length) resolve(); }; } cursor.continue(); } }; request.onerror = (error) => reject(this._expandError(error, `Error on «browsie.updateMany» operation over store «${store}»: `)); }); } // Método a tipo upsertAll para llenar los valores pero dejar los que no async overwriteMany(store, filter, item) { this.constructor.trace("browsie.overwriteMany", arguments); this.triggers.emit(`crud.overwrite.many.${store}`, { store, filter, item }); const allMatches = await this.selectMany(store, filter); const allResults = []; for (let indexRow = 0; indexRow < allMatches.length; indexRow++) { const row = allMatches[indexRow]; const result = await this.overwrite(store, row, item); allResults.push(result); } return allResults; } // Método para eliminar varios items de un store según un filtro deleteMany(store, filterCallback) { this.constructor.trace("browsie.deleteMany", arguments); this.triggers.emit(`crud.delete.many.${store}`, { store, filterCallback }); return new Promise((resolve, reject) => { const transaction = this.$db.transaction(store, 'readwrite'); const objectStore = transaction.objectStore(store); const request = objectStore.openCursor(); let deletedCount = 0; request.onsuccess = () => { const cursor = request.result; if (!cursor) { return resolve(); } const isAccepted = filterCallback(cursor.value); if (isAccepted) { const deleteRequest = cursor.delete(); deleteRequest.onsuccess = () => { deletedCount++; if (deletedCount === cursor.value.length) { return resolve(); } }; deleteRequest.onerror = (error) => reject(this._expandError(error, `Error on «browsie.deleteMany» operation over store «${store}» and id «${cursor.value.id}»: `)); } cursor.continue(); }; request.onerror = (error) => reject(this._expandError(error, `Error on «browsie.deleteMany» operation over store «${store}»: `)); }); } } // @TOCONTINUEFROM class BrowsieMigration { static from(...args) { return new this(...args); } static createTable(arg) { return this.from({ operation: "createTable", parameters: arg }); } static renameTable(arg) { return this.from({ operation: "renameTable", parameters: arg }); } static deleteTable(arg) { return this.from({ operation: "deleteTable", parameters: arg }); } static createColumn(arg) { return this.from({ operation: "createColumn", parameters: arg }); } static renameColumn(arg) { return this.from({ operation: "renameColumn", parameters: arg }); } static deleteColumn(arg) { return this.from({ operation: "deleteColumn", parameters: arg }); } constructor(options = {}) { LswDatabase.trace("LswDatabaseMigration.constructor"); const { operation, parameters } = options; this.$validateOperation(operation); this.$validateParameters(parameters); this.operation = operation; this.parameters = parameters; this.config = { temporaryDatabase: this.parameters.fromDatabase + "_" + this.$getRandomString(5), }; this.migrated = false; } $getRandomString(len = 10) { LswDatabase.trace("LswDatabaseMigration.$getRandomString"); const alphabet = "abcdefghijklmnopqrstuvwxyz".split(""); let out = ""; while (out.length < len) { out += alphabet[Math.floor(Math.random() * alphabet.length)]; } return out; }; $validateOperation(operation) { LswDatabase.trace("LswDatabaseMigration.$validateOperation"); if (["createTable", "renameTable", "deleteTable", "createColumn", "renameColumn", "deleteColumn", "cloneDatabase", "moveDatabase"].indexOf(operation) === -1) { throw new Error("Required «operation» to be a valid operation on «LswDatabaseMigration.$validateOperation»"); } } $validateParameters(parameters) { LswDatabase.trace("LswDatabaseMigration.$validateParameters"); if (typeof parameters !== "object") { throw new Error("Required «parameters» to be an object on «LswDatabaseMigration.$validateParameters»"); } } async $$transferBackTemporaryDatabase() { await LswDatabase.deleteDatabase(this.parameters.fromDatabase); await this.$replicateSchema({ fromDatabase: this.config.temporaryDatabase, toDatabase: this.parameters.fromDatabase, }); await this.$populateDatabase({ fromDatabase: this.config.temporaryDatabase, toDatabase: this.parameters.fromDatabase, }); await LswDatabase.deleteDatabase(this.config.temporaryDatabase); } commit() { LswDatabase.trace("LswDatabaseMigration.commit"); return this["$$" + this.operation].call(this).finally(() => { this.migrated = true; }); } $validateCreateTableParameters() { LswDatabase.trace("LswDatabaseMigration.$validateCreateTableParameters"); if (typeof this.parameters.fromDatabase !== "string") { throw new Error("Required «parameters.fromDatabase» to be a string on «LswDatabaseMigration.$validateCreateTableParameters»"); } if (typeof this.parameters.table !== "string") { throw new Error("Required «parameters.table» to be a string on «LswDatabaseMigration.$validateCreateTableParameters»"); } if (typeof this.parameters.tableDefinition !== "object") { throw new Error("Required «parameters.tableDefinition» to be an object on «LswDatabaseMigration.$validateCreateTableParameters»"); } } async $$cloneDatabase() { LswDatabase.trace("LswDatabaseMigration.$$cloneDatabase"); await this.$replicateSchema({ fromDatabase: this.parameters.fromDatabase, toDatabase: this.parameters.toDatabase, }); await this.$populateDatabase({ fromDatabase: this.parameters.fromDatabase, toDatabase: this.parameters.toDatabase, }); } async $$moveDatabase() { LswDatabase.trace("LswDatabaseMigration.$$moveDatabase"); await this.$replicateSchema({ fromDatabase: this.parameters.fromDatabase, toDatabase: this.parameters.toDatabase, }); await this.$populateDatabase({ fromDatabase: this.parameters.fromDatabase, toDatabase: this.parameters.toDatabase, }); await LswDatabase.deleteDatabase(this.parameters.fromDatabase); } async $$createTable() { LswDatabase.trace("LswDatabaseMigration.$$createTable"); this.$validateCreateTableParameters(); await this.$replicateSchema({ fromDatabase: this.parameters.fromDatabase, toDatabase: this.config.temporaryDatabase, onAlterSchema: schema => { schema[this.parameters.table] = this.parameters.tableDefinition; return schema; }, }); await this.$populateDatabase({ fromDatabase: this.parameters.fromDatabase, toDatabase: this.config.temporaryDatabase, onMapTableId: false, onMapColumnId: false, }); await this.$$transferBackTemporaryDatabase(); } $validateDeleteTableParameters() { LswDatabase.trace("LswDatabaseMigration.$validateDeleteTableParameters"); if (typeof this.parameters.fromDatabase !== "string") { throw new Error("Required «parameters.fromDatabase» to be a string on «LswDatabaseMigration.$validateDeleteTableParameters»"); } if (typeof this.parameters.table !== "string") { throw new Error("Required «parameters.table» to be a string on «LswDatabaseMigration.$validateDeleteTableParameters»"); } } async $$deleteTable() { LswDatabase.trace("LswDatabaseMigration.$$deleteTable"); this.$validateDeleteTableParameters(); await this.$replicateSchema({ fromDatabase: this.parameters.fromDatabase, toDatabase: this.config.temporaryDatabase, onAlterSchema: schema => { delete schema[this.parameters.table]; return schema; }, }); await this.$populateDatabase({ fromDatabase: this.parameters.fromDatabase, toDatabase: this.config.temporaryDatabase, onMapTableId: false, onMapColumnId: false, }); await this.$$transferBackTemporaryDatabase(); } $validateRenameTableParameters() { LswDatabase.trace("LswDatabaseMigration.$validateRenameTableParameters"); if (typeof this.parameters.fromDatabase !== "string") { throw new Error("Required «parameters.fromDatabase» to be a string on «LswDatabaseMigration.$validateDeleteTableParameters»"); } if (typeof this.parameters.tableSource !== "string") { throw new Error("Required «parameters.tableSource» to be a string on «LswDatabaseMigration.$validateDeleteTableParameters»"); } if (typeof this.parameters.tableDestination !== "string") { throw new Error("Required «parameters.tableDestination» to be a string on «LswDatabaseMigration.$validateDeleteTableParameters»"); } } async $$renameTable() { LswDatabase.trace("LswDatabaseMigration.$$renameTable"); this.$validateRenameTableParameters(); const currentSchema = await LswDatabase.getSchema(this.parameters.fromDatabase); await this.$replicateSchema({ fromDatabase: this.parameters.fromDatabase, toDatabase: this.config.temporaryDatabase, onAlterSchema: schema => { delete schema[this.parameters.tableSource]; const tableInput = this.$adaptSchemaTableAsSchemaDefinition(currentSchema[this.parameters.tableSource]); schema[this.parameters.tableDestination] = tableInput; return schema; }, }); await this.$populateDatabase({ fromDatabase: this.parameters.fromDatabase, toDatabase: this.config.temporaryDatabase, onMapTableId: tableId => { return this.parameters.tableDestination; }, onMapColumnId: false, }); await this.$$transferBackTemporaryDatabase(); } $validateCreateColumnParameters() { LswDatabase.trace("LswDatabaseMigration.$validateCreateColumnParameters"); if (typeof this.parameters.fromDatabase !== "string") { throw new Error("Required «parameters.fromDatabase» to be a string on «LswDatabaseMigration.$validateCreateColumnParameters»"); } if (typeof this.parameters.table !== "string") { throw new Error("Required «parameters.table» to be a string on «LswDatabaseMigration.$validateCreateColumnParameters»"); } if (typeof this.parameters.column !== "string") { throw new Error("Required «parameters.column» to be a string on «LswDatabaseMigration.$validateCreateColumnParameters»"); } if (typeof this.parameters.columnDefinition !== "object") { throw new Error("Required «parameters.columnDefinition» to be an object on «LswDatabaseMigration.$validateCreateColumnParameters»"); } } async $$createColumn() { LswDatabase.trace("LswDatabaseMigration.$$createColumn"); this.$validateCreateColumnParameters(); const isUnique = !!this.parameters.columnDefinition.isUnique; const columnSymbol = `${isUnique ? "!" : ""}${this.parameters.column}`; await this.$replicateSchema({ fromDatabase: this.parameters.fromDatabase, toDatabase: this.config.temporaryDatabase, table: this.parameters.table, onAlterSchema: schema => { schema[this.parameters.table].push(columnSymbol); return schema; }, }); await this.$populateDatabase({ fromDatabase: this.parameters.fromDatabase, toDatabase: this.config.temporaryDatabase, onMapTableId: false, onMapColumnId: false, }); await this.$$transferBackTemporaryDatabase(); } $validateDeleteColumnParameters() { LswDatabase.trace("LswDatabaseMigration.$validateDeleteColumnParameters"); if (typeof this.parameters.fromDatabase !== "string") { throw new Error("Required «parameters.fromDatabase» to be a string on «LswDatabaseMigration.$validateDeleteColumnParameters»"); } if (typeof this.parameters.table !== "string") { throw new Error("Required «parameters.table» to be a string on «LswDatabaseMigration.$validateDeleteColumnParameters»"); } if (typeof this.parameters.column !== "string") { throw new Error("Required «parameters.column» to be a string on «LswDatabaseMigration.$validateDeleteColumnParameters»"); } } async $$deleteColumn() { LswDatabase.trace("LswDatabaseMigration.$$deleteColumn"); this.$validateDeleteColumnParameters(); await this.$replicateSchema({ fromDatabase: this.parameters.fromDatabase, toDatabase: this.config.temporaryDatabase, onAlterSchema: schema => { console.log(schema); const columnPosition = schema[this.parameters.table].indexOf(this.parameters.column); schema[this.parameters.table].splice(columnPosition, 1); return schema; }, }); await this.$populateDatabase({ fromDatabase: this.parameters.fromDatabase, toDatabase: this.config.temporaryDatabase, // !@TOCONFIGURE: $$deleteColumn needs a specific hook (or none). onMapTableId: false, onMapColumnId: false, }); await this.$$transferBackTemporaryDatabase(); } $validateRenameColumnParameters() { LswDatabase.trace("LswDatabaseMigration.$validateRenameColumnParameters"); if (typeof this.parameters.fromDatabase !== "string") { throw new Error("Required «parameters.fromDatabase» to be a string on «LswDatabaseMigration.$validateRenameColumnParameters»"); } if (typeof this.parameters.table !== "string") { throw new Error("Required «parameters.table» to be a string on «LswDatabaseMigration.$validateRenameColumnParameters»"); } if (typeof this.parameters.columnSource !== "string") { throw new Error("Required «parameters.columnSource» to be a string on «LswDatabaseMigration.$validateRenameColumnParameters»"); } if (typeof this.parameters.columnDestination !== "string") { throw new Error("Required «parameters.columnDestination» to be a string on «LswDatabaseMigration.$validateRenameColumnParameters»"); } } async $$renameColumn() { LswDatabase.trace("LswDatabaseMigration.$$renameColumn"); this.$validateRenameColumnParameters(); await this.$replicateSchema({ fromDatabase: this.parameters.fromDatabase, toDatabase: this.config.temporaryDatabase, onAlterSchema: schema => { console.log(schema); const columnPosition = schema[this.parameters.table].indexOf(this.parameters.columnSource); schema[this.parameters.table].splice(columnPosition, 1); schema[this.parameters.table].push(this.parameters.columnDestination); return schema; }, }); await this.$populateDatabase({ fromDatabase: this.parameters.fromDatabase, toDatabase: this.config.temporaryDatabase, onMapTableId: false, onMapColumnId: (columnId) => { return columnId; }, }); await this.$$transferBackTemporaryDatabase(); } $adaptSchemaAsSchemaDefinition(schemaDefinition) { const output = {}; const tableIds = Object.keys(schemaDefinition); for (let index = 0; index < tableIds.length; index++) { const storeId = tableIds[index]; const tableDefinition = schemaDefinition[storeId]; const columns = tableDefinition.indexes; if (!(storeId in output)) { output[storeId] = []; } for (let indexColumn = 0; indexColumn < columns.length; indexColumn++) { const column = columns[indexColumn]; const columnId = column.name; const columnInput = this.$adaptSchemaColumnAsSchemaDefinition(column, columnId); output[storeId].push(columnInput); } } return output; } $adaptSchemaTableAsSchemaDefinition(tableDefinition) { const output = []; const columns = tableDefinition.indexes; for (let indexColumn = 0; indexColumn < columns.length; indexColumn++) { const column = columns[indexColumn]; const columnId = column.name; const columnInput = this.$adaptSchemaColumnAsSchemaDefinition(column, columnId); output.push(columnInput); } return output; } $adaptSchemaColumnAsSchemaDefinition(column, columnId) { if (column.unique) { return "!" + columnId; } else { return columnId; } } async $replicateSchema(scenario) { LswDatabase.trace("LswDatabaseMigration.$replicateSchema"); const { fromDatabase, toDatabase, onAlterSchema } = scenario; console.log(`⌛️ Replicating database from «${fromDatabase}» to «${toDatabase}» on «LswDatabaseMigration.$replicateSchema»`); const schemaDefinition = await LswDatabase.getSchema(fromDatabase); const schemaInput = this.$adaptSchemaAsSchemaDefinition(schemaDefinition); let alteredSchema = schemaInput; if (onAlterSchema) { alteredSchema = onAlterSchema(schemaInput); if (typeof alteredSchema === "undefined") { throw new Error("Required «onAlterSchema» to return an object on «LswDatabaseMigration.$replicateSchema»") } } console.log("Replicated schema:", alteredSchema); await LswDatabase.createDatabase(toDatabase, alteredSchema); } async $populateDatabase(scenario) { LswDatabase.trace("LswDatabaseMigration.$populateDatabase"); const { fromDatabase, toDatabase, onMapTableId = false, onMapColumnId = false } = scenario; console.log(`⌛️ Populating database from «${fromDatabase}» to «${toDatabase}» on «LswDatabaseMigration.$populateDatabase»`); const schemaDefinition = await LswDatabase.getSchema(fromDatabase); const tableIds = Object.keys(schemaDefinition); let fromConnection = undefined; let toConnection = undefined; let indexTable = 0; let indexColumn = 0; let tableId = undefined; let alteredTableId = undefined; try { fromConnection = new LswDatabase(fromDatabase); toConnection = new LswDatabase(toDatabase); await fromConnection.open(); await toConnection.open(); for (indexTable = 0; indexTable < tableIds.length; indexTable++) { tableId = tableIds[indexTable]; console.log("table:", tableId); Transfering_tables: { console.log(`⌛️ Transfering table «${tableId}» on «LswDatabaseMigration.$populateDatabase»`); let allRows = await fromConnection.selectMany(tableId, v => true); console.log("[*] Getting table id"); alteredTableId = tableId; if (onMapTableId) { alteredTableId = onMapTableId(tableId); } console.log("[*] Getting column id"); if (onMapColumnId) { allRows = allRows.reduce((output, row) => { const allKeys = Object.keys(row); const alteredRow = {}; for (let indexKeys = 0; indexKeys < allKeys.length; indexKeys++) { console.log("column:", indexKeys); const columnId = allKeys[indexKeys]; const alteredColumnId = onMapColumnId(columnId, tableId, alteredTableId, { fromConnection, toConnection }) || columnId; alteredRow[alteredColumnId] = row[columnId]; } output.push(alteredRow); return output; }, []); } console.log("[*] Got:", alteredTableId, allRows); await toConnection.insertMany(alteredTableId, allRows); console.log("What??? 444") } } } catch (error) { console.log(`💥 Error while populating database on table ${tableId || "-"} (alias «${alteredTableId}»):`, error); } finally { try { await fromConnection.close(); } catch (error) { console.log(error); } try { await toConnection.close(); } catch (error) { console.log(error); } console.log(`[*] Database «${toDatabase}» population finished successfully.`); } } } class LswDatabaseMigration extends BrowsieMigration { } LswDatabaseMigration.default = LswDatabaseMigration; window.LswDatabaseMigration = LswDatabaseMigration; window.BrowsieMigration = BrowsieMigration; class BrowsieMigrable extends BrowsieCrudAPI { static migration = LswDatabaseMigration; } window.Browsie = BrowsieMigrable; Browsie.default = BrowsieMigrable; /* Extended API */ class LswDatabase extends BrowsieMigrable { class = this.constructor; } LswDatabase.default = LswDatabase; window.LswDatabase = LswDatabase;
const LswDatabaseVirtualizer = class { static create(...args) { return new this(...args); } static start(...args) { const virtualization = new this(...args); return virtualization; } $lifecycle = [ "onStart", "onStartValidation", "onValidateConnection", "onValidateSchema", "onFinishValidation", "onDeleteVirtualDatabase", "onStartClonation", "onCloneDatabase", "onFinishClonation", "onStartVirtualization", "onVirtualizeSchema", "onVirtualizeTables", "onVirtualizeColumns", "onFinishVirtualization", "onStartFormalization", "onFormalizeColumns", "onFormalizeTables", "onFormalizeSchema", "onReport", "onFinishFormalization", "onFinish", ]; $defaultConfigurations = { trace: true, }; $trace(method, args) { if(this.$configurations.trace) { const methodArgs = Array.from(args); console.log(`[trace][lsw-database-virtualizer] ${method}: (${methodArgs.length}) ${methodArgs.map(e => typeof e).join(", ")}`); } } constructor(configurations = {}) { this.$configurations = Object.assign({}, this.$defaultConfigurations, configurations || {}); this.$trace("constructor", arguments); this.triggers = new TriggersClass(); this.physicalConnection = undefined; this.virtualConnection = undefined; this.schema = undefined; } configure(options = {}) { this.$trace("configure", arguments); $ensure({ options }, 1).to.have.uniquelyKeys(["physicalConnection", "virtualConnection", "schema"]); Object.assign(this, options); return this; } setPhysicalConnection(physicalConnection) { this.$trace("setPhysicalConnection", arguments); this.physicalConnection = physicalConnection; return this; } setVirtualConnection(virtualConnection) { this.$trace("setVirtualConnection", arguments); this.virtualConnection = virtualConnection; return this; } setSchema(schema) { this.$trace("setSchema", arguments); this.schema = schema; return this; } start() { this.$trace("start", arguments); return LswCycler.from(this, "*").run(this.$lifecycle); } async onStart() { this.$trace("onStart", arguments); // *@TODO: } async onStartValidation() { this.$trace("onStartValidation", arguments); // *@TODO: } async onValidateConnection() { this.$trace("onValidateConnection", arguments); // *@TODO: } async onValidateSchema() { this.$trace("onValidateSchema", arguments); // *@TODO: } async onFinishValidation() { this.$trace("onFinishValidation", arguments); // *@TODO: } async onDeleteVirtualDatabase() { this.$trace("onDeleteVirtualDatabase", arguments); // *@TODO: } async onStartClonation() { this.$trace("onStartClonation", arguments); // *@TODO: } async onCloneDatabase() { this.$trace("onCloneDatabase", arguments); // *@TODO: } async onFinishClonation() { this.$trace("onFinishClonation", arguments); // *@TODO: } async onStartVirtualization() { this.$trace("onStartVirtualization", arguments); // *@TODO: } async onVirtualizeSchema() { this.$trace("onVirtualizeSchema", arguments); // *@TODO: } async onVirtualizeTables() { this.$trace("onVirtualizeTables", arguments); // *@TODO: } async onVirtualizeColumns() { this.$trace("onVirtualizeColumns", arguments); // *@TODO: } async onFinishVirtualization() { this.$trace("onFinishVirtualization", arguments); // *@TODO: } async onStartFormalization() { this.$trace("onStartFormalization", arguments); // *@TODO: } async onFormalizeColumns() { this.$trace("onFormalizeColumns", arguments); // *@TODO: } async onFormalizeTables() { this.$trace("onFormalizeTables", arguments); // *@TODO: } async onFormalizeSchema() { this.$trace("onFormalizeSchema", arguments); // *@TODO: } async onFinishFormalization() { this.$trace("onFinishFormalization", arguments); // *@TODO: } async onReport() { this.$trace("onReport", arguments); // *@TODO: } async onFinish() { this.$trace("onFinish", arguments); // *@TODO: } }
const Definition = class { constructor({ id, dependencies = [] }) { this.id = id; this.dependencies = dependencies; } }; const LswDepender = class { static create(...args) { return new this(...args); } constructor(definitions = {}) { this.$definitions = definitions; } hasDefined(name) { if (name in this.$definitions) { if (this.$definitions[name] instanceof Definition) { return true; } } return false; } define(...args) { if (typeof args[0] === "string") { return this.addDefinition(...args); } return this.addUniqueDefinitions(...args); } resolve(idsInput = this, defs = this.$definitions) { const ids = idsInput === this ? Object.keys(this.$definitions) : idsInput; let resolved = new Set(); let resultado = []; const resolverNodo = function(id) { console.log("resolviendo nodo:", id, defs); if (resolved.has(id)) return; if (!defs[id]) return; // Si no está definido, lo ignoramos for (let dep of defs[id].dependencies || []) { resolverNodo(dep); } resolved.add(id); resultado.push(id); } for (let id of [].concat(ids)) { resolverNodo(id); } return resultado; } addDefinition(name, definition, shouldFailOnRedundancy = 1, shouldOverrideOnRedundancy = 1) { Validation: { if (this.hasDefined(name)) { if (shouldFailOnRedundancy) { throw new Error(`Dependency «${name}» is already defined and should not redund on «LswDepender.define»`); } else if (!shouldOverrideOnRedundancy) { return false; // !@BREAK: the fallback must not override it } else if (shouldOverrideOnRedundancy) { // !@OK: the fallback will override it } else { throw new Error("Cannot logically happen (1)"); } } } Define_it: { if (typeof definition !== "object") { throw new Error(`Required definition of «${name}» to be an object on «LswDepender.define»`); } else if (typeof definition.id !== "string") { definition.id = name; } else if (Array.isArray(definition.dependencies)) { throw new Error(`Required definition of «${name}» its property «dependencies» to be a array on «LswDepender.define»`); } else { for (let indexDependency = 0; indexDependency < definition.dependencies.length; indexDependency++) { const dependencyRef = definition.dependencies[indexDependency]; if (typeof dependencyRef !== "string") { throw new Error(`Required definition of «${name}» its property «dependencies» on its index «${indexDependency}» to be a string on «LswDepender.define»`); } } } this.$definitions[name] = new Definition(definition); } } addUniqueDefinitions(moreDefinitions = {}) { const definitionIds = Object.keys(moreDefinitions); for (let indexId = 0; indexId < definitionIds.length; indexId++) { const definitionId = definitionIds[indexId]; const definitionInstance = moreDefinitions[definitionId]; this.define(definitionId, definitionInstance, 1); } } addMissingDefinitions(moreDefinitions = {}) { const definitionIds = Object.keys(moreDefinitions); for (let indexId = 0; indexId < definitionIds.length; indexId++) { const definitionId = definitionIds[indexId]; const definitionInstance = moreDefinitions[definitionId]; this.define(definitionId, definitionInstance, 0, 0); } } resetDefinitions(moreDefinitions = {}) { const definitionIds = Object.keys(moreDefinitions); for (let indexId = 0; indexId < definitionIds.length; indexId++) { const definitionId = definitionIds[indexId]; const definitionInstance = moreDefinitions[definitionId]; this.define(definitionId, definitionInstance, 0, 1); } } deleteDefinitions(definitionsInput = []) { const definitions = Array.isArray(definitionsInput) ? definitionsInput : [definitionsInput]; for (let indexDefinition = 0; indexDefinition < definitions.length; indexDefinition++) { const definitionId = definitions[indexDefinition]; delete this.$definitions[definitionId]; } } } LswDepender.default = LswDepender;
Vue.directive("call", { inserted: function(el) { } });
(() => { const getDescriptorKeys = function (el, binding) { if (binding.expression.startsWith("'") || binding.expression.startsWith('"')) { return (binding.value || el.getAttribute("descriptor")).split(" "); } return (binding.expression || el.getAttribute("descriptor")).split(" "); }; Vue.directive("descriptor", { bind(el, binding) { const resolveClasses = key => { let resolved = window.stylingDescriptor[key]; if (!resolved) return key; if (typeof resolved === "string") { resolved = resolved.split(" "); } return resolved.map(subKey => resolveClasses(subKey)).flat(); }; const descriptorKeys = getDescriptorKeys(el, binding); const descriptorClasses = descriptorKeys.map(key => resolveClasses(key)).flat(); descriptorClasses.forEach(cls => { if (cls.indexOf(".") === -1) { el.classList.add(cls); } }); } }); const styleTag = document.createElement("style"); styleTag.textContent = ` .title_of_form { border: 1px solid #113; box-shadow: 0 0 4px black; border-radius: 0pt; color: black; width: 100%; padding: 8px; font-size: 12px; background-color: #AAF; } .block_of_form { padding: 4px; padding-left: 0px; padding-right: 0px; padding-bottom: 0px; padding-top: 0px; } .bordered_1 { border: 1px solid #999; border-radius: 2pt; } .with_separator_on_bottom_1 { border-bottom: 1px solid #999; } .lateral_button { height: 100%; } .lateral_button_cell { padding-top: 4px; padding-bottom: 4px; padding-right: 4px; } .padded_1 { padding: 4px; } .vertically_padded_1 { padding-top: 4px; padding-bottom: 4px; } .horizontally_padded_1 { padding-left: 4px; padding-right: 4px; } .left_padded_1 { padding-left: 4px; } .right_padded_1 { padding-right: 4px; } .top_padded_1 { padding-top: 4px; } .bottom_padded_1 { padding-bottom: 4px; } .calendar_main_panel { padding-left: 0px; padding-right: 0px; padding-top: 0px; padding-bottom: 4px; } .calendar_buttons_panel_1 { padding-bottom: 0px; padding-top: 0px; } `; window.addEventListener("load", function() { document.body.appendChild(styleTag); }); window.stylingDescriptor = { "agenda.calendar.buttons_panel_1": "calendar_main_panel calendar_buttons_panel_1", "agenda.task_form.title": "title_of_form", "agenda.task_form.block": "block_of_form", "agenda.task_form.block_of_add_button": "block_of_form vertically_padded_1", "agenda.task_form.block_of_aggregated_field": "bordered_1", "agenda.task_form.section": "with_separator_on_bottom_1", "agenda.task_form.aggregations.block": "block_of_form", "agenda.task_form.aggregations.lateral_button": "lateral_button", "agenda.task_form.aggregations.lateral_button_cell": "lateral_button_cell", "lsw_table.no_data_provided_message": "pad_top_2 pad_bottom_2" } })();
Vue.directive("focus", { inserted: function(el) { el.focus(); } });
(function (factory) { const mod = factory(); if (typeof window !== 'undefined') { window['LswXForm'] = mod; } if (typeof global !== 'undefined') { global['LswXForm'] = mod; } if (typeof module !== 'undefined') { module.exports = mod; } })(function () { let isTracing = true; let $COUNTADOR1 = 0; const trace = function (method, args = []) { if (isTracing) { console.log("[trace][lsw-xform][" + method + "]", args.length); } }; const XformCommonInterface = class { formInterface = "Common"; static create(...args) { return new this(...args); } constructor(inheritedArgument, subtype) { const { el, binding, scope } = inheritedArgument; trace("XformCommonInterface.constructor:" + this.formInterface); this.$el = el; this.$binding = binding; this.$component = scope; this.$type = subtype; this.$name = this.$binding.value?.name || false; this.$onValidateInterfaceArguments(); this.$injectAttributesToElement(); } $injectAttributesToElement() { trace("XformCommonInterface.$injectAttributesToElement:" + this.formInterface); this.$el.setAttribute("data-xform-node", this); } $onValidateInterfaceArguments() { trace("XformCommonInterface.$onValidateInterfaceArguments:" + this.formInterface); const ensureEl = $ensure(this.$el).type("object").to.be.instanceOf(HTMLElement); const ensureBinding = $ensure(this.$binding).type("object"); const ensureValue = ensureBinding.its("value").type("object"); const checkValue = $check(ensureValue.$subject); $ensure(this.$type).type("string").to.be.oneOf(["form", "control", "input", "error"]); ensureValue.to.have.keys(["name"]).its("name").type("string").back(); if (checkValue.to.have.key("onSetError")) { ensureValue.its("onSetError").type("function"); } if (checkValue.to.have.key("onClearError")) { ensureValue.its("onClearError").type("function"); } if (checkValue.to.have.key("onGetValue")) { ensureValue.its("onGetValue").type("function"); } if (checkValue.to.have.key("onGetChildren")) { ensureValue.its("onGetChildren").type("function"); } if (checkValue.to.have.key("onValidate")) { ensureValue.its("onValidate").type("function"); } if (checkValue.to.have.key("onSubmit")) { ensureValue.its("onSubmit").type("function"); } } validate() { trace("XformCommonInterface.validate:" + this.formInterface); const value = this.getValue(); const result = this.$hookWith("onValidate", [value, this]); this.$propagateSuccess(); return true; } $getParent(onlyTypes = false) { trace("XformCommonInterface.$getParent:" + this.formInterface); if (typeof onlyTypes === "string") { onlyTypes = [onlyTypes]; } const found = LswDom.getClosestParent(this.$el, "[data-xform-node]"); if (!Array.isArray(onlyTypes)) { return found; } else if (!found?.length) { return found; } return found.filter(el => onlyTypes.indexOf(el.$xform.$type) !== -1); } $getChildren(onlyTypesInput = false) { trace("XformCommonInterface.$getChildren:" + this.formInterface); let onlyTypes = onlyTypesInput; if (typeof onlyTypesInput === "string") { onlyTypes = [onlyTypesInput]; } const found = LswDom.getClosestChildren(this.$el, "[data-xform-node]"); if (!Array.isArray(onlyTypes)) { return found; } else if (!found?.length) { return found; } const foundChildren = found.filter(el => onlyTypes.indexOf(el.$xform.$type) !== -1); return foundChildren; } getValue() { trace("XformCommonInterface.getValue:" + this.formInterface); const result = this.$hookWith("onGetValue"); if (typeof result !== "undefined") { return result; } return this.$getChildren(["form", "control", "input"]).reduce((output, el) => { const hasName = el.$xform.$binding.value.name; if (hasName === "*") { output = el.$xform.getValue(); } else if (!hasName) { // @OK... } else { output[hasName] = el.$xform.getValue(); } return output; }, {}); } $hookWith(hookId, parameters = []) { trace("XformCommonInterface.$hookWith:" + this.formInterface); if (!(hookId in this.$binding.value)) { console.log(`[-] No hooks found for ${hookId}`); return undefined; } const hookFunction = this.$binding.value[hookId]; if (typeof hookFunction === "undefined") { console.log(`[-] Hook with bad type found for ${hookId}`); return undefined; } else if (typeof hookFunction !== "function") { throw new Error(`Expected parameter «${hookId}» to be a function on «$hookWith»`); } console.log(`[*] Executing hook for ${hookId}`); console.log(hookFunction.toString(), parameters); return hookFunction(...parameters); } $setError(error) { trace("XformCommonInterface.$setError:" + this.formInterface); this.$error = error; this.$hookWith("onSetError", [error, this]); return this; } $clearError() { trace("XformCommonInterface.$clearError:" + this.formInterface); this.$error = false; this.$hookWith("onClearError", [this]); return this; } $propagateError(error, rethrowIt = 1, propagateDown = 1, propagateUp = 1) { trace("XformCommonInterface.$propagateError:" + this.formInterface); try { if (this.$binding.value.debug) { console.error(`[DEBUG] Error propagated to «v-form.${this.$type}»:`, error); } const contador = ++$COUNTADOR1; Propagate_down: { if(!propagateDown) { break Propagate_down; } console.log("propagate down now " + contador + " " + this.formInterface); const propagableChildren = this.$getChildren(["error"]); console.log(propagableChildren); if (propagableChildren && propagableChildren.length) { for (let index = 0; index < propagableChildren.length; index++) { const child = propagableChildren[index]; child.$xform.$setError(error); } } console.log("ok down now " + contador + " " + this.formInterface); } Propagate_up: { if(!propagateUp) { break Propagate_up; } console.log("propagate up now " + contador + " " + this.formInterface); const propagableParent = this.$getParent(["form", "control"]); console.log(propagableParent); if (propagableParent) { try { propagableParent.$xform.$propagateError(error, 1); } catch (error) { console.log(error); } } console.log("ok up now " + contador + " " + this.formInterface); } } catch (error) { console.log(error); } finally { this.$setError(error); if (rethrowIt === 1) { throw error; } } } $propagateSuccess() { trace("XformCommonInterface.$propagateSuccess:" + this.formInterface); // this.$getParent(["form", "control"]).$propagateSuccess(error); const contador = ++$COUNTADOR1; Propagate_down: { console.log("propagate SUCCESS down now (to errors)" + contador + " " + this.formInterface); const propagableChildren = this.$getChildren(["error"]); console.log(propagableChildren); for (let index = 0; index < propagableChildren.length; index++) { const child = propagableChildren[index]; child.$xform.$clearError(); } console.log("ok down now (to errors)" + contador + " " + this.formInterface); } this.$clearError(); } }; const XformForm = class extends XformCommonInterface { formInterface = "Form"; $onValidateInterfaceArguments() { trace("XformForm.$onValidateInterfaceArguments"); const ensureEl = $ensure(this.$el); const ensureBinding = $ensure(this.$binding); const ensureValue = ensureBinding.its("value").type("object"); const checkValue = $check(ensureValue.$subject); } submit() { trace("XformForm.submit"); const resultado = this.validate(); if(resultado !== true) throw new Error("Could not validate form"); const value = this.getValue(); const result = this.$hookWith("onSubmit", [value], this); if (typeof result !== "undefined") { return result; } } validate() { trace("XformForm.validate"); try { const allChildren = this.$getChildren(["form", "control"]); let allErrors = 0; for (let indexChild = 0; indexChild < allChildren.length; indexChild++) { const child = allChildren[indexChild]; console.log("Validating [from form] now child to validate:", child); try { const result = child.$xform.validate(); if(!result) { allErrors++; } } catch (error) { allErrors++; } } if(allErrors > 0) { throw new Error(`Form has «${allErrors}» validation errors. Fix them to continue.`); } const resultado = super.validate(); if(resultado !== true) { throw new Error("Could not validate form natively (calling super.validate) on «XformForm.validate»"); } this.$propagateSuccess(); return true; } catch (error) { this.$propagateError(error, 0, 1, 0); throw error; } } }; const XformControl = class extends XformCommonInterface { formInterface = "Control"; $onValidateInterfaceArguments() { trace("XformControl.$onValidateInterfaceArguments"); const ensureEl = $ensure(this.$el); const ensureBinding = $ensure(this.$binding); const ensureValue = ensureBinding.its("value").type("object"); const checkValue = $check(ensureValue.$subject); ensureValue.to.have.keys(["name"]).its("name").type("string").back(); if (checkValue.to.have.key("onSetError")) { ensureValue.its("onSetError").type("function"); } if (checkValue.to.have.key("onClearError")) { ensureValue.its("onClearError").type("function"); } if (checkValue.to.have.key("onGetValue")) { ensureValue.its("onGetValue").type("function"); } if (checkValue.to.have.key("onGetChildren")) { ensureValue.its("onGetChildren").type("function"); } if (checkValue.to.have.key("onValidate")) { ensureValue.its("onValidate").type("function"); } } $validateChildren() { trace("XformControl.$validateChildren"); const allChildren = this.$getChildren(["form", "control", "input"]); for (let indexChild = 0; indexChild < allChildren.length; indexChild++) { const child = allChildren[indexChild]; console.log("Validating [from control] now child to validate:", child); child.$xform.validate(); } this.$propagateSuccess(); } validate(deeply = false) { trace("XformControl.validate"); try { const value = this.getValue(); this.$hookWith("onValidate", [value, this]); this.$propagateSuccess(); return true; } catch (error) { this.$propagateError(error, 0, 1, 0); } } }; const XformInput = class extends XformCommonInterface { formInterface = "Input"; validate() { trace("XformInput.validate"); const value = this.getValue(); this.$hookWith("onValidate", [value, this]); return true; } $onValidateInterfaceArguments() { trace("XformInput.$onValidateInterfaceArguments"); const ensureEl = $ensure(this.$el); const ensureBinding = $ensure(this.$binding); const ensureValue = ensureBinding.its("value").type("object"); const checkValue = $check(ensureValue.$subject); ensureValue.to.have.keys(["name"]).its("name").type("string").back(); if (checkValue.to.have.key("onSetError")) { ensureValue.its("onSetError").type("function"); } if (checkValue.to.have.key("onClearError")) { ensureValue.its("onClearError").type("function"); } if (checkValue.to.have.key("onGetValue")) { ensureValue.its("onGetValue").type("function"); } } getValue() { trace("XformInput.getValue"); if (["INPUT", "TEXTAREA", "SELECT"].indexOf(this.$el.tagName) !== -1) { const ownValue = this.$el.value; return ownValue; } else { return super.getValue(); } } $propagateSuccess() { const control = this.$getParent(["control"]); control.$xform.$propagateSuccess(); } }; const XformError = class extends XformCommonInterface { formInterface = "Error"; $onValidateInterfaceArguments() { trace("XformError.$onValidateInterfaceArguments"); const ensureEl = $ensure(this.$el); const ensureBinding = $ensure(this.$binding); const ensureValue = ensureBinding.its("value").type("object"); const checkValue = $check(ensureValue.$subject); if (checkValue.to.have.key("onSetError")) { ensureValue.its("onSetError").type("function"); } if (checkValue.to.have.key("onClearError")) { ensureValue.its("onClearError").type("function"); } } validate() { // @EMPTY. } $getChildren() { trace("XformError.$getChildren"); throw new Error(`Error can not contain children on «XformError.$getChildren»`); } getValue() { trace("XformError.getValue"); throw new Error(`Error can not contain a value on «XformError.getValue»`); } $setError(error) { trace("XformError.$setError"); this.$error = error; this.$el.classList.add("error_is_affecting_field"); try { const summarizedError = error.summarized(); summarizedError.stack2 = summarizedError.stack.map(tr => { return tr.split("\n").map((msg, errorIndex) => { const [callbackName, rest1] = LswUtils.splitStringOnce(msg, "@"); if (!rest1) { return [1, callbackName, rest1]; } const rest2 = LswUtils.reverseString(rest1); const [columnReversed, rest3] = LswUtils.splitStringOnce(rest2, ":"); if (!rest3) { return [3, rest3, columnReversed, callbackName]; return msg; } const [lineReversed, errorSource] = LswUtils.splitStringOnce(rest3, ":"); if (!errorSource) { return [5, errorSource, lineReversed, rest3, columnReversed, callbackName]; return msg; } const line = LswUtils.reverseString(lineReversed); const column = LswUtils.reverseString(columnReversed); return `${errorIndex + 1}. ${LswUtils.reverseString(errorSource)}:${line}:${column}::${callbackName}`; }).join("\n") }); this.$getErrorMessageElement().textContent = `${error.name}: ${error.message}.\n${summarizedError.stack2}`; } catch (error2) { this.$getErrorMessageElement().textContent = `${error.name}: ${error.message} [${error.stack}]`; } try { this.$hookWith("onSetError", [error, this]); } catch (error) { console.log(error); } return this; } $getErrorMessageElement() { return (this.$el.querySelector(".errorMessage") || this.$el); } $clearError() { trace("XformError.$clearError"); this.$error = undefined; this.$el.classList.remove("error_is_affecting_field"); this.$getErrorMessageElement().textContent = ``; this.$hookWith("onClearError", [this]); return this; } }; const xformClasses = { form: XformForm, control: XformControl, input: XformInput, error: XformError, }; Vue.directive("xform", { bind(el, binding) { trace("xform-directive.bind"); // console.log(binding); const modifierType = Object.keys(binding.modifiers)[0]; if (!(modifierType in xformClasses)) { throw new Error("Required directive «v-form» to be injected with a known modifier on «xform.bind»"); } const xformClass = xformClasses[modifierType]; const xformInstance = xformClass.create({ el, binding, scope: this }, modifierType); el.$xform = xformInstance; }, unbind(el) { trace("xform-directive.unbind"); delete el.$xform.$binding; delete el.$xform.$el; delete el.$xform; } }); const XFormPublicAPI = { validateSettings(settings) { trace("XFormPublicAPI.validateSettings"); const checkSettings = $check(settings); const ensureSettings = $ensure(settings).type("object").to.have.key("name"); ensureSettings.its("name").type("string").back(); if (checkSettings.to.have.key("input")) { const ensureInput = ensureSettings.its("input").type("object"); ensureInput.to.have.uniquelyKeys(["props", "events"]); if(checkSettings.its("input").to.have.key("props")) { ensureInput.its("props").type("object"); } if(checkSettings.its("input").to.have.key("events")) { ensureInput.its("events").type("object"); } } } } return XFormPublicAPI; });
const LswDom = class { static collectLeaves(originalCollection, selectorSequence = []) { $ensure(originalCollection).type("object"); $ensure(selectorSequence).type("object").to.be.array(); let collection = Array.isArray(originalCollection) ? originalCollection : [originalCollection]; const mapperFactory = selector => element => { return [].concat(element.querySelectorAll(selector)); }; for(let indexSelector=0; indexSelector<selectorSequence.length; indexSelector++) { const selector = selectorSequence[indexSelector]; const subnodes = collection.map(mapperFactory(selector)).flat(); collection = [].concat(subnodes); } return collection; } static getClosestParent(originalElement, selector) { $ensure(originalElement).type("object").to.be.instanceOf(HTMLElement); $ensure(selector).type("string"); let element = originalElement.parentElement; while(element && (element !== document)) { if(element.matches(selector)) { return element; } element = element.parentElement; } return null; } static getClosestChildren(originalElement, selector) { $ensure(originalElement).type("object").to.be.instanceOf(HTMLElement); $ensure(selector).type("string"); return [...originalElement.querySelectorAll(selector)].filter(element => { return this.getClosestParent(element, selector) === originalElement; }); } static querySelectorFirst(selector, matchingText = false, originalElement = document) { const all = originalElement.querySelectorAll(selector); const matched = Array.from(all).filter(element => { return element.textContent.trim().toLowerCase() === matchingText.toLowerCase(); }); return matched.length ? matched[0] : null; } static findVue(selector, matchingText = false, base = document) { const all = base.querySelectorAll(selector); const matched = Array.from(all).filter(element => { if(!matchingText) { return true; } return element.textContent.trim().toLowerCase() === matchingText.toLowerCase(); }); return matched.length ? matched[0].__vue__ : null; } static waitForMilliseconds(ms) { return new Promise((resolve, reject) => { setTimeout(resolve, ms); }); } static extractPlaintextFromHtmltext(htmlText) { const el1 = document.createElement("div"); el1.innerHTML = htmlText; return el1.textContent; } static findCssRulesContaining(selector) { const matchingRules = []; for (const styleSheet of document.styleSheets) { let rules; try { rules = styleSheet.cssRules || styleSheet.rules; } catch (e) { // Evita errores por CORS en hojas de estilo externas continue; } if (!rules) continue; for (const rule of rules) { if (rule.selectorText && rule.selectorText.includes(selector)) { matchingRules.push(rule); } } } return matchingRules; } static hasCssRulesContaining(selector) { const matchingRules = []; for (const styleSheet of document.styleSheets) { let rules; try { rules = styleSheet.cssRules || styleSheet.rules; } catch (e) { // Evita errores por CORS en hojas de estilo externas continue; } if (!rules) continue; for (const rule of rules) { if (rule.selectorText && rule.selectorText.includes(selector)) { return true; } } } return false; } };
const LswDomIrruptor = class { static async aniadirNota() { LswDom.querySelectorFirst(".home_bottom_panel > button", "+ 💬").click(); } static async verNotas() { LswDom.querySelectorFirst(".home_mobile_off_panel > .mobile_off_panel_cell", "💬").click(); } static async abrirHomepage() { LswDom.querySelectorFirst(".main_topbar_button", "📟").click(); } static async calendario() { LswDom.querySelectorFirst(".home_mobile_off_panel > .mobile_off_panel_cell", "📅").click(); } static async abrirCalendario() { LswDom.querySelectorFirst(".main_topbar_button", "📆").click(); } static async reportesDeCalendario() { LswDom.querySelectorFirst(".home_mobile_off_panel > .mobile_off_panel_cell", "📅").click(); await LswDom.waitForMilliseconds(200); LswDom.querySelectorFirst("button.nowrap", "📊").click(); await LswDom.waitForMilliseconds(200); LswDom.querySelectorFirst("button", "🔮 Iniciar conductometría").click(); } static async abrirNavegacionRapida() { LswDom.querySelectorFirst(".main_topbar_button", "📟").click(); } static async abrirBinarios() { LswDom.querySelectorFirst(".main_topbar_button", "📟").click(); await LswDom.waitForMilliseconds(200); LswDom.querySelectorFirst(".lista_apps div", "💣 Binarios").click(); } static async abrirBaseDeDatos() { LswDom.querySelectorFirst(".main_topbar_button", "📟").click(); await LswDom.waitForMilliseconds(200); LswDom.querySelectorFirst("div", "📦 Base de datos").click(); } static async abrirBaseDeDatosPorTabla(tabla) { LswDom.querySelectorFirst(".main_topbar_button", "📟").click(); await LswDom.waitForMilliseconds(200); LswDom.querySelectorFirst("div", "📦 Base de datos").click(); await LswDom.waitForMilliseconds(200); LswDom.querySelectorFirst("span.table_name", tabla).click(); } static async abrirBaseDeDatosPorNuevoDatoDeTabla(tabla) { this.abrirBaseDeDatosPorTabla(tabla); await LswDom.waitForMilliseconds(500); LswDom.querySelectorFirst("button", "➕").click(); } static async abrirAccionesVirtuales() { await this.abrirBaseDeDatos(); await LswDom.waitForMilliseconds(200); LswDom.querySelectorFirst("button", "Accion_virtual").click(); } static async abrirTareasPosterioresDeNavegacionRapida() { LswDom.querySelectorFirst(".lista_apps button", "🕓 Tareas posteriores").click(); } static async abrirRecords() { this.abrirTareasPosterioresDeNavegacionRapida(); await LswDom.waitForMilliseconds(500); LswDom.querySelectorFirst("button", "📷📊").click(); } static async configuraciones() { LswDom.querySelectorFirst("#windows_pivot_button", "🔵").click(); await LswDom.waitForMilliseconds(200); LswDom.querySelectorFirst("button.main_tab_topbar_button", "🔧").click(); } static async abrirConfiguraciones() { LswDom.querySelectorFirst("#windows_pivot_button", "🔵").click(); await LswDom.waitForMilliseconds(200); LswDom.querySelectorFirst("button.main_tab_topbar_button", "🔧").click(); } static async abrirFicheros() { LswDom.querySelectorFirst("#windows_pivot_button", "🔵").click(); await LswDom.waitForMilliseconds(200); LswDom.querySelectorFirst("button.main_tab_topbar_button", "📂").click(); } static async abrirWiki() { LswDom.querySelectorFirst(".mobile_off_panel_cell", "🔬").click(); } static async abrirWikiArticulos() { await this.abrirWiki(); await LswDom.waitForMilliseconds(); Abrir_articulos: { await LswDom.waitForMilliseconds(200); LswDom.querySelectorFirst(".lsw_wiki button.supermini", "🔬").click(); return; } } static async abrirWikiLibros() { await this.abrirWiki(); await LswDom.waitForMilliseconds(); Abrir_libros: { await LswDom.waitForMilliseconds(200); LswDom.querySelectorFirst(".lsw_wiki button.supermini", "📚").click(); } } static async abrirWeekPlanner() { await this.abrirCalendario(); Abrir_planificador: { await LswDom.waitForMilliseconds(1000); LswDom.querySelectorFirst("button", "7️⃣").click(); } } static async abrirAcciones() { await this.abrirBaseDeDatos(); Abrir_planificador: { await LswDom.waitForMilliseconds(1000); LswDom.querySelectorFirst("button", "7️⃣").click(); } } static async abrirNuevaFeature() { await this.abrirHomepage(); Abrir_planificador: { await LswDom.waitForMilliseconds(1000); LswDom.querySelectorFirst("div", "✨ Nueva feature").click(); } } static async abrirJsInspector() { await this.abrirHomepage(); Abrir_inspector: { await LswDom.waitForMilliseconds(1000); LswDom.querySelectorFirst("div", "🪲 Inspector de JS").click(); } } static async abrirTestsDeAplicacion() { await this.abrirHomepage(); Abrir_tests: { await LswDom.waitForMilliseconds(200); LswDom.querySelectorFirst("div", "✅ Tests de aplicación").click(); } } static async arrancarTestsDeAplicacion() { await this.abrirTestsDeAplicacion(); Abrir_tests: { await LswDom.waitForMilliseconds(200); LswDom.querySelectorFirst("button", "▶️").click(); } } static async getRutinerTimeout() { const rutinerConfig = await Vue.prototype.$lsw.fs.evaluateAsDotenvFileOrReturn("/kernel/settings/rutiner.config.env", []); LswTimer.utils.fromDurationstringToMilliseconds(rutinerConfig.timeout); return rutinerConfig.timeout; } static async setRutinerTimeout(durationstring) { LswTimer.utils.fromDurationstringToMilliseconds(durationstring); await Vue.prototype.$lsw.fs.write_file("/kernel/settings/rutiner.config.env", "timeout=" + durationstring); } static async configurarRutinerTimeout() { const currentTimeout = await LswDomIrruptor.getRutinerTimeout(); const duracion = await Vue.prototype.$lsw.dialogs.open({ title: "Configurar frecuencia de mensaje rutinario", template: ` <div class="pad_1" v-xform.form="{ onSubmit }" ref="formulario"> <div class="pad_bottom_1">Especifica la duración preferida:</div> <lsw-duration-control :settings="{name:'duracion',initialValue}" /> <hr/> <div class="flex_row centered pad_1"> <div class="flex_100"></div> <div class="flex_1 pad_left_1"> <button class="supermini" v-on:click="submitForm">Aceptar</button> </div> <div class="flex_1 pad_left_1"> <button class="supermini" v-on:click="cancel">Cancelar</button> </div> </div> </div> `, factory: { data: { initialValue: currentTimeout, }, methods: { onSubmit(formdata) { return this.accept(formdata.duracion); }, async submitForm() { this.$trace("Dialogs.configurar-frecuencia-rutiner.methods.submitForm"); return await this.$refs.formulario.$xform.submit(); } }, } }); if(duracion === -1) { return; } if(typeof duracion !== "string") { console.log("duracion", duracion); } const milliseconds = LswTimer.utils.fromDurationstringToMilliseconds(duracion); return await LswDomIrruptor.setRutinerTimeout(duracion); } } return LswDomIrruptor;
class AssertionError extends Error { constructor(...args) { super(...args); this.name = "AssertionError"; } } class Ensurement { static create(...args) { return new this(...args); } constructor(source, asLabeledObject = 0) { Resolve_subject: { if ((asLabeledObject === 1) && (typeof source === "object")) { const sourceKeys = Object.keys(source); if (sourceKeys.length !== 1) { throw new Error(`The first parameter of $ensure or $check {when $2 is literally 1} must have 1 property (not ${sourceKeys.length}) on «Ensurement.constructor»`); } this.$subjectId = sourceKeys[0]; this.$subject = source[this.$subjectId]; } else if(typeof asLabeledObject === "string") { this.$subjectId = asLabeledObject; this.$subject = source; } else { this.$subjectId = "@"; this.$subject = source; } } this.$operation = undefined; this.$objectation = undefined; this.asBoolean = false; } type(value) { this.$operation = "is of type"; this.$objectation = value; if(typeof value === "string") { if (typeof this.$subject !== value) { return this.$asFailed(); } } else if(Array.isArray(value)) { if(value.indexOf(typeof this.$subject) === -1) { return this.$asFailed(); } } else { throw new Error(`Bad parameter on «$ensure(...).type(?)» (${typeof value} not admitted)`); } return this.$asResolved(); } notType(value) { this.$operation = "is not of type"; this.$objectation = value; if (typeof this.$subject === value) { return this.$asFailed(); } return this.$asResolved(); } is(value) { this.$operation = "is"; this.$objectation = value; if (this.$subject !== value) { return this.$asFailed(); } return this.$asResolved(); } isnt(value) { this.$operation = "is not"; this.$objectation = value; if (this.$subject === value) { return this.$asFailed(); } return this.$asResolved(); } can(value) { this.$operation = "can"; this.$objectation = value; if (!value(this.$subject)) { return this.$asFailed(); } return this.$asResolved(); } cant(value) { this.$operation = "cant"; this.$objectation = value; if (value(this.$subject)) { return this.$asFailed(); } return this.$asResolved(); } throws(value) { this.$operation = "throws"; this.$objectation = value; try { objectation(this.$subject); return this.$asFailed(); } catch (error) { return this.$asResolved(); } } doesntThrow(value) { this.$operation = "doesntThrow"; this.$objectation = value; try { value(this.$subject); return this.$asFailed(); } catch (error) { return this.$asResolved(); } } $asFailed(operation = this.$operation) { if (this.asBoolean) { return false; } throw new AssertionError("could not ensure «" + this.$subjectId + "» " + operation + (this.$objectation ? " «" + this.$getObjectationAsString() + "»": "") + ""); } $getObjectationAsString() { return JSON.stringify(this.$objectation); } $asResolved() { if (this.asBoolean) { return true; } else { return this; } } }; const BasicToBeInterface = class { $isNegated = false; set $operation(value) { this.$ensurement.$operation = value; } get $operation() { return this.$ensurement.$operation; } set $objectation(value) { this.$ensurement.$objectation = value; } get $objectation() { return this.$ensurement.$objectation; } constructor(ensurement) { this.$ensurement = ensurement; this.$subject = this.$ensurement.$subject; } $makeNegable(condition) { return this.$isNegated === true ? !condition : condition; } $asFailed() { return this.$ensurement.$asFailed(); } $resolveNegableString(text) { return text.replace(/\{not\?\} */g, this.$isNegated ? "not " : ""); } }; const ToBeInterface = class extends BasicToBeInterface { string() { this.$operation = this.$resolveNegableString("to {not?} be string"); this.$objectation = undefined; if (this.$makeNegable(typeof this.$subject !== "string")) return this.$asFailed(); return this.$ensurement.$asResolved(); } number() { this.$operation = this.$resolveNegableString("to {not?} be number"); this.$objectation = undefined; if (this.$makeNegable(typeof this.$subject !== "number") || Number.isNaN(this.$subject)) return this.$asFailed(); return this.$ensurement.$asResolved(); } object() { this.$operation = this.$resolveNegableString("to {not?} be object"); this.$objectation = undefined; if (this.$makeNegable(typeof this.$subject !== "object")) return this.$asFailed(); return this.$ensurement.$asResolved(); } null() { this.$operation = this.$resolveNegableString("to {not?} be null"); this.$objectation = undefined; if (this.$makeNegable(typeof this.$subject !== null)) return this.$asFailed(); return this.$ensurement.$asResolved(); } undefined() { this.$operation = this.$resolveNegableString("to {not?} be undefined"); this.$objectation = undefined; if (this.$makeNegable(typeof this.$subject !== "undefined")) return this.$asFailed(); return this.$ensurement.$asResolved(); } boolean() { this.$operation = this.$resolveNegableString("to {not?} be boolean"); this.$objectation = undefined; if (this.$makeNegable(typeof this.$subject !== "boolean")) return this.$asFailed(); return this.$ensurement.$asResolved(); } instanceOf(clazz) { this.$operation = this.$resolveNegableString("to {not?} be instanceOf"); this.$objectation = undefined; if (this.$makeNegable(!(this.$subject instanceof clazz))) return this.$asFailed(); return this.$ensurement.$asResolved(); } array() { this.$operation = this.$resolveNegableString("to {not?} be array"); this.$objectation = undefined; if (this.$makeNegable(!Array.isArray(this.$subject))) return this.$asFailed(); return this.$ensurement.$asResolved(); } function() { this.$operation = this.$resolveNegableString("to {not?} be function"); this.$objectation = undefined; if (this.$makeNegable(typeof (this.$subject) !== "function")) return this.$asFailed(); return this.$ensurement.$asResolved(); } empty() { this.$operation = this.$resolveNegableString("to {not?} be empty"); this.$objectation = undefined; const isEmpty = (() => { const s = this.$subject; if (Array.isArray(s)) { return s.length === 0; } else if (typeof s === "object") { return s === null || Object.keys(s).length === 0; } else if (typeof s === "string") { return s === ""; } else if (typeof s === "number") { return s === 0; } else if (typeof s === "boolean") { return s === false; } else { return true; } })(); if (this.$makeNegable(!isEmpty)) return this.$asFailed(); return this.$ensurement.$asResolved(); } equalTo(value) { this.$operation = this.$resolveNegableString("to {not?} be equal to"); this.$objectation = value; let isEqual = this.$subject === value; if (this.$makeNegable(!isEqual)) return this.$asFailed(); return this.$ensurement.$asResolved(); } equalOrGreaterThan(value) { this.$operation = this.$resolveNegableString("to {not?} be equal or greater than"); this.$objectation = value; let isGreaterOrEqual = this.$subject >= value; if (this.$makeNegable(!isGreaterOrEqual)) return this.$asFailed(); return this.$ensurement.$asResolved(); } greaterThan(value) { this.$operation = this.$resolveNegableString("to {not?} be greater than"); this.$objectation = value; let isGreater = this.$subject > value; if (this.$makeNegable(!isGreater)) return this.$asFailed(); return this.$ensurement.$asResolved(); } greaterOrEqualTo(...args) { return this.equalOrGreaterThan(...args); } equalOrLowerThan(value) { this.$operation = this.$resolveNegableString("to {not?} equal or lower than"); this.$objectation = value; let isGreaterOrEqual = this.$subject <= value; if (this.$makeNegable(!isGreaterOrEqual)) return this.$asFailed(); return this.$ensurement.$asResolved(); } lowerThan(value) { this.$operation = this.$resolveNegableString("to {not?} be lower than"); this.$objectation = value; let isGreater = this.$subject < value; if (this.$makeNegable(!isGreater)) return this.$asFailed(); return this.$ensurement.$asResolved(); } lowerOrEqualTo(value) { return this.equalOrLowerThan(...args); } oneOf(value) { this.$operation = this.$resolveNegableString("to {not?} be one of"); this.$objectation = value; if(!Array.isArray(value)) { throw new Error(`Required on «$ensure(...).to.be.oneOf(!)» to provide an array on «ToBeInterface.oneOf»`); } let isOne = this.$objectation.indexOf(this.$subject) !== -1; if (this.$makeNegable(!isOne)) return this.$asFailed(); return this.$ensurement.$asResolved(); } }; const ToNotBeInterface = class extends ToBeInterface { $isNegated = true; }; const ToHaveInterface = class extends BasicToBeInterface { text(prop) { this.$operation = this.$resolveNegableString("to {not?} have text"); this.$objectation = prop; const hasSubstring = this.$subject.indexOf(prop) !== -1; if (this.$makeNegable(!hasSubstring)) return this.$asFailed(); return this.$ensurement.$asResolved(); } key(prop) { this.$operation = this.$resolveNegableString("to {not?} have key"); this.$objectation = prop; const keys = Object.keys(this.$subject); const hasKey = keys.indexOf(prop) !== -1; if (this.$makeNegable(!hasKey)) return this.$asFailed(); return this.$ensurement.$asResolved(); } value(prop) { this.$operation = this.$resolveNegableString("to {not?} have value"); this.$objectation = prop; const values = Object.values(this.$subject); const hasValue = values.indexOf(prop) !== -1; if (this.$makeNegable(!hasValue)) return this.$asFailed(); return this.$ensurement.$asResolved(); } onlyPotentialKeys(props) { this.$operation = this.$resolveNegableString("to {not?} have only potential keys"); this.$objectation = props; const keys = Object.keys(this.$subject); let hasOnly = true; Iterating_props: for (let index = 0; index < keys.length; index++) { const key = keys[index]; if (props.indexOf(key) === -1) { hasOnly = false; break Iterating_props; } } if (this.$makeNegable(!hasOnly)) return this.$asFailed(); return this.$ensurement.$asResolved(); } keys(props) { this.$operation = this.$resolveNegableString("to {not?} have keys"); this.$objectation = props; const keys = Object.keys(this.$subject); let hasKeys = true; Iterating_props: for (let index = 0; index < props.length; index++) { const prop = props[index]; if (keys.indexOf(prop) === -1) { hasKeys = false; break Iterating_props; } } if (this.$makeNegable(!hasKeys)) return this.$asFailed(); return this.$ensurement.$asResolved(); } values(props) { this.$operation = this.$resolveNegableString("to {not?} have values"); this.$objectation = props; const values = Object.values(this.$subject); let hasValues = true; Iterating_props: for (let index = 0; index < props.length; index++) { const prop = props[index]; if (values.indexOf(prop) === -1) { hasValues = false; break Iterating_props; } } if (this.$makeNegable(!hasValues)) return this.$asFailed(); return this.$ensurement.$asResolved(); } uniquelyKeys(props) { this.$operation = this.$resolveNegableString("to {not?} have uniquelyKeys"); this.$objectation = props; const keys = Object.keys(this.$subject); let hasKeys = true; Iterating_props: for (let index = 0; index < keys.length; index++) { const key = keys[index]; if (props.indexOf(key) === -1) { hasKeys = false; break Iterating_props; } } if (this.$makeNegable(!hasKeys)) return this.$asFailed(); return this.$ensurement.$asResolved(); } uniquelyValues(props) { this.$operation = this.$resolveNegableString("to {not?} have uniquelyValues"); this.$objectation = props; const values = Object.values(this.$subject); let hasValues = true; Iterating_props: for (let index = 0; index < values.length; index++) { const value = values[index]; if (props.indexOf(value) === -1) { hasValues = false; break Iterating_props; } } if (this.$makeNegable(!hasValues)) return this.$asFailed(); return this.$ensurement.$asResolved(); } }; const ToNotHaveInterface = class extends ToHaveInterface { $isNegated = true; }; const EnsurementV1 = class extends Ensurement { selfExtend(obj) { return Object.assign(this, obj); } get $toNotBe() { return new ToNotBeInterface(this); } get $toNotHave() { return new ToNotHaveInterface(this); } get $toNot() { return { be: this.$toNotBe, have: this.$toNotHave, } } get $toBe() { return new ToBeInterface(this); } get $toHave() { return new ToHaveInterface(this); } get to() { return { be: this.$toBe, have: this.$toHave, not: this.$toNot, }; } its(id) { return this.constructor.create({ [id]: this.$subject[id] }, 1).selfExtend({ $parent: this, asBoolean: this.asBoolean, }); } getSubject() { return this.$subject; } safelyBack(levels = 1) { for (let index = 0; index < levels; index++) { try { parent = this.$parent; } catch (error) { // @OK. } } } back(levels = 1) { let parent = this; for (let index = 0; index < levels; index++) { try { parent = this.$parent; } catch (error) { throw new Error(`Ensurement could not go «back» reaching parent on level «${index}» on «ensure(...).back»`); } } return parent; } static $or(options) { let correctOption = undefined; const allIds = Object.keys(options); const orError = new Error(`could not ensure «or» group with options: «${allIds.join("», «")}»`); for(let index=0; index<allIds.length; index++) { const currentId = allIds[index]; const currentOptionCallback = options[currentId]; try { currentOptionCallback(); return currentId; } catch (error) { orError.appendError(error); } } throw orError.unified(); } static id(obj) { return this.create(obj,1); } static ensure(...args) { return this.create(...args); } static check(...args) { return this.create(...args).selfExtend({ asBoolean: true }); } static assert(condition, errorMessage = "Assertion error happened") { if (!condition) { throw new AssertionError(errorMessage); } return true; } static fails(callback, errorMessage = "Assertion error happened") { let passes = true; try { callback(); passes = false; } catch (error) { return true; } if (!passes) { throw new AssertionError(errorMessage); } } static AssertionError = AssertionError; }; Export_to_globals: { globalThis.$fails = EnsurementV1.fails.bind(EnsurementV1); globalThis.$ensure = EnsurementV1.ensure.bind(EnsurementV1); globalThis.$ensure.id = EnsurementV1.id.bind(EnsurementV1); globalThis.$ensure.check = EnsurementV1.check.bind(EnsurementV1); globalThis.$ensure.assert = EnsurementV1.assert.bind(EnsurementV1); globalThis.$ensure.fails = EnsurementV1.fails.bind(EnsurementV1); globalThis.$check = EnsurementV1.check.bind(EnsurementV1); globalThis.$assert = EnsurementV1.assert.bind(EnsurementV1); // globalThis.AssertionError = AssertionError; globalThis.$ensure.$or = EnsurementV1.$or; } return EnsurementV1;
globalThis.ErrorSummary = class { constructor(data) { Object.assign(this, data); } toString() { return JSON.stringify(this, null, 2); } } globalThis.Error = class AccumulableError extends Error { constructor(...args) { super(...args); this.$accumulatedErrors = []; } toString() { return JSON.stringify(this, null, 2); } toJSON() { const data = { name: this.name || "Error", message: this.message || "", stack: this.stack ? this.stack.split("\n at ") : "", ...this, }; if (this.$accumulatedErrors && this.$accumulatedErrors.length) { data.$accumulatedErrors = this.$accumulatedErrors; } return data; } unified() { this.message = this.message + "\n" + this.$accumulatedErrors.map((e, i) => (i + 1) + ': ' + e.name + ': ' + e.message).join("\n"); this.$accumulatedErrors = []; return this; } prependError(error) { this.$accumulatedErrors.unshift(error); return this; } appendError(error) { this.$accumulatedErrors.push(error); return this; } summarized() { let uniqueTraces = []; let commonTraces = []; // Recopilar las trazas de la pila de errores acumulados const allStacks = this.$accumulatedErrors.map( (error) => (error.stack ? error.stack.split("\n at ") : []) ); // Si no hay acumulados, no hay comunes ni únicos if (allStacks.length === 0) { return new ErrorSummary({ name: this.name, message: this.message, stack: this.stack ? this.stack.split("\n").map(line => line.split("@")) : [], uniqueTraces: uniqueTraces, commonTraces: commonTraces, }); } // Identificar trazas comunes const firstStack = allStacks[0]; for (let i = 0; i < firstStack.length; i++) { const trace = firstStack[i]; let isCommon = true; for (let j = 1; j < allStacks.length; j++) { if (!allStacks[j].includes(trace)) { isCommon = false; break; } } if (isCommon) { commonTraces.push(trace); } } // Identificar trazas únicas for (let i = 0; i < allStacks.length; i++) { const uniqueForStack = []; for (let j = 0; j < allStacks[i].length; j++) { const trace = allStacks[i][j]; if (!commonTraces.includes(trace)) { uniqueForStack.push(trace); } } uniqueTraces.push(uniqueForStack); } return new ErrorSummary({ ...this, name: this.name, message: this.message, stack: this.stack ? this.stack.split("\n at ") : [], uniqueTraces: uniqueTraces, commonTraces: commonTraces, }); } }
const LocalUtilities = class { }; const LswGoals = class { static async getGoalsReport(someDate = new Date()) { Vue.prototype.$trace("lsw-goals-viewer.methods.getGoalsReport"); let originalGoals = undefined; // @HISTORICAL: Previous_goals_setter: { break Previous_goals_setter; const parsedLinesPromise = Vue.prototype.$lsw.fs.evaluateAsDotenvListFileOrReturn("/kernel/settings/goals.env", []); const parsedLines = await parsedLinesPromise; // 1. Get original goals: const previousOriginalGoals = parsedLines.map(line => { const [concept, condition = "> 0", urgency = "0"] = line.split(/\|/g).map(item => { return item.trim(); }); return { line, concept, condition, urgency }; }); originalGoals = previousOriginalGoals; } // @CURRENT: Current_goals_setter: { originalGoals = await Vue.prototype.$lsw.fs.evaluateAsWeekFileOrReturn("/kernel/goals/goals.week", []); } // 2. Get today's completed actions: const todayCompletedActions = await this.getSomeDayActions(someDate, false, "completada"); const formattedGoals = []; const insertedGoals = []; // 3. Expand goals: Iterating_goals: for (let indexGoal = 0; indexGoal < originalGoals.length; indexGoal++) { try { const originalGoal = originalGoals[indexGoal]; const { type: goalType, concept, condition, urgency, from: goalBegin, to: goalEnd } = originalGoal; const isRequire = goalType === "REQ"; const isSet = goalType === "SET"; const datestring = LswTimer.utils.fromDateToDatestring(someDate); // Filtros de fecha: if (goalBegin !== "*") { if (datestring < goalBegin) { continue Iterating_goals; } } if (goalEnd !== "*") { if (datestring > goalEnd) { continue Iterating_goals; } } // Aplica cambios en cada caso: if (isSet) { // Solo si es en un rango de 20 días antes o después: const today = new Date(); const maxBefore = new Date(today); maxBefore.setDate(maxBefore.getDate() - 1); const maxLater = new Date(today); maxLater.setDate(maxLater.getDate() + 1); const isInRange = (someDate > maxBefore) && (someDate < maxLater); if(!isInRange) { continue Iterating_goals; } console.log(originalGoal); const duration = originalGoal.duration || "1h"; const hour = originalGoal.hour || "00"; const minute = originalGoal.minute || "00"; const weekday = originalGoal.weekday; const isAnyWeekday = weekday === "*"; const currentWeekday = this.fromDateToWeekday(someDate); if(!isAnyWeekday && (currentWeekday !== weekday)) { continue Iterating_goals; } const currDatestring = LswTimer.utils.fromDateToDatestring(someDate, true); const fullDatestring = `${currDatestring} ${hour}:${minute}`; const matchedAcciones = await Vue.prototype.$lsw.database.select("Accion", acc => { const sameConcept = acc.en_concepto === concept; const sameMinute = acc.tiene_inicio.startsWith(fullDatestring); return sameConcept && sameMinute; }); if(matchedAcciones.length === 1) { continue Iterating_goals; } No_haremos_el_insert_aqui: { const isToday = LswTimer.utils.areSameDayDates(today, someDate); if(!isToday) { break No_haremos_el_insert_aqui; } await Vue.prototype.$lsw.database.insert("Accion", { en_concepto: concept, tiene_estado: "pendiente", tiene_inicio: fullDatestring, tiene_duracion: duration, tiene_parametros: "[*goal]", tiene_resultados: "", tiene_comentarios: "", }); insertedGoals.push(`${concept}@${fullDatestring}`); } continue Iterating_goals; } else if (isRequire) { const formattedGoal = {}; const formattedConcept = this.formatConcept(concept); const formattedCondition = this.formatCondition(condition, formattedConcept); const formattedUrgency = this.formatUrgency(urgency); formattedGoal.concept = formattedConcept; Object.assign(formattedGoal, formattedCondition(todayCompletedActions)); formattedGoal.urgency = formattedUrgency; formattedGoals.push(formattedGoal); } } catch (error) { console.log(error); } } const sortedGoals = [].concat(formattedGoals).sort((g1, g2) => { try { const u1 = g1.urgency || 0; const u2 = g2.urgency || 0; const c1 = g1.filledAsint || 0; const c2 = g2.filledAsint || 0; const g1over = c1 > 100; const g2over = c2 > 100; if (g2over) return -1; if (g1over) return 1; if (u1 > u2) return -1; if (u1 < u2) return 1; if (c1 < c2) return -1; if (c1 > c2) return 1; return 0; } catch (error) { console.log(error); } }); if(insertedGoals.length) { Vue.prototype.$lsw.toasts.debug(insertedGoals); } return { goals: sortedGoals, actions: todayCompletedActions }; } static fromDateToWeekday(date) { const weekdayNum = date.getDay(); if(weekdayNum === 0) { return "dom"; } else if(weekdayNum === 1) { return "lun"; } else if(weekdayNum === 2) { return "mar"; } else if(weekdayNum === 3) { return "mie"; } else if(weekdayNum === 4) { return "jue"; } else if(weekdayNum === 5) { return "vie"; } else if(weekdayNum === 6) { return "sab"; } } static $appliesRange(rangeStart, rangeEnd, date) { Vue.prototype.$trace("lsw-goals-viewer.methods.$appliesRange"); if (rangeStart === "*") { if (rangeEnd === "*") { return true; } else { return rangeEnd >= date; } } else if (rangeStart <= date) { if (rangeEnd === "*") { return true; } else { return rangeEnd >= date; } } return true; } static formatConcept(txt) { return txt; } static formatCondition(originalTxt, concept) { const isMin = originalTxt.startsWith(">"); const op = originalTxt.trim().match(/(\<|\>)(=)?/g); const opCorrected = op.length === 1 ? op + "=" : op; const txt = originalTxt.replace(/(\<|\>)(=)?/g, "") const isTimes = this.isConditionByTimes(txt); const isDuration = this.isConditionByDuration(txt); if ((!isTimes) && (!isDuration)) { throw new Error(`Condition «${txt}» must be a number or a durationstring on «LswGoals.formatCondition»`); } const referenceValue = isTimes ? parseInt(txt) : LswTimer.utils.fromDurationstringToMilliseconds(txt); return completedActions => { const conclusion = {}; conclusion.type = isTimes ? "by times" : isDuration ? "by duration" : undefined; conclusion.expectedAs = isMin ? "minimum" : "maximum"; conclusion.expectedAsAbbr = isMin ? "min" : "max"; const matchedActions = []; Collect_matched_actions: for (let index = 0; index < completedActions.length; index++) { const acc = completedActions[index]; const sameConcept = acc.en_concepto === concept; const isCompleted = acc.tiene_estado === "completada"; // Redundante, pero bueno. if (sameConcept && isCompleted) { matchedActions.push(acc); } } Expand_data: { conclusion.currentTimes = matchedActions.length; conclusion.currentDurationInms = matchedActions.reduce((total, acc) => { try { total += LswTimer.utils.fromDurationstringToMilliseconds(acc.tiene_duracion || "0min"); } catch (error) { console.log(error); } return total; }, 0); conclusion.currentDuration = LswTimer.utils.fromMillisecondsToDurationstring(conclusion.currentDurationInms) || "0min"; } Apply_proper_filters: { if (isTimes) { const expectedTimes = referenceValue; conclusion.expectedTimes = expectedTimes; const evaluableSource = `${conclusion.currentTimes} ${opCorrected} ${expectedTimes}`; console.log("[*] Evaluating JavaScript for condition: ", evaluableSource); conclusion.filledAsint = Math.round(100 * (conclusion.currentTimes / conclusion.expectedTimes)); conclusion.filled = conclusion.filledAsint + "%"; Specific_for_time_cases: { conclusion.missingTimes = conclusion.expectedTimes - conclusion.currentTimes; } conclusion.missingAsint = 100 - conclusion.filledAsint; conclusion.missing = conclusion.missingAsint + "%"; conclusion.solved = window.eval(evaluableSource); conclusion.solvable = evaluableSource; } else if (isDuration) { const expectedDuration = referenceValue; conclusion.expectedDurationInms = expectedDuration; conclusion.expectedDuration = LswTimer.utils.fromMillisecondsToDurationstring(expectedDuration); const evaluableSource = `${conclusion.currentDurationInms} ${opCorrected} ${expectedDuration}`; console.log("[*] Evaluating JavaScript for condition: ", evaluableSource); conclusion.filledAsint = Math.round(100 * (conclusion.currentDurationInms / conclusion.expectedDurationInms)); conclusion.filled = conclusion.filledAsint + "%"; const missingDurationInms = LswUtils.zeroIfNegative(conclusion.expectedDurationInms - conclusion.currentDurationInms); Specific_for_duration_cases: { conclusion.missingDuration = LswTimer.utils.fromMillisecondsToDurationstring(missingDurationInms); conclusion.missingDurationInms = missingDurationInms; } conclusion.missingAsint = 100 - conclusion.filledAsint; conclusion.missing = conclusion.missingAsint + "%"; conclusion.solved = window.eval(evaluableSource); conclusion.solvable = evaluableSource; } } this.expandColor(conclusion); return conclusion; }; } static COLOR_GAMA_1 = { SUSPENSO: "red", INSUFICIENTE: "#e87489", SUFICIENTE: "#5353bf", NOTABLE: "orange", EXCELENTE: "yellow", SOBRESALIENTE: "lime", }; static COLOR_GAMA_2 = { SUSPENSO: "#D32F2F", INSUFICIENTE: "#F57C00", SUFICIENTE: "#FBC02D", NOTABLE: "#C0CA33", EXCELENTE: "#7CB342", SOBRESALIENTE: "#388E3C", }; static COLOR_GAMA_3 = { SUSPENSO: "#c62828", INSUFICIENTE: "#ef6c00", SUFICIENTE: "#ffe300", NOTABLE: "#29b6f6", EXCELENTE: "#00897b", SOBRESALIENTE: "#66bb6a", }; static COLOR = this.COLOR_GAMA_3; static COLOR_MEANING = { [this.COLOR.SUSPENSO]: "SUSPENSO", [this.COLOR.INSUFICIENTE]: "INSUFICIENTE", [this.COLOR.SUFICIENTE]: "SUFICIENTE", [this.COLOR.NOTABLE]: "NOTABLE", [this.COLOR.EXCELENTE]: "EXCELENTE", [this.COLOR.SOBRESALIENTE]: "SOBRESALIENTE", }; /* static COLOR_MEANING = { "red": "SUSPENSO", "#e87489": "INSUFICIENTE", "#5353bf": "SUFICIENTE", "orange": "NOTABLE", "yellow": "EXCELENTE", "lime": "SOBRESALIENTE", }; //*/ static expandColor(_) { const percentage = _.filledAsint; const asMin = _.expectedAs === "minimum" ? true : false; const assignedColor = (() => { if (percentage <= 0) { return asMin ? this.COLOR.SUSPENSO : this.COLOR.SOBRESALIENTE; } else if (percentage < 20) { return asMin ? this.COLOR.INSUFICIENTE : this.COLOR.EXCELENTE; } else if (percentage < 40) { return asMin ? this.COLOR.SUFICIENTE : this.COLOR.NOTABLE; } else if (percentage < 60) { return asMin ? this.COLOR.NOTABLE : this.COLOR.SUFICIENTE; } else if (percentage < 80) { return asMin ? this.COLOR.EXCELENTE : this.COLOR.INSUFICIENTE; } else if (percentage > 80) { return asMin ? this.COLOR.SOBRESALIENTE : this.COLOR.SUSPENSO; } else { } })(); Object.assign(_, { color: assignedColor, colorMeaning: this.COLOR_MEANING[assignedColor], }); } static formatUrgency(txt) { return parseFloat(txt.replace(/\!/g, "").trim()); } static isConditionByTimes(txtVal) { return txtVal.trim().match(/^[0-9]+(\.[0-9]+)?$/g); } static isConditionByDuration(txtVal) { try { return LswTimer.utils.fromDurationstringToMilliseconds(txtVal); return true; } catch (error) { return false; } } static getSomeDayActions(dateToday = new Date(), concept = false, state = false) { Vue.prototype.$trace("lsw-goals-viewer.methods.getSomeDayActions"); return Vue.prototype.$lsw.database.selectMany("Accion", acc => { const dateInicio = LswTimer.utils.fromDatestringToDate(acc.tiene_inicio); const isSameDay = LswTimer.utils.areSameDayDates(dateInicio, dateToday); if (!isSameDay) { return false; } if (concept) { const isSameConcept = acc.en_concepto === concept; if (!isSameConcept) { return false; } } if (state) { const isSameState = acc.tiene_estado === state; if (!isSameState) { return false; } } return true; }); } }; return LswGoals;
const GoalFactory = class { static validate(input) { Vue.prototype.$trace("Lsw.GoalFactory.getTodayActions"); } } const LswGoals = class { static GoalFactory = GoalFactory; static validateConcepto(val) { Vue.prototype.$trace("LswGoals.validateConcepto"); return val; } static validateMedida(val) { Vue.prototype.$trace("LswGoals.validateMedida"); try { const isMax = val.startsWith("<"); const isMin = val.startsWith(">"); const isLimit = isMax || isMin; const innerVal = val.replace(/>|</g, "").trim(); const isNumber = (() => { const numberCast = parseFloat(innerVal); return !isNaN(numberCast); })(); const comparator = isMax ? "max" : "min"; if (isNumber) { return [`${comparator}TimesToday`, parseFloat(innerVal)]; } else { Validate_string_or_fail: { LswTimer.utils.fromDurationstringToMilliseconds(innerVal); } return [`${comparator}DurationToday`, innerVal]; } return val; } catch (error) { console.log(error); throw error; } } static validateUrgencia(val) { Vue.prototype.$trace("LswGoals.validateUrgencia"); $ensure({ val }, 1).type("string").can(it => it.trim().startsWith("!")); return parseInt(val.replace(/\!/g, "").trim()); } static async loadGoals() { Vue.prototype.$trace("LswGoals.loadGoals"); const output = []; const errors = []; console.log("[*] Carga de objetivos por factories:"); Carga_por_factories: { try { const factoryIds = await Vue.prototype.$lsw.fs.scan_directory("/kernel/settings/goals/factory"); for (let indexFactory = 0; indexFactory < factoryIds.length; indexFactory++) { const factoryFilename = factoryIds[indexFactory]; try { console.log("[*] Carga de objetivos por factory file:", factoryFilename); const factoryDefinition = await Vue.prototype.$lsw.fs.evaluateAsJavascriptFile(`/kernel/settings/goals/factory/${factoryFilename}`); GoalFactory.validate(factoryDefinition); output.push(factoryDefinition); } catch (error) { console.log("[!] Error cargando objetivo por fichero único en:", factoryFilename); errors.push(error); } } } catch (error) { console.log("[!] Error leyendo factories de objetivos:", error); } } Carga_por_el_fichero_unico: { console.log("[*] Carga de objetivos por fichero único:"); const goalsByFileCrude = await Vue.prototype.$lsw.fs.evaluateAsDotenvFileOrReturn("/kernel/settings/goals.env", {}); const goalFiles = Object.keys(goalsByFileCrude); Iterando_reglas_de_fichero: for (let indexGoal = 0; indexGoal < goalFiles.length; indexGoal++) { const key = goalFiles[indexGoal]; const val = goalsByFileCrude[key]; try { console.log("[*] Carga de objetivos por clave de fichero único:", key, val); const tokens = val.split(/ *\| */g); const [concepto, medida, urgencia] = tokens; let conceptoSan = concepto; let medidaSan = undefined; let urgenciaSan = 0; conceptoSan = this.validateConcepto(concepto); medidaSan = this.validateMedida(medida); urgenciaSan = this.validateUrgencia(urgencia); const [metodoParaMedir, medidaValor] = medidaSan; console.log(conceptoSan, metodoParaMedir, medidaValor, urgenciaSan); const accionesCoincidentesCompletadas = await this.getTodayActions(conceptoSan, "completada"); let porcentaje = undefined; Calcular_porcentaje_actual: { if(metodoParaMedir.endsWith("TimesToday")) { const totalTimes = accionesCoincidentesCompletadas.length; porcentaje = Math.round((totalTimes / medidaValor) * 100); console.log("CALC:", totalTimes, medidaValor, porcentaje); } else if(metodoParaMedir.endsWith("DurationToday")) { const totalDurationMs = LswGoals.extractDurationFromActions(accionesCoincidentesCompletadas, true); porcentaje = Math.round((totalDurationMs / medidaValor) * 100); console.log("CALC:", totalDurationMs, medidaValor, porcentaje); } } output.push({ id: conceptoSan, urgencia: urgenciaSan, porcentaje, nombre: key, }); } catch (error) { console.log("[!] Error cargando objetivo por fichero único en:", key); errors.push(error); } } console.log("output:", output); } if (errors.length) { console.error("[!] Errores al cargar objetivos", errors); Vue.prototype.$lsw.toasts.send({ title: "Hubo errores en la carga de objetivos", text: `Los siguientes ${errors.length} se dieron al cargar los objetivos`, }); } return output; } static async getTodayActions(onlyConcept = false, onlyState = false) { Vue.prototype.$trace("LswGoals.getTodayActions"); const errores = LswErrorHandler.createGroup(); const allAcciones = await Vue.prototype.$lsw.database.selectMany("Accion"); const todayDate = new Date(); let todayAcciones = []; Iterando_acciones: for (let indexAccion = 0; indexAccion < allAcciones.length; indexAccion++) { const accion = allAcciones[indexAccion]; if (!accion.tiene_inicio) { continue Iterando_acciones; } try { const accionDate = LswTimer.utils.fromDatestringToDate(accion.tiene_inicio); const sameDate = LswTimer.utils.areSameDayDates(todayDate, accionDate); if (sameDate) { todayAcciones.push(accion); } } catch (error) { errores.push(error); } } errores.selfThrowIfNeeded(); if (onlyConcept) { todayAcciones = todayAcciones.filter(acc => acc.en_concepto === onlyConcept); } if(onlyState) { todayAcciones = todayAcciones.filter(acc => acc.tiene_estado === onlyState); } return todayAcciones; } static async filterActionsByConcept(actions, conceptId) { Vue.prototype.$trace("LswGoals.filterActionsByConcept"); const matchedActions = []; for (let indexAction = 0; indexAction < actions.length; indexAction++) { const action = actions[indexAction]; const isMatch = action.en_concepto === conceptId; if (isMatch) { matchedActions.push(action); } } return matchedActions; } static async filterActionsByState(actions, stateId) { Vue.prototype.$trace("LswGoals.filterActionsByState"); const matchedActions = []; for (let indexAction = 0; indexAction < actions.length; indexAction++) { const action = actions[indexAction]; const isMatch = action.tiene_estado === stateId; if (isMatch) { matchedActions.push(action); } } return matchedActions; } static async ensureActionHasLimitedTimesToday(actionId, times, minOrMax = "min", options) { Vue.prototype.$trace("LswGoals.ensureActionHasLimitedTimesToday"); try { const mensajeExplicativo = `${actionId} ${minOrMax === 'min' ? 'mínimo' : 'máximo'} ${times} veces hoy`; const { completado: completedMessage = `Sí está: ${mensajeExplicativo}`, fallido: notYetMessage = `Aún no está: ${mensajeExplicativo}`, } = options; const salida = input => Object.assign({ id: mensajeExplicativo, urgencia: options.urgencia || 0, }, input); const todayActions = await LswGoals.getTodayActions(); const matchedActions = await LswGoals.filterActionsByConcept(todayActions, actionId); const completedActions = await LswGoals.filterActionsByState(matchedActions, "completada"); const isValid = (minOrMax === "min") ? completedActions.length < times : completedActions.length > times; if (isValid) { return salida({ completadas: completedActions.length, mensaje: notYetMessage, porcentaje: Math.round((completedActions.length / times) * 100), }); } else { return salida({ mensaje: completedMessage, porcentaje: Math.round((completedActions.length / times) * 100), }); } } catch (error) { console.error(error); } } static ensureActionHasMinimumTimesToday(actionId, minimumTimes, options) { return this.ensureActionHasLimitedTimesToday(actionId, minimumTimes, "min", options); } static ensureActionHasMaximumTimesToday(actionId, maximumTimes, options) { return this.ensureActionHasLimitedTimesToday(actionId, maximumTimes, "max", options); } static async ensureActionHasLimitedDurationToday(actionId, durationDatestring, minOrMax = "min", options = {}) { Vue.prototype.$trace("LswGoals.ensureActionHasLimitedDurationToday"); try { const mensajeExplicativo = `${actionId} ${minOrMax === 'min' ? 'mínimo' : 'máximo'} ${durationDatestring} hoy`; const minimumDurationMs = LswTimer.utils.fromDurationstringToMilliseconds(durationDatestring); const { completado: completedMessage = `Sí está: ${mensajeExplicativo}`, fallido: notYetMessage = `Aún no está: ${mensajeExplicativo}`, } = options; const salida = input => Object.assign({ id: mensajeExplicativo, urgencia: options.urgencia || 0, }, input); const todayActions = await LswGoals.getTodayActions(); const matchedActions = await LswGoals.filterActionsByConcept(todayActions, actionId); const completedActions = await LswGoals.filterActionsByState(matchedActions, "completada"); let currentDurationMs = 0; for (let indexActions = 0; indexActions < completedActions.length; indexActions++) { const action = completedActions[indexActions]; try { const actionDurationMs = LswTimer.utils.fromDurationstringToMilliseconds(action.tiene_duracion); currentDurationMs += actionDurationMs; } catch (error) { // @BADLUCK. } } const currentTotal = LswTimer.utils.fromMillisecondsToDurationstring(currentDurationMs); const isValid = (minOrMax === "min") ? (currentDurationMs < minimumDurationMs) : (currentDurationMs > minimumDurationMs); if (isValid) { return salida({ completadas: currentTotal, mensaje: notYetMessage, porcentaje: Math.round((currentDurationMs / minimumDurationMs) * 100), }); } else { return salida({ completadas: currentTotal, mensaje: completedMessage, porcentaje: Math.round((currentDurationMs / minimumDurationMs) * 100), }); } } catch (error) { console.error(error); } } static ensureActionHasMinimumDurationToday(actionId, durationDatestring, options = {}) { return this.ensureActionHasLimitedDurationToday(actionId, durationDatestring, "min", options); } static ensureActionHasMaximumDurationToday(actionId, durationDatestring, options = {}) { return this.ensureActionHasLimitedDurationToday(actionId, durationDatestring, "max", options); } static minTimesToday(action, times, urgencia = 0) { return this.ensureActionHasLimitedTimesToday(action, times, "min", { id: action, urgencia: urgencia, }); } static maxTimesToday(action, times, urgencia = 0) { return this.ensureActionHasLimitedTimesToday(action, times, "max", { id: action, urgencia: urgencia, }); } static minDurationToday(action, duration, urgencia = 0) { return this.ensureActionHasLimitedDurationToday(action, duration, "min", { id: action, urgencia: urgencia, }); } static maxDurationToday(action, duration, urgencia = 0) { return this.ensureActionHasLimitedDurationToday(action, duration, "max", { id: action, urgencia: urgencia, }); } static extractDurationFromActions(actions, inMilliseconds = false) { let totalMs = 0; for (let index = 0; index < actions.length; index++) { const action = actions[index]; try { const ms = LswTimer.utils.fromDurationstringToMilliseconds(action.tiene_duracion); totalMs += ms; } catch (error) { // @BADLUCK } } if (inMilliseconds) { return totalMs; } return LswTimer.utils.fromMillisecondsToDurationstring(totalMs); } }; return LswGoals;
const IntruderJob = class { constructor(options = {}) { Object.assign(this, options); } }; const LswIntruder = class { static create(...args) { return new this(...args); } static defaultOptions = { trace: true, }; $trace(method, args) { // @INJECTION: from LSW const traceActivatedGlobally = (typeof Vue === "undefined") || (typeof Vue.prototype.$lsw === "undefined") || ((typeof Vue !== "undefined") && (typeof Vue.prototype.$lsw !== "undefined") && (Vue.prototype.$lsw.logger.$options.active)); if(this.$options.trace && traceActivatedGlobally) { console.log("[trace][lsw-intruder] " + method, Array.from(args)); } } constructor(options = {}) { this.$jobs = {}; this.$options = Object.assign({}, this.constructor.defaultOptions, options); } addJob(options = {}) { this.$trace("addJob", []); const { id = LswRandomizer.getRandomString(7), timeout = 0, dialog = false } = options; $ensure({id},1).type("string"); $ensure({timeout},1).type("number"); $ensure({dialog},1).type("object").to.have.keys(["title", "template"]); const startDate = new Date(); const timeoutDate = new Date(startDate.getTime() + (timeout)); this.$jobs[id] = { id, timeout, dialog, state: "created", createdAt: startDate, firedAt: timeoutDate, }; this.$jobs[id].timeoutId = setTimeout(() => { this.startJob(id); }, timeout); return this.$jobs[id]; } removeJob(id) { this.$trace("removeJob", []); if(!(id in this.$jobs)) { return false; } clearTimeout(this.$jobs[id].timeout); delete this.$jobs[id]; } startJob(id, parameters = []) { this.$trace("startJob", []); const job = this.$jobs[id]; const isStarted = job.state === "started"; if(isStarted) { throw new Error(`Job «${id}» is already started`); } if(!Vue.prototype.$lsw) { throw new Error(`Job «${id}» could not find lsw api from Vue.prototype.$lsw`); } return Vue.prototype.$lsw.dialogs.open(job.dialog).finally(() => { this.removeJob(id); }); } } return LswIntruder;
const $defaultScope = {}; const UnsolvedLazyLoadModule = class { static create(...args) { return new this(...args); } }; const LswLazyLoader = class { static UnsolvedLazyLoadModule = UnsolvedLazyLoadModule; static global = new this(); constructor() { this.$loads = {}; this.$alias = {}; } register(options = {}) { const url = options.url; this.$loads[url] = Object.assign({}, { alias: false, scope: $defaultScope, getter: options.getter || Vue.prototype.$noop, confirmer: options.confirmer || Vue.prototype.$noop, confirmation: true, once: false, onceDone: false, }, options); if (this.$loads[url].alias) { this.$alias[this.$loads[url].alias] = url; } } $solveAlias(urlOrAlias) { const hasAlias = urlOrAlias in this.$alias; if (hasAlias) { return this.$alias[urlOrAlias]; } return urlOrAlias; } $softRegister(url, options = {}) { if (!this.hasLoaded(url)) { this.register(url, options); } } hasLoaded(url) { return url in this.$loads; } $loadLocally(url) { const options = this.$loads[url] || {}; const _getter = options.getter || Vue.prototype.$noop; const _confirmer = options.confirmer || Vue.prototype.$noop; const _confirmation = options.confirmation || Vue.prototype.$noop; const _once = options.once || false; const _onceDone = options.onceDone || false; const currentGetterValue = (() => { try { return _getter(); } catch (error) { return undefined; } })(); Kick_by_getter: { const hasGetterOk = typeof currentGetterValue !== "undefined"; if (hasGetterOk) { return currentGetterValue; } } Kick_by_confirmer: { const currentConfirmerValue = _confirmer(); const hasConfirmerOk = currentConfirmerValue === true; if (hasConfirmerOk) { return _confirmation; } } Kick_by_once_flag: { if(_once) { if(_onceDone) { return; } } } this.$loads[url].onceDone = true; return this.constructor.UnsolvedLazyLoadModule.create(url); } loadScriptAsync(url) { const value = this.$loadLocally(url); if (!(value instanceof this.constructor.UnsolvedLazyLoadModule)) { return value; } this.$softRegister(url, {}); const options = this.$loads[url]; const _scope = options.scope || $defaultScope; return importer.scriptAsync(url, _scope); } loadScriptSrc(url) { const value = this.$loadLocally(url); if (!(value instanceof this.constructor.UnsolvedLazyLoadModule)) { return value; } this.$softRegister(url, {}); const options = this.$loads[url]; const _scope = options.scope || $defaultScope; return importer.scriptSrc(url, _scope); } loadScriptSrcModule(url) { const value = this.$loadLocally(url); if (!(value instanceof this.constructor.UnsolvedLazyLoadModule)) { return value; } this.$softRegister(url, {}); const options = this.$loads[url]; const _scope = options.scope || $defaultScope; return importer.scriptSrcModule(url, _scope); } loadLinkStylesheet(url) { const value = this.$loadLocally(url); if (!(value instanceof this.constructor.UnsolvedLazyLoadModule)) { return value; } this.$softRegister(url, {}); const options = this.$loads[url]; const _scope = options.scope || $defaultScope; return importer.linkStylesheet(url); } load(aliasOrUrl) { const url = this.$solveAlias(aliasOrUrl); const value = this.$loadLocally(url); if (!(value instanceof this.constructor.UnsolvedLazyLoadModule)) { return value; } this.$softRegister(url, {}); const options = this.$loads[url]; const _scope = options.scope || $defaultScope; const _type = options.type || "scriptSrc"; if (!(_type in importer)) { throw new Error(`Required «type» from «${url}» options to be a known method for $importer on «LswLazyLoader.load»`); } return importer[_type](url, _scope); } }; return LswLazyLoader;
LswLazyLoader.global.register({ alias: "highlight.js:global", url: "assets/lib/highlight/highlight.js", confirmer: () => typeof hljs !== "undefined", getter: () => hljs, type: "scriptSrc", }); LswLazyLoader.global.register({ alias: "highlight.js:themes:default", url: "assets/lib/highlight/styles/default.min.css", type: "linkStylesheet", once: true, }); LswLazyLoader.global.register({ alias: "highlight.js:themes:monokai", url: "assets/lib/highlight/styles/monokai.min.css", type: "linkStylesheet", once: true, }); LswLazyLoader.global.register({ alias: "pegjs", url: "assets/lib/pegjs/pegjs.js", type: "scriptSrc", once: true, confirmer: () => typeof pegjs !== "undefined", }); LswLazyLoader.global.register({ alias: "beautifier", url: "assets/lib/beautifier/beautifier.js", type: "scriptSrc", once: true, confirmer: () => typeof beautifier !== "undefined", }); LswLazyLoader.global.register({ alias: "babel", url: "assets/lib/babel/babel.js", type: "scriptSrc", once: true, confirmer: () => typeof Babel !== "undefined", }); LswLazyLoader.global.register({ alias: "eruda", url: "assets/lib/eruda/eruda.js", type: "scriptSrc", once: true, confirmer: () => typeof eruda !== "undefined", }); LswLazyLoader.global.register({ alias: "jmespath", url: "assets/lib/jmespath/jmespath.min.js", type: "scriptSrc", once: true, confirmer: () => typeof jmespath !== "undefined", }); LswLazyLoader.global.register({ alias: "jquery", url: "assets/lib/jquery/jquery-v3.7.1.js", type: "scriptSrc", once: true, confirmer: () => typeof jQuery !== "undefined", }); LswLazyLoader.global.register({ alias: "qunit.js", url: "assets/lib/qunit/qunit.js", type: "scriptSrc", once: true, confirmer: () => typeof QUnit !== "undefined", }); LswLazyLoader.global.register({ alias: "qunit.css", url: "assets/lib/qunit/qunit.css", type: "linkStylesheet", once: true, confirmer: () => typeof QUnit !== "undefined", }); LswLazyLoader.global.register({ alias: "ejs", url: "assets/lib/ejs/ejs.js", type: "scriptSrc", once: true, confirmer: () => typeof ejs !== "undefined", }); LswLazyLoader.global.register({ alias: "sqlite3", url: "assets/lib/sqlite/sqlite3.js", type: "scriptSrc", once: true, confirmer: () => typeof sqlite3InitModule !== "undefined", }); LswLazyLoader.global.register({ alias: "lsw-sqlite", url: "assets/lib/lsw-sqlite/lsw-sqlite.js", type: "scriptAsync", once: true, confirmer: () => typeof LswSqlite !== "undefined", }); class LswLazyLoads { static loadHighlightJs() { return Promise.all([ LswLazyLoader.global.load("highlight.js:global"), // LswLazyLoader.global.load("highlight.js:themes:default"), LswLazyLoader.global.load("highlight.js:themes:monokai"), ]).then(() => { hljs.highlightAll(); }); } static loadPegjs() { return LswLazyLoader.global.load("pegjs"); } static loadBeautifier() { return LswLazyLoader.global.load("beautifier"); } static loadHtml2Pdf() { return LswLazyLoader.global.load("html2pdf"); } static loadBabel() { return LswLazyLoader.global.load("babel"); } static loadEruda() { return LswLazyLoader.global.load("eruda"); } static loadJmespath() { return LswLazyLoader.global.load("jmespath"); } static loadJquery() { return LswLazyLoader.global.load("jquery"); } static loadQunit() { return Promise.all([ LswLazyLoader.global.load("jquery"), LswLazyLoader.global.load("qunit.css"), LswLazyLoader.global.load("qunit.js"), ]); } static loadEjs() { return LswLazyLoader.global.load("ejs"); } static async loadSqlite() { if(!("sqlite3" in window)) { await LswLazyLoader.global.load("sqlite3"); const sqlite3 = await sqlite3InitModule(); Export_globally_sqlite3: { window.sqlite3 = sqlite3; } } if(!("LswSqlite" in window)) { await LswLazyLoader.global.load("lsw-sqlite"); } return LswSqlite; } }; return LswLazyLoads;
const cycle = LswCycler.from({ steps: [ "onStarted", "onInitialize", "onInitialized", "onBoot", "onBooted", "onLoadSchema", "onSchemaLoaded", "onLoadDatabase", "onDatabaseLoaded", "onLoadComponents", "onComponentsLoaded", "onLoadCordovaSupport", "onCordovaSupportLoaded", "onLoadModules", "onModulesLoaded", "onInstallModules", "onModulesInstalled", "onLoadApplication", "onApplicationLoaded", "onAllLoaded", "onRunApplication", "onApplicationDeployed", "onFinished", ], hooks: TriggersClass.create(), $trace: function(method, args) { if(Vue.prototype.$trace) { Vue.prototype.$trace("lsw-app-lifecycle." + method, args); } }, onStarted: function () { this.$trace("onStarted", []); return this.hooks.emit("app:started"); }, onInitialize: function () { this.$trace("onInitialize", []); return this.hooks.emit("app:initialize"); }, onInitialized: function () { this.$trace("onInitialized", []); return this.hooks.emit("app:initialized"); }, onBoot: function () { this.$trace("onBoot", []); return this.hooks.emit("app:boot"); }, onBooted: function () { this.$trace("onBooted", []); return this.hooks.emit("app:booted"); }, onLoadModules: function () { this.$trace("onLoadModules", []); if (!Vue.options.components.App) { throw new Error("Required Vue.js (v2) component «App» to be defined on «LswLifecycle.onRunApplication» for hook «app:run_application»"); } return this.hooks.emit("app:load_modules"); }, onModulesLoaded: function () { this.$trace("onModulesLoaded", []); return this.hooks.emit("app:modules_loaded"); }, onInstallModules: function () { this.$trace("onInstallModules", []); return this.hooks.emit("app:install_modules"); }, onModulesInstalled: function () { this.$trace("onModulesInstalled", []); return this.hooks.emit("app:modules_installed"); }, onLoadSchema: async function () { this.$trace("onLoadSchema", []); let hasNeededTables = false; Check_if_has_needed_tables: { try { const currentSchema = await LswDatabase.getSchema("lsw_default_database"); const neededTables = [ "Accion", "Automensaje", "Categoria_de_concepto", "Concepto", "Impresion_de_concepto", "Limitador", "Nota", "Propagador_de_concepto", "Propagador_prototipo", "Lista", "Recordatorio", ]; Iterating_needed_tables: { const currentTables = Object.keys(currentSchema); for(let index=0; index<neededTables.length; index++) { const neededTable = neededTables[index]; const containsTable = currentTables.indexOf(neededTable) !== -1; if(!containsTable) { hasNeededTables = false; break Iterating_needed_tables; } } Confirm_it_contains_tables: { hasNeededTables = true; } } } catch (error) { // @OK console.log("Error:", error); } } if (!hasNeededTables) { await LswDatabase.deleteDatabase("lsw_default_database"); } $lswSchema.loadSchemaByProxies("SchemaEntity"); const databaseSchema = await $lswSchema.getDatabaseSchemaForLsw(); console.log("[*] Creating database from schema by proxies:", "\n - " + Object.keys(databaseSchema).join("\n - ")); await LswDatabase.createDatabase("lsw_default_database", databaseSchema); return await this.hooks.emit("app:load_schema"); }, onSchemaLoaded: function () { this.$trace("onSchemaLoaded", []); return this.hooks.emit("app:schema_loaded"); }, onSeedDatabase: async function () { this.$trace("onSeedDatabase", []); Fill_with_your_own_requirements: { // @TOFILLIFNEEDED: } return await this.hooks.emit("app:seed_database"); }, onDatabaseSeeded: async function () { this.$trace("onDatabaseSeeded", []); Fill_with_your_own_requirements: { // @TOFILLIFNEEDED: } return await this.hooks.emit("app:database_seeded"); }, onLoadDatabase: async function () { this.$trace("onLoadDatabase", []); Load_database_connection: { Vue.prototype.$lsw.database = await LswDatabase.open("lsw_default_database"); Vue.prototype.$lsw.database.setInnerSchema($lswSchema); } let hasNeededRows = false; if(!hasNeededRows) { await this.onSeedDatabase(); await this.onDatabaseSeeded(); } return await this.hooks.emit("app:load_database"); }, onDatabaseLoaded: function () { this.$trace("onDatabaseLoaded", []); return this.hooks.emit("app:database_loaded"); }, onLoadComponents: async function () { this.$trace("onLoadComponents", []); Load_components: { const allComponents = await Vue.prototype.$lsw.fs.read_directory("/kernel/components"); const errores = []; for(let componentId in allComponents) { try { await Vue.prototype.$lsw.fs.import_as_component(`/kernel/components/${componentId}/${componentId}`); } catch (error) { errores.push(error); } } if(errores.length) { console.log("[!] Errores en onLoadComponents:"); console.log(errores); } } return this.hooks.emit("app:load_components"); }, onComponentsLoaded: function () { this.$trace("onComponentsLoaded", []); return this.hooks.emit("app:components_loaded"); }, onLoadCordovaSupport: async function() { this.$trace("onLoadCordovaSupport", []); Try_to_download_cordova: { await importer.scriptSrc("cordova.js").then(() => { console.log("[*] Cordova support loaded"); this.hooks.register("app:application_mounted", "cordova_loaded:org.allnulled.lsw.mobile", function() { try { Vue.prototype.$lsw.toasts.send({ title: "Cordova was enabled", text: "You can access Cordova APIs" }); } catch (error) { console.error(error); } }); return true; }).catch(error => { console.error(error); console.log("[!] Support for Cordova was dismissed"); this.hooks.register("app:application_mounted", "cordova_loaded:org.allnulled.lsw.mobile", function() { try { Vue.prototype.$lsw.toasts.send({ title: "Cordova was not enabled", text: "Cordova APIs are not accessible" }); } catch (error) { console.error(error); } }); return false; }); } return await this.hooks.emit("app:load_cordova_support"); }, onCordovaSupportLoaded: async function() { this.$trace("onCordovaSupportLoaded", []); return await this.hooks.emit("app:cordova_support_loaded"); }, onLoadApplication: function () { this.$trace("onLoadApplication", []); return this.hooks.emit("app:load_application"); }, onApplicationLoaded: function () { this.$trace("onApplicationLoaded", []); return this.hooks.emit("app:application_loaded"); }, onAllLoaded: function () { this.$trace("onAllLoaded", []); return this.hooks.emit("app:all_loaded"); }, onRunApplication: function() { this.$trace("onRunApplication", []); if(!Vue.options.components.App) { throw new Error("Required Vue.js (v2) component «App» to be defined on «LswLifecycle.onRunApplication» for hook «app:run_application»"); } const vueInstance = new Vue({ render: h => h(Vue.options.components.App), }).$mount("#app"); return this.hooks.emit("app:run_application"); }, onApplicationDeployed: function () { this.$trace("onApplicationDeployed", []); return this.hooks.emit("app:application_deployed"); }, onFinished: function () { this.$trace("onFinished", []); return this.hooks.emit("app:finished"); }, loadModule: function (moduleId) { this.$trace("loadModule", []); return Vue.prototype.$lsw.importer.scriptAsync(`modules/${moduleId}/load.js`); }, loadSubmodule: function (moduleId, subpath) { this.$trace("loadSubmodule", []); return Vue.prototype.$lsw.importer.scriptAsync(`modules/${moduleId}/${subpath}`); }, onApplicationMounted: function() { this.$trace("onApplicationMounted", []); return this.hooks.emit("app:application_mounted"); }, start: function () { this.$trace("start", []); return this.run(this.steps); }, }, "*");
const LswSchema = class { $trace(methodId, argsList) { if(this.$options && this.$options.trace) { console.log("[trace][lsw-schema][" + methodId + "] " + argsList.length); } } static Signature = LswSchemaSignature; static createSignature(creatorCallback, creatorParameters, creatorScope) { return this.Signature.create(creatorCallback, creatorParameters, creatorScope); } onValidateSchema(schema) { this.$trace("onValidateSchema", arguments); // @OVERRIDABLE } onValidateTable(id, definition, schema) { this.$trace("onValidateTable", arguments); // @OVERRIDABLE } onValidateColumn(id, definition, tableId, schema) { this.$trace("onValidateColumn", arguments); // @OVERRIDABLE } onFusionateSchema(schema) { this.$trace("onFusionateSchema", arguments); // @OVERRIDABLE } onFusionateTable(table, tableId, schema) { this.$trace("onFusionateTable", arguments); // @OVERRIDABLE } onFusionateColumn(column, columnId, tableId, schema) { this.$trace("onFusionateColumn", arguments); // @OVERRIDABLE } static create(...args) { return new this(...args); } constructor(options = {}) { this.$cache = { schemaForLsw: null }; this.$schema = { hasTables: { } }; this.$options = options; } getDatabaseSchemaForLsw(refresh = false) { this.$trace("getDatabaseSchemaForLsw", arguments); if(refresh) { this.$cache.schemaForLsw = null; } if(this.$cache.schemaForLsw) { return this.$cache.schemaForLsw; } const schemaForLsw = {}; for(let tableId in this.$schema.hasTables) { const tableData = this.$schema.hasTables[tableId]; let tableSchema = []; for(let columnId in tableData.hasColumns) { const columnData = tableData.hasColumns[columnId]; const prefix = columnData.isUnique ? "!" : ""; tableSchema.push(prefix + columnId); } schemaForLsw[tableId] = tableSchema; } this.$cache.schemaForLsw = schemaForLsw; return schemaForLsw; } loadSchemaByProxies(aspectId = "SchemaEntity") { this.$trace("loadSchemaByProxies", arguments); const schema = this.getSchemaByProxies(aspectId); return this.registerSchema(schema); } getSchemaByProxies(aspectId = "SchemaEntity") { this.$trace("getSchemaByProxies", arguments); const allSchemaEntities = Object.values($proxifier.$definitions).filter(d => d[aspectId]).map(d => d[aspectId]); const schemaTemp = new LswSchema(); for(let index=0; index<allSchemaEntities.length; index++) { const SchemaEntityClass = allSchemaEntities[index]; const lswDatabaseSchema = this.adaptSchemaEntityToDatabaseSchema(SchemaEntityClass); schemaTemp.registerSchema(lswDatabaseSchema); } return schemaTemp.$schema; } adaptSchemaEntityToDatabaseSchema(SchemaEntityClass) { this.$trace("adaptSchemaEntityToDatabaseSchema", arguments); const schema = { hasTables: {} }; const data = SchemaEntityClass.toObject(); schema.hasTables[data.name] = { ...data, hasEntityId: SchemaEntityClass.getEntityId(), hasColumns: data.properties, hasExtraAttributes: data.extraAttributes, }; return schema; } registerSchema(partialSchema = {}) { this.$trace("registerSchema", arguments); if (typeof partialSchema !== "object") { throw new Error("Required parameter «partialSchema» to be an object on «LswSchema.registerSchema»"); } this.$validateSchema(partialSchema); this.$fusionateSchema(partialSchema); return this; } $validateSchema(schema) { this.$trace("$validateSchema", arguments); Native: { this.$validateSchemaNative(schema); } Core_process: { if ("hasTables" in schema) { const tableIds = Object.keys(schema.hasTables); Iterating_tables: for (let indexTable = 0; indexTable < tableIds.length; indexTable++) { const tableId = tableIds[indexTable]; const table = schema.hasTables[tableId]; this.$validateTableNative(table, tableId, schema); if (!("hasColumns" in table)) { continue Iterating_tables; } const columnIds = Object.keys(table.hasColumns); Iterating_columns: for (let indexColumn = 0; indexColumn < columnIds.length; indexColumn++) { const columnId = columnIds[indexColumn]; const column = table.hasColumns[columnId]; this.$validateColumnNative(column, columnId, tableId, schema); } } } } User: { this.onValidateSchema(schema); } } $fusionateSchema(partialSchema) { this.$trace("$fusionateSchema", arguments); const tableIds = Object.keys(partialSchema?.hasTables || {}); Debug_purposes: { const columnIds = tableIds.map(tableId => Object.keys(partialSchema.hasTables[tableId].hasColumns || {}).map(columnId => [tableId, columnId].join("."))); const tablesMessage = tableIds.length === 0 ? "No tables to fusionate" : "Tables to fusionate:\n - " + tableIds.join("\n - "); const columnsMessage = columnIds.length === 0 ? "No columns to fusionate" : "Columns to fusionate:\n - " + columnIds.join("\n - "); this.$trace(`[*] ${tablesMessage}`, []); this.$trace(`[*] ${columnsMessage}`, []); } this.$fusionateSchemaNative(partialSchema); Iterating_tables: for (let indexTable = 0; indexTable < tableIds.length; indexTable++) { const tableId = tableIds[indexTable]; const tableInfo = partialSchema.hasTables[tableId]; this.$fusionateTableNative(tableInfo, tableId, partialSchema); const columnIds = Object.keys(tableInfo.columns || {}); Iterating_columns: for (let indexColumn = 0; indexColumn < columnIds.length; indexColumn++) { const columnId = columnIds[indexColumn]; const columnInfo = tableInfo.columns[columnId]; this.$fusionateColumnNative(columnInfo, columnId, tableId, partialSchema); } } } $validateSchemaNative(schema) { this.$trace("$validateSchemaNative", arguments); Native: { const ensureSchema = $ensure(schema).type("object").to.have.key("hasTables"); ensureSchema.its("hasTables").type("object"); } User: { this.onValidateSchema(schema); } } $validateTableNative(definition, id, schema) { this.$trace("$validateTableNative", arguments); Native: { const ensureTable = $ensure(definition).type("object").to.have.key("hasColumns"); const ensureHasColumns = ensureTable.its("hasColumns").type("object"); const columnIds = Object.keys(ensureHasColumns.$subject); for(let index=0; index<columnIds.length; index++) { const columnId = columnIds[index]; const ensureColumn = ensureHasColumns.its(columnId).type("object"); ensureColumn.its("isType").type("string"); ensureColumn.its("isUnique").type(["boolean", "undefined"]); ensureColumn.its("refersTo").type(["object", "undefined", "boolean"]); if(typeof ensureColumn.$subject.refersTo === "object") { const ensureRefersTo = ensureColumn.its("refersTo").type("object"); ensureRefersTo.to.have.keys(["entity", "property"]); ensureRefersTo.its("entity").type("string"); ensureRefersTo.its("property").type("string"); } ensureColumn.its("isFormType").type("string"); ensureColumn.its("hasValidator").type(["string", "boolean", "function", "undefined"]); ensureColumn.its("hasFormatter").type(["string", "boolean", "function", "undefined"]); ensureColumn.its("hasLabel").type(["string", "boolean", "undefined"]); ensureColumn.its("hasDescription").type(["string", "boolean", "undefined"]); ensureColumn.its("hasPlaceholder").type(["string", "boolean", "undefined"]); } } User: { this.onValidateTable(id, definition, schema); } } $validateColumnNative(id, definition, tableId, schema) { this.$trace("$validateColumnNative", arguments); Native: { // !@OK: the validation is already made on the $validateTableNative } User: { this.onValidateColumn(id, definition, tableId, schema); } } $fusionateSchemaNative(partialSchema) { this.$trace("$fusionateSchemaNative", arguments); Native_fusion: { } User_fusion: { this.onFusionateSchema(partialSchema); } } $fusionateTableNative(tableInfo, tableId, partialSchema) { this.$trace("$fusionateTableNative", arguments); Native_fusion: { const isKnown = tableId in this.$schema.hasTables; if(!isKnown) { this.$schema.hasTables[tableId] = tableInfo; } else { throw new Error(`Schema cannot fusionate table «${tableId}» to schema for second time on «$fusionateTableNative»`); } } User_fusion: { this.onFusionateTable(tableInfo, tableId, partialSchema); } } $fusionateColumnNative(columnInfo, columnId, tableId, partialSchema) { this.$trace("$fusionateColumnNative", arguments); Native_fusion: { const isKnown = columnId in this.$schema.hasTables[tableId].hasColumns; if(!isKnown) { this.$schema.hasTables[tableId].hasColumns[columnId] = columnInfo; } else { throw new Error(`Schema cannot fusionate column «${tableId}.${columnId}» to schema for second time on «$fusionateTableNative»`); } } User_fusion: { this.onFusionateColumn(columnInfo, columnId, tableId, partialSchema); } } }; // Last global injection for a unique main instance: window.$lswSchema = LswSchema.create();
const LswUtils = {}; LswUtils.hello = () => console.log("Hello!"); /////////////////////////////////////////////////////// // API de Excel: usa SheetJS Object.assign(LswUtils, { readFileAsArrayBuffer(file) { return new Promise((resolve, reject) => { const reader = new FileReader(); reader.onload = event => resolve(event.target.result); reader.onerror = error => reject(error); reader.readAsArrayBuffer(file); }); }, readFileAsText(file) { return new Promise((resolve, reject) => { const reader = new FileReader(); reader.onload = event => resolve(event.target.result); reader.onerror = error => reject(error); reader.readAsText(file); }); }, readFileAsBinaryString(file) { return new Promise((resolve, reject) => { const reader = new FileReader(); reader.onload = event => resolve(event.target.result); reader.onerror = error => reject(error); reader.readAsBinaryString(file); }); }, selectFile() { return new Promise(resolve => { const inputHtml = document.createElement("input"); inputHtml.setAttribute("type", "file"); inputHtml.setAttribute("accept", ".ods,.xlsx,.xls,.csv"); inputHtml.style.display = "none"; document.body.appendChild(inputHtml); inputHtml.addEventListener("change", event => { try { const file = event.target.files[0]; if (file) { return resolve(file); } else { return resolve(undefined); } } catch (error) { console.log("This should not happen :(", error); } finally { inputHtml.remove(); } }); inputHtml.click(); }); }, sheetToArray(sheet) { // Obtener el rango de celdas activo de la hoja const range = sheet['!ref']; // Ejemplo: 'A1:C3' // Extraer las coordenadas de la celda inicial y final del rango const [startCell, endCell] = range.split(':'); const startCol = startCell.match(/[A-Z]+/)[0]; // Columna de la primera celda (por ejemplo, 'A') const startRow = parseInt(startCell.match(/\d+/)[0], 10); // Fila de la primera celda (por ejemplo, 1) const endCol = endCell.match(/[A-Z]+/)[0]; // Columna de la última celda (por ejemplo, 'C') const endRow = parseInt(endCell.match(/\d+/)[0], 10); // Fila de la última celda (por ejemplo, 3) const data = []; // Iterar sobre las filas y columnas dentro del rango for (let row = startRow; row <= endRow; row++) { const rowData = []; for (let col = startCol.charCodeAt(0); col <= endCol.charCodeAt(0); col++) { const cellAddress = String.fromCharCode(col) + row; const cell = sheet[cellAddress]; // Obtener la celda rowData.push(cell ? cell.v : null); // Si la celda existe, tomar su valor. Si no, agregar `null` } data.push(rowData); // Agregar la fila al array de datos } return data; } }); /////////////////////////////////////////////////////// // API de Conductometria: usa API de Excel (so: SheetJS) Object.assign(LswUtils, { isDatePassed(date, time, currentDate = new Date()) { const [day, month, year] = date.split("/").map(Number); const [hour, minute, second] = time.split(":").map(Number); const targetDate = new Date(year, month - 1, day, hour, minute, second); return currentDate > targetDate; }, sheetToRegistros(sheet, asObjectIsOkay = false) { const raw = this.sheetToArray(sheet); const byDate = {}; let lastDate = undefined; const currentDate = new Date(); Compact_by_date_using_last_date: { for (let index = 0; index < raw.length; index++) { const cells = raw[index]; const [time, content] = cells; const isDate = time.match(/[0-9][0-9]\/[0-9][0-9]\/[0-9][0-9]/g); if (isDate) { if (!(time in byDate)) { byDate[time] = {}; } lastDate = time; } else { if (typeof content === "string") { if (!(time in byDate[lastDate])) { byDate[lastDate][time] = []; } Add_properties_to_hour: { } const items = content.split(".").filter(l => l !== ""); for (let indexItem = 0; indexItem < items.length; indexItem++) { const item = items[indexItem]; const [name, details] = item.split(":").filter(l => l !== ""); let event = {}; Add_properties_to_event: { Object.assign(event, { name }); Object.assign(event, details ? { details: details.trim() } : {}); } byDate[lastDate][time].push(event); } } } } } if (asObjectIsOkay) { return byDate; } const output = []; Format_to_pure_array_to_avoid_confusions: { const daysSorted = Object.keys(byDate).sort(); for (let index_day = 0; index_day < daysSorted.length; index_day++) { const day_id = daysSorted[index_day]; const day_data = byDate[day_id]; const day_output = { day: day_id, hours: [] }; const hoursSorted = Object.keys(day_data).sort(); for (let index_hour = 0; index_hour < hoursSorted.length; index_hour++) { const hour_id = hoursSorted[index_hour]; const hour_data = day_data[hour_id]; const hour_is_passed = this.isDatePassed(day_id, hour_id, currentDate); const hour_is_current = hour_is_passed && (() => { const [hours, minutes, seconds] = hour_id.split(":").map(Number); const hour_next_id = [hours + 1, minutes, seconds].map(t => ("" + t).padStart(2, "0")).join(":"); console.log(hour_next_id); return !this.isDatePassed(day_id, hour_next_id, currentDate); })(); const hour_output = { hour: hour_id, events: [], passed: hour_is_passed, current: hour_is_current, }; for (let index_item = 0; index_item < hour_data.length; index_item++) { const item = hour_data[index_item]; hour_output.events.push(item); } day_output.hours.push(hour_output); } output.push(day_output); } } return output; }, async loadConductometriaByExcelFile() { try { const file = await this.selectFile(); const data = await this.readFileAsBinaryString(file); const workbook = XLSX.read(data, { type: "binary", cellDates: false }); const sheet = workbook.Sheets["Tracking"]; const registros = this.sheetToRegistros(sheet); return { registros }; } catch (error) { console.log(error); } }, }); // API de LSW: LswUtils.toPlainObject = function (obj) { const seen = new WeakSet(); return JSON.parse(JSON.stringify(obj, (key, value) => { if (typeof value === "object" && value !== null) { if (seen.has(value)) return undefined; // Ignora referencias circulares seen.add(value); } return value; })); }; LswUtils.stringify = function (argInput, avoidedIndexes = [], currentLevel = 0, maxLevel = -1) { const seen = new WeakSet(); return JSON.stringify(argInput, function (key, value) { if (avoidedIndexes.indexOf(key) !== -1) { return; } if (typeof value === "object") { if(value === null) { return null; } if (value.$el) { return `[VueComponent:${value?.$options?.name}]`; } if (seen.has(value)) { return "[Circular]"; } if (value !== null) { seen.add(value); } } return value; }, 2); }; LswUtils.pluralizar = function (singular, plural, contexto, cantidad) { return contexto.replace("%s", cantidad === 1 ? singular : plural).replace("%i", cantidad); }; LswUtils.getRandomString = function (len = 10) { const alphabet = "abcdefghijklmnopqrstuvwxyz".split(""); let out = ""; while (out.length < len) { out += alphabet[Math.floor(Math.random() * alphabet.length)]; } return out; }; LswUtils.hello = function () { console.log("hello"); }; LswUtils.waitForMilliseconds = function (ms) { return new Promise(resolve => { setTimeout(resolve, ms); }); }; LswUtils.splitStringOnce = function (text, splitter) { if (typeof text !== "string") { throw new Error("Required parameter «text» to be a string on «LswUtils.splitStringOnce»"); } if (typeof splitter !== "string") { throw new Error("Required parameter «text» to be a string on «LswUtils.splitStringOnce»"); } const pos = text.indexOf(splitter); if (pos === -1) return [undefined, text]; const parts = text.split(""); return [[...parts].splice(0, pos).join(""), [...parts].splice(pos + 1).join("")]; }; LswUtils.reverseString = function (text) { return text.split("").reverse().join(""); }; LswUtils.capitalize = function (text) { return text.substr(0, 1).toUpperCase() + text.substr(1); }; LswUtils.startThread = function (callback) { setTimeout(callback, 0); }; LswUtils.openAddNoteDialog = async function () { const response = await Vue.prototype.$lsw.dialogs.open({ title: "Nueva nota", template: ` <div class="pad_1 position_absolute top_0 right_0 left_0 bottom_0 flex_column"> <div class="flex_1"> <input class="width_100" type="text" v-model="value.tiene_fecha" placeholder="Fecha de la nota" ref="fecha" /> </div> <div class="flex_1 flex_row centered" style="padding-top: 1px;"> <div class="flex_1">Estado: </div> <select class="flex_100" v-model="value.tiene_estado"> <option value="creada">Creada</option> <option value="procesada">Procesada</option> <option value="dudosa">Dudosa</option> <option value="desestimada">Desestimada</option> </select> </div> <div class="flex_1" style="padding-top: 2px;"> <input class="width_100" type="text" v-model="value.tiene_categorias" placeholder="categoría 1; categoria 2; categoria 3" /> </div> <div class="flex_100" style="padding-top: 1px;"> <textarea v-model="value.tiene_contenido" spellcheck="false" style="height: 100%;" placeholder="Contenido de la nota. Acepta **markdown**, recuerda." ref="contenido" /> </div> <div class="flex_1" style="padding-top: 2px;"> <input class="width_100" type="text" v-model="value.tiene_titulo" placeholder="Título de la nota" ref="titulo" /> </div> <div class="flex_row pad_top_1"> <div class="flex_100"></div> <div class="flex_1 flex_row"> <div class="pad_right_1"> <button class="mini" v-on:click="validate">➕ Añadir</button> </div> </div> </div> </div> `, factory: { methods: { validate() { const isValidFecha = LswTimer.parser.parse(this.value.tiene_fecha); const isValidContenido = this.value.tiene_contenido.trim() !== ""; const isValidTitulo = this.value.tiene_titulo.trim() !== ""; if (!isValidTitulo) { window.alert("Necesita un título la nota."); return this.$refs.titulo.focus(); } if (!isValidContenido) { window.alert("Necesita un contenido la nota."); return this.$refs.contenido.focus(); } if (!isValidFecha) { window.alert("Necesita una fecha válida la nota."); return this.$refs.fecha.focus(); } return this.accept(); } }, data: { value: { tiene_fecha: LswTimer.utils.formatDatestringFromDate(new Date(), false, false, true), tiene_titulo: "", tiene_categorias: "", tiene_contenido: "", tiene_estado: "creada", // "procesada" } } } }); return response; }; LswUtils.openAddArticuloDialog = async function () { const response = await Vue.prototype.$lsw.dialogs.open({ title: "Nuevo artículo", template: ` <div class=""> <lsw-schema-based-form :model="{ databaseId:'lsw_default_database', tableId:'Articulo', rowId: -1, }" :on-submit="validate" /> </div> `, factory: { methods: { validate(value) { console.log("Validating:", value); this.value = value; const isValidFecha = LswTimer.parser.parse(this.value.tiene_fecha); const isValidContenido = this.value.tiene_contenido.trim() !== ""; const isValidTitulo = this.value.tiene_titulo.trim() !== ""; if (!isValidTitulo) { window.alert("Necesita un título la nota."); return this.$refs.titulo.focus(); } if (!isValidContenido) { window.alert("Necesita un contenido la nota."); return this.$refs.contenido.focus(); } if (!isValidFecha) { window.alert("Necesita una fecha válida la nota."); return this.$refs.fecha.focus(); } return this.accept(); } }, data: { value: { tiene_fecha: LswTimer.utils.formatDatestringFromDate(new Date(), false, false, true), tiene_titulo: "", tiene_categorias: "", tiene_contenido: "", tiene_estado: "creada", // "procesada" } } } }); return response; }; LswUtils.createAsyncFunction = function (code, parameters = []) { const AsyncFunction = (async function () { }).constructor; const asyncFunction = new AsyncFunction(...parameters, code); return asyncFunction; }; LswUtils.createSyncFunction = function (code, parameters = []) { const syncFunction = new Function(...parameters, code); return syncFunction; }; LswUtils.callSyncFunction = function (code, parameters = {}, scope = globalThis) { const parameterKeys = Object.keys(parameters); const parameterValues = Object.values(parameters); const syncFunction = new Function(...parameterKeys, code); return syncFunction.call(scope, ...parameterValues); }; LswUtils.arrays = {}; LswUtils.extractFirstStringOr = function (txt, defaultValue = "") { if (!txt.startsWith('"')) return defaultValue; const pos1 = txt.substr(1).indexOf('"'); if (pos1 === -1) return defaultValue; const pos = pos1 - 1; const extractedSubstr = txt.substr(0, pos); // // @OK: No escapamos, porque se entiende que no se va a usar ese string en el concepto nunca. return JSON.parse(extractedSubstr); }; LswUtils.uniquizeArray = function (list) { const appeared = []; for (let index = 0; index < list.length; index++) { const item = list[index]; const pos = appeared.indexOf(item); if (pos === -1) { appeared.push(item); } } return appeared; }; LswUtils.arrays.uniquizeArray = LswUtils.uniquizeArray; LswUtils.arrays.getMissingInFirst = function (a, b) { const excludeds = []; for (let index = 0; index < b.length; index++) { const b_item = b[index]; const pos = a.indexOf(b_item); if (pos === -1) { excludeds.push(b_item); } } return excludeds; }; LswUtils.fromJsonToNatural = function (json, nivel = 0) { // @CHATGPT: const indent = ' '.repeat(nivel); let texto = ''; if (Array.isArray(json)) { texto += `${indent}Esta es una lista con ${json.length} elemento(s):\n`; json.forEach((item, index) => { texto += `${indent}- Elemento ${index + 1}: `; if (typeof item === 'object' && item !== null) { texto += '\n' + LswUtils.fromJsonToNatural(item, nivel + 1); } else { texto += `${LswUtils.naturalizeValue(item)}\n`; } }); } else if (typeof json === 'object' && json !== null) { const keys = Object.keys(json); texto += `${indent}Este objeto tiene ${keys.length} propiedad(es):\n`; for (const key of keys) { const valor = json[key]; texto += `${indent}- La propiedad "${key}" `; if (typeof valor === 'object' && valor !== null) { texto += `contiene:\n` + LswUtils.fromJsonToNatural(valor, nivel + 1); } else { texto += `tiene ${LswUtils.naturalizeValue(valor)}.\n`; } } } else { texto += `${indent}${LswUtils.naturalizeValue(json)}\n`; } return texto; }; LswUtils.naturalizeValue = function (valor) { switch (typeof valor) { case 'string': return `un texto que dice "${valor}"`; case 'number': return `un número con valor ${valor}`; case 'boolean': return valor ? 'el valor verdadero' : 'el valor falso'; case 'object': return valor === null ? 'un valor nulo' : 'un objeto'; default: return 'un valor desconocido'; } }; LswUtils.downloadFile = function (filename, filecontent) { const blob = new Blob([filecontent], { type: "text/plain" }); const enlace = document.createElement("a"); enlace.href = URL.createObjectURL(blob); enlace.download = filename; document.body.appendChild(enlace); enlace.click(); document.body.removeChild(enlace); }; LswUtils.extractPropertiesFrom = function (base, props = [], voidedProps = [], overridings = {}) { const out = {}; for (let index = 0; index < props.length; index++) { const propId = props[index]; if (propId in base) { out[propId] = base[propId]; } } for(let index=0; index<voidedProps.length; index++) { const propId = voidedProps[index]; delete out[propId]; } return Object.assign(out, overridings); }; LswUtils.dehydrateFunction = function(f) { return f.toString(); }; LswUtils.hydrateFunction = function(fSource) { return new Function(fSource); }; LswUtils.zeroIfNegative = function(numero) { if(numero < 0) { return 0; } return numero; }; LswUtils.filterObject = function(obj, filterer) { return Object.keys(obj).reduce((output, key, index) => { const val = obj[key]; console.log(key, val, index, output); const result = filterer(key, val, index, output); if(result) { output[key] = val; } return output; }, {}); }; LswUtils.mapObject = function(obj, mapper, deleterValue = undefined) { return Object.keys(obj).reduce((output, key, index) => { const val = obj[key]; const result = mapper(key, val, index, output); if(result !== deleterValue) { output[key] = result; } return output; }, {}); }; LswUtils.reduceObject = function(obj, reducer) { return Object.keys(obj).reduce((output, key, index) => { const val = obj[key]; return reducer(key, val, index, output); }, {}); }; LswUtils.askForFileText = async function() { return new Promise((resolve, reject) => { const inputHtml = document.createElement("input"); inputHtml.type = "file"; inputHtml.style.display = "none"; inputHtml.onchange = function() { const file = event.target.files[0]; if(file) { resolve(file); } else { reject(new Error("No file selected finally")); } document.body.removeChild(inputHtml); }; document.body.appendChild(inputHtml); inputHtml.click(); }).then(file => { return new Promise((resolve, reject) => { const lector = new FileReader(); lector.onload = () => { resolve(lector.result); }; lector.readAsText(file); }); }); }; LswUtils.padStart = function(txt, ...args) { return ("" + txt).padStart(...args); }; LswUtils.flattenObjects = function(list, options = {}) { const { keyMapper = false, // can be function or false valueMapper = false, // can be function or false duplicatedsStrategy = 'override', // can be "override" | "error" nonFlattenablesStrategy = 'ignore', // can be "ignore" | "error" } = options; const output = {}; let totalKeys = 0; for(let index=0; index<list.length; index++) { const item = list[index]; const isFlattenable = (typeof item === "object") && (item !== null); if(isFlattenable) { const allKeys = Object.keys(item); for(let indexKey=0; indexKey<allKeys.length; indexKey++) { const key = allKeys[indexKey]; const isDuplicated = key in output; if((!isDuplicated) || (duplicatedsStrategy === 'override')) { const finalKey = typeof keyMapper === 'function' ? keyMapper(key, totalKeys, indexKey, item, index, list) : key; const finalValue = typeof valueMapper === 'function' ? valueMapper(item[key], totalKeys, indexKey, item, index, list) : item[key]; totalKeys++; output[finalKey] = finalValue; } else if(duplicatedsStrategy === 'error') { throw new Error(`Required item on index «${index}» key «${key}» to not be duplicated on «LswUtils.flattenObjects»`); } else { throw new Error(`Unknown strategy for duplicateds «${duplicatedsStrategy}» on «LswUtils.flattenObjects»`); } } } else if(nonFlattenablesStrategy === 'ignore') { // @OK. } else if(nonFlattenablesStrategy === 'error') { throw new Error(`Required item on index «${index}=${typeof item}» to be flattenable on «LswUtils.flattenObjects»`); } else { throw new Error(`Unknown strategy for non-flattenables «${nonFlattenablesStrategy}» on «LswUtils.flattenObjects»`); } } return output; } Global_injection: { window.kk = (...args) => Object.keys(...args); window.dd = (...args) => Vue.prototype.$lsw.toasts.view(...args); window.ddd = (...args) => Vue.prototype.$lsw.toasts.collapse(...args); }
const LswVue2 = class { static getClosestParent(component, filterCallback) { $ensure(component).type("object"); $ensure(filterCallback).type("function"); let parentOf = component; do { parentOf = parentOf.$parent; const isValid = filterCallback(parentOf); if (isValid) { return parentOf; } } while (typeof parentOf !== "undefined"); return undefined; } static extendComponent(baseComponent = {}) { const extendedComponent = Object.assign({}, baseComponent); extendedComponent.props = Object.assign({}, baseComponent.props || {}); extendedComponent.methods = Object.assign({}, baseComponent.methods || {}); extendedComponent.watch = Object.assign({}, baseComponent.watch || {}); extendedComponent.computed = Object.assign({}, baseComponent.computed || {}); return extendedComponent; } }
const ReturnControl = class { constructor(value) { this.value = value; } }; const MutateControl = class { constructor(mutator = {}) { this.mutator = mutator; } }; const ReturnController = class { static create(...args) { return new this(...args); } constructor() { this.results = new Map(); this.functions = new Map(); this.middlewares = []; this.properties = new Map(); } prehook(middleware) { this.middlewares.unshift(middleware); return this; } hook(middleware) { this.middlewares.push(middleware); return this; } unhook(middleware) { this.middlewares = this.middlewares.filter(m => m !== middleware); return this; } prop(properties = {}) { Object.assign(this.properties, properties); return this; } hasProp(id) { return this.properties.has(id); } getProp(id, defaultValue = undefined) { if (!this.properties.has(id)) { return defaultValue; } return this.properties.get(id); } setProp(id, value) { this.properties.set(id, value); return this; } load(functions) { this.functions = new Map(Object.entries(functions)); return this; } solved(name) { return this.results.get(name); } pipe(outputName, functionNames, parameters = []) { for (let fnName of functionNames) { const fnCallback = this.functions.get(fnName); if (fnCallback) { const result = fnCallback(...parameters); if (this.processResult(result, outputName)) { return this.solved(outputName); } } for (const middleware of this.middlewares) { const result = middleware(this); if (this.processResult(result, outputName)) { return this.solved(outputName); } } } return null; } processResult(result, outputName) { if (result instanceof ReturnControl) { this.results.set(outputName, result.value); return true; } else if (result instanceof MutateControl) { const mutator = result.mutator; if (typeof mutator === "function") { const mutatorResult = mutator(this); if (typeof mutatorResult === "object") { Object.assign(this.properties, mutatorResult); } else if (mutatorResult !== undefined) { throw new Error( `MutateControl's function mutator must return an object or undefined, found: ${typeof mutatorResult}` ); } } else if (typeof mutator === "object") { Object.assign(this, mutator); } else { throw new Error( `MutateControl's mutator must be a function or object, found: ${typeof mutator}` ); } } return false; } reset() { this.results.clear(); this.properties.clear(); return this; } }; const ControlledFunction = { MutateControl, ReturnControl, ReturnController, }; ControlledFunction.default = ControlledFunction; return ControlledFunction;
const Superlogger = class { static create(id, options) { return new this(id, options); } static levels = { trace: 4, debug: 3, log: 2, warn: 1, error: 0, }; static defaultOptions = { // active: true, active: false, level: "trace" }; static loggers = {}; static alphabet = "abcdefghijklmnopqrstuvwxyz"; static generateRandomString(len /* istanbul ignore next */ = 5) { let out = ""; while(out.length < len) { out += this.alphabet[Math.floor(Math.random() * this.alphabet.length - 1)]; } return out; } constructor(idInput = false, options = {}) { const id = idInput || this.constructor.generateRandomString(10); if (typeof id !== "string") { throw new Error("Required parameter «id» to be a string on «Superlogger.constructor»"); } if (id in this.constructor.loggers) { throw new Error("Required parameter «id» to be a unique string on «Superlogger.constructor»"); } if (typeof options !== "object") { throw new Error("Required parameter «options» to be an object on «Superlogger.constructor»"); } this.$id = id; this.$options = Object.assign({}, this.constructor.defaultOptions, options); this.$source = undefined; this.$events = {}; this.$callbacks = { before: undefined, after: undefined, }; this.resetEvents(); this.resetCallbacks(); this.constructor.loggers[id] = this; } activate() { this.$options.active = true; } deactivate() { this.$options.active = false; } setSource(source) { this.source = source; } setLevel(level) { if (!(level in this.constructor.levels)) { throw new Error("Required parameter «level» to be a recognized level on «Superlogger.setLevel»"); } this.$options.level = this.constructor.levels[level]; } setEvent(id, callback) { this.$events[id] = callback; } resetEvents() { this.$events = { trace: undefined, debug: undefined, log: undefined, warn: undefined, error: undefined, }; } setBefore(callback) { this.$callbacks.before = callback; } setAfter(callback) { this.$callbacks.after = callback; } resetCallbacks() { this.$callbacks = { after: undefined, before: undefined, }; } replacerFactory() { const visited = new WeakMap(); return (key, value) => { if (typeof value === "function") { return "[Function] " + value.toString(); } if (typeof value === "object" && value !== null) { if (visited.has(value)) { return "[Circular]"; } visited.set(value, true); } else /* istanbul ignore else */ {} return value; } } stringifyForDebugging(obj) { return JSON.stringify(obj, this.replacerFactory(), 2); } stringifyForTracing(obj) { if(typeof obj === "object") { if(obj._isVue) { return "[Vue instance::" + obj.$options.name + "]"; } else if(obj === window) { return "[Window instance]"; } } return JSON.stringify(obj, this.replacerFactory(), 2); } $emit(event, args) { if(!(event in this.$events)) { return "void::event not defined"; } const callback = this.$events[event]; if(typeof callback === "undefined") { return "void::callback not defined"; } return callback(this, args); } $log(levelId, elements, methodId = false) { if(!(levelId in this.constructor.levels)) { throw new Error("Required parameter «levelId» to be an identified level on «Superlogger.$log»"); } const level = this.constructor.levels[levelId]; if (!this.$options.active) { return "void::currently active=false state"; } if (this.$options.level < level) { return "void::level of tracing out of bounds"; } let message = `[${this.$id}][${levelId}]`; if (methodId !== false) { message += `[${methodId}]`; } for (let index = 0; index < elements.length; index++) { const element = elements[index]; const stringification = typeof element === "string" ? element : this.stringifyForTracing(element); message += " " + stringification; } Event_triggering: { if(typeof this.$callbacks.before !== "undefined") { this.$callbacks.before(message, this, levelId, elements, methodId); } console.log(message); if(typeof this.$callbacks.after !== "undefined") { this.$callbacks.after(message, this, levelId, elements, methodId); } this.$emit(levelId, {elements, methodId}); } } trace(methodId, ...data) { return this.$log("trace", data, methodId); } debug(...data) { return this.$log("debug", data); } log(...data) { return this.$log("log", data); } warn(...data) { return this.$log("warn", data); } error(...data) { return this.$log("error", data); } };
class BaseClass { initialize(...args) { const promise = this.onInitialize(...args); if (promise instanceof Promise) { return promise.then(output => { return this; }); } return this; } onInitialize() { return this; } } const AbstractProxy = class { constructor(value) { this.value = value; } } class AbstractVirtualizer extends AbstractProxy {} class AbstractSchemaEntity extends AbstractProxy { static toObject() { return { entityId: this.getEntityId(), name: this.getName(), version: this.getVersion(), properties: this.getProperties(), externalProperties: this.getExternalProperties(), methods: this.getMethods(), virtualizerId: this.getVirtualizerId(), formSettings: this.getFormSettings(), extraAttributes: this.getExtraAttributes(), }; } static getEntityId() { throw new Error(`Required method «getEntityId» to be overriden by «AbstractSchemaEntity» inherited class on «AbstractSchemaEntity.getEntityId»`); } static getName() { throw new Error(`Required method «getName» to be overriden by «AbstractSchemaEntity» inherited class on «AbstractSchemaEntity.getName»`); } static getVersion() { throw new Error(`Required method «getVersion» to be overriden by «AbstractSchemaEntity» inherited class on «AbstractSchemaEntity.getVersion»`); } static getProperties() { throw new Error(`Required method «getProperties» to be overriden by «AbstractSchemaEntity» inherited class on «AbstractSchemaEntity.getProperties»`); } static getExternalProperties() { return {}; } static getMethods() { throw new Error(`Required method «getMethods» to be overriden by «AbstractSchemaEntity» inherited class on «AbstractSchemaEntity.getMethods»`); } static getVirtualizerId() { throw new Error(`Required method «getVirtualizerId» to be overriden by «AbstractSchemaEntity» inherited class on «AbstractSchemaEntity.getVirtualizerId»`); } static getFormSettings() { throw new Error(`Required method «getFormSettings» to be overriden by «AbstractSchemaEntity» inherited class on «AbstractSchemaEntity.getFormSettings»`); } static getExtraAttributes() { throw new Error(`Required method «getExtraAttributes» to be overriden by «AbstractSchemaEntity» inherited class on «AbstractSchemaEntity.getExtraAttributes»`); } } class AbstractItem { } class AbstractList { constructor(value) { this.value = Array.isArray(value) ? value : []; } forEach(callback) { this.value.forEach(callback); return this; } filter(callback) { this.value = this.value.filter(callback); return this; } map(callback) { this.value = this.value.map(callback); return this; } reduce(callback, initialValue = []) { this.value = this.value.reduce(callback, initialValue); return this; } modify(callback) { this.value = callback(this.value); return this; } concat(...lists) { this.value = this.value.concat(...lists); return this; } onlyProp(prop) { this.value = this.value.map(it => it[prop]); return this; } onlyProps(props) { this.value = this.value.map(it => { const out = {}; props.forEach(prop => { out[prop] = it[prop]; }); return out; }); return this; } removeProp(prop) { return this.removeProps([prop]); } removeProps(props) { this.value = this.value.map(it => { const out = {}; const keys = Object.keys(it).filter(prop => { return props.indexOf(prop) === -1; }); keys.forEach(key => { out[key] = it[key]; }); return out; }); return this; } deduplicate() { const out = []; this.value.forEach(it => { if (out.indexOf(it) === -1) { out.push(it); } }); this.value = out; return this; } sort(callback) { this.value = this.value.sort(callback); return this; } }; class LswProxifier { static create(...args) { return new this(...args); } AbstractProxy = AbstractProxy; AbstractSchemaEntity = AbstractSchemaEntity; AbstractVirtualizer = AbstractVirtualizer; AbstractItem = AbstractItem; AbstractList = AbstractList; constructor(mainInjection = {}) { this.$definitions = {}; this.$mainInjection = mainInjection; this.$splitterChar = "@"; } define(name, classesDef) { if(!(name in this.$definitions)) { this.$definitions[name] = {}; } if(typeof classesDef !== "object") { throw new Error(`Required parameter «classesDef» to be a class on «LswProxifier.define»`) } const classesIds = Object.keys(classesDef); for(let index=0; index<classesIds.length; index++) { const classId = classesIds[index]; const classDef = classesDef[classId]; if(typeof classDef !== "function") { throw new Error(`Required proxy class «${classId}» to be a class on «LswProxifier.define»`) } } Object.assign(this.$definitions[name], classesDef); } find(selector) { const [name, aspectId = false] = selector.split(this.$splitterChar); if(!(name in this.$definitions)) { throw new Error(`Could not find proxy classes from name «${name}» on «LswProxifier.find»`); } if(!aspectId) { return this.$definitions[name]; } if(!(aspectId in this.$definitions[name])) { throw new Error(`Could not find proxy aspect «${aspectId}» from class «${name}» on «LswProxifier.find»`); } return this.$definitions[name][aspectId]; } getFactory() { return this.proxify.bind(this); } proxify(obj) { return { as: (typeSelector = "", proxyExtraArguments = []) => { if(typeof typeSelector !== "string") { throw new Error(`Required parameter «typeSelector» to be a string on «proxify(@).as(@)»`); } const [definitionId, aspectId = "Item"] = typeSelector.split(this.$splitterChar); if(!(definitionId in this.$definitions)) { throw new Error(`Required parameter «definitionId» [«${definitionId}»] to exist in «proxifier.$definitions» but it does not on «proxify(@).as(@)`); } if(!(aspectId in this.$definitions[definitionId])) { throw new Error(`Required parameter «aspectId» [«${aspectId}»] to exist in «proxifier.$definitions[${JSON.stringify(definitionId)}]» but it does not on «proxify(@).as(@)`); } const proxyClass = this.$definitions[definitionId][aspectId]; const proxyInstance = new proxyClass(obj, ...proxyExtraArguments); if(typeof this.$mainInjection === "function") { this.$mainInjection(proxyInstance, proxyClass); } else if(typeof this.$mainInjection === "object") { Object.assign(proxyInstance, this.$mainInjection); } return proxyInstance; } }; } }; LswProxifier.default = LswProxifier; globalThis.$proxifier = LswProxifier.create();
const LswRandomizer = class { static $defaultAlphabet = "abcdefghijklmnopqrstuvwxyz"; static getRandomIntegerBetween(start = 0, end = 100) { const min = Math.ceil(Math.min(start, end)); const max = Math.floor(Math.max(start, end)); return Math.floor(Math.random() * (max - min + 1)) + min; } static getRandomString(len, alphabet = this.$defaultAlphabet) { let out = ""; while (out.length < len) { out += alphabet[Math.floor(Math.random() * alphabet.length)]; } return out; } static getRandomItem(list) { return list[Math.floor(Math.random() * list.length)]; } static getRandomObject(totalProps = [0, 10], listOf = false) { let randomProps = totalProps; if (Array.isArray(totalProps)) { randomProps = this.getRandomIntegerBetween(...totalProps); } const buildRandomObject = () => { const randomObject = {}; while (Object.keys(randomObject).length < randomProps) { const key = this.getRandomString(5); const value = this.getRandomString(10); randomObject[key] = value; } return randomObject; }; if (listOf === false) { return buildRandomObject(); } const randomList = []; for(let index=0; index<listOf; index++) { const randomObject = buildRandomObject(); randomList.push(randomObject); } return randomList; } } return LswRandomizer;
const serverUrl = 'http://127.0.0.1'; const serverPort = 3000; if (process?.env?.NODE_ENV === "test") { const socket = io(`${serverUrl}:${serverPort}`); socket.on('refrescar', () => { console.log('Recibida la señal de refrescar desde el servidor'); location.reload(); }); }
const Store = class { static create(...args) { return new this(...args); } constructor(initialState = {}, path_to_store = "original_store.json") { this.$store = initialState; this.$storePath = path_to_store; this.events = {}; } _triggerParentEvents(event, path, value) { const originalPath = path.join("."); if (this.events[originalPath]) { this.events[originalPath].forEach((callback) => { return callback(event, path, value); }); } while (path.length > 0) { path.pop(); const parentPath = path.join("."); if (this.events[parentPath]) { this.events[parentPath].forEach((callback) => { const value = this.get(path); return callback(event, path, value); }); } } } get(path = []) { return path.reduce((acc, key) => (acc && acc[key] !== undefined ? acc[key] : undefined), this.$store); } set(path, value) { if (!Array.isArray(path) || path.length === 0) { throw new Error("La ruta debe ser un array de strings no vacío."); } const lastKey = path.pop(); const target = path.reduce((acc, key) => { if (!acc[key]) acc[key] = {}; return acc[key]; }, this.$store); target[lastKey] = value; path.push(lastKey); this._triggerParentEvents("set", path, value); } watch(path, callback) { const key = path.join("."); if (!this.events[key]) { this.events[key] = []; } this.events[key].push(callback); } unwatch(path, callback) { const key = path.join("."); if (this.events[key]) { this.events[key] = this.events[key].filter((cb) => { return cb !== callback; }); if (this.events[key].length === 0) { delete this.events[key]; } } } delete(path) { if (!Array.isArray(path) || path.length === 0) { throw new Error("La ruta debe ser un array de strings no vacío."); } const lastKey = path.pop(); const target = this.get(path); if (target && target.hasOwnProperty(lastKey)) { delete target[lastKey]; this._triggerParentEvents("delete", path); } } push(path, value) { const array = this.get(path); if (Array.isArray(array)) { array.push(value); this._triggerParentEvents("push", path, array); } } pop(path) { const array = this.get(path); if (Array.isArray(array)) { const value = array.pop(); this._triggerParentEvents("pop", path, array); return value; } } unshift(path, value) { const array = this.get(path); if (Array.isArray(array)) { array.unshift(value); this._triggerParentEvents("unshift", path, array); } } shift(path) { const array = this.get(path); if (Array.isArray(array)) { const value = array.shift(); this._triggerParentEvents("shift", path, array); return value; } } add(path, key, value) { const object = this.get(path); if (object && typeof object === 'object' && !Array.isArray(object)) { object[key] = value; this._triggerParentEvents("add", path, object); } } remove(path, key) { const object = this.get(path); if (object && typeof object === 'object' && !Array.isArray(object)) { delete object[key]; this._triggerParentEvents("remove", path, object); } } splice(path, start, deleteCount, ...items) { const array = this.get(path); if (Array.isArray(array)) { const result = array.splice(start, deleteCount, ...items); this._triggerParentEvents("splice", path, array); return result; } } extend(path, newProps) { const object = this.get(path); if (object && typeof object === 'object' && !Array.isArray(object)) { Object.assign(object, newProps); this._triggerParentEvents("extend", path, object); } } multiextend(...extensions) { for (let index = 0; index < extensions.length; index++) { const { selector, value: targetValue, mode, modifier } = extensions[index]; this.modify(selector, currentValue => { let lastValue = currentValue; Set_value: { if (mode === "assign") { lastValue = Object.assign(currentValue, targetValue); } else if (mode === "default") { lastValue = Object.assign({}, targetValue, currentValue); } else if (mode === "set") { lastValue = targetValue; } else if (mode === "concat") { lastValue = currentValue.concat(targetValue); } } Run_modifier: { if (modifier) { const result = modifier(lastValue); if (typeof result !== "undefined") { return result; } } return lastValue; } }); } } modify(path, modifier) { const currentValue = this.get(path); const newValue = modifier(currentValue); const isNotSame = newValue !== currentValue; const isNotUndefined = typeof newValue !== "undefined"; if (isNotUndefined && isNotSame) { this.set(path, newValue); } } hydrate(file) { const ufs = UFS_manager.create(this.$storePath); this.$store = JSON.parse(ufs.read_file(file)); } dehydrate(file) { const ufs = UFS_manager.create(this.$storePath); ufs.write_file(file, JSON.stringify(this.$store)); } }; Store.default = Store; return Store;
const LswTestRegistry = class { static create(...args) { return new this(...args); } constructor() { this.$tests = {}; } define(id, callback) { this.mustMiss(id); this.$tests[id] = callback; } mustHave(id) { if(typeof id !== "string") { throw new Error(`Required parameter 1 «id=${id}» to be string in «$tests» on «LswTestRegistry.mustHave»`); } if(!(id in this.$tests)) { throw new Error(`Required parameter 1 «id=${id}» to be an identifier in «$tests» on «LswTestRegistry.mustHave»`); } } mustMiss(id) { if(typeof id !== "string") { throw new Error(`Required parameter 1 «id=${id}» to be string in «$tests» on «LswTestRegistry.mustMiss»`); } if(id in this.$tests) { throw new Error(`Required parameter 1 «id=${id}» to NOT be an identifier in «$tests» on «LswTestRegistry.mustMiss»`); } } pick(id) { this.mustHave(id); return this.$tests[id]; } run(id) { this.mustHave(id); return this.$tests[id].call(); } all() { const allKeys = Object.keys(this.$tests); const output = []; for(let index=0; index<allKeys.length; index++) { const key = allKeys[index]; const testCallback = this.$tests[key]; testCallback.$lswTestId = key; output.push(testCallback); } return Object.values(this.$tests); } collect(id, testCallback) { this.mustMiss(id); const testSource = testCallback.toString(); const testFunction = new Function(`return LswTester.collection(${JSON.stringify(id)}, ${testSource});`) this.$tests[id] = testFunction; return { [id]: testFunction.toString().length + " chars", }; } }; LswTestRegistry.defaultInstance = LswTestRegistry.create(); return LswTestRegistry.defaultInstance;
const LswAssertion = class { static create(...args) { return new this(...args); } $trace(method) { this.$tester.$trace(method); } constructor(tester, submodule, result = undefined, message = undefined) { this.$tester = tester; this.$module = submodule; this.$moduleId = submodule.id; this.$result = result; this.$message = message; } as(message = undefined) { this.$trace("LswAssertion.as"); this.$message = message; return this; } that(result = undefined) { this.$trace("LswAssertion.that"); this.$result = result; if(this.$result === true) { this.$tester.hookedBy("onSuccessAssertion", { tester: this.$tester, value: this.$module, assertionText: this.$message, assertionResult: this.$result }); } else { this.$tester.hookedBy("onErrorAssertion", { tester: this.$tester, value: this.$module, assertionText: this.$message, assertionResult: this.$result }); } } }; const createAssert = (tester, submodule) => { return LswAssertion.create(tester, submodule); }; const LswTester_BasicLayer = class { async innerLoad() { this.$trace("LswTester.innerLoad"); return this.innerLoadTest(this.$composition, [], null); } innerCheckIsNotTyped(val, path) { this.$trace("LswTester.innerCheckIsNotTyped"); if (typeof val.fromType === "string") { throw new Error(`Required test on index «${path.join(".")}» to not have property «fromType» resolved on «LswTester.innerCheckIsNotTyped»`); } } innerTypeAsUrl(val, path) { this.$trace("LswTester.innerTypeAsUrl"); this.innerCheckIsNotTyped(val, path); val.fromType = "url"; } innerTypeAsFile(val, path) { this.$trace("LswTester.innerTypeAsFile"); this.innerCheckIsNotTyped(val, path); val.fromType = "file"; } innerTypeAsId(val, path) { this.$trace("LswTester.innerTypeAsId"); this.innerCheckIsNotTyped(val, path); val.fromType = "id"; } innerTypeAsCallback(val, path) { this.$trace("LswTester.innerTypeAsCallback"); this.innerCheckIsNotTyped(val, path); val.fromType = "callback"; } innerTypeAsCollection(val, path) { this.$trace("LswTester.innerTypeAsCollection"); this.innerCheckIsNotTyped(val, path); val.fromType = "collection"; } addDefinition(test, pathToTest = false) { this.$trace("LswTester.addDefinition"); if (pathToTest) { test.path = pathToTest; } if (test.id in this.$definitions) { throw new Error(`Required property «id» in test on index «${pathToTest.join(".")}» to be a non-duplicated id for «this.$definitions» on «LswTester.addDefinition»`); } this.$definitions[test.id] = test.path; } innerLoadTest(val, path, parent = null, parentIndex = []) { this.$trace("LswTester.innerLoadTest"); this.addDefinition(val, path); if (val.fromUrl) { this.innerTypeAsUrl(val, path); } if (val.fromFile) { this.innerTypeAsFile(val, path); } if (val.fromId) { this.innerTypeAsId(val, path); } if (val.fromCallback) { this.innerTypeAsCallback(val, path); } if (val.fromCollection) { this.innerTypeAsCollection(val, path); for (let index = 0; index < val.fromCollection.length; index++) { const subval = val.fromCollection[index]; this.innerLoadTest(subval, path.concat(["fromCollection", index]), val, path); } } } async innerRun() { this.$trace("LswTester.innerRun"); this.hookedBy("onStartTester", []); let results = new Error(`Tester «${this.$composition.id}» was not started yet`); try { results = await this.innerRunTests(this.$composition, [], null, [], []); this.hookedBy("onSuccessTester", [results]); } catch (error) { this.hookedBy("onErrorTester", [error]); results = results.concat([error]); } finally { this.hookedBy("onEndTester", [results]); return results; } } innerHandleCallback(val, path, parent, parentIndex, accumulated) { this.$trace("LswTester.innerHandleCallback"); return val.fromCallback.call(this, { tester: this, test: val, testPath: path, assert: createAssert(this, val), }); } innerHandleUrl(val, path, parent, parentIndex, accumulated) { this.$trace("LswTester.innerHandleUrl"); return importer.scriptAsync(val.fromUrl, { $tester: this, $test: val, $testPath: path, $testParent: parent, $testParentPath: parentIndex, $accumulated: accumulated, assert: createAssert(this, val), }); } innerHandleFile(val, path, parent, parentIndex, accumulated) { this.$trace("LswTester.innerHandleFile"); return Vue.prototype.$lsw.fs.evaluateAsJavascriptFile(val.fromFile); } async innerRunTests(val, path, parent = null, parentIndex = [], accumulated = []) { this.$trace("LswTester.innerRunTests"); this.hookedBy("onStartModule", { tester:this, value:val, path, parent, parentIndex, accumulated }); let reported = undefined; RunningTest: { try { Manage_bad_inputs: { if (typeof val !== "object") { throw new Error(`[ERROR: LswTester complain x004006] Required type of test «${typeof val}» on index «${path.join(".")}» to be an object on «LswTester.innerRunTests» in order to process module as test by «LswTester» instance`); } if (val === null) { break RunningTest; } if (!val.id) { throw new Error(`[ERROR: LswTester complain x004001] Required test «${typeof val}» on index «${path.join(".")}» to have property «id» on «LswTester.innerRunTests» in order to process module as test by «LswTester» instance`); } if (typeof val.id !== "string") { throw new Error(`[ERROR: LswTester complain x004002] Required test «${typeof val}» on index «${path.join(".")}» to have a string on property «id» on «LswTester.innerRunTests» in order to process module as test by «LswTester» instance`); } if (typeof val.fromType !== "string") { throw new Error(`[ERROR: LswTester complain x004003] Required test «${typeof val}» on index «${path.join(".")}» to have a string on property «fromType» on «LswTester.innerRunTests» in order to process module as test by «LswTester» instance. This indicates that the previous step «LswTester.innerLoad» DID NOT (while it SHOULD, or SHOULD HAVE arised an error otherwise) mark this test module as a known type`); } const validTypes = ["url", "file", "collection", "callback"]; if (validTypes.indexOf(val.fromType) === -1) { throw new Error(`[ERROR: LswTester complain x004008] Required test «${typeof val}» on index «${path.join(".")}» to have property «fromType» with a valid type instead of «${val.fromType}» on «LswTester.innerRunTests» in order to process module as test by «LswTester» instance. This indicates that the previous step «LswTester.innerLoad» DID NOT (while it SHOULD, or SHOULD HAVE arised an error otherwise) mark this test module as a known type`); } } if (val.fromType === "url") { reported = await this.innerHandleUrl(val, path, parent, parentIndex, accumulated); } else if (val.fromType === "callback") { reported = await this.innerHandleCallback(val, path, parent, parentIndex, accumulated); } else if (val.fromType === "file") { reported = await this.innerHandleFile(val, path, parent, parentIndex, accumulated); } else if (val.fromType === "collection") { const collection = val.fromCollection; reported = []; for (let index = 0; index < collection.length; index++) { const item = collection[index]; try { const result = await this.innerRunTests(item, path.concat(["fromCollection", index]), val, path, accumulated); reported.push(result); } catch (error) { if (this.$options.continueOnErrors === false) { throw error; } } } } this.hookedBy("onSuccessModule", { tester:this, value:val, path, parent, parentIndex, accumulated }); } catch (error) { this.$trace(`Error on test artifact «${val.id}» located at «${path.join(".")}» on LswTester instance`); if (this.$options.printErrors === true) { console.error(error); } this.hookedBy("onErrorModule", { tester:this, value:val, error, path, parent, parentIndex, accumulated }); if (this.$options.continueOnErrors === false) { throw error; } else { reported = error; } } } this.hookedBy("onEndModule", { tester:this, value:val, path, parent, parentIndex, accumulated, reported }); if (path.length === 0) { this.$result = reported } return reported; } } const LswTester = class extends LswTester_BasicLayer { static Assertion = LswAssertion; static createAssertion(...args) { return new this.Assertion(...args); } static create(...args) { return new this(...args); } $trace(method) { if (this.$options.trace) { console.log(`[trace][lsw-tester][${method}]`); } } static defaultOptions = { trace: false, continueOnErrors: false, printErrors: true, onAnything: false, onStartTester: false, onEndTester: false, onSuccessTester: false, onErrorTester: false, onStartModule: false, onFinishModule: false, onSuccessModule: false, onErrorModule: false, onSuccessAssertion: false, onErrorAssertion: false, }; static noop() { } constructor() { super(); this.reset(); } reset() { this.$isLoaded = false; this.$isExecuted = false; this.$definitions = {}; this.$options = Object.assign({}, this.constructor.defaultOptions); this.$composition = undefined; } hookedBy(callbackId, uniqueParameter = {}) { this.$trace("LswTester.hookedBy"); const isAnyEventCallback = typeof this.$options.onAnything === "function"; if (isAnyEventCallback) { this.$options.onAnything.call(this, { eventType: callbackId }, uniqueParameter); } const isCallback = typeof this.$options[callbackId] === "function"; if (!isCallback) { return false; } return this.$options[callbackId].call(this, uniqueParameter); } define(composition = {}) { this.$trace("LswTester.define"); if (typeof this.$composition !== "undefined") { throw new Error("Required property «$composition» to not be defined before on «LswTester.define»"); } this.$composition = composition; return this; } options(options) { this.$trace("LswTester.options"); this.$options = Object.assign({}, this.constructor.defaultOptions, options); return this; } onAnything(callback) { this.$trace("LswTester.onAnything"); this.$options.onAnything = callback; return this; } onStartTester(callback) { this.$trace("LswTester.onStartTester"); this.$options.onStartTester = callback; return this; } onEndTester(callback) { this.$trace("LswTester.onEndTester"); this.$options.onEndTester = callback; return this; } onSuccessTester(callback) { this.$trace("LswTester.onSuccessTester"); this.$options.onSuccessTester = callback; return this; } onErrorTester(callback) { this.$trace("LswTester.onErrorTester"); this.$options.onErrorTester = callback; return this; } onStartModule(callback) { this.$trace("LswTester.onStartModule"); this.$options.onStartModule = callback; return this; } onFinishModule(callback) { this.$trace("LswTester.onFinishModule"); this.$options.onFinishModule = callback; return this; } onSuccessModule(callback) { this.$trace("LswTester.onSuccessModule"); this.$options.onSuccessModule = callback; return this; } onErrorModule(callback) { this.$trace("LswTester.onErrorModule"); this.$options.onErrorModule = callback; return this; } onSuccessAssertion(callback) { this.$trace("LswTester.onSuccessAssertion"); this.$options.onSuccessAssertion = callback; return this; } onErrorAssertion(callback) { this.$trace("LswTester.onErrorAssertion"); this.$options.onErrorAssertion = callback; return this; } async load() { this.$trace("LswTester.load"); try { await this.innerLoad(); this.$isLoaded = true; return this; } catch (error) { console.error("[!] Failed «LswTester» on «load» phase with:", error); throw error; } } async run(skipLoad = false) { this.$trace("LswTester.run"); try { if(!skipLoad) { await this.load(); } await this.innerRun(); this.$isExecuted = true; } catch (error) { console.error("[!] Failed «LswTester» on «run» phase with:", error); throw error; } finally { return this; } } getStatus() { this.$trace("LswTester.getStatus"); return this.$composition; } }; LswTester.global = LswTester.create();
const TestSettings = class { static AVAILABLE_MODES = ["normally", "never", "always", "only"]; constructor(testObject) { this.$test = testObject; } timeout(value = undefined) { if(typeof value !== "number") { throw new Error(`Required parameter 1 «value=${typeof value}» to be number on «TestSettings.mode»`) } this.$test.timeoutLimit = value; return this; } mode(mode) { if(this.constructor.AVAILABLE_MODES.indexOf(mode) === -1) { throw new Error(`Required parameter 1 «mode=${mode}» to be one of: «${this.constructor.AVAILABLE_MODES.join("|")}» on «TestSettings.mode»`) } this.$test.mode = mode; return this; } onError(onErrorCallback) { if(typeof onErrorCallback !== "function") { throw new Error(`Required parameter 1 «onErrorCallback=${typeof onErrorCallback}» to be string on «TestSettings.onError»`) } this.$test.errorHandler = onErrorCallback; return this; } onSuccess(onSuccessCallback) { if(typeof onSuccessCallback !== "function") { throw new Error(`Required parameter 1 «onSuccessCallback=${typeof onSuccessCallback}» to be string on «TestSettings.onError»`) } this.$test.successHandler = onSuccessCallback; return this; } }; const LswTester = class { static run(...args) { return this.create(...args).run(); } static create(...args) { return new this(...args); } static STATES = { DEFINED: "defined", STARTED: "started", PASSED: "passed", FAILED: "failed", } static formatDate(dateObject = new Date()) { const anio = ("" + (dateObject.getFullYear() ?? 0)).padStart(4, '0'); const mes = ("" + ((dateObject.getMonth() ?? 0) + 1)).padStart(2, '0'); const dia = ("" + (dateObject.getDate() ?? 0)).padStart(2, '0'); const hora = ("" + (dateObject.getHours() ?? 0)).padStart(2, '0'); const minuto = ("" + (dateObject.getMinutes() ?? 0)).padStart(2, '0'); const segundo = ("" + (dateObject.getSeconds() ?? 0)).padStart(2, '0'); const milisegundo = ("" + (dateObject.getMilliseconds() ?? 0)).padStart(3, '0'); const laHora = `${hora}:${minuto}:${segundo}.${milisegundo}`; return `${anio}/${mes}/${dia} ${laHora}`; } static getTimeDifferenceBetweenDates(dateStr1, dateStr2) { const date1 = new Date(dateStr1); const date2 = new Date(dateStr2); return date2 - date1; } static getDefaultOptions(overrider = {}) { return Object.assign({}, { trace: true, successHandler: false, errorHandler: false, failureHandler: false, finishHandler: false, }, overrider); }; constructor(id, callback, options = {}) { if(typeof id !== "string") throw new Error("Required argument 1 to be string on «LswTester.constructor»"); if(typeof callback !== "function") throw new Error("Required argument 2 to be function on «LswTester.constructor»"); this.$options = this.constructor.getDefaultOptions(options); this.$id = id; this.$callback = callback; this.$queue = []; this.$state = { testCollectionId: this.$id, loaded: false, createdAt: this.constructor.formatDate(), startedAt: false, finishedAt: false, loadedAt: false, timeoutLimit: 4000, tests: { ids: [], all: [], }, }; Validate_parameters: { if(typeof this.$id !== "string") throw new Error(`Required parameter 1 «id=${typeof this.$id}» to be string on «LswTester.constructor»`); if(typeof this.$callback !== "function") throw new Error(`Required parameter 2 «callback=${typeof this.$callback}» to be function on «LswTester.constructor»`); if(typeof this.$options !== "object") throw new Error(`Required parameter 3 «options=${typeof this.$options}» to be object on «LswTester.constructor»`); if(!Array.isArray(this.$queue)) throw new Error(`Required property «$queue=${typeof this.$queue}» to be object on «LswTester.constructor»`); if(typeof this.$state !== "object") throw new Error(`Required property «$state=${typeof this.$state}» to be object on «LswTester.constructor»`); } } $trace(method) { if(this.$options.trace) { console.log(`[trace][lsw-tester] ${method}`); } } $createTestObject(id, action, attachedMode = "normally") { this.$trace("$createTestObject"); return { id: id, // Property «currently» can be: "started", "passed", "failed" or "defined". currently: this.constructor.STATES.DEFINED, took: undefined, action: action, mode: attachedMode, successHandler: undefined, errorHandler: undefined, timeoutLimit: undefined, }; } $createItFunction() { this.$trace("$createItFunction"); let it2 = undefined; // Cuidaico con esta variable porque si llamas al mismo «it» 2 veces, puede jugártela. let testObject = undefined; const it = function(id, action, attachedMode = "normally") { Ensure_id_uniqueness: { if(this.$state.tests.ids.indexOf(id) !== -1) { console.log(this.$state.tests.ids); throw new Error(`Required parameter 1 «id=${id}» to not be a repeated test identifier on «LswTester.it»`); } } Append_test_object: { testObject = this.$createTestObject(id, action, attachedMode); this.$state.tests.ids.push(id); this.$state.tests.all.push(testObject); } return it2; }; it2 = it.bind(this); it2.always = (id, action) => it2(id, action, "always"); it2.normally = (id, action) => it2(id, action, "normally"); it2.only = (id, action) => it2(id, action, "only"); it2.never = (id, action) => it2(id, action, "never"); it2.timeout = this.timeout.bind(this); it2.onSuccess = this.onSuccess.bind(this); it2.onError = this.onError.bind(this); it2.onFailure = this.onFailure.bind(this); return it2; } $handleLoadError(error) { this.$trace("$handleLoadError"); console.error("LswTester failed on «load» step:", error); throw error; } $handleRunError(error) { this.$trace("$handleRunError"); console.error("LswTester arised error on «run» step:", error); throw error; } $handleTestError(error, lastStartedTest) { this.$trace("$handleTestError"); By_tester_first: { if(typeof this.$options.errorHandler === "function") { const handlerFeedback = this.$options.errorHandler(error, lastStartedTest); if(typeof handlerFeedback !== "undefined") { return handlerFeedback; } } } By_test_second: { if(typeof lastStartedTest.errorHandler === "function") { const testHandlerFeedback = lastStartedTest.errorHandler(error, lastStartedTest); if(typeof testHandlerFeedback !== "undefined") { return testHandlerFeedback; } } else { console.error("LswTester arised error on «test» step:", error); throw error; } } } $handleTestSuccess(lastStartedTest) { this.$trace("$handleTestSuccess"); By_tester_first: { if(typeof this.$options.successHandler === "function") { const handlerFeedback = this.$options.successHandler(lastStartedTest); if(typeof handlerFeedback !== "undefined") { return handlerFeedback; } } } By_test_second: { if(typeof lastStartedTest.successHandler === "function") { const testHandlerFeedback = lastStartedTest.successHandler(lastStartedTest); if(typeof testHandlerFeedback !== "undefined") { return testHandlerFeedback; } } else { // @OK. } } if(typeof lastStartedTest.successHandler === "function") { return lastStartedTest.successHandler(lastStartedTest); } else { // @OK. } } $handleTestTimeout(indexTest) { const testObject = this.$state.tests.all[indexTest]; const isStarted = testObject.currently === this.constructor.STATES.STARTED; if(isStarted) { testObject.currently = this.constructor.STATES.FAILED; testObject.failureReason = "timed out"; } } timeout(limit) { this.$trace("timeout"); this.$state.timeoutLimit = limit; } onSuccess(successHandler) { this.$trace("onSuccess"); this.$options.successHandler = successHandler; } onError(errorHandler) { this.$trace("onError"); this.$options.errorHandler = errorHandler; } onFailure(failureHandler) { this.$trace("onFailure"); this.$options.failureHandler = failureHandler; } async load(ignoreDuplication = false) { this.$trace("load"); try { Validate_state_before_reloading_by_error: { const wasLoadedBefore = typeof this.$state.loadedAt === "string"; const allowsDuplication = !ignoreDuplication; if(wasLoadedBefore && !allowsDuplication) { throw new Error("Cannot call «LswTester.load» more than once"); } } this.$state.loadedAt = this.constructor.formatDate(); const it = this.$createItFunction(); await this.$callback(it); this.$state.loaded = true; } catch (error) { this.$handleLoadError(error); } } async run() { this.$trace("run"); try { await this.load(); const testList = this.$state.tests.all; const wasActivatedOnlyMode = this.$state.tests.all.reduce((out, testObject) => { out = out || (testObject.mode === 'only'); return out; }, false); let lastStartedTest = undefined; this.$state.startedAt = this.constructor.formatDate(); Executing_tests: for(let indexTest=0; indexTest<testList.length; indexTest++) { try { const testObject = testList[indexTest]; lastStartedTest = testObject; const { id, mode, action, currently, } = testObject; let testTimeoutId = undefined; Ignore_by_mode: { if(mode === "never") { continue Executing_tests; } if(wasActivatedOnlyMode) { if(mode === "normally") { continue Executing_tests; } } } Throw_on_state_conflict: { if(currently !== this.constructor.STATES.DEFINED) { throw new Error(`Cannot run test «${id}» because its state is «${currently}» on «LswTester.run»`); } } Change_state_to_started: { this.$state.tests.all[indexTest].currently = this.constructor.STATES.STARTED; this.$state.tests.all[indexTest].startedAt = this.constructor.formatDate(); } let testPromise = undefined; Execute_test_and_reserve_promise: { const testSettings = new TestSettings(testObject); testPromise = action(testSettings); } Start_timeout_callback_to_fail_test: { // Priorizamos el timeout del test al del tester: const timeoutLimit = testObject.timeoutLimit || this.$state.timeoutLimit; if((typeof timeoutLimit !== "number") || (timeoutLimit < 0)) { break Start_timeout_callback_to_fail_test; } const timeoutHandler = () => { this.$handleTestTimeout(indexTest); }; testTimeoutId = setTimeout(timeoutHandler.bind(this), timeoutLimit); } Wait_for_test: { await testPromise; } Check_state_before_confirm_test_is_passed: { clearTimeout(testTimeoutId); const currentState = this.$state.tests.all[indexTest].currently; const isStarted = currentState === this.constructor.STATES.STARTED; if(!isStarted) { throw new Error(`Test «${id}» was already considered «${currentState}» and cannot promote it to «passed» on «LswTester.run»`); } } Change_state_to_passed: { this.$state.tests.all[indexTest].finishedAt = this.constructor.formatDate(); this.$state.tests.all[indexTest].currently = this.constructor.STATES.PASSED; this.$state.tests.all[indexTest].took = this.constructor.getTimeDifferenceBetweenDates(this.$state.tests.all[indexTest].startedAt, this.$state.tests.all[indexTest].finishedAt); } Trigger_success_handler: { await this.$handleTestSuccess(lastStartedTest); } } catch (error) { let errorFeedback = undefined; Change_state_to_failed: { this.$state.tests.all[indexTest].finishedAt = this.constructor.formatDate(); this.$state.tests.all[indexTest].currently = this.constructor.STATES.FAILED; this.$state.tests.all[indexTest].took = this.constructor.getTimeDifferenceBetweenDates(this.$state.tests.all[indexTest].startedAt, this.$state.tests.all[indexTest].finishedAt); this.$state.tests.all[indexTest].failureReason = error instanceof Error ? `${error.name}: ${error.message}` : error; console.log(error); } Trigger_error_handler: { errorFeedback = await this.$handleTestError(error, lastStartedTest); } Interrupt_tests_if_handler_returns_0: { if(errorFeedback === false) { break Executing_tests; } } } } } catch (error) { this.$handleRunError(error); } finally { this.$state.finishedAt = this.constructor.formatDate(); if(typeof this.$options.finishHandler === "function") { this.$options.finishHandler(this.getReport(), this); } return this; } } getReport() { this.$trace("getReport"); let testResult = "passed"; for(let index=0; index<this.$state.tests.all.length; index++) { const testObject = this.$state.tests.all[index]; const isOk = ["passed", "defined"].indexOf(testObject.currently) !== -1; if(!isOk) { testResult = testObject.currently; } } const tookMilliseconds = this.constructor.getTimeDifferenceBetweenDates(this.$state.startedAt, this.$state.finishedAt); return { result: testResult, took: tookMilliseconds, ...this.$state, }; } getReportAsJson(compressed = false) { this.$trace("getReportAsJson"); const report = this.getReport(); if(!compressed) { return JSON.stringify(report, null, 2); } else { return JSON.stringify(report); } } static collection = this.run; };
// Example of usage: /* LswTestRegistry.collect("Lsw Fifth Test", async function (test) { test("can wait 1 second", async function () { await new Promise((resolve, reject) => { setTimeout(() => { resolve(); }, 1000); }); }); }); //*/ return LswTestRegistry;
// Example of usage: /* LswTestRegistry.collect("Lsw Fifth Test", async function (test) { test("can wait 1 second", async function () { await new Promise((resolve, reject) => { setTimeout(() => { resolve(); }, 1000); }); }); }); //*/ return LswTestRegistry;
// exported to LswTimer const LswTimer = { utils: {}, parser: Timeformat_parser, }; LswTimer.utils.formatHour = function (horaInput, minutoInput) { const hora = ("" + horaInput).padStart(2, '0'); const minuto = ("" + minutoInput).padStart(2, '0'); return `${hora}:${minuto}`; }; LswTimer.utils.formatDatestringFromDate = function (dateObject, setUntilDay = false, setMeridian = false, setSeconds = false, setOnlyHour = false, setMilliseconds = false) { if (typeof dateObject === "undefined") { return undefined; } const anio = ("" + (dateObject.getFullYear() ?? 0)).padStart(4, '0'); const mes = ("" + ((dateObject.getMonth() ?? 0) + 1)).padStart(2, '0'); const dia = ("" + (dateObject.getDate() ?? 0)).padStart(2, '0'); if (setUntilDay) { return `${anio}/${mes}/${dia}`; } const hora = ("" + (dateObject.getHours() ?? 0)).padStart(2, '0'); const minuto = ("" + (dateObject.getMinutes() ?? 0)).padStart(2, '0'); const segundo = setSeconds ? ("" + (dateObject.getSeconds() ?? 0)).padStart(2, '0') : false; let milisegundo = false; if(setMilliseconds) { milisegundo = dateObject.getMilliseconds(); } const laHora = `${hora}:${minuto}${typeof segundo !== "boolean" ? (':' + segundo) : ''}${milisegundo ? '.' + milisegundo : ''}${setMeridian ? hora >= 12 ? 'pm' : 'am' : ''}`; if (setOnlyHour) { return laHora; } return `${anio}/${mes}/${dia} ${laHora}`; }; LswTimer.utils.fromDateToDatestring = LswTimer.utils.formatDatestringFromDate; LswTimer.utils.getDateFromMomentoText = function (momentoText, setMeridian = false) { const momentoBrute = Timeformat_parser.parse(momentoText)[0]; const date = new Date(); if (momentoBrute.anio) { date.setFullYear(momentoBrute.anio); if (momentoBrute.mes === 0) { throw new Error("Cannot set «mes» to «0» in momento text on «LswTimer.utils.getDateFromMomentoText»"); } date.setMonth((momentoBrute.mes - 1) || 0); date.setDate(momentoBrute.dia || 0); } date.setHours(momentoBrute.hora || 0); date.setMinutes(momentoBrute.minuto || 0); date.setSeconds(momentoBrute.segundo || 0); date.setMilliseconds(0); return date; }; LswTimer.utils.fromDatestringToDate = LswTimer.utils.getDateFromMomentoText; LswTimer.utils.formatDatetimeFromMomento = function (momentoBrute, setMeridian = false) { const momento = LswTimer.utils.toPlainObject(momentoBrute); const anio = ("" + (momento.anio ?? 0)).padStart(4, '0'); const mes = ("" + (momento.mes ?? 0)).padStart(2, '0'); const dia = ("" + (momento.dia ?? 0)).padStart(2, '0'); const hora = ("" + (momento.hora ?? 0)).padStart(2, '0'); const minuto = ("" + (momento.minuto ?? 0)).padStart(2, '0'); return `${anio}/${mes}/${dia} ${hora}:${minuto}${setMeridian ? hora >= 12 ? 'pm' : 'am' : ''}`; }; LswTimer.utils.formatHourFromMomento = function (momentoBrute, setMeridian = false) { const momento = LswTimer.utils.toPlainObject(momentoBrute); const hora = ("" + (momento.hora ?? 0)).padStart(2, '0'); const minuto = ("" + (momento.minuto ?? 0)).padStart(2, '0'); return `${hora}:${minuto}${setMeridian ? hora >= 12 ? 'pm' : 'am' : ''}`; }; LswTimer.utils.formatHourFromMomentoCode = function (momentoCode, setMeridian = false) { const momentoBruteList = Timeformat_parser.parse(momentoCode); const momentoBrute = momentoBruteList[0]; const momento = LswTimer.utils.toPlainObject(momentoBrute); const hora = ("" + (momento.hora ?? 0)).padStart(2, '0'); const minuto = ("" + (momento.minuto ?? 0)).padStart(2, '0'); return `${hora}:${minuto}${setMeridian ? hora >= 12 ? 'pm' : 'am' : ''}`; }; LswTimer.utils.addDuracionToMomento = function (momentoBrute, duracion) { const momentoFinal = {}; const duracionParsed = Timeformat_parser.parse(duracion)[0]; const props = ["anio", "mes", "dia", "hora", "minuto", "segundo"]; const propsInDuracion = ["anios", "meses", "dias", "horas", "minutos", "segundos"]; for (let index = 0; index < props.length; index++) { const prop = props[index]; const propInDuracion = propsInDuracion[index]; const base = momentoBrute[prop] ?? 0; const aggregated = duracionParsed[propInDuracion] ?? 0; momentoFinal[prop] = base + aggregated; } return momentoFinal; }; LswTimer.utils.toPlainObject = function (obj) { const seen = new WeakSet(); return JSON.parse(JSON.stringify(obj, (key, value) => { if (typeof value === "object" && value !== null) { if (seen.has(value)) return undefined; // Ignora referencias circulares seen.add(value); } return value; })); }; LswTimer.utils.isDurationOrThrow = function (text) { const errorMessage = "It must be a duration only, like 0y 0mon 0d 0h 0min 0s 0ms"; try { const ast = Timeformat_parser.parse(text); const mainExpression = ast[0]; if (mainExpression.tipo !== "Duracion") { throw new Error(`Expression of type «${mainExpression.tipo}» is not valid. ${errorMessage}`); } } catch (error) { throw new Error(errorMessage); } return true; }; LswTimer.utils.isDatetimeOrThrow = function (text) { const errorMessage = "It must be a datetime only, like 2025/01/01 00:00"; try { const ast = Timeformat_parser.parse(text); const mainExpression = ast[0]; if (mainExpression.tipo !== "FechaHora") { throw new Error(errorMessage); } } catch (error) { throw new Error(errorMessage); } return true; }; LswTimer.utils.isDateOrThrow = function (text) { const errorMessage = "It must be a date only, like 2025/01/01"; try { const ast = Timeformat_parser.parse(text); const mainExpression = ast[0]; if (mainExpression.tipo !== "SoloFecha") { throw new Error(errorMessage); } } catch (error) { throw new Error(errorMessage); } return true; }; LswTimer.utils.isHourOrThrow = function (text) { const errorMessage = "It must be an hour only, like 00:00 or 23:00"; try { const ast = Timeformat_parser.parse(text); const mainExpression = ast[0]; if (mainExpression.tipo === "Hora") { throw new Error(errorMessage); } } catch (error) { throw new Error(errorMessage); } return true; }; LswTimer.utils.formatDateToSpanish = function (date) { const anio = date.getFullYear(); const month = date.getMonth(); const day = date.getDate(); const weekday = date.getDay(); const diaSemana = (() => { if (weekday === 0) return "Domingo"; if (weekday === 1) return "Lunes"; if (weekday === 2) return "Martes"; if (weekday === 3) return "Miércoles"; if (weekday === 4) return "Jueves"; if (weekday === 5) return "Viernes"; if (weekday === 6) return "Sábado"; })(); const mes = (() => { if (month === 0) return "Enero"; if (month === 1) return "Febrero"; if (month === 2) return "Marzo"; if (month === 3) return "Abril"; if (month === 4) return "Mayo"; if (month === 5) return "Junio"; if (month === 6) return "Julio"; if (month === 7) return "Agosto"; if (month === 8) return "Septiembre"; if (month === 9) return "Octubre"; if (month === 10) return "Noviembre"; if (month === 11) return "Diciembre"; })(); return `${diaSemana}, ${day} de ${mes} del ${anio}`; } LswTimer.utils.formatMomentoObjectToMomentoString = function (momento) { let out = ""; const { anio = false, mes = false, dia = false, hora = false, minuto = false, segundo = false, milisegundo = false } = momento; if (anio !== false) { out += ("" + anio).padStart(4, '0'); out += "/"; } if (mes !== false) { out += ("" + mes).padStart(2, '0'); out += "/"; } if (dia !== false) { out += ("" + dia).padStart(2, '0'); out += " "; } if (hora !== false) { out += ("" + hora).padStart(2, '0'); out += ":"; } if (minuto !== false) { out += ("" + minuto).padStart(2, '0'); out += ":"; } if (segundo !== false) { out += ("" + segundo).padStart(2, '0'); out += "."; } if (milisegundo !== false) { out += ("" + milisegundo).padStart(3, '0'); } return out.trim(); }; LswTimer.utils.areSameDayDates = function (date1, date2) { const areSameYear = date1.getFullYear() === date2.getFullYear(); const areSameMonth = date1.getMonth() === date2.getMonth(); const areSameDate = date1.getDate() === date2.getDate(); const areSameDay = areSameYear && areSameMonth && areSameDate; return areSameDay; }; LswTimer.utils.extractHourFromDatestring = function (date) { try { return date.split(" ")[1]; } catch (error) { console.log(error); return date; } }; LswTimer.utils.fromDateToHour = function (date, addSeconds = false) { try { const hora = date.getHours(); const minuto = date.getMinutes(); let out = ""; if (hora !== false) { out += ("" + hora).padStart(2, '0'); out += ":"; } if (minuto !== false) { out += ("" + minuto).padStart(2, '0'); } if (addSeconds) { const segundo = date.getSeconds(); out += ":"; out += ("" + segundo).padStart(2, '0'); } return out; } catch (error) { console.log(error); return date; } }; LswTimer.utils.parseToNumberOrReturn = function (txt, defaultValue) { const output = parseFloat(txt); return isNaN(output) ? defaultValue : output; }; LswTimer.utils.fromDurationstringToMilliseconds = function (durationString) { const lines = Timeformat_parser.parse(durationString); if (lines.length === 0) { return 0; } else if (lines.length !== 1) { throw new Error("Only accepted 1 expression") } const line = lines[0]; let ms = 0; if (line.anios) { ms += line.anios * 1000 * 60 * 60 * 24 * 365; } if (line.meses) { ms += line.meses * 1000 * 60 * 60 * 24 * 30; } if (line.dias) { ms += line.dias * 1000 * 60 * 60 * 24; } if (line.horas) { ms += line.horas * 1000 * 60 * 60; } if (line.minutos) { ms += line.minutos * 1000 * 60; } if (line.segundos) { ms += line.segundos * 1000; } if (line.milisegundos) { ms += line.milisegundos; } return ms; }; LswTimer.utils.fromMillisecondsToDurationstring = function (ms) { const units = { y: 1000 * 60 * 60 * 24 * 365, mon: 1000 * 60 * 60 * 24 * 30, d: 1000 * 60 * 60 * 24, h: 1000 * 60 * 60, min: 1000 * 60, s: 1000, ms: 1 }; let remaining = ms; const parts = []; for (const [unit, value] of Object.entries(units)) { const amount = Math.floor(remaining / value); if (amount > 0) { parts.push(`${amount}${unit}`); remaining %= value; } } return parts.join(' '); }; LswTimer.utils.multiplyDuration = function (duration, multiplier) { const operand = LswTimer.utils.parseToNumberOrReturn(multiplier, 0); const durationMiliSource = LswTimer.utils.fromDurationstringToMilliseconds(duration); const durationMiliDest = durationMiliSource * operand; const durationDest = LswTimer.utils.fromMillisecondsToDurationstring(durationMiliDest); return durationDest; }; return LswTimer;
// exported to TriggersClass class TriggersClass { static globMatch(patterns, list) { const matches = new Set(); const regexes = patterns.map(pattern => { let regexPattern = pattern .replace(/[-/\\^$+?.()|[\]{}]/g, "\\$&") // Escapa caracteres especiales .replace(/\\\*/g, ".*") // '*' => cualquier cosa return new RegExp(`^${regexPattern}$`); }); for (const item of list) { for (const regex of regexes) { if (regex.test(item)) { matches.add(item); break; } } } return Array.from(matches); } static create(...args) { return new this(...args); } all = {}; findTriggerCollectionByEventAndId(event, id) { return LswLifecycle.hooks.all[event].filter(trigger => trigger.id === id); } register(triggerNamePattern, triggerIdentifier, triggerCallback, triggerConfigurations = {}) { const { priority = 0 } = triggerConfigurations; // Default priority is 0 if (!this.all[triggerNamePattern]) { this.all[triggerNamePattern] = []; } this.all[triggerNamePattern].push({ id: triggerIdentifier, callback: triggerCallback, priority, }); } async emit(triggerName, parameters = {}) { const matchedTriggers = []; const allPatterns = Object.keys(this.all); // Encuentra patrones que coincidan con el nombre del evento const matchedPatterns = this.constructor.globMatch(allPatterns, [triggerName]); // Agrega todos los eventos coincidentes a la lista de disparos for (const pattern of matchedPatterns) { matchedTriggers.push(...this.all[pattern]); } // Ordena por prioridad descendente matchedTriggers.sort((a, b) => b.priority - a.priority); // Ejecuta los callbacks en orden const output = []; for (const trigger of matchedTriggers) { const result = await trigger.callback(parameters); output.push(result); } return output; } unregister(triggerIdentifier) { for (const pattern in this.all) { this.all[pattern] = this.all[pattern].filter( (trigger) => trigger.id !== triggerIdentifier ); if (this.all[pattern].length === 0) { delete this.all[pattern]; // Limpia patrones vacíos } } } } TriggersClass.default = TriggersClass; return TriggersClass;
class LswTyper { static parse(jsontypedText) { return LswTyperParser.parse(jsontypedText); } constructor(types = {}) { this.$types = types; } define(typeId, typeCallback) { this.$types[typeId] = typeCallback; } getTypeCallbackById(typeId) { return this.$types[typeId] || null; } parse(jsontypedText) { const ast = this.constructor.parse(jsontypedText); const json = this.constructor.resolveTypes(ast, this.$types); return JSON.parse(json); } static resolveTypes(input, reducers) { return JSON.stringify(input, (key, value) => { const isTyped = value && (typeof value === 'object') && ("$value" in value) && ("$type" in value); // Filtramos los que no son type: if (!isTyped) { return value; } console.log("is typed:", key, value); const $types = Array.isArray(value.$type) ? value.$type : [value.$type]; Iterating_possible_types: for(let index=0; index<$types.length; index++) { const reducerId = $types[index]; // Filtramos los que cuyo $type no se encuentra entre los reducers: if(!(reducerId in reducers)) { console.log("Id not found:", reducerId); continue Iterating_possible_types; } console.log("Id reduceable:", reducerId); // Aplicamos el reducer pertinente... const reducer = reducers[reducerId]; const result = reducer(value); // Y si devuelven diferente de undefined... console.log("Reduction:", result); if (typeof result !== "undefined") { // Los retornamos. return result; } } // Y si no devolvemos lo normal. return value; }, 2); } } globalThis.$lswTyper = new LswTyper(); return LswTyper;
Vue.component("LswAppsViewerPanel", { template: $template, props: { }, data() { this.$trace("lsw-apps-viewer-panel.data"); return { isOpened: false, selectedApplication: 'despues', // 'antes', 'despues' accionesAntes: false, accionesDespues: false, horaActual: LswTimer.utils.fromDateToHour(new Date()), }; }, methods: { selectApplication(section) { this.$trace("lsw-apps-viewer-panel.methods.selectApplication"); Gestiona_casos_excepcionales: { if(section === "js consola") { // Activamos eruda en lugar de cambiar de pestaña: return LswConsoleHooker.toggleConsole(); } } this.$lsw.dialogs.minimizeAll(); this.selectedApplication = section; Cargas_segun_aplicacion: { if (["antes", "despues"].indexOf(section) !== -1) { this.loadAcciones(); } else { this.$forceUpdate(true); } } }, getSimboloEstadoAccion(estado) { return (estado === "completada") ? "💚" : (estado === "pendiente") ? "❓" : (estado === "fallida") ? "🔥" : ""; }, async loadAcciones() { this.$trace("lsw-apps-viewer-panel.methods.loadAcciones"); const output = await this.$lsw.database.selectMany("Accion"); const estaHora = (() => { const d = new Date(); d.setMinutes(0); return d; })(); const accionesAntes = []; const accionesDespues = []; output.forEach(accion => { try { const dateAccion = LswTimer.utils.fromDatestringToDate(accion.tiene_inicio); const areSameDay = LswTimer.utils.areSameDayDates(dateAccion, estaHora); if (!areSameDay) return; if (dateAccion >= estaHora) { accionesDespues.push(accion); } else { accionesAntes.push(accion); } } catch (error) { console.log(error); } }); this.accionesAntes = accionesAntes.sort(this.getSorterOfAccionesAntes()); this.accionesDespues = accionesDespues.sort(this.getSorterOfAccionesDespues()); this.$forceUpdate(true); }, getSorterOfAccionesAntes() { this.$trace("lsw-apps-viewer-panel.methods.getSorterOfAccionesAntes"); return function (accion1, accion2) { let inicio1, inicio2; try { inicio1 = LswTimer.utils.fromDatestringToDate(accion1.tiene_inicio); } catch (error) { return 1; } try { inicio2 = LswTimer.utils.fromDatestringToDate(accion2.tiene_inicio); } catch (error) { return -1; } const firstIsLower = inicio1 < inicio2; return firstIsLower ? 1 : -1; }; }, getSorterOfAccionesDespues() { this.$trace("lsw-apps-viewer-panel.methods.getSorterOfAccionesDespues"); return function (accion1, accion2) { let inicio1, inicio2; try { inicio1 = LswTimer.utils.fromDatestringToDate(accion1.tiene_inicio); } catch (error) { return 1; } try { inicio2 = LswTimer.utils.fromDatestringToDate(accion2.tiene_inicio); } catch (error) { return -1; } const firstIsLower = inicio1 <= inicio2; return firstIsLower ? -1 : 1; }; }, async alternarEstado(accion) { this.$trace("lsw-apps-viewer-panel.methods.alternarEstado"); const nextEstado = accion.tiene_estado === "pendiente" ? "completada" : accion.tiene_estado === "completada" ? "fallida" : "pendiente"; await this.$lsw.database.update("Accion", accion.id, { ...accion, tiene_estado: nextEstado }); await this.loadAcciones(); }, async reloadPanel() { this.$trace("lsw-apps-viewer-panel.methods.reloadPanel"); await this.loadAcciones(); }, async openNotaUploader() { this.$trace("lsw-apps-viewer-panel.methods.openNotaUploader", arguments); const response = await LswUtils.openAddNoteDialog(); if (typeof response !== "object") { return; } await this.$lsw.database.insert("Nota", response); }, openWikiExplorer() { this.$trace("lsw-windows-main-tab.methods.openWikiExplorer", arguments); this.$dialogs.open({ id: "wiki-explorer-" + LswRandomizer.getRandomString(5), title: "Wiki explorer", template: `<div class="pad_2"><lsw-wiki /></div>`, }); }, async openArticuloUploader() { this.$trace("lsw-windows-main-tab.methods.openArticuloUploader", arguments); const response = await LswUtils.openAddArticuloDialog(); if (typeof response !== "object") { return; } await this.$lsw.database.insert("Articulo", response); }, updateHoraActual() { this.$trace("lsw-windows-main-tab.methods.updateHoraActual", arguments); this.horaActual = LswTimer.utils.fromDateToHour(new Date()); } }, watch: {}, async mounted() { try { this.$trace("lsw-apps-viewer-panel.mounted"); await this.loadAcciones(); } catch (error) { console.log(error); } }, });
Vue.component("LswAppsViewerButton", { template: $template, props: { viewer: { type: Object, required: true, } }, data() { this.$trace("lsw-apps-viewer-button.data"); return { isOpened: false, }; }, methods: { toggleOpen() { this.$trace("lsw-apps-viewer-button.methods.toggleOpen"); this.isOpened = !this.isOpened; }, open() { this.$trace("lsw-apps-viewer-button.methods.open"); this.isOpened = true; }, close() { this.$trace("lsw-apps-viewer-button.methods.close"); this.isOpened = false; }, selectApplication(application) { this.$trace("lsw-apps-viewer-button.methods.selectApplication"); console.log(this.viewer); const isSame = this.viewer.selectedApplication === application; if(!isSame) { this.viewer.selectApplication(application); } else { // @NOTHING. } this.close(); }, openHomepage() { this.selectApplication("homepage"); }, openEventTracker() { this.selectApplication("event-tracker"); } }, watch: {}, async mounted() { try { this.$trace("lsw-apps-viewer-button.mounted"); } catch (error) { console.log(error); } }, });
Vue.component("LswAutomensajesViewer", { template: $template, props: {}, data() { this.$trace("lsw-automensajes-viewer.data"); return { isMounted: false, automensajes: [], selectedAutomensaje: undefined, selectedFontsize: 12, automessagingId: undefined, automessagingSeconds: 0, simboloActual: "🕓", // "✴️", // "♠️", // simboloActual: LswRandomizer.getRandomItem("🌅🌄🌠🎇🎆🌇🌆🏙🌃🌌🌉🌁".split("")), // simboloActual: LswRandomizer.getRandomItem("🐶🐱🐵🐗🐴🐌🐜🌋🏭🏢🏬🏣🚀🛸🚁🎲🎯🎳✴️🗽🗼🛟🎱🐞🌝🌛🌜🌚🌕🌖🌗🌘🌑🌒🌓🌔🌙🌎🌍🌏🪐💫⭐️🌟✨⚡️☄️💥🔥🌪🌈🐉🐲🐦🔥🌵🎄🌲🌳🌴🪹🪺🪵🌱🌿🍀🍁🍄🍄🟫🌾💐🌷🪷🌹🥀🌺🎪🤹🤹♂️🤹♀️🎭🎨🎼🎹🥁🪘🪇🎷🎺🪗🎸🪕🎻🪈♟🎰🧩🚗🚕🚙🎬🎤🎧💧💦🫧☔️☂️🌊🍏🍎🍐🍊🍋🍋🟩🍌🍉🍇🍓🫐🍈🍒🍑🥭🍍🥥🥝🍅🍆🥑🥦🫛".split("")), }; }, methods: { procedureForPicas() { this.$trace("LswAutomensajesViewer.methods.procedureForPicas", []); this.selectApplication("despues"); }, async loadAutomensajes() { this.$trace("LswAutomensajesViewer.methods.loadAutomensajes", []); const automensajes = await this.$lsw.fs.evaluateAsDotenvFileOrReturn("/kernel/settings/automessages.env", {}); this.automensajes = Object.keys(automensajes); }, async sendAutomessage() { this.$trace("LswAutomensajesViewer.methods.sendAutomessage", []); const availableAutomensajes = this.automensajes.filter(a => { if(typeof this.selectedAutomensaje !== "string") return true; return a !== this.selectedAutomensaje; }); const nextAutomensaje = LswRandomizer.getRandomItem(availableAutomensajes); const nextFontsize = this.calculateFontsize(nextAutomensaje); this.selectedFontsize = nextFontsize; this.selectedAutomensaje = nextAutomensaje; }, calculateFontsize(text) { this.$trace("LswAutomensajesViewer.methods.calculateFontsize", []); const textLength = text.length; if(textLength < 10) { return 18; } else if(textLength < 20) { return 16; } else if(textLength < 30) { return 14; } else { return 12; } }, async startAutomessaging() { this.$trace("LswAutomensajesViewer.methods.startAutomessaging", []); await this.loadAutomensajes(); await this.sendAutomessage(); await this.continueAutomessaging(); }, async continueAutomessaging() { this.$trace("LswAutomensajesViewer.methods.continueAutomessaging", []); clearTimeout(this.automessagingId); this.automessagingSeconds = LswRandomizer.getRandomIntegerBetween(60,120); this.automessagingId = setTimeout(() => this.sendAutomessage(), this.automessagingSeconds * 1000); }, stopAutomessaging() { this.$trace("LswAutomensajesViewer.methods.stopAutomessaging"); clearTimeout(this.automessagingId); }, async refreshAutomessaging() { this.$trace("LswAutomensajesViewer.methods.refreshAutomessaging", []); this.stopAutomessaging(); this.startAutomessaging(); this.$window.changeBackgroundImage(); }, goToDesktop() { this.$trace("LswAutomensajesViewer.methods.goToDesktop", []); this.$lsw.windows.hide(); this.$refs.appPanel.selectApplication("none"); }, selectApplication(application) { this.$trace("LswAutomensajesViewer.methods.selectApplication", []); this.$refs.appPanel.selectApplication(application); } }, watch: {}, async mounted() { try { this.$trace("lsw-automensajes-viewer.mounted"); this.$window.$automensajesUi = this; // this.startAutomessaging(); this.isMounted = true; this.refreshAutomessaging(); } catch(error) { console.log(error); } }, unmount() { this.$trace("lsw-automensajes-viewer.unmount"); this.stopAutomessaging(); } });
Vue.component("LswFastDatetimeControl", { template: $template, props: { mode: { type: String, default: () => "datetime", // can also be: "date" }, initialValue: { type: [Date, String], default: null, }, onChangeDate: { type: Function, default: () => {} } }, data() { this.$trace("lsw-fast-datetime-control.data"); return { value: this.adaptDate(this.initialValue || new Date()), isEditable: false, }; }, methods: { adaptDate(dateInput) { this.$trace("lsw-fast-date-control.methods.adaptDate"); if(dateInput instanceof Date) { return LswTimer.utils.fromDateToDatestring(dateInput, this.mode === "date"); } return dateInput; }, getValue() { this.$trace("lsw-fast-date-control.methods.getValue"); return this.value; }, toggleEditable() { this.$trace("lsw-fast-datetime-control.methods.toggleEditable"); this.isEditable = !this.isEditable; }, showEditable() { this.$trace("lsw-fast-datetime-control.methods.showEditable"); this.isEditable = true; }, hideEditable() { this.$trace("lsw-fast-datetime-control.methods.hideEditable"); this.isEditable = false; }, setValue(v) { this.$trace("lsw-fast-datetime-control.methods.propagateValue"); this.value = this.adaptDate(v); this.onChangeDate(this.value, this); this.hideEditable(); } }, watch: {}, mounted() { try { this.$trace("lsw-fast-datetime-control.mounted"); // } catch(error) { console.log(error); } } });
Vue.component("LswProtolangEditor", { template: $template, props: { initialContents: { type: String, default: () => "" } }, data() { this.$trace("lsw-protolang-editor.data"); return { error: false, result: false, contents: this.initialContents, placeholder: `rel correr > cardio * 1 > musculación * 0.3 >> propagador de correr * []` }; }, methods: { setError(error) { this.$trace("lsw-protolang-editor.methods.setError"); this.error = error; }, setResult(result) { this.$trace("lsw-protolang-editor.methods.setResult"); this.result = result; }, async validateCode() { this.$trace("lsw-protolang-editor.methods.validateCode"); try { const value = this.contents; const js = await Protolang.codify(value); console.log(js); this.setError(false); this.setResult(js); } catch (error) { this.setError(error); } }, async evaluateCode() { this.$trace("lsw-protolang-editor.methods.evaluateCode"); try { const value = this.contents; const js = await Protolang.codify(value); console.log(js); this.setError(false); } catch (error) { this.setError(error); } }, }, watch: {}, mounted() { try { this.$trace("lsw-protolang-editor.mounted"); this.$window.protolangEditor = this; } catch(error) { console.log(error); } } });
Vue.component("LswSpontaneousFormAccion", { template: $template, props: {}, data() { this.$trace("lsw-spontaneous-form-accion.data"); return {}; }, methods: {}, watch: {}, mounted() { try { this.$trace("lsw-spontaneous-form-accion.mounted"); // } catch(error) { console.log(error); } } });
Vue.component("LswSpontaneousFormArticulo", { template: $template, props: {}, data() { this.$trace("lsw-spontaneous-form-articulo.data"); return { ...this.getInitialContents(), opcionesGarantia: { "ns/nc": "ns/nc", "muy inestable": "muy inestable", "inestable": "inestable", "estable": "estable", "muy estable": "muy estable", "popular": "popular", } }; }, methods: { getInitialContents() { return Object.assign({}, { tiene_titulo: "", tiene_contenido: "", tiene_categorias: "", tiene_garantia: "ns/ns", tiene_fecha: LswTimer.utils.fromDateToDatestring(new Date()), tiene_tags: "", }); }, async addArticulo() { this.$trace("lsw-spontaneous-form-articulo.methods.addArticulo"); try { await this.$lsw.database.insert("Articulo", { tiene_titulo: this.tiene_titulo, tiene_contenido: this.tiene_contenido, tiene_categorias: this.tiene_categorias, tiene_garantia: this.tiene_garantia, tiene_fecha: this.tiene_fecha, tiene_tags: this.tiene_tags, }); this.$lsw.toasts.send({ title: "Artículo insertado", message: "El artículo fue insertado con éxito." }); Object.assign(this, this.getInitialContents()); } catch (error) { console.log(error); this.$lsw.toasts.send({ title: "Error al insertar artículo", message: "Hubo errores al insertar el artículo: " + error.message }); } } }, watch: {}, mounted() { try { this.$trace("lsw-spontaneous-form-articulo.mounted"); // } catch(error) { console.log(error); } } });
Vue.component("LswSpontaneousFormLista", { template: $template, props: {}, data() { this.$trace("lsw-spontaneous-form-lista.data"); return {}; }, methods: {}, watch: {}, mounted() { try { this.$trace("lsw-spontaneous-form-lista.mounted"); // } catch(error) { console.log(error); } } });
Vue.component("LswSpontaneousFormNota", { template: $template, props: { onSubmitted: { type: Function, default: () => {} } }, data() { this.$trace("lsw-spontaneous-form-nota.data"); return this.getInitialData({ }); }, methods: { getInitialData(extendedWith = {}) { return Object.assign({ tiene_titulo: "", tiene_contenido: "", tiene_fecha: LswTimer.utils.fromDateToDatestring(new Date()), tiene_categorias: "", }, extendedWith); }, async addNota() { this.$trace("lsw-spontaneous-form-nota.methods.addNota"); const nota = { tiene_titulo: this.tiene_titulo, tiene_contenido: this.tiene_contenido, tiene_fecha: this.tiene_fecha, tiene_categorias: this.tiene_categorias, }; if(nota.tiene_titulo.trim() === "") { const superaLimite = nota.tiene_contenido.length > 30; nota.tiene_titulo = nota.tiene_contenido.substr(0,30) + (superaLimite ? "..." : ""); } const notaId = await this.$lsw.database.insert("Nota", nota); Object.assign(this, this.getInitialData()); this.$forceUpdate(true); this.focusContenidos(); if(this.onSubmitted) { this.onSubmitted(notaId, nota, this); } }, focusContenidos() { this.$trace("lsw-spontaneous-form-nota.methods.addNota"); // this.$refs.tiene_contenido.focus(); } }, watch: {}, mounted() { try { this.$trace("lsw-spontaneous-form-nota.mounted"); // } catch(error) { console.log(error); } } });
Vue.component("LswSpontaneousFormRecordatorio", { template: $template, props: {}, data() { this.$trace("lsw-spontaneous-form-recordatorio.data"); return {}; }, methods: {}, watch: {}, mounted() { try { this.$trace("lsw-spontaneous-form-recordatorio.mounted"); // } catch(error) { console.log(error); } } });
Vue.component("LswSpontaneousTableAccion", { template: $template, props: {}, data() { this.$trace("lsw-spontaneous-table-accion.data"); return {}; }, methods: {}, watch: {}, mounted() { try { this.$trace("lsw-spontaneous-table-accion.mounted"); // } catch(error) { console.log(error); } } });
Vue.component("LswSpontaneousTableArticulo", { template: $template, props: {}, data() { this.$trace("lsw-spontaneous-table-articulo.data"); return {}; }, methods: {}, watch: {}, mounted() { try { this.$trace("lsw-spontaneous-table-articulo.mounted"); // } catch(error) { console.log(error); } } });
Vue.component("LswSpontaneousTableLista", { template: $template, props: {}, data() { this.$trace("lsw-spontaneous-table-lista.data"); return {}; }, methods: {}, watch: {}, mounted() { try { this.$trace("lsw-spontaneous-table-lista.mounted"); // } catch(error) { console.log(error); } } });
Vue.component("LswSpontaneousTableNota", { template: $template, props: {}, data() { this.$trace("lsw-spontaneous-table-nota.data"); return { allNotas: false, currentNotas: false, currentPage: 0, totalPages: 0, currentItemsPerPage: 100, selectedNotas: [], }; }, methods: { toggleNota(notaId) { this.$trace("lsw-spontaneous-table-nota.methods.toggleNota"); const pos = this.selectedNotas.indexOf(notaId); if (pos === -1) { this.selectedNotas.push(notaId); } else { this.selectedNotas.splice(pos, 1); } }, goToNextPage() { this.$trace("lsw-spontaneous-table-nota.methods.goToNextPage"); if ((this.currentPage + 1) < this.totalPages) { this.currentPage++; this.synchronizePagination(); } }, goToFirstPage() { this.$trace("lsw-spontaneous-table-nota.methods.goToFirstPage"); this.currentPage = 0; this.synchronizePagination(); }, goToLastPage() { this.$trace("lsw-spontaneous-table-nota.methods.goToLastPage"); this.currentPage = (this.totalPages - 1); this.synchronizePagination(); }, goToPreviousPage() { this.$trace("lsw-spontaneous-table-nota.methods.goToPreviousPage"); if (this.currentPage > 0) { this.currentPage--; this.synchronizePagination(); } }, async loadNotes() { this.$trace("lsw-spontaneous-table-nota.methods.loadNotes"); const allNotas = await this.$lsw.database.selectMany("Nota"); const sortedNotas = allNotas.sort((n1, n2) => { Ordena_por_urgencia: { const estado1 = n1.tiene_estado; const estado2 = n2.tiene_estado; const urgencia1 = estado1 === "urgente" ? 100 : 1; const urgencia2 = estado2 === "urgente" ? 100 : 1; if (urgencia1 > urgencia2) { return -1; } else if (urgencia1 < urgencia2) { return 1; } } Ordena_por_fecha: { const fecha1 = LswTimer.utils.fromDatestringToDate(n1.tiene_fecha); const fecha2 = LswTimer.utils.fromDatestringToDate(n2.tiene_fecha); if (fecha1 > fecha2) { return -1; } else if (fecha1 < fecha2) { return 1; } else { return -1; } } }); this.allNotas = sortedNotas; this.synchronizePagination(); }, synchronizePagination() { this.$trace("lsw-spontaneous-table-nota.methods.synchronizePagination"); this.totalPages = (() => { const totalFullPages = Math.floor(this.allNotas.length / this.currentItemsPerPage); const totalResidualPages = this.allNotas.length % this.currentItemsPerPage ? 1 : 0; return totalFullPages + totalResidualPages; })(); this.currentNotas = (() => { const paginatedNotas = []; const minIndex = this.currentPage * this.currentItemsPerPage; const maxIndex = (this.currentPage + 1) * this.currentItemsPerPage; for (let index = 0; index < this.allNotas.length; index++) { const nota = this.allNotas[index]; const validByMin = index >= minIndex; const validByMax = index < maxIndex; const isValid = validByMin && validByMax; if (isValid) { paginatedNotas.push(nota); } } return paginatedNotas; })(); }, async goToDeleteNota(row) { this.$trace("lsw-spontaneous-table-nota.methods.goToDeleteNota"); const confirmed = await this.$lsw.dialogs.open({ id: `eliminar-registro-Nota-#${row.id}-${LswRandomizer.getRandomString(5)}`, title: "Eliminar registro", template: ` <div> <div class="pad_2 font_weight_bold">ATENCIÓN: </div> <div class="pad_2">¿Seguro que quieres eliminar el registro <b>{{ tableId }}</b> cuyo <b>id</b>#<b>{{ rowId }}</b>?</div> <div class="pad_2"> <pre class="pad_2 codeblock">{{ JSON.stringify(rowValue, null, 2) }}</pre> </div> <hr class="margin_0" /> <div class="pad_2 text_align_right"> <button class="supermini danger_button" v-on:click="() => accept(true)">Eliminar</button> <button class="supermini " v-on:click="() => accept(false)">Cancelar</button> </div> </div> `, factory: { data: { tableId: "Nota", rowValue: row, rowId: row.id } } }); if (!confirmed) return false; await this.$lsw.database.delete("Nota", row.id); this.$lsw.toasts.send({ title: `Registro eliminado`, text: `El registro #${this.notaId} de «Nota» fue eliminado correctamente.` }); this.loadNotes(); }, async goToEditNota(notaId) { this.$trace("lsw-spontaneous-table-nota.methods.goToEditNota"); await this.$lsw.dialogs.open({ title: "Actualizar nota", template: ` <div> <lsw-schema-based-form :on-submit="(value) => submitCallback(value)" :on-delete-row="deleteCallback" :model="{ connection: $lsw.database, databaseId: 'lsw_default_database', tableId: 'Nota', rowId: notaId, }" /> </div> `, factory: { methods: { async submitCallback(value) { console.log("Submiting form: ", value); await this.$lsw.database.update("Nota", notaId, value); this.$lsw.toasts.send({ title: `Nueva actualización`, text: `El registro #${this.notaId} de «Nota» fue actualizado correctamente.` }); this.close(); }, async deleteCallback() { // EL DELETE YA LO HACE DENTRO, POR ALGUNA RAZÓN, NO ME ACABES DE PREGUNTAR. this.close(); } }, data: { notaId, } } }); }, goToAddNota() { this.$trace("lsw-spontaneous-table-nota.methods.goToEditNota"); const that = this; this.$lsw.dialogs.open({ title: "Añadir nota", template: `<lsw-spontaneous-form-nota :on-submitted="closeAndRefresh" />`, factory: { methods: { closeAndRefresh() { this.close(); that.loadNotes(); } } } }); }, async sendNotaToArticulos(nota) { this.$trace("lsw-spontaneous-table-nota.methods.sendNotaToArticulos"); const respuesta = await this.$lsw.dialogs.open({ title: "Pasar nota a artículo", template: ` <div class="pad_1"> <div>Vas a pasar la siguiente nota a artículo: </div> <div class="pad_2"> <pre class="codeblock">{{ nota }}</pre> </div> <div>¿Estás seguro?</div> <hr/> <div class="flex_row centered text_align_right"> <div class="flex_100"></div> <div class="flex_1 pad_right_1"> <button class="supermini danger_button" v-on:click="accept">Aceptar</button> </div> <div class="flex_1"> <button class="supermini" v-on:click="cancel">Cancelar</button> </div> </div> </div> `, factory: { data: { nota } }, }); if(respuesta === -1) return; const articuloNew = Object.assign({ tiene_titulo: '', tiene_fecha: '', tiene_categorias: '', tiene_contenido: '', tiene_garantia: '', tiene_tags: '', }, nota); delete articuloNew.id; await this.$lsw.database.insert("Articulo", articuloNew); await this.$lsw.database.delete("Nota", nota.id); this.$lsw.toasts.send({ title: "Nota a artículo bien", text: "La nota ha sido pasada a artículo correctamente", }); this.loadNotes(); } }, watch: {}, mounted() { try { this.$trace("lsw-spontaneous-table-nota.mounted"); this.loadNotes(); this.$window.sptt_notas = this; } catch (error) { console.log(error); } } });
Vue.component("LswSpontaneousTableRecordatorio", { template: $template, props: {}, data() { this.$trace("lsw-spontaneous-table-recordatorio.data"); return {}; }, methods: {}, watch: {}, mounted() { try { this.$trace("lsw-spontaneous-table-recordatorio.mounted"); // } catch(error) { console.log(error); } } });
(function (factory) { const name = "UFS_manager"; const modulo = factory(); if (typeof window !== 'undefined') { window[name] = modulo; } if (typeof module !== 'undefined') { module.exports = modulo; } if (typeof global !== 'undefined') { global[name] = modulo; } return modulo; })(function () { const FilesystemError = class extends Error { constructor(...args) { super(...args); this.name = "FilesystemError"; } } const UFS_manager_for_node = class { constructor() { // @OK } init() { return this; } trace(method, args = []) { // @INJECTION: from LSW const traceActivatedGlobally = (typeof Vue === "undefined") || (typeof Vue.prototype.$lsw === "undefined") || ((typeof Vue !== "undefined") && (typeof Vue.prototype.$lsw !== "undefined") && (Vue.prototype.$lsw.logger.$options.active)); if(traceActivatedGlobally) { console.log("[ufs][node-driver][" + method + "]", Array.from(args).map(arg => typeof (arg) + ": " + arg).join(", ")); } } resolve_path(...args) { this.trace("resolve_path", arguments); return require("path").resolve(...args); } get_current_directory() { this.trace("get_current_directory", arguments); return process.cwd(); } change_directory(node) { this.trace("change_directory", arguments); return process.chdir(node); } rename(node, node2) { this.trace("rename", arguments); return require("fs").renameSync(node, node2); } read_directory(node) { this.trace("read_directory", arguments); return require("fs").readdirSync(node).reduce((out, item) => { const subnode_fullpath = require("path").resolve(node, item); out[item] = require("fs").lstatSync(subnode_fullpath).isFile() ? "..." : {}; return out; }, {}); } read_file(node) { this.trace("read_file", arguments); return require("fs").readFileSync(node).toString(); } make_directory(node) { this.trace("make_directory", arguments); return require("fs").mkdirSync(node); } write_file(node, contents) { this.trace("write_file", arguments); return require("fs").writeFileSync(node, contents); } exists(node) { this.trace("exists", arguments); return require("fs").existsSync(node); } is_file(node) { this.trace("is_file", arguments); return require("fs").lstatSync(node).isFile(); } is_directory(node) { this.trace("is_directory", arguments); return require("fs").lstatSync(node).isDirectory(); } delete_file(node) { this.trace("delete_file", arguments); return require("fs").unlinkSync(node); } delete_directory(node) { this.trace("delete_directory", arguments); return require("fs").rmdirSync(node, { recursive: true }); } } const UFS_manager_for_localstorage = class extends UFS_manager_for_node { constructor(storage_id = "ufs_main_storage") { super(); this.storage_id = storage_id; this.current_directory = this.environment === "node" ? process.cwd : "/"; } trace(method, args = []) { console.log("[ufs][ls-driver][" + method + "]", Array.from(args).map(arg => typeof (arg) + ": " + arg).join(", ")); } get_persisted_data() { this.trace("get_persisted_data", arguments); if (!(this.storage_id in localStorage)) { localStorage[this.storage_id] = '{"files":{}}'; } const data = JSON.parse(localStorage[this.storage_id]); return data; } set_persisted_data(data) { this.trace("set_persisted_data", arguments); localStorage[this.storage_id] = JSON.stringify(data); } remove_slash_end(txt) { // this.trace("remove_slash_end", arguments); const txt2 = txt.replace(/\/$/g, ""); if (txt2.length === 0) { return "/"; } return txt2; } remove_repeated_slahes(txt) { // this.trace("remove_repeated_slahes", arguments); return txt.replace(/\/(\/)+/g, "/"); } resolve_path(...args) { this.trace("resolve_path", arguments); Validate_args: { if (args.length === 0) { throw new Error("Method «resolve_path» requires 1 or more parameters"); } for (let index_parameter = 0; index_parameter < args.length; index_parameter++) { const arg = args[index_parameter]; if (typeof arg !== "string") { throw new Error("Method «resolve_path» requires only strings as parameters (on index «" + index_parameter + "»)"); } } } let path_parts = []; Format_path: { const replace_last_slash_for_nothing = arg => this.remove_slash_end(arg); path_parts = args.map(replace_last_slash_for_nothing); if (!path_parts[0].startsWith("/")) { path_parts.unshift(this.current_directory.replace(/\/$/g, "")); } } let path_text = ""; Join_path: { const replace_fist_slash_for_nothing = arg => arg.replace(/^\//g, ""); for (let index_part = 0; index_part < path_parts.length; index_part++) { const path_part = path_parts[index_part]; if (path_part.startsWith("/")) { path_text = path_part; } else { if (path_text !== "/") { path_text += "/"; } path_text += path_part.replace(replace_fist_slash_for_nothing); } } } Fix_slash_repetitions: { path_text = this.remove_repeated_slahes(path_text); } Resolve_double_dots: { const parts = path_text.split("/"); const stack = []; Iterating_parts: for (const part of parts) { if (part === "" || part === ".") { continue Iterating_parts; } else if (part === "..") { if (stack.length > 0) { stack.pop(); } } else { stack.push(part); } } path_text = "/" + stack.join("/"); } return path_text; } get_current_directory() { this.trace("get_current_directory", arguments); return this.resolve_path(this.current_directory); } change_directory(node) { this.trace("change_directory", arguments); const is_directory = this.exists(node); if (!is_directory) { throw new FilesystemError("Cannot «change_directory» because destination does not exist at: «" + this.resolve_path(node) + "»"); } this.current_directory = this.resolve_path(node); return this.current_directory; } operate_on_node(node, callback, should_persist = true) { this.trace("operate_on_node", arguments); const data = this.get_persisted_data(); const node_solved = this.resolve_path(node); const node_parts = node_solved.split("/").filter(p => p !== ""); const root = data.files; const current_index = ["data"]; let pivot = root; let output = undefined; if (node_parts.length === 0) { output = callback(data, "files", current_index); } else { for (let index_part = 0; index_part < node_parts.length; index_part++) { const node_part = node_parts[index_part]; if (index_part === (node_parts.length - 1)) { output = callback(pivot, node_part, current_index); } else { pivot = pivot[node_part]; } current_index.push(node_part); } } if (should_persist) { this.set_persisted_data(data); } return output; } read_directory(node) { this.trace("read_directory", arguments); return this.operate_on_node(node, (pivot, last_property, index) => { if (!(last_property in pivot)) { throw new FilesystemError("Cannot «read_directory» because node does not exist at: «" + this.resolve_path(node) + "»"); } if (typeof pivot[last_property] !== "object") { throw new FilesystemError("Cannot «read_directory» because node is a file at: «" + this.resolve_path(node) + "»"); } return pivot[last_property]; }); } read_file(node) { this.trace("read_file", arguments); return this.operate_on_node(node, (pivot, last_property, index) => { if (!(last_property in pivot)) { throw new FilesystemError("Cannot «read_file» because node does not exist at: «" + this.resolve_path(node) + "»"); } if (typeof pivot[last_property] !== "string") { throw new FilesystemError("Cannot «read_file» because node is a directory at: «" + this.resolve_path(node) + "»"); } return pivot[last_property]; }); } make_directory(node) { this.trace("make_directory", arguments); this.operate_on_node(node, (pivot, last_property, index) => { if (last_property in pivot) { throw new FilesystemError("Cannot «make_directory» because node already exists at: «" + this.resolve_path(node) + "»"); } pivot[last_property] = {}; }); } write_file(node, contents) { this.trace("write_file", arguments); this.operate_on_node(node, (pivot, last_property, index) => { if (last_property in pivot) { if (typeof pivot[last_property] !== "string") { throw new FilesystemError("Cannot «write_file» because node is a directory at: «" + this.resolve_path(node) + "»"); } } pivot[last_property] = contents; }); } exists(node) { this.trace("exists", arguments); return this.operate_on_node(node, (pivot, last_property, index) => { if (!(last_property in pivot)) { return false; } return true; }, false); } is_file(node) { this.trace("is_file", arguments); return this.operate_on_node(node, (pivot, last_property, index) => { if (!(last_property in pivot)) { return false; } if (typeof pivot[last_property] !== "string") { return false; } return true; }, false); } is_directory(node) { this.trace("is_directory", arguments); return this.operate_on_node(node, (pivot, last_property, index) => { if (!(last_property in pivot)) { return false; } if (typeof pivot[last_property] !== "object") { return false; } return true; }, false); } delete_file(node) { this.trace("delete_file", arguments); return this.operate_on_node(node, (pivot, last_property, index) => { if (typeof pivot[last_property] === "undefined") { throw new FilesystemError("Cannot «delete_file» because node does not exist at: «" + this.resolve_path(node) + "»"); } if (typeof pivot[last_property] !== "string") { throw new FilesystemError("Cannot «delete_file» because node is a directory at: «" + this.resolve_path(node) + "»"); } delete pivot[last_property]; return true; }, true); } delete_directory(node) { this.trace("delete_directory", arguments); return this.operate_on_node(node, (pivot, last_property, index) => { if (typeof pivot[last_property] === "undefined") { console.log(pivot); console.log(last_property); throw new FilesystemError("Cannot «delete_directory» because does not exists at: «" + this.resolve_path(node) + "»"); } if (typeof pivot[last_property] !== "object") { throw new FilesystemError("Cannot «delete_directory» because node is a file at: «" + this.resolve_path(node) + "»"); } delete pivot[last_property]; return true; }, true); } rename(node, node2) { this.trace("rename", arguments); const last_name = this.resolve_path(node2).split("/").filter(p => p !== "").pop(); return this.operate_on_node(node, (pivot, last_property, index) => { if (typeof pivot[last_property] === "undefined") { throw new FilesystemError("Cannot «rename» because does not exists at: «" + this.resolve_path(node) + "»"); } pivot[last_name] = pivot[last_property]; pivot[last_property] = undefined; delete pivot[last_property]; return true; }, true); } } const UFS_manager_for_idb = class extends UFS_manager_for_localstorage { constructor(db_name = "ufs_db") { super(); this.db_name = db_name; this.db = null; this.current_directory = "/"; } trace(method, args = []) { const traceActivatedGlobally = (typeof Vue === "undefined") || (typeof Vue.prototype.$lsw === "undefined") || ((typeof Vue !== "undefined") && (typeof Vue.prototype.$lsw !== "undefined") && (Vue.prototype.$lsw.logger.$options.active)); // @INJECTION: from LSW if(traceActivatedGlobally) { console.log("[ufs][idb-driver][" + method + "]", Array.from(args).map(arg => typeof (arg) + ": " + arg).join(", ")); } } init() { return new Promise((resolve, reject) => { const request = indexedDB.open(this.db_name, 1); request.onupgradeneeded = (event) => { let db = event.target.result; if (!db.objectStoreNames.contains("ufs")) { let store = db.createObjectStore("ufs", { keyPath: "id", autoIncrement: true, }); store.createIndex("filepath", "filepath", { unique: true }); } }; request.onsuccess = (event) => { this.db = event.target.result; resolve(); }; request.onerror = (event) => reject(event.target.error); }); } _get_filename(somepath) { return somepath.split("/").filter(p => typeof (p) !== "undefined").pop(); } isImmediateSubpathFrom(subpath, matchable) { const matchablePos = matchable.length; const coincidesParentPath = subpath.substr(0, matchablePos) === matchable; if (!coincidesParentPath) return false; const hasNoMoreSlashes = subpath.substr(matchablePos).indexOf("/") === -1; if (!hasNoMoreSlashes) return false; return true; } read_directory(parentIdInput = "/") { this.trace("read_directory", arguments); const parentId = this.resolve_path(parentIdInput); return new Promise((resolve, reject) => { The_previous_process: { break The_previous_process; const transaction = this.db.transaction("ufs", "readonly"); const store = transaction.objectStore("ufs"); const index = store.index("filepath"); const request = index.getAll(parentId); request.onsuccess = () => { let result = {}; for (let item of request.result) { result[item.name] = item.type === "file" ? "..." : {}; } resolve(result); }; } const transaction = this.db.transaction("ufs", 'readonly'); const objectStore = transaction.objectStore("ufs"); const request = objectStore.openCursor(); // Usa cursor para recorrer la BD sin cargar todo en memoria const results = []; const matchableSubpath = (parentId === "/") ? parentId : (parentId + "/"); request.onsuccess = (event) => { const cursor = event.target.result; if (cursor) { let isAccepted = false; try { isAccepted = cursor.value.filepath.startsWith(matchableSubpath); isAccepted = isAccepted && this.isImmediateSubpathFrom(cursor.value.filepath, matchableSubpath); } catch (error) { console.error("Error arised from filter callback on «browsie.selectMany»", error); } if (isAccepted) { // Añade a la colección de salida results.push(cursor.value); } cursor.continue(); // Avanza al siguiente registro } else { // Se formatean los resultados: const formattedResults = {}; results.forEach(row => { const rowName = this._get_filename(row.filepath); formattedResults[rowName] = row.type === "file" ? "..." : {}; }); resolve(formattedResults); } }; request.onerror = () => reject(request.error); }); } read_file(nodeInput) { this.trace("read_file", arguments); const node = this.resolve_path(nodeInput); return new Promise((resolve, reject) => { const transaction = this.db.transaction("ufs", "readonly"); const store = transaction.objectStore("ufs"); const indexStore = store.index("filepath"); const request = indexStore.get(node); request.onsuccess = () => { resolve(request.result ? request.result.content : null); }; request.onerror = () => reject(request.error); }); } async write_file(nodeInput, contents) { this.trace("write_file", arguments); const node = this.resolve_path(nodeInput); const file = await this.$filepath(node); return await new Promise((resolve, reject) => { const transaction = this.db.transaction("ufs", "readwrite"); const store = transaction.objectStore("ufs"); const filedata = { filepath: node, type: "file", content: contents }; if (file) { filedata.id = file.id; } store.put(filedata); transaction.oncomplete = () => resolve(); transaction.onerror = () => reject(transaction.error); }); } async make_directory(nodeInput) { this.trace("make_directory", arguments); const node = this.resolve_path(nodeInput); return await new Promise((resolve, reject) => { const transaction = this.db.transaction("ufs", "readwrite"); const store = transaction.objectStore("ufs"); store.put({ filepath: node, type: "directory" }); transaction.oncomplete = () => resolve(); transaction.onerror = () => reject(transaction.error); }); } exists(nodeInput) { this.trace("exists", arguments); const node = this.resolve_path(nodeInput); return new Promise((resolve) => { const transaction = this.db.transaction("ufs", "readonly"); const store = transaction.objectStore("ufs"); const indexStore = store.index("filepath"); const request = indexStore.get(node); request.onsuccess = () => resolve(!!request.result); request.onerror = () => resolve(false); }); } is_file(nodeInput) { this.trace("is_file", arguments); const node = this.resolve_path(nodeInput); return new Promise((resolve, reject) => { const transaction = this.db.transaction("ufs", "readonly"); const store = transaction.objectStore("ufs"); const indexStore = store.index("filepath"); const request = indexStore.get(node); request.onsuccess = () => resolve(request.result ? request.result.type === "file" : false); request.onerror = () => reject(request.error); }); } is_directory(nodeInput) { this.trace("is_directory", arguments); const node = this.resolve_path(nodeInput); return new Promise((resolve, reject) => { const transaction = this.db.transaction("ufs", "readonly"); const store = transaction.objectStore("ufs"); const indexStore = store.index("filepath"); const request = indexStore.get(node); request.onsuccess = () => resolve(request.result ? request.result.type === "directory" : false); request.onerror = () => reject(request.error); }); } delete_file(nodeInput) { this.trace("delete_file", arguments); const node = this.resolve_path(nodeInput); return this.$deleteMany(file => { return (file.type === "file") && (file.filepath === node); }); } async delete_directory(nodeInput) { this.trace("delete_directory", arguments); const node = this.resolve_path(nodeInput); await this.$deleteMany(file => file.filepath.startsWith(node)); await this.$deleteMany(file => file.filepath === node); } $updateMany(filterCallback, expanderCallback) { this.trace("$updateMany", arguments); return new Promise((resolve, reject) => { const transaction = this.db.transaction("ufs", 'readwrite'); const objectStore = transaction.objectStore("ufs"); const request = objectStore.openCursor(); let updatedCount = 0; request.onsuccess = () => { const cursor = request.result; if (cursor) { const isAccepted = filterCallback(cursor.value, cursor); if (isAccepted) { const expanderItem = expanderCallback({ ...cursor.value }, cursor); const updatedItem = { ...cursor.value, ...expanderItem }; const updateRequest = cursor.update(updatedItem); updateRequest.onsuccess = () => { updatedCount++; }; } cursor.continue(); } else { return resolve(updatedCount); } }; request.onerror = () => reject(transaction.error); }); } $deleteMany(filterCallback) { this.trace("$deleteMany", arguments); return new Promise((resolve, reject) => { const transaction = this.db.transaction("ufs", 'readwrite'); const objectStore = transaction.objectStore("ufs"); const request = objectStore.openCursor(); let deletedCount = 0; request.onsuccess = () => { const cursor = request.result; if (cursor) { const isAccepted = filterCallback(cursor.value, cursor); if (isAccepted) { const deleteRequest = cursor.delete(); deleteRequest.onsuccess = () => { deletedCount++; }; } cursor.continue(); } else { return resolve(deletedCount); } }; request.onerror = () => reject(transaction.error); }); } rename(nodeInput, newName) { this.trace("rename", arguments); const node = this.resolve_path(nodeInput); const newNode = node.split("/").slice(0, -1).concat(newName).join("/") || "/"; const pathBegin = node.replace(/\/$/g, "") + "/"; const newNodeBegin = newNode.replace(/\/$/g, "") + "/"; console.log("Buscando nodos que empiecen por: «" + pathBegin + "»"); const renameSubnodes = async () => { const allSubnodes = await this.$selectMany(file => file.filepath.startsWith(pathBegin)); const allPromises = []; for (let index = 0; index < allSubnodes.length; index++) { const subnode = allSubnodes[index]; const newSubpath = subnode.filepath.replace(pathBegin, newNodeBegin);; console.log("Reemplazando a:", subnode.filepath, "Por:", newSubpath); const subpromise = this.$update(subnode.id, { filepath: newSubpath }); allPromises.push(subpromise); } return await Promise.all(allPromises); }; const renameNode = () => new Promise((resolve, reject) => { const transaction = this.db.transaction("ufs", "readwrite"); const store = transaction.objectStore("ufs"); const indexStore = store.index("filepath"); const request = indexStore.get(node); request.onsuccess = () => { if (!request.result) { reject(new Error("Node not found")); return; } const data = request.result; data.filepath = newNode; store.put(data); transaction.oncomplete = () => resolve(); transaction.onerror = () => reject(transaction.error); }; request.onerror = () => reject(request.error); }); return Promise.all([ renameNode().then(() => renameSubnodes()), ]); } async $filepath(filepath) { const selection = await this.$selectMany(file => file.filepath === filepath); if (selection.length === 1) { return selection[0]; } else if (selection.length > 1) { return selection; } return null; } $selectMany(filterCallback) { this.trace("$selectMany", arguments); return new Promise((resolve, reject) => { const transaction = this.db.transaction("ufs", 'readonly'); const objectStore = transaction.objectStore("ufs"); const request = objectStore.openCursor(); // Usa cursor para recorrer la BD sin cargar todo en memoria const results = []; request.onsuccess = (event) => { const cursor = event.target.result; if (cursor) { let isAccepted = undefined; try { isAccepted = filterCallback(cursor.value); } catch (error) { console.error("Error arised from filter callback on «selectMany»", error); } if (isAccepted) { // Aplica la función de filtro results.push(cursor.value); } cursor.continue(); // Avanza al siguiente registro } else { resolve(results); // Se terminó el recorrido } }; request.onerror = () => reject(request.error); }); } $update(id, item) { this.trace("$update", arguments); return new Promise((resolve, reject) => { const transaction = this.db.transaction("ufs", 'readwrite'); const objectStore = transaction.objectStore("ufs"); const request0 = objectStore.get(id); request0.onsuccess = () => { const originalState = request0.result; if (!originalState) return reject(`No item found by id «${id}» on «$update»`); const request = objectStore.put({ ...originalState, ...item, id }); request.onsuccess = () => resolve(request.result); request.onerror = () => reject(request.error); }; request0.onerror = () => reject(request0.error); }); } } const api = { node_driver: UFS_manager_for_node, localstorage_driver: UFS_manager_for_localstorage, idb_driver: UFS_manager_for_idb, create(...args) { const clazz = typeof global !== "undefined" ? UFS_manager_for_node : UFS_manager_for_localstorage; return new clazz(...args); }, driver(id) { const driverId = id.toLowerCase() + "_driver"; if (!(driverId in api)) { throw new Error(`Cannot find driver «${driverId}» on «UFS_manager.driver»`); } return { create(...args) { const clazz = api[driverId]; return new clazz(...args); } } } }; return api;
Vue.component("LswFilesystemButtonsPanel", { name: "LswFilesystemButtonsPanel", template: $template, props: { explorer: { type: Object, required: true }, orientation: { type: String, default: () => "row" // could be "column" too } }, data() { return { buttons: [] }; }, watch: { }, methods: { setButtons(...buttons) { this.$trace("lsw-filesystem-buttons-panel.methods.prependButtons"); this.buttons = buttons; }, prependButtons(...buttons) { this.$trace("lsw-filesystem-buttons-panel.methods.prependButtons"); this.buttons = buttons.concat(this.buttons); }, appendButtons(...buttons) { this.$trace("lsw-filesystem-buttons-panel.methods.appendButtons"); this.buttons = this.buttons.concat(buttons); }, }, mounted() { } });
Vue.component("LswFilesystemEditor", { name: "LswFilesystemEditor", template: $template, props: { explorer: { type: Object, required: true }, filecontents: { type: String, required: true }, }, data() { return { contents: this.filecontents, cursorPosition: false, currentFontsize: 12, currentFontfamily: "Arial" }; }, watch: { }, methods: { getContents() { return this.contents; }, setContents(contents) { this.contents = contents; }, gainFocus() { this.$refs.editorTextarea.focus(); }, increaseFontsize() { this.$trace("lsw-filesystem-editor.methods.increaseFontsize"); this.currentFontsize++; }, decreaseFontsize() { this.$trace("lsw-filesystem-editor.methods.decreaseFontsize"); this.currentFontsize--; }, toggleFontfamily() { this.$trace("lsw-filesystem-editor.methods.toggleFontfamily"); if(this.currentFontfamily === "monospace") { this.currentFontfamily = "Arial"; } else { this.currentFontfamily = "monospace"; } }, async saveDocument() { this.$trace("lsw-filesystem-editor.methods.saveDocument"); await this.$lsw.fs.write_file(this.explorer.current_node, this.contents); this.$lsw.toasts.send({ title: "Documento guardado", text: "El documento se guardó correctamente" }); }, async executeDocument() { this.$trace("lsw-filesystem-editor.methods.executeDocument"); if(this.explorer.current_node.endsWith(".js")) { Ejecutar_javascript_asincronamente: { this.explorer.processToExecuteFile(); } } }, async executeMobileDocument() { this.$trace("lsw-filesystem-editor.methods.executeMobileDocument"); if(this.explorer.current_node.endsWith(".js")) { Ejecutar_javascript_asincronamente: { this.explorer.processToExecuteFileOnAndroid(); } } }, beautifyDocument() { this.$trace("lsw-filesystem-editor.methods.beautifyDocument"); const fsExplorer = LswDom.getClosestParent(this.$el, ".lsw_filesystem_explorer"); if(!fsExplorer) return; LswDom.querySelectorFirst("button", "{js}", fsExplorer).click(); }, searchReplace() { this.$trace("lsw-filesystem-editor.methods.searchReplace"); const fsExplorer = LswDom.getClosestParent(this.$el, ".lsw_filesystem_explorer"); if(!fsExplorer) return; LswDom.querySelectorFirst("button", "🔎↔️", fsExplorer).click(); }, synchronizeCursor() { this.$trace("lsw-filesystem-editor.methods.synchronizeCursor"); const editorHtml = this.$refs.editorTextarea; const offsetStart = editorHtml.selectionStart const offsetEnd = editorHtml.selectionEnd; let lineStart = undefined; let lineEnd = undefined; let columnStart = undefined; let columnEnd = undefined; Col_start: { const beforeCursor = editorHtml.value.slice(0, offsetStart); const lines = beforeCursor.split("\n"); lineStart = lines.length - 1; columnStart = lines[lines.length - 1].length; } Col_end: { const beforeCursor = editorHtml.value.slice(0, offsetEnd); const lines = beforeCursor.split("\n"); lineEnd = lines.length - 1; columnEnd = lines[lines.length - 1].length; } const cursor = { start: { offset: offsetStart, line: lineStart, column: columnStart, }, end: { offset: offsetEnd, line: lineEnd, column: columnEnd, } }; this.cursorPosition = cursor; return cursor; } }, mounted() { } });
Vue.component("LswFilesystemExplorer", { name: "LswFilesystemExplorer", template: $template, props: { absoluteLayout: { type: Boolean, default: () => false, }, openedBy: { type: String, default: () => "/", } }, data() { this.$trace("lsw-filesystem-explorer.data"); return { is_ready: false, current_node: "/", current_node_parts: undefined, current_node_basename: undefined, current_node_basedir: undefined, current_node_contents: undefined, current_node_subnodes: [], current_node_is_file: false, current_node_is_directory: false, syntaxValidators: {}, STANDARIZED_REFRESH_DELAY: 100 }; }, methods: { open(...args) { this.$trace("lsw-filesystem-explorer.methods.open"); return this.open_node(...args); }, goUp() { this.$trace("lsw-filesystem-explorer.methods.goUp"); const parts = this.current_node.split("/"); parts.pop(); const dest = this.normalize_path("/" + parts.join("/")); return this.open(dest); }, async refresh() { this.$trace("lsw-filesystem-explorer.methods.refresh"); this.is_ready = false; try { await this.open(this.current_node); } catch (error) { throw error; } finally { this.$nextTick(() => { this.is_ready = true; this.$forceUpdate(true); }); } }, normalize_path(subpath) { this.$trace("lsw-filesystem-explorer.methods.normalize_path"); return this.$lsw.fs.resolve_path(this.current_node, subpath); }, async open_node(subpath = this.current_node) { this.$trace("lsw-filesystem-explorer.methods.open_node"); try { if (["", "/"].indexOf(subpath) !== -1) { return await this._openDirectory("/"); } const temporaryPath = this.normalize_path(subpath); const is_directory = await this.$lsw.fs.is_directory(temporaryPath); if (is_directory) { return await this._openDirectory(temporaryPath); } const is_file = await this.$lsw.fs.is_file(temporaryPath); if (is_file) { return await this._openFile(temporaryPath); } throw new Error(`Cannot open path because it does not exist: ${temporaryPath} on «LswFilesystemExplorer.methods.open_node»`); } catch (error) { console.log(error); throw error; } }, async processToCreateFile() { this.$trace("lsw-filesystem-explorer.methods.processToCreateFile"); const filename = await this.$lsw.dialogs.open({ title: "Crear fichero", template: `<div> <div class="pad_1"> <div>Estás en la carpeta:</div> <div class="pad_2">{{ current_directory }}</div> <div>Di el nombre del nuevo fichero:</div> <div class="pad_top_1"> <input class="width_100" type="text" placeholder="myfile.txt" v-model="filename" v-focus v-on:keyup.enter="() => accept(filename)" /> </div> </div> <hr /> <div class="flex_row centered pad_1"> <div class="flex_100"></div> <div class="flex_1 pad_right_1"> <button class="supermini nowrap danger_button" v-on:click="() => accept(filename)">Crear fichero</button> </div> <div class="flex_1"> <button class="supermini nowrap " v-on:click="() => accept(false)">Cancelar</button> </div> </div> </div>`, factory: { data() { return { current_directory: this.$lsw.fs.get_current_directory(), filename: "", }; }, }, }); if (!filename) return; const filepath = this.$lsw.fs.resolve_path(this.$lsw.fs.get_current_directory(), filename); await this.$lsw.fs.write_file(filepath, ""); this.refresh(); }, async processToCreateDirectory() { this.$trace("lsw-filesystem-explorer.methods.processToCreateDirectory"); const filename = await this.$lsw.dialogs.open({ title: "Crear directorio", template: `<div> <div class="pad_1"> <div>Estás en la carpeta:</div> <div class="pad_2">{{ current_directory }}</div> <div>Di el nombre del nuevo directorio:</div> <div class="pad_top_1"> <input class="width_100" type="text" placeholder="myfolder" v-model="filename" v-focus v-on:keyup.enter="() => accept(filename)" /> </div> </div> <hr /> <div class="flex_row centered pad_1"> <div class="flex_100"></div> <div class="flex_1 pad_right_1"> <button class="supermini nowrap danger_button" v-on:click="() => accept(filename)">Sí, seguro</button> </div> <div class="flex_1"> <button class="supermini nowrap " v-on:click="() => accept(false)">Cancelar</button> </div> </div> </div>`, factory: { data() { return { current_directory: this.$lsw.fs.get_current_directory(), filename: "", }; }, }, }); if (!filename) return; const filepath = this.$lsw.fs.resolve_path(this.$lsw.fs.get_current_directory(), filename); await this.$lsw.fs.make_directory(filepath); this.refresh(); }, async processToDeleteDirectory() { this.$trace("lsw-filesystem-explorer.methods.processToDeleteDirectory"); const confirmation = await this.$lsw.dialogs.open({ title: "Eliminar directorio", template: `<div> <div class="pad_1"> <div>¿Seguro que quieres eliminar el directorio?</div> <div class="pad_2">{{ current_directory }}</div> </div> <hr /> <div class="flex_row centered pad_1"> <div class="flex_100"></div> <div class="flex_1 pad_right_1"> <button class="supermini nowrap danger_button" v-on:click="() => accept(true)">Sí, seguro</button> </div> <div class="flex_1"> <button class="supermini nowrap " v-on:click="() => accept(false)">Cancelar</button> </div> </div> </div>`, factory: { data: { current_directory: this.$lsw.fs.get_current_directory(), } } }); if (!confirmation) return; await this.$lsw.fs.delete_directory(this.$lsw.fs.get_current_directory()); this.refresh(); }, async processToDeleteFile() { this.$trace("lsw-filesystem-explorer.methods.processToDeleteFile"); const confirmation = await this.$lsw.dialogs.open({ title: "Eliminar fichero", template: `<div> <div class="pad_1"> <div>¿Seguro que quieres eliminar el fichero?</div> <div class="pad_2">{{ current_file }}</div> </div> <hr /> <div class="flex_row centered pad_1"> <div class="flex_100"></div> <div class="flex_1 pad_right_1"> <button class="supermini nowrap danger_button" v-on:click="() => accept(true)">Sí, seguro</button> </div> <div class="flex_1"> <button class="supermini nowrap " v-on:click="() => accept(false)">Cancelar</button> </div> </div> </div>`, factory: { data: { current_file: this.current_node, } } }); if (!confirmation) return; await this.$lsw.fs.delete_file(this.current_node); const upperDir = (() => { const parts = this.current_node.split("/"); parts.pop(); return parts.join("/"); })(); this.refresh(); }, async processToRenameFile() { this.$trace("lsw-filesystem-explorer.methods.processToRenameFile"); const elementType = this.current_node_is_file ? "fichero" : "directorio"; const newName = await this.$lsw.dialogs.open({ title: "Renombrar " + elementType, template: `<div> <div class="pad_1"> <div>Refiriéndose al {{ elementType }}:</div> <div class="pad_2">{{ filename }}</div> </div> <div class="pad_1"> <div>Di el nuevo nombre del {{ elementType }}:</div> <div class="pad_top_1"> <input v-focus class="width_100" type="text" placeholder="Nuevo nombre aquí" v-model="new_filename" v-on:keyup.enter="() => accept(new_filename)" /> </div> </div> <hr /> <div class="flex_row centered"> <div class="flex_100"></div> <div class="flex_1 pad_right_1"> <button class="supermini nowrap danger_button" v-on:click="() => accept(new_filename)">Sí, seguro</button> </div> <div class="flex_1"> <button class="supermini nowrap " v-on:click="() => accept(false)">Cancelar</button> </div> </div> </div>`, factory: { data: { elementType, filename: this.current_node, new_filename: this.current_node.split("/").pop(), } } }); if (newName === false) return; if (newName.trim() === "") return; const allParts = this.current_node.split("/"); allParts.pop(); const dirPath = "/" + allParts.join("/"); const newFullpath = this.$lsw.fs.resolve_path(dirPath, newName); await this.$lsw.fs.rename(this.current_node, newName.replace(/^\/+/g, "")); await this.open(newFullpath); }, async processToExecuteFile() { this.$trace("lsw-filesystem-explorer.methods.processToExecuteFile"); const editorContents = this.$refs.editor.getContents(); const AsyncFunction = (async function () { }).constructor; const asyncFunction = new AsyncFunction(editorContents); try { const result = await asyncFunction.call(this); this.$lsw.toasts.debug(result); } catch (error) { this.$lsw.toasts.showError(error); } }, async processToLoadFile() { this.$trace("lsw-filesystem-explorer.methods.processToLoadFile"); this.is_ready = false; const contents = await this.$lsw.fs.read_file(this.current_node); this.current_node_contents = contents; this.$nextTick(() => { this.is_ready = true; }); }, async processToSaveFile() { this.$trace("lsw-filesystem-explorer.methods.processToSaveFile"); try { if (!this.$refs.editor) { throw new Error("No hay editor ahora mismo"); } const editorContents = this.$refs.editor.getContents(); await this.$lsw.fs.write_file(this.current_node, editorContents); this.$lsw.toasts.send({ title: "Fichero guardado", text: "Hablamos de: " + this.current_node }); } catch (error) { this.$lsw.toasts.showError(error); } }, _setButtonsForFile() { this.$trace("lsw-filesystem-explorer.methods._setButtonsForFile"); this.is_ready = false; this.current_node_is_file = true; this.current_node_is_directory = false; Setup_panel_top_on_file: { this.$refs.panelTop.setButtons({ text: "➜", classes: "reversed", click: () => this.goUp(), }); } Setup_panel_right_on_file: { const rightButtonsOnFile = [ { text: "💾", click: () => this.processToSaveFile(), }, { text: "↔️", click: () => this.processToRenameFile(), }, { text: "🔄", click: () => this.processToLoadFile(), }, { text: "📄 🔥", classes: "danger_button", click: () => this.processToDeleteFile(), } ]; BUTTON_INJECTION_HERE__RIGHT_PANEL: { // @INJECTABLE: add custom buttons for extensions: Button_to_execute_javascript: { if (this.current_node.endsWith(".js")) { rightButtonsOnFile.push({ text: "⚡️", classes: "danger_button", click: () => this.processToExecuteFile(), }); } } Button_to_compile_markdown_to_html: { if (this.current_node.endsWith(".md")) { rightButtonsOnFile.push({ text: "🔩", classes: "", click: () => this.processToCompileMarkdown(), }); } } Button_to_compile_pegjs_to_js: { if (this.current_node.endsWith(".pegjs")) { rightButtonsOnFile.push({ text: "🔩", classes: "", click: () => this.processToCompilePegjs(), }); } } Button_to_format_code: { if (this.current_node.endsWith(".html")) { rightButtonsOnFile.push({ text: "{html}", classes: "", click: () => this.processToFormatHtml(), }); } else if (this.current_node.endsWith(".css")) { rightButtonsOnFile.push({ text: "{css}", classes: "", click: () => this.processToFormatCss(), }); } else if (this.current_node.endsWith(".js")) { rightButtonsOnFile.push({ text: "{js}", classes: "", click: () => this.processToFormatJs(), }); if(typeof cordova !== "undefined") { rightButtonsOnFile.push({ text: "📱⚡️", classes: "", click: () => this.processToExecuteFileOnAndroid(), }); } } } Button_to_download_file: { rightButtonsOnFile.push({ text: "📥", classes: "", click: () => this.processToDownloadFile(), }); } Button_to_search_replace: { rightButtonsOnFile.push({ text: "🔎↔️", classes: "", click: () => this.processToSearchReplace(), }); } Button_to_validate_code: { if(this.hasSyntaxValidator(this.current_node)) { rightButtonsOnFile.push({ text: "✅", classes: "", click: () => this.processToValidateCode(), }); } } } this.$refs.panelRight.setButtons(...rightButtonsOnFile); } Setup_panel_bottom_on_file: { const bottomButtonsOnFile = [ { text: "➕", click: () => this.increaseFontsize(), }, { text: "➖", click: () => this.decreaseFontsize(), }, { text: "✍🏻|🐒", click: () => this.toggleFontfamily(), } ]; BUTTON_INJECTION_HERE__BOTTOM_PANEL: { // @INJECTABLE: add custom buttons for extensions: Button_to_view_code: { if (this.current_node.endsWith(".js")) { bottomButtonsOnFile.push({ text: "🌈", click: () => this.openCodeViewerForJs() }); } else if (this.current_node.endsWith(".css")) { bottomButtonsOnFile.push({ text: "🌈", click: () => this.openCodeViewerForCss() }); } else if (this.current_node.endsWith(".html")) { bottomButtonsOnFile.push({ text: "🌈", click: () => this.openCodeViewerForHtml() }); } } Button_to_view_html: { if (this.current_node.endsWith(".html")) { bottomButtonsOnFile.push({ text: "📻", classes: "", click: () => this.processToViewHtml(), }); } else if (this.current_node.endsWith(".md")) { bottomButtonsOnFile.push({ text: "📻", classes: "", click: () => this.processToViewMarkdown(), }); } } } this.$refs.panelBottom.setButtons(...bottomButtonsOnFile); } this.$nextTick(() => { this.is_ready = true; }); }, _setButtonsForDirectory() { this.$trace("lsw-filesystem-explorer.methods._setButtonsForDirectory"); this.is_ready = false; this.current_node_is_directory = true; this.current_node_is_file = false; Setup_panel_top_on_directory: { if (this.current_node === "/") { this.$refs.panelTop.setButtons(); } else { this.$refs.panelTop.setButtons({ text: "➜", classes: "reversed", click: () => this.goUp(), }); } } Setup_panel_right_on_directory: { this.$refs.panelRight.setButtons({ text: "📄+", click: () => this.processToCreateFile(), }, { text: "📁+", click: () => this.processToCreateDirectory(), }, { text: "📁 🔥", classes: "danger_button", click: () => this.processToDeleteDirectory() }); } Setup_panel_bottom_on_directory: { this.$refs.panelBottom.setButtons(); } this.$nextTick(() => { this.is_ready = true; }); }, increaseFontsize() { this.$trace("lsw-filesystem-explorer.methods.increaseFontsize"); this.$refs.editor.increaseFontsize(); }, decreaseFontsize() { this.$trace("lsw-filesystem-explorer.methods.decreaseFontsize"); this.$refs.editor.decreaseFontsize(); }, toggleFontfamily() { this.$trace("lsw-filesystem-explorer.methods.toggleFontfamily"); this.$refs.editor.toggleFontfamily(); }, async _openFile(subpath) { this.$trace("lsw-filesystem-explorer.methods._openFile"); this.current_node = subpath; const contents = await this.$lsw.fs.read_file(this.current_node); this.current_node_contents = contents; this._setButtonsForFile(); }, async _openDirectory(subpath) { this.$trace("lsw-filesystem-explorer.methods._openDirectory"); this.current_node = subpath; const subnodes = await this.$lsw.fs.read_directory(this.current_node); const sortedSubnodes = { files: [], folders: [] }; Object.keys(subnodes).forEach(id => { const subnode = subnodes[id]; const subnodeType = typeof subnode === "string" ? "files" : "folders"; sortedSubnodes[subnodeType].push(id); }); const formattedSubnodes = {}; sortedSubnodes.folders.sort().forEach(folder => { formattedSubnodes[folder] = {}; }); sortedSubnodes.files.sort().forEach(file => { formattedSubnodes[file] = "..."; }); console.log(subnodes, formattedSubnodes); this.$lsw.fs.change_directory(subpath); this.current_node_subnodes = formattedSubnodes; this._setButtonsForDirectory(); }, __update_node_parts(newValue = this.current_node) { this.$trace("lsw-filesystem-explorer.methods.__update_node_parts"); this.current_node_parts = newValue.split("/").filter(p => p !== ""); }, __update_current_node_basename(current_node_parts = this.current_node_parts) { this.$trace("lsw-filesystem-explorer.methods.__update_current_node_basename"); if (current_node_parts.length) { this.current_node_basename = current_node_parts[current_node_parts.length - 1]; } else { this.current_node_basename = "/"; } }, __update_current_node_basedir(current_node_parts = this.current_node_parts) { this.$trace("lsw-filesystem-explorer.methods.__update_current_node_basedir"); if (current_node_parts.length > 1) { this.current_node_basedir = "/" + [].concat(current_node_parts).splice(0, current_node_parts.length - 1).join("/") + "/"; } else { this.current_node_basedir = "/"; } }, _updateNodeSubdata(newValue = this.current_node) { this.$trace("lsw-filesystem-explorer.methods._updateNodeSubdata"); this.__update_node_parts(newValue); this.__update_current_node_basename(); this.__update_current_node_basedir(); }, setPanelButtons(panelOptions = {}) { this.$trace("lsw-filesystem-explorer.methods.setPanelButtons"); Validation: { if (typeof panelOptions !== "object") { throw new Error("Required argument «panelOptions» to be an object on «LswFilesystemExplorer.methods.setPanelButtons»"); } const keys = Object.keys(panelOptions); if (keys.length === 0) { throw new Error("Required argument «panelOptions» to be have 1 or more keys on «LswFilesystemExplorer.methods.setPanelButtons»"); } const valid_keys = ["top", "bottom", "left", "right"]; for (let index = 0; index < keys.length; index++) { const key = keys[index]; if (valid_keys.indexOf(key) === -1) { throw new Error(`Required argument «panelOptions[${key}]» to be a valid key out of «${valid_keys.join(",")}», not «${key}» on «LswFilesystemExplorer.methods.setPanelButtons»`); } const value = panelOptions[key]; if (typeof value !== "object") { throw new Error(`Required argument «panelOptions[${key}]» to be an object or array, not ${typeof value}» on «LswFilesystemExplorer.methods.setPanelButtons»`); } } } }, async openCodeViewerForJs() { this.$trace("lsw-filesystem-explorer.methods.openCodeViewerForJs"); console.log(this.current_node_contents); this.$lsw.dialogs.open({ title: "Visualizando código JS", template: ` <div class="pad_1"> <div class="">{{ file }}:</div> <hr /> <lsw-code-viewer :code="code" language="js" /> </div>`, factory: { data: { file: this.current_node, code: this.$refs.editor.getContents(), } }, }) }, async openCodeViewerForCss() { this.$trace("lsw-filesystem-explorer.methods.openCodeViewerForCss"); this.$lsw.dialogs.open({ title: "Visualizando código CSS", template: ` <div class="pad_1"> <div class="">{{ file }}:</div> <hr /> <lsw-code-viewer :code="code" language="css" /> </div>`, factory: { data: { file: this.current_node, code: this.$refs.editor.getContents(), } }, }) }, async openCodeViewerForHtml() { this.$trace("lsw-filesystem-explorer.methods.openCodeViewerForHtml"); this.$lsw.dialogs.open({ title: "Visualizando código HTML", template: ` <div class="pad_1"> <div class="">{{ file }}:</div> <hr /> <lsw-code-viewer :code="code" language="html" /> </div>`, factory: { data: { file: this.current_node, code: this.$refs.editor.getContents(), } }, }) }, async processToCompileMarkdown() { this.$trace("lsw-filesystem.explorer.methods.processToCompileMarkdown"); const mdContent = this.$refs.editor.getContents(); const htmlContent = marked.parse(mdContent); const fileoutput = await this.$lsw.dialogs.open({ title: "Compilar markdown a html", template: ` <div class="pad_1"> <div>¿A qué fichero quieres exportar el html? Especifica solo el nombre:</div> <!--lsw-sourceable :code="htmlContent"--> <input class="width_100" type="text" v-model="value" /> <hr /> <div class="flex_row centered"> <div class="flex_100"></div> <div class="flex_1"> <button class="supermini" v-on:click="accept">Aceptar</button> </div> <div class="flex_1 pad_left_1"> <button class="supermini" v-on:click="cancel">Cancelar</button> </div> </div> </div> `, factory: { data: { value: this.current_node.replace(this.current_node_basedir, "").replace(/\.md$/, ".html"), } } }); if (typeof fileoutput !== "string") { return; } const filepath = this.$lsw.fs.resolve_path(this.current_node_basedir, fileoutput); try { this.$lsw.fs.write_file(filepath, htmlContent); this.$lsw.toasts.send({ title: "Markdown compilado a HTML", text: "La salida está en: " + filepath, }); } catch (error) { this.$lsw.toasts.showError(error, false, true); } }, async processToCompilePegjs() { this.$trace("lsw-filesystem.explorer.methods.processToCompilePegjs"); const currentFile = this.current_node; const pegjsContent = this.$refs.editor.getContents(); const parserOptions = await this.$lsw.dialogs.open({ title: "Compilar pegjs a js", template: ` <div class="pad_1"> <div>Global a la que exportar:</div> <input class="width_100" type="text" v-model="value.exportVar" /> <hr /> <div>Fichero final:</div> <input class="width_100" type="text" v-model="value.output" /> <hr /> <div>Formato de exportación:</div> <input class="width_100" type="text" v-model="value.format" /> <hr /> <div class="flex_row centered"> <div class="flex_100"></div> <div class="flex_1"> <button class="supermini" v-on:click="accept">Aceptar</button> </div> <div class="flex_1 pad_left_1"> <button class="supermini" v-on:click="cancel">Cancelar</button> </div> </div> </div> `, factory: { data: { value: { format: 'globals', output: this.current_node.replace(this.current_node_basedir, "").replace(/\.pegjs/g, ".js"), exportVar: 'DemoParser', } } } }); if (typeof parserOptions !== "object") return; const fileoutput = parserOptions.output; const parserFormat = parserOptions.format; const parserExporter = parserOptions.exportVar; await this.$lsw.lazyLoads.loadPegjs(); const filepath = this.$lsw.fs.resolve_path(this.current_node_basedir, fileoutput); try { const jsContent = PEG.buildParser(pegjsContent, { output: "source", format: parserFormat, exportVar: parserExporter, }); this.$lsw.fs.write_file(filepath, jsContent); this.$lsw.toasts.send({ title: "Pegjs compilado a JavaScript", text: "La salida está en: " + filepath, }); } catch (error) { this.$lsw.toasts.showError(error, false, true); } }, async processToFormatHtml() { this.$trace("lsw-filesystem.exporer.methods.processToFormatHtml"); try { const input = this.$refs.editor.getContents(); await this.$lsw.lazyLoads.loadBeautifier(); const output = beautifier.html(input); this.$refs.editor.setContents(output); } catch (error) { this.$lsw.toasts.showError(error, false, true); } }, async processToFormatCss() { this.$trace("lsw-filesystem.exporer.methods.processToFormatCss"); try { const input = this.$refs.editor.getContents(); await this.$lsw.lazyLoads.loadBeautifier(); const output = beautifier.css(input); this.$refs.editor.setContents(output); } catch (error) { this.$lsw.toasts.showError(error, false, true); } }, async processToFormatJs() { this.$trace("lsw-filesystem.exporer.methods.processToFormatJs"); try { const input = this.$refs.editor.getContents(); await this.$lsw.lazyLoads.loadBeautifier(); const output = beautifier.js(input); this.$refs.editor.setContents(output); this.$lsw.toasts.send({ title: "Documento formateado", text: "El documento fue formateado en js correctamente" }); } catch (error) { this.$lsw.toasts.showError(error, false, true); } }, processToExecuteFileOnAndroid() { this.$trace("lsw-filesystem.exporer.methods.processToExecuteFileOnAndroid"); return LswAndroid.evalFile(this.current_node); }, async processToDownloadFile() { this.$trace("lsw-filesystem.explorer.methods.processToDownloadFile"); const filename = this.current_node.replace(this.current_node_basedir, ""); const confirmation = await this.$lsw.dialogs.open({ title: "Descargar fichero", template: ` <div class="pad_1"> <div class="pad_top_1">¿Qué nombre quieres para el fichero a descargar?</div> <input type="text" class="width_100 margin_top_1" v-model="value" /> <hr /> <div class="flex_row centered pad_top_1"> <div class="flex_100"></div> <div class="flex_1 pad_left_1"> <button class="supermini danger_button" v-on:click="accept">Aceptar</button> </div> <div class="flex_1 pad_left_1"> <button class="supermini " v-on:click="cancel">Cancelar</button> </div> </div> </div>`, factory: { data: { value: filename } }, }); if (typeof confirmation !== "string") return; const filecontents = this.current_node_contents; LswUtils.downloadFile(filename, filecontents); }, processToViewHtml() { this.$trace("lsw-filesystem.explorer.methods.processToCompilePegjs"); const htmlContent = this.$refs.editor.getContents(); return this.$lsw.dialogs.open({ title: "Ver html en vivo", template: ` <div class="pad_1"> ${htmlContent} </div> `, }); }, processToViewMarkdown() { this.$trace("lsw-filesystem.explorer.methods.processToCompilePegjs"); const mdContent = this.$refs.editor.getContents(); const htmlContent = marked.parse(mdContent); return this.$lsw.dialogs.open({ title: "Ver markdown en vivo", template: ` <div class="pad_1"> ${htmlContent} </div> `, }); }, async processToSearchReplace() { this.$trace("lsw-filesystem.explorer.methods.processToSearchReplace"); let selectedText = ""; Extract_selected_text: { try { const textareaHtml = this.$refs.editor.$refs.editorTextarea; selectedText = textareaHtml.value.substring(textareaHtml.selectionStart, textareaHtml.selectionEnd); } catch (error) { // @BADLUCK console.log(error); } } const fse = this; const value = await this.$lsw.dialogs.open({ title: "Buscar y reemplazar", template: ` <lsw-search-replacer :input="input" :initial-search="search" :initial-replace="replace" :on-accept="out => accept(out)" :on-cancel="comeBack" /> `, factory: { data: { input: this.$refs.editor.getContents(), search: selectedText, replace: selectedText, }, methods: { comeBack() { this.cancel(); fse.$refs.editor.gainFocus(); } } } }); if (typeof value !== "string") return; this.$refs.editor.setContents(value); }, processToValidateCode() { this.$trace("lsw-filesystem.explorer.methods.processToValidateCode"); const currentContents = this.$refs.editor.getContents(); const syntaxExtensions = Object.keys(this.syntaxValidators).map(id => "." + id); let associatedSyntax = undefined; Iterating_syntaxes: for (let index = 0; index < syntaxExtensions.length; index++) { const syntaxExtension = syntaxExtensions[index]; const isSyntaxCompliant = this.current_node.endsWith(syntaxExtension); if (isSyntaxCompliant) { associatedSyntax = syntaxExtension; break Iterating_syntaxes; } } if (!associatedSyntax) { return -1; } const associatedValidator = this.syntaxValidators[associatedSyntax]; if (!associatedValidator) { return -2; } try { const isValid = associatedValidator(currentContents); this.$lsw.toasts.debug(isValid); } catch (error) { this.$lsw.toasts.showError(error); } }, async loadSyntaxValidators() { this.$trace("lsw-filesystem-explorer.methods.loadSyntaxValidators"); const validatorsAsMap = await this.$lsw.fs.read_directory("/kernel/editor/validators"); const ids = Object.keys(validatorsAsMap).map(f => f.replace(/\.js/g, "")); const allValidators = {}; for(let index=0; index<ids.length; index++) { const id = ids[index]; const validator = await this.$lsw.fs.evaluateAsJavascriptFileOrReturn(`/kernel/editor/validators/${id}.js`, () => true); allValidators[id] = validator; } this.syntaxValidators = allValidators; }, hasSyntaxValidator(file) { const currentExtension = file.replace(/^([^.]*\.)+/g, ""); return Object.keys(this.syntaxValidators || {}).indexOf(currentExtension) !== -1; }, }, watch: { current_node(newValue) { this.$trace("lsw-filesystem-explorer.watch.current_node"); this._updateNodeSubdata(newValue); } }, computed: { }, async mounted() { try { this.$trace("lsw-filesystem-explorer.mounted"); await this.loadSyntaxValidators(); this.$lsw.fsExplorer = this; // await this.initializeFilesystemForLsw(); await this.open(this.openedBy ?? "/"); } catch (error) { console.log(error); } } });
Vue.component("LswFilesystemTreeviewer", { name: "LswFilesystemTreeviewer", template: $template, props: { explorer: { type: Object, required: true } }, data() { this.$trace("lsw-filesystem-treeviewer.data"); return {}; }, watch: {}, methods: { goUp() { this.$trace("lsw-filesystem-treeviewer.methods.goUp"); return this.explorer.goUp(); }, openSubnode(subnodeIndex) { this.$trace("lsw-filesystem-treeviewer.methods.openSubnode"); return this.explorer.open(subnodeIndex); }, async deleteNode(subnodeIndex) { this.$trace("lsw-filesystem-treeviewer.methods.deleteNode"); const fullpath = this.$lsw.fs.resolve_path(subnodeIndex); const isDirectory = await this.$lsw.fs.is_directory(fullpath); const elementType = isDirectory ? 'directorio' : 'fichero'; const confirmation = await this.$lsw.dialogs.open({ title: `Proceder a eliminar ${elementType}`, template: ` <div class="pad_1"> <div>Seguro que quieres eliminar el {{ elementType }} «{{ fullpath }}»?</div> <hr /> <div class="flex_row centered"> <div class="flex_100"></div> <div class="flex_1 pad_right_1"> <button class="supermini danger_button nowrap" v-on:click="() => accept(true)">Sí, eliminar</button> </div> <div class="flex_1"> <button class="supermini" v-on:click="() => accept(false)">Cancelar</button> </div> </div> </div> `, factory: { data: { elementType, fullpath, } } }); if (!confirmation) return; try { if (isDirectory) { await this.$lsw.fs.delete_directory(fullpath); } else { await this.$lsw.fs.delete_file(fullpath); } await this.explorer.refresh(); } catch (error) { await this.$lsw.dialogs.open({ title: `El fichero no se pudo eliminar`, template: ` <div class="pad_1"> <div>El fichero «{{ fullpath }}» no se pudo eliminar debido al siguiente error:</div> <hr /> <div v-if="error">{{ error.name }}: {{ error.message }}</div> </div> `, factory: { data: { error, fullpath, } } }); } }, async renameNode(subnodeIndex) { this.$trace("lsw-filesystem-treeviewer.methods.renameNode"); const fullpath = this.$lsw.fs.resolve_path(subnodeIndex); const isDirectory = await this.$lsw.fs.is_directory(fullpath); const elementType = isDirectory ? 'directorio' : 'fichero'; const newName = await this.$lsw.dialogs.open({ title: "Renombrar " + elementType, template: `<div> <div class="pad_1"> <div>Refiriéndose al {{ elementType }}:</div> <div class="pad_2">{{ filename }}</div> <div>Di el nuevo nombre del {{ elementType }}:</div> <input v-focus class="width_100" type="text" v-model="newFilename" v-on:keyup.enter="() => accept(newFilename)" /> </div> <hr /> <div class="flex_row centered pad_1"> <div class="flex_100"></div> <div class="flex_1 pad_right_1"> <button class="supermini" v-on:click="() => accept(newFilename)">Renombrar</button> </div> <div class="flex_1"> <button class="supermini" v-on:click="() => accept(false)">Cancelar</button> </div> </div> </div>`, factory: { data: { elementType, fullpath, filename: subnodeIndex, newFilename: subnodeIndex, } } }); if (typeof newName !== "string") return; if (newName.trim() === "") return; await this.$lsw.fs.rename(subnodeIndex, newName.replace(/^\/+/g, "")); this.explorer.refresh(); } }, mounted() { this.$trace("lsw-filesystem-treeviewer.mounted"); this.explorer.setPanelButtons({ top: [], left: [], right: [], bottom: [], }) }, unmounted() { this.$trace("lsw-filesystem-treeviewer.unmounted"); } });
Vue.component("LswBooleanControl", { template: $template, props: { configurations: { type: Object, default: () => ({}) } }, data() { this.$trace("lsw-boolean-control.data"); return {}; }, methods: {}, watch: {}, mounted() { try { this.$trace("lsw-boolean-control.mounted"); } catch(error) { console.log(error); } } });
Vue.component("LswButtonActionControl", { template: $template, props: { settings: { type: Object, default: () => ({}) }, }, data() { this.$trace("lsw-button-action-control.data"); this.validateSettings(); const value = this.settings?.initialValue || this.settings?.column.hasDefaultValue || ""; return { uuid: LswRandomizer.getRandomString(5), value, isEditable: true, }; }, methods: { async submit() { this.$trace("lsw-text-control.methods.submit"); return LswFormtypes.utils.submitControl.call(this); }, validate() { this.$trace("lsw-text-control.methods.validateSettings"); return LswFormtypes.utils.validateControl.call(this); }, validateSettings() { this.$trace("lsw-text-control.methods.validateSettings"); return LswFormtypes.utils.validateSettings.call(this); } }, watch: {}, mounted() { try { this.$trace("lsw-button-action-control.mounted"); } catch (error) { console.log(error); } } });
Vue.component("LswControlError", { template: $template, props: { }, data() { this.$trace("lsw-control-error-control.data"); return { }; }, methods: { removeError() { this.$trace("lsw-control-error-control.methods.removeError"); this.$refs.errorBox.$xform.$clearError(); } }, watch: {}, mounted() { try { this.$trace("lsw-control-error-control.mounted"); } catch (error) { console.log(error); } } });
Vue.component("LswControlLabel", { template: $template, props: { parentFormtype: { type: Object, required: false, }, settings: { type: Object, default: () => ({}) }, }, data() { this.$trace("lsw-control-label-control.data"); this.validateSettings(); return { uuid: LswRandomizer.getRandomString(5), isShowingDescription: false, name: this.settings?.name, label: (typeof (this.settings?.label) !== "undefined") ? this.settings.label : this.settings?.column?.hasLabel, description: this.settings?.column?.hasDescription }; }, methods: { toggleDescription() { this.isShowingDescription = !this.isShowingDescription; }, validateSettings() { this.$trace("lsw-control-label-control.methods.validateSettings"); LswXForm.validateSettings(this.settings); const ensureSettings = $ensure(this.settings); const checkSettings = $check(this.settings); // @OK }, makeEditable() { this.$trace("lsw-control-label-control.methods.makeEditable"); Behaviour_for_controls: { const immediateControl = LswVue2.getClosestParent(this, component => { return component.$el.classList.contains("lsw_form_control"); }); if (immediateControl) { immediateControl.isEditable = true; // immediateControl.$forceUpdate(true); } } Behaviour_for_schema_forms: { } }, makeUneditable() { this.$trace("lsw-control-label-control.methods.makeUneditable"); Behaviour_for_controls: { const immediateControl = LswVue2.getClosestParent(this, component => { return component.$el.classList.contains("lsw_form_control"); }); if (immediateControl) { immediateControl.isEditable = false; // immediateControl.$forceUpdate(true); } } Behaviour_for_schema_forms: { } } }, watch: {}, mounted() { try { this.$trace("lsw-control-label-control.mounted"); } catch (error) { console.log(error); } } });
Vue.component("LswDateControl", { template: $template, props: { settings: { type: Object, default: () => ({}) }, mode: { type: String, default: () => "date" // can be: date, datetime, time } }, data() { this.$trace("lsw-date-control.data"); this.validateMode(); this.validateSettings(); const respectivePlaceholder = this.generatePlaceholder(); return { uuid: LswRandomizer.getRandomString(5), value: this.settings?.initialValue || this.settings?.column?.hasInitialValue?.call() || "", isEditable: true, isShowingCalendar: false, respectivePlaceholder, formMode: this.settings?.column?.isFormSubtype || this.mode || "datetime", }; }, methods: { toggleCalendar() { this.$trace("LswDateControl.methods.toggleCalendar", arguments); this.isShowingCalendar = !this.isShowingCalendar; }, generatePlaceholder() { return this.settings.column.isFormSubtype === "date" ? 'Ej: 2025/01/01' : this.settings.column.isFormSubtype === "datetime" ? 'Ej: 2025/01/01 00:00' : this.settings.column.isFormSubtype === "time" ? 'Ej: 00:00' : '' }, async submit() { this.$trace("lsw-text-control.methods.submit"); return LswFormtypes.utils.submitControl.call(this); }, validate() { this.$trace("lsw-text-control.methods.validateSettings"); return LswFormtypes.utils.validateControl.call(this); }, validateSettings() { this.$trace("lsw-text-control.methods.validateSettings"); return LswFormtypes.utils.validateSettings.call(this); }, validateMode() { this.$trace("lsw-date-control.methods.validateSettings"); $ensure({mode: this.mode}, 1).to.be.oneOf(["date", "time", "datetime"]); }, setValueFromCalendar(v) { this.$trace("lsw-date-control.methods.setValueFromCalendar"); console.log("Valor:", v); const value = LswTimer.utils.formatDatestringFromDate(v, false, false, true); if(this.formMode === "datetime") { this.value = value; } else if(this.formMode === "date") { this.value = value.split(" ")[0]; } else if(this.formMode === "time") { this.value = value.split(" ")[1]; } else { this.value = value; } } }, watch: {}, mounted() { try { this.$trace("lsw-date-control.mounted"); } catch (error) { console.log(error); } } });
Vue.component("LswDurationControl", { template: $template, props: { settings: { type: Object, default: () => ({}) }, skipLabel: { type: Boolean, default: () => false, } }, data() { this.$trace("lsw-duration-control.data"); this.validateSettings(); const value = this.settings?.initialValue || this.settings?.column?.hasDefaultValue || ""; return { uuid: LswRandomizer.getRandomString(5), value, isEditable: true, isShowingDetails: true, submitError: false, validateError: false, }; }, methods: { async submit() { this.$trace("lsw-text-control.methods.submit"); try { return LswFormtypes.utils.submitControl.call(this); } catch (error) { this.submitError = error; throw error; } }, validate() { this.$trace("lsw-text-control.methods.validateSettings"); try { return LswFormtypes.utils.validateControl.call(this); } catch (error) { this.validateError = error; throw error; } }, validateSettings() { this.$trace("lsw-text-control.methods.validateSettings"); return LswFormtypes.utils.validateSettings.call(this); }, toggleDetails() { this.$trace("lsw-duration-control.methods.toggleDetails"); this.isShowingDetails = !this.isShowingDetails; }, setValue(v) { this.$trace("lsw-duration-control.methods.setValue"); this.value = v; } }, watch: {}, mounted() { try { this.$trace("lsw-duration-control.mounted"); } catch (error) { console.log(error); } } });
Vue.component("LswFormBuilder", { template: $template, props: { validate: { type: Object, default: () => ({}) }, submit: { type: Object, default: () => ({}) }, fields: { type: Array, required: true, } }, data() { this.$trace("lsw-form-builder.data"); this.formatFields(); return { formMetadata: false, }; }, methods: { setError(error) { this.$trace("lsw-form-builder.setError"); this.error = error; }, formatFields(value = this.fields) { this.$trace("lsw-form-builder.formatFields"); try { const $outterScope = {}; if (value.length === 0) { throw new Error("Required property «prop.fields» to be an array on «LswFormBuilder.props.fields.validator»"); } const fields = []; const form = { scope: $outterScope, id: "form.default" }; const metadata = { fields, form, scope: $outterScope }; form.vForm = { selfScope: $outterScope, selfId: form.id, onValidate: typeof this.validate.onClick === 'function' ? this.validate.onClick : this.$noop, onSubmit: typeof this.submit.onClick === 'function' ? this.submit.onClick : this.$noop, } for (let index = 0; index < value.length; index++) { const row = value[index]; if (typeof row !== "object") { throw new Error(`Required all rows on «prop.fields» to be an object but row «${index}» does not on «LswFormBuilder.props.fields.validator»`) } if (!("type" in row)) { throw new Error(`Required all rows on «prop.fields» to have property «type» but row «${index}» does not on «LswFormBuilder.props.fields.validator»`) } if (typeof row.type !== "string") { throw new Error(`Required all rows on «prop.fields» to have property «type» as a string but row «${index}» does not on «LswFormBuilder.props.fields.validator»`) } if (!("name" in row)) { throw new Error(`Required all rows on «prop.fields» to have property «name» but row «${index}» does not on «LswFormBuilder.props.fields.validator»`) } if (typeof row.name !== "string") { throw new Error(`Required all rows on «prop.fields» to have property «name» as a string but row «${index}» does not on «LswFormBuilder.props.fields.validator»`) } const $innerScope = {}; row.vForm = { parentScope: $outterScope, parentId: metadata.form.id, selfScope: $innerScope, selfId: row.name, name: row.name, }; if (row.type === "point") { row.dimensions = []; row.vFormForPoint = { parentScope: $innerScope, parentId: row.name, selfScope: $innerScope, selfId: "point.control", name: null, }; row.dimensions = [{ label: "Axis 1:", vForm: { parentScope: $innerScope, parentId: "point.control", name: "axis_1" } }, { label: "Axis 2:", vForm: { parentScope: $innerScope, parentId: "point.control", name: "axis_2" } }]; if (row.dimensions.length < 2) { throw new Error(`Required property «row.dimensions» to have more than 1 item on row «${index}» on «adaptRowToVForm»`); } for (let indexDimension = 0; indexDimension < row.dimensions.length; indexDimension++) { } } fields.push(row); } this.formMetadata = Object.freeze(metadata); } catch (error) { console.log(error); this.setError(error); } }, adaptRowToVForm(row, metadata, indexRow) { this.$trace("lsw-form-builder.adaptRowToVForm"); } }, watch: {}, mount() { try { this.$trace("lsw-form-builder.mount"); } catch (error) { console.log(error); } }, mounted() { try { this.$trace("lsw-form-builder.mounted"); this.formatFields(); } catch (error) { console.log(error); } } });
class LswFormtypesUtils { static class = this; static async submitControl() { if (this.settings.parentSchemaForm) { await this.validate(); } } static validateControl() { return this.$refs.controller.$xform.validate(); } static validateSettings() { LswXForm.validateSettings(this.settings); const ensureSettings = $ensure(this.settings); const checkSettings = $check(this.settings); ensureSettings.to.have.onlyPotentialKeys([ "name", "input", "entity", "database", "table", "column", "initialValue", "label", "parentSchemaForm", "extraAttributes", "formtypeParameters", "formtypeSettings" ]); if (checkSettings.to.have.key("initialValue")) { const ensureInitialValue = ensureSettings.its("initialValue").type("string"); } if (checkSettings.to.have.key("label")) { const ensureHasLabel = ensureSettings.its("label").type(["string", "undefined", "boolean"]); } } } class LswFormtypes { static class = this; constructor() { this.$formtypes = new Map(); } static utils = LswFormtypesUtils; } window.commonFormtypes = new LswFormtypes(); return LswFormtypes;
Vue.component("LswLongTextControl", { template: $template, props: { settings: { type: Object, default: () => ({}) }, }, data() { this.$trace("lsw-long-text-control.data"); this.validateSettings(); const value = this.settings?.initialValue || this.settings?.column.hasDefaultValue || ""; return { uuid: LswRandomizer.getRandomString(5), value, isEditable: true, }; }, methods: { async submit() { this.$trace("lsw-text-control.methods.submit"); return LswFormtypes.utils.submitControl.call(this); }, validate() { this.$trace("lsw-text-control.methods.validateSettings"); return LswFormtypes.utils.validateControl.call(this); }, validateSettings() { this.$trace("lsw-text-control.methods.validateSettings"); return LswFormtypes.utils.validateSettings.call(this); } }, watch: {}, mounted() { try { this.$trace("lsw-long-text-control.mounted"); } catch (error) { console.log(error); } } });
Vue.component("LswNumberControl", { template: $template, props: { configurations: { type: Object, default: () => ({}) } }, data() { this.$trace("lsw-number-control.data"); return {}; }, methods: {}, watch: {}, mounted() { try { this.$trace("lsw-number-control.mounted"); } catch(error) { console.log(error); } } });
Vue.component("LswOptionsControl", { template: $template, props: { settings: { type: Object, default: () => ({}) }, }, data() { this.$trace("lsw-options-control.data"); this.validateSettings(); const value = this.settings?.initialValue || this.settings?.column?.hasInitialValue?.call() || ""; return { uuid: LswRandomizer.getRandomString(5), value, isEditable: true, parameters: this.settings?.hasFormtypeParameters || {} }; }, methods: { async submit() { this.$trace("lsw-text-control.methods.submit"); return LswFormtypes.utils.submitControl.call(this); }, validate() { this.$trace("lsw-text-control.methods.validateSettings"); return LswFormtypes.utils.validateControl.call(this); }, validateSettings() { this.$trace("lsw-text-control.methods.validateSettings"); return LswFormtypes.utils.validateSettings.call(this); } }, watch: {}, mounted() { try { this.$trace("lsw-options-control.mounted"); } catch (error) { console.log(error); } } });
Vue.component("LswRefListControl", { template: $template, props: { settings: { type: Object, default: () => ({}) }, }, data() { this.$trace("lsw-ref-list-control.data"); this.validateSettings(); return { uuid: LswRandomizer.getRandomString(5), value: this.settings.initialValue || [], isValueLoaded: false, isEditable: true, isShownSelector: false, isShownInfo: false, rows: [] }; }, methods: { toggleSelector() { this.$trace("lsw-ref-list-control.methods.toggleSelector"); this.isShownSelector = !this.isShownSelector; }, toggleInfo() { this.$trace("lsw-ref-list-control.methods.toggleInfo"); this.isShownInfo = !this.isShownInfo; }, async submit() { this.$trace("lsw-ref-list-control.methods.submit"); return LswFormtypes.utils.submitControl.call(this); }, validate() { this.$trace("lsw-ref-list-control.methods.validateSettings"); return LswFormtypes.utils.validateControl.call(this); }, validateSettings() { this.$trace("lsw-ref-list-control.methods.validateSettings"); return LswFormtypes.utils.validateSettings.call(this); }, async loadRows() { this.$trace("lsw-page-rows.methods.loadRows", arguments); const selection = await this.$lsw.database.select(this.settings.column.refersTo.table, it => true); this.rows = selection; return selection; }, async loadValue() { this.$trace("lsw-ref-list-control.methods.loadValue"); const selection = await this.$lsw.database.select(this.settings.tableId, it => true); }, }, watch: {}, async mounted() { try { this.$trace("lsw-ref-list-control.mounted"); await this.loadRows(); } catch (error) { console.log(error); } } });
Vue.component("LswRefObjectByLabelControl", { template: $template, props: { settings: { type: Object, default: () => ({}) }, }, data() { this.$trace("lsw-ref-object-by-label-control.data"); this.validateSettings(); return { uuid: LswRandomizer.getRandomString(5), value: this.settings.initialValue || [], isLoaded: false, isValueLoaded: false, isEditable: true, isShownSelector: true, isShownInfo: false, rows: [] }; }, methods: { toggleSelector() { this.$trace("lsw-ref-object-by-label-control.methods.toggleSelector"); this.isShownSelector = !this.isShownSelector; }, toggleInfo() { this.$trace("lsw-ref-object-by-label-control.methods.toggleInfo"); this.isShownInfo = !this.isShownInfo; }, async submit() { this.$trace("lsw-ref-object-by-label-control.methods.submit"); return LswFormtypes.utils.submitControl.call(this); }, validate() { this.$trace("lsw-ref-object-by-label-control.methods.validateSettings"); return LswFormtypes.utils.validateControl.call(this); }, validateSettings() { this.$trace("lsw-ref-object-by-label-control.methods.validateSettings"); return LswFormtypes.utils.validateSettings.call(this); }, async loadRows() { this.$trace("lsw-page-rows.methods.loadRows", arguments); const selection = await this.$lsw.database.select(this.settings.column.refersTo.table, it => true); this.rows = selection; this.isLoaded = true; return selection; }, async loadValue() { this.$trace("lsw-ref-object-by-label-control.methods.loadValue"); const selection = await this.$lsw.database.select(this.settings.tableId, it => true); }, }, watch: {}, async mounted() { try { this.$trace("lsw-ref-object-by-label-control.mounted"); await this.loadRows(); } catch (error) { console.log(error); } } });
Vue.component("LswRefObjectControl", { template: $template, props: { settings: { type: Object, default: () => ({}) }, labelField: { type: String, required: true, } }, data() { this.$trace("lsw-ref-object-control.data"); this.validateSettings(); return { uuid: LswRandomizer.getRandomString(5), value: this.settings.initialValue || [], isLoaded: false, isValueLoaded: false, isEditable: true, isShownSelector: true, isShownInfo: false, rows: [], }; }, methods: { toggleSelector() { this.$trace("lsw-ref-object-control.methods.toggleSelector"); this.isShownSelector = !this.isShownSelector; }, toggleInfo() { this.$trace("lsw-ref-object-control.methods.toggleInfo"); this.isShownInfo = !this.isShownInfo; }, async submit() { this.$trace("lsw-ref-object-control.methods.submit"); return LswFormtypes.utils.submitControl.call(this); }, validate() { this.$trace("lsw-ref-object-control.methods.validateSettings"); return LswFormtypes.utils.validateControl.call(this); }, validateSettings() { this.$trace("lsw-ref-object-control.methods.validateSettings"); return LswFormtypes.utils.validateSettings.call(this); }, async loadRows() { this.$trace("lsw-page-rows.methods.loadRows", arguments); const selection = await this.$lsw.database.select(this.settings.column.refersTo.table, it => true); this.rows = selection; this.isLoaded = true; return selection; }, async loadValue() { this.$trace("lsw-ref-object-control.methods.loadValue"); const selection = await this.$lsw.database.select(this.settings.tableId, it => true); }, selectRow(row) { this.$trace("lsw-ref-object-control.methods.selectRow"); this.value = row; } }, watch: {}, async mounted() { try { this.$trace("lsw-ref-object-control.mounted"); await this.loadRows(); } catch (error) { console.log(error); } } });
Vue.component("LswRefRelationControl", { template: $template, props: { settings: { type: Object, default: () => ({}) }, }, data() { this.$trace("lsw-ref-relation-control.data"); this.validateSettings(); return { uuid: LswRandomizer.getRandomString(5), value: this.settings?.initialValue || "", isEditable: true, }; }, methods: { async submit() { this.$trace("lsw-ref-relation-control.methods.submit"); return LswFormtypes.utils.submitControl.call(this); }, validate() { this.$trace("lsw-ref-relation-control.methods.validateSettings"); return LswFormtypes.utils.validateControl.call(this); }, validateSettings() { this.$trace("lsw-ref-relation-control.methods.validateSettings"); return LswFormtypes.utils.validateSettings.call(this); } }, watch: {}, mounted() { try { this.$trace("lsw-ref-relation-control.mounted"); } catch (error) { console.log(error); } } });
Vue.component("LswSourceCodeControl", { template: $template, props: { settings: { type: Object, default: () => ({}) }, skipLabel: { type: Boolean, default: () => false, } }, data() { this.$trace("lsw-source-code-control.data"); this.validateSettings(); const value = this.settings?.initialValue || this.settings?.column.hasDefaultValue || ""; return { uuid: LswRandomizer.getRandomString(5), value, isEditable: true, currentFontsize: 10, currentFontfamily: "monospace", }; }, methods: { increaseFontsize(points = 1) { this.$trace("lsw-source-code-control.methods.increaseFontsize"); this.currentFontsize += points; }, alternateFontfamily() { this.$trace("lsw-source-code-control.methods.alternateFontfamily"); if(this.currentFontfamily === 'monospace') { this.currentFontfamily = 'Arial'; } else { this.currentFontfamily = 'monospace'; } }, async submit() { this.$trace("lsw-source-code-control.methods.submit"); return LswFormtypes.utils.submitControl.call(this); }, validate() { this.$trace("lsw-source-code-control.methods.validateSettings"); return LswFormtypes.utils.validateControl.call(this); }, validateSettings() { this.$trace("lsw-source-code-control.methods.validateSettings"); return LswFormtypes.utils.validateSettings.call(this); } }, watch: {}, mounted() { try { this.$trace("lsw-source-code-control.mounted"); } catch (error) { console.log(error); } } });
Vue.component("LswSyntaxErrorViewer", { template: $template, props: { error: { type: [Object, Boolean], default: () => false }, onClearError: { type: Function, default: () => {} } }, data() { this.$trace("lsw-syntax-error-viewer.data"); return { currentError: this.error, }; }, methods: { setError(error = undefined) { this.$trace("lsw-syntax-error-viewer.methods.setError"); this.currentError = error; if(typeof error === "undefined") { this.onClearError(); } }, }, watch: {}, mounted() { try { this.$trace("lsw-syntax-error-viewer.mounted"); } catch (error) { console.log(error); } } });
Vue.component("LswSyntaxErrorViewer", { template: $template, props: { error: { type: [Object, Boolean], default: () => false }, onClearError: { type: Function, default: () => {} } }, data() { this.$trace("lsw-syntax-error-viewer.data"); return { currentError: this.error, }; }, methods: { setError(error = undefined) { this.$trace("lsw-syntax-error-viewer.methods.setError"); this.currentError = error; if(typeof error === "undefined") { this.onClearError(); } }, }, watch: {}, mounted() { try { this.$trace("lsw-syntax-error-viewer.mounted"); } catch (error) { console.log(error); } } });
Vue.component("LswTextControl", { template: $template, props: { settings: { type: Object, default: () => ({}) }, skipLabel: { type: Boolean, default: () => false, } }, data() { this.$trace("lsw-text-control.data"); this.validateSettings(); const value = this.settings?.initialValue || this.settings?.column?.hasDefaultValue || ""; return { uuid: LswRandomizer.getRandomString(5), value, isEditable: true, }; }, methods: { async submit() { this.$trace("lsw-text-control.methods.submit"); return LswFormtypes.utils.submitControl.call(this); }, validate() { this.$trace("lsw-text-control.methods.validateSettings"); return LswFormtypes.utils.validateControl.call(this); }, validateSettings() { this.$trace("lsw-text-control.methods.validateSettings"); return LswFormtypes.utils.validateSettings.call(this); } }, watch: {}, computed: { getSettingsInputEvents() { const base0 = this.settings.input?.events || false; if(typeof base0 === "object") { return base0; } return {}; }, getSettingsInputProps() { const base0 = this.settings.input?.props || false; if(typeof base0 === "object") { return base0; } return {}; } }, mounted() { try { this.$trace("lsw-text-control.mounted"); } catch (error) { console.log(error); } } });
Vue.component("LswJsFileCoverageViewer", { template: $ template, props: { source: { type: String, required: true, }, coverage: { type: [Object, Boolean], default: () => false, }, }, data() { this.$trace("lsw-js-file-coverage-viewer.data"); return { isLoaded: false, isExpanded: false, beautifiedSource: false, }; }, methods: { toggleExpansion() { this.$trace("lsw-js-file-coverage-viewer.methods.toggleExpansion"); this.isExpanded = !this.isExpanded; }, async load() { this.$trace("lsw-js-file-coverage-viewer.methods.load"); await LswLazyLoads.loadHighlightJs(); await LswLazyLoads.loadBeautifier(); try { this.beautifiedSource = beautifier.js(this.source); } catch (error) { // @BADLUCK! this.beautifiedSource = this.source; } finally { this.isLoaded = true; } this.$nextTick(() => { hljs.highlightElement(this.$refs.sourceTag); }); } }, watch: {}, async mounted() { try { this.$trace("lsw-js-file-coverage-viewer.mounted"); await this.load(); } catch (error) { console.log(error); } } });
(() => { const emptyOutput = {}; const InspectorTab = class { constructor(options = {}, inspector) { this.inspector = inspector; this.uid = LswRandomizer.getRandomString(5); this.id = inspector.tabs.length; // this.inputSource = "Vue.options.components.LswCalendario.options.template"; this.inputSource = "Vue"; this.pathSource = ''; this.transformerSource = ''; this.searchSource = ''; Object.assign(this, options); } } Vue.component("LswJsInspector", { template: $template, props: { initialBase: { type: [], default: () => window, } }, data() { this.$trace("lsw-js-inspector.data"); return { isDigesting: false, selectedTab: undefined, base: this.initialBase, tabs: [], errorGettingInput: false, errorPathingInput: false, errorTransformingInput: false, output: emptyOutput, emptyOutput: emptyOutput, digestTimeoutId: undefined, digestTimeoutMilliseconds: 1000, }; }, methods: { addTab(specifications = {}) { this.$trace("lsw-js-inspector.methods.addTab"); const anotherTab = new InspectorTab(specifications, this); this.tabs.push(anotherTab); this.selectedTab = anotherTab.uid; return anotherTab; }, cloneTab() { const specifications = { input: this.inputSource, path: this.pathSource, transformer: this.transformerSource, search: this.searchSource, }; const anotherTab = new InspectorTab(specifications, this); this.tabs.push(anotherTab); this.selectedTab = anotherTab.uid; return anotherTab; }, async digestInput() { try { this.$trace("lsw-js-inspector.methods.digestInput"); this.isDigesting = true; clearTimeout(this.digestTimeoutId); const currentTab = this.tabs.filter(tab => tab.uid === this.selectedTab)[0]; let transformedInput = undefined; Getting_input: { try { if (currentTab.inputSource.trim() === "") { throw new Error("Este campo es requerido para iniciar la exploración"); } const callback = LswUtils.createAsyncFunction("return " + currentTab.inputSource, ["it"]) transformedInput = await callback.call(this, transformedInput); } catch (error) { this.errorGettingInput = error; throw error; } } Pathing_input: { try { if (currentTab.pathSource.trim() === "") { break Pathing_input; } transformedInput = jmespath.search(transformedInput, currentTab.pathSource); } catch (error) { this.errorPathingInput = error; throw error; } } Transforming_input: { try { if (currentTab.transformerSource.trim() === "") { break Transforming_input; } transformedInput = await LswUtils.createAsyncFunction(currentTab.transformerSource, ["it"]).call(this, transformedInput); } catch (error) { this.errorTransformingInput = error; throw error; } } Set_output: { this.outputValue = transformedInput; this.output = LswJsInspector.stringifyBeautify(transformedInput); } } catch (error) { console.log(error); this.$lsw.toasts.showError(error); } finally { this.isDigesting = false; } }, backProperty(tabIndex) { this.$trace("lsw-js-inspector.methods.backProperty"); const tab = this.tabs[tabIndex]; const propPath = tab.inputSource.trim(); let newPath = undefined; let pos = undefined; if (propPath.endsWith("]")) { pos = propPath.lastIndexOf("["); } else { pos = propPath.lastIndexOf("."); } const shouldOpenGlobal = (pos === 0) || (propPath.trim() === ''); const shouldIgnore = (pos === -1) && (propPath.trim() !== ''); if (shouldIgnore) { return; } else if (shouldOpenGlobal) { tab.inputSource = "window"; } else { newPath = propPath.slice(0, pos); tab.inputSource = newPath; } this.digestInput(); }, openProperty(tabIndex, propId) { this.$trace("lsw-js-inspector.methods.openProperty"); const tab = this.tabs[tabIndex]; const propRegex = /^[A-Za-z$_][A-Za-z0-9$_]*$/g; const byString = !propRegex.test(propId); const appendment = byString ? `[${JSON.stringify(propId)}]` : `.${propId}`; tab.inputSource += appendment; this.digestInput(); }, updateSearchWithDelay(tab, searchSource) { this.$trace("lsw-js-inspector.methods.updateSearchWithDelay"); clearTimeout(this.digestTimeoutId); this.digestTimeoutId = setTimeout(() => { tab.searchSource = searchSource; this.digestInput(); }, this.digestTimeoutMilliseconds); }, resetTabId(tabIndex) { this.$trace("lsw-js-inspector.methods.resetTabId"); this.tabs[tabIndex].idSource = ''; this.digestInput(); }, resetTabInput(tabIndex) { this.$trace("lsw-js-inspector.methods.resetTabInput"); this.tabs[tabIndex].inputSource = ''; this.digestInput(); }, resetTabPath(tabIndex) { this.$trace("lsw-js-inspector.methods.resetTabPath"); this.tabs[tabIndex].pathSource = ''; this.digestInput(); }, resetTabTransformer(tabIndex) { this.$trace("lsw-js-inspector.methods.resetTabTransformer"); this.tabs[tabIndex].transformerSource = ''; this.digestInput(); }, resetTabSearch(tabIndex) { this.$trace("lsw-js-inspector.methods.resetTabSearch"); this.tabs[tabIndex].searchSource = ''; this.digestInput(); }, openConsole() { this.$trace("lsw-js-inspector.methods.openConsole"); this.$consoleHooker.toggleConsole(); }, }, watch: { selectedTab() { this.digestInput(); } }, async mounted() { try { this.$trace("lsw-js-inspector.mounted"); await LswLazyLoads.loadBeautifier(); await LswLazyLoads.loadJmespath(); await LswJsInspector.initializeFully(); if (this.tabs.length === 0) { this.selectedTab = this.addTab({ id: "Principal", }).uid; } await this.digestInput(); } catch (error) { console.log(error); } } }); })();
Vue.component("LswJsViewer", { template: $template, props: { source: { type: String, required: true, }, }, data() { this.$trace("lsw-js-viewer.data"); return { isLoaded: false, isExpanded: false, beautifiedSource: false, }; }, methods: { toggleExpansion() { this.isExpanded = !this.isExpanded; }, async load() { await LswLazyLoads.loadHighlightJs(); await LswLazyLoads.loadBeautifier(); try { this.beautifiedSource = beautifier.js(this.source); } catch (error) { // @BADLUCK! this.beautifiedSource = this.source; } finally { this.isLoaded = true; } this.$nextTick(() => { hljs.highlightElement(this.$refs.sourceTag); }); } }, watch: {}, async mounted() { try { this.$trace("lsw-js-viewer.mounted"); await this.load(); } catch (error) { console.log(error); } } });
Vue.component("LswMermoidViewer", { template: $template, props: { source: { type: String, required: true, } }, data() { this.$trace("lsw-mermoid-viewer.data"); return { isLoaded: false, }; }, methods: { composeVisualTree() { this.$trace("lsw-mermoid-viewer.methods.composeVisualTree"); // ... console.log("composeVisualTree:", this.source); } }, watch: {}, async mounted() { try { this.$trace("lsw-mermoid-viewer.mounted"); } catch (error) { console.log(error); } } });
Vue.component("LswNotes", { template: $template, props: { autoDialog: { type: Boolean, default: () => false, }, onAutoDialogSuccess: { type: Function, default: () => {}, }, onAutoDialogError: { type: Function, default: () => {}, } }, data() { this.$trace("lsw-notes.data"); return { isLoaded: false, allNotes: false, openedNotes: [], currentError: this.error, }; }, methods: { setError(error = undefined) { this.$trace("lsw-notes.methods.setError"); this.currentError = error; }, toggleNote(noteId) { this.$trace("lsw-notes.methods.toggleNote"); const pos = this.openedNotes.indexOf(noteId); if(pos === -1) { this.openedNotes.push(noteId); } else { this.openedNotes.splice(pos, 1); } }, async loadNotes() { this.$trace("lsw-notes.methods.loadNotes"); // *@TODO: seleccionar e importar notes. this.isLoaded = false; const notes = await this.$lsw.database.selectMany("Nota"); const notesSorted = notes.sort((n1, n2) => { const d1 = LswTimer.utils.getDateFromMomentoText(n1.tiene_fecha); const d2 = LswTimer.utils.getDateFromMomentoText(n2.tiene_fecha); if(d1 >= d2) return -1; return 1; }); this.allNotes = notesSorted; this.isLoaded = true; }, async openAddNoteDialog() { this.$trace("lsw-notes.methods.openAddNoteDialog"); const response = await LswUtils.openAddNoteDialog(); if(typeof response !== "object") { return; } await this.$lsw.database.insert("Nota", response); await this.loadNotes(); } }, watch: {}, async mounted() { try { this.$trace("lsw-notes.mounted"); await this.loadNotes(); if(this.autoDialog) { this.openAddNoteDialog(); } } catch (error) { console.log(error); } } });
Vue.component("LswPegjsTester", { template: $template, props: { source: { type: String, default: () => 'Example = "0"', } }, data() { this.$trace("lsw-pegjs-tester.data"); return { isLoaded: false, currentAst: false, currentParserSource: this.source, currentParser: false, currentSnippet: "0", currentParserSyntaxError: false, currentSnippetSyntaxError: false, showingPaneOf: 'parser', }; }, methods: { async loadDependencies() { await LswLazyLoads.loadPegjs(); }, showParserError(error) { this.$refs.parserErrorViewer.setError(error); }, showSnippetError(error) { this.$refs.snippetErrorViewer.setError(error); }, compileSyntax() { this.$trace("lsw-pegjs-tester.methods.compileSyntax"); try { this.currentParser = PEG.buildParser(this.currentParserSource); this.showParserError(false); } catch (error) { this.showParserError(error); this.$lsw.toasts.showError(error); throw error; } }, compileParser() { this.$trace("lsw-pegjs-tester.methods.compileParser"); this.compileSyntax(); this.showingPaneOf = 'snippet'; }, resetState() { this.$trace("lsw-pegjs-tester.methods.resetState"); this.currentSnippetSyntaxError = false; this.currentAst = false; }, testSnippet() { this.$trace("lsw-pegjs-tester.methods.testSnippet"); try { this.resetState(); Check_it_has_parser: { if (!this.currentParser) { throw new Error("No parser selected on «testSnippet»"); } } Parse_contents: { try { this.currentAst = this.currentParser.parse(this.currentSnippet); this.currentSnippetSyntaxError = false; this.showSnippetError(false); } catch (error) { this.showSnippetError(error); throw error; } } } catch (error) { this.$lsw.toasts.showError(error); } } }, watch: {}, async mounted() { try { this.$trace("lsw-pegjs-tester.mounted"); await this.loadDependencies(); } catch (error) { console.log(error); } } });
Vue.component("LswSchemaBasedForm", { template: $template, props: { model: { type: Object, required: true, }, onSubmit: { type: Function, default: () => this.$noop, }, onValidate: { type: Function, default: () => this.$noop, }, onDeleteRow: { type: Function, default: () => this.$noop, }, overridenValues: { type: Object, default: () => ({}) } }, data() { this.$trace("lsw-schema-based-form.data"); this.validateModel(this.model); const isOperation = (this.model.row && this.model.row.id) || (this.model.rowId && (this.model.rowId !== -1)) ? "update" : "insert"; return { own: this, validFormtypes: [ "text", "long-text", "options", "boolean", "date", "duration", "ref-object", "ref-object-by-label", "ref-list", "ref-relation", "source-code", ], section: 'campos propios', // 'campos reflejos' isShowingFormInfo: false, isLoaded: false, tableDefinition: false, columnDefinitions: false, value: this.model.row ?? false, editableFields: [], minimizedFields: [], isOperation, isUpdateOperation: isOperation === "update", isInsertOperation: isOperation === "insert", }; }, methods: { selectSection(section) { this.section = section; }, toggleMinimizedField(field) { this.$trace("lsw-schema-based-form.methods.toggleMinimizedField"); const fieldPos = this.minimizedFields.indexOf(field); if (fieldPos === -1) { this.minimizedFields.push(field); } else { this.minimizedFields.splice(fieldPos, 1); } this.$forceUpdate(true); }, hideMinimizedField(field) { this.$trace("lsw-schema-based-form.methods.hideMinimizedField"); const fieldPos = this.minimizedFields.indexOf(field); if (fieldPos === -1) { this.minimizedFields.push(field); } this.$forceUpdate(true); }, showMinimizedField(field) { this.$trace("lsw-schema-based-form.methods.showMinimizedField"); const fieldPos = this.minimizedFields.indexOf(field); if (fieldPos !== -1) { this.minimizedFields.splice(fieldPos, 1); } this.$forceUpdate(true); }, toggleEditableField(field) { this.$trace("lsw-schema-based-form.methods.toggleEditableField"); const fieldPos = this.editableFields.indexOf(field); if (fieldPos === -1) { this.editableFields.push(field); } else { this.editableFields.splice(fieldPos, 1); } }, saveField(field, value) { this.$trace("lsw-schema-based-form.methods.saveField"); console.log("Should save field:", field, value); // *@TODO: use $lsw.database.overwrite to send the field only }, validateModel(model) { this.$trace("lsw-schema-based-form.methods.validateModel"); try { const ensureModel = $ensure({ model }, 1); const checkModel = $check(model); Basic_type_and_signature: { ensureModel.type("object"); ensureModel.to.have.uniquelyKeys(["connection", "databaseId", "tableId", "rowId", "row", "databaseExplorer"]); ensureModel.to.have.keys(["databaseId", "tableId"]); const correctOption = $ensure.$or({ "has connection and rowId (set -1 for new instances)": () => ensureModel.to.have.key("rowId"), "has row": () => ensureModel.to.have.key("row"), }); if (!checkModel.to.have.key("rowId")) { ensureModel.to.have.key("row"); } } Component_types_and_signatures: { if (checkModel.to.have.key("connection")) { ensureModel.its("connection").type("object"); } if (checkModel.to.have.key("databaseId")) { ensureModel.its("databaseId").type("string"); } if (checkModel.to.have.key("tableId")) { ensureModel.its("tableId").type("string"); } if (checkModel.to.have.key("rowId")) { ensureModel.its("rowId").type("number"); } if (checkModel.to.have.key("row")) { $ensure.$or({ "row is object": () => ensureModel.its("row").type("object"), "row is false": () => ensureModel.its("row").type("boolean").is(false), }); } if(checkModel.to.have.key("databaseExplorer")) { ensureModel.its("databaseExplorer").type("object"); } } } catch (error) { console.error("Failed validating «model» property on «lsw-schema-based-form.validateModel»"); console.error(error); } }, async loadValue() { this.$trace("lsw-schema-based-form.methods.loadValue"); if (this.model.rowId) { const originalValues = await LswDatabase.pickRow(this.model.databaseId, this.model.tableId, this.model.rowId); this.value = Object.assign({}, originalValues, this.overridenValues); } }, onlyKnownTypes(formtype) { if(this.validFormtypes.indexOf(formtype) !== -1) { return formtype; } return "long-text"; }, async loadSchema() { this.$trace("lsw-schema-based-form.methods.loadSchema"); const columnIds = Object.keys($lswSchema.$schema.hasTables[this.model.tableId].hasColumns); for(let columnId of columnIds) { const columnData = $lswSchema.$schema.hasTables[this.model.tableId].hasColumns[columnId]; Object.assign(columnData, { belongsToDatabase: this.model.databaseId, belongsToTable: this.model.tableId, hasFormtypeSettings: { id: 'lsw-' + this.onlyKnownTypes(columnData.isFormType) + '-control', name: columnId, input: { props: { placeholder: columnData.hasPlaceholder, }, events: { } }, } }) } this.tableDefinition = $lswSchema.$schema.hasTables[this.model.tableId]; this.columnDefinitions = this.tableDefinition.hasColumns; }, toggleFormInfo() { this.$trace("lsw-schema-based-form.methods.toggleFormInfo"); this.isShowingFormInfo = !this.isShowingFormInfo; }, closeEditables() { this.$trace("lsw-schema-based-form.methods.closeEditables"); const uneditables = this.$el.querySelectorAll(".lsw_form_control .lsw_control_label .button_to_uneditable"); for(let index=0; index<uneditables.length; index++) { const uneditable = uneditables[index]; uneditable.click(); } }, openEditables() { this.$trace("lsw-schema-based-form.methods.openEditables"); const editables = this.$el.querySelectorAll(".lsw_form_control .lsw_control_label .button_to_editable"); for(let index=0; index<editables.length; index++) { const editable = editables[index]; editable.click(); } }, validateForm() { this.$trace("lsw-schema-based-form.methods.validateForm"); return this.$refs.schemaForm0.$xform.validate(); }, async submitForm(v) { this.$trace("lsw-schema-based-form.methods.submitForm"); return await this.$refs.schemaForm0.$xform.submit(); }, passToPrinter() { this.$trace("lsw-schema-based-form.methods.passToPrinter"); const value = this.$refs.schemaForm0.$xform.getValue(); this.$lsw.dialogs.open({ title: "Impresión de estado de formulario", template: ` <lsw-data-printer-report :input="input" /> `, factory: { data: { input: value } }, }); }, async deleteRow() { this.$trace("lsw-schema-based-form.methods.deleteRow"); const confirmed = await this.$lsw.dialogs.open({ id: `eliminar-registro-${this.model.tableId}-${this.model.rowId || this.model.row.id}-${LswRandomizer.getRandomString(5)}`, title: "Eliminar registro", template: ` <div> <div class="pad_2 font_weight_bold">ATENCIÓN: </div> <div class="pad_2">¿Seguro que quieres eliminar el registro <b>{{ model.tableId }}</b> cuyo <b>id</b>#<b>{{ model.rowId || model.row.id }}</b>?</div> <div class="pad_2"> <pre class="pad_2 codeblock">{{ JSON.stringify(model.row || rowValue, null, 2) }}</pre> </div> <hr class="margin_0" /> <div class="pad_2 text_align_right"> <button class="supermini danger_button" v-on:click="() => accept(true)">Eliminar</button> <button class="supermini " v-on:click="() => accept(false)">Cancelar</button> </div> </div> `, factory: { data: { model: this.model, rowValue: this.value } } }); if(!confirmed) return false; const rowIdentifier = this.model.rowId || this.model.row.id; await this.$lsw.database.delete(this.model.tableId, rowIdentifier); if(this.onDeleteRow) { const result = this.onDeleteRow(this.model.rowId, this.model.tableId, true); // INTERCEPT REDIRECTION RETURNING FALSE FROM onDeleteRow PARAMETRIC CALLBACK if(result === false) return; } if(this.model.databaseExplorer) { if(this.model.databaseExplorer.showBreadcrumb) { this.model.databaseExplorer.selectPage("LswPageRows", { database: this.model.databaseId, table: this.model.tableId, }); } else { } } } }, watch: { }, async mounted() { try { this.$trace("lsw-schema-based-form.mounted"); await this.loadSchema(); await this.loadValue(); this.isLoaded = true; this.$nextTick(() => { window.sf0 = this.$refs.schemaForm0; }); } catch (error) { console.log(error); } } });
Vue.component("LswTable", { template: $template, props: { initialInput: { type: Array, default: () => [] }, initialSettings: { type: Object, default: () => ({}) }, rowButtons: { type: Array, default: () => [] }, tableButtons: { type: Array, default: () => [] }, selectable: { type: String, default: () => "none" }, onChooseRow: { type: Function, default: () => {} }, choosableId: { type: String, default: () => "id" }, initialChoosenValue: { type: [], default: () => [] }, storageId: { type: [String, Boolean], default: () => false }, storageStrategy: { type: String, default: () => "ufs/lsw", // No otras de momento. } }, data() { this.$trace("lsw-table.data"); const input = [].concat(this.initialInput); return { input, title: this.initialSettings?.title || "", isShowingMenu: this.initialSettings?.isShowingMenu || false, isShowingSubpanel: this.initialSettings?.isShowingSubpanel || "Todo", // "Buscador", ... selectedRows: [], choosenRows: this.initialChoosenValue || [], searcher: this.initialSettings?.searcher || "", extender: this.initialSettings?.extender || "", filter: this.initialSettings?.filter || "", sorter: this.initialSettings?.sorter || "", itemsPerPageOnForm: this.initialSettings?.itemsPerPage || 10, itemsPerPage: this.initialSettings?.itemsPerPage || 10, currentPage: this.initialSettings?.currentPage || 0, currentPageOnForm: (this.initialSettings?.currentPage+1) || 1, columnsAsList: this.initialSettings?.columnsAsList || [], columnsOrder: this.initialSettings?.columnsOrder || [], columnsOrderInput: (this.initialSettings?.columnsOrder || []).join(", "), output: [], paginatedOutput: [], headers: [], attachedHeaders: this._adaptRowButtonsToHeaders(this.rowButtons), attachedColumns: this._adaptRowButtonsToColumns(this.rowButtons), attachedTopButtons: this._adaptRowButtonsToColumns(this.tableButtons), placeholderForExtensor: "data.map(function(it, i) {\n return Object.assign({}, it, /* you start here */ || {});\n});", placeholderForOrdenador: "data.sort(function(a, b) {\n return /* you start here */;\n});", placeholderForFiltro: "data.filter(function(it, i) {\n return /* you start here */;\n});", placeholderForBuscador: "Búsqueda de texto rápida", placeholderForPaginador: "Ítems por página. Por defecto: 10" }; }, methods: { goToFirstPage() { this.$trace("lsw-table.methods.goToFirstPage"); this.currentPage = 0; }, decreasePage() { this.$trace("lsw-table.methods.decreasePage"); if (this.currentPage > 0) { this.currentPage--; } }, increasePage() { this.$trace("lsw-table.methods.increasePage"); const lastPage = Math.floor(this.output.length / this.itemsPerPage); if (this.currentPage < lastPage) { this.currentPage++; } }, goToLastPage() { this.$trace("lsw-table.methods.goToLastPage"); const lastPage = Math.floor(this.output.length / this.itemsPerPage); if (this.currentPage !== lastPage) { this.currentPage = lastPage; } }, toggleChoosenRow(rowId) { this.$trace("lsw-table.methods.toggleChoosenRow"); if(this.selectable === 'many') { const pos = this.choosenRows.indexOf(rowId); if (pos === -1) { this.choosenRows.push(rowId); } else { this.choosenRows.splice(pos, 1); } } else if(this.selectable === 'one') { const isSame = this.choosenRows === rowId; if(isSame) { this.choosenRows = undefined; } else { this.choosenRows = rowId; } } }, toggleRow(rowIndex) { this.$trace("lsw-table.methods.toggleRow"); if(typeof rowIndex === "undefined") { return this.$lsw.toasts.send({ title: "La row no se desplegará", text: "Añade «id» para que se puedan seleccionar las rows" }) } const pos = this.selectedRows.indexOf(rowIndex); if (pos === -1) { this.selectedRows.push(rowIndex); } else { this.selectedRows.splice(pos, 1); } }, toggleMenu() { this.$trace("lsw-table.methods.toggleMenu"); this.isShowingMenu = !this.isShowingMenu; }, digestOutput() { this.$trace("lsw-table.methods.digestOutput"); const input = this.input; let temp = []; const extenderExpression = this.extender.trim() || "{}"; const extenderFunction = new Function("it", "i", `return ${extenderExpression}`); const filterExpression = this.filter.trim() || "true"; const filterFunction = new Function("it", "i", `return ${filterExpression}`); const sorterExpression = this.sorter.trim() || "0"; const sorterFunction = new Function("a", "b", `return ${sorterExpression}`); let tempHeaders = new Set(); Iterating_rows: for (let index = 0; index < input.length; index++) { const row = input[index]; let extendedRow = undefined; Apply_extender: { try { const extenderProduct = extenderFunction(row, index) || {}; extendedRow = Object.assign({}, row, extenderProduct); } catch (error) { extendedRow = Object.assign({}, row); } } let isValidFinally = true; Apply_searcher: { if(this.searcher.trim() !== "") { const hasMatch = JSON.stringify(extendedRow).toLowerCase().indexOf(this.searcher.toLowerCase()) !== -1; if(!hasMatch) { isValidFinally = isValidFinally && false; } } } Apply_filter: { try { const filterProduct = filterFunction(extendedRow, index); if (filterProduct !== true) { isValidFinally = isValidFinally && false; } } catch (error) { // @OK. } } Extract_row: { if(isValidFinally) { temp.push(extendedRow); } } Extract_headers: { try { Object.keys(extendedRow).forEach(key => { tempHeaders.add(key); }); } catch (error) { // @OK. } } } Apply_sorter: { try { temp = temp.sort(sorterFunction); } catch (error) { // @OK. } Also_to_headers: { if(Array.isArray(this.columnsOrder) && this.columnsOrder.length) { tempHeaders = [...tempHeaders].sort((h1, h2) => { const pos1 = this.columnsOrder.indexOf(h1); const pos2 = this.columnsOrder.indexOf(h2); if(pos1 === -1 && pos2 === -1) { return -1; } else if(pos1 === -1) { return 1; } else if(pos2 === -1) { return -1; } else if(pos1 > pos2) { return 1; } return -1; }); } } } this.headers = tempHeaders; this.output = temp; this.digestPagination(); }, digestPagination() { this.$trace("lsw-table.methods.digestPagination"); const page = this.currentPageOnForm - 1; Inject_form_state_of_items_per_page_here: { this.itemsPerPage = this.itemsPerPageOnForm; } const items = this.itemsPerPage; const firstPosition = items * (page); this.selectedRows = []; this.paginatedOutput = [].concat(this.output).splice(firstPosition, items); }, saveCurrentTransformer() { this.$trace("lsw-table.methods.saveCurrentTransformer"); }, _adaptRowButtonsToHeaders(rowButtons) { const attachedHeaders = []; for(let index=0; index<rowButtons.length; index++) { const attachedButton = rowButtons[index]; attachedHeaders.push({ text: attachedButton.header || "" }); } return attachedHeaders; }, _adaptRowButtonsToColumns(rowButtons) { const attachedColumns = []; for(let index=0; index<rowButtons.length; index++) { const attachedButton = rowButtons[index]; attachedColumns.push({ text: attachedButton.text || "", event: attachedButton.event || this.$noop, }); } return attachedColumns; }, updateColumnsOrderFromInput() { this.$trace("lsw-table.methods.updateColumnsOrderFromInput"); this.columnsOrder = this.columnsOrderInput.split(",").map(it => it.trim()); this.digestOutput(); }, increaseItemsPerPage() { this.$trace("lsw-table.methods.increaseItemsPerPage"); this.itemsPerPageOnForm++; }, decreaseItemsPerPage() { this.$trace("lsw-table.methods.decreaseItemsPerPage"); this.itemsPerPageOnForm--; }, getStoragePathFor(id) { this.$trace("lsw-table.methods.getStoragePathFor"); return this.$lsw.fs.resolve_path("/kernel/settings/table/storage/", id); }, async loadState() { this.$trace("lsw-table.methods.loadState"); Check_strategy_and_validation: { if(this.storageStrategy !== "ufs/lsw") { console.log(`[*] Could not load state on lsw-table because of: UnknownStorageStrategy (=${this.storageStrategy})`); return -1; } if(!this.storageId) { // console.log(`[*] Could not load state on lsw-table because of: NoStorageId (=${this.storageId})`); return -2; } } const storagePath = this.getStoragePathFor(this.storageId); const storageJson = await (() => { try { return this.$lsw.fs.read_file(storagePath); } catch (error) { console.log(`[*] Could not load state on lsw-table because of: BadStoragePath (=${this.storagePath})`); return undefined; } })(); if(typeof storageJson !== "string") { console.log(`[*] Could not load state on lsw-table because of: JsonStorageNotString (=${typeof storageJson})`); return -3; } let storageData = undefined; try { storageData = JSON.parse(storageJson); } catch (error) { console.log(`[*] Could not load state on lsw-table because of: JsonStorageNotParseable (${error.name}=${error.message})`); return -4; } Cargar_estado: { if(typeof storageData !== "object") { console.log(`[*] Could not load state on lsw-table because of: StorageDataNotObject (${typeof storageData})`); return -5; } console.log("[*] Loading lsw-table state from: ", storageData); Object.assign(this, storageData); } }, saveState() { this.$trace("lsw-table.methods.saveState"); Check_strategy_and_validation: { if(this.storageStrategy !== "ufs/lsw") { console.log(`[*] Could not save state on lsw-table because of: UnknownStorageStrategy (=${this.storageStrategy})`); return -1; } if(!this.storageId) { // console.log(`[*] Could not save state on lsw-table because of: NoStorageId (=${this.storageId})`); return -2; } } const storagePath = this.getStoragePathFor(this.storageId); const storageState = this.extractState(); const storageJson = JSON.stringify(storageState, null, 2); Guardar_estado: { console.log("[*] Saving lsw-table state as: ", storageState); this.$lsw.fs.write_file(storagePath, storageJson); this.$lsw.toasts.send({ title: "Estado de tabla guardado", text: "Con identificador: " + this.storageId, }); } return true; }, extractState() { this.$trace("lsw-table.methods.extractState"); return LswUtils.extractPropertiesFrom(this, [ // "input", "title", "isShowingMenu", "isShowingSubpanel", "selectedRows", "choosenRows", "searcher", "extender", "filter", "sorter", "itemsPerPageOnForm", "itemsPerPage", "currentPage", "currentPageOnForm", "columnsAsList", "columnsOrder", "columnsOrderInput", // "output", // "paginatedOutput", "headers", // "attachedHeaders", // "attachedColumns", // "attachedTopButtons", // "placeholderForExtensor", // "placeholderForOrdenador", // "placeholderForFiltro", // "placeholderForBuscador", // "placeholderForPaginador", ], [ "input", "output", "paginatedOutput", "attachedHeaders", "attachedColumns", "attachedTopButtons", "placeholderForExtensor", "placeholderForOrdenador", "placeholderForFiltro", "placeholderForBuscador", "placeholderForPaginador", ], { isShowingMenu: false, isShowingSubpanel: "Todo", }); }, }, watch: { itemsPerPage(value) { this.$trace("lsw-table.watch.itemsPerPage"); /// this.digestPagination(); }, currentPage(value) { this.$trace("lsw-table.watch.currentPage"); this.currentPageOnForm = value + 1; this.digestPagination(); }, choosenRows(v) { this.$trace("lsw-table.watch.value"); this.onChooseRow(v, this); } }, computed: { hasFiltersApplying() { // @BUGGY: estos logs causan recursividad en el console-hooker // this.$trace("lsw-table.computed.hasFiltersApplying"); if (this.extender.length) { return true; } if (this.filter.length) { return true; } if (this.sorter.length) { return true; }; if (this.searcher.length) { return true; } if (this.currentPage !== 0) { return true; } if ((this.currentPage+1) !== this.currentPageOnForm) { return true; } if (this.itemsPerPage < 10) { return true; } if (this.itemsPerPageOnForm !== this.itemsPerPage) { return true; } if(this.columnsOrderInput !== "id") { return true; } if(this.columnsOrder.length !== 1) { return true; } return false; }, totalOfPages() { // @BUGGY: estos logs causan recursividad en el console-hooker // this.$trace("lsw-table.computed.totalOfPages"); return Math.ceil(this.output.length / this.itemsPerPage) || 1; }, currentLastPage() { this.$trace("lsw-table.computed.currentLastPage"); return Math.floor(this.output.length / this.itemsPerPage) || 1; } }, mounted() { this.$trace("lsw-table.mounted"); this.loadState(); this.digestOutput(); } });
Vue.component("LswToasts", { template: $template, props: {}, data() { return { sent: {} }; }, methods: { getRandomString(len = 10) { const alphabet = "abcdefghijklmnopqrstuvwxyz".split(""); let out = ""; while (out.length < len) { out += alphabet[Math.floor(Math.random() * alphabet.length)]; } return out; }, showError(error, args = {}, propagate = false, log = true) { this.$trace("lsw-toasts.methods.showError"); let parameters = {}; const isSyntaxError = typeof error.location === "object"; if (isSyntaxError) { parameters = { title: `Error sintáctico en ${error.location.start.line}:${error.location.start.column} (${error.found})`, text: error.expected, } } else { parameters = { title: "Un error ocurrió", text: error.name + ": " + error.message, ...args, } } const output = this.send(parameters); if (log) { console.log(error); } if (propagate) { throw error; } return output; }, send(toastsInput = {}) { const toastData = Object.assign({ id: this.getRandomString(), title: "", text: "", timeout: 3000, orientation: "bottom", background: "rgba(255,255,255,0.5)", foreground: "#000", started_at: new Date() }, toastsInput); if (typeof toastData.timeout !== "number") { throw new Error("Required parameter «timeout» to be a number or empty on «LswToasts.methods.send»"); } if (isNaN(toastData.timeout)) { throw new Error("Required parameter «timeout» to be a (non-NaN) number or empty on «LswToasts.methods.send»"); } if (["top", "bottom", "center"].indexOf(toastData.orientation) === -1) { throw new Error("Required parameter «orientation» to be a string (top, center, bottom) or empty on «LswToasts.methods.send»"); } if (toastData.id in this.sent) { throw new Error("Required parameter «id» to not be repeated on «LswToasts.methods.send»"); } this.sent = Object.assign({}, this.sent, { [toastData.id]: toastData }); setTimeout(() => { this.close(toastData.id); }, toastData.timeout); }, close(id) { delete this.sent[id]; this.$forceUpdate(true); }, debug(anyzing) { this.send({ title: typeof anyzing, text: LswUtils.stringify(anyzing), }); }, collapse(anyzing, timeout = 3000) { this.$lsw.dialogs.open({ title: "Debugging: type " + typeof anyzing, template: ` <div class="pad_1"> <div>Moment: {{ moment }}</div> <pre class="codeblock" style="font-size: 10px;">{{ code }}</pre> </div> `, factory: function() { return { data() { return { moment: LswTimer.utils.fromDateToDatestring(new Date(), true), code: typeof anyzing === "string" ? anyzing : JSON.stringify(anyzing, null, 2), }; }, mounted() { setTimeout(() => this.cancel(), timeout); }, }; } }); } }, watch: {}, mounted() { this.$toasts = this; this.$window.LswToasts = this; if (this.$lsw) { this.$lsw.toasts = this; } this.$window.dd = (...args) => { return this.debug(...args); }; } });
window.asserters = []; Vue.component("LswTesterModuleViewer", { template: $template, props: { tester: { type: Object, required: true, }, test: { type: Object, required: true, } }, data() { return { state: "not started", // "started", "ignored", "passed" or "failed" assertions: [], }; }, methods: { addAssertion(assertionData) { this.$trace("lsw-tester-module-viewer.methods.addAssertion"); this.assertions = [].concat(this.assertions).concat([assertionData]); this.$forceUpdate(true); }, changeState(newState) { this.$trace("lsw-tester-module-viewer.methods.changeState"); this.state = newState; this.$forceUpdate(true); } }, watch: {}, async mounted() { this.$trace("lsw-tests-page.mounted"); asserters.push(this); } });
const LswTesterViewerUtils = {}; LswTesterViewerUtils.getEventSourceId = function(eventData) { return eventData?.value?.id || "tester"; }; LswTesterViewerUtils.initModule = function(component, id) { if(!(id in component.states)) { component.states[id] = "not started"; } }; LswTesterViewerUtils.registerEvent = function(component, eventType, id, eventData) { LswTesterViewerUtils.initModule(component, id); Notify_new_state_to_tester: { if(eventType === "onStartModule") { component.states[id] = "started"; } else if(eventType === "onSuccessModule") { component.states[id] = "passed"; Notify_to_submodule: { const selector = `[data-test-module=${JSON.stringify(id)}]`; const moduleComponent = LswDom.findVue(selector, false, component.$refs.esquema_de_tests); if(!moduleComponent) { break Notify_new_state_to_tester; } moduleComponent.changeState("passed"); } } else if(eventType === "onErrorModule") { component.states[id] = "failed"; Notify_to_submodule: { const selector = `[data-test-module=${JSON.stringify(id)}]`; const moduleComponent = LswDom.findVue(selector, false, component.$refs.esquema_de_tests); if(!moduleComponent) { break Notify_new_state_to_tester; } moduleComponent.changeState("failed"); } } } Append_assertion: { if((eventType === "onSuccessAssertion") || (eventType === "onErrorAssertion")) { const selector = `[data-test-module=${JSON.stringify(id)}]`; const moduleComponent = LswDom.findVue(selector, false, component.$refs.esquema_de_tests); if(!moduleComponent) { break Append_assertion; } moduleComponent.addAssertion({ text: eventData.assertionText, result: eventData.assertionResult, }); } } }; Vue.component("LswTesterViewer", { template: $template, props: { tester: { type: Object, required: true, }, testsPage: { type: Object, default: () => false } }, data() { return this.getInitialState(); }, methods: { getInitialState() { this.$trace("lsw-tests-page.methods.getInitialState"); return { isRun: false, isStarted: false, isLoaded: false, isShowingSubmodules: false, isShowingErrors: false, isShowingEventsConsole: false, isSuccessfullyCompleted: false, currentErrors: [], loadingError: false, runningError: false, temporizer: LswTemporizer.create(), states: {}, }; }, goToCoverage() { this.$trace("lsw-tests-page.methods.goToCoverage"); if(this.testsPage) { this.testsPage.selectSection("coverage"); } }, addEvent(event, eventData) { this.$trace("lsw-tests-page.methods.addEvent"); if(event.eventType === "onStartTester") { this.temporizer.start(); } else if(event.eventType === "onEndTester") { this.isSuccessfullyCompleted = this.currentErrors.length === 0; } else if((event.eventType === "onErrorModule") || (event.eventType === "onErrorAssertion")) { this.currentErrors.push({ errorType: event.eventType, ...eventData }); } const id = LswTesterViewerUtils.getEventSourceId(eventData); The_magic_should_happen_mostly_here: { eventData.timeoff = this.temporizer.getTime(); LswTesterViewerUtils.registerEvent(this, event.eventType, id, eventData); } this.$refs.eventsConsole.textContent = `>> ${id} > ${ event.eventType } @${eventData.timeoff}\n${ this.$refs.eventsConsole.textContent }`; }, toggleEventsConsole() { this.$trace("lsw-tests-page.methods.toggleEventsConsole"); this.isShowingEventsConsole = !this.isShowingEventsConsole; }, toggleErrors() { this.$trace("lsw-tests-page.methods.toggleErrors"); this.isShowingErrors = !this.isShowingErrors; }, toggleSubmodules() { this.$trace("lsw-tests-page.methods.toggleSubmodules"); this.isShowingSubmodules = !this.isShowingSubmodules; }, validateTester() { this.$trace("lsw-tests-page.methods.validateTester"); $ensure(this.tester).to.be.instanceOf(LswTester); }, async loadTester() { this.$trace("lsw-tests-page.methods.loadTester"); try { this.validateTester(); await this.tester.options({}).load(); } catch (error) { this.loadingError = error; this.$lsw.toasts.showError(error); console.log(error); } finally { this.isLoaded = true; } }, async runTester() { this.$trace("lsw-tests-page.methods.runTester"); try { this.isStarted = true; const viewer = this; await this.tester.options({ trace: true, onAnything(event, ...args) { console.log("Notify to widget event:", event, args); viewer.addEvent(event, ...args); } }).run(1); } catch (error) { this.runningError = error; this.$lsw.toasts.showError(error); console.log(error); } finally { this.isRun = true; } }, resetTester() { this.$trace("lsw-tests-page.methods.resetTester"); LswDomIrruptor.abrirTestsDeAplicacion(); } }, watch: {}, computed: { isSuccessfullyCompleted() { }, isCompleted() { return this.tester.$isExecuted; }, currentState() { return !this.isLoaded ? "to be loaded" : !this.isStarted ? "loaded" : !this.isRun ? "running" : "finished"; } }, async mounted() { this.$trace("lsw-tests-page.mounted"); this.loadTester(); window.testerviewer = this; } });
Vue.component("LswTestsPage", { template: $template, props: { }, data() { return { selectedSection: "coverage", initializationError: false, choosenTester: false, }; }, methods: { selectSection(subsection) { this.$trace("lsw-tests-page.methods.selectSection"); this.selectedSection = subsection; }, async initializeTester() { this.$trace("lsw-tests-page.methods.initializeTester"); try { this.choosenTester = LswTester.create().define({ id: "lsw.test.main", fromCollection: [{ id: "lsw.test.main.intro", fromCallback: async function (context) { const { assert } = context; assert.as("some assertion").that(true); await LswDom.waitForMilliseconds(500); assert.as("some assertion").that(true); await LswDom.waitForMilliseconds(500); assert.as("some assertion").that(true); await LswDom.waitForMilliseconds(500); assert.as("some assertion").that(true); throw new Error("algo pasó aquí") await LswDom.waitForMilliseconds(500); assert.as("some assertion").that(true); await LswDom.waitForMilliseconds(500); console.log("lsw.test.main.intro"); }, }, { id: "lsw.test.main.body", fromCollection: [{ id: "lsw.test.main.body.check-globals", fromCallback: async function (context) { const { assert } = context; assert.as("some assertion").that(true); await LswDom.waitForMilliseconds(500); assert.as("some assertion").that(true); await LswDom.waitForMilliseconds(500); assert.as("some assertion").that(true); await LswDom.waitForMilliseconds(500); assert.as("some assertion").that(true); await LswDom.waitForMilliseconds(500); assert.as("some assertion").that(true); await LswDom.waitForMilliseconds(500); console.log("lsw.test.main.body.check-globals"); } }, { id: "lsw.test.main.body.check-vue", fromCallback: async function (context) { const { assert } = context; assert.as("some assertion").that(true); await LswDom.waitForMilliseconds(500); assert.as("some assertion").that(true); await LswDom.waitForMilliseconds(500); assert.as("some assertion").that(true); await LswDom.waitForMilliseconds(500); assert.as("some assertion").that(true); await LswDom.waitForMilliseconds(500); assert.as("some assertion").that(true); await LswDom.waitForMilliseconds(500); console.log("lsw.test.main.body.check-vue"); } }], }, { id: "lsw.test.main.end", fromCallback: async function (context) { const { assert } = context; assert.as("some assertion").that(true); await LswDom.waitForMilliseconds(500); assert.as("some assertion").that(true); await LswDom.waitForMilliseconds(500); assert.as("some assertion").that(true); await LswDom.waitForMilliseconds(500); assert.as("some assertion").that(true); await LswDom.waitForMilliseconds(500); assert.as("some assertion").that(true); await LswDom.waitForMilliseconds(500); console.log("lsw.test.main.end"); }, }] }).options({ onAnything(event) { console.log("eventuated:", event); } }); } catch (error) { this.initializationError = error; this.$lsw.toasts.showError(error); console.log(error); throw error; } } }, watch: {}, async mounted() { this.$trace("lsw-tests-page.mounted"); await this.initializeTester(); } });
Vue.component("LswTestsPage", { template: $template, props: { }, data() { return { isLoaded: false, isRunning: false, isDownloaded: false, isCompleted: false, section: "available", baseUrl: "assets/tests/cases/", available: [], selected: [], downloaded: [], }; }, methods: { goToSection(section) { this.$trace("lsw-tests-page.methods.goToSection"); this.section = section; }, backToTests() { this.$trace("lsw-tests-page.methods.backToTests"); this.resetState(); }, resetState() { this.$trace("lsw-tests-page.methods.resetState"); this.isLoaded = false; this.isRunning = false; this.isDownloaded = false; }, selectTest(test) { const pos = this.selected.indexOf(test); if(pos !== -1) { this.selected.splice(pos, 1); } else { this.selected.push(test); } }, resetSelectedTests() { this.$trace("lsw-tests-page.methods.resetSelectedTests"); this.selected = [].concat(this.available); }, async loadTests() { this.$trace("lsw-tests-page.methods.loadTests"); try { this.available = await importer.json("assets/tests/testcases.json"); this.selected = [].concat(this.available); this.isLoaded = true; } catch (error) { this.$lsw.toasts.showError(error); } }, async downloadTests() { this.$trace("lsw-tests-page.methods.downloadTests"); try { const allDownloads = []; for (let index = 0; index < this.selected.length; index++) { const selectedTest = this.selected[index]; const testCallbackPromise = importer.scriptAsync(selectedTest); allDownloads.push(testCallbackPromise) } const testsResult = await Promise.all(allDownloads); const testsFormatted = LswUtils.flattenObjects(testsResult, { keyMapper(key, totalKeys, indexKey) { return `[T${('' + (totalKeys + 1)).padStart(4, '0')}] ${key}`; } }); this.downloaded = testsFormatted; this.isDownloaded = true; } catch (error) { this.$lsw.toasts.showError(error); } }, selectAllTests() { this.$trace("lsw-tests-page.methods.selectAllTests"); if(this.available.length === this.selected.length ) { this.selected = []; } else { this.selected = [].concat(this.available); } this.$forceUpdate(true); }, shortSubpath(subpath) { return subpath.replace(this.baseUrl, ""); }, async runTests() { this.$trace("lsw-tests-page.methods.runTests"); try { this.isRunning = true; this.goToSection("running"); // @TODO: continue running tests: } catch (error) { this.showError(error); } }, interruptTests() { this.$trace("lsw-tests-page.methods.interruptTests"); this.isRunning = false; }, async start() { this.$trace("lsw-tests-page.methods.start"); try { await this.downloadTests(); await this.runTests(); } catch (error) { this.showError(error); } } }, watch: {}, async mounted() { this.$trace("lsw-tests-page.mounted"); await this.loadTests(); } });
Vue.component("LswUnitTestPage", { template: $template, props: { }, data() { return { testsBaseUrl: "assets/tests/cases/", isLoaded: false, isReady: false, availableTests: false, selectedTests: [], downloadedTests: [], }; }, methods: { backToTests() { this.$trace("lsw-unit-test-page.methods.backToTests"); this.isReady = false; }, async loadTests() { this.$trace("lsw-unit-test-page.methods.loadTests"); this.availableTests = await importer.json("assets/tests/testcases.json"); this.selectedTests = [].concat(this.availableTests); }, async start() { const allDownloads = []; for(let index=0; index<this.selectedTests.length; index++) { const selectedTest = this.selectedTests[index]; const testCallbackPromise = importer.scriptSrc(selectedTest); allDownloads.push(testCallbackPromise) } await Promise.all(allDownloads); this.downloadedTests = LswTests.all(); console.log(this.downloadedTests); this.isReady = true; } }, watch: {}, async mounted() { this.$trace("lsw-unit-test-page.mounted"); await this.loadTests(); this.isLoaded = true; } });
Vue.component("LswUnitTestAll", { template: $template, props: { }, data() { return { mode: false, autorunOption: false, }; }, methods: { openTests() { this.autorunOption = false; this.mode = "open"; }, runTests() { this.autorunOption = true; this.mode = "run"; }, goBack() { this.autorunOption = false; this.mode = false; } }, watch: {}, async mounted() { this.$trace("lsw-unit-test-all.mounted"); } });
Vue.component("LswUnitTester", { template: $template, props: { test: { type: Function, required: true, }, autorun: { type: Boolean, default: true, }, onFinish: { type: Function, default: () => {}, }, }, data() { return { hasRunTests: false, isExpanded: false, report: false, selectedTests: [], isSourceAccessed: false, error: false, }; }, methods: { showError(error) { this.error = error; this.$forceUpdate(true); }, toggleTest(testIndex) { const pos = this.selectedTests.indexOf(testIndex); if(pos !== -1) { this.selectedTests.splice(pos, 1); } else { this.selectedTests.push(testIndex); } }, toggleDetails() { this.isExpanded = !this.isExpanded; }, async setupTest() { this.$trace("lsw-unit-tester.methods.setupTest"); // @OK. }, createBlankReport() { return { testCollectionId: 'not specified', } }, async runTest() { this.$trace("lsw-unit-tester.methods.runTest"); try { console.log("[*] Starts test...") const tester = await this.test.call(); console.log("[*] Test ok!"); this.hasRunTests = true; if(tester instanceof LswTester) { this.report = tester.getReport(); } else { this.report = this.createBlankReport(); } if(typeof this.onFinish === "function") { this.onFinish(this.report, this); } } catch (error) { console.log("[!] Test failed!"); this.showError(error); console.log(error); } }, toggleSource() { this.$trace("lsw-unit-tester.methods.toggleSource"); this.isSourceAccessed = !this.isSourceAccessed; }, }, watch: {}, async mounted() { this.$trace("lsw-unit-tester.mounted"); await this.setupTest(); if(this.autorun) { this.runTest(); } } });
Vue.component("LswWeekPlanner", { template: $template, props: {}, data() { return { scriptContent: "", scriptOutput: "", }; }, methods: { parsear() { try { const ast = WeekLang.parse(this.scriptContent); this.scriptOutput = JSON.stringify(ast, null, 2); } catch (error) { this.scriptOutput = JSON.stringify({ error: true, name: error.name, message: error.message, stack: error.stack, }, null, 2); } } }, watch: {}, mounted() { } });
Vue.component("LswBookFactory", { template: $template, props: {}, data() { this.$trace("lsw-book-factory.data"); return { titles: [], }; }, methods: { async loadTitles() { this.$trace("lsw-book-factory.methods.loadTitles"); const librosMap = await this.$lsw.fs.read_directory("/kernel/wiki/libros"); this.titles = Object.keys(librosMap); // await LswLazyLoads.loadEjs(); }, openLibrosDirectory() { this.$trace("lsw-book-factory.methods.openLibrosDirectory"); this.$lsw.dialogs.open({ title: "Directorio de libros", template: `<lsw-filesystem-explorer opened-by="/kernel/wiki/libros" />`, }); }, editLibro(title) { this.$trace("lsw-book-factory.methods.editLibro"); this.$lsw.dialogs.open({ title: "Editar libro", template: `<lsw-filesystem-explorer :opened-by="'/kernel/wiki/libros/' + title" />`, factory: { data: { title, } } }); }, async openLibro(title) { this.$trace("lsw-book-factory.methods.openLibro"); const originalSource = await this.$lsw.fs.read_file(`/kernel/wiki/libros/${title}`); const source = originalSource; this.$lsw.dialogs.open({ title: "Leer libro", template: ` <div class="pad_1"> <lsw-markdown-viewer :source="source" :activate-ejs="true" /> </div> `, factory: { data: { source, } } }); }, }, async mounted() { this.$trace("lsw-book-factory.mounted"); await this.loadTitles(); }, unmount() { this.$trace("lsw-book-factory.unmount"); } });
Vue.component("LswBookLibrary", { template: $template, props: {}, data() { this.$trace("lsw-book-library.data"); return {}; }, methods: {}, mounted() { this.$trace("lsw-book-library.mounted"); }, unmount() { this.$trace("lsw-book-library.unmount"); } });
Vue.component("LswMicrodataExplorer", { template: $template, props: {}, data() { this.$trace("lsw-microdata-explorer.data"); return {}; }, methods: {}, mounted() { this.$trace("lsw-microdata-explorer.mounted"); }, unmount() { this.$trace("lsw-microdata-explorer.unmount"); } });
Vue.component("LswWiki", { name: "LswWiki", template: $template, props: {}, data() { this.$trace("lsw-wiki.data"); return { isSearching: true, isShowingNavigation: false, isLoadedLibros: true, isLoadedCategorias: true, selectedSection: 'articulos', }; }, methods: { selectSection(section) { this.$trace("lsw-wiki.methods.selectSection"); this.selectedSection = section; this.isShowingNavigation = false; }, toggleNavigation() { this.$trace("lsw-wiki.methods.toggleNavigation"); this.isShowingNavigation = !this.isShowingNavigation; }, hideNavigation() { this.$trace("lsw-wiki.methods.hideNavigation"); this.isShowingNavigation = false; }, openLibrosFolder() { this.$trace("lsw-wiki.methods.openLibrosFolder"); this.$lsw.dialogs.open({ id: LswRandomizer.getRandomString(10), title: "Todos los libros", template: ` <lsw-filesystem-explorer :absolute-layout="true" opened-by="/kernel/wiki/libros" /> ` }); }, openCategoriasFile() { this.$trace("lsw-wiki.methods.openCategoriasFile"); this.$lsw.dialogs.open({ id: LswRandomizer.getRandomString(10), title: "Todas las categorías", template: ` <lsw-filesystem-explorer :absolute-layout="true" opened-by="/kernel/wiki/categorias.tri" /> ` }); }, refreshLibros() { this.$trace("lsw-wiki.methods.refreshLibros"); this.isLoadedLibros = false; setTimeout(() => { this.isLoadedLibros = true; }, 100); }, refreshCategorias() { this.$trace("lsw-wiki.methods.refreshCategorias"); this.isLoadedCategorias = false; setTimeout(() => { this.isLoadedCategorias = true; }, 100); }, refreshRevistas() { this.$trace("lsw-wiki.methods.refreshRevistas"); }, refreshBuscador() { this.$trace("lsw-wiki.methods.refreshBuscador"); }, refreshArticulos() { this.$trace("lsw-wiki.methods.refreshArticulos"); }, goToAddArticulo() { this.$trace("lsw-wiki.methods.goToAddArticulo"); this.$lsw.dialogs.open({ title: "Añadir nuevo artículo", template: `<lsw-spontaneous-form-articulo :on-submitted="closeAndRefresh" />`, factory: { methods: { closeAndRefresh() { this.close(); // @DONT because maybe you have something in hands. } } }, }); } }, watch: { }, async mounted() { try { this.$trace("lsw-wiki.mounted"); } catch (error) { console.log(error); } } });
Vue.component("LswWikiArticuloViewer", { name: "LswWikiArticuloViewer", template: $template, props: { articuloId: { type: [Number, String], required: true, } }, data() { this.$trace("lsw-wiki-articulo-viewer.data"); return { isLoaded: false, selectedArticulos: false, markdownContent: false, error: false, }; }, methods: { setError(error) { this.$trace("lsw-wiki-articulo-viewer.methods.setError"); this.error = error; }, async loadContent() { this.$trace("lsw-wiki-articulo-viewer.methods.loadContent"); const matchedRows = await this.$lsw.database.selectMany("Articulo", articulo => { return articulo.tiene_titulo === this.articuloId; }); console.log("[*] Artículos coincidentes:", matchedRows); try { if(matchedRows.length === 0) { throw new Error(`Articulo no encontrado por «${this.articuloId}»`); } else if(matchedRows.length === 1) { this.markdownContent = marked.parse(matchedRows[0].tiene_contenido); } else { this.markdownContent = matchedRows.map(row => marked.parse(row.tiene_contenido)).join("\n\n----\n\n"); } this.selectedArticulos = matchedRows; } catch (error) { console.log("[*] Error loading articulo:", error); this.setError(error); } finally { this.isLoaded = true; } } }, watch: { }, async mounted() { try { this.$trace("lsw-wiki-articulo-viewer.mounted"); await this.loadContent(); } catch (error) { console.log(error); } } });
Vue.component("LswWikiArticulos", { name: "LswWikiArticulos", template: $template, props: {}, data() { this.$trace("lsw-wiki-articulos.data"); return { articulos: false, isSearching: false, searchText: "", timeoutId: undefined, timeoutSeconds: 0.4, openedArticulos: [], }; }, methods: { async loadArticulos() { this.$trace("lsw-wiki-articulos.methods.loadArticulos"); clearTimeout(this.searchTimeoutId); this.isSearching = true; const articulos = await (() => { if(this.searchText) { return this.$lsw.database.selectMany("Articulo", articulo => { return JSON.stringify(articulo).indexOf(this.searchText) !== -1; }); } else { return this.$lsw.database.selectMany("Articulo"); } })(); const articulosOrdenados = articulos.sort((a1, a2) => { const f1 = a1.tiene_fecha || 0; const f2 = a2.tiene_fecha || 0; if(f1 < f2) return 1; if(f1 > f2) return -1; return -1; }); this.openedArticulos = []; this.articulos = articulosOrdenados; this.isSearching = false; }, loadArticulosDelayed() { this.$trace("lsw-wiki-articulos.methods.loadArticulosDelayed"); clearTimeout(this.searchTimeoutId); this.isSearching = true; this.searchTimeoutId = setTimeout(() => { this.loadArticulos(); }, 1000 * this.timeoutSeconds); }, toggleArticulo(articuloId) { this.$trace("lsw-wiki-articulos.methods.toggleArticulo"); const pos = this.openedArticulos.indexOf(articuloId); if(pos === -1) { this.openedArticulos.push(articuloId); } else { this.openedArticulos.splice(pos, 1); } }, async createArticulo() { this.$trace("lsw-wiki-articulos.methods.createArticulo"); const articulosComponent = this; await this.$dialogs.open({ id: LswRandomizer.getRandomString(10), title: "Crear nuevo artículo", template: ` <div> <lsw-schema-based-form :on-submit="submitCallback" :model="{ connection: $lsw.database, databaseId: 'lsw_default_database', tableId: 'Articulo', rowId: -1, }" /> </div> `, factory: { methods: { async submitCallback(value) { try { this.$trace("Dialogs.CrearNuevoArticulo.methods.submitCallback"); await this.$lsw.database.insert("Articulo", value); await this.$lsw.toasts.send({ title: "Artículo añadido correctamente", text: "El artículo ha sido añadido con éxito." }); this.close(); articulosComponent.loadArticulos(); } catch (error) { console.log(error); await this.$lsw.toasts.send({ title: "Error al añadir artículo", text: "No se pudo añadir el artículo por un error: " + error.message, background: "red", }); } } } } }) }, async editArticulo(articulo) { this.$trace("lsw-wiki-articulos.methods.editArticulo"); const articulosComponent = this; await this.$lsw.dialogs.open({ title: "Editar artículo", template: ` <div> <lsw-schema-based-form :show-breadcrumb="false" :on-submit="(value) => submitCallback(value)" :on-delete-row="deleteCallback" :model="{ connection: $lsw.database, databaseId: 'lsw_default_database', tableId: 'Articulo', rowId: articuloId, }" /> </div> `, factory: { data: { articuloId: articulo.id }, methods: { async submitCallback(value) { this.$trace("Dialogs.EditarArticulo.methods.submitCallback"); try { await this.$lsw.database.update("Articulo", this.articuloId, value); await this.$lsw.toasts.send({ title: "Artículo actualizado correctamente", text: "El artículo ha sido actualizado con éxito." }); this.close(); articulosComponent.loadArticulos(); } catch (error) { console.log(error); await this.$lsw.toasts.send({ title: "Error al actualizar artículo", text: "No se pudo actualizar el artículo por un error: " + error.message, background: "red", }); } }, async deleteCallback() { this.$trace("Dialogs.EditarArticulo.methods.deleteCallback"); this.close(); articulosComponent.loadArticulos(); await this.$lsw.toasts.send({ title: "Artículo eliminado correctamente", text: "El artículo se eliminó con éxito.", }); } } } }); this.loadArticulos(); }, async deleteArticulo(articulo) { this.$trace("lsw-wiki-articulos.methods.deleteArticulo"); const articulosComponent = this; const respuesta = await this.$lsw.dialogs.open({ title: "Eliminar artículo", template: ` <div class="pad_2"> <div class="">¿Estás seguro que quieres eliminar el artículo?</div> <pre class="codeblock margin_top_2 margin_bottom_2">{{ articulo }}</pre> <hr /> <div class="flex_row centered"> <div class="flex_100"></div> <div class="flex_1 pad_right_1"> <button class="flex_1" v-on:click="() => accept(true)">Aceptar</button> </div> <div class="flex_1"> <button class="flex_1 " v-on:click="cancel">Cancelar</button> </div> </div> </div> `, factory: { data: { articulo }, methods: {} } }); if(respuesta !== true) return; await this.$lsw.database.delete("Articulo", articulo.id); this.$lsw.toasts.send({ title: "Artículo eliminado correctamente", text: "El artículo fue eliminado correctamente" }); this.loadArticulos(); }, async sendArticuloToNotas(articulo) { this.$trace("lsw-spontaneous-table-nota.methods.sendArticuloToNotas"); const respuesta = await this.$lsw.dialogs.open({ title: "Pasar artículo a notas", template: ` <div class="pad_1"> <div>Vas a pasar el siguiente artículo a nota: </div> <div class="pad_2"> <pre class="codeblock">{{ articulo }}</pre> </div> <div>¿Estás seguro?</div> <hr/> <div class="flex_row centered text_align_right"> <div class="flex_100"></div> <div class="flex_1 pad_right_1"> <button class="supermini danger_button" v-on:click="accept">Aceptar</button> </div> <div class="flex_1"> <button class="supermini" v-on:click="cancel">Cancelar</button> </div> </div> </div> `, factory: { data: { articulo } }, }); if(respuesta === -1) return; const notaNew = Object.assign({ tiene_titulo: '', tiene_fecha: '', tiene_estado: "creada", tiene_categorias: '', tiene_contenido: '', tiene_garantia: '', tiene_tags: '', }, articulo); delete notaNew.id; await this.$lsw.database.insert("Nota", notaNew); await this.$lsw.database.delete("Articulo", articulo.id); this.$lsw.toasts.send({ title: "Artículo a nota bien", text: "El artículo ha sido pasado a nota correctamente", }); this.loadArticulos(); } }, watch: { }, async mounted() { try { this.$trace("lsw-wiki-articulos.mounted"); await this.loadArticulos(); } catch (error) { console.log(error); } } });
Vue.component("LswWikiCategorias", { name: "LswWikiCategorias", template: $template, props: {}, data() { this.$trace("lsw-wiki-categorias.data"); return { categorias: false, }; }, methods: { async loadCategorias() { this.$trace("LswWikiCategorias.methods.loadCategorias"); this.categorias = await LswWikiUtils.getCategorias(); }, async abrirCategoria(categoria) { this.$trace("LswWikiCategorias.methods.abrirCategoria"); const articulosCategorizados = await this.$lsw.database.selectMany("Articulo", articulo => { return articulo.tiene_categorias.indexOf(categoria.id) !== -1; }) console.log(categoria); this.$lsw.dialogs.open({ id: LswRandomizer.getRandomString(10), title: "Ver categoría: " + categoria.id, template: ` <div> <lsw-database-explorer :show-breadcrumb="false" initial-page="lsw-page-rows" :initial-args="{ database: 'lsw_default_database', table: 'Articulo', tableStorageId: 'categoria-' + categoriaId, filterCallback: it => it.tiene_categorias && (it.tiene_categorias.toLowerCase().indexOf(categoriaId.toLowerCase()) !== -1), }" /> </div> `, factory: { data: { categoriaId: categoria.id, articulosCategorizados } } }); } }, watch: { }, async mounted() { try { this.$trace("lsw-wiki-categorias.mounted"); await this.loadCategorias(); } catch (error) { console.log(error); } } });
Vue.component("LswWikiLibroViewer", { name: "LswWikiLibroViewer", template: $template, props: { idHeredado: { type: Array, default: () => [], }, indiceDeArbol: { type: Number, default: () => 0, }, arbol: { type: Object, required: true, }, onClickLink: { type: [Boolean, Function], default: () => false, }, onClickClip: { type: Function, default: () => {}, } }, data() { this.$trace("lsw-wiki-libro-viewer.data"); return { isSelected: false, isShowingTree: false, isShowingDefinition: false, }; }, methods: { getIdFor(node) { this.$trace("lsw-wiki-libro-viewer.methods.getIdFor"); const lastPartId = node.id || node.link; return [].concat(this.idHeredado).concat([lastPartId]); }, toggleState() { this.$trace("lsw-wiki-libro-viewer.methods.toggleState"); this.isSelected = !this.isSelected; }, toggleTree() { this.$trace("lsw-wiki-libro-viewer.methods.toggleTree"); this.isShowingTree = !this.isShowingTree; }, toggleDefinition() { this.$trace("lsw-wiki-libro-viewer.methods.toggleDefinition"); this.isShowingDefinition = !this.isShowingDefinition; }, abrirLink(nodo) { this.$trace("lsw-wiki-libro-viewer.methods.abrirLink"); if(this.onClickLink) { this.onClickLink(nodo, this); } else { this.toggleState(); } }, abrirClip(nodo) { this.$trace("lsw-wiki-libro-viewer.methods.abrirClip"); this.onClickClip(nodo, this); } }, watch: { }, async mounted() { try { this.$trace("lsw-wiki-libro-viewer.mounted"); } catch (error) { console.log(error); } } });
Vue.component("LswWikiLibros", { name: "LswWikiLibros", template: $template, props: {}, data() { this.$trace("lsw-wiki-libros.data"); return { selectedLibros: [], selectedLibroInfos: [], loadedLibros: {}, libros: false, }; }, methods: { async toggleLibro(libroId) { this.$trace("LswWikiLibros.methods.loadLibros"); const pos = this.selectedLibros.indexOf(libroId); if(pos === -1) { await this.loadLibro(libroId); this.selectedLibros.push(libroId); } else { this.selectedLibros.splice(pos, 1); } this.$forceUpdate(true); }, async toggleLibroInfo(libroId) { this.$trace("LswWikiLibros.methods.toggleLibroInfo"); const pos = this.selectedLibroInfos.indexOf(libroId); if(pos === -1) { this.selectedLibroInfos.push(libroId); } else { this.selectedLibroInfos.splice(pos, 1); } this.$forceUpdate(true); }, async loadLibro(libroId) { this.$trace("LswWikiLibros.methods.loadLibro"); const libroData = await this.$lsw.fs.evaluateAsTripiFileOrReturn(`/kernel/wiki/libros/${libroId}`, false); if(!libroData) return; this.loadedLibros[libroId] = libroData; return libroData; }, async loadLibros() { this.$trace("LswWikiLibros.methods.loadLibros"); const librosBrute = await LswWikiUtils.getLibros(); this.libros = librosBrute; }, getLibroName(file) { this.$trace("LswWikiLibros.methods.getLibroName"); return file.replace(/\.tri(pi)?/g, "") }, async abrirArticulo(articulo, componenteDeArticulo) { this.$trace("LswWikiLibros.methods.abrirLibro"); console.log("Abriendo artículo:", articulo.link); componenteDeArticulo.toggleState(); }, async editLibro(libro) { this.$trace("LswWikiLibros.methods.editLibro"); await this.$lsw.dialogs.open({ id: LswRandomizer.getRandomString(10), title: "Editar libro", template: ` <lsw-filesystem-explorer :absolute-layout="true" :opened-by="'/kernel/wiki/libros/' + libro" /> `, factory: { data: { libro } } }); }, async printLibro(libroId) { this.$trace("LswWikiLibros.methods.printLibro"); const libroTree = await this.loadLibro(libroId); const libroTexted = await this.resolveLibroTree(libroTree) await this.$lsw.dialogs.open({ id: LswRandomizer.getRandomString(10), title: "Imprimir libro", template: ` <lsw-data-printer-report :input="libro" /> `, factory: { data: { libro: libroTexted } } }); }, async resolveLibroTree(treeNode) { this.$trace("LswWikiLibros.methods.resolveLibroTree"); if(typeof treeNode === "undefined") { return ""; } let out = ""; const { id, link, subtree } = treeNode; const reference = id || link; const articulosCoincidentes = await this.$lsw.database.selectMany("Articulo", articulo => { return articulo.tiene_titulo === reference; }); out += `### ${id}\n\n`; if(articulosCoincidentes && articulosCoincidentes.length) { const articuloTextualizado = articulosCoincidentes.map(articulo => articulo.tiene_contenido).join("\n\n"); out += `${articuloTextualizado || ""}\n\n`; } if(typeof subtree === "object") { for(let prop in subtree) { out += await this.resolveLibroTree(subtree[prop]); } } return out; }, }, watch: { }, async mounted() { try { this.$trace("lsw-wiki-libros.mounted"); await this.loadLibros(); } catch (error) { console.log(error); } } });
Vue.component("LswWikiRevistas", { name: "LswWikiRevistas", template: $template, props: {}, data() { this.$trace("lsw-wiki-revistas.data"); return { revistas: false, }; }, methods: { async loadRevistas() { this.$trace("lsw-wiki.methods.loadRevistas"); this.revistas = await LswWikiUtils.getRevistas(); }, }, watch: { }, async mounted() { try { this.$trace("lsw-wiki-revistas.mounted"); await this.loadRevistas(); } catch (error) { console.log(error); } } });
Vue.component("LswWikiTree", { name: "LswWikiTree", template: $template, props: { initialInput: { type: Array, default: () => [], }, initialSettings: { type: Object, default: () => {}, } }, data() { this.$trace("lsw-wiki-tree.data"); const initialData = this.validateInput(this.initialInput || {}); return { selectedNodes: [], treeData: initialData, }; }, methods: { validateInput(jsonTree) { this.$trace("LswWikiTree.methods.validateInput"); $ensure({ jsonTree }, 1).to.be.array().its("length").type("number"); return jsonTree; }, toggleNode(nodeId) { this.$trace("LswWikiTree.methods.toggleNode"); const pos = this.selectedNodes.indexOf(nodeId); if(pos === -1) { this.selectedNodes.push(nodeId); } else { this.selectedNodes.splice(nodeId, 1); } } }, watch: { }, async mounted() { try { this.$trace("lsw-wiki-tree.mounted"); } catch (error) { console.log(error); } } });
Vue.component("LswWikiTreenode", { name: "LswWikiTreenode", template: $template, props: { initialInput: { type: Array, default: () => [], }, initialSettings: { type: Object, default: () => {}, } }, data() { this.$trace("lsw-wiki-treenode.data"); return { treeNode: this.initialInput || [], }; }, methods: { }, watch: { }, async mounted() { try { this.$trace("lsw-wiki-treenode.mounted"); } catch (error) { console.log(error); } } });
// Change this component at your convenience: Vue.component("LswClockwatcher", { template: $template, props: { viewer: { type: Object, required: true } }, data() { this.$trace("lsw-clockwatcher.data"); return { currentDate: new Date(), currentMessage: false, }; }, methods: { onClick(event) { this.$trace("lsw-clockwatcher.methods.onClick"); this.viewer.toggleState(); }, startTimer() { this.$trace("lsw-clockwatcher.methods.startTimer"); this.timerId = setTimeout(() => { this.currentDate = new Date(); this.startTimer(); }, 1000 * 60); }, stopTimer() { this.$trace("lsw-clockwatcher.methods.stopTimer"); clearTimeout(this.timerId); }, async loadMessage() { this.$trace("lsw-clockwatcher.methods.loadMessage"); try { const userPreferences = await this.$lsw.fs.evaluateAsDotenvFileOrReturn('/kernel/settings/user.env', {}); const clockMessage = userPreferences["app.clock_message"] || false; this.currentMessage = clockMessage; } catch (error) { // @OK. console.error("[!] Could not load preference «app.clock_message» from clockwatcher:", error); } } }, mounted() { this.$trace("lsw-clockwatcher.mounted"); this.startTimer(); this.loadMessage(); }, unmount() { this.$trace("lsw-clockwatcher.unmounted"); this.stopTimer(); } });
Vue.component("LswCodeViewer", { template: $template, props: { code: { type: String, required: () => true, }, language: { type: String, required: () => true, }, }, data() { this.$trace("lsw-code-viewer.data"); return { isLoaded: false, fontsize: 10, }; }, methods: { increaseFontsize(q = 1) { this.$trace("lsw-code-viewer.methods.increaseFontsize"); this.fontsize += q; }, async loadHighlightJs(event) { this.$trace("lsw-code-viewer.methods.loadHighlightJs"); await LswLazyLoads.loadHighlightJs(); this.isLoaded = true; }, }, mounted() { this.$trace("lsw-code-viewer.mounter"); this.loadHighlightJs(); }, unmount() { this.$trace("lsw-code-viewer.mounter"); } });
// Change this component at your convenience: Vue.component("LswWindowsMainTab", { template: $template, props: { viewer: { type: Object, required: true } }, data() { this.$trace("lsw-windows-main-tab.data", arguments); return { }; }, methods: { async showConsole() { this.$trace("lsw-windows-main-tab.methods.showConsole", arguments); this.$consoleHooker.toggleConsole(); }, getRandomString(len = 10) { this.$trace("lsw-windows-main-tab.methods.getRandomString", arguments); const alphabet = "abcdefghijklmnopqrstuvwxyz".split(""); let out = ""; while(out.length < len) { out += alphabet[Math.floor(Math.random() * alphabet.length)]; } return out; }, openRest() { this.$trace("lsw-windows-main-tab.methods.openRest", arguments); this.viewer.hide(); this.$dialogs.open({ id: "database-explorer-" + this.getRandomString(5), title: "Database explorer", template: `<div class="pad_1"><lsw-database-explorer /></div>`, }); }, openFilesystem() { this.$trace("lsw-windows-main-tab.methods.openFilesystem", arguments); this.viewer.hide(); this.$dialogs.open({ id: "filesystem-explorer-" + this.getRandomString(5), title: "Filesystem explorer", template: `<lsw-filesystem-explorer :absolute-layout="true" />`, }); }, openWiki() { this.$trace("lsw-windows-main-tab.methods.openWiki", arguments); this.viewer.hide(); this.$dialogs.open({ id: "wiki-explorer-" + this.getRandomString(5), title: "Wiki explorer", template: `<div class="pad_1"><lsw-wiki /></div>`, }); }, openAgenda() { this.$trace("lsw-windows-main-tab.methods.openAgenda", arguments); this.viewer.hide(); this.$dialogs.open({ id: "agenda-viewer-" + this.getRandomString(5), title: "Agenda viewer", template: `<div class="pad_1"><lsw-agenda /></div>`, }); }, openAutomessages() { this.$trace("lsw-windows-main-tab.methods.openAutomessages", arguments); this.viewer.hide(); }, openNoteUploader() { this.$trace("lsw-windows-main-tab.methods.openNoteUploader", arguments); this.viewer.hide(); this.$dialogs.open({ id: "notas-" + this.getRandomString(5), title: "Notas", template: `<div class="pad_1"><lsw-spontaneous-table-nota /></div>`, }); }, openConfigurationsPage() { this.$trace("lsw-windows-main-tab.methods.openConfigurationsPage", arguments); this.viewer.hide(); this.$dialogs.open({ id: "configurations-page-" + this.getRandomString(5), title: "Configuraciones", template: `<div class="pad_1"><lsw-configurations-page /></div>`, }); }, closeProcess(dialog) { this.$trace("lsw-windows-main-tab.methods.closeProcess", arguments); this.$lsw.dialogs.close(dialog.id); }, }, mounted() { this.$lsw.windowsMainTab = this; } });
// Change this component at your convenience: Vue.component("LswWindowsPivotButton", { template: $template, props: { viewer: { type: Object, required: true } }, data() { this.$trace("lsw-windows-pivot-button.data"); return { }; }, methods: { onClick(event) { this.$trace("lsw-windows-pivot-button.methods.onClick"); this.viewer.toggleState(); }, }, });
// Change this component at your convenience: Vue.component("LswWindowsViewer", { template: $template, props: {}, data() { return { isShowing: false }; }, methods: { hide() { this.isShowing = false; }, show() { this.isShowing = true; }, toggleState() { this.isShowing = !this.isShowing; this.$forceUpdate(true); }, selectDialog(id) { this.hide(); this.$refs.dialogs.maximize(id); } }, mounted() { this.$window.LswWindows = this; this.$lsw.windows = this; this.$lsw.windowsViewer = this; } });
Vue.component("LswAgenda", { name: "LswAgenda", template: $template, props: {}, data() { this.$trace("lsw-agenda.data"); return { counter: 0, isLoading: false, hasPsicodelia: true, selectedHiddenMenu: "none", selectedContext: "agenda", selectedAction: 'calendario', selectedDate: undefined, selectedDateTasks: undefined, selectedDateTasksSorted: undefined, selectedDateTasksFormattedPerHour: undefined, selectedForm: undefined, hiddenDateHours: [], shownAcciones: [], }; }, methods: { toggleShowAccion(accionId) { this.$trace("lsw-agenda.methods.toggleShowAccion"); const pos = this.shownAcciones.indexOf(accionId); if (pos === -1) { this.shownAcciones.push(accionId); } else { this.shownAcciones.splice(pos, 1); } }, selectHiddenMenu(menuId) { this.$trace("lsw-agenda.methods.selectHiddenMenu"); this.selectedHiddenMenu = menuId; }, selectAction(accionId, contextId = false) { this.$trace("lsw-agenda.methods.selectAction"); if (contextId) { this.selectContext(contextId); } this.selectedAction = accionId; }, selectContext(id, parameters = {}) { this.$trace("lsw-agenda.methods.selectContext"); this.selectedHiddenMenu = "none"; this.selectedContextParameters = parameters; this.selectedContext = id; }, toggleCalendario() { this.$trace("lsw-agenda.methods.toggleCalendario"); const finalState = (this.selectedAction === "calendario") ? "none" : "calendario"; if (this.selectedContext !== "agenda") { this.selectContext("agenda"); this.selectAction("calendario"); return; } this.selectAction(finalState); }, selectCalendario() { this.$trace("lsw-agenda.methods.selectCalendario"); this.selectContext("agenda"); this.selectAction("calendario"); }, selectConductometria() { this.$trace("lsw-agenda.methods.selectCondutometria"); this.selectContext("conductometria"); // this.selectAction("calendario"); }, togglePsicodelia() { this.$trace("lsw-agenda.methods.togglePsicodelia"); this.hasPsicodelia = !this.hasPsicodelia; }, toggleHour(hourInt) { this.$trace("lsw-agenda.methods.toggleHour"); const pos = this.hiddenDateHours.indexOf(hourInt); if (pos === -1) { this.hiddenDateHours.push(hourInt); } else { this.hiddenDateHours.splice(pos, 1); } }, reloadDateTasks() { this.$trace("lsw-agenda.methods.reloadDateTasks"); return this.loadDateTasks(this.selectedDate); }, async loadDateTasks(dateInput, calendario, isOnMounted = false) { this.$trace("lsw-agenda.methods.loadDateTasks"); // this.isLoading = true; const newDate = dateInput || new Date(); console.log("[*] Loading date tasks of: " + LswTimer.utils.fromDateToDatestring(newDate)); try { this.selectedDate = newDate; const selectedDate = this.selectedDate; const selectedDateTasks = await this.$lsw.database.selectMany("Accion", valueBrute => { try { const valueList = LswTimer.parser.parse(valueBrute.tiene_inicio); const value = valueList[0]; const isSameYear = value.anio === selectedDate.getFullYear(); const isSameMonth = value.mes === (selectedDate.getMonth() + 1); const isSameDay = value.dia === selectedDate.getDate(); const isAccepted = isSameYear && isSameMonth && isSameDay; return isAccepted; } catch (error) { return true; } }); this.selectedDateTasks = selectedDateTasks; this.selectedDateTasksSorted = selectedDateTasks.sort((accion1, accion2) => { let inicio1 = undefined; let inicio2 = undefined; try { inicio1 = LswTimer.utils.fromDatestringToDate(accion1.tiene_inicio); } catch (error) { return 1; } try { inicio2 = LswTimer.utils.fromDatestringToDate(accion2.tiene_inicio); } catch (error) { return -1; } if (inicio1 < inicio2) { return -1; } else if (inicio1 > inicio2) { return 1; } else { return -1; } }); if (isOnMounted) { const noTasksFound = (!this.selectedDateTasks) || (!this.selectedDateTasks.length); if (noTasksFound) { this.isCalendarioSelected = true; } } this.propagateDateTasks(); } catch (error) { console.log("Error loading date taskes:", error); } finally { setTimeout(() => { this.isLoading = false }, 100); } await this.reloadCalendarioMarks(calendario); this.refreshTasks(); }, async reloadCalendarioMarks(calendario) { if (calendario) { const selectedDate = this.selectedDate; const tasksOfMonth = await this.$lsw.database.selectMany("Accion", valueBrute => { const valueList = LswTimer.parser.parse(valueBrute.tiene_inicio); const value = valueList[0]; const isSameYear = value.anio === selectedDate.getFullYear(); const isSameMonth = value.mes === (selectedDate.getMonth() + 1); const isAccepted = isSameYear && isSameMonth; return isAccepted; }); const tasksOfMonthByDay = tasksOfMonth.reduce((out, item) => { const valueList = LswTimer.parser.parse(item.tiene_inicio); const value = valueList[0]; const day = value.dia; if (!(day in out)) { out[day] = []; } out[day].push(item); return out; }, {}); calendario.establecer_marcadores_del_mes(tasksOfMonthByDay); } }, groupTasksByHour(tareas = this.selectedDateTasks) { this.$trace("lsw-agenda.methods.groupTasksByHour"); const mapaHoras = {}; Agrupacion_inicial: for (let i = 0; i < tareas.length; i++) { const tarea = tareas[i]; const { tiene_inicio } = tarea; const [inicioObject] = LswTimer.parser.parse(tiene_inicio); const { hora, minuto } = inicioObject; if (typeof hora !== "number") { continue Agrupacion_inicial; } if (!(hora in mapaHoras)) { mapaHoras[hora] = []; } mapaHoras[hora].push(tarea); } //return mapaHoras; const segunHoras = []; Formateo_final: for (let hora in mapaHoras) { const lista = mapaHoras[hora]; segunHoras.push({ hora, tareas: lista, }); } return segunHoras; }, propagateDateTasks() { this.$trace("lsw-agenda.methods.propagateDateTasks"); this.selectedDateTasksFormattedPerHour = this.groupTasksByHour(); }, async openInsertTaskDialog() { this.$trace("lsw-agenda.methods.openInsertTaskDialog"); // *@TODO: }, async openDeleteTaskDialog(tarea, e) { this.$trace("lsw-agenda.methods.openDeleteTaskDialog"); const confirmed = await Vue.prototype.$dialogs.open({ title: "Eliminar registro", template: ` <div> <div class="pad_2">¿Seguro que quieres eliminar el registro?</div> <hr class="margin_0" /> <div class="pad_2 text_align_right"> <button class="supermini danger_button" v-on:click="() => accept(true)">Eliminar</button> <button class="supermini " v-on:click="() => accept(false)">Cancelar</button> </div> </div> `, }); if (!confirmed) return false; await this.$lsw.database.delete("Accion", tarea.id); this.selectedForm = undefined; this.refreshTasks(); }, selectHour(hora) { this.$trace("lsw-agenda.methods.selectHour"); if (this.selectedForm === hora) { this.selectedForm = undefined; } else { this.selectedForm = hora; } }, async refreshTasks() { this.$trace("lsw-agenda.methods.refreshTasks"); if (this.$refs.agenda_acciones_viewer) { this.$refs.agenda_acciones_viewer.changeDate(new Date(this.selectedDate)); } }, async synchronizeAlarms() { this.$trace("lsw-agenda.methods.synchronizeAlarms"); Cordova_injection: { if (typeof this.$window.cordova !== "undefined") { const dateToday = new Date(); const allAlarms = await this.$lsw.database.selectMany("Accion", accion => { const dateAccion = LswTimer.utils.fromDatestringToDate(accion.tiene_inicio); return LswTimer.utils.areSameDayDates(dateToday, dateAccion); }); const soundFile = LswRandomizer.getRandomItem([ "file://assets/sounds/alarm.busca.wav", "file://assets/sounds/alarm.clock-light.wav", "file://assets/sounds/alarm.facility-breach.wav", "file://assets/sounds/alarm.heavy.wav", "file://assets/sounds/alarm.submarine.wav", ]) try { for (let index = 0; index < allAlarms.length; index++) { const accion = allAlarms[index]; const id = index + 1; this.$window.cordova.plugins.notification.local.cancel(id); this.$window.cordova.plugins.notification.local.schedule({ id, title: "¡Requiéresete!", text: `¡Vamos con «${accion.en_concepto}»!`, trigger: { at: LswTimer.utils.fromDatestringToDate(accion.tiene_inicio) }, vibrate: [1000, 1000, 1000, 1000], wakeUp: true, lockscreen: true, sound: soundFile }); } this.$lsw.toasts.send({ title: "Alarmas sincronizadas", text: `Unas ${allAlarms.length} alarmas fueron sincronizadas con el dispositivo` }); } catch (error) { this.$lsw.toasts.showError(error); } } } }, unsynchronizeAlarms() { this.$trace("lsw-agenda.methods.unsynchronizeAlarms"); Cordova_injection: { if (typeof this.$window.cordova !== "undefined") { try { this.$window.cordova.plugins.notification.local.cancelAll(() => { this.$lsw.toasts.send({ title: "Alarmas desincronizadas", text: "Las alarmas se eliminaron del dispositivo" }); }) } catch (error) { this.$lsw.toasts.showError(error); } } } }, }, watch: { }, computed: { isCalendarioSelected() { return this.selectedAction === "calendario"; } }, async mounted() { try { this.$trace("lsw-agenda.mounted"); const selectedDate = this.$refs.calendario.getValue(); await this.loadDateTasks(selectedDate, undefined, true); } catch (error) { console.log(error); } } });
Vue.component("LswAgendaAccionAdd", { template: $template, props: {}, data() { this.$trace("lsw-agenda-accion-add.data"); return { // }; }, methods: { async insertAccion(v) { this.$trace("lsw-agenda-accion-add.methods.insertAccion"); await this.$lsw.database.insert("Accion", v); // *@TODO: should redirect } }, watch: {}, mounted() { try { this.$trace("lsw-agenda-accion-add.mounted"); } catch(error) { console.log(error); } } });
Vue.component("LswAgendaAccionSearch", { template: $template, props: {}, data() { this.$trace("lsw-agenda-accion-search.data"); return { isLoaded: false, }; }, methods: { async loadRows() { this.$trace("lsw-agenda-accion-search.methods.loadRows"); this.rows = await this.$lsw.database.selectMany("Accion", it => true); this.isLoaded = true; } }, watch: {}, mounted() { try { this.$trace("lsw-agenda-accion-search.mounted"); this.loadRows(); } catch(error) { console.log(error); } } });
Vue.component("LswAgendaAccionesViewer", { name: "LswAgendaAccionesViewer", template: $template, props: { initialDate: { type: Date, required: true, }, sorterStrategy: { type: String, default: () => false, } }, data() { this.$trace("lsw-agenda-acciones-viewer.data"); return { isLoading: true, isShowingGoals: true, isShowingRandomizer: false, selectedDate: this.initialDate, selectedAccion: "", selectedForm: false, selectedDateTasks: undefined, selectedDateTasksSorted: undefined, hiddenDateHours: [], shownAcciones: [], }; }, methods: { hideRandomizer() { this.$trace("lsw-agenda-acciones-viewer.methods.hideRandomizer"); this.isShowingRandomizer = false; }, toggleRandomizer() { this.$trace("lsw-agenda-acciones-viewer.methods.toggleRandomizer"); this.isShowingRandomizer = !this.isShowingRandomizer; }, openRandomizerFile() { this.$trace("lsw-agenda-acciones-viewer.methods.openRandomizerFile"); this.hideRandomizer(); Load_secretly_random_actions_as_concepts: { setTimeout(async () => { const randomizables = await this.$lsw.fs.evaluateAsDotenvFileOrReturn("/kernel/settings/randomizables.env", {}); const names = Object.keys(randomizables); const knownNames = await this.$lsw.database.selectMany("Concepto", c => { return names.indexOf(c.tiene_nombre) !== -1; }); const missingNames = LswUtils.arrays.getMissingInFirst(knownNames, names); const conceptos = missingNames.map(name => { return { tiene_nombre: name, tiene_comentarios: "", } }); for(let indexConcepto=0; indexConcepto<conceptos.length; indexConcepto++) { const concepto = conceptos[indexConcepto]; try { await this.$lsw.database.insert("Concepto", concepto); } catch (error) { // @BADLUCK } } }, 0); } this.$lsw.dialogs.open({ title: "Editar randomizables.env", template: ` <div> <lsw-filesystem-explorer opened-by="/kernel/settings/randomizables.env" :absolute-layout="true" /> </div> `, }); }, changeDate(selectedDate) { this.$trace("lsw-agenda-acciones-viewer.methods.changeDate"); this.selectedDate = selectedDate; this.loadDateTasks(); }, selectForm(hora) { this.$trace("lsw-agenda-acciones-viewer.methods.selectForm"); if (this.selectedForm === hora) { this.selectedForm = undefined; } else { this.selectedForm = hora; } }, async onInsertTask(v, tarea) { this.$trace("lsw-agenda-acciones-viewer.methods.onInsertTask"); const id = await this.$lsw.database.insert('Accion', v); this.selectForm(id); this.loadDateTasks(); }, async toggleAutogeneration(tarea) { this.$trace("lsw-agenda-acciones-viewer.methods.toggleAutogeneration"); const siguientesParametros = (() => { if(tarea.tiene_parametros.startsWith("[*autogenerada]")) { return tarea.tiene_parametros.replace(/^\[\*autogenerada\] */g, ""); } return "[*autogenerada] " + tarea.tiene_parametros; })(); await this.$lsw.database.overwrite('Accion', tarea.id, { tiene_parametros: siguientesParametros }); await this.loadDateTasks(); }, async advanceTaskState(tarea) { this.$trace("lsw-agenda-acciones-viewer.methods.advanceTaskState"); const siguienteEstado = (() => { switch (tarea.tiene_estado) { case "pendiente": return "completada"; case "completada": return "fallida"; case "fallida": return "pendiente"; default: return "pendiente"; } })(); await this.$lsw.database.overwrite('Accion', tarea.id, { tiene_estado: siguienteEstado }); await this.loadDateTasks(); }, toggleShowAccion(accionId) { this.$trace("lsw-agenda-acciones-viewer.methods.toggleShowAccion"); const pos = this.shownAcciones.indexOf(accionId); if (pos === -1) { this.shownAcciones.push(accionId); } else { this.shownAcciones.splice(pos, 1); } }, async loadDateTasks() { this.isLoading = true; const selectedDate = this.selectedDate; const selectedDateTasks = await this.$lsw.database.selectMany("Accion", valueBrute => { try { const valueList = LswTimer.parser.parse(valueBrute.tiene_inicio); const value = valueList[0]; const isSameYear = value.anio === selectedDate.getFullYear(); const isSameMonth = value.mes === (selectedDate.getMonth() + 1); const isSameDay = value.dia === selectedDate.getDate(); const isAccepted = isSameYear && isSameMonth && isSameDay; return isAccepted; } catch (error) { return true; } }); Constitute_date_tasks_as_required: { if (this.sorterStrategy === false) { this.selectedDateTasks = selectedDateTasks; this.selectedDateTasksSorted = selectedDateTasks.sort((accion1, accion2) => { let inicio1 = undefined; let inicio2 = undefined; try { inicio1 = LswTimer.utils.fromDatestringToDate(accion1.tiene_inicio); } catch (error) { return 1; } try { inicio2 = LswTimer.utils.fromDatestringToDate(accion2.tiene_inicio); } catch (error) { return -1; } if (inicio1 < inicio2) { return -1; } else if (inicio1 > inicio2) { return 1; } else { return -1; } }); } else if (this.sorterStrategy === "despues") { this.selectedDateTasks = selectedDateTasks; const momentoActual = new Date(); Mostramos_las_tareas_de_la_hora_actual_en_adelante: { momentoActual.setMinutes(0); } this.selectedDateTasksSorted = selectedDateTasks.filter(accion => { const dateInicio = LswTimer.utils.fromDatestringToDate(accion.tiene_inicio); try { return momentoActual <= dateInicio; } catch (error) { console.log(error); return false; } }).sort((accion1, accion2) => { let inicio1 = undefined; let inicio2 = undefined; try { inicio1 = LswTimer.utils.fromDatestringToDate(accion1.tiene_inicio); } catch (error) { return 1; } try { inicio2 = LswTimer.utils.fromDatestringToDate(accion2.tiene_inicio); } catch (error) { return -1; } if (inicio1 < inicio2) { return -1; } else if (inicio1 > inicio2) { return 1; } else { return -1; } }); } else if (this.sorterStrategy === "antes") { this.selectedDateTasks = selectedDateTasks; const momentoActual = new Date(); this.selectedDateTasksSorted = selectedDateTasks.filter(accion => { const dateInicio = LswTimer.utils.fromDatestringToDate(accion.tiene_inicio); try { return momentoActual >= dateInicio; } catch (error) { console.log(error); return false; } }).sort((accion1, accion2) => { let inicio1 = undefined; let inicio2 = undefined; try { inicio1 = LswTimer.utils.fromDatestringToDate(accion1.tiene_inicio); } catch (error) { return 1; } try { inicio2 = LswTimer.utils.fromDatestringToDate(accion2.tiene_inicio); } catch (error) { return -1; } if (inicio1 < inicio2) { return 1; } else if (inicio1 > inicio2) { return -1; } else { return 1; } }); } } this.isLoading = false; }, showAllHours() { this.$trace("lsw-agenda-acciones-viewer.methods.showAllHours"); this.hiddenDateHours = []; }, hideAllHours() { this.$trace("lsw-agenda-acciones-viewer.methods.hideAllHours"); this.hiddenDateHours = ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15", "16", "17", "18", "19", "20", "21", "22", "23"]; }, sameDayPendingAndAutogeneratedFilter(currentDate) { return (accion) => { try { const accionDate = LswTimer.utils.fromDatestringToDate(accion.tiene_inicio); const sameYear = currentDate.getFullYear() === accionDate.getFullYear(); const sameMonth = currentDate.getMonth() === accionDate.getMonth(); const sameDay = currentDate.getDate() === accionDate.getDate(); const isPendiente = accion.tiene_estado === "pendiente"; const isAutogenerated = accion.tiene_parametros.startsWith("[*autogenerada]"); const isValid = sameYear && sameMonth && sameDay && isPendiente && isAutogenerated; if (isValid) { console.log("!!!", accion.en_concepto); } else { console.log("sameYear, sameMonth, sameDay, isPendiente"); console.log("Fallo:", sameYear, sameMonth, sameDay, isPendiente, isAutogenerated); } return isValid; } catch (error) { console.log(error); return false; } } }, async cleanRandomizedDays() { this.$trace("lsw-agenda-acciones-viewer.methods.cleanRandomizedDays"); this.hideRandomizer(); const currentDate = this.selectedDate || new Date(); const filterAutogeneratedPendingOfCurrentDate = this.sameDayPendingAndAutogeneratedFilter(currentDate); const matchedAcciones = await this.$lsw.database.selectMany("Accion", filterAutogeneratedPendingOfCurrentDate); if (!matchedAcciones.length) { return this.$lsw.toasts.send({ title: "No hay acciones randomizadas", text: "Niniguna acción fue eliminada por ello." }); } const respuesta = await this.$lsw.dialogs.open({ title: "Eliminar registros randomizados", template: `<div> <div class="pad_1"> <div>¿Estás seguro que quieres eliminar los registros randomizados?</div> <div>Se eliminarán {{ accionesToDelete.length }} registros de acciones randomizados de hoy.</div> <hr /> <div class="flex_row pad_1"> <div class="flex_100"></div> <div class="flex_1 pad_left_1"> <button v-on:click="() => accept(true)" class="supermini danger_button">Eliminar</button> </div> <div class="flex_1 pad_left_1"> <button v-on:click="cancel" class="supermini">Cancelar</button> </div> </div> </div> </div>`, factory: { data: { accionesToDelete: matchedAcciones } } }); if (respuesta !== true) return; await this.$lsw.database.deleteMany("Accion", filterAutogeneratedPendingOfCurrentDate); await this.loadDateTasks(currentDate); }, async randomizeDay() { this.$trace("lsw-agenda-acciones-viewer.methods.randomizeDay"); this.hideRandomizer(); const respuesta = await this.$dialogs.open({ title: "Cuestionario de randomizar día", template: `<div> <div class="pad_1 pad_bottom_0"> <div class="pad_1 pad_bottom_0">¿Qué duración quieres para las acciones de randomizado de día?</div> <div class="pad_1 pad_top_2 pad_bottom_0"> <lsw-duration-control ref="duracion" :settings="{name:'duracion',initialValue:'20min'}" :skip-label="true" /> </div> <hr /> <div class="pad_1 pad_bottom_0">¿Desde qué hora quieres randomizar? Mínimo: 0.</div> <div class="pad_1 pad_top_2 pad_bottom_0"> <lsw-text-control ref="hora_inicio" :settings="{name:'hora_inicio',initialValue:currentHour}" :skip-label="true" /> </div> <hr /> <div class="pad_1 pad_bottom_0">¿Hasta qué hora quieres randomizar? Máximo: 24</div> <div class="pad_1 pad_top_2 pad_bottom_0"> <lsw-text-control ref="hora_final" :settings="{name:'hora_final',initialValue:'24'}" :skip-label="true" /> </div> </div> <div v-if="error"> <hr/> <div class="box_error_container error_is_affecting_field" v-on:click="() => setError(false)"> <div class="box_error_content">{{ error.name }}: {{ error.message }}</div> </div> </div> <hr /> <div class="text_align_right pad_right_1"> <button class="supermini danger_button" v-on:click="submit">Randomizar día</button> <button class="supermini" v-on:click="cancel">Cancelar</button> </div> </div>`, factory: { data: { error: false, currentHour: (new Date()).getHours() + "", }, methods: { setError(error) { this.error = error; }, submit() { this.$trace("Dialogs.randomizar_dia.methos.submit"); try { const valor = this.$refs.duracion.value; const ast = LswTimer.parser.parse(valor); const esValido = (valor.trim() !== "") && (typeof ast[0] === "object") && (ast[0].tipo === "Duracion"); if (!esValido) return; this.value = { duracion: valor, hora_inicio: parseInt(this.$refs.hora_inicio.value), hora_final: parseInt(this.$refs.hora_final.value), }; if (this.value.hora_inicio < 0) { throw new Error("Hora de inicio debe ser mayor que 0"); } if (this.value.hora_inicio > 24) { throw new Error("Hora de inicio debe ser menor o igual que 24"); } if (this.value.hora_final < 0) { throw new Error("Hora de final debe ser mayor que 0"); } if (this.value.hora_final > 24) { throw new Error("Hora de final debe ser menor o igual que 24"); } return this.accept(); } catch (error) { console.log(error); this.setError(error); } } } } }); const { duracion: duracion_de_bloques, hora_inicio, hora_final } = respuesta; if (typeof duracion_de_bloques !== "string") return; const currentDate = this.selectedDate; const accionesDelDia = await this.$lsw.database.select("Accion", accion => { try { const accionDate = LswTimer.utils.fromDatestringToDate(accion.tiene_inicio); const sameYear = currentDate.getFullYear() === accionDate.getFullYear(); const sameMonth = currentDate.getMonth() === accionDate.getMonth(); const sameDay = currentDate.getDate() === accionDate.getDate(); const isNotPendiente = accion.tiene_estado !== "pendiente"; const isValid = sameYear && sameMonth && sameDay && isNotPendiente; return isValid; } catch (error) { console.log(error); return false; } }); const momentoInicio = new Date(this.selectedDate); Configurar_hora_de_inicio: { momentoInicio.setHours(hora_inicio); momentoInicio.setMinutes(0); momentoInicio.setSeconds(0); momentoInicio.setMilliseconds(0); } const momentoFinal = new Date(this.selectedDate); Configurar_hora_de_final: { momentoFinal.setHours(hora_final - 1); momentoFinal.setMinutes(0); momentoFinal.setSeconds(0); momentoFinal.setMilliseconds(0); } const randomizableRules = await this.$lsw.fs.evaluateAsDotenvFileOrReturn("/kernel/settings/randomizables.env", {}); const accionesAutogeneradas = LswAgendaRandomizer.generar( randomizableRules, accionesDelDia, momentoInicio, duracion_de_bloques, momentoFinal, 0.2 ); accionesAutogeneradas.forEach(accion => { delete accion.id; accion.tiene_estado = "pendiente"; accion.tiene_parametros = ("[*autogenerada] " + (accion.tiene_parametros.replace(/^\[\*autogenerada\]/g, ""))).trim(); }); Insertar_rows: { await this.$lsw.database.insertMany("Accion", accionesAutogeneradas); await this.loadDateTasks(this.selectedDate); } }, async openDeleteTaskDialog(tarea, e) { this.$trace("lsw-agenda-acciones-viewer.methods.openDeleteTaskDialog"); const confirmed = await Vue.prototype.$dialogs.open({ title: "Eliminar registro", template: ` <div> <div class="pad_2">¿Seguro que quieres eliminar el registro?</div> <hr class="margin_0" /> <div class="pad_2 text_align_right"> <button class="supermini danger_button" v-on:click="() => accept(true)">Eliminar</button> <button class="supermini " v-on:click="() => accept(false)">Cancelar</button> </div> </div> `, }); if (!confirmed) return false; await this.$lsw.database.delete("Accion", tarea.id); this.selectedForm = undefined; this.loadDateTasks(); }, async onUpdateTask(v, tarea) { this.$trace("lsw-agenda-acciones-viewer.methods.onUpdateTask"); await this.$lsw.database.update('Accion', tarea.id, v); this.selectedForm = tarea.id; this.loadDateTasks(); }, openNewRowDialog() { this.$trace("lsw-agenda-acciones-viewer.methods.openNewRowDialog"); const that = this; this.$lsw.dialogs.open({ title: "Crear acción", template: ` <lsw-schema-based-form :on-submit="v => onInsertTask(v)" :on-delete-row="loadDateTasks" :overriden-values="{ tiene_estado: 'pendiente', tiene_inicio: $lsw.timer.utils.formatDatestringFromDate(selectedDate, 1) + ' ' + $lsw.timer.utils.fromDateToHour(selectedDate, true) }" :model="{ connection: $lsw.database, databaseId: 'lsw_default_database', rowId: -1, tableId: 'Accion', }" /> `, factory: { data: { selectedDate: that.selectedDate, }, methods: { onInsertTask(v) { that.onInsertTask(v) this.cancel(); }, loadDateTasks() { that.loadDateTasks(); this.cancel(); }, } } }); }, openEditRowDialog(accion) { this.$trace("lsw-agenda-acciones-viewer.methods.openEditRowDialog"); const that = this; this.$lsw.dialogs.open({ title: "Editar acción", template: ` <lsw-schema-based-form :on-submit="v => onUpdateTask(v, accion)" :on-delete-row="loadDateTasks" :overriden-values="{ tiene_inicio: accion.tiene_inicio }" :model="{ connection: $lsw.database, databaseId: 'lsw_default_database', rowId: accion.id, tableId: 'Accion', }" /> `, factory: { data: { accion, selectedDate: that.selectedDate, }, methods: { onUpdateTask(v, accion) { that.onUpdateTask(v, accion); this.cancel(); }, loadDateTasks() { that.loadDateTasks(); this.cancel(); }, } } }); }, }, watch: { }, async mounted() { try { this.$trace("lsw-agenda-acciones-viewer.mounted"); await this.loadDateTasks(); } catch (error) { console.log(error); } } });
Vue.component("LswAgendaBreadcrumb", { name: "LswAgendaBreadcrumb", template: $template, props: { agenda: { type: Object, default: () => null }, pathItems: { type: Array, required: true } }, data() { this.$trace("lsw-agenda-breadcrumb.data"); return { }; }, methods: { goToSection(section) { this.$trace("lsw-agenda-breadcrumb.methods.goToSection"); if(this.agenda) { this.agenda.selectContext(section); } } }, watch: { }, async mounted() { try { this.$trace("lsw-agenda-breadcrumb.mounted"); } catch (error) { console.log(error); } } });
Vue.component("LswAgendaConceptoAdd", { template: $template, props: {}, data() { this.$trace("lsw-agenda-concepto-add.data"); return { // }; }, methods: { async insertConcepto(v) { this.$trace("lsw-agenda-concepto-add.methods.insertConcepto"); await this.$lsw.database.insert("Concepto", v); // *@TODO: should redirect } }, watch: {}, mounted() { try { this.$trace("lsw-agenda-concepto-add.mounted"); } catch(error) { console.log(error); } } });
Vue.component("LswAgendaConceptoSearch", { template: $template, props: {}, data() { this.$trace("lsw-agenda-concepto-search.data"); return { isLoaded: false, }; }, methods: { async loadRows() { this.$trace("lsw-agenda-concepto-search.methods.loadRows"); this.rows = await this.$lsw.database.selectMany("Concepto", it => true); this.isLoaded = true; } }, watch: {}, mounted() { try { this.$trace("lsw-agenda-concepto-search.mounted"); this.loadRows(); } catch(error) { console.log(error); } } });
Vue.component("LswAgendaEventoSearch", { template: $template, props: {}, data() { this.$trace("lsw-agenda-evento-search.data"); return {}; }, methods: {}, watch: {}, mounted() { try { this.$trace("lsw-agenda-evento-search.mounted"); } catch(error) { console.log(error); } } });
Vue.component("LswAgendaForm", { template: $template, props: { formMetadata: { type: Object, required: true, } }, data() { this.$trace("lsw-agenda-form.data"); this.validateFormMetadata(this.formMetadata); return { expandedExplanations: [], formScope: {}, formState: {} }; }, methods: { validateFormMetadata(v) { const isObject = typeof v === "object"; const hasFormAsObject = typeof v.form === "object"; const hasFieldsAsArray = Array.isArray(v.fields); if(!isObject) { throw new Error("Required parameter «formMetadata» to be an object on «LswAgendaForm.methods.validateFormMetadata»"); } if(!hasFormAsObject) { throw new Error("Required parameter «formMetadata.form» to be an object on «LswAgendaForm.methods.validateFormMetadata»"); } if(!hasFieldsAsArray) { throw new Error("Required parameter «formMetadata.fields» to be an array on «LswAgendaForm.methods.validateFormMetadata»"); } }, toggleExplanation(id) { const pos = this.expandedExplanations.indexOf(id); if(pos === -1) { this.expandedExplanations.push(id); } else { this.expandedExplanations.splice(pos, 1); } }, loadFields() { this.$window.F = this.$refs.agenda_form; } }, watch: {}, mounted() { try { this.$trace("lsw-agenda-form.mounted"); this.loadFields(); } catch(error) { console.log(error); } } });
Vue.component("LswAgendaImpresionAdd", { template: $template, props: {}, data() { this.$trace("lsw-agenda-impresion-add.data"); return {}; }, methods: {}, watch: {}, mounted() { try { this.$trace("lsw-agenda-impresion-add.mounted"); } catch(error) { console.log(error); } } });
Vue.component("LswAgendaImpresionSearch", { template: $template, props: {}, data() { this.$trace("lsw-agenda-impresion-search.data"); return {}; }, methods: {}, watch: {}, mounted() { try { this.$trace("lsw-agenda-impresion-search.mounted"); } catch(error) { console.log(error); } } });
Vue.component("LswAgendaInfraccionSearch", { template: $template, props: {}, data() { this.$trace("lsw-agenda-infraccion-search.data"); return {}; }, methods: {}, watch: {}, mounted() { try { this.$trace("lsw-agenda-infraccion-search.mounted"); } catch(error) { console.log(error); } } });
Vue.component("LswAgendaLimitadorAdd", { template: $template, props: {}, data() { this.$trace("lsw-agenda-limitador-add.data"); return { // }; }, methods: { async insertLimitador(v) { this.$trace("lsw-agenda-limitador-add.methods.insertLimitador"); await this.$lsw.database.insert("Limitador", v); // *@TODO: should redirect } }, watch: {}, mounted() { try { this.$trace("lsw-agenda-limitador-add.mounted"); } catch(error) { console.log(error); } } });
Vue.component("LswAgendaLimitadorSearch", { template: $template, props: {}, data() { this.$trace("lsw-agenda-limitador-search.data"); return { isLoaded: false, }; }, methods: { async loadRows() { this.$trace("lsw-agenda-limitador-search.methods.loadRows"); this.rows = await this.$lsw.database.selectMany("Limitador", it => true); this.isLoaded = true; } }, watch: {}, mounted() { try { this.$trace("lsw-agenda-limitador-search.mounted"); this.loadRows(); } catch(error) { console.log(error); } } });
Vue.component("LswAgendaLimitadorViewer", { template: $template, props: {}, data() { this.$trace("lsw-agenda-limitador-viewer.data"); return { isLoaded: false, limitadores: undefined, infracciones: [], }; }, methods: { fixAsyncCode(asyncCode) { if(asyncCode.trim().startsWith("async ")) { return `return await (${asyncCode}).call(this)` } return asyncCode; }, async executeLimitadores() { const lims = this.limitadores; for(let index=0; index<lims.length; index++) { const limitador = lims[index]; const asyncCode = limitador.tiene_funcion; const AsyncFunc = (async function() {}).constructor; const fixedAsyncCode = this.fixAsyncCode(asyncCode); const asyncFunc = new AsyncFunc(fixedAsyncCode); console.log(asyncFunc); try { await asyncFunc.call(this); } catch (error) { this.infracciones.push(error); } } }, async loadLimitadores() { this.$trace("lsw-agenda-limitador-viewer.methods.loadLimitadores"); const limitadores = await this.$lsw.database.selectMany("Limitador"); this.limitadores = limitadores; await this.executeLimitadores(); } }, watch: {}, async mounted() { try { this.$trace("lsw-agenda-limitador-viewer.mounted"); await this.loadLimitadores(); this.isLoaded = true; } catch(error) { console.log(error); } } });
Vue.component("LswAgendaPostimpresionSearch", { template: $template, props: {}, data() { this.$trace("lsw-agenda-postimpresion-search.data"); return {}; }, methods: {}, watch: {}, mounted() { try { this.$trace("lsw-agenda-postimpresion-search.mounted"); } catch(error) { console.log(error); } } });
Vue.component("LswAgendaPropagacionSearch", { template: $template, props: {}, data() { this.$trace("lsw-agenda-propagacion-search.data"); return {}; }, methods: {}, watch: {}, mounted() { try { this.$trace("lsw-agenda-propagacion-search.mounted"); } catch(error) { console.log(error); } } });
Vue.component("LswAgendaPropagadorSearch", { template: $template, props: {}, data() { this.$trace("lsw-agenda-propagador-search.data"); return {}; }, methods: {}, watch: {}, mounted() { try { this.$trace("lsw-agenda-propagador-search.mounted"); } catch(error) { console.log(error); } } });
const LswConductometria = class { static create(...args) { Vue.prototype.$trace("LswConductometria.create"); return new this(...args); } constructor(component = false) { Vue.prototype.$trace("lswConductometria.constructor"); this.$component = component; } async reload() { Vue.prototype.$trace("lswConductometria.reload"); const virtualization = LswConductometriaVirtualization.create(this.$component); await virtualization.$resetVirtualTables(); await virtualization.$reloadProtolangScriptBoot(); await virtualization.$virtualizePropagations(); } } const LswConductometriaVirtualization = class { static create(...args) { Vue.prototype.$trace("LswConductometriaVirtualization.create"); return new this(...args); } constructor(component) { this.$component = component; } reportErrorFromComponent(error) { Vue.prototype.$trace("lswConductometriaVirtualization.reportErrorFromComponent"); this.DEBUG("Lsw-cond-virt.reportErrorFromComponent"); console.log(error); if (this.$component && (typeof this.$component.addError === "function")) { this.$component.addError(error); } } async $resetVirtualTables() { Vue.prototype.$trace("lswConductometriaVirtualization.$resetVirtualTables"); this.DEBUG("Lsw-cond-virt.$resetVirtualTables"); await Vue.prototype.$lsw.database.deleteMany("Accion_virtual", it => true); await Vue.prototype.$lsw.database.deleteMany("Propagador_prototipo", it => true); await Vue.prototype.$lsw.database.deleteMany("Propagador_de_concepto", it => true); } async $reloadProtolangScriptBoot() { Vue.prototype.$trace("lswConductometriaVirtualization.$reloadProtolangScriptBoot"); this.DEBUG("Lsw-cond-virt.$reloadProtolangScriptBoot"); const protoSource = await Vue.prototype.$lsw.fs.read_file("/kernel/agenda/proto/boot.proto"); return await this.$evaluateProtolangScript(protoSource, { sourcePath: "/kernel/agenda/script/boot.proto" }); } async $evaluateProtolangScript(source, parameters) { Vue.prototype.$trace("lswConductometriaVirtualization.$evaluateProtolangScript"); this.DEBUG("Lsw-cond-virt.$evaluateProtolangScript"); const ast = Vue.prototype.$lsw.parsers.proto.parse(source, { options: parameters }); for (let index = 0; index < ast.length; index++) { const sentence = ast[index]; if (sentence.type === "inc") { await this.$evaluateInclude(sentence); } else if (sentence.type === "def") { await this.$evaluateDefine(sentence); } else if (sentence.type === "fun") { await this.$evaluateFunction(sentence); } else if (sentence.type === "rel") { await this.$evaluateRelation(sentence); } } } async $evaluateInclude(sentence) { Vue.prototype.$trace("lswConductometriaVirtualization.$evaluateInclude"); this.DEBUG("Lsw-cond-virt.$evaluateInclude"); this.DEBUG(sentence); let isFile = undefined; let isDirectory = undefined; const allFiles = []; const filepath = sentence.path; Read_node: { this.DEBUG("read node"); isFile = await Vue.prototype.$lsw.fs.is_file(filepath); isDirectory = await Vue.prototype.$lsw.fs.is_directory(filepath);; if (isFile) { Vue.prototype.$trace("[*] Reading file: ", filepath); this.DEBUG("[*] Reading file: ", filepath); const contents = await Vue.prototype.$lsw.fs.read_file(filepath); allFiles.push({ incBy: sentence, file: filepath, contents: contents }); } else if (isDirectory) { Vue.prototype.$trace("[*] Reading directory: ", filepath); this.DEBUG("[*] Reading directory: ", filepath); const subfilesMap = await Vue.prototype.$lsw.fs.read_directory(filepath); const subfiles = Object.keys(subfilesMap); Iterating_subfiles: for (let indexSubfile = 0; indexSubfile < subfiles.length; indexSubfile++) { const subfile = subfiles[indexSubfile]; this.DEBUG("iterating subfile:", subfile); const subfilepath = Vue.prototype.$lsw.fs.resolve_path(filepath, subfile); const is_file = await Vue.prototype.$lsw.fs.is_file(subfilepath); if (!is_file) { continue Iterating_subfiles; } Vue.prototype.$trace("[*] Reading subfile: ", subfilepath); this.DEBUG("[*] Reading subfile: ", subfilepath); const filecontents = await Vue.prototype.$lsw.fs.read_file(subfilepath); allFiles.push({ incBy: sentence, file: subfilepath, contents: filecontents }); } } else { throw new Error(`File does not exits «${filepath}» on «lswConductometriaVirtualization.$evaluateInclude»`); } } Vue.prototype.$trace("[*] Evaluating all subfiles:", allFiles); this.DEBUG("[*] Evaluating all subfiles:", allFiles); Evaluate_subnodes: { for (let indexFile = 0; indexFile < allFiles.length; indexFile++) { const metafile = allFiles[indexFile]; const file = metafile.file; const contents = metafile.contents; await this.$evaluateProtolangScript(contents, { sourcePath: file }); } } } async $evaluateDefine(sentence) { Vue.prototype.$trace("lswConductometriaVirtualization.$evaluateDefine"); this.DEBUG("Lsw-cond-virt.$evaluateDefine"); const { names } = sentence; // @DONE: insertar names en Concepto Iterating_names: for (let index = 0; index < names.length; index++) { const name = names[index]; this.DEBUG("in name:", name); try { await Vue.prototype.$lsw.database.insert("Concepto", { tiene_nombre: name, }); } catch (error) { if (error.message === "Error on «browsie.insert» operation over store «Concepto»: A mutation operation in the transaction failed because a constraint was not satisfied.") { continue Iterating_names; } await this.reportErrorFromComponent(error); } } } async $evaluateFunction(sentence) { Vue.prototype.$trace("lswConductometriaVirtualization.$evaluateFunction"); this.DEBUG("Lsw-cond-virt.$evaluateFunction"); const { name, params, code } = sentence; // @DONE: insertar name+params+code en Propagador_prototipo try { this.DEBUG("inserting prototipo:", name); await Vue.prototype.$lsw.database.insert("Propagador_prototipo", { tiene_nombre: name, tiene_parametros: JSON.stringify(params), tiene_funcion: code, }); } catch (error) { await this.reportErrorFromComponent(error); } } async $evaluateRelation(sentence) { Vue.prototype.$trace("lswConductometriaVirtualization.$evaluateRelation"); this.DEBUG("Lsw-cond-virt.$evaluateRelation"); const { name, effects, triggers } = sentence; Iterating_effects: for (let indexEffect = 0; indexEffect < effects.length; indexEffect++) { const effect = effects[indexEffect]; const { consecuencia, ratio, argumentos } = effect; this.DEBUG("inserting propagador:", name); await Vue.prototype.$lsw.database.insert("Propagador_de_concepto", { tiene_propagador_prototipo: "multiplicador", tiene_concepto_disparador: name, tiene_concepto_destino: consecuencia, tiene_parametros_extra: ratio + (argumentos ? (", " + argumentos) : ''), tiene_codigo: null, }); } Iterating_triggers: for (let indexTrigger = 0; indexTrigger < triggers.length; indexTrigger++) { const trigger = triggers[indexTrigger]; if (trigger.type === "trigger by prototype") { const { prototipo, conceptos, argumentos } = trigger; if (conceptos) { for (let index = 0; index < conceptos.length; index++) { const concepto = conceptos[index]; Insertar_propagador_con_consecuencia: { await Vue.prototype.$lsw.database.insert("Propagador_de_concepto", { tiene_propagador_prototipo: prototipo, tiene_concepto_disparador: name, tiene_concepto_destino: concepto, tiene_parametros_extra: argumentos, tiene_codigo: null, }); } } } else { Insertar_propagador_por_llamada: { await Vue.prototype.$lsw.database.insert("Propagador_de_concepto", { tiene_propagador_prototipo: prototipo, tiene_concepto_disparador: name, tiene_concepto_destino: null, tiene_parametros_extra: argumentos, tiene_codigo: null, }); } } } else if (trigger.type === "trigger by code") { Insertar_propagador_por_codigo_directo: { await Vue.prototype.$lsw.database.insert("Propagador_de_concepto", { tiene_propagador_prototipo: null, tiene_concepto_disparador: name, tiene_concepto_destino: null, tiene_parametros_extra: null, tiene_codigo: trigger.code }); } } } } $toJsExtension(txt) { return txt.replace(/\.js$/g, "") + ".js"; } async $virtualizePropagations() { Vue.prototype.$trace("lswConductometriaVirtualization.$virtualizePropagations"); this.DEBUG("Lsw-cond-virt.$virtualizePropagations"); const accionesReales = await Vue.prototype.$lsw.database.selectMany("Accion", accion => true); // console.log("Acciones reales", accionesReales); const errorOptions = { timeout: 1000 * 10 }; Iterando_acciones_reales: for (let indexAccionReal = 0; indexAccionReal < accionesReales.length; indexAccionReal++) { const accionReal = accionesReales[indexAccionReal]; Virtualizar_accion_real: { await this.addVirtualAction(accionReal); } } } $showError(error, options = {}, propagate = false, log = true) { Vue.prototype.$trace("lswConductometriaVirtualization.$showError"); this.DEBUG("Lsw-cond-virt.$showError"); try { this.$component.showError(error, options, propagate, log); } catch (error) { console.log("[!] Could not notify to vue component about this previous error"); } if (propagate) { throw error; } } async addVirtualAction(accion_inicial) { Vue.prototype.$trace("lswConductometriaVirtualization.addVirtualAction"); this.DEBUG("Lsw-cond-virt.addVirtualAction"); Validaciones_minimas: { break Validaciones_minimas; const ensure1 = $ensure({ accion_inicial }, 1).type("object").to.have.keys(["en_concepto", "tiene_inicio", "tiene_duracion"]); ensure1.its("en_concepto").type("string"); ensure1.its("tiene_inicio").type("string").its("length").to.be.greaterThan(0); ensure1.its("tiene_duracion").type("string").its("length").to.be.greaterThan(0); } Filtramos_los_estados_no_completados: { if (accion_inicial.tiene_estado === 'pendiente') { return "IGNORED BECAUSE OF STATE PENDING"; } if (accion_inicial.tiene_estado === 'fallida') { return "IGNORED BECAUSE OF STATE FAILED"; } } let conc_inicial = accion_inicial.en_concepto; let asoc_propags = false; let asoc_proto_ids = false; let asoc_protos_found = false; let asoc_proto_ids_found = false; let asoc_proto_ids_missing = false; let asoc_protos_as_map = false; Insertar_accion_virtual: { await Vue.prototype.$lsw.database.insert("Accion_virtual", accion_inicial); } Extraer_propags: { asoc_propags = await Vue.prototype.$lsw.database.selectMany("Propagador_de_concepto", propag => { return propag.tiene_concepto_disparador === conc_inicial; }); if (!asoc_propags) { return "NO ASSOCIATED PROPAGATORS FOUND"; } } Extraer_protos: { asoc_proto_ids = LswUtils.uniquizeArray(asoc_propags.map(propag => { return propag.tiene_propagador_prototipo; })); asoc_protos_found = await Vue.prototype.$lsw.database.selectMany("Propagador_prototipo", proto_it => { return asoc_proto_ids.indexOf(proto_it.tiene_nombre) !== -1; }); asoc_proto_ids_found = LswUtils.arrays.uniquizeArray(asoc_protos_found.map(proto_it => proto_it.tiene_nombre)); asoc_proto_ids_missing = LswUtils.arrays.getMissingInFirst(asoc_proto_ids_found, asoc_proto_ids); asoc_protos_as_map = asoc_protos_found.reduce((out, proto_it) => { const nombre = proto_it.tiene_nombre; out[nombre] = proto_it; return out; }, {}); if (asoc_proto_ids_missing.length) { this.$showError(new Error("[!] Cuidado: no se encontraron los siguientes «Propagador_prototipo»: " + asoc_proto_ids_missing.join(", "))); } } let propagation_molecule = {}; Resolver_propags_con_protos_y_propagar: { for (let index_propag = 0; index_propag < asoc_propags.length; index_propag++) { const propag = asoc_propags[index_propag]; const proto_id = propag.tiene_propagador_prototipo; const proto_it = asoc_protos_as_map[proto_id]; try { await this.$propagateVirtualAction(accion_inicial, propag, proto_it); } catch (error) { this.$showError(error); } } } } async $propagateVirtualAction(accion, propagador_de_concepto, propagador_prototipo = false) { Vue.prototype.$trace("lswConductometriaVirtualization.$propagateVirtualAction"); this.DEBUG("Lsw-cond-virt.$propagateVirtualAction"); try { let concepto_origen = undefined; let concepto_destino = undefined; let funcion_propagadora = undefined; let funcion_propagadora_parametros = []; let funcion_propagadora_ambito = this; Validaciones_minimas: { this.DEBUG("Lsw-cond-virt.$propagateVirtualAction::Validaciones_minimas"); const ensure1 = $ensure({ accion }, 1).type("object") ensure1.its("en_concepto").type("string"); ensure1.its("tiene_inicio").type("string").its("length").to.be.greaterThan(0); ensure1.its("tiene_duracion").type("string").its("length").to.be.greaterThan(0); const ensure2 = $ensure({ propagador_de_concepto }, 1).type("object"); const ensure3 = $ensure({ propagador_prototipo }, 1).type(["object", "boolean"]); // console.log("propagador_prototipo", propagador_prototipo); } const { tiene_nombre, // un texto tiene_funcion, // un JavaScript (cuerpo de función) tiene_parametros: tiene_parametros_prototipo, // un JSON bi-array } = propagador_prototipo; const { tiene_propagador_prototipo, // un Propagador_prototipo.tiene_nombre tiene_concepto_disparador, // un Concepto.tiene_nombre tiene_concepto_destino, // un Concepto.tiene_nombre tiene_parametros: tiene_parametros_asociado, // un JSON bi-array tiene_parametros_extra, // un JavaScript (solo parámetros) tiene_codigo, // un JavaScript (cuerpo de función) } = propagador_de_concepto; Extraemos_conceptos: { this.DEBUG("Lsw-cond-virt.$propagateVirtualAction::Extraemos_conceptos"); const conceptos_origen_matched = await Vue.prototype.$lsw.database.selectMany("Concepto", conc => conc.tiene_nombre === tiene_concepto_disparador); concepto_origen = conceptos_origen_matched[0] || undefined; const conceptos_destino_matched = await Vue.prototype.$lsw.database.selectMany("Concepto", conc => conc.tiene_nombre === tiene_concepto_destino); concepto_destino = conceptos_destino_matched[0] || undefined; } Check_point: { // console.log("accion", accion); // console.log("concepto_origen", concepto_origen); // console.log("concepto_destino", concepto_destino); // console.log("funcion_propagadora", funcion_propagadora); // console.log("funcion_propagadora_parametros", funcion_propagadora_parametros); // console.log("funcion_propagadora_ambito", funcion_propagadora_ambito); // console.log("CHECKPOINT!"); } let propagacion_resultado = {}; Fabricamos_la_funcion_propagadora_y_la_llamamos: { this.DEBUG("Lsw-cond-virt.$propagateVirtualAction::Fabricamos_la_funcion_propagadora_y_la_llamamos"); const propagacion_params = (() => { try { return JSON.parse(tiene_parametros_prototipo); } catch (error) { return []; } })(); const propagacion_source = tiene_funcion; if (!propagacion_source) { break Fabricamos_la_funcion_propagadora_y_la_llamamos; } const propagacion_callback = LswUtils.createAsyncFunction(propagacion_source, propagacion_params); if (!propagacion_callback) { break Fabricamos_la_funcion_propagadora_y_la_llamamos; } this.$debugEvaluation(propagacion_callback.toString(), "$propagateVirtualAction") try { const propagacion_context = { accion, propagador_de_concepto, propagador_prototipo }; const propagacion_callback_wrapper = LswUtils.createSyncFunction(`return propagacion_callback(propagacion_context, ${tiene_parametros_asociado || "{}"}, ${tiene_parametros_extra || "undefined"})`, [ "propagacion_context", "propagacion_callback", ]); this.$debugEvaluation(propagacion_callback_wrapper.toString(), "$propagateVirtualAction") propagacion_resultado = await propagacion_callback_wrapper.call(this, propagacion_context, propagacion_callback); } catch (error) { this.$showError(error); } } let accionVirtual = undefined; Fabricamos_nueva_accion: { this.DEBUG("Lsw-cond-virt.$propagateVirtualAction::Fabricamos_nueva_accion"); if (!concepto_destino?.tiene_nombre) { return "NO TIENE CONCEPTO DESTINO"; } if (!concepto_origen?.tiene_nombre) { return "NO TIENE CONCEPTO ORIGEN"; } accionVirtual = this.createDefaultAction({ en_concepto: concepto_destino.tiene_nombre, desde_concepto: concepto_origen.tiene_nombre, tiene_estado: "propagada", tiene_inicio: accion.tiene_inicio, tiene_duracion: accion.tiene_duracion, tiene_accion_anterior: accion.id, tiene_accion_origen: undefined, viene_de_propagador_de_concepto: propagador_de_concepto.id, viene_de_propagador_prototipo: propagador_prototipo.tiene_nombre, ...propagacion_resultado }); } Insertamos_accion_en_virtuales: { this.DEBUG("Lsw-cond-virt.$propagateVirtualAction::Insertamos_accion_en_virtuales"); this.DEBUG(accionVirtual); await this.addVirtualAction(accionVirtual); } // @TODO: fabricar la función propagadora y enchufarla. // @TODO: fabricar la función propagadora y enchufarla. // @TODO: fabricar la función propagadora y enchufarla. // @TODO: fabricar la función propagadora y enchufarla. // @TODO: fabricar la función propagadora y enchufarla. } catch (error) { this.$showError(error); } } $debugEvaluation(jsCode, traceId) { Vue.prototype.$trace("lswConductometriaVirtualization.$debugEvaluation"); this.DEBUG("Lsw-cond-virt.$debugEvaluation"); console.log("[*] Evaluating js from: " + traceId); console.log(jsCode); } createDefaultAction(overwrites = {}) { Vue.prototype.$trace("lswConductometriaVirtualization.createDefaultAction"); this.DEBUG("Lsw-cond-virt.createDefaultAction"); return Object.assign({ en_concepto: "?", tiene_inicio: LswTimer.utils.fromDateToDatestring(new Date()), tiene_duracion: "1min", }, overwrites); } DEBUG(...args) { console.log(...args); } } return LswConductometria;
Vue.component("LswConductometria", { template: $template, props: {}, data() { this.$trace("lsw-conductometria.data"); return { isLoaded: false, reportes: [], }; }, methods: { async reloadEverything() { this.$trace("lsw-conductometria.methods.reloadEverything"); this.isLoaded = null; const files = await this.$lsw.fs.read_directory("/kernel/agenda/report"); this.reportes = Object.keys(files); Reload_conductometria_fully: { await this.$lsw.conductometria.reload(this); } this.isLoaded = true; }, goToReports() { this.$trace("lsw-conductometria.methods.goToReports"); this.$lsw.dialogs.open({ id: 'ver-reportes', title: "Reportes de conductometría", template: ` <lsw-filesystem-explorer opened-by="/kernel/agenda/report/" :absolute-layout="true" /> ` }); }, goToScripts() { this.$trace("lsw-conductometria.methods.goToScripts"); this.$lsw.dialogs.open({ id: 'ver-script', title: "Scripts de conductometría", template: ` <lsw-filesystem-explorer opened-by="/kernel/agenda/proto" :absolute-layout="true" /> ` }); }, async editReport(reporte) { this.$trace("lsw-conductometria.methods.editReport"); this.$lsw.dialogs.open({ title: "Editar reporte " + reporte, template: ` <lsw-filesystem-explorer :opened-by="'/kernel/agenda/report/' + reporte" :absolute-layout="true" /> `, factory: { data: { reporte } } }); }, openReport(reporteId) { this.$trace("lsw-conductometria.methods.openReport"); this.$lsw.dialogs.open({ title: "Reproducir reporte " + reporteId, template: ` <lsw-conductometria-report :report-id="'/kernel/agenda/report/' + reporteId" /> `, factory: { data: { reporteId } } }); }, showError(error, ...args) { Vue.prototype.$lsw.toasts.showError(error, ...args); } }, watch: {}, mounted() { try { this.$trace("lsw-conductometria.mounted"); this.$lsw.conductometria = LswConductometria.create(this); } catch(error) { this.$lsw.toasts.showError(error); } } });
Vue.component("LswBarsGraph", { template: $template, props: { sample: { type: Object, required: true, }, keys: { type: Array, required: true, }, negativeKeys: { type: Array, default: () => [], }, propId: { type: String, default: () => false, }, propOptions: { type: String, default: () => "$options", }, }, data() { this.$trace("lsw-bars-graph.data"); const currentSample = LswBarsGraph.toSample(this.sample, this.propId, this.keys, this.propOptions); return { currentPropertyView: undefined, currentSample, }; }, methods: { selectPropertyView(propIndex) { this.$trace("lsw-bars-graph.methods.selectPropertyView"); const isSame = this.currentPropertyView === propIndex; this.currentPropertyView = isSame ? undefined : propIndex; }, selectPropertyViewByName(propId) { this.$trace("lsw-bars-graph.methods.selectPropertyView"); const propIndex = this.keys.indexOf(propId); if(propIndex === -1) return; const isSame = this.currentPropertyView === propIndex; this.currentPropertyView = isSame ? undefined : propIndex; }, getColor(propId, value) { this.$trace("lsw-bars-graph.methods.getColor"); const polaridad = this.negativeKeys.indexOf(propId) === -1; if(polaridad) { if (value <= 20) { return "red" ; } else if (value <= 40) { return "violet" ; } else if (value <= 60) { return "white"; } else if (value <= 80) { return "yellow" ; } else { return "lime" ; } } else { if (value <= 20) { return "lime" ; } else if (value <= 40) { return "yellow" ; } else if (value <= 60) { return "white" ; } else if (value <= 80) { return "violet"; } else { return "red" ; } } } }, mounted() { this.$trace("lsw-bars-graph.mounted"); }, unmount() { this.$trace("lsw-bars-graph.unmounted"); } });
Vue.component("LswBarsGraphBar", { template: $template, props: { value: { type: Number, required: true, }, text: { type: [String, Boolean], required: true, }, onClick: { type: Function, default: () => {}, }, color: { type: [String, Boolean], default: () => false } }, data() { this.$trace("lsw-bars-graph-bar.data"); return { }; }, methods: { }, mounted() { this.$trace("lsw-bars-graph-bar.mounted"); }, unmount() { this.$trace("lsw-bars-graph-bar.unmounted"); } });
Vue.component("LswBinDirectory", { template: $template, props: { directory: { type: String, required: true, }, }, data() { this.$trace("lsw-bin-directory.data"); return { hasError: false, isLoaded: false, isAboutSearching: false, searchText: "", binaries: [], binariesInSelection: [], delayedTimeout: 0.7 * 1000, delayedTimeoutId: undefined, }; }, methods: { async loadBinaries() { this.$trace("lsw-bin-directory.methods.loadBinaries"); try { this.isLoaded = false; const allBinaries = await this.$lsw.fs.$selectMany(it => { return it.type === "file" && it.filepath.startsWith("/kernel/bin") && it.filepath.endsWith(".js"); }); this.binaries = allBinaries; await this.digestOutput(); } catch (error) { this.$lsw.toasts.send({ title: "No pudieron cargarse los binarios", text: 'Hubo un error al cargar los binarios' }); console.error(`[!] Could not load bin-directory «${this.directory}» because:`, error); this.hasError = error; } finally { this.isLoaded = true; } }, digestOutput() { this.$trace("lsw-bin-directory.methods.digestOutput"); return new Promise((resolve, reject) => { let output = []; try { this.isAboutSearching = true; Apply_search: { if (this.searchText.trim() === "") { output = this.binaries; break Apply_search; } const loweredSearchText = this.searchText.toLowerCase(); for (let index = 0; index < this.binaries.length; index++) { const binarie = this.binaries[index]; const hasMatch = binarie.filepath.toLowerCase().indexOf(loweredSearchText) !== -1; if (hasMatch) { output.push(binarie); } } } return resolve(output); } catch (error) { return reject(error); } finally { Export_results: { this.isAboutSearching = false; this.binariesInSelection = output; } } }); }, digestDelayed() { this.$trace("lsw-bin-directory.methods.digestDelayed"); clearTimeout(this.delayedTimeoutId); this.isAboutSearching = true; this.delayedTimeoutId = setTimeout(this.digestOutput, this.delayedTimeout); }, async executeBin(binarie) { this.$trace("lsw-bin-directory.methods.executeBin"); const asyncBin = LswUtils.createAsyncFunction(binarie.content); try { const output = await asyncBin.call(this); Aqui_se_hookearia_pero_creo_que_no: { console.log(output); } return output; } catch (error) { this.$lsw.toasts.sendError(error); } }, editBin(binarie) { this.$trace("lsw-bin-directory.methods.editBin"); this.$lsw.dialogs.open({ title: "Editando binario", template: `<lsw-filesystem-explorer :opened-by="binarie.filepath" :absolute-layout="true" />`, factory: { data: { binarie } }, }); }, openBinarios() { this.$trace("lsw-bin-directory.methods.openBinarios"); this.$lsw.dialogs.open({ title: "Explorando binarios", template: `<lsw-filesystem-explorer opened-by="/kernel/bin" :absolute-layout="true" />` }); }, formatFilepathForUser(txt) { return txt.replace(this.directory, "").replace(/^\//g, "").replace(/\.js$/g, ""); } }, mounted() { this.$trace("lsw-bin-directory.mounted"); this.loadBinaries(); }, unmount() { this.$trace("lsw-bin-directory.unmounted"); // @OK } });
Vue.component("LswCalendario", { template: $template, props: { modo: { type: String, default: () => "datetime" // can be: date, time, datetime }, valorInicial: { type: [String, Date], default: () => new Date() }, alCambiarValor: { type: Function, default: () => { } }, alIniciar: { type: Function, default: () => { } }, }, data() { try { this.$trace("lsw-calendario.data"); const hoy = new Date(); return { es_carga_inicial: true, valor_inicial_adaptado: this.adaptar_valor_inicial(this.valorInicial), es_solo_fecha: this.modo === "date", es_solo_hora: this.modo === "time", es_fecha_y_hora: this.modo === "datetime", fecha_seleccionada: undefined, celdas_del_mes_actual: undefined, marcadores_del_mes: {}, hoy: hoy, hora_actual: hoy.getHours(), minuto_actual: hoy.getMinutes(), dia_actual: hoy.getDate(), mes_actual: hoy.getMonth(), anio_actual: hoy.getFullYear(), /* hora_seleccionada: "0", minuto_seleccionado: "0", segundo_seleccionado: "0", milisegundo_seleccionado: "0", //*/ }; } catch (error) { console.log(error); throw error; } }, methods: { getValue() { this.$trace("lsw-calendario.methods.getValue"); return this.fecha_seleccionada; }, adaptar_valor_inicial(valor) { this.$trace("lsw-calendario.methods.adaptar_valor_inicial"); if (typeof valor === "string") { try { const resultado = LswTimer.utils.getDateFromMomentoText(valor); console.log("FECHA ENTRADA:", resultado); return resultado; } catch (error) { console.error("Error parseando valor inicial de lsw-calendario:", error); } } return valor || new Date(); }, agregar_digito_de_hora(indice) { this.$trace("lsw-calendario.methods.agregar_digito_de_hora"); const value = this.obtener_digito_de_hora(indice); const isInMaximum = ([3, 5].indexOf(indice) !== -1) ? value === 5 : ([1].indexOf(indice) !== -1) ? value === 2 : value === 9; if (!isInMaximum) { this.establecer_digito_de_hora(indice, value + 1); } }, quitar_digito_de_hora(indice) { this.$trace("lsw-calendario.methods.quitar_digito_de_hora"); const value = this.obtener_digito_de_hora(indice); const isInMinimum = value === 0; if (!isInMinimum) { this.establecer_digito_de_hora(indice, value - 1); } }, obtener_digito_de_hora(indice, fecha = this.fecha_seleccionada) { this.$trace("lsw-calendario.methods.obtener_digito_de_hora"); if (indice === 1) { return parseInt(this.espaciar_izquierda(fecha.getHours(), 2)[0]); } else if (indice === 2) { return parseInt(this.espaciar_izquierda(fecha.getHours(), 2)[1]); } else if (indice === 3) { return parseInt(this.espaciar_izquierda(fecha.getMinutes(), 2)[0]); } else if (indice === 4) { return parseInt(this.espaciar_izquierda(fecha.getMinutes(), 2)[1]); } else if (indice === 5) { return parseInt(this.espaciar_izquierda(fecha.getSeconds(), 2)[0]); } else if (indice === 6) { return parseInt(this.espaciar_izquierda(fecha.getSeconds(), 2)[1]); } else { throw new Error("No se reconoció el índice del dígito: " + indice); } }, cambiar_posicion_en_texto(texto, posicion, valor) { this.$trace("lsw-calendario.methods.cambiar_posicion_en_texto"); const arr = ("" + texto).split(""); arr[posicion] = valor; return arr.join(""); }, establecer_digito_de_hora(indice, valor) { this.$trace("lsw-calendario.methods.establecer_digito_de_hora"); console.log(indice, valor); const fecha_clonada = new Date(this.fecha_seleccionada); if (indice === 1) { let horas = this.espaciar_izquierda(this.fecha_seleccionada.getHours(), 2); horas = this.cambiar_posicion_en_texto(horas, 0, valor); const horasInt = parseInt(horas); if(horasInt > 23) return; fecha_clonada.setHours(horasInt); } else if (indice === 2) { let horas = this.espaciar_izquierda(this.fecha_seleccionada.getHours(), 2); horas = this.cambiar_posicion_en_texto(horas, 1, valor); const horasInt = parseInt(horas); if(horasInt > 23) return; fecha_clonada.setHours(horasInt); } else if (indice === 3) { let minutos = this.espaciar_izquierda(this.fecha_seleccionada.getMinutes(), 2); minutos = this.cambiar_posicion_en_texto(minutos, 0, valor); const minutosInt = parseInt(minutos); if(minutosInt > 59) return; fecha_clonada.setMinutes(minutosInt); } else if (indice === 4) { let minutos = this.espaciar_izquierda(this.fecha_seleccionada.getMinutes(), 2); minutos = this.cambiar_posicion_en_texto(minutos, 1, valor); const minutosInt = parseInt(minutos); if(minutosInt > 59) return; fecha_clonada.setMinutes(minutosInt); } else if (indice === 5) { // @OK } else if (indice === 6) { // @OK } else { throw new Error("No se reconoció el índice del dígito: " + indice); } console.log(fecha_clonada); this.fecha_seleccionada = fecha_clonada; this.actualizar_fecha_seleccionada(true); }, ir_a_mes_anterior() { this.$trace("lsw-calendario.methods.ir_a_mes_anterior"); try { const nueva_fecha = new Date(this.fecha_seleccionada); this.fecha_seleccionada = new Date(nueva_fecha.getFullYear(), nueva_fecha.getMonth()-1, 1); } catch (error) { console.log(error); throw error; } }, ir_a_mes_siguiente() { this.$trace("lsw-calendario.methods.ir_a_mes_siguiente"); try { const nueva_fecha = new Date(this.fecha_seleccionada); this.fecha_seleccionada = new Date(nueva_fecha.getFullYear(), nueva_fecha.getMonth()+1, 1); } catch (error) { console.log(error); throw error; } }, seleccionar_dia(dia) { this.$trace("lsw-calendario.methods.seleccionar_dia"); try { this.fecha_seleccionada = dia; this.actualizar_fecha_seleccionada(true); } catch (error) { console.log(error); throw error; } }, espaciar_izquierda(texto, longitud, relleno = "0") { this.$trace("lsw-calendario.methods.espaciar_izquierda"); try { let salida = "" + texto; while (salida.length < longitud) { salida = relleno + salida; } return salida; } catch (error) { console.log(error); throw error; } }, obtener_fecha_formateada(fecha) { this.$trace("lsw-calendario.methods.obtener_fecha_formateada"); try { if (!(fecha instanceof Date)) { console.log(fecha); throw new Error("Required parameter «fecha» to be a Date on «LswCalendario.methods.obtener_fecha_formateada»"); } let formato = ""; formato += (() => { try { if (fecha.getDay() === 0) { return "Domingo"; } if (fecha.getDay() === 1) { return "Lunes"; } if (fecha.getDay() === 2) { return "Martes"; } if (fecha.getDay() === 3) { return "Miércoles"; } if (fecha.getDay() === 4) { return "Jueves"; } if (fecha.getDay() === 5) { return "Viernes"; } if (fecha.getDay() === 6) { return "Sábado"; } } catch (error) { console.log(error); throw error; } })(); formato += ", "; formato += fecha.getDate(); formato += " de "; formato += (() => { try { if (fecha.getMonth() === 0) { return "Enero"; } if (fecha.getMonth() === 1) { return "Febrero"; } if (fecha.getMonth() === 2) { return "Marzo"; } if (fecha.getMonth() === 3) { return "Abril"; } if (fecha.getMonth() === 4) { return "Mayo"; } if (fecha.getMonth() === 5) { return "Junio"; } if (fecha.getMonth() === 6) { return "Julio"; } if (fecha.getMonth() === 7) { return "Agosto"; } if (fecha.getMonth() === 8) { return "Septiembre"; } if (fecha.getMonth() === 9) { return "Octubre"; } if (fecha.getMonth() === 10) { return "Noviembre"; } if (fecha.getMonth() === 11) { return "Diciembre"; } } catch (error) { console.log(error); throw error; } })(); formato += " de "; formato += fecha.getFullYear(); return formato; } catch (error) { console.log(error); throw error; } }, actualizar_calendario(nuevo_valor = this.fecha_seleccionada) { this.$trace("lsw-calendario.methods.actualizar_calendario"); try { const dias = []; const dia_1_del_mes = new Date(nuevo_valor); dia_1_del_mes.setDate(1); dia_1_del_mes.setHours(0); dia_1_del_mes.setMinutes(0); dia_1_del_mes.setSeconds(0); dia_1_del_mes.setMilliseconds(0); const dias_antes_de_entrar_en_el_mes = (() => { try { const dia_de_semana = dia_1_del_mes.getDay(); if (dia_de_semana === 0) { return 6; } if (dia_de_semana === 1) { return 0; } if (dia_de_semana === 2) { return 1; } if (dia_de_semana === 3) { return 2; } if (dia_de_semana === 4) { return 3; } if (dia_de_semana === 5) { return 4; } if (dia_de_semana === 6) { return 5; } } catch (error) { console.log(error); throw error; } })(); const celdas_vacias_anteriores = new Array(dias_antes_de_entrar_en_el_mes); let dia_final_del_mes = undefined; Logica_anterior: { dia_final_del_mes = new Date(nuevo_valor); dia_final_del_mes.setMonth(dia_final_del_mes.getMonth() + 1); dia_final_del_mes.setDate(1); dia_final_del_mes.setDate(dia_final_del_mes.getDate() - 1); } Logica_chatgpt: { dia_final_del_mes = new Date(nuevo_valor.getFullYear(), nuevo_valor.getMonth() + 1, 0); } const numero_final_de_mes = dia_final_del_mes.getDate(); let fila_actual = celdas_vacias_anteriores; for (let index = 1; index <= numero_final_de_mes; index++) { const nueva_fecha = new Date(dia_1_del_mes); nueva_fecha.setDate(index); fila_actual.push(nueva_fecha); if (nueva_fecha.getDay() === 0) { dias.push(fila_actual); fila_actual = []; } } if (fila_actual.length) { dias.push(fila_actual); } this.celdas_del_mes_actual = dias; this.propagar_cambio(); } catch (error) { console.log(error); throw error; } }, actualizar_fecha_seleccionada(con_propagacion = true, fecha_seleccionada = this.fecha_seleccionada) { this.$trace("lsw-calendario.methods.actualizar_fecha_seleccionada"); if (con_propagacion) { const clon_fecha = new Date(fecha_seleccionada); this.fecha_seleccionada = clon_fecha; } }, propagar_cambio() { this.$trace("lsw-calendario.methods.propagar_cambio"); if (typeof this.alCambiarValor === "function") { // Si es carga inicial, no propagamos el evento: if(this.es_carga_inicial) { return; } this.alCambiarValor(this.fecha_seleccionada, this); } }, obtener_expresion_de_hora(fecha = this.fecha_seleccionada) { this.$trace("lsw-calendario.methods.obtener_expresion_de_hora"); let hours = fecha.getHours(); let minutes = fecha.getMinutes(); let seconds = fecha.getSeconds(); hours = this.espaciar_izquierda(hours, 2, "0"); minutes = this.espaciar_izquierda(minutes, 2, "0"); seconds = this.espaciar_izquierda(seconds, 2, "0"); return `${hours}:${minutes}:${seconds}`; }, establecer_marcadores_del_mes(marcadores_del_mes) { this.$trace("lsw-calendario.methods.establecer_marcadores_del_mes"); this.marcadores_del_mes = marcadores_del_mes; }, establecer_hora_directamente(hora, minutos = 0) { this.$trace("lsw-calendario.methods.establecer_hora_directamente"); this.fecha_seleccionada.setHours(hora); this.fecha_seleccionada.setMinutes(minutos); this.fecha_seleccionada.setSeconds(0); this.fecha_seleccionada = new Date(this.fecha_seleccionada); }, increaseHora(horas_aniadidos) { this.$trace("lsw-calendario.methods.increaseHora"); const horas_actuales = this.fecha_seleccionada.getHours(); this.fecha_seleccionada.setHours(horas_actuales + horas_aniadidos); this.fecha_seleccionada.setSeconds(0); this.fecha_seleccionada = new Date(this.fecha_seleccionada); }, increaseMinuto(minutos_aniadidos) { this.$trace("lsw-calendario.methods.increaseMinuto"); const minutos_actuales = this.fecha_seleccionada.getMinutes(); this.fecha_seleccionada.setMinutes(minutos_actuales + minutos_aniadidos); this.fecha_seleccionada.setSeconds(0); this.fecha_seleccionada = new Date(this.fecha_seleccionada); }, setHora(horas) { this.$trace("lsw-calendario.methods.setHora"); this.fecha_seleccionada.setHours(horas); this.fecha_seleccionada.setSeconds(0); this.fecha_seleccionada = new Date(this.fecha_seleccionada); }, setMinuto(minutos) { this.$trace("lsw-calendario.methods.setMinuto"); this.fecha_seleccionada.setMinutes(minutos); this.fecha_seleccionada.setSeconds(0); this.fecha_seleccionada = new Date(this.fecha_seleccionada); }, askHora() { this.$trace("lsw-calendario.methods.askHora"); const hora = window.prompt("Qué hora quieres poner?", this.fecha_seleccionada.getHours()); if(typeof hora !== "string") return; this.fecha_seleccionada.setHours(hora); this.fecha_seleccionada.setSeconds(0); this.fecha_seleccionada = new Date(this.fecha_seleccionada); }, askMinuto() { this.$trace("lsw-calendario.methods.askMinuto"); const minuto = window.prompt("Qué minuto quieres poner?", this.fecha_seleccionada.getMinutes()); if(typeof minuto !== "string") return; this.fecha_seleccionada.setMinutes(minuto); this.fecha_seleccionada.setSeconds(0); this.fecha_seleccionada = new Date(this.fecha_seleccionada); }, }, watch: { fecha_seleccionada(nuevo_valor) { this.$trace("lsw-calendario.watch.fecha_seleccionada"); this.hora_actual = nuevo_valor.getHours(); this.minuto_actual = nuevo_valor.getMinutes(); this.actualizar_calendario(nuevo_valor); }, }, mounted() { this.$trace("lsw-calendario.mounted"); try { this.fecha_seleccionada = this.valor_inicial_adaptado; this.$nextTick(() => { this.es_carga_inicial = false; }); if(this.alIniciar) { this.alIniciar(this.fecha_seleccionada, this); } } catch (error) { console.log(error); throw error; } } });
Vue.component("LswConductometriaReport", { name: "LswConductometriaReport", template: $template, props: { reportId: { type: String, required: true, } }, data() { this.$trace("lsw-conductometria-report.data"); return { isLoaded: false, report: false, }; }, methods: { async loadReport() { this.$trace("lsw-conductometria-report.methods.loadReport"); this.isLoaded = false; const reportSource = await this.$lsw.fs.read_file(this.reportId); const reportInstance = LswConductometriaReport.create(reportSource, this); const report = await reportInstance.buildReport(); this.report = report; this.$nextTick(() => {this.isLoaded = true;}); }, goToReportTitle(reportIndex) { this.$trace("lsw-conductometria-report.methods.loadReport"); const presuntReportTitle = this.$refs["report_" + reportIndex]; try { presuntReportTitle[0].$el.scrollIntoView(); } catch (error) { console.log(error); } }, async openReportSource() { this.$trace("lsw-conductometria-report.methods.openReportSource"); await this.$lsw.dialogs.open({ title: "Editar reporte", template: ` <lsw-filesystem-explorer :opened-by="reportId" :absolute-layout="true" /> `, factory: { data: { reportId: this.reportId } } }); }, }, watch: { }, computed: { }, async mounted() { try { this.$trace("lsw-conductometria-report.mounted"); await this.loadReport(); } catch (error) { console.log(error); } } });
// Change this component at your convenience: Vue.component("LswConfigurationsPage", { template: $template, props: { }, data() { this.$trace("lsw-configurations-page.data", arguments); return { isDebuggerLoaded: true, selectedSection: "preferencias", // puede ser: datos, preferencias currentBackup: false, }; }, methods: { async showCurrentBackup() { this.$trace("lsw-configurations-page.methods.toggleCurrentBackup"); const currentBackup = await this.$lsw.backuper.getLastBackup(); this.$lsw.dialogs.open({ title: "Ver copia de seguridad actual", template: ` <div class="pad_1"> <div class="flex_row pad_bottom_1"> <div class="flex_100"></div> <div class="flex_1 pad_left_1"> <button class="supermini" v-on:click="copySource">📃 Copiar</button> </div> <div class="flex_1 pad_left_1"> <button class="supermini" v-on:click="downloadSource">📥 Descargar</button> </div> </div> <pre class="codeblock">{{ source }}</pre> </div> `, factory: { data: { source: JSON.stringify(currentBackup, null, 2), }, methods: { copySource() { this.$trace("lsw-configurations-page.methods.copySource"); this.$window.navigator.clipboard.writeText(this.source); this.$lsw.toasts.send({ title: "Texto copiado", text: "El texto fue copiado al portapapeles." }); }, downloadSource() { this.$trace("lsw-configurations-page.methods.downloadSource"); LswUtils.downloadFile("lsw-backup.json", this.source); }, } } }); }, async deleteCurrentBackup() { this.$trace("lsw-configurations-page.methods.deleteCurrentBackup"); const confirmation = await this.$lsw.dialogs.open({ title: "Eliminar copia de seguridad actual", template: ` <div class="pad_top_1 pad_left_1"> <div>¿Seguro que quieres eliminar la copia de seguridad actual?</div> <hr /> <div class="flex_row centered text_align_right"> <div class="flex_100"></div> <div class="flex_1 pad_left_1"> <button class="supermini danger_button" v-on:click="accept">Eliminar igual</button> </div> <div class="flex_1 pad_left_1"> <button class="supermini" v-on:click="cancel">Cancelar</button> </div> </div> </div> `, factory: {}, }); if(confirmation !== true) { return; } return this.$lsw.backuper.deleteLastBackup(); }, selectSection(seccion) { this.$trace("lsw-configurations-page.methods.selectSection"); this.selectSection = seccion; }, async startExportarAJsonFile() { this.$trace("lsw-configurations-page.methods.startExportarAJsonFile"); const allData = await LswDatabase.exportDatabase("lsw_default_database"); const minuteUid = LswTimer.utils.formatDatestringFromDate(new Date()).replace(/\/|-|:|\.| /g, "."); this.$lsw.utils.downloadFile(`lsw_default_database.${minuteUid}.json`, JSON.stringify(allData, null, 2)); }, async startExportarAJson() { this.$trace("lsw-configurations-page.methods.startExportarAJson"); const allData = await LswDatabase.exportDatabase("lsw_default_database"); this.$dialogs.open({ title: "Exportar base de datos a JSON", template: `<div class="pad_1"> <div class="flex_row centered"> <div class="flex_100 pad_1">Exportación a JSON:</div> <div class="flex_1 pad_1"> <button class="supermini nowrap" v-on:click="copyToClipboard">Copiar</button> </div> </div> <textarea class="width_100" style="min-height: 220px;" v-model="jsonData" disabled="true"></textarea> <hr /> <div class="pad_1 text_align_right"> <button class="supermini" v-on:click="cancel">Cancelar</button> </div> </div>`, factory: { data: { jsonData: JSON.stringify(allData, null, 2), }, methods: { copyToClipboard() { this.$trace("Dialogo.exportar_a_json.methods.copyToClipboard"); this.$window.navigator.clipboard.writeText(this.jsonData); this.$lsw.toasts.send({ title: "¡Texto copiado!", text: "El texto fue copiado con éxito." }); } } } }) }, async startImportarDeJsonFile() { this.$trace("lsw-configurations-page.methods.startImportarDeJsonFile"); // @TODO: importar texto de un JSON file con un input type file y tol royo. let data = await LswUtils.askForFileText(); try { console.log(data); data = JSON.parse(data); } catch (error) { return this.$lsw.toasts.showError(error); } const confirmacion = await this.$lsw.dialogs.open({ title: "Importación de JSON", template: ` <div class="pad_1"> <div>Estás a punto de importar los siguientes datos a la base de datos:</div> <pre style="max-height: 400px; overflow: scroll; font-size: 10px;">{{ datos }}</pre> <div class="flex_row"> <div class="flex_100"></div> <div class="flex_1 pad_left_1 pad_top_1"> <button class="danger_button supermini" v-on:click="accept">Importar igualmente</button> </div> <div class="flex_1 pad_left_1 pad_top_1"> <button class="supermini" v-on:click="cancel">Cancelar</button> </div> </div> </div> `, factory: { data: { datos: data, } } }); if(confirmacion <= 0) { return false; } // @OK: return; await LswDatabase.importToDatabase("lsw_default_database", data); return this.$lsw.toasts.send({ title: "👍 Importación completada", text: `La importación fue un éxito.` }); }, async startImportarDeJson() { this.$trace("lsw-configurations-page.methods.startImportarDeJson"); const resultado = await this.$dialogs.open({ title: "Importar JSON a base de datos", template: `<div class="pad_1"> <div class="flex_row centered"> <div class="flex_100 pad_1">Importación de JSON:</div> </div> <textarea class="width_100" style="min-height: 220px;" v-model="value.importation" placeholder="Por ejemplo: {NombreDeStore:[{},{},{}]}"></textarea> <hr /> <div class="pad_1 text_align_right"> <button class="supermini danger_button" v-on:click="sendForm">Importar</button> <button class="supermini" v-on:click="cancel">Cancelar</button> </div> </div>`, factory: { data: { value: { importation: "", } }, methods: { sendForm() { this.$trace("Dialogo.importar_de_json.methods.sendForm"); this.validateImportation(); return this.accept(); }, validateImportation() { this.$trace("Dialogo.importar_de_json.methods.validateImportation"); try { JSON.parse(this.value.importation); } catch (error) { this.$lsw.toasts.send({ title: "⛔️ Error al parsear JSON", text: `La importación se interrumpió.` }); throw error; } } } } }); if(typeof resultado !== "object") { return false; } let data = undefined; try { data = JSON.parse(resultado.importation); } catch (error) { return this.$lsw.toasts.send({ title: "⛔️ Error al parsear JSON", text: `La importación falló.` }); } // @OK: await LswDatabase.importToDatabase("lsw_default_database", data); return this.$lsw.toasts.send({ title: "👍 Importación completada", text: `La importación fue un éxito.` }); }, async startResetearBaseDeDatos() { this.$trace("lsw-configurations-page.methods.startResetearBaseDeDatos"); const confirmation = await this.$dialogs.open({ title: "Resetear la base de datos", template: `<div class="pad_1"> <div class="text_align_center" style="min-height: 90px;"> <div class="pad_1 font_weight_bold">¡CUIDADO!</div> <div class="pad_1 pad_top_0">¿Seguro que quieres resetear la base de datos?</div> <div class="pad_1 pad_top_0">Considera que perderás todos los datos.</div> </div> <hr /> <div class="pad_1 text_align_right"> <button class="supermini danger_button" v-on:click="accept">Aceptar</button> <button class="supermini" v-on:click="cancel">Cancelar</button> </div> </div>`, factory: { data: { value: {} }, methods: {} } }); if(typeof confirmation !== "object") return; await this.$lsw.database.close(); await LswDatabase.deleteDatabase("lsw_default_database"); this.$window.location.reload(); }, startConfigureBackgrounds() { this.$trace("lsw-configurations-page.methods.startConfigureBackgrounds"); this.$dialogs.open({ title: "Configurar fondos de pantalla", template: `<lsw-filesystem-explorer :absolute-layout="true" opened-by="/kernel/settings/backgrounds.env" />`, }); }, startConfigureAutomessages() { this.$trace("lsw-configurations-page.methods.startConfigureAutomessages"); this.$dialogs.open({ title: "Configurar automensajes", template: `<lsw-filesystem-explorer :absolute-layout="true" opened-by="/kernel/settings/automessages.env" />`, }); }, startConfigureRutiner() { this.$trace("lsw-configurations-page.methods.startConfigureRutiner"); this.$dialogs.open({ title: "Configurar mensaje rutinario", template: `<lsw-filesystem-explorer :absolute-layout="true" opened-by="/kernel/settings/rutiner.md" />`, }); }, startConfigureRutinerTimeout() { this.$trace("lsw-configurations-page.methods.startConfigureRutinerTimeout"); return LswDomIrruptor.configurarRutinerTimeout(); }, startConfigureRandomizables() { this.$trace("lsw-configurations-page.methods.startConfigureRandomizables"); this.$dialogs.open({ title: "Configurar acciones randomizables", template: `<lsw-filesystem-explorer :absolute-layout="true" opened-by="/kernel/settings/randomizables.env" />`, }); }, startConfigureUserIdentity() { this.$trace("lsw-configurations-page.methods.startConfigureUserIdentity"); this.$dialogs.open({ title: "Configurar identidad de usuario", template: `<lsw-filesystem-explorer :absolute-layout="true" opened-by="/kernel/settings/user.env" />`, }); }, startCodeReference() { this.$trace("lsw-configurations-page.methods.startCodeReference"); this.$window.open("https://allnulled.github.io/lsw-one/reference", "_blank"); }, startGithubHomepage() { this.$trace("lsw-configurations-page.methods.startGithubHomepage"); this.$window.open("https://github.com/allnulled/lsw-one", "_blank"); }, async saveBackup() { this.$trace("lsw-configurations-page.methods.saveBackup"); const allData = await LswDatabase.exportDatabase("lsw_default_database"); await this.$lsw.backuper.setLastBackup(allData); await this.$lsw.toasts.send({ title: "Backup exportado", text: "La copia de seguridad fue exportada con el estado actual con éxito." }); }, async loadBackup() { this.$trace("lsw-configurations-page.methods.loadBackup"); // @TODO: esta función no está terminada. const respuesta = await this.$lsw.dialogs.open({ title: "Importar copia de seguridad", template: ` <div class="pad_1"> <div class="">¿Seguro que quieres importar la actual copia de seguridad?</div> <hr /> <div class="text_align_right"> <button class="supermini danger_button" v-on:click="() => accept(true)">Sí, importar</button> <button class="supermini" v-on:click="close">Cancelar</button> </div> </div> ` }); if(respuesta !== true) return; const backupData = await this.$lsw.backuper.getLastBackup(); try { for(const tableId in backupData) { const tableRows = backupData[tableId]; await this.$lsw.database.insertMany(tableId, tableRows); } } catch (error) { console.log(error); return await this.$lsw.toasts.send({ title: "Backup con errores", text: "La copia de seguridad tuvo errores de importación: " + error.message, background: "rgba(212, 74, 74, 0.62)", }); } await this.$lsw.toasts.send({ title: "Backup importado", text: "La copia de seguridad fue importada al estado actual con éxito." }); }, toggleTracer() { this.$trace("lsw-configurations-page.methods.toggleTracer"); this.isDebuggerLoaded = false; const isActive = this.$lsw.logger.$options.active; if(isActive) { this.$lsw.logger.deactivate(); } else { this.$lsw.logger.activate(); } setTimeout(() => { this.isDebuggerLoaded = true; }, 1); }, startConfigureBoot() { this.$trace("lsw-configurations-page.methods.startConfigureBoot"); this.$dialogs.open({ title: "Configurar arranque", template: `<lsw-filesystem-explorer :absolute-layout="true" opened-by="/kernel/boot.js" />`, }); } }, mounted() { this.$trace("lsw-configurations-page.mounter"); }, });
class ConsoleHooker { static extractProps(obj, props = []) { const out = {}; for(let index=0; index<props.length; index++) { const prop = props[index]; out[prop] = obj[prop].bind(obj); } return out; } constructor(outputElementId) { this.originalConsole = { ...console }; // Guardar los métodos originales this.outputElementId = outputElementId; this.isHooked = false; this.hookConsole(); this.messageCounter = 0; } HOOKED_METHODS = [ "log", "info", "warn", "error", "debug", ]; hookConsole() { if(this.isHooked) { return; } this.HOOKED_METHODS.forEach(method => { if (typeof console[method] === 'function') { console[method] = (...args) => { this.writeToHtml(method, args); this.originalConsole[method](...args); // Llamar al método original }; } }); this.isHooked = true; } formatError(error) { let errorMessage = ""; errorMessage += "Error: " + error.name + ": " + error.message; if (error.location) { errorMessage += JSON.stringify({ found: error.found, expected: error.expected, location: error.location }, null, 2); } return errorMessage; } consoleReducer() { return (arg) => { if (typeof arg === 'object') { if (arg instanceof Error) { return this.formatError(arg); } else { const seen = new WeakSet(); return JSON.stringify(arg, function (key, value) { if (typeof value === "object") { if (seen.has(value)) { return "[Circular]"; } if (value !== null) { seen.add(value); } } return value; }, 2); } } else { return arg; } }; } writeToHtml(method, args) { // Do not log from this method or it becomes recursive: const message = document.createElement('div'); message.className = `console-${method}`; message.textContent = `[${this.messageCounter++}] ${args.map(this.consoleReducer()).join(' ')}`; const outputElement = document.getElementById(this.outputElementId); if (!outputElement) { // console.log("no console hooker output element found"); return; } const subnodes = outputElement.children; const subnodesLength = outputElement.children.length; const hasMoreThan100 = outputElement.children.length > 100; if (hasMoreThan100) { for (let index = subnodes.length - 1; index > 50; index--) { const subnode = subnodes[index]; subnode.remove(); } } const parent = outputElement; parent.insertBefore(message, parent.firstChild); } restoreConsole() { if(!this.isHooked) { return; } this.HOOKED_METHODS.forEach(method => { console[method] = this.originalConsole[method]; }); this.isHooked = false; } } ConsoleHooker.default = ConsoleHooker; return ConsoleHooker;
Vue.component("LswConsoleHooker", { template: $template, props: {}, data() { return { } }, methods: { async loadEruda() { this.$trace("console-hooker.methods.loadEruda"); await LswLazyLoads.loadEruda(); }, async toggleConsole() { this.$trace("console-hooker.methods.toggleConsole"); if (typeof eruda === "undefined") { await this.loadEruda(); eruda.init({ container: this.$refs.console_hooker_box, }); eruda.show(); } else { const isShowing = eruda._$el.find(".eruda-dev-tools").css("display") === "block"; if (isShowing) { eruda.hide(); } else { eruda.show(); } } }, }, mounted() { this.$trace("console-hooker.mounted"); Exportar_consola: { Vue.prototype.$consoleHooker = this; this.$window.LswConsoleHooker = this; } }, unmounted() { } });
Vue.component('LswDataExplorer', { template: $template, props: { value: { required: true }, pageSize: { type: Number, default: 10 }, level: { type: Number, default: 0 }, pointer: { type: Array, default: () => [] } }, data() { return { hasLoadedInnerValue: true, isLoadingInnerValue: false, originalValue: this.value, innerValue: this.value, textFilter: "", isShowingTopPanel: false, isShowingRelatedDocuments: false, documentTitle: "", documentContent: "", expanded: {}, relatedDocuments: [{ title: "Document 1", text: "console.log('hi!');", }], propagateFastFilterTimeoutId: undefined, propagateFastFilterTimeoutMs: 1500 }; }, methods: { getRandomId() { return this.$lsw.toasts.getRandomString(); }, toggleTopPanel() { this.isShowingTopPanel = !this.isShowingTopPanel; }, toggleExpand(key) { this.$set(this.expanded, key, !this.expanded[key]); }, toggleRelatedDocuments() { this.isShowingRelatedDocuments = !this.isShowingRelatedDocuments; }, openDocument(docIndex) { // *@TODO: const doc = this.relatedDocuments[docIndex]; this.documentTitle = doc.title; this.documentContent = doc.text; this.isShowingRelatedDocuments = false; }, saveRelatedDocument() { }, async applyFastFilter(textFilter = this.textFilter) { // *@TODO: try { this.hasLoadedInnerValue = false; this.$forceUpdate(true); if(textFilter.trim() === "") { this.innerValue = this.originalValue; return; } const textFilterFunction = new Function("it,key,i", "try {\n return " + textFilter + ";\n} catch(e) {\n return false;\n}"); console.log("User-built filter function:"); console.log(textFilterFunction.toString()); if(typeof this.originalValue !== "object") { this.innerValue = this.originalValue; return; } else if(Array.isArray(this.originalValue)) { this.innerValue = [].concat(this.originalValue).filter(textFilterFunction); } else { Object.keys(this.originalValue).reduce((out, key, i) => { const value = this.originalValue[key]; const passesFilter = textFilterFunction(value, key, i); if(passesFilter) { out[key] = value; } return out; }, {}); this.innerValue = out; } } catch (error) { console.log(error); } finally { this.isLoadingInnerValue = false; this.hasLoadedInnerValue = true; this.$forceUpdate(true); } }, propagateFastFilter(textFilter = this.textFilter) { this.isLoadingInnerValue = true; clearTimeout(this.propagateFastFilterTimeoutId); this.propagateFastFilterTimeoutId = setTimeout(() => { this.applyFastFilter(textFilter); }, this.propagateFastFilterTimeoutMs); } }, watch: { textFilter(newValue) { this.propagateFastFilter(newValue); } } });
Vue.component('LswDataImplorer', { template: $template, props: { value: { required: true }, pageSize: { type: Number, default: () => 10 }, level: { type: Number, default: () => 0 }, pointer: { type: Array, default: () => [] } }, data() { return { expanded: {}, isRoot: this.pointer.length === 0, currentPageSize: this.pageSize, currentPage: 1, page: {}, entries: [], paginatedEntries: [], isPaginated: false, }; }, methods: { loadEntries() { if (typeof this.value !== 'object' || this.value === null) { return [{ key: 'value', value: this.value }]; } this.entries = Object.entries(this.value).map(([key, value]) => ({ key, value })); }, toggleExpand(key) { this.$set(this.expanded, key, !this.expanded[key]); }, goToPage(page) { this.currentPage = page; this.loadPaginatedEntries(); }, goToPreviousPage() { if(this.currentPage <= 1) { return; } this.currentPage--; this.loadPaginatedEntries(); }, goToNextPage() { if(this.currentPage >= Math.ceil(this.entries.length / this.pageSize)) { return; } this.currentPage++; this.loadPaginatedEntries(); }, goToLastPage() { this.currentPage = Math.ceil(this.entries.length / this.pageSize); this.loadPaginatedEntries(); }, paginateArray(array, pageSize = this.currentPageSize, currentPage = this.currentPage) { const start = (currentPage - 1) * pageSize; const end = start + pageSize; return array.slice(start, end); }, loadPaginatedEntries(entries = this.entries) { this.paginatedEntries = this.paginateArray(entries); this.isPaginated = this.paginatedEntries.length !== this.entries.length; }, }, watch: { entries(newValue) { if(this.pageSize <= 0) { return newValue; } this.loadPaginatedEntries(newValue); } }, mounted() { this.loadEntries(); } });
Vue.component("LswDataPrinterButton", { template: $template, props: { input: { type: [Object, String, Boolean, Number, Function], default: () => false, }, }, data() { this.$trace("lsw-data-printer-button.data"); return { inputType: typeof(this.input), formatType: undefined, }; }, methods: { async openViewer() { this.$trace("lsw-data-printer-button.methods.openViewer"); let input = this.input; if(this.inputType === "function") { input = await this.input(); } this.formatType = typeof input; this.$lsw.dialogs.open({ title: "Impresión de «" + this.formatType + "»", template: ` <lsw-data-printer-report :input="input" /> `, factory: { data: { input } }, }); } }, watch: {}, mounted() { this.$trace("lsw-data-printer-button.mounted"); }, unmounted() { this.$trace("lsw-data-printer-button.unmounted"); } });
Vue.component("LswDataPrinterReport", { template: $template, props: { input: { type: [Object, String, Boolean, Number], default: () => false, }, }, data() { this.$trace("lsw-data-printer-report.data"); const inputType = typeof(this.input); const availableOptions = (() => { switch(inputType) { case "string": return ["Crudo", "Markdown", "Solo texto plano"]; default: return ["Crudo", "Natural"]; } })(); return { availableOptions, selectedSection: "Crudo", inputType, }; }, methods: { copyCrude() { this.$trace("lsw-data-printer-report.methods.copyCrude"); const json = JSON.stringify(this.input, null, 2); this.$window.navigator.clipboard.writeText(json); this.$lsw.toasts.send({ title: "Texto copiado correctamente!" }); }, askForFilename() { return this.$lsw.dialogs.open({ title: "Descargar en fichero", template: ` <div class="pad_1"> <div>¿Qué nombre de fichero quieres para la descarga?</div> <input class="width_100 margin_top_1" type="text" v-model="value" /> <hr /> <div class="flex_row centered pad_1"> <div class="flex_100"></div> <div class="flex_1 pad_left_1"> <button class="supermini" v-on:click="accept">Descargar</button> </div> <div class="flex_1 pad_left_1"> <button class="supermini" v-on:click="cancel">Cancelar</button> </div> </div> </div> `, factory: { data: { value: "" } }, }); }, async downloadCrude() { this.$trace("lsw-data-printer-report.methods.downloadCrude"); const filename = await this.askForFilename(); if(filename === -1) return; LswUtils.downloadFile(filename, JSON.stringify(this.input, null, 2)); }, copyNatural() { this.$trace("lsw-data-printer-report.methods.copyNatural"); const text = this.inputNatural; this.$window.navigator.clipboard.writeText(text); this.$lsw.toasts.send({ title: "Texto copiado correctamente!" }); }, async downloadNatural() { this.$trace("lsw-data-printer-report.methods.downloadNatural"); const filename = await this.askForFilename(); if(filename === -1) return; LswUtils.downloadFile(filename, this.inputNatural); }, copyMarkdown() { this.$trace("lsw-data-printer-report.methods.copyMarkdown"); const text = this.inputMarkdown; this.$window.navigator.clipboard.writeText(text); this.$lsw.toasts.send({ title: "Texto copiado correctamente!" }); }, async downloadMarkdown() { this.$trace("lsw-data-printer-report.methods.downloadMarkdown"); const filename = await this.askForFilename(); if(filename === -1) return; LswUtils.downloadFile(filename, this.inputMarkdown); }, copyHtml() { this.$trace("lsw-data-printer-report.methods.copyHtml"); const text = this.inputHtml; this.$window.navigator.clipboard.writeText(text); this.$lsw.toasts.send({ title: "Texto copiado correctamente!" }); }, async downloadHtml() { this.$trace("lsw-data-printer-report.methods.downloadHtml"); return this.$window.alert("Exportación a HTML no disponible. xD"); const filename = await this.askForFilename(); }, copyPlain() { this.$trace("lsw-data-printer-report.methods.copyPlain"); const text = this.inputPlain; this.$window.navigator.clipboard.writeText(text); this.$lsw.toasts.send({ title: "Texto copiado correctamente!" }); }, async downloadPlain() { this.$trace("lsw-data-printer-report.methods.downloadPlain"); const filename = await this.askForFilename(); if(filename === -1) return; LswUtils.downloadFile(filename, this.inputPlain); } }, computed: { inputNatural() { this.$trace("lsw-data-printer-report.computed.inputNatural"); return LswUtils.fromJsonToNatural(this.input); }, inputMarkdown() { this.$trace("lsw-data-printer-report.computed.inputMarkdown"); return marked.parse(this.input); }, inputPlain() { this.$trace("lsw-data-printer-report.computed.inputPlain"); return LswDom.extractPlaintextFromHtmltext(this.inputMarkdown); } }, watch: {}, mounted() { this.$trace("lsw-data-printer-report.mounted"); }, unmounted() { this.$trace("lsw-data-printer-report.unmounted"); } });
Vue.component("LswDatabaseBreadcrumb", { template: $template, props: { databaseExplorer: { type: Object, required: true }, breadcrumb: { type: Array, required: true } }, data() { return { } }, methods: { selectPage(page, args = {}) { return this.databaseExplorer.selectPage(page, args); } }, async mounted() { }, unmounted() { } });
Vue.component("LswDatabaseExplorer", { template: $template, props: { showBreadcrumb: { type: Boolean, default: () => true }, initialPage: { type: String, default: () => "lsw-page-tables" }, initialArgs: { type: Object, default: () => ({ database: "lsw_default_database" }) }, }, data() { this.$trace("lsw-database-explorer.data", []); return { isLoading: false, selectedPage: this.initialPage, selectedArgs: this.initialArgs, } }, methods: { selectPage(page, args = {}) { try { this.$trace("lsw-database-explorer.methods.selectPage", arguments); $ensure({page}, 1).type("string"); $ensure({args}, 1).type("object"); this.isLoading = true; this.$nextTick(() => { this.selectedArgs = args; this.selectedPage = page; this.isLoading = false; }); } catch (error) { console.log(error); throw error; } } }, async mounted() { this.$trace("lsw-database-explorer.methods.mounted", arguments); }, unmounted() { this.$trace("lsw-database-explorer.methods.unmounted", arguments); } });
Vue.component("LswDebugger", { template: $template, props: { }, data() { this.$trace("lsw-error-box.data"); return { currentMessages: [], }; }, methods: { debug(data, options = {}) { this.$trace("lsw-debugger.methods.debug"); const id = options.id || LswRandomizer.getRandomString(10); const timeout = options.timeout || 3000; const timeoutId = setTimeout(() => { const pos = this.currentMessages.findIndex(it => it.id === id); if(pos === -1) return; this.currentMessages.splice(pos, 1); this.$forceUpdate(true); }, timeout); this.currentMessages.unshift({ id, data: data, options, timeout, timeoutId, created_at: LswTimer.utils.fromDateToDatestring(new Date(), 0, 0, 1, 0, 1), }); }, clearMessages() { this.$trace("lsw-debugger.methods.clearMessages"); this.currentMessages = []; } }, watch: {}, mounted() { try { this.$trace("lsw-error-box.mounted"); LswDebugger.global = LswDebugger.create(this); this.$debugger = LswDebugger.global; this.$lsw.debugger = LswDebugger.global; } catch (error) { console.log(error); } } });
const defaultDialogFactory = () => { return { props: {}, data() { return {}; }, methods: {}, mounted() { }, }; }; class Dialog { static fromIdToComponentName(id) { return "lsw-dialog-" + id; } constructor(info = {}) { Object.assign(this, info); Validations: { if (typeof this.id !== "string") { throw new Error(`Required parameter «dialog.id» to be a string on «Dialog.constructor»`); } if (typeof this.name !== "string") { throw new Error(`Required parameter «dialog.name» to be a string on «Dialog.constructor»`); } if (typeof this.priority !== "number") { throw new Error(`Required parameter «dialog.priority» to be a number on «Dialog.constructor»`); } if (typeof this.component !== "object") { throw new Error(`Required parameter «dialog.component» to be an object on «Dialog.constructor»`); } if (typeof this.promiser !== "object") { throw new Error(`Required parameter «dialog.promiser» to be an object on «Dialog.constructor»`); } if (!(this.promiser.promise instanceof Promise)) { throw new Error(`Required parameter «dialog.promiser.promise» to be an instance of Promise on «Dialog.constructor»`); } if (typeof this.promiser.resolve !== "function") { throw new Error(`Required parameter «dialog.promiser.resolve» to be an function on «Dialog.constructor»`); } if (typeof this.promiser.reject !== "function") { throw new Error(`Required parameter «dialog.promiser.reject» to be an function on «Dialog.constructor»`); } if (typeof this.acceptButton !== "object") { this.acceptButton = false; } if (typeof this.cancelButton !== "object") { this.cancelButton = false; } } } } const closeSubdialogsHook = function (id, lswDialogs) { const ids = Object.keys(lswDialogs.opened); for (let index_dialog = 0; index_dialog < ids.length; index_dialog++) { const idOpened = ids[index_dialog]; const idParent = lswDialogs.opened[idOpened].parentId; if (idParent === id) { lswDialogs.close(idOpened); } } }; Vue.component("LswDialogs", { name: "LswDialogs", template: $template, props: { asWindows: { type: Boolean, default: () => false } }, data() { this.$trace("lsw-dialogs.data", []); return { enabledWindowsSystem: this.asWindows, opened: {}, openedLength: 0, notMinimizedLength: 0, hookOnOpen: undefined, hookOnClose: closeSubdialogsHook, }; }, watch: { opened(newValue) { this.$trace("lsw-dialogs.watch.opened", []); this.openedLength = (typeof newValue !== "object") ? 0 : Object.keys(newValue).length; this._refreshMinimizedLength(newValue); } }, methods: { open(parametricObject = {}) { this.$trace("lsw-dialogs.methods.open", []); if (typeof parametricObject !== "object") { throw new Error(`Required argument «parametricObject» to be an object on «LswDialogs.methods.open»`); } const { template, title = "", // @OK: El ID debería ser único o no se abrirán las duplicadas. // @PERO: Pero por algo lo tenía así también y no recuerdo. id = LswRandomizer.getRandomString(10), priority = 500, factory = defaultDialogFactory, parentId = undefined, created_at = new Date() } = parametricObject; const componentInfo = {}; if (typeof id !== "string") { throw new Error(`Required parameter «id» to be a string on «LswDialogs.methods.open»`); } if (id in this.opened) { return this.maximize(id); // throw new Error(`Cannot open dialog «${id}» because it is already opened on «LswDialogs.methods.open»`); } if (typeof template !== "string") { throw new Error(`Required parameter «template» to be a string on «LswDialogs.methods.open»`); } if (typeof factory === "object") { // @OK } else if (typeof factory !== "function") { throw new Error(`Required parameter «factory» to be an object or a function on «LswDialogs.methods.open»`); } if (typeof priority !== "number") { throw new Error(`Required parameter «priority» to be a number on «LswDialogs.methods.open»`); } const dialogComponentInput = typeof factory === "function" ? factory() : factory; const dialogComponentData = (() => { if (typeof dialogComponentInput.data === "undefined") { return function () { return {}; }; } else if (typeof dialogComponentInput.data === "object") { return function () { return dialogComponentInput.data }; } else if (typeof dialogComponentInput.data === "function") { return dialogComponentInput.data; } else { console.log(dialogComponentInput.data); throw new Error("Required parameter «data» returned by «factory» to be an object, a function or empty on «LswDialogs.methods.open»"); } })(); const scopifyMethods = function (obj, scope) { return Object.keys(obj).reduce((out, k) => { const v = obj[k]; if (typeof v !== "function") { out[k] = v; } else { out[k] = v.bind(scope); } return out; }, {}); }; // 1) Este es para el Vue.component: const componentId = Dialog.fromIdToComponentName(id); const dialogComponent = Object.assign({}, dialogComponentInput, { name: componentId, template, data(component, ...args) { this.$trace(`lsw-dialogs.[${componentId}].data`, []); const preData = dialogComponentData.call(this); if (typeof preData.value === "undefined") { preData.value = ""; }; // console.log("El data del nuevo componente dialog:", preData); dialogComponentInput.watch = scopifyMethods(dialogComponentInput.watch || {}, component); dialogComponentInput.computed = scopifyMethods(dialogComponentInput.computed || {}, component); dialogComponentInput.methods = scopifyMethods(dialogComponentInput.methods || {}, component); return preData; }, watch: (dialogComponentInput.watch || {}), computed: (dialogComponentInput.computed || {}), methods: { getValue() { this.$trace(`lsw-dialogs.[${componentId}].methods.getValue`, []); return JSON.parse(JSON.stringify(this.value)); }, accept(solution = undefined, ...args) { this.$trace(`lsw-dialogs.[${componentId}].methods.accept`, [solution, ...args]); if (solution instanceof Event) { return this.$dialogs.resolve(id, this.getValue()).close(id); } return this.$dialogs.resolve(id, typeof solution !== "undefined" ? solution : this.getValue()).close(id); }, cancel(...args) { this.$trace("lsw-dialogs.[${componentId}].methods.cancel", args); return this.$dialogs.resolve(id, -1).close(id); }, abort(error = undefined, ...args) { this.$trace(`lsw-dialogs.[${componentId}].methods.abort`, [error, ...args]); if (solution instanceof Event) { return this.$dialogs.reject(id, new Error("Aborted dialog error")).close(id); } return this.$dialogs.reject(id, error).close(id); }, close(...args) { this.$trace(`lsw-dialogs.[${componentId}].methods.close`, args); return this.$dialogs.resolve(id, -2).close(id); }, ...(dialogComponentInput.methods || {}) } }); Define_component: { Vue.component(dialogComponent.name, dialogComponent); } // 1) Este es para el this.$dialogs: const dialogDefinition = Object.assign({}, { ...parametricObject, id, title, name: dialogComponent.name, component: dialogComponent, priority, minimized: false, parentId, created_at, promiser: Promise.withResolvers(), }); const dialogInstance = new Dialog(dialogDefinition); // console.log("Definición final del dialogo", dialogInstance); Define_dialog: { this.opened = Object.assign({}, this.opened, { [id]: dialogInstance }); } if (typeof this.hookOnOpen === "function") { this.hookOnOpen(this.opened[id], id, this); } return this.opened[id].promiser.promise; }, resolve(id, solution, ...args) { this.$trace("lsw-dialogs.methods.resolve", [id, solution, ...args]); if (typeof id !== "string") { throw new Error("Required parameter «id» (argument:1) to be a string on «LswDialogs.resolve»"); } if (!(id in this.opened)) { throw new Error(`Cannot resolve dialog «${id}» because it is not opened on «LswDialogs.resolve»`); } this.opened[id].promiser.resolve(solution); return { close: () => this.close(id) }; }, reject(id, error, ...args) { this.$trace("lsw-dialogs.methods.reject", [id, error, ...args]); if (typeof id !== "string") { throw new Error("Required parameter «id» (argument:1) to be a string on «LswDialogs.reject»"); } if (!(id in this.opened)) { throw new Error(`Cannot reject dialog «${id}» because it is not opened on «LswDialogs.reject»`); } this.opened[id].promiser.reject(error); return { close: () => this.close(id) }; }, close(id, ...args) { this.$trace("lsw-dialogs.methods.close", [id, ...args]); if (typeof id !== "string") { throw new Error("Required parameter «id» (argument:1) to be a string on «LswDialogs.close»"); } if (!(id in this.opened)) { throw new Error(`Cannot close dialog «${id}» because it is not opened on «LswDialogs.close»`); } let promiseOfDialog = undefined; Undefine_component: { const dialogName = Dialog.fromIdToComponentName(id); delete Vue.options.components[dialogName]; } Undefine_dialog: { Solve_promise_if_not_already: { if (this.opened[id].promiser.promise.state === "pending") { this.opened[id].promiser.resolve(-3); } } promiseOfDialog = this.opened[id].promiser.promise; delete this.opened[id]; this.opened = Object.assign({}, this.opened); } if (typeof this.hookOnClose === "function") { this.hookOnClose(id, this); } return promiseOfDialog; // this.$forceUpdate(true); }, minimize(id, ...args) { this.$trace("lsw-dialogs.methods.minimize", [id, ...args]); if (typeof id !== "string") { throw new Error("Required parameter «id» (argument:1) to be a string on «LswDialogs.minimize»"); } if (!(id in this.opened)) { throw new Error(`Cannot minimize dialog «${id}» because it is not opened on «LswDialogs.minimize»`); } this.opened[id].minimized = true; this._refreshMinimizedLength(this.opened); }, minimizeAll() { this.$trace("lsw-dialogs.methods.minimizeAll"); for(let id in this.opened) { this.opened[id].minimized = true; } Also_main_tab_if_exists: { const windowsViewer = this?.$lsw?.windowsViewer; if(windowsViewer) { windowsViewer.hide(); } } this._refreshMinimizedLength(this.opened); }, maximize(id, ...args) { this.$trace("lsw-dialogs.methods.maximize", [id, ...args]); if (typeof id !== "string") { throw new Error("Required parameter «id» (argument:1) to be a string on «LswDialogs.maximize»"); } if (!(id in this.opened)) { console.log(this.opened); console.log(id); console.log(Object.keys(this.opened)[0] === id); throw new Error(`Cannot maximize dialog «${id}» because it is not opened on «LswDialogs.maximize»`); } Iterating_dialogs: for (let dialogId in this.opened) { if (id === dialogId) { continue Iterating_dialogs; } const dialogData = this.opened[dialogId]; const currentPriority = parseInt(dialogData.priority); this.opened[dialogId].priority = currentPriority - 1; } this.opened[id].priority = 500; this.opened[id].minimized = false; this._refreshMinimizedLength(); }, _refreshMinimizedLength(newValue = this.opened, ...args) { this.$trace("lsw-dialogs.methods._refreshMinimizedLength", []); this.notMinimizedLength = Object.keys(newValue).reduce((out, k) => { const v = newValue[k]; if (v.minimized === false) { out++; } return out; }, 0); this.$forceUpdate(true); }, goHome(...args) { this.$trace("lsw-dialogs.methods.goHome", []); this.$window.LswWindows.show(); }, onOpen(callback, ...args) { this.$trace("lsw-dialogs.methods.onOpen", []); this.hookOnOpen = callback; }, onClose(callback, ...args) { this.$trace("lsw-dialogs.methods.onClose", []); this.hookOnClose = callback; } }, mounted(...args) { this.$trace("lsw-dialogs.mounted", []); if(Vue.prototype.$dialogs) { throw new Error("Cannot install «lsw-dialogs» as global on «Vue.prototype.$dialogs» because it is another instance mounted on «LswDialogs.mounted»"); } Vue.prototype.$dialogs = this; Vue.prototype.$lsw.dialogs = this; window.LswDialogs = this; console.log("[*] LswDialogs mounted."); } });
Vue.component("LswErrorBox", { template: $template, props: { error: { type: [Object, Boolean], default: () => false, }, onClearError: { type: [Function, Boolean], default: () => false, }, context: { type: String, default: () => {}, } }, data() { this.$trace("lsw-error-box.data"); return { isShowingTrace: false, }; }, methods: { toggleTrace() { this.$trace("lsw-error-box.methods.toggleTrace"); this.isShowingTrace = !this.isShowingTrace; }, clearError() { this.$trace("lsw-error-box.methods.clearError"); this.onClearError(); } }, watch: {}, mounted() { try { this.$trace("lsw-error-box.mounted"); } catch (error) { console.log(error); } } });
Vue.component("LswEventTracker", { template: $template, props: { }, data() { this.$trace("lsw-event-tracker.data"); return { isLoaded: false, isSearching: true, allTrackables: [], trackables: false, searchText: "", digestTimeout: 1000, digestTimeoutId: undefined, }; }, methods: { async loadTrackables() { this.$trace("lsw-event-tracker.methods.loadTrackables"); this.isLoaded = false; try { let trackableIds = undefined; Get_trackables: { trackableIds = await this.$lsw.fs.evaluateAsDotenvListFileOrReturn("/kernel/settings/trackables.env", []); } let trackedData = {}; Count_trackables: { const accionesTrackeadas = await this.$lsw.database.selectMany("Accion", it => { return trackableIds.indexOf(it.en_concepto) !== -1; }); for(let indexTrackables=0; indexTrackables<trackableIds.length; indexTrackables++) { const trackableId = trackableIds[indexTrackables]; trackedData[trackableId] = []; for(let indexTrackeadas=0; indexTrackeadas<accionesTrackeadas.length; indexTrackeadas++) { const accion = accionesTrackeadas[indexTrackeadas]; if(accion.en_concepto === trackableId) { trackedData[trackableId].push(accion); } } } } this.allTrackables = trackedData; this.digestOutput(); } catch (error) { this.$lsw.toasts.sendError(error); } }, digestDelayed() { this.$trace("lsw-event-tracker.methods.digestDelayed"); clearTimeout(this.digestTimeoutId); setTimeout(() => { this.digestOutput(); }, this.digestTimeout); }, digestOutput() { this.$trace("lsw-event-tracker.methods.digestOutput"); clearTimeout(this.digestTimeoutId); this.isSearching = true; setTimeout(() => { if(this.searchText.trim() === "") { this.trackables = this.allTrackables; } else { this.trackables = LswUtils.filterObject(this.allTrackables, (key, val) => { return key.toLowerCase().indexOf(this.searchText.toLowerCase()) !== -1; }); } this.isLoaded = true; this.isSearching = false; }, 0); }, editTrackables() { this.$trace("lsw-event-tracker.methods.editTrackables"); this.$lsw.dialogs.open({ title: "Editar trackeables", template: ` <lsw-filesystem-explorer opened-by="/kernel/settings/trackables.env" :absolute-layout="true" /> `, }); }, async deleteTrackable(trackableId) { this.$trace("lsw-event-tracker.methods.deleteTrackable"); const eventos = await this.$lsw.database.selectMany("Accion", it => { return (it.tiene_estado === "trackeada") && (it.en_concepto === trackableId); }); const eventosOrdenados = eventos.sort((e1, e2) => { return e1.tiene_inicio > e2.tiene_inicio ? -1 : 1; }); const evento = eventosOrdenados[0] || false; if(!evento) { return; } const respuesta = await this.$lsw.dialogs.open({ title: "Eliminar último evento trackeada", template: ` <div class="pad_1"> <div>¿Seguro que quieres eliminar el evento trackeada?</div> <pre class="codeblock">{{ evento }}</pre> <hr /> <div class="flex_row centered"> <div class="flex_100"></div> <div class="flex_1 pad_left_1"> <button class="supermini danger_button" v-on:click="() => accept(true)">Eliminar</button> </div> <div class="flex_1 pad_left_1"> <button class="supermini" v-on:click="cancel">Cancelar</button> </div> </div> </div> `, factory: { data: { evento } } }); if(respuesta !== true) { console.log(respuesta); return; } await this.$lsw.database.delete("Accion", evento.id); this.$lsw.toasts.send({ title: "Evento eliminado correctamente", text: "El último evento asociado fue eliminado", }); this.loadTrackables(); }, async addTrackableWithComment(trackableId) { this.$trace("lsw-event-tracker.methods.addTrackableWithComment"); const comentario = await this.$lsw.dialogs.open({ title: "Comentario adjunto a evento", template: ` <div class="pad_1"> <div class="pad_bottom_1"> Comentario a adjuntar en el evento: </div> <div class="pad_bottom_1"> <textarea class="width_100" v-model="value" spellcheck="false" /> </div> <hr /> <div class="flex_row centered"> <div class="flex_100"></div> <div class="flex_1 pad_left_1"> <button class="supermini" v-on:click="accept">Añadir trackeo</button> </div> <div class="flex_1 pad_left_1"> <button class="supermini" v-on:click="cancel">Cancelar</button> </div> </div> </div> ` }); if(typeof comentario !== "string") return; await this.$lsw.database.insert("Accion", { en_concepto: trackableId, tiene_estado: "trackeada", tiene_inicio: LswTimer.utils.fromDateToDatestring(new Date()), tiene_duracion: "1min", tiene_parametros: "", tiene_resultados: "", tiene_comentarios: comentario, }); this.$lsw.toasts.send({ title: "Evento añadido correctamente", text: "Con comentario adjunto", }); this.loadTrackables(); }, async addTrackable(trackableId) { this.$trace("lsw-event-tracker.methods.addTrackable"); await this.$lsw.database.insert("Accion", { en_concepto: trackableId, tiene_estado: "trackeada", tiene_inicio: LswTimer.utils.fromDateToDatestring(new Date()), tiene_duracion: "1min", tiene_parametros: "", tiene_resultados: "", tiene_comentarios: "", }); this.loadTrackables(); this.$lsw.toasts.send({ title: "Evento añadido correctamente", text: "Sin comentario adjunto", }); }, async insertTrackable() { this.$trace("lsw-event-tracker.methods.insertTrackable"); const trackableId = this.searchText; if(trackableId.trim() === "") { return; } const trackableIds = await this.$lsw.fs.evaluateAsDotenvListFileOrReturn("/kernel/settings/trackables.env", []); const pos = trackableIds.indexOf(trackableId); if(pos !== -1) { this.$lsw.toasts.send({ title: "Este trackable ya existe", text: "No se insertó porque ya existe.", }); } else { const previousContent = await this.$lsw.fs.read_file("/kernel/settings/trackables.env"); const lastContent = previousContent.trim() + "\n" + trackableId; await this.$lsw.fs.write_file("/kernel/settings/trackables.env", lastContent); this.$lsw.toasts.send({ title: "Trackable insertado correctamente", text: "", }); this.loadTrackables(); } }, clearSearchText() { this.$trace("lsw-event-tracker.watch.clearSearchText"); this.searchText = ""; this.digestOutput(); } }, watch: { searchText() { this.$trace("lsw-event-tracker.watch.searchText"); this.digestDelayed(); } }, mounted() { this.$trace("lsw-event-tracker.mounted"); this.loadTrackables(); }, unmounted() { this.$trace("lsw-event-tracker.unmounted"); } });
Vue.component("LswGoalsRecordsViewer", { template: $template, props: { }, data() { this.$trace("lsw-goals-records-viewer.data"); return { isLoaded: false, isShowingGoals: true, records: false, selectedGoals: [], availableGoals: [], }; }, methods: { async loadRecords() { this.$trace("lsw-goals-records-viewer.methods.loadRecords"); this.isLoaded = false; try { const recordsFiles = await this.$lsw.fs.read_directory("/kernel/goals/records"); const recordDays = Object.keys(recordsFiles).map(file => file.replace(/\.json$/g, "")); const allRecords = []; const allGoalConcepts = []; const errors = []; for (let index = 0; index < recordDays.length; index++) { const recordDay = recordDays[index]; try { const recordPath = `/kernel/goals/records/${recordDay}.json`; const recordJson = await this.$lsw.fs.read_file(recordPath); const recordData = JSON.parse(recordJson); allRecords.push(recordData); for(let indexGoals=0; indexGoals<recordData.goals.length; indexGoals++) { const goal = recordData.goals[indexGoals]; try { const goalId = goal.originalConcept; const goalPos = allGoalConcepts.indexOf(goalId); if(goalPos === -1) { allGoalConcepts.push(goalId); } } catch (error) { // @BADLUCK } } } catch (error) { console.log(error); error.message = `(${recordDay}) ` + error.message; error.fileOrigin = recordDay; errors.push(error); } } if (errors.length) { console.log(errors); this.$lsw.toasts.send({ title: `Hubo ${errors.length} errores cargando los records`, text: "Errores en: " + errors.map(err => err.fileOrigin).join(", "), }); return; } this.records = allRecords; this.availableGoals = allGoalConcepts; } catch (error) { console.log(error); } this.isLoaded = true; }, toggleGoals() { this.$trace("lsw-goals-records-viewer.methods.toggleGoals"); this.isShowingGoals = !this.isShowingGoals; }, toggleAllSelectedGoals() { this.$trace("lsw-goals-records-viewer.methods.toggleAllSelectedGoals"); if(this.selectedGoals.length) { this.selectedGoals = []; } else { this.selectedGoals = [].concat(this.availableGoals); } } }, watch: {}, mounted() { this.$trace("lsw-goals-records-viewer.mounted"); this.loadRecords(); }, unmounted() { this.$trace("lsw-goals-records-viewer.unmounted"); } });
Vue.component("LswGoalsViewer", { template: $template, props: { onClose: { type: [Function, Boolean], default: false, }, onRefresh: { type: [Function, Boolean], default: false, }, dayToAnalize: { type: [Boolean, Date], default: () => new Date(), } }, data() { this.$trace("lsw-goals-viewer.data"); const solverSymbols = ['👍', '✔️', '😃'] const penderSymbols = ['🌵', '❌', '🥶']; const randomIndex = LswRandomizer.getRandomIntegerBetween(0, penderSymbols.length-1); return { isLoaded: false, isLoadingGoals: false, isFiltering: "none", isClicking: false, specifiedGoals: {}, interestingFields: { "colorMeaning": "Estado actual", "filled": "Completado", "missing": "Faltante", "solved": "Resuelto", // "originalConcept": "Concepto", // "originalCondition": "Condición", // "originalUrgency": "Urgencia", "type": "Tipo", "expectedAs": "Formato", "expectedDuration": "Duración esperada", // "expectedAsAbbr": "Formato abreviado", "currentDuration": "Duración actual", "missingDuration": "Duración faltante", "expectedTimes": "Veces esperadas", "currentTimes": "Veces actuales", "missingTimes": "Veces faltantes", // "currentDurationInms": "Duración en ms actual", // "expectedDurationInms": "Duración esperada en ms", // "filledAsint": "Llenado como número", // "missingDurationInms": "Duración faltante en ms", // "missingAsint": "Faltante como número", // "solvable": "Resolvible", // "color": "color", // "urgency": "Urgencia" "originalLine": "Origen", }, symbolForSolved: solverSymbols[randomIndex], symbolForPending: penderSymbols[randomIndex], selectedGoal: false, report: [], summary: false, }; }, methods: { async selectGoal(goal) { this.$trace("lsw-goals-viewer.methods.selectGoal"); if(this.selectedGoal === goal) { this.selectedGoal = undefined; } else { this.selectedGoal = goal; } await this.loadGoalSpecification(goal); }, selectFilter(id) { this.$trace("lsw-goals-viewer.methods.selectFilter"); this.isFiltering = id; }, async loadGoalSpecification(goal) { this.$trace("lsw-goals-viewer.methods.loadGoalSpecification"); try { this.isLoadingGoals = true; const filepath = "/kernel/goals/todos/" + goal.concept + ".md"; const filecontent = await this.$lsw.fs.read_file(filepath); const parsedContent = marked.parse(filecontent); this.specifiedGoals[goal.concept] = `<div class="markdown_texto">${parsedContent}</div>`; return parsedContent; } catch (error) { return false; } finally { this.isLoadingGoals = false; } }, passesFilter(goal) { this.$trace("lsw-goals-viewer.methods.passesFilter"); if(this.isFiltering === "none") { return true; } else if(this.isFiltering === "completed") { return goal.solved === true; } else { return goal.solved === false; } }, async loadGoals() { this.$trace("lsw-goals-viewer.methods.loadGoals"); this.isLoaded = false; this.report = await LswGoals.getGoalsReport(this.dayToAnalize); let resolved = 0; let failed = 0; for(let index=0; index<this.report.goals.length; index++) { const goal = this.report.goals[index]; if(goal.solved) { resolved++; } else { failed++; } } this.summary = { total: this.report.goals.length, resolved, failed, }; this.isLoaded = true; }, openGoalsFile() { this.$trace("lsw-goals-viewer.methods.openGoalsFile"); this.$dialogs.open({ title: "Editar objetivos", template: ` <div> <lsw-filesystem-explorer opened-by="/kernel/goals/goals.week" :absolute-layout="true" /> </div> ` }); }, openRecordsDirectory() { this.$trace("lsw-goals-viewer.methods.saveMoment"); this.$dialogs.open({ title: "Ver récords anteriores", template: ` <div> <lsw-filesystem-explorer opened-by="/kernel/goals/records" :absolute-layout="true" /> </div> ` }); }, openRecordsViewer() { this.$trace("lsw-goals-viewer.methods.openRecordsViewer"); this.$dialogs.open({ title: "Visualizar récords", template: ` <div class="pad_1"> <lsw-goals-records-viewer /> </div> ` }); }, openWeekPlanner() { this.$trace("lsw-goals-viewer.methods.openWeekPlanner"); this.$dialogs.open({ title: "Planificador de semana", template: ` <div class="pad_1"> <lsw-week-planner /> </div> ` }); }, async saveMoment() { this.$trace("lsw-goals-viewer.methods.saveMoment"); const dayUid = LswTimer.utils.fromDateToDatestring(new Date(), false, false, true).replace(/\/|\:/g, "-").replace(/ .*$/g, ""); const filepath = "/kernel/goals/records/" + dayUid + ".json"; const reportSnapshot = Object.assign({ date: LswTimer.utils.fromDateToDatestring(this.dayToAnalize || new Date()), }, this.report, {}); const filecontents = JSON.stringify(reportSnapshot, null, 2); await this.$lsw.fs.write_file(filepath, filecontents); this.$lsw.toasts.send({ title: "Estadísticas del día guardadas", text: `En: ${filepath}` }); }, getAbbrvWord(id) { return id === "min" ? "🔺" : "🔻"; return id === "min" ? "mínimo" : "máximo"; }, async editTodoOfGoal(goal) { this.$trace("lsw-goals-viewer.methods.editTodoOfGoal"); const goalFilepath = `/kernel/goals/todos/${goal.concept}.md`; const exists = await this.$lsw.fs.exists(goalFilepath); if(!exists) { await this.$lsw.fs.write_file(goalFilepath, ""); } this.$lsw.dialogs.open({ title: "Detallando objetivo", template: `<lsw-filesystem-explorer :opened-by="goalFilepath" :absolute-layout="true" />`, factory: { data: { goal, goalFilepath }, methods: { } } }); }, async importGoalsToDay() { this.$trace("lsw-goals-viewer.methods.importGoalsToDay"); const originalGoals = await Vue.prototype.$lsw.fs.evaluateAsWeekFileOrReturn("/kernel/goals/goals.week", []); const dayString = LswTimer.utils.fromDateToDatestring(this.dayToAnalize, true); const weekdayString = LswGoals.fromDateToWeekday(this.dayToAnalize); const goalsMatched = originalGoals.filter(goal => { if(goal.type !== "SET") { return false; } Filtro_de_goal_segun_dia: { const { from: goalBegin, to: goalEnd, type: goalType, concept: goalConcept, duration: goalDuration, hour: goalHour, minute: goalMinute, weekday: goalWeekday, } = goal; // No excede el «desde fecha». if(goalBegin !== "*") { if(dayString < goalBegin) { return false; } } // No excede el «hasta fecha» if(goalEnd !== "*") { if(dayString > goalEnd) { return false; } } // Coincide con el «dia de la semana» if(goalWeekday !== "*") { if(weekdayString !== goalWeekday) { return false; } } } return true; }); const goalsMissing = []; const accionesDia = await this.$lsw.database.select("Accion", acc => acc.tiene_inicio.startsWith(dayString)); Iterating_objetivos: for(let indexGoal=0; indexGoal<goalsMatched.length; indexGoal++) { const goalMatched = goalsMatched[indexGoal]; const { from: goalBegin, to: goalEnd, type: goalType, concept: goalConcept, duration: goalDuration, hour: goalHour, minute: goalMinute, weekday: goalWeekday, } = goalMatched; const presuntoInicio = dayString + " " + goalMatched.hour + ":" + goalMatched.minute; let missingAccion = { ast: goalMatched, acc: { en_concepto: goalConcept, tiene_estado: "pendiente", tiene_inicio: presuntoInicio, tiene_duracion: goalDuration || "1h", tiene_parametros: "[*semanal]", tiene_descripcion: "", tiene_comentarios: "", } }; Iterating_acciones: for(let indexAcc=0; indexAcc<accionesDia.length; indexAcc++) { const accionDia = accionesDia[indexAcc]; const matchesParameter = accionDia.tiene_parametros.indexOf("[*semanal]") !== -1; const matchesTime = accionDia.tiene_inicio.startsWith(presuntoInicio); const matchesConcept = accionDia.tiene_inicio.startsWith(presuntoInicio); if(matchesParameter && matchesTime && matchesConcept) { missingAccion = false; break Iterating_acciones; }; } if(missingAccion) { goalsMissing.push(missingAccion); } } if(!goalsMissing.length) { return this.$lsw.dialogs.open({ title: "No hay objetivos por importar", template: ` <div class="pad_1"> <div>No hay objetivos por importar actualmente.</div> </div> ` }); } const confirmation = await this.$lsw.dialogs.open({ title: "Importar objetivos a día", template: ` <div class="pad_1"> <div>¿Seguro que quieres importar los objetivos al día seleccionado?</div> <div class="pad_vertical_2">Se añadirán las siguientes {{ goalsToImport.length }} acciones:</div> <ul class="margin_vertical_0"> <li v-for="goal, goalIndex in goalsToImport" v-bind:key="'goal_' + goalIndex"> <div>{{ currentWeekday }}, {{ currentDay }} ➞ {{ goal.ast.hour }}:{{ goal.ast.minute }}@{{ goal.acc.en_concepto }}</div> </li> </ul> <hr /> <div class="flex_row centered"> <div class="flex_100"></div> <div class="flex_1"> <button class="supermini" v-on:click="() => accept(true)">Aceptar</button> </div> <div class="flex_1"> <button class="supermini" v-on:click="cancel">Cancelar</button> </div> </div> </div> `, factory: { data: { currentWeekday: LswGoals.fromDateToWeekday(this.dayToAnalize), currentDay: dayString, goalsToImport: goalsMissing } } }); if(confirmation !== true) { return; } await this.$lsw.database.insertMany("Accion", goalsMissing.map(goalMetadata => goalMetadata.acc)); this.$lsw.toasts.send({ title: `Se insertaron ${goalsMissing.length} acciones`, text: `Los objetivos generaron ${goalsMissing.length} acciones para el día seleccionado` }); this.updateCalendario(); }, updateCalendario() { this.$trace("lsw-goals-viewer.methods.updateCalendario"); try { LswDom.findVue(".lsw_agenda").reloadDateTasks(); } catch (error) { // @BADLUCK } } }, watch: {}, mounted() { this.$trace("lsw-goals-viewer.mounted"); this.loadGoals(); }, unmounted() { this.$trace("lsw-goals-viewer.unmounted"); } });
Vue.component("LswGoalsViewer", { template: $template, props: { onClose: { type: [Function, Boolean], default: false, }, onRefresh: { type: [Function, Boolean], default: false, } }, data() { this.$trace("lsw-goals-viewer.data"); return { isLoaded: false, goalsData: false, isShowingBars: true, completedGoalsCounter: 0, missingGoalsCounter: 0, }; }, methods: { async loadGoals() { this.$trace("lsw-goals-viewer.methods.loadGoals"); const allGoals = await LswGoals.loadGoals(); console.log("allGoals", allGoals); this.goalsData = allGoals; for(let indexGoal=0; indexGoal<allGoals.length; indexGoal++) { const goal = allGoals[indexGoal]; const it = this.expandGoal(goal); try { if(it["tiene el"] >= 100) { this.completedGoalsCounter++; } else { this.missingGoalsCounter++; } } catch (error) { console.log(error); } } this.isLoaded = true; }, toggleBars() { this.$trace("lsw-goals-viewer.methods.emitClose"); const graphComp = this.$refs.barsGraph; graphComp.selectPropertyViewByName("falta el"); }, emitClose() { this.$trace("lsw-goals-viewer.methods.emitClose"); if(typeof this.onClose === "function") { this.onClose(this); } }, emitRefresh() { this.$trace("lsw-goals-viewer.methods.emitRefresh"); if(typeof this.onRefresh === "function") { this.onRefresh(this); } }, openGoalsDirectory() { this.$trace("lsw-goals-viewer.methods.openGoalsDirectory"); this.$lsw.dialogs.open({ title: "Directorio de objetivos", template: ` <lsw-filesystem-explorer :absolute-layout="true" opened-by="/kernel/goals/goals.week" /> ` }); }, expandGoal(goal) { this.$trace("lsw-goals-viewer.methods.expandGoal"); return Object.assign({}, goal, { "tiene el": goal.porcentaje, "falta el": 100-goal.porcentaje }); }, sortGoals(g1, g2) { this.$trace("lsw-goals-viewer.methods.sortGoals"); const u1 = g1.urgencia || 0; const u2 = g2.urgencia || 0; const c1 = g1["tiene el"] || 0; const c2 = g2["tiene el"] || 0; const g1over = c1 > 100; const g2over = c2 > 100; if(g2over) return -1; if(g1over) return 1; if(u1 > u2) return -1; if(u1 < u2) return 1; if(c1 < c2) return -1; if(c1 > c2) return 1; return 0; }, adaptSample(goalsData) { this.$trace("lsw-goals-viewer.methods.adaptSample"); return goalsData.map(it => { const expandedGoal = this.expandGoal(it); return expandedGoal; }).sort((...args) => { return this.sortGoals(...args); }); } }, watch: {}, mounted() { this.$trace("lsw-goals-viewer.mounted"); this.loadGoals(); }, unmounted() { this.$trace("lsw-goals-viewer.unmounted"); } });
Vue.component("LswHomepage", { template: $template, props: { appsThrower: { type: Object, required: true, } }, data() { this.$trace("lsw-homepage.data"); return { isLoaded: false, ownApps: {}, lastAppliedFilter: false, filterSearchText: "", filteredApps: {}, systemApps: [{ label: "📦 Base de datos", event: () => this.abrirApp("base de datos"), }, { label: "📂 Sistema de ficheros", event: () => this.abrirApp("sistema de ficheros"), }, { label: "💣 Binarios", event: () => this.abrirApp("binarios"), }, { label: "📆 Calendario", event: () => this.abrirApp("calendario"), }, { label: "⬅️🕔 Tareas anteriores", event: () => this.abrirApp("antes"), }, { label: "🕔➡️ Tareas posteriores", event: () => this.abrirApp("despues"), }, { label: "💬 Notas", event: () => this.abrirApp("notas"), }, { label: "💬➕ Nueva nota", event: () => this.abrirApp("nueva nota"), }, { label: "🔬 Enciclopedia", event: () => this.abrirApp("enciclopedia"), }, { label: "🔬➕ Nuevo artículo", event: () => this.abrirApp("nuevo articulo"), }, { label: "🪲 Inspector de JS", event: () => this.abrirApp("js inspector"), }, { label: "💻 Consola de JS", event: () => this.abrirApp("js consola"), }, { label: "💻 Consola de SQL", event: () => this.abrirApp("sqlite-console"), }, { label: "✅ Tests de aplicación", event: () => this.abrirApp("app tests"), }, { label: "🔧 Configuraciones", event: () => this.abrirApp("configuraciones"), }, { label: "✨ Nueva feature", event: () => this.abrirApp("nueva feature"), }] }; }, methods: { registerApp(appName, appData, force = false, silence = false) { this.$trace("lsw-homepage.methods.registerApp"); try { if ((appName in this.ownApps) && !force) { throw new Error(`App «${appName}» is already registered`); } Validate_app_data: { const $ensureAppData = $ensure({ [appName]: appData }, 1).type("object"); $ensureAppData.to.have.keys([ "label", "event", ]); } this.ownApps[appName] = appData; } catch (error) { if (!silence) { this.$lsw.toasts.showError(error); } } }, filterApps() { this.$trace("lsw-homepage.methods.filterApps"); const s = this.filterSearchText.toLowerCase(); if (s.trim() === "") { this.lastAppliedFilter = false; return this.filteredApps = Object.assign({}, this.ownApps); } this.filteredApps = {}; for (let appName in this.ownApps) { const appData = this.ownApps[appName]; const pos = JSON.stringify([appName, appData]).toLowerCase().indexOf(s); if (pos !== -1) { this.filteredApps[appName] = appData; } } this.lastAppliedFilter = s; this.isLoaded = true; }, async loadOwnApps() { this.$trace("lsw-homepage.methods.loadOwnApps"); this.ownApps = {}; System_apps: { for (let indexApp = 0; indexApp < this.systemApps.length; indexApp++) { const app = this.systemApps[indexApp]; try { this.registerApp(app.label, app); } catch (error) { console.log(error); } } } Custom_apps: { const appsFilesMap = await this.$lsw.fs.read_directory("/kernel/apps"); const appsFiles = Object.keys(appsFilesMap); for (let indexApp = 0; indexApp < appsFiles.length; indexApp++) { const appName = appsFiles[indexApp]; try { const appData = await lsw.fs.evaluateAsJavascriptFileOrReturn("/kernel/apps/" + appName + "/load.js", false); this.registerApp(appName, appData); } catch (error) { console.log(error); } } } this.filterApps(); }, openAppsDirectory() { this.$trace("lsw-homepage.methods.openAppsDirectory"); this.$lsw.dialogs.open({ title: "Directorio de apps", template: `<lsw-filesystem-explorer opened-by="/kernel/apps" />`, }); }, async abrirApp(appId) { this.$trace("lsw-homepage.methods.abrirApp"); this.$lsw.toasts.send({ title: "Yendo a: " + appId, }); this.appsThrower.selectApplication(appId); } }, watch: {}, mounted() { this.$trace("lsw-homepage.mounted"); this.loadOwnApps(); }, unmounted() { this.$trace("lsw-homepage.unmounted"); } });
Vue.component("LswInlineTagsPicker", { template: $template, props: { from: { type: Object, default: () => [] }, field: { type: String, required: true, }, onChoose: { type: [Function, Boolean], default: () => false, } }, data() { this.$trace("lsw-inline-tags-picker.data"); return { fromData: this.from, digestedData: [], searchText: "", selectedRow: false, }; }, methods: { digestSearch() { this.$trace("lsw-inline-tags-picker.methods.digestSearch"); if (this.searchText.trim() === "") { this.digestedData = this.fromData; return; } this.digestedData = this.fromData.filter(row => { return JSON.stringify(row).toLowerCase().indexOf(this.searchText.toLowerCase()) !== -1; }); }, selectRow(row) { this.$trace("lsw-inline-tags-picker.methods.selectRow"); this.selectedRow = row; if (typeof this.onChoose === "function") { this.onChoose(row, this); } } }, watch: {}, async mounted() { try { this.$trace("lsw-inline-tags-picker.mounted"); } catch (error) { console.log(error); } } });
Vue.component("LswMarkdownViewer", { template: $template, props: { source: { type: String, required: true, }, activateEjs: { type: Boolean, default: () => false, }, }, data() { this.$trace("lsw-markdown-viewer.data"); return { parsingEjsError: false, parsingMarkdownError: false, output: false, }; }, methods: { async buildSource() { this.$trace("lsw-markdown-viewer.methods.buildSource"); await LswLazyLoads.loadEjs(); let sourceTransformed = this.source; Render_ejs: { if (this.activateEjs) { try { sourceTransformed = await ejs.render(sourceTransformed, { component: this }, { async: true, }); } catch (error) { console.log(error); this.parsingEjsError = error; throw error; } } } Render_markdown: { try { sourceTransformed = await marked.parse(sourceTransformed); } catch (error) { console.log(error); this.parsingMarkdownError = error; throw error; } } this.output = sourceTransformed; } }, watch: {}, async mounted() { try { this.$trace("lsw-markdown-viewer.mounted"); await this.buildSource(); } catch (error) { console.log(error); } } });
Vue.component("LswNuevaFeature", { template: $template, props: {}, data() { this.$trace("lsw-nueva-feature.data"); return { }; }, methods: { async load() { } }, watch: {}, async mounted() { try { this.$trace("lsw-nueva-feature.mounted"); this.load(); } catch (error) { console.log(error); } } });
Vue.component("LswPageDatabases", { template: $template, props: { databaseExplorer: { type: Object, required: true }, args: { type: Object, required: true }, }, data() { return { databases: [], databasesForTable: false, breadcrumb: [{ page: "LswPageDatabases", name: "Databases", args: {}, current: true }], } }, methods: { openDatabase(name) { this.databaseExplorer.selectPage("LswPageTables", { database: name }); } }, watch: { databases(value) { AdaptingForTable: { const databasesForTable = []; if (typeof value !== "object") { break AdaptingForTable; } const databaseIds = Object.keys(value); for(let indexDatabase=0; indexDatabase<databaseIds.length; indexDatabase++) { const databaseId = databaseIds[indexDatabase]; const databaseObject = value[databaseId]; } this.databasesForTable = databasesForTable; } } }, async mounted() { this.databases = await LswDatabaseAdapter.listDatabases(); Filter_by_entity_schema_matched_db_names: { $lswSchema } }, unmounted() { } });
Vue.component("LswPageRow", { template: $template, props: { databaseExplorer: { type: Object, required: true }, args: { type: Object, required: true }, }, data() { this.$trace("lsw-page-row.data", arguments); $ensure(this.args.database).type("string"); $ensure(this.args.table).type("string"); $ensure(this.args.rowId).type("number"); return { isLoaded: false, breadcrumb: [{ page: "LswPageTables", name: this.args.database, args: { database: this.args.database } }, { page: "LswPageRows", name: this.args.table, args: { database: this.args.database, table: this.args.table }, }, { page: "LswPageRow", name: (this.args.rowId === -1) ? '#new' : ("#" + this.args.rowId), args: { database: this.args.database, table: this.args.table, rowId: this.args.rowId }, current: true }], database: this.args.database, table: this.args.table, rowId: this.args.rowId, connection: undefined, row: false, } }, methods: { goBack() { this.$trace("lsw-page-row.methods.goBack", arguments); return this.databaseExplorer.selectPage("LswPageRows", { database: this.database, table: this.table }); }, async loadRow() { this.$trace("lsw-page-row.methods.loadRow", arguments); try { if (this.rowId === -1) { return false; } this.connection = this.connection ?? new LswDatabaseAdapter(this.database); await this.connection.open(); const matches = await this.connection.select(this.table, it => it.id === this.rowId); this.row = matches[0]; } catch (error) { console.log("Error loading row:", error); this.$lsw.toasts.showError(error); throw error; } finally { this.row = false; } }, async upsertRow(value) { this.$trace("lsw-page-row.methods.upsertRow", arguments); try { const existsRow = this.rowId || ((typeof (this.row) === "object") && (typeof (this.row.id) === "number") && (this.row.id !== -1)); let id = this.rowId || this.row.id; const operation = (existsRow && (id !== -1)) ? "update" : "insert"; if (operation === "insert") { id = await this.$lsw.database.insert(this.table, value); } else { await this.$lsw.database.update(this.table, id, value); } this.$lsw.toasts.send({ title: `Nueva ${operation === 'insert' ? 'inserción' : 'actualización'}`, text: `El registro #${id} de «${this.table}» fue ${operation === 'insert' ? 'insertado' : 'actualizado'} correctamente.` }); if (operation === "insert") { this.databaseExplorer.selectPage("LswPageRow", { database: this.database, table: this.table, rowId: id }); } else { // @OK. } } catch (error) { console.log(error); this.$lsw.toasts.showError(error); } } }, async mounted() { this.$trace("lsw-page-row.mounted", arguments); try { await this.loadRow(); } catch (error) { console.log("Error loading row:", error); throw error; } finally { this.isLoaded = true; } }, unmounted() { this.$trace("lsw-page-row.unmounted", arguments); this.connection.close(); } });
Vue.component("LswPageRows", { template: $template, props: { databaseExplorer: { type: Object, required: true }, args: { type: Object, required: true }, }, data() { this.$trace("lsw-page-rows.data", []); $ensure({ "args": this.args }, 1).type("object"); $ensure({ "args.database": this.args.database }, 1).type("string"); $ensure({ "args.table": this.args.table }, 1).type("string"); return { breadcrumb: [{ page: "LswPageTables", name: this.args.database, args: { database: this.args.database } }, { page: "LswPageRows", name: this.args.table, args: { database: this.args.database, table: this.args.table }, current: true }], database: this.args.database, table: this.args.table, rows: undefined, connection: undefined, } }, methods: { goBack() { this.$trace("lsw-page-rows.methods.goBack", arguments); return this.databaseExplorer.selectPage("LswPageTables", { database: this.database, }); }, getTableId() { if(this.args.tableStorageId) { return this.args.tableStorageId + '.json'; } else { return 'lsw-database-ui.page-rows.' + this.args.database + '.' + this.args.table + '.json'; } }, async loadRows() { this.$trace("lsw-page-rows.methods.loadRows", arguments); this.connection = this.connection ?? new LswDatabaseAdapter(this.database); await this.connection.open(); const filterCallback = (this.args.filterCallback && typeof(this.args.filterCallback) === "function") ? this.args.filterCallback : () => true; const selection = await this.connection.select(this.table, filterCallback); this.rows = selection; return selection; }, openRow(rowId) { this.$trace("lsw-page-rows.methods.openRow", arguments); return this.databaseExplorer.selectPage("LswPageRow", { database: this.database, table: this.table, rowId: rowId }); } }, mounted() { this.$trace("lsw-page-rows.mounted", arguments); this.loadRows(); }, unmounted() { this.$trace("lsw-page-rows.unmounted", arguments); this.connection.close(); } });
Vue.component("LswPageSchema", { template: $template, props: {}, data() { return { } }, methods: { }, mounted() { }, unmounted() { } });
Vue.component("LswPageTables", { template: $template, props: { databaseExplorer: { type: Object, required: true }, args: { type: Object, required: true }, }, data() { const ensureArgs = $ensure(this.args).type("object"); ensureArgs.to.have.key("database").its("database").type("string"); return { breadcrumb: [{ page: "LswPageTables", name: this.args.database, args: { database: this.args.database }, current: true }], database: this.args.database, tables: false, tablesAsList: false, isShowingSchema: false, } }, methods: { async loadDatabase() { const db = await LswDatabaseAdapter.getSchema(this.database); this.tables = db; console.log(`[*] Tables of database ${this.args.database}:`, db); }, openTable(table) { $ensure({ table }, 1).type("string"); return this.databaseExplorer.selectPage("LswPageRows", { database: this.database, table: table }); }, toggleSchema() { this.isShowingSchema = !this.isShowingSchema; } }, watch: { tables(value) { const tablesAsList = []; const tableIds = Object.keys(value); for(let index=0; index<tableIds.length; index++) { const tableId = tableIds[index]; const tableData = value[tableId]; tablesAsList.push({ id: tableId, name: tableId, ...tableData, indexes: tableData.indexes ? tableData.indexes.map(ind => ind.name) : [] }); } this.tablesAsList = tablesAsList; }, }, computed: { tablesAsIdsList() { return Object.keys(this.tablesAsList || {}); } }, mounted() { this.loadDatabase(); }, unmounted() { } });
Vue.component("LswSearchReplacer", { template: $template, props: { input: { type: String, default: () => false, }, initialSearch: { type: String, default: () => "", }, initialReplace: { type: String, default: () => "", }, onAccept: { type: Function, default: () => {}, }, onCancel: { type: Function, default: () => {}, }, onFinally: { type: Function, default: () => {}, }, }, data() { this.$trace("lsw-search-replacer.data"); return { currentInput: this.input, currentMatch: false, search: this.initialSearch || "", searchAsRegexp: false, replace: this.initialReplace || "", }; }, methods: { toggleRegexpMode() { this.$trace("lsw-search-replacer.methods.toggleRegexpMode"); this.searchAsRegexp = !this.searchAsRegexp; }, accept() { this.$trace("lsw-search-replacer.methods.accept"); this.onAccept(this.currentInput, this); this.onFinally(this.currentInput, this); }, cancel() { this.$trace("lsw-search-replacer.methods.cancel"); this.onCancel(this.currentInput, this); this.onFinally(this.currentInput, this); }, illuminateMatches() { this.$trace("lsw-search-replacer.methods.illuminateMatches"); if(!this.searchAsRegexp) { } }, replaceAllMatches() { this.$trace("lsw-search-replacer.methods.replaceAllMatches"); if(this.searchAsRegexp) { const regexp = new RegExp(this.search, "g"); this.currentInput = this.currentInput.replaceAll(regexp, this.replace); } else { this.currentInput = this.currentInput.replaceAll(this.search, this.replace); } }, }, watch: {}, mounted() { this.$trace("lsw-search-replacer.mounted"); }, unmounted() { this.$trace("lsw-search-replacer.unmounted"); } });
Vue.component("LswSourceable", { template: $template, props: { fixedId: { type: [String, Boolean], default: () => false, }, source: { type: [String, Boolean], default: () => false, }, composition: { type: Object, default: () => ({}) } }, data() { this.$trace("lsw-sourceable.data"); const reflectedId = this.fixedId || LswRandomizer.getRandomString(10); return { isLoaded: false, componentId: "lsw-sourceable-" + reflectedId, }; }, methods: { loadReflection() { this.$trace("lsw-sourceable.methods.loadReflection"); const that = this; const source = this.source || this.$slots.default; Vue.component(this.componentId, { template: "<div>" + source + "</div>", ...this.composition, }); this.isLoaded = true; }, unloadReflection() { this.$trace("lsw-sourceable.methods.loadReflection"); delete Vue.options.components[this.componentId]; } }, watch: {}, mounted() { this.$trace("lsw-sourceable.mounted"); this.loadReflection(); }, unmounted() { this.$trace("lsw-sourceable.unmounted"); this.unloadReflection(); } });
const defaultScript = ` SELECT * FROM sqlite_master; `; Vue.component("LswSqliteConsole", { template: $template, props: { }, data() { this.$trace("lsw-sqlite-console.data"); return { selectedSection: "js", // "js", "sql", "data" code: defaultScript.trim(), output: "", executionError: false, isSqliteLoaded: false, }; }, methods: { async loadEnvironment() { LswLazyLoads.loadSqlite().then(() => { this.isSqliteLoaded = true; }); }, async executeCode() { this.$trace("lsw-sqlite-console.methods.executeCode"); try { this.output = await LswSqlite.execute(this.code); } catch (error) { this.output = "Error"; this.executionError = error; console.error(error); } }, async developmentPayload() { //const schema = await LswSqlite.getSchemaFromDatabase(); //this.$lsw.debugger.debug(schema); } }, watch: {}, async mounted() { try { this.$trace("lsw-sqlite-console.mounted"); await this.loadEnvironment(); await this.developmentPayload(); } catch (error) { console.log(error); } } });
Vue.component("LswUnitTesterCollection", { template: $template, props: { allTests: { type: Array, required: true, }, autorun: { type: Boolean, default: () => false, } }, data() { this.$trace("lsw-unit-tester-collection.data"); return { isCompleted: false, isPassed: undefined, startedTests: [], testResults: [], }; }, methods: { async nextTest(previousReport = false) { this.$trace("lsw-unit-tester-collection.methods.nextTest"); if(typeof previousReport === 'object') { this.testResults.push(previousReport.result); } const shouldNotInterrupt = this.autorun || force; if(!shouldNotInterrupt) { return; } if(this.allTests.length === this.startedTests.length) { this.isCompleted = true; this.calculatePass(); return; } try { const nextTestIndex = this.startedTests.length; const nextTestCallback = this.allTests[nextTestIndex]; this.startedTests.push(nextTestCallback); } catch (error) { this.startedTests.push(error); console.log(error); } }, calculatePass() { let isPassed = true; Iterating_results: for(let index=0; index<this.testResults.length; index++) { const result = this.testResults[index]; const isOk = ["defined", "passed"].indexOf(result) !== -1; if(!isOk) { isPassed = false; break Iterating_results; } } this.isPassed = isPassed; }, startTest(testIndex) { this.allTests[testIndex].$lswTester = { state: "started", }; const nextTestCallback = this.allTests[testIndex]; this.startedTests.push(nextTestCallback); this.$forceUpdate(true); }, }, watch: {}, async mounted() { try { this.$trace("lsw-unit-tester-collection.mounted"); this.nextTest(); } catch (error) { console.log(error); } } });