feat: Introduce API.Task API that allow to track promise object via AntOS announcement system
All checks were successful
gitea-sync/antos/pipeline/head This commit looks good

This commit is contained in:
DanyLE 2023-06-16 17:54:12 +02:00
parent f8213f7aeb
commit 9fa766963a
5 changed files with 74 additions and 223 deletions

81
d.ts/antos.d.ts vendored
View File

@ -2034,13 +2034,21 @@ declare namespace OS {
*/ */
var lang: GenericObject<string>; var lang: GenericObject<string>;
/** /**
* Re-export the system announcement {@link OS.announcer.getMID} function to the * A task is a Promise object that is tracked by AntOS via the
* core API * Announcerment system
*
* Task manager implementation can subscribe to the following global events
* - ANTOS-TASK-PENDING : a new task/promise is created and executing
* - ANTOS-TASK-FULFILLED: a fullfilled task is a resolved promise
* - ANTOS-TASK-REJECTED: a rejected task is a rejected or error promise
*
* Whenever a task is created by this API, it states will be automatically announced
* to any subscribers of these events
* *
* @export * @export
* @returns {number} * @param {Promise} a Promise object
*/ */
function mid(): number; function Task(fn: (resolve: (any: any) => void, reject: (any: any) => void) => void): Promise<any>;
/** /**
* REST-based API. * REST-based API.
* *
@ -2086,28 +2094,6 @@ declare namespace OS {
* @param {*} b file content * @param {*} b file content
*/ */
function saveblob(name: string, b: any): void; function saveblob(name: string, b: any): void;
/**
* 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 {@link mid}
* @param {string} p message string
*/
function loading(q: number, p: string): void;
/**
* 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`)
*/
function loaded(q: number, p: string, m: string): void;
/** /**
* Perform an REST GET request * Perform an REST GET request
* *
@ -2348,21 +2334,6 @@ declare namespace OS {
* @memberof BaseApplication * @memberof BaseApplication
*/ */
sysdock: GUI.tag.AppDockTag; sysdock: GUI.tag.AppDockTag;
/**
* Loading animation check timeout
*
* @private
* @memberof BaseApplication
*/
private _loading_toh;
/**
* Store pending loading task
*
* @private
* @type {number[]}
* @memberof BaseApplication
*/
private _pending_task;
/** /**
*Creates an instance of BaseApplication. *Creates an instance of BaseApplication.
* @param {string} name application name * @param {string} name application name
@ -2393,9 +2364,8 @@ declare namespace OS {
protected loadScheme(): void; protected loadScheme(): void;
/** /**
* API function to perform an heavy task. * API function to perform an heavy task.
* This function will trigger the global `loading` * This function will create a Task that is tracked by any
* event at the beginning of the task, and the `loaded` * task manager implementation
* event after finishing the task
* *
* @protected * @protected
* @param {Promise<any>} promise the promise on a task to be performed * @param {Promise<any>} promise the promise on a task to be performed
@ -2569,14 +2539,6 @@ declare namespace OS {
* @memberof BaseApplication * @memberof BaseApplication
*/ */
protected cleanup(e: BaseEvent): void; protected cleanup(e: BaseEvent): void;
/**
* Check if the loading tasks ended,
* if it the case, stop the animation
*
* @private
* @memberof BaseApplication
*/
private animation_check;
} }
} }
} }
@ -6452,7 +6414,7 @@ declare namespace OS {
* Store pending loading task * Store pending loading task
* *
* @private * @private
* @type {number[]} * @type {Promise<any>[]}
* @memberof SystemPanelTag * @memberof SystemPanelTag
*/ */
private _pending_task; private _pending_task;
@ -10681,12 +10643,6 @@ declare namespace OS {
* and callbacks * and callbacks
*/ */
var observable: API.Announcer; var observable: API.Announcer;
/**
* This variable is used to allocate the `id` of all messages
* passing between publishers and subscribers in the
* system announcement
*/
var quota: 0;
/** /**
* Placeholder of all global events listeners * Placeholder of all global events listeners
*/ */
@ -10759,13 +10715,6 @@ declare namespace OS {
* @returns {void} * @returns {void}
*/ */
function unregister(app: BaseModel): void; function unregister(app: BaseModel): void;
/**
* Allocate message id
*
* @export
* @returns {number}
*/
function getMID(): number;
} }
} }
declare namespace OS { declare namespace OS {

View File

@ -290,12 +290,6 @@ namespace OS {
* and callbacks * and callbacks
*/ */
export var observable: API.Announcer = new API.Announcer(); export var observable: API.Announcer = new API.Announcer();
/**
* This variable is used to allocate the `id` of all messages
* passing between publishers and subscribers in the
* system announcement
*/
export var quota: 0;
/** /**
* Placeholder of all global events listeners * Placeholder of all global events listeners
*/ */
@ -412,16 +406,5 @@ namespace OS {
} }
announcer.listeners.delete(app); announcer.listeners.delete(app);
} }
/**
* Allocate message id
*
* @export
* @returns {number}
*/
export function getMID(): number {
quota += 1;
return quota;
}
} }
} }

View File

@ -61,22 +61,6 @@ namespace OS {
*/ */
sysdock: GUI.tag.AppDockTag; sysdock: GUI.tag.AppDockTag;
/**
* Loading animation check timeout
*
* @private
* @memberof BaseApplication
*/
private _loading_toh: any;
/**
* Store pending loading task
*
* @private
* @type {number[]}
* @memberof BaseApplication
*/
private _pending_task: number[];
/** /**
*Creates an instance of BaseApplication. *Creates an instance of BaseApplication.
* @param {string} name application name * @param {string} name application name
@ -90,8 +74,6 @@ namespace OS {
} }
this.setting = setting.applications[this.name]; this.setting = setting.applications[this.name];
this.keycomb = {}; this.keycomb = {};
this._loading_toh = undefined;
this._pending_task = [];
} }
/** /**
@ -139,25 +121,7 @@ namespace OS {
this.applySetting(m.message as string); this.applySetting(m.message as string);
} }
}); });
this.subscribe("loading", (o: API.AnnouncementDataType<number>) => {
if(o.u_data != this.pid)
{
return;
}
this._pending_task.push(o.id);
this.trigger("loading", undefined);
});
this.subscribe("loaded", (o: API.AnnouncementDataType<number>) => {
const i = this._pending_task.indexOf(o.id);
if (i >= 0) {
this._pending_task.splice(i, 1);
}
if (this._pending_task.length === 0) {
// set time out
if(!this._loading_toh)
this._loading_toh = setTimeout(() => this.animation_check(),1000);
}
});
this.updateLocale(this.systemsetting.system.locale); this.updateLocale(this.systemsetting.system.locale);
return this.loadScheme(); return this.loadScheme();
} }
@ -178,9 +142,8 @@ namespace OS {
/** /**
* API function to perform an heavy task. * API function to perform an heavy task.
* This function will trigger the global `loading` * This function will create a Task that is tracked by any
* event at the beginning of the task, and the `loaded` * task manager implementation
* event after finishing the task
* *
* @protected * @protected
* @param {Promise<any>} promise the promise on a task to be performed * @param {Promise<any>} promise the promise on a task to be performed
@ -188,15 +151,11 @@ namespace OS {
* @memberof BaseApplication * @memberof BaseApplication
*/ */
protected load(promise: Promise<any>): Promise<void> { protected load(promise: Promise<any>): Promise<void> {
const q = this._api.mid(); return this._api.Task(async (resolve, reject) => {
return new Promise(async (resolve, reject) => {
this._api.loading(q, this.name);
try { try {
await promise; await promise;
this._api.loaded(q, this.name, "OK"); return resolve(undefined);
return resolve();
} catch (e) { } catch (e) {
this._api.loaded(q, this.name, "FAIL");
return reject(__e(e)); return reject(__e(e));
} }
}); });
@ -505,23 +464,6 @@ namespace OS {
* @memberof BaseApplication * @memberof BaseApplication
*/ */
protected cleanup(e: BaseEvent): void {} protected cleanup(e: BaseEvent): void {}
/**
* Check if the loading tasks ended,
* if it the case, stop the animation
*
* @private
* @memberof BaseApplication
*/
private animation_check(): void {
if(this._pending_task.length === 0)
{
this.trigger("loaded", undefined);
}
if(this._loading_toh)
clearTimeout(this._loading_toh);
this._loading_toh = undefined;
}
} }
BaseApplication.type = ModelType.Application; BaseApplication.type = ModelType.Application;

View File

@ -878,7 +878,6 @@ namespace OS {
$("#wrapper").empty(); $("#wrapper").empty();
GUI.clearTheme(); GUI.clearTheme();
announcer.observable = new API.Announcer(); announcer.observable = new API.Announcer();
announcer.quota = 0;
resetSetting(); resetSetting();
PM.processes = {}; PM.processes = {};
PM.pidalloc = 0; PM.pidalloc = 0;
@ -1183,14 +1182,44 @@ namespace OS {
export var lang: GenericObject<string> = {}; export var lang: GenericObject<string> = {};
/** /**
* Re-export the system announcement {@link OS.announcer.getMID} function to the * A task is a Promise object that is tracked by AntOS via the
* core API * Announcerment system
* *
* Task manager implementation can subscribe to the following global events
* - ANTOS-TASK-PENDING : a new task/promise is created and executing
* - ANTOS-TASK-FULFILLED: a fullfilled task is a resolved promise
* - ANTOS-TASK-REJECTED: a rejected task is a rejected or error promise
*
* Whenever a task is created by this API, it states will be automatically announced
* to any subscribers of these events
*
* @export * @export
* @returns {number} * @param {Promise} a Promise object
*/ */
export function mid(): number { export function Task(fn: ( resolve: (any)=>void,reject:(any)=>void) => void ): Promise<any>
return announcer.getMID(); {
return new Promise(async (ok,nok) =>{
const promise = new Promise(fn);
const ann:API.AnnouncementDataType<Promise<any>> = {} as API.AnnouncementDataType<Promise<any>>;
ann.name = "OS";
ann.u_data = promise;
ann.id = Math.floor(Math.random() * 1e6);
try
{
ann.message = "ANTOS-TASK-PENDING";
announcer.trigger(ann.message, ann);
const data = await promise;
ok(data);
ann.message = "ANTOS-TASK-FULFILLED";
announcer.trigger(ann.message, ann);
}
catch(e)
{
ann.message = "ANTOS-TASK-REJECTED";
announcer.trigger(ann.message, ann);
nok(__e(e));
}
});
} }
/** /**
@ -1205,9 +1234,7 @@ namespace OS {
* @returns {Promise<any>} a promise on the result data * @returns {Promise<any>} a promise on the result data
*/ */
export function post(p: string, d: any): Promise<any> { export function post(p: string, d: any): Promise<any> {
return new Promise(function (resolve, reject) { return API.Task(function (resolve, reject) {
const q = announcer.getMID();
API.loading(q, p);
return $.ajax({ return $.ajax({
type: "POST", type: "POST",
url: p, url: p,
@ -1226,11 +1253,9 @@ namespace OS {
success: null, success: null,
}) })
.done(function (data) { .done(function (data) {
API.loaded(q, p, "OK");
return resolve(data); return resolve(data);
}) })
.fail(function (j, s, e) { .fail(function (j, s, e) {
API.loaded(q, p, "FAIL");
return reject(API.throwe(s)); return reject(API.throwe(s));
}); });
}); });
@ -1248,21 +1273,17 @@ namespace OS {
* @returns {Promise<ArrayBuffer>} a promise on the returned binary data * @returns {Promise<ArrayBuffer>} a promise on the returned binary data
*/ */
export function blob(p: string): Promise<ArrayBuffer> { export function blob(p: string): Promise<ArrayBuffer> {
return new Promise(function (resolve, reject) { return API.Task(function (resolve, reject) {
const q = announcer.getMID();
const r = new XMLHttpRequest(); const r = new XMLHttpRequest();
r.open("GET", p, true); r.open("GET", p, true);
r.responseType = "arraybuffer"; r.responseType = "arraybuffer";
r.onload = function (e) { r.onload = function (e) {
if (this.status === 200 && this.readyState === 4) { if (this.status === 200 && this.readyState === 4) {
API.loaded(q, p, "OK");
resolve(this.response); resolve(this.response);
} else { } else {
API.loaded(q, p, "FAIL");
reject(API.throwe(__("Unable to get blob: {0}", p))); reject(API.throwe(__("Unable to get blob: {0}", p)));
} }
}; };
API.loading(q, p);
r.send(); r.send();
}); });
} }
@ -1278,8 +1299,7 @@ namespace OS {
* @returns {Promise<any>} * @returns {Promise<any>}
*/ */
export function upload(p: string, d: string): Promise<any> { export function upload(p: string, d: string): Promise<any> {
return new Promise(function (resolve, reject) { return API.Task(function (resolve, reject) {
const q = announcer.getMID();
//insert a temporal file selector //insert a temporal file selector
const o = const o =
$("<input>") $("<input>")
@ -1287,9 +1307,6 @@ namespace OS {
.attr("multiple", "true"); .attr("multiple", "true");
o.on("change", function () { o.on("change", function () {
const files = (o[0] as HTMLInputElement).files; const files = (o[0] as HTMLInputElement).files;
const n_files = files.length;
if (n_files > 0)
API.loading(q, p);
const formd = new FormData(); const formd = new FormData();
formd.append("path", d); formd.append("path", d);
jQuery.each(files, (i, file) => { jQuery.each(files, (i, file) => {
@ -1303,11 +1320,9 @@ namespace OS {
processData: false, processData: false,
}) })
.done(function (data) { .done(function (data) {
API.loaded(q, p, "OK");
resolve(data); resolve(data);
}) })
.fail(function (j, s, e) { .fail(function (j, s, e) {
API.loaded(q, p, "FAIL");
o.remove(); o.remove();
reject(API.throwe(s)); reject(API.throwe(s));
}); });
@ -1337,44 +1352,6 @@ namespace OS {
o.remove(); 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 {@link mid}
* @param {string} p message string
*/
export function loading(q: number, p: string): void {
const data: API.AnnouncementDataType<number> = {} as API.AnnouncementDataType<number>;
data.id = q;
data.message = p;
data.name = p;
data.u_data = 0; //PM.pidactive;
announcer.trigger("loading", data);
}
/**
* 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 {
const data: API.AnnouncementDataType<boolean> = {} as API.AnnouncementDataType<boolean>;
data.id = q;
data.message = p;
data.name = "OS";
data.u_data = false;
announcer.trigger("loaded", data);
}
/** /**
* Perform an REST GET request * Perform an REST GET request
* *
@ -1388,7 +1365,7 @@ namespace OS {
* @returns {Promise<any>} a Promise on the requested data * @returns {Promise<any>} a Promise on the requested data
*/ */
export function get(p: string, t: string = undefined): Promise<any> { export function get(p: string, t: string = undefined): Promise<any> {
return new Promise(function (resolve, reject) { return API.Task(function (resolve, reject) {
const conf: any = { const conf: any = {
type: "GET", type: "GET",
url: p, url: p,
@ -1396,15 +1373,11 @@ namespace OS {
if (t) { if (t) {
conf.dataType = t; conf.dataType = t;
} }
const q = announcer.getMID();
API.loading(q, p);
return $.ajax(conf) return $.ajax(conf)
.done(function (data) { .done(function (data) {
API.loaded(q, p, "OK");
return resolve(data); return resolve(data);
}) })
.fail(function (j, s, e) { .fail(function (j, s, e) {
API.loaded(q, p, "FAIL");
return reject(API.throwe(s)); return reject(API.throwe(s));
}); });
}); });

View File

@ -34,10 +34,10 @@ namespace OS {
* Store pending loading task * Store pending loading task
* *
* @private * @private
* @type {number[]} * @type {Promise<any>[]}
* @memberof SystemPanelTag * @memberof SystemPanelTag
*/ */
private _pending_task: number[]; private _pending_task: Promise<any>[];
/** /**
* Flag indicate where the selected application shall be openned * Flag indicate where the selected application shall be openned
@ -519,17 +519,13 @@ namespace OS {
this.refs.osmenu.contextmenuHandle = (e, m) => { }; this.refs.osmenu.contextmenuHandle = (e, m) => { };
systray.contextmenuHandle = (e, m) => { }; systray.contextmenuHandle = (e, m) => { };
this.refs.panel.contextmenuHandle = (e, m) => { }; this.refs.panel.contextmenuHandle = (e, m) => { };
announcer.on("loading", (o: API.AnnouncementDataType<number>) => { announcer.on("ANTOS-TASK-PENDING", (o: API.AnnouncementDataType<Promise<any>>) => {
if(o.u_data != 0)
{
return;
}
if(this._pending_task.length == 0) if(this._pending_task.length == 0)
{ {
$(this.refs.panel).addClass("loading"); $(this.refs.panel).addClass("loading");
systray.iconclass = "fa-spin fa fa-cog"; systray.iconclass = "fa-spin fa fa-cog";
} }
this._pending_task.push(o.id); this._pending_task.push(o.u_data);
$(GUI.workspace).css("cursor", "wait"); $(GUI.workspace).css("cursor", "wait");
}); });
@ -538,8 +534,8 @@ namespace OS {
e.data.stopPropagation(); e.data.stopPropagation();
this.show_systray(); this.show_systray();
}; };
announcer.on("loaded", (o: API.AnnouncementDataType<number>) => { const remove_task = (o: Promise<any>) => {
const i = this._pending_task.indexOf(o.id); const i = this._pending_task.indexOf(o);
if (i >= 0) { if (i >= 0) {
this._pending_task.splice(i, 1); this._pending_task.splice(i, 1);
} }
@ -549,7 +545,15 @@ namespace OS {
if(!this._loading_toh) if(!this._loading_toh)
this._loading_toh = setTimeout(() => this.animation_check(),1000); this._loading_toh = setTimeout(() => this.animation_check(),1000);
} }
};
announcer.on("ANTOS-TASK-FULFILLED", (o: API.AnnouncementDataType<Promise<any>>) => {
remove_task(o.u_data);
}); });
announcer.on("ANTOS-TASK-REJECTED", (o: API.AnnouncementDataType<Promise<any>>) => {
remove_task(o.u_data);
});
announcer.on("desktopresize", (e) => { announcer.on("desktopresize", (e) => {
this.calibrate(); this.calibrate();
}); });