feat: Introduce API.Task API that allow to track promise object via AntOS announcement system

This commit is contained in:
DanyLE 2023-06-16 17:54:12 +02:00 committed by Dany LE
parent 0ac886f644
commit 4d59b104b9
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>;
/**
* Re-export the system announcement {@link OS.announcer.getMID} function to the
* core API
* A task is a Promise object that is tracked by AntOS via the
* 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
* @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.
*
@ -2086,28 +2094,6 @@ declare namespace OS {
* @param {*} b file content
*/
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
*
@ -2348,21 +2334,6 @@ declare namespace OS {
* @memberof BaseApplication
*/
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.
* @param {string} name application name
@ -2393,9 +2364,8 @@ declare namespace OS {
protected loadScheme(): void;
/**
* API function to perform an heavy task.
* This function will trigger the global `loading`
* event at the beginning of the task, and the `loaded`
* event after finishing the task
* This function will create a Task that is tracked by any
* task manager implementation
*
* @protected
* @param {Promise<any>} promise the promise on a task to be performed
@ -2569,14 +2539,6 @@ declare namespace OS {
* @memberof BaseApplication
*/
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
*
* @private
* @type {number[]}
* @type {Promise<any>[]}
* @memberof SystemPanelTag
*/
private _pending_task;
@ -10681,12 +10643,6 @@ declare namespace OS {
* and callbacks
*/
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
*/
@ -10759,13 +10715,6 @@ declare namespace OS {
* @returns {void}
*/
function unregister(app: BaseModel): void;
/**
* Allocate message id
*
* @export
* @returns {number}
*/
function getMID(): number;
}
}
declare namespace OS {

View File

@ -290,12 +290,6 @@ namespace OS {
* and callbacks
*/
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
*/
@ -412,16 +406,5 @@ namespace OS {
}
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;
/**
* 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.
* @param {string} name application name
@ -90,8 +74,6 @@ namespace OS {
}
this.setting = setting.applications[this.name];
this.keycomb = {};
this._loading_toh = undefined;
this._pending_task = [];
}
/**
@ -139,25 +121,7 @@ namespace OS {
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);
return this.loadScheme();
}
@ -178,9 +142,8 @@ namespace OS {
/**
* API function to perform an heavy task.
* This function will trigger the global `loading`
* event at the beginning of the task, and the `loaded`
* event after finishing the task
* This function will create a Task that is tracked by any
* task manager implementation
*
* @protected
* @param {Promise<any>} promise the promise on a task to be performed
@ -188,15 +151,11 @@ namespace OS {
* @memberof BaseApplication
*/
protected load(promise: Promise<any>): Promise<void> {
const q = this._api.mid();
return new Promise(async (resolve, reject) => {
this._api.loading(q, this.name);
return this._api.Task(async (resolve, reject) => {
try {
await promise;
this._api.loaded(q, this.name, "OK");
return resolve();
return resolve(undefined);
} catch (e) {
this._api.loaded(q, this.name, "FAIL");
return reject(__e(e));
}
});
@ -505,23 +464,6 @@ namespace OS {
* @memberof BaseApplication
*/
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;

View File

@ -878,7 +878,6 @@ namespace OS {
$("#wrapper").empty();
GUI.clearTheme();
announcer.observable = new API.Announcer();
announcer.quota = 0;
resetSetting();
PM.processes = {};
PM.pidalloc = 0;
@ -1183,14 +1182,44 @@ namespace OS {
export var lang: GenericObject<string> = {};
/**
* Re-export the system announcement {@link OS.announcer.getMID} function to the
* core API
* A task is a Promise object that is tracked by AntOS via the
* 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
* @returns {number}
* @param {Promise} a Promise object
*/
export function mid(): number {
return announcer.getMID();
export function Task(fn: ( resolve: (any)=>void,reject:(any)=>void) => void ): Promise<any>
{
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
*/
export function post(p: string, d: any): Promise<any> {
return new Promise(function (resolve, reject) {
const q = announcer.getMID();
API.loading(q, p);
return API.Task(function (resolve, reject) {
return $.ajax({
type: "POST",
url: p,
@ -1226,11 +1253,9 @@ namespace OS {
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));
});
});
@ -1248,21 +1273,17 @@ namespace OS {
* @returns {Promise<ArrayBuffer>} a promise on the returned binary data
*/
export function blob(p: string): Promise<ArrayBuffer> {
return new Promise(function (resolve, reject) {
const q = announcer.getMID();
return API.Task(function (resolve, reject) {
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();
});
}
@ -1278,8 +1299,7 @@ namespace OS {
* @returns {Promise<any>}
*/
export function upload(p: string, d: string): Promise<any> {
return new Promise(function (resolve, reject) {
const q = announcer.getMID();
return API.Task(function (resolve, reject) {
//insert a temporal file selector
const o =
$("<input>")
@ -1287,9 +1307,6 @@ namespace OS {
.attr("multiple", "true");
o.on("change", function () {
const files = (o[0] as HTMLInputElement).files;
const n_files = files.length;
if (n_files > 0)
API.loading(q, p);
const formd = new FormData();
formd.append("path", d);
jQuery.each(files, (i, file) => {
@ -1303,11 +1320,9 @@ namespace OS {
processData: false,
})
.done(function (data) {
API.loaded(q, p, "OK");
resolve(data);
})
.fail(function (j, s, e) {
API.loaded(q, p, "FAIL");
o.remove();
reject(API.throwe(s));
});
@ -1337,44 +1352,6 @@ namespace OS {
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
*
@ -1388,7 +1365,7 @@ namespace OS {
* @returns {Promise<any>} a Promise on the requested data
*/
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 = {
type: "GET",
url: p,
@ -1396,15 +1373,11 @@ namespace OS {
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));
});
});

View File

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