Added an async task manager to the visual console client
Former-commit-id: 4f5c92a976ae976e77cc11505c7a57bf31121930
This commit is contained in:
parent
2b2daf2b8b
commit
a25a08f48d
|
@ -1,5 +1,5 @@
|
|||
// TODO: Add Artica ST header.
|
||||
/* globals jQuery, VisualConsole */
|
||||
/* globals jQuery, VisualConsole, AsyncTaskManager */
|
||||
|
||||
/*
|
||||
* *********************
|
||||
|
@ -28,63 +28,68 @@ function createVisualConsole(
|
|||
updateInterval,
|
||||
onUpdate
|
||||
) {
|
||||
var visualConsole = null;
|
||||
var linkedVCRequest = null;
|
||||
var updateVCRequest = null;
|
||||
|
||||
if (container == null || props == null || items == null) return null;
|
||||
if (baseUrl == null) baseUrl = "";
|
||||
|
||||
// Code which will be executed between intervals.
|
||||
var intervalRef = null;
|
||||
var stopInterval = function() {
|
||||
if (intervalRef !== null) window.clearInterval(intervalRef);
|
||||
};
|
||||
var startInterval = function() {
|
||||
if (updateInterval == null || updateInterval <= 0) return;
|
||||
stopInterval();
|
||||
var visualConsole = null;
|
||||
var asyncTaskManager = new AsyncTaskManager();
|
||||
|
||||
intervalRef = window.setInterval(function() {
|
||||
if (updateVCRequest !== null) updateVCRequest.abort();
|
||||
updateVCRequest = loadVisualConsoleData(
|
||||
baseUrl,
|
||||
visualConsole.props.id,
|
||||
function(error, data) {
|
||||
if (error) {
|
||||
console.log(
|
||||
"[ERROR]",
|
||||
"[VISUAL-CONSOLE-CLIENT]",
|
||||
"[API]",
|
||||
error.message
|
||||
);
|
||||
return;
|
||||
function updateVisualConsole(visualConsoleId, updateInterval) {
|
||||
asyncTaskManager.add(
|
||||
"visual-console",
|
||||
function(done) {
|
||||
var abortable = loadVisualConsoleData(
|
||||
baseUrl,
|
||||
visualConsoleId,
|
||||
function(error, data) {
|
||||
if (error) {
|
||||
console.log(
|
||||
"[ERROR]",
|
||||
"[VISUAL-CONSOLE-CLIENT]",
|
||||
"[API]",
|
||||
error.message
|
||||
);
|
||||
done();
|
||||
return;
|
||||
}
|
||||
|
||||
// Replace Visual Console.
|
||||
if (data != null && data.props != null && data.items != null) {
|
||||
try {
|
||||
var props =
|
||||
typeof data.props === "string"
|
||||
? JSON.parse(data.props)
|
||||
: data.props;
|
||||
var items =
|
||||
typeof data.items === "string"
|
||||
? JSON.parse(data.items)
|
||||
: data.items;
|
||||
|
||||
var prevProps = visualConsole.props;
|
||||
// Update the data structure.
|
||||
visualConsole.props = props;
|
||||
// Update the items.
|
||||
visualConsole.updateElements(items);
|
||||
// Emit the VC update event.
|
||||
if (onUpdate) onUpdate(prevProps, visualConsole.props);
|
||||
} catch (ignored) {} // eslint-disable-line no-empty
|
||||
|
||||
done();
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
// Replace Visual Console.
|
||||
if (data != null && data.props != null && data.items != null) {
|
||||
try {
|
||||
var props =
|
||||
typeof data.props === "string"
|
||||
? JSON.parse(data.props)
|
||||
: data.props;
|
||||
var items =
|
||||
typeof data.items === "string"
|
||||
? JSON.parse(data.items)
|
||||
: data.items;
|
||||
|
||||
var prevProps = visualConsole.props;
|
||||
// Update the data structure.
|
||||
visualConsole.props = props;
|
||||
// Update the items.
|
||||
visualConsole.updateElements(items);
|
||||
// Emit the VC update event.
|
||||
if (onUpdate) onUpdate(prevProps, visualConsole.props);
|
||||
} catch (ignored) {} // eslint-disable-line no-empty
|
||||
return {
|
||||
cancel: function() {
|
||||
abortable.abort();
|
||||
}
|
||||
}
|
||||
);
|
||||
}, updateInterval);
|
||||
};
|
||||
};
|
||||
},
|
||||
updateInterval
|
||||
);
|
||||
|
||||
asyncTaskManager.init("visual-console");
|
||||
}
|
||||
|
||||
// Initialize the Visual Console.
|
||||
try {
|
||||
|
@ -102,64 +107,23 @@ function createVisualConsole(
|
|||
) {
|
||||
// Stop the current link behavior.
|
||||
e.nativeEvent.preventDefault();
|
||||
|
||||
// Fetch and update the old VC with the new.
|
||||
if (linkedVCRequest !== null) linkedVCRequest.abort();
|
||||
linkedVCRequest = loadVisualConsoleData(
|
||||
baseUrl,
|
||||
e.data.linkedLayoutId,
|
||||
function(error, data) {
|
||||
if (error) {
|
||||
console.log(
|
||||
"[ERROR]",
|
||||
"[VISUAL-CONSOLE-CLIENT]",
|
||||
"[API]",
|
||||
error.message
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// Replace Visual Console.
|
||||
if (data != null && data.props != null && data.items != null) {
|
||||
// Cancel the old VC updates.
|
||||
stopInterval();
|
||||
|
||||
try {
|
||||
var props =
|
||||
typeof data.props === "string"
|
||||
? JSON.parse(data.props)
|
||||
: data.props;
|
||||
var items =
|
||||
typeof data.items === "string"
|
||||
? JSON.parse(data.items)
|
||||
: data.items;
|
||||
|
||||
if (updateVCRequest !== null) updateVCRequest.abort();
|
||||
// Save the old props.
|
||||
var prevProps = visualConsole.props;
|
||||
// Update the data structure.
|
||||
visualConsole.props = props;
|
||||
// Update the items.
|
||||
visualConsole.updateElements(items);
|
||||
// Emit the VC update event.
|
||||
if (onUpdate) onUpdate(prevProps, visualConsole.props);
|
||||
} catch (ignored) {} // eslint-disable-line no-empty
|
||||
|
||||
// Restart the updates.
|
||||
startInterval();
|
||||
}
|
||||
}
|
||||
);
|
||||
updateVisualConsole(e.data.linkedLayoutId, updateInterval);
|
||||
}
|
||||
});
|
||||
|
||||
// Start an interval to update the Visual Console.
|
||||
startInterval();
|
||||
updateVisualConsole(props.id, updateInterval);
|
||||
} catch (error) {
|
||||
console.log("[ERROR]", "[VISUAL-CONSOLE-CLIENT]", error.message);
|
||||
}
|
||||
|
||||
return visualConsole;
|
||||
return {
|
||||
visualConsole: visualConsole,
|
||||
changeUpdateInterval: function(updateInterval) {
|
||||
updateVisualConsole(visualConsole.props.id, updateInterval);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -7,8 +7,12 @@
|
|||
|
||||
import "./main.css"; // CSS import.
|
||||
import VisualConsole from "./VisualConsole";
|
||||
import AsyncTaskManager from "./lib/AsyncTaskManager";
|
||||
|
||||
// Export the VisualConsole class to the global object.
|
||||
|
||||
// eslint-disable-next-line
|
||||
(window as any).VisualConsole = VisualConsole;
|
||||
|
||||
// Export the AsyncTaskManager class to the global object.
|
||||
// eslint-disable-next-line
|
||||
(window as any).AsyncTaskManager = AsyncTaskManager;
|
||||
|
|
|
@ -0,0 +1,167 @@
|
|||
import TypedEvent, { Disposable, Listener } from "../TypedEvent";
|
||||
|
||||
interface Cancellable {
|
||||
cancel(): void;
|
||||
}
|
||||
|
||||
type AsyncTaskStatus = "waiting" | "started" | "cancelled" | "finished";
|
||||
type AsyncTaskInitiator = (done: () => void) => Cancellable;
|
||||
|
||||
/**
|
||||
* Defines an async task which can be started and cancelled.
|
||||
* It's possible to observe the status changes of the task.
|
||||
*/
|
||||
class AsyncTask {
|
||||
private readonly taskInitiator: AsyncTaskInitiator;
|
||||
private cancellable: Cancellable = { cancel: () => {} };
|
||||
private _status: AsyncTaskStatus = "waiting";
|
||||
|
||||
// Event manager for status change events.
|
||||
private readonly statusChangeEventManager = new TypedEvent<AsyncTaskStatus>();
|
||||
// List of references to clean the event listeners.
|
||||
private readonly disposables: Disposable[] = [];
|
||||
|
||||
public constructor(taskInitiator: AsyncTaskInitiator) {
|
||||
this.taskInitiator = taskInitiator;
|
||||
}
|
||||
|
||||
/**
|
||||
* Public setter of the `status` property.
|
||||
* @param status.
|
||||
*/
|
||||
public set status(status: AsyncTaskStatus) {
|
||||
this._status = status;
|
||||
this.statusChangeEventManager.emit(status);
|
||||
}
|
||||
|
||||
/**
|
||||
* Public accessor of the `status` property.
|
||||
* @return status.
|
||||
*/
|
||||
public get status() {
|
||||
return this._status;
|
||||
}
|
||||
|
||||
/**
|
||||
* Start the async task.
|
||||
*/
|
||||
public init(): void {
|
||||
this.cancellable = this.taskInitiator(() => {
|
||||
this.status = "finished";
|
||||
});
|
||||
this.status = "started";
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancel the async task.
|
||||
*/
|
||||
public cancel(): void {
|
||||
this.cancellable.cancel();
|
||||
this.status = "cancelled";
|
||||
}
|
||||
|
||||
/**
|
||||
* Add an event handler to the status change.
|
||||
* @param listener Function which is going to be executed when the status changes.
|
||||
*/
|
||||
public onStatusChange(listener: Listener<AsyncTaskStatus>): Disposable {
|
||||
/*
|
||||
* The '.on' function returns a function which will clean the event
|
||||
* listener when executed. We store all the 'dispose' functions to
|
||||
* call them when the item should be cleared.
|
||||
*/
|
||||
const disposable = this.statusChangeEventManager.on(listener);
|
||||
this.disposables.push(disposable);
|
||||
|
||||
return disposable;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrap an async task into another which will execute that task indefinitely
|
||||
* every time the tash finnish and the chosen period ends.
|
||||
* Will last until cancellation.
|
||||
*
|
||||
* @param task Async task to execute.
|
||||
* @param period Time in milliseconds to wait until the next async esecution.
|
||||
*
|
||||
* @return A new async task.
|
||||
*/
|
||||
function asyncPeriodic(task: AsyncTask, period: number): AsyncTask {
|
||||
return new AsyncTask(() => {
|
||||
let ref: number | null = null;
|
||||
|
||||
task.onStatusChange(status => {
|
||||
if (status === "finished") {
|
||||
ref = window.setTimeout(() => {
|
||||
task.init();
|
||||
}, period);
|
||||
}
|
||||
});
|
||||
|
||||
task.init();
|
||||
|
||||
return {
|
||||
cancel: () => {
|
||||
if (ref) clearTimeout(ref);
|
||||
task.cancel();
|
||||
}
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Manages a list of async tasks.
|
||||
*/
|
||||
export default class AsyncTaskManager {
|
||||
private tasks: { [identifier: string]: AsyncTask } = {};
|
||||
|
||||
/**
|
||||
* Adds an async task to the manager.
|
||||
*
|
||||
* @param identifier Unique identifier.
|
||||
* @param taskInitiator Function to initialize the async task.
|
||||
* Should return a structure to cancel the task.
|
||||
* @param period Optional period to repeat the task indefinitely.
|
||||
*/
|
||||
public add(
|
||||
identifier: string,
|
||||
taskInitiator: AsyncTaskInitiator,
|
||||
period: number | null = null
|
||||
): AsyncTask {
|
||||
if (this.tasks[identifier] && this.tasks[identifier].status === "started") {
|
||||
this.tasks[identifier].cancel();
|
||||
}
|
||||
|
||||
const asyncTask =
|
||||
period !== null
|
||||
? asyncPeriodic(new AsyncTask(taskInitiator), period)
|
||||
: new AsyncTask(taskInitiator);
|
||||
|
||||
this.tasks[identifier] = asyncTask;
|
||||
|
||||
return this.tasks[identifier];
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts an async task.
|
||||
*
|
||||
* @param identifier Unique identifier.
|
||||
*/
|
||||
public init(identifier: string) {
|
||||
if (this.tasks[identifier] && this.tasks[identifier].status === "waiting") {
|
||||
this.tasks[identifier].init();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancel a running async task.
|
||||
*
|
||||
* @param identifier Unique identifier.
|
||||
*/
|
||||
public cancel(identifier: string) {
|
||||
if (this.tasks[identifier] && this.tasks[identifier].status === "started") {
|
||||
this.tasks[identifier].cancel();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -6,7 +6,7 @@ import {
|
|||
WithModuleProps,
|
||||
LinkedVisualConsoleProps,
|
||||
LinkedVisualConsolePropsStatus
|
||||
} from "./types";
|
||||
} from "../types";
|
||||
|
||||
/**
|
||||
* Return a number or a default value from a raw value.
|
||||
|
@ -14,8 +14,7 @@ import {
|
|||
* @param defaultValue Default value to use if we cannot extract a valid number.
|
||||
* @return A valid number or the default value.
|
||||
*/
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
export function parseIntOr<T>(value: any, defaultValue: T): number | T {
|
||||
export function parseIntOr<T>(value: unknown, defaultValue: T): number | T {
|
||||
if (typeof value === "number") return value;
|
||||
if (typeof value === "string" && value.length > 0 && !isNaN(parseInt(value)))
|
||||
return parseInt(value);
|
||||
|
@ -28,8 +27,7 @@ export function parseIntOr<T>(value: any, defaultValue: T): number | T {
|
|||
* @param defaultValue Default value to use if we cannot extract a valid number.
|
||||
* @return A valid number or the default value.
|
||||
*/
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
export function parseFloatOr<T>(value: any, defaultValue: T): number | T {
|
||||
export function parseFloatOr<T>(value: unknown, defaultValue: T): number | T {
|
||||
if (typeof value === "number") return value;
|
||||
if (
|
||||
typeof value === "string" &&
|
||||
|
@ -55,8 +53,10 @@ export function stringIsEmpty(value?: string | null): boolean {
|
|||
* @param defaultValue Default value to use if we cannot extract a non empty string.
|
||||
* @return A non empty string or the default value.
|
||||
*/
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
export function notEmptyStringOr<T>(value: any, defaultValue: T): string | T {
|
||||
export function notEmptyStringOr<T>(
|
||||
value: unknown,
|
||||
defaultValue: T
|
||||
): string | T {
|
||||
return typeof value === "string" && value.length > 0 ? value : defaultValue;
|
||||
}
|
||||
|
||||
|
@ -65,8 +65,7 @@ export function notEmptyStringOr<T>(value: any, defaultValue: T): string | T {
|
|||
* @param value Raw value from which we will try to extract the boolean.
|
||||
* @return A valid boolean value. false by default.
|
||||
*/
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
export function parseBoolean(value: any): boolean {
|
||||
export function parseBoolean(value: unknown): boolean {
|
||||
if (typeof value === "boolean") return value;
|
||||
else if (typeof value === "number") return value > 0;
|
||||
else if (typeof value === "string") return value === "1" || value === "true";
|
|
@ -8,7 +8,7 @@ import {
|
|||
humanDate,
|
||||
humanTime,
|
||||
replaceMacros
|
||||
} from "./lib";
|
||||
} from ".";
|
||||
|
||||
describe("function parseIntOr", () => {
|
||||
it("should retrieve valid int or a default value", () => {
|
Loading…
Reference in New Issue