// Copyright 2017-2020 Xuan Sang LE // AnTOS Web desktop is is licensed under the GNU General Public // License v3.0, see the LICENCE file for more information // This program is free software: you can redistribute it and/or // modify it under the terms of the GNU General Public License as // published by the Free Software Foundation, either version 3 of // the License, or (at your option) any later version. // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // General Public License for more details. // You should have received a copy of the GNU General Public License //along with this program. If not, see https://www.gnu.org/licenses/. "use strict"; /** * Reference to the global this */ const Ant = this; /** * Extend the String prototype with some API * functions used by AntOS API * * @interface String */ interface String { /** * Simple string hash function * * @returns {number} * @memberof String */ hash(): number; /** * Parse the current string and convert it * to an object of type [[Version]] if the string * is in the format recognized by [[Version]], * e.g.: `1.0.1-a` * * @returns {OS.Version} * @memberof String */ __v(): OS.Version; /** * Convert the current string to base 64 string * * @returns {string} * @memberof String */ asBase64(): string; /** * Unescape all escaped characters on the * string using `\` * * @returns {string} * @memberof String */ unescape(): string; /** * Escape the current string using backslash * * @returns {string} * @memberof String */ escape(): string; /** * Convert the current string to uint8 array * * @returns {Uint8Array} * @memberof String */ asUint8Array(): Uint8Array; /** * Format the current using input parameters. * The current string should be a formatted string * in the following form: * * ```typescript * "example string: {0} and {1}".format("hello", "world") * // return "example string: hello and world" * ``` * * @param {...any[]} args * @returns {string} * @memberof String */ format(...args: any[]): string; /** * Create a [[FormattedString]] object using the current * string and the input parameters * * @param {...any[]} args * @returns {OS.FormattedString} * @memberof String */ f(...args: any[]): OS.FormattedString; /** * Check if the current string is translatable, if it * is the case, translate the string to the language specified * in the current system locale setting. * * A translatable string is a string in the following * form: `"__(example string)"` * * @returns {string} * @memberof String */ __(): string; /** * Translate current string to the language specified * by the system locale setting * * @returns {string} * @memberof String */ l(): string; /** * Trim left of a string by a mask string * * @param {string} arg specifies a sub-string to be removed * @returns {string} * @memberof String */ trimLeft(arg: string): string; /** * Trim right of a string by a mask string * * @param {string} arg specifies a sub-string to be removed * @returns {string} * @memberof String */ trimRight(arg: string): string; /** * Trim both left and right of a string by a mask string * * @param {string} arg specifies a sub-string to be removed * @returns {string} * @memberof String */ trimBy(arg: string): string; } /** * Extend the Data prototype with the * [[timestamp]] function * * @interface Date */ interface Date { /** * Return the timestamp of the current Date object * * @returns {number} * @memberof Date */ timestamp(): number; } /** * Generic key-value pair object interface * * @interface GenericObject * @template T */ interface GenericObject { [index: string]: T; } /** * Global function to create a [[FormattedString]] from * a formatted string and a list of parameters. Example * * ```typescript * __("hello {0}", world) // return a FormattedString object * ``` * * @param {...any[]} args * @returns {(OS.FormattedString | string)} */ declare function __(...args: any[]): OS.FormattedString | string; /** * This global function allow chaining stack trace from one error to * another. It is particular helping when tracking the source of * the error in promises chain which results in some obfuscated stack * traces as the stack resets on every new promise. * * @param {Error} e * @returns {Error} */ declare function __e(e: Error): Error; /** * This namespace is the main entry point of AntOS * API */ namespace OS { /** * Return an range of numbers * * @param {number} left start of the range * @param {number} right end of the range * @param {boolean} inclusive specifies whether the * `right` of the range is included in the returned array * @returns {number[]} */ function __range__( left: number, right: number, inclusive: boolean ): number[] { let range = []; let ascending = left < right; let end = !inclusive ? right : ascending ? right + 1 : right - 1; for ( let i = left; ascending ? i < end : i > end; ascending ? i++ : i-- ) { range.push(i); } return range; } Ant.__ = function(...args: any[]): FormattedString | string { if (!(args.length > 0)) { return "Undefined"; } const d = args[0]; d.l(); return new FormattedString( d, __range__(1, args.length - 1, true).map((i) => args[i]) ); }; Ant.__e = function(e: Error): Error { const reason = new Error(e.toString()); reason.stack += "\nCaused By:\n" + e.stack; return reason; }; /** * Represent a translatable formatted string * * @export * @class FormattedString */ export class FormattedString { /** * Format string in the following form * * ```typescript * "format string with {0} and {1}" * // {[0-9]} is the format pattern * ``` * * @type {string} * @memberof FormattedString */ fs: string; /** * The value of the format pattern represented * in [[fs]] * * @type {any[]} * @memberof FormattedString */ values: any[]; /** * Creates an instance of FormattedString. * @param {string} fs format string * @param {any[]} args input values of the format patterns * @memberof FormattedString */ constructor(fs: string, args: any[]) { this.fs = fs; this.values = []; if (!args) { return; } for ( let i = 0, end = args.length - 1, asc = 0 <= end; asc ? i <= end : i >= end; asc ? i++ : i-- ) { this.values[i] = args[i]; } } /** * Convert FormattedString to String * * @returns {string} * @memberof FormattedString */ toString(): string { return this.__(); } /** * Translate the format string to the current system * locale language, format the string with values and * then converted it to normal `string` * * @returns {string} * @memberof FormattedString */ __(): string { return this.fs .l() .replace(/{(\d+)}/g, (match: string, n: number) => { if (typeof this.values[n] !== "undefined") { return this.values[n].__(); } else { return match; } }); } /** * Return the hash number of the formatted string * * @returns {number} * @memberof FormattedString */ hash(): number { return this.__().hash(); } /** * Match the formatted string against a regular expression * a string pattern * * @param {(string | RegExp)} t string or regular expression * @returns {RegExpMatchArray} * @memberof FormattedString */ match(t: string | RegExp): RegExpMatchArray { return this.__().match(t); } /** * Convert the formatted string to Base^$ * * @returns {string} * @memberof FormattedString */ asBase64(): string { return this.__().asBase64(); } /** * Un escape the formatted string * * @returns {string} * @memberof FormattedString */ unescape(): string { return this.__().unescape(); } /** * Escape the formatted string * * @returns {string} * @memberof FormattedString */ escape(): string { return this.__().escape(); } /** * Convert the formatted string to uint8 array * * @returns {Uint8Array} * @memberof FormattedString */ asUint8Array(): Uint8Array { return this.__().asUint8Array(); } /** * Input values for the format string * * @param {...any[]} args * @memberof FormattedString */ format(...args: any[]): void { __range__(0, args.length - 1, true).map( (i) => (this.values[i] = args[i]) ); } } /** * This class represents the Version number format used by AntOS. A typical * AntOS version number is in the following format: * * ``` * [major_number].[minor_number].[patch]-[branch] * * e.g.: 1.2.3-r means that: * - version major number is 1 * - version minor number is 2 * - patch version is 3 * - the current branch is release `r` * ``` * * @export * @class Version */ export class Version { /** * The version string * * @type {string} * @memberof Version */ string: string; /** * The current branch * - 1: `a` - alpha branch * - 2: `b` - beta branch * - 3: `r` - release branch * * @private * @type {number} * @memberof Version */ private branch: number; /** * Version major number * * @type {number} * @memberof Version */ major: number; /** * Version minor number * * @type {number} * @memberof Version */ minor: number; /** * Version patch number * * @type {number} * @memberof Version */ patch: number; /** *Creates an instance of Version. * @param {string} string string represents the version * @memberof Version */ constructor(string: string) { this.string = string; const arr = this.string.split("-"); const br = { r: 3, b: 2, a: 1, }; this.branch = 3; if (arr.length === 2 && br[arr[1]]) { this.branch = br[arr[1]]; } const mt = arr[0].match(/\d+/g); if (!mt) { API.throwe( __("Version string is in invalid format: {0}", this.string) ); } this.major = 0; this.minor = 0; this.patch = 0; if (mt.length >= 1) { this.major = Number(mt[0]); } if (mt.length >= 2) { this.minor = Number(mt[1]); } if (mt.length >= 3) { this.patch = Number(mt[2]); } } /** * Compare the current version with another version. * * The comparison priority is `branch>major>minor>patch`. * * For the branch, the priority is `r>b>a` * * @param {(string | Version)} o version string or object * @returns {(0 | 1 | -1)} * Return 0 if the two versions are the same, 1 if * the current version is newer than the input version, * otherwise return -1 * @memberof Version */ compare(o: string | Version): 0 | 1 | -1 { const other = o.__v(); if (this.branch > other.branch) { return 1; } if (this.branch < other.branch) { return -1; } if ( this.major === other.major && this.minor === other.minor && this.patch === other.patch ) { return 0; } if (this.major > other.major) { return 1; } if (this.major < other.major) { return -1; } if (this.minor > other.minor) { return 1; } if (this.minor < other.minor) { return -1; } if (this.patch > other.patch) { return 1; } return -1; } /** * Check if the current version is newer than * the input version * * @param {(string | Version)} o version string or object * @returns {boolean} * @memberof Version */ nt(o: string | Version): boolean { return this.compare(o) === 1; } /** * Check if the current version is older than * the input version * * @param {(string | Version)} o version string or object * @returns {boolean} * @memberof Version */ ot(o: string | Version): boolean { return this.compare(o) === -1; } /** * Return itself * * @returns {Version} * @memberof Version */ __v(): Version { return this; } /** * Convert Version object to string * * @returns {string} * @memberof Version */ toString(): string { return this.string; } } Object.defineProperty(Object.prototype, "__", { value() { if (this) return this.toString(); }, enumerable: false, writable: true, }); String.prototype.hash = function(): number { let hash = 5381; let i = this.length; while (i) { hash = (hash * 33) ^ this.charCodeAt(--i); } return hash >>> 0; }; String.prototype.__v = function(): Version { return new Version(this); }; String.prototype.asBase64 = function(): string { const tmp = encodeURIComponent(this); return btoa( tmp.replace(/%([0-9A-F]{2})/g, (match, p1) => String.fromCharCode(parseInt(p1, 16)) ) ); }; String.prototype.escape = function(): string { return this.replace(/[\0\x08\x09\x1a\n\r"'\\\%]/g, function( c: string ) { switch (c) { case "\0": return "\\0"; case "\x08": return "\\b"; case "\x09": return "\\t"; case "\x1a": return "\\z"; case "\n": return "\\n"; case "\r": return "\\r"; case '"': case "'": case "\\": case "%": return "\\" + c; // prepends a backslash to backslash, percent, // and double/single quotes default: return c; } }); }; String.prototype.unescape = function(): string { let json = JSON.parse(`{ "text": "${this}"}`) return json.text; }; String.prototype.asUint8Array = function(): Uint8Array { let bytes = []; for ( let i = 0, end = this.length - 1, asc = 0 <= end; asc ? i <= end : i >= end; asc ? i++ : i-- ) { bytes.push(this.charCodeAt(i)); } return new Uint8Array(bytes); }; if (!String.prototype.format) { String.prototype.format = function(...args: any[]): string { return this.replace(/{(\d+)}/g, function( match: string, number: number ) { if (typeof args[number] !== "undefined") { return args[number].__(); } else { return match; } }); }; } String.prototype.f = function(...args: any[]): FormattedString { return new FormattedString(this, args); }; String.prototype.__ = function(): string { const match = this.match(/^__\((.*)\)$/); if (match) { return match[1].l(); } return this; }; String.prototype.l = function(): string { if (!API.lang[this]) { API.lang[this] = this; } return API.lang[this]; }; String.prototype.trimLeft = function(charlist: string): string { if (charlist === undefined) charlist = "s"; return this.replace(new RegExp("^[" + charlist + "]+"), "") as string; }; String.prototype.trimRight = function(charlist: string): string { if (charlist === undefined) charlist = "s"; return this.replace(new RegExp("[" + charlist + "]+$"), "") as string; }; String.prototype.trimBy = function(charlist: string): string { return this.trimLeft(charlist).trimRight(charlist) as string; }; Date.prototype.toString = function(): string { let dd = this.getDate(); let mm = this.getMonth() + 1; const yyyy = this.getFullYear(); let hh = this.getHours(); let mi = this.getMinutes(); let se = this.getSeconds(); if (dd < 10) { dd = `0${dd}`; } if (mm < 10) { mm = `0${mm}`; } if (hh < 10) { hh = `0${hh}`; } if (mi < 10) { mi = `0${mi}`; } if (se < 10) { se = `0${se}`; } return `${dd}/${mm}/${yyyy} ${hh}:${mi}:${se}`; }; Date.prototype.timestamp = function(): number { return (this.getTime() / 1000) | 0; }; /** * Variable represents the current AntOS version, it * is an instance of [[Version]] */ export const VERSION: Version = "1.0.0-a".__v(); /** * Register a model prototype to the system namespace. * There are two types of model to be registered, if the model * is of type [[SubWindow]], its prototype will be registered * in the [[dialogs]] namespace, otherwise, if the model type * is [[Application]] or [[Service]], its prototype will be * registered in the [[application]] namespace. * * When a model is loaded in the system, its prototype is registered * for later uses * * @export * @param {string} name class name * @param {*} x the corresponding class * @returns {*} */ export function register(name: string, x: PM.ModelTypeClass): void { if (((x as any) as typeof BaseModel).type === ModelType.SubWindow) { GUI.dialogs[name] = x; } else { application[name] = x; } } /** * This function cleans up the entire system and * makes sure the system is in a new and clean session. * It performs the following operations: * * - Kill all running processes * - Unregister all global events and reset the global * announcement system * - Clear the current theme * - Reset process manager and all system settings * * @export */ export function cleanup(): void { console.log("Clean up system"); for (let a in PM.processes) { const v = PM.processes[a]; PM.killAll(a, true); } if (announcer.observable) { announcer.observable.off("*"); } $(window).off("keydown"); $("#workspace").off("mouseover"); delete announcer.observable; $("#wrapper").empty(); GUI.clearTheme(); announcer.observable = new API.Announcer(); announcer.quota = 0; resetSetting(); PM.processes = {}; PM.pidalloc = 0; } /** * Booting up AntOS. This function checks whether the user * is successfully logged in, then call [[startAntOS]], otherwise * it shows the login screen * * @export */ export function boot(): void { //first login console.log("Booting system"); API.handle .auth() .then(function(d: API.RequestResult) { // in case someone call it more than once :) if (d.error) { // show login screen return GUI.login(); } else { // startX :) return GUI.startAntOS(d.result); } }) .catch((e: Error) => console.error(e)); } /** * Placeholder for all the callbacks that are called when the system * exits. These callbacks are useful when an application or service wants * to perform a particular task before shuting down the system */ export const cleanupHandles: { [index: string]: () => void } = {}; /** * Perform the system shutdown operation. This function calls all * clean up handles in [[cleanupHandles]], then save the system setting * before exiting * * @export */ export function exit(): void { //do clean up first for (let n in cleanupHandles) { const f = cleanupHandles[n]; f(); } API.handle .setting() .then(function(r: any) { cleanup(); return API.handle.logout().then((d: any) => boot()); }) .catch((e: Error) => console.error(e)); } /** * Register a callback to the system [[cleanupHandles]] * * @export * @param {string} n callback string name * @param {() => void} f the callback handle * @returns */ export function onexit(n: string, f: () => void) { if (!cleanupHandles[n]) { return (cleanupHandles[n] = f); } } /** * The namespace API is dedicated to the definition of the core system APIs * used by AntOS and its applications. The following core APIs are defined: * * - The AntOS announcement system * - Virtual File system * - Virtual Database * - Low-level REST based client-server communication * - Dependencies management * - System utilities * * These APIs are considered as middle-ware that abstracts the client-server * communication and provide the application layer with a standardized APIs * for file/database access, system events handling (announcement), automatic * dependencies resolving, etc. */ export namespace API { /** * AntOS package meta-data type definition * * @export * @interface PackageMetaType */ export interface PackageMetaType { /** * The application class name, if the package has only services * this property is ignored and [[pkgname]] should be specified * * @type {string} * @memberof PackageMetaType */ app?: string; /** * Package name, in case of [[app]] being undefined, this property * need to be specified * * @type {string} * @memberof PackageMetaType */ pkgname?: string; /** * Package category * * @type {string} * @memberof PackageMetaType */ category: string; /** * Package description string * * @type {string} * @memberof PackageMetaType */ description: string; /** * List of services that is attached to the * package * * @type {string[]} * @memberof PackageMetaType */ services?: string[]; /** * CSS icon class of the package * * @type {string} * @memberof PackageMetaType */ iconclass?: string; /** * VFS application icon path * * @type {string} * @memberof PackageMetaType */ icon?: string; /** * Package information * * @type {{ * author: string; * email: string; * [propName: string]: any; * }} * @memberof PackageMetaType */ info: { /** * Author of the package * * @type {string} */ author: string; /** * Author's email * * @type {string} */ email: string; [propName: string]: any; }; /** * Application-specific locale definition. When the system locale changes, * translatable texts inside the application will be first translated using * the locale dictionary defined in the package meta-data. If no translation * found, the system locale dictionary is used instead. * * A local dictionary definition should be in the following format: * * ```typescript * { * [locale_name: string]: { * [origin_string]: string // translation string * } * } * ``` * * Example of locale dictionaries: * * ```typescript * { * "en_GB": { * "Cancel": "Cancel", * "Modify": "Modify" * }, * "fr_FR": { * "Cancel": "Annuler", * "Modify": "Modifier" * } * } * ``` * * @type {{ [index: string]: GenericObject }} locale dictionaries * @memberof PackageMetaType */ locales: { [index: string]: GenericObject }; /** * Mime types supported by the packages, regular expression can be used * to specified a range of mimes in common * * @type {string[]} * @memberof PackageMetaType */ mimes: string[]; /** * Package (application) name * * @type {string} * @memberof PackageMetaType */ name: string; /** * VFS path to package installation location * * @type {string} * @memberof PackageMetaType */ path: string; /** * Package version, should be in a format conforming * to the version definition in [[Version]] class * * @type {string} * @memberof PackageMetaType */ version: string; [propName: string]: any; } /** * Placeholder to store all loaded shared libraries. Once * a shared library is firstly loaded, its identity will be * stored in this variable. Based on this information, in * the next use of the library, the system knows that the * library is already loaded and ready to use. * * A shared library can be a javascript or a CSS file. */ export const shared: GenericObject = {}; /** * Placeholder for all global search handles registered to the system. * These callbacks will be called when user performs the search operation * in the spotlight UI. * * Applications can define their own search handle to provide the spotlight UI * with additional search results * */ export const searchHandle: GenericObject<(text: string) => any[]> = {}; /** * Placeholder of the current system locale dictionary, the system uses * this dictionary to translate all translatable texts to the current * locale language */ export var lang: GenericObject = {}; /** * Re-export the system announcement [[getMID]] function to the * core API * * @export * @returns {number} */ export function mid(): number { return announcer.getMID(); } /** * REST-based API. * * Perform a POST request to the server. Data exchanged * is in `application/json` * * @export * @param {string} p the server URI * @param {*} d data object that will be converted to JSON * @returns {Promise} a promise on the result data */ export function post(p: string, d: any): Promise { return new Promise(function(resolve, reject) { const q = announcer.getMID(); API.loading(q, p); return $.ajax({ type: "POST", url: p, contentType: "application/json", data: JSON.stringify( d, function(k, v) { if (k === "domel") { return undefined; } return v; }, 4 ), dataType: "json", success: null, }) .done(function(data) { API.loaded(q, p, "OK"); return resolve(data); }) .fail(function(j, s, e) { API.loaded(q, p, "FAIL"); return reject(API.throwe(s)); }); }); } /** * REST-based API. * * Perform a GET request and read back the data in * `ArrayBuffer` (binary) format. This is useful for * binary data reading * * @export * @param {string} p resource URI * @returns {Promise} a promise on the returned binary data */ export function blob(p: string): Promise { return new Promise(function(resolve, reject) { const q = announcer.getMID(); const r = new XMLHttpRequest(); r.open("GET", p, true); r.responseType = "arraybuffer"; r.onload = function(e) { if (this.status === 200 && this.readyState === 4) { API.loaded(q, p, "OK"); resolve(this.response); } else { API.loaded(q, p, "FAIL"); reject(API.throwe(__("Unable to get blob: {0}", p))); } }; API.loading(q, p); r.send(); }); } /** * REST-based API. * * Send file to server * * @export * @param {string} p resource URI * @param {string} d VFS path of the destination file * @returns {Promise} */ export function upload(p: string, d: string): Promise { return new Promise(function(resolve, reject) { const q = announcer.getMID(); //insert a temporal file selector const o = $("") .attr("type", "file") .css("display", "none"); o.change(function() { API.loading(q, p); const formd = new FormData(); formd.append("path", d); // TODO: only one file is selected at this time formd.append("upload", (o[0] as HTMLInputElement).files[0]); return $.ajax({ url: p, data: formd, type: "POST", contentType: false, processData: false, }) .done(function(data) { API.loaded(q, p, "OK"); resolve(data); return o.remove(); }) .fail(function(j, s, e) { API.loaded(q, p, "FAIL"); reject(API.throwe(s)); return o.remove(); }); }); return o.click(); }); } /** * REST-based API. * * Download a file * * @export * @param {string} name file name * @param {*} b file content */ export function saveblob(name: string, b: any): void { const url = window.URL.createObjectURL(b); const o = $("") .attr("href", url) .attr("download", name) .css("display", "none") .appendTo("body"); o[0].click(); window.URL.revokeObjectURL(url); o.remove(); } /** * Helper function to trigger the global `loading` * event. This event should be triggered in the * beginning of a heavy task * * @export * @param {number} q message id, see [[mid]] * @param {string} p message string */ export function loading(q: number, p: string): void { announcer.trigger("loading", { id: q, data: { m: `${p}`, s: true }, name: "OS", }); } /** * Helper function to trigger the global `loaded` * event: This event should be triggered in the * end of a heavy task that has previously triggered * the `loading` event * * @export * @param {number} q the message id of the corresponding `loading` event * @param {string} p the message string * @param {string} m message status (`OK` of `FAIL`) */ export function loaded(q: number, p: string, m: string): void { announcer.trigger("loaded", { id: q, data: { m: `${m}: ${p}`, s: false }, name: "OS", }); } /** * Perform an REST GET request * * @export * @param {string} p the URI of the request * @param {string} [t=undefined] the response data type: * - jsonp: the response is an json object * - script: the response is a javascript code * - xm, html: the response is a XML/HTML object * - text: plain text * @returns {Promise} a Promise on the requested data */ export function get(p: string, t: string = undefined): Promise { return new Promise(function(resolve, reject) { const conf: any = { type: "GET", url: p, }; if (t) { conf.dataType = t; } const q = announcer.getMID(); API.loading(q, p); return $.ajax(conf) .done(function(data) { API.loaded(q, p, "OK"); return resolve(data); }) .fail(function(j, s, e) { API.loaded(q, p, "FAIL"); return reject(API.throwe(s)); }); }); } /** * REST-based API * * Perform a GET operation and executed the returned * content as javascript * * @export * @param {string} p URI resource * @returns {Promise} promise on the executed content */ export function script(p: string): Promise { return API.get(p, "script"); } /** * REST-based API * * Get the content of a global asset resource stored * in `os://resources/` * * @export * @param {string} r relative path to the resource * @returns {Promise} promise on the returned content */ export function resource(r: string): Promise { const path = `resources/${r}`; return API.get(path); } /** * Helper function to verify whether a shared library * is loaded and ready to use * * @export * @param {string} l path to the library * @returns {boolean} */ export function libready(l: string): boolean { return API.shared[l] || false; } /** * Load a shared library if not ready * * @export * @param {string} l VFS path to the library * @returns {Promise} a promise on the result data */ export function requires(l: string): Promise { return new Promise(function(resolve, reject) { if (!API.shared[l]) { const libfp = l.asFileHandle(); switch (libfp.ext) { case "css": return libfp .onready() .then(function() { $("", { rel: "stylesheet", type: "text/css", href: `${libfp.getlink()}`, }).appendTo("head"); API.shared[l] = true; console.log("Loaded :", l); announcer.trigger("sharedlibraryloaded", l); return resolve(undefined); }) .catch((e: Error) => reject(__e(e))); case "js": return API.script(libfp.getlink()) .then(function(data: any) { API.shared[l] = true; console.log("Loaded :", l); announcer.trigger("sharedlibraryloaded", l); return resolve(data); }) .catch((e: Error) => reject(__e(e))); default: return reject( API.throwe(__("Invalid library: {0}", l)) ); } } else { console.log(l, "Library exist, no need to load"); announcer.trigger("sharedlibraryloaded", l); return resolve(); } }); } /** * Synchronously load a list of shared libraries * * @export * @param {string[]} libs list of shared libraries * @returns {Promise} */ export function require(libs: string[]): Promise { return new Promise(function(resolve, reject) { if (!(libs.length > 0)) { return resolve(); } announcer.observable.one("sharedlibraryloaded", async function( l ) { libs.splice(0, 1); let r: void; try { r = await API.require(libs); } catch (e) { r = reject(__e(e)); } return resolve(r); }); return API.requires(libs[0]).catch((e: Error) => reject(__e(e)) ); }); } /** * The namespace packages is dedicated to all package management * related APIs. */ export namespace packages { /** * Fetch the package meta-data from the server * * @export * @returns {Promise} Promise on a [[RequestResult]]. * A success request result should contain a list of [[PackageMetaType]] */ export function fetch(): Promise { return API.handle.packages({ command: "list", args: { paths: (() => { const result = []; for (let k in OS.setting.system.pkgpaths) { const v = OS.setting.system.pkgpaths[k]; result.push(v); } return result; })(), }, }); } /** * Request the server to regenerate the package * caches * * @export * @returns {Promise} */ export function cache(): Promise { return API.handle.packages({ command: "cache", args: { paths: (() => { const result = []; for (let k in OS.setting.system.pkgpaths) { const v = OS.setting.system.pkgpaths[k]; result.push(v); } return result; })(), }, }); } } /** * Save the current user setting * * @export * @returns {Promise} promise on a [[RequestResult]] */ export function setting(): Promise { return API.handle.setting(); } /** * An apigateway allows client side to execute a custom server-side * script and get back the result. This gateway is particularly * useful in case of performing a task that is not provided by the core * API * * @export * @param {GenericObject} d execution indication, provided only when ws is `false` * otherwise, `d` should be written directly to the websocket stream as JSON object. * Two possible formats of `d`: * ```text * execute an server-side script file: * * { * path: [VFS path], * parameters: [parameters of the server-side script] * } * * or, execute directly a snippet of server-side script: * * { code: [server-side script code snippet as string] } * * ``` * * @param {boolean} ws flag indicate whether to use websocket for the connection * to the gateway API. In case of streaming data, the websocket is preferred * @returns {Promise} a promise on the result object (any) */ export function apigateway( d: GenericObject, ws: boolean ): Promise { return API.handle.apigateway(d, ws); } /** * Perform the global search operation when user enter * text in spotlight. * * This function will call all the search handles stored * in [[searchHandle]] and build the search result based * on output of these handle * * @export * @param {string} text text to search * @returns {any[]} */ export function search(text: string): any[] { let r = []; for (let k in searchHandle) { const ret = searchHandle[k](text); if (ret.length > 0) { ret.unshift({ text: k, class: "search-header", dataid: "header", }); r = r.concat(ret); } } return r; } /** * Register a search handle to the global [[searchHandle]] * * @export * @param {string} name handle name string * @param {(text: string) => any[]} fn search handle */ export function onsearch( name: string, fn: (text: string) => any[] ): void { if (!searchHandle[name]) { searchHandle[name] = fn; } } /** * Set the current system locale: This function will * find and load the locale dictionary definition file in the * system asset resource, then trigger the global event * `systemlocalechange` to translated all translatable text * to the target language * * @export * @param {string} name locale name, e.g. `en_GB` * @returns {Promise} */ export function setLocale(name: string): Promise { return new Promise(async function(resolve, reject) { const path = `resources/languages/${name}.json`; try { const d = await API.get(path, "json"); OS.setting.system.locale = name; API.lang = d; announcer.trigger("systemlocalechange", name); return resolve(d); } catch (e) { return reject(__e(e)); } }); } /** * Return an error Object: AntOS use this function to * collect information (stack trace) from user reported * error. * * @export * @param {(string | FormattedString)} n error string * @returns {Error} */ export function throwe(n: string | FormattedString): Error { let err = undefined; try { throw new Error(n.__()); } catch (e) { err = e; } return err; } /** * Set value to the system clipboard * * @export * @param {string} v clipboard value * @returns {boolean} */ export function setClipboard(v: string): boolean { const $el = $("#clipboard"); $el.val(v); ($el[0] as HTMLInputElement).select(); ($el[0] as HTMLInputElement).setSelectionRange(0, 99999); return document.execCommand("copy"); } /** * Get the clipboard data * * @export * @returns {Promise} Promise on the clipboard data */ export function getClipboard(): Promise { return new Promise(function(resolve, reject) { const $el = $("#clipboard"); if (!navigator.clipboard) { return resolve($el.val()); } return navigator.clipboard .readText() .then((d: string) => resolve(d)) .catch((e) => reject(__e(e))); }); } /** * A switcher object is a special object in which * each object's property is a boolean option. All object's * properties are mutual exclusive. It means that when a property * is set to true, all other properties will be reset to false. * * Example: * * ```typescript * let view = API.switcher("tree", "list", "icon") * view.tree = true // view.list = false and view.icon = false * view.list = true // view.tree = false and view.icon = false * ``` * * @export * @returns {*} */ export function switcher(...args: string[]): any { let k: any, v: any; const o: any = {}; const p = {}; for ( let i = 0, end = arguments.length - 1, asc = 0 <= end; asc ? i <= end : i >= end; asc ? i++ : i-- ) { p[arguments[i]] = false; } Object.defineProperty(o, "__p", { enumerable: false, value: p, }); const fn = function(o: any, v: any) { return Object.defineProperty(o, v, { enumerable: true, set(value) { for (let k in this.__p) { const l = this.__p[k]; this.__p[k] = false; } return (o.__p[v] = value); }, get() { return o.__p[v]; }, }); }; for (k in o.__p) { v = o.__p[k]; fn(o, k); } Object.defineProperty(o, "selected", { configurable: true, enumerable: false, get() { for (k in o.__p) { v = o.__p[k]; if (v) { return k; } } }, }); return o; } } }