mirror of
https://github.com/pandorafms/pandorafms.git
synced 2025-04-08 18:55:09 +02:00
Visual Console Client: fixed some lint errors and added the digital clock item
Former-commit-id: 643bc4997ec971e8892b4dcd439a2828dc4e441b
This commit is contained in:
parent
527a27940c
commit
7a45d9f62e
@ -1,7 +1,8 @@
|
||||
{
|
||||
"env": {
|
||||
"browser": true,
|
||||
"node": true
|
||||
"node": true,
|
||||
"jest": true
|
||||
},
|
||||
"parser": "@typescript-eslint/parser",
|
||||
"plugins": ["@typescript-eslint", "prettier"],
|
||||
@ -11,6 +12,7 @@
|
||||
"plugin:@typescript-eslint/recommended"
|
||||
],
|
||||
"rules": {
|
||||
"@typescript-eslint/indent": "off"
|
||||
"@typescript-eslint/indent": "off",
|
||||
"no-console": "off"
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
export interface Listener<T> {
|
||||
(event: T): any;
|
||||
(event: T): void;
|
||||
}
|
||||
|
||||
export interface Disposable {
|
||||
@ -11,23 +11,23 @@ export default class TypedEvent<T> {
|
||||
private listeners: Listener<T>[] = [];
|
||||
private listenersOncer: Listener<T>[] = [];
|
||||
|
||||
on = (listener: Listener<T>): Disposable => {
|
||||
public on = (listener: Listener<T>): Disposable => {
|
||||
this.listeners.push(listener);
|
||||
return {
|
||||
dispose: () => this.off(listener)
|
||||
};
|
||||
};
|
||||
|
||||
once = (listener: Listener<T>): void => {
|
||||
public once = (listener: Listener<T>): void => {
|
||||
this.listenersOncer.push(listener);
|
||||
};
|
||||
|
||||
off = (listener: Listener<T>): void => {
|
||||
public off = (listener: Listener<T>): void => {
|
||||
const callbackIndex = this.listeners.indexOf(listener);
|
||||
if (callbackIndex > -1) this.listeners.splice(callbackIndex, 1);
|
||||
};
|
||||
|
||||
emit = (event: T): void => {
|
||||
public emit = (event: T): void => {
|
||||
/** Update any general listeners */
|
||||
this.listeners.forEach(listener => listener(event));
|
||||
|
||||
@ -36,7 +36,5 @@ export default class TypedEvent<T> {
|
||||
this.listenersOncer = [];
|
||||
};
|
||||
|
||||
pipe = (te: TypedEvent<T>): Disposable => {
|
||||
return this.on(e => te.emit(e));
|
||||
};
|
||||
public pipe = (te: TypedEvent<T>): Disposable => this.on(e => te.emit(e));
|
||||
}
|
||||
|
@ -8,6 +8,7 @@ import StaticGraph, { staticGraphPropsDecoder } from "./items/StaticGraph";
|
||||
import Icon, { iconPropsDecoder } from "./items/Icon";
|
||||
import ColorCloud, { colorCloudPropsDecoder } from "./items/ColorCloud";
|
||||
import Group, { groupPropsDecoder } from "./items/Group";
|
||||
import Clock, { clockPropsDecoder } from "./items/Clock";
|
||||
|
||||
// Base properties.
|
||||
export interface VisualConsoleProps extends Size {
|
||||
@ -69,11 +70,12 @@ export function visualConsolePropsDecoder(
|
||||
}
|
||||
|
||||
// TODO: Document.
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
|
||||
function itemInstanceFrom(data: UnknownObject) {
|
||||
const type = parseIntOr(data.type, null);
|
||||
if (type == null) throw new TypeError("missing item type.");
|
||||
|
||||
switch (<VisualConsoleItemType>type) {
|
||||
switch (type as VisualConsoleItemType) {
|
||||
case VisualConsoleItemType.STATIC_GRAPH:
|
||||
return new StaticGraph(staticGraphPropsDecoder(data));
|
||||
case VisualConsoleItemType.MODULE_GRAPH:
|
||||
@ -113,7 +115,7 @@ function itemInstanceFrom(data: UnknownObject) {
|
||||
case VisualConsoleItemType.BARS_GRAPH:
|
||||
throw new TypeError("item not found");
|
||||
case VisualConsoleItemType.CLOCK:
|
||||
throw new TypeError("item not found");
|
||||
return new Clock(clockPropsDecoder(data));
|
||||
case VisualConsoleItemType.COLOR_CLOUD:
|
||||
return new ColorCloud(colorCloudPropsDecoder(data));
|
||||
default:
|
||||
@ -129,7 +131,7 @@ export default class VisualConsole {
|
||||
// Visual Console Item instances.
|
||||
private elements: VisualConsoleItem<VisualConsoleItemProps>[] = [];
|
||||
|
||||
constructor(
|
||||
public constructor(
|
||||
container: HTMLElement,
|
||||
props: VisualConsoleProps,
|
||||
items: UnknownObject[]
|
||||
@ -167,7 +169,7 @@ export default class VisualConsole {
|
||||
* Public accessor of the `props` property.
|
||||
* @return Properties.
|
||||
*/
|
||||
get props(): VisualConsoleProps {
|
||||
public get props(): VisualConsoleProps {
|
||||
return this._props;
|
||||
}
|
||||
|
||||
@ -177,7 +179,7 @@ export default class VisualConsole {
|
||||
* stored props, a render would be fired.
|
||||
* @param newProps
|
||||
*/
|
||||
set props(newProps: VisualConsoleProps) {
|
||||
public set props(newProps: VisualConsoleProps) {
|
||||
const prevProps = this.props;
|
||||
// Update the internal props.
|
||||
this._props = newProps;
|
||||
@ -192,7 +194,7 @@ export default class VisualConsole {
|
||||
* Recreate or update the HTMLElement which represents the Visual Console into the DOM.
|
||||
* @param prevProps If exists it will be used to only DOM updates instead of a full replace.
|
||||
*/
|
||||
render(prevProps: VisualConsoleProps | null = null): void {
|
||||
public render(prevProps: VisualConsoleProps | null = null): void {
|
||||
if (prevProps) {
|
||||
if (prevProps.backgroundURL !== this.props.backgroundURL) {
|
||||
this.containerRef.style.backgroundImage = this.props.backgroundURL;
|
||||
@ -217,7 +219,7 @@ export default class VisualConsole {
|
||||
* @param newSize
|
||||
* @return Whether the size changed or not.
|
||||
*/
|
||||
sizeChanged(prevSize: Size, newSize: Size): boolean {
|
||||
public sizeChanged(prevSize: Size, newSize: Size): boolean {
|
||||
return (
|
||||
prevSize.width !== newSize.width || prevSize.height !== newSize.height
|
||||
);
|
||||
@ -228,7 +230,7 @@ export default class VisualConsole {
|
||||
* @param width
|
||||
* @param height
|
||||
*/
|
||||
resizeElement(width: number, height: number): void {
|
||||
public resizeElement(width: number, height: number): void {
|
||||
this.containerRef.style.width = `${width}px`;
|
||||
this.containerRef.style.height = `${height}px`;
|
||||
}
|
||||
@ -238,7 +240,7 @@ export default class VisualConsole {
|
||||
* @param width
|
||||
* @param height
|
||||
*/
|
||||
resize(width: number, height: number): void {
|
||||
public resize(width: number, height: number): void {
|
||||
this.props = {
|
||||
...this.props, // Object spread: http://es6-features.org/#SpreadOperator
|
||||
width,
|
||||
@ -249,7 +251,7 @@ export default class VisualConsole {
|
||||
/**
|
||||
* To remove the event listeners and the elements from the DOM.
|
||||
*/
|
||||
remove(): void {
|
||||
public remove(): void {
|
||||
this.elements.forEach(e => e.remove()); // Arrow function.
|
||||
this.elements = [];
|
||||
}
|
||||
|
@ -45,16 +45,18 @@ export interface VisualConsoleItemProps extends Position, Size {
|
||||
}
|
||||
|
||||
// FIXME: Fix type compatibility.
|
||||
export type ItemClickEvent<ItemProps extends VisualConsoleItemProps> = {
|
||||
export interface ItemClickEvent<ItemProps extends VisualConsoleItemProps> {
|
||||
// data: ItemProps;
|
||||
data: UnknownObject;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract a valid enum value from a raw label position value.
|
||||
* @param labelPosition Raw value.
|
||||
*/
|
||||
const parseLabelPosition = (labelPosition: any) => {
|
||||
const parseLabelPosition = (
|
||||
labelPosition: any // eslint-disable-line @typescript-eslint/no-explicit-any
|
||||
): VisualConsoleItemProps["labelPosition"] => {
|
||||
switch (labelPosition) {
|
||||
case "up":
|
||||
case "right":
|
||||
@ -123,7 +125,7 @@ abstract class VisualConsoleItem<ItemProps extends VisualConsoleItemProps> {
|
||||
*/
|
||||
abstract createDomElement(): HTMLElement;
|
||||
|
||||
constructor(props: ItemProps) {
|
||||
public constructor(props: ItemProps) {
|
||||
this.itemProps = props;
|
||||
|
||||
/*
|
||||
@ -166,7 +168,7 @@ abstract class VisualConsoleItem<ItemProps extends VisualConsoleItemProps> {
|
||||
* Public accessor of the `props` property.
|
||||
* @return Properties.
|
||||
*/
|
||||
get props(): ItemProps {
|
||||
public get props(): ItemProps {
|
||||
return this.itemProps;
|
||||
}
|
||||
|
||||
@ -176,7 +178,7 @@ abstract class VisualConsoleItem<ItemProps extends VisualConsoleItemProps> {
|
||||
* stored props, a render would be fired.
|
||||
* @param newProps
|
||||
*/
|
||||
set props(newProps: ItemProps) {
|
||||
public set props(newProps: ItemProps) {
|
||||
const prevProps = this.props;
|
||||
// Update the internal props.
|
||||
this.itemProps = newProps;
|
||||
@ -206,7 +208,7 @@ abstract class VisualConsoleItem<ItemProps extends VisualConsoleItemProps> {
|
||||
* To recreate or update the HTMLElement which represents the item into the DOM.
|
||||
* @param prevProps If exists it will be used to only perform DOM updates instead of a full replace.
|
||||
*/
|
||||
render(prevProps: ItemProps | null = null): void {
|
||||
public render(prevProps: ItemProps | null = null): void {
|
||||
// Move box.
|
||||
if (!prevProps || prevProps.x !== this.props.x) {
|
||||
this.elementRef.style.left = `${this.props.x}px`;
|
||||
@ -228,7 +230,7 @@ abstract class VisualConsoleItem<ItemProps extends VisualConsoleItemProps> {
|
||||
/**
|
||||
* To remove the event listeners and the elements from the DOM.
|
||||
*/
|
||||
remove(): void {
|
||||
public remove(): void {
|
||||
// Event listeners.
|
||||
this.disposables.forEach(_ => _.dispose());
|
||||
// VisualConsoleItem extension DOM element.
|
||||
@ -242,7 +244,7 @@ abstract class VisualConsoleItem<ItemProps extends VisualConsoleItemProps> {
|
||||
* @param x Horizontal axis position.
|
||||
* @param y Vertical axis position.
|
||||
*/
|
||||
move(x: number, y: number): void {
|
||||
public move(x: number, y: number): void {
|
||||
// Compare position.
|
||||
if (x === this.props.x && y === this.props.y) return;
|
||||
// Update position. Change itemProps instead of props to avoid re-render.
|
||||
@ -258,7 +260,7 @@ abstract class VisualConsoleItem<ItemProps extends VisualConsoleItemProps> {
|
||||
* @param width Width.
|
||||
* @param height Height.
|
||||
*/
|
||||
resize(width: number, height: number): void {
|
||||
public resize(width: number, height: number): void {
|
||||
// Compare size.
|
||||
if (width === this.props.width && height === this.props.height) return;
|
||||
// Update size. Change itemProps instead of props to avoid re-render.
|
||||
@ -273,7 +275,7 @@ abstract class VisualConsoleItem<ItemProps extends VisualConsoleItemProps> {
|
||||
* To add an event handler to the click of the linked visual console elements.
|
||||
* @param listener Function which is going to be executed when a linked console is clicked.
|
||||
*/
|
||||
onClick(listener: Listener<ItemClickEvent<ItemProps>>): void {
|
||||
public onClick(listener: Listener<ItemClickEvent<ItemProps>>): void {
|
||||
/*
|
||||
* The '.on' function returns a function which will clean the event
|
||||
* listener when executed. We store all the 'dispose' functions to
|
||||
|
@ -5,10 +5,7 @@
|
||||
* https://www.typescriptlang.org/
|
||||
*/
|
||||
|
||||
import VisualConsole, {
|
||||
visualConsolePropsDecoder,
|
||||
VisualConsoleProps
|
||||
} from "./VisualConsole";
|
||||
import VisualConsole, { visualConsolePropsDecoder } from "./VisualConsole";
|
||||
|
||||
// declare global {
|
||||
// interface Window {
|
||||
@ -28,7 +25,7 @@ if (container != null) {
|
||||
width: 800,
|
||||
height: 300,
|
||||
backgroundURL: null,
|
||||
backgroundColor: "#000000",
|
||||
backgroundColor: "rgb(154, 154, 154)",
|
||||
isFavorite: false
|
||||
};
|
||||
|
||||
@ -62,7 +59,7 @@ if (container != null) {
|
||||
const colorCloudRawProps = {
|
||||
// Generic props.
|
||||
id: 2,
|
||||
type: 20, // Static graph = 0
|
||||
type: 20, // Color cloud = 20
|
||||
label: null,
|
||||
labelText: "CLOUD",
|
||||
isLinkEnabled: false,
|
||||
@ -85,11 +82,34 @@ if (container != null) {
|
||||
color: "rgb(100, 50, 245)"
|
||||
};
|
||||
|
||||
const digitalClockRawProps = {
|
||||
// Generic props.
|
||||
id: 2,
|
||||
type: 19, // clock = 19
|
||||
label: null,
|
||||
isLinkEnabled: false,
|
||||
isOnTop: false,
|
||||
parentId: null,
|
||||
aclGroupId: null,
|
||||
// Position props.
|
||||
x: 500,
|
||||
y: 100,
|
||||
// Size props.
|
||||
width: 300,
|
||||
height: 150,
|
||||
// Custom props.
|
||||
clockType: "digital",
|
||||
clockFormat: "datetime",
|
||||
clockTimezone: "Madrid",
|
||||
clockTimezoneOffset: 60,
|
||||
showClockTimezone: true
|
||||
};
|
||||
|
||||
try {
|
||||
const visualConsole = new VisualConsole(
|
||||
container,
|
||||
visualConsolePropsDecoder(rawProps),
|
||||
[staticGraphRawProps, colorCloudRawProps]
|
||||
[staticGraphRawProps, colorCloudRawProps, digitalClockRawProps]
|
||||
);
|
||||
console.log(visualConsole);
|
||||
} catch (error) {
|
||||
|
73
visual_console/src/items/Clock.spec.ts
Normal file
73
visual_console/src/items/Clock.spec.ts
Normal file
@ -0,0 +1,73 @@
|
||||
import Clock, { clockPropsDecoder } from "./Clock";
|
||||
|
||||
const genericRawProps = {
|
||||
id: 1,
|
||||
type: 19, // Clock item = 19
|
||||
label: null,
|
||||
isLinkEnabled: false,
|
||||
isOnTop: false,
|
||||
parentId: null,
|
||||
aclGroupId: null
|
||||
};
|
||||
|
||||
const positionRawProps = {
|
||||
x: 100,
|
||||
y: 50
|
||||
};
|
||||
|
||||
const sizeRawProps = {
|
||||
width: 100,
|
||||
height: 100
|
||||
};
|
||||
|
||||
const digitalClockProps = {
|
||||
clockType: "digital",
|
||||
clockFormat: "datetime",
|
||||
clockTimezone: "Madrid",
|
||||
clockTimezoneOffset: 60
|
||||
};
|
||||
|
||||
const linkedModuleProps = {
|
||||
// Agent props.
|
||||
agentId: null,
|
||||
agentName: null,
|
||||
// Module props.
|
||||
moduleId: null,
|
||||
moduleName: null
|
||||
};
|
||||
|
||||
describe("Clock item", () => {
|
||||
const clockInstance = new Clock(
|
||||
clockPropsDecoder({
|
||||
...genericRawProps,
|
||||
...positionRawProps,
|
||||
...sizeRawProps,
|
||||
...linkedModuleProps,
|
||||
...digitalClockProps
|
||||
})
|
||||
);
|
||||
|
||||
it("should have the digital-clock class", () => {
|
||||
expect(
|
||||
clockInstance.elementRef.getElementsByClassName("digital-clock").length
|
||||
).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
describe("getDate function", () => {
|
||||
it("should return the date with padded 0's", () => {
|
||||
const expected = "01/02/0123";
|
||||
const date = new Date(`02/01/0123 12:00:00`);
|
||||
const digitalDate = clockInstance.getDigitalDate(date);
|
||||
expect(digitalDate).toBe(expected);
|
||||
});
|
||||
});
|
||||
|
||||
describe("getTime function", () => {
|
||||
it("should return the time with padded 0's when hours/minutes/seconds are less than 10", () => {
|
||||
const expected = "01:02:03";
|
||||
const date = new Date(`01/01/1970 ${expected}`);
|
||||
const digitalTime = clockInstance.getDigitalTime(date);
|
||||
expect(digitalTime).toBe(expected);
|
||||
});
|
||||
});
|
||||
});
|
180
visual_console/src/items/Clock.ts
Normal file
180
visual_console/src/items/Clock.ts
Normal file
@ -0,0 +1,180 @@
|
||||
import { LinkedVisualConsoleProps, UnknownObject } from "../types";
|
||||
|
||||
import {
|
||||
linkedVCPropsDecoder,
|
||||
parseIntOr,
|
||||
padLeft,
|
||||
parseBoolean
|
||||
} from "../lib";
|
||||
|
||||
import VisualConsoleItem, {
|
||||
VisualConsoleItemProps,
|
||||
itemBasePropsDecoder,
|
||||
VisualConsoleItemType
|
||||
} from "../VisualConsoleItem";
|
||||
|
||||
export type ClockProps = {
|
||||
type: VisualConsoleItemType.CLOCK;
|
||||
clockType: "analogic" | "digital";
|
||||
clockFormat: "datetime" | "time";
|
||||
clockTimezone: string;
|
||||
clockTimezoneOffset: number; // Offset of the timezone to UTC in seconds.
|
||||
showClockTimezone: boolean;
|
||||
} & VisualConsoleItemProps &
|
||||
LinkedVisualConsoleProps;
|
||||
|
||||
/**
|
||||
* Extract a valid enum value from a raw unknown value.
|
||||
* @param clockType Raw value.
|
||||
*/
|
||||
const parseClockType = (
|
||||
clockType: any // eslint-disable-line @typescript-eslint/no-explicit-any
|
||||
): ClockProps["clockType"] => {
|
||||
switch (clockType) {
|
||||
case "analogic":
|
||||
case "digital":
|
||||
return clockType;
|
||||
default:
|
||||
return "analogic";
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Extract a valid enum value from a raw unknown value.
|
||||
* @param clockFormat Raw value.
|
||||
*/
|
||||
const parseClockFormat = (
|
||||
clockFormat: any // eslint-disable-line @typescript-eslint/no-explicit-any
|
||||
): ClockProps["clockFormat"] => {
|
||||
switch (clockFormat) {
|
||||
case "datetime":
|
||||
case "date":
|
||||
case "time":
|
||||
return clockFormat;
|
||||
default:
|
||||
return "datetime";
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Build a valid typed object from a raw object.
|
||||
* This will allow us to ensure the type safety.
|
||||
*
|
||||
* @param data Raw object.
|
||||
* @return An object representing the clock props.
|
||||
* @throws Will throw a TypeError if some property
|
||||
* is missing from the raw object or have an invalid type.
|
||||
*/
|
||||
export function clockPropsDecoder(data: UnknownObject): ClockProps | never {
|
||||
if (
|
||||
typeof data.clockTimezone !== "string" ||
|
||||
data.clockTimezone.length === 0
|
||||
) {
|
||||
throw new TypeError("invalid timezone.");
|
||||
}
|
||||
|
||||
return {
|
||||
...itemBasePropsDecoder(data), // Object spread. It will merge the properties of the two objects.
|
||||
type: VisualConsoleItemType.CLOCK,
|
||||
clockType: parseClockType(data.clockType),
|
||||
clockFormat: parseClockFormat(data.clockFormat),
|
||||
clockTimezone: data.clockTimezone,
|
||||
clockTimezoneOffset: parseIntOr(data.clockTimezoneOffset, 0),
|
||||
showClockTimezone: parseBoolean(data.showClockTimezone),
|
||||
...linkedVCPropsDecoder(data) // Object spread. It will merge the properties of the two objects.
|
||||
};
|
||||
}
|
||||
|
||||
export default class Clock extends VisualConsoleItem<ClockProps> {
|
||||
public createDomElement(): HTMLElement {
|
||||
switch (this.props.clockType) {
|
||||
case "analogic":
|
||||
throw new Error("not implemented.");
|
||||
case "digital":
|
||||
return this.createDigitalClock();
|
||||
}
|
||||
}
|
||||
|
||||
public createDigitalClock(): HTMLElement {
|
||||
const element: HTMLDivElement = document.createElement("div");
|
||||
element.className = "digital-clock";
|
||||
|
||||
// The proportion of the clock should be (height * 2 = width) aproximately.
|
||||
const width =
|
||||
this.props.height * 2 < this.props.width
|
||||
? this.props.height * 2
|
||||
: this.props.width;
|
||||
this.props.clockTimezone = "Madrid";
|
||||
const baseTimeFontSize = 20; // Per 100px of width.
|
||||
const dateFontSizeMultiplier = 0.5;
|
||||
const tzFontSizeMultiplier = 6 / this.props.clockTimezone.length;
|
||||
const timeFontSize = (baseTimeFontSize * width) / 100;
|
||||
const dateFontSize =
|
||||
(baseTimeFontSize * dateFontSizeMultiplier * width) / 100;
|
||||
const tzFontSize = Math.min(
|
||||
(baseTimeFontSize * tzFontSizeMultiplier * width) / 100,
|
||||
(width / 100) * 10
|
||||
);
|
||||
|
||||
// Date.
|
||||
if (this.props.clockFormat === "datetime") {
|
||||
const dateElem: HTMLSpanElement = document.createElement("span");
|
||||
dateElem.className = "date";
|
||||
dateElem.textContent = this.getDigitalDate();
|
||||
dateElem.style.fontSize = `${dateFontSize}px`;
|
||||
element.append(dateElem);
|
||||
}
|
||||
|
||||
// Time.
|
||||
const timeElem: HTMLSpanElement = document.createElement("span");
|
||||
timeElem.className = "time";
|
||||
timeElem.textContent = this.getDigitalTime();
|
||||
timeElem.style.fontSize = `${timeFontSize}px`;
|
||||
element.append(timeElem);
|
||||
|
||||
// Timezone name.
|
||||
if (this.props.showClockTimezone) {
|
||||
const tzElem: HTMLSpanElement = document.createElement("span");
|
||||
tzElem.className = "timezone";
|
||||
tzElem.textContent = this.props.clockTimezone;
|
||||
tzElem.style.fontSize = `${tzFontSize}px`;
|
||||
element.append(tzElem);
|
||||
}
|
||||
|
||||
return element;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate the current date using the timezone offset stored into the properties.
|
||||
* @return The current date.
|
||||
*/
|
||||
public getDate(): Date {
|
||||
const d = new Date();
|
||||
const targetTZOffset = this.props.clockTimezoneOffset * 60 * 1000; // In ms.
|
||||
const localTZOffset = d.getTimezoneOffset() * 60 * 1000; // In ms.
|
||||
const utimestamp = d.getTime() + targetTZOffset + localTZOffset;
|
||||
|
||||
return new Date(utimestamp);
|
||||
}
|
||||
|
||||
public getDigitalDate(initialDate: Date | null = null): string {
|
||||
const date = initialDate || this.getDate();
|
||||
// Use getDate, getDay returns the week day.
|
||||
const day = padLeft(date.getDate(), 2, 0);
|
||||
// The getMonth function returns the month starting by 0.
|
||||
const month = padLeft(date.getMonth() + 1, 2, 0);
|
||||
const year = padLeft(date.getFullYear(), 4, 0);
|
||||
|
||||
// Format: 'd/m/Y'.
|
||||
return `${day}/${month}/${year}`;
|
||||
}
|
||||
|
||||
public getDigitalTime(initialDate: Date | null = null): string {
|
||||
const date = initialDate || this.getDate();
|
||||
const hours = padLeft(date.getHours(), 2, 0);
|
||||
const minutes = padLeft(date.getMinutes(), 2, 0);
|
||||
const seconds = padLeft(date.getSeconds(), 2, 0);
|
||||
|
||||
return `${hours}:${minutes}:${seconds}`;
|
||||
}
|
||||
}
|
@ -34,7 +34,7 @@ const linkedModuleProps = {
|
||||
};
|
||||
|
||||
describe("Color cloud item", () => {
|
||||
const groupInstance = new ColorCloud(
|
||||
const colorCloudInstance = new ColorCloud(
|
||||
colorCloudPropsDecoder({
|
||||
...genericRawProps,
|
||||
...positionRawProps,
|
||||
@ -46,7 +46,7 @@ describe("Color cloud item", () => {
|
||||
|
||||
it("should have the color-cloud class", () => {
|
||||
expect(
|
||||
groupInstance.elementRef.getElementsByClassName("color-cloud").length
|
||||
colorCloudInstance.elementRef.getElementsByClassName("color-cloud").length
|
||||
).toBeGreaterThan(0);
|
||||
});
|
||||
});
|
||||
|
@ -49,7 +49,7 @@ export function colorCloudPropsDecoder(
|
||||
const svgNS = "http://www.w3.org/2000/svg";
|
||||
|
||||
export default class ColorCloud extends VisualConsoleItem<ColorCloudProps> {
|
||||
createDomElement(): HTMLElement {
|
||||
public createDomElement(): HTMLElement {
|
||||
const container: HTMLDivElement = document.createElement("div");
|
||||
container.className = "color-cloud";
|
||||
|
||||
@ -59,7 +59,7 @@ export default class ColorCloud extends VisualConsoleItem<ColorCloudProps> {
|
||||
return container;
|
||||
}
|
||||
|
||||
createSvgElement(): SVGSVGElement {
|
||||
public createSvgElement(): SVGSVGElement {
|
||||
const gradientId = `grad_${this.props.id}`;
|
||||
// SVG container.
|
||||
const svg = document.createElementNS(svgNS, "svg");
|
||||
@ -108,12 +108,11 @@ export default class ColorCloud extends VisualConsoleItem<ColorCloudProps> {
|
||||
/**
|
||||
* @override VisualConsoleItem.resize
|
||||
* To resize the item.
|
||||
* @param width Width.
|
||||
* @param height Height.
|
||||
* @param diameter Diameter.
|
||||
*/
|
||||
resize(width: number, height: number): void {
|
||||
// Resize parent. Use only the width, cause this element only needs a diameter.
|
||||
super.resize(width, width);
|
||||
public resize(diameter: number): void {
|
||||
// Resize parent. Use the diameter as width and height.
|
||||
super.resize(diameter, diameter);
|
||||
|
||||
// Get SVG element.
|
||||
const svgElement = this.elementRef.getElementsByTagName("svg").item(0);
|
||||
|
@ -42,7 +42,7 @@ export function groupPropsDecoder(data: UnknownObject): GroupProps | never {
|
||||
}
|
||||
|
||||
export default class Group extends VisualConsoleItem<GroupProps> {
|
||||
createDomElement(): HTMLElement {
|
||||
public createDomElement(): HTMLElement {
|
||||
const img: HTMLImageElement = document.createElement("img");
|
||||
img.className = "group";
|
||||
img.src = this.props.imageSrc;
|
||||
|
@ -37,7 +37,7 @@ export function iconPropsDecoder(data: UnknownObject): IconProps | never {
|
||||
}
|
||||
|
||||
export default class Icon extends VisualConsoleItem<IconProps> {
|
||||
createDomElement(): HTMLElement {
|
||||
public createDomElement(): HTMLElement {
|
||||
const img: HTMLImageElement = document.createElement("img");
|
||||
img.className = "icon";
|
||||
img.src = this.props.imageSrc;
|
||||
|
@ -23,7 +23,9 @@ export type StaticGraphProps = {
|
||||
* Extract a valid enum value from a raw unknown value.
|
||||
* @param showLastValueTooltip Raw value.
|
||||
*/
|
||||
const parseShowLastValueTooltip = (showLastValueTooltip: any) => {
|
||||
const parseShowLastValueTooltip = (
|
||||
showLastValueTooltip: any // eslint-disable-line @typescript-eslint/no-explicit-any
|
||||
): StaticGraphProps["showLastValueTooltip"] => {
|
||||
switch (showLastValueTooltip) {
|
||||
case "default":
|
||||
case "enabled":
|
||||
@ -61,7 +63,7 @@ export function staticGraphPropsDecoder(
|
||||
}
|
||||
|
||||
export default class StaticGraph extends VisualConsoleItem<StaticGraphProps> {
|
||||
createDomElement(): HTMLElement {
|
||||
public createDomElement(): HTMLElement {
|
||||
const img: HTMLImageElement = document.createElement("img");
|
||||
img.className = "static-graph";
|
||||
img.src = this.props.imageSrc;
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { parseIntOr } from "./lib";
|
||||
import { parseIntOr, padLeft } from "./lib";
|
||||
|
||||
describe("function parseIntOr", () => {
|
||||
it("should retrieve valid int or a default value", () => {
|
||||
@ -11,3 +11,18 @@ describe("function parseIntOr", () => {
|
||||
expect(parseIntOr(1, null)).toBe(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe("function padLeft", () => {
|
||||
it("should pad properly", () => {
|
||||
expect(padLeft(1, 2, 0)).toBe("01");
|
||||
expect(padLeft(1, 4, 0)).toBe("0001");
|
||||
expect(padLeft(1, 4, "0")).toBe("0001");
|
||||
expect(padLeft("1", 4, "0")).toBe("0001");
|
||||
expect(padLeft(10, 4, 0)).toBe("0010");
|
||||
expect(padLeft("bar", 6, "foo")).toBe("foobar");
|
||||
expect(padLeft("bar", 11, "foo")).toBe("foofoofobar");
|
||||
expect(padLeft("bar", 4, "foo")).toBe("fbar");
|
||||
expect(padLeft("bar", 2, "foo")).toBe("ar");
|
||||
expect(padLeft("bar", 3, "foo")).toBe("bar");
|
||||
});
|
||||
});
|
||||
|
@ -14,6 +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 {
|
||||
if (typeof value === "number") return value;
|
||||
if (typeof value === "string" && value.length > 0 && !isNaN(parseInt(value)))
|
||||
@ -26,6 +27,7 @@ export function parseIntOr<T>(value: any, defaultValue: T): number | 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 {
|
||||
if (typeof value === "boolean") return value;
|
||||
else if (typeof value === "number") return value > 0;
|
||||
@ -33,6 +35,42 @@ export function parseBoolean(value: any): boolean {
|
||||
else return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Pad the current string with another string (multiple times, if needed)
|
||||
* until the resulting string reaches the given length.
|
||||
* The padding is applied from the start (left) of the current string.
|
||||
* @param value Text that needs to be padded.
|
||||
* @param length Length of the returned text.
|
||||
* @param pad Text to add.
|
||||
* @return Padded text.
|
||||
*/
|
||||
export function padLeft(
|
||||
value: string | number,
|
||||
length: number,
|
||||
pad: string | number = " "
|
||||
): string {
|
||||
if (typeof value === "number") value = `${value}`;
|
||||
if (typeof pad === "number") pad = `${pad}`;
|
||||
|
||||
const diffLength = length - value.length;
|
||||
if (diffLength === 0) return value;
|
||||
if (diffLength < 0) return value.substr(Math.abs(diffLength));
|
||||
|
||||
if (diffLength === pad.length) return `${pad}${value}`;
|
||||
if (diffLength < pad.length) return `${pad.substring(0, diffLength)}${value}`;
|
||||
|
||||
const repeatTimes = Math.floor(diffLength / pad.length);
|
||||
const restLength = diffLength - pad.length * repeatTimes;
|
||||
|
||||
let newPad = "";
|
||||
for (let i = 0; i < repeatTimes; i++) newPad += pad;
|
||||
|
||||
if (restLength === 0) return `${newPad}${value}`;
|
||||
return `${newPad}${pad.substring(0, restLength)}${value}`;
|
||||
}
|
||||
|
||||
/* Decoders */
|
||||
|
||||
/**
|
||||
* Build a valid typed object from a raw object.
|
||||
* @param data Raw object.
|
||||
@ -125,7 +163,7 @@ export function linkedVCPropsDecoder(
|
||||
linkedLayoutStatusType: "default"
|
||||
};
|
||||
switch (data.linkedLayoutStatusType) {
|
||||
case "weight":
|
||||
case "weight": {
|
||||
const weight = parseIntOr(data.linkedLayoutStatusTypeWeight, null);
|
||||
if (weight == null)
|
||||
throw new TypeError("invalid status calculation properties.");
|
||||
@ -136,7 +174,8 @@ export function linkedVCPropsDecoder(
|
||||
linkedLayoutStatusTypeWeight: weight
|
||||
};
|
||||
break;
|
||||
case "service":
|
||||
}
|
||||
case "service": {
|
||||
const warningThreshold = parseIntOr(
|
||||
data.linkedLayoutStatusTypeWarningThreshold,
|
||||
null
|
||||
@ -155,6 +194,7 @@ export function linkedVCPropsDecoder(
|
||||
linkedLayoutStatusTypeCriticalThreshold: criticalThreshold
|
||||
};
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
const linkedLayoutBaseProps = {
|
||||
|
@ -1,6 +1,6 @@
|
||||
export type UnknownObject = {
|
||||
[key: string]: any;
|
||||
};
|
||||
export interface UnknownObject {
|
||||
[key: string]: any; // eslint-disable-line @typescript-eslint/no-explicit-any
|
||||
}
|
||||
|
||||
export interface Position {
|
||||
x: number;
|
||||
|
BIN
visual_console/static/alarm-clock.ttf
Executable file
BIN
visual_console/static/alarm-clock.ttf
Executable file
Binary file not shown.
@ -6,3 +6,32 @@
|
||||
width: inherit;
|
||||
height: inherit;
|
||||
}
|
||||
|
||||
/* Clock item */
|
||||
|
||||
@font-face {
|
||||
font-family: Alarm Clock;
|
||||
src: url(alarm-clock.ttf);
|
||||
}
|
||||
|
||||
.visual-console-item .digital-clock {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
justify-items: center;
|
||||
align-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.visual-console-item .digital-clock > span {
|
||||
font-family: "Alarm Clock", "Courier New", Courier, monospace;
|
||||
font-size: 50px;
|
||||
}
|
||||
|
||||
.visual-console-item .digital-clock > span.date {
|
||||
font-size: 25px;
|
||||
}
|
||||
|
||||
.visual-console-item .digital-clock > span.timezone {
|
||||
font-size: 25px;
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user