iobroker.lovelace
Version:
With this adapter you can build visualization for ioBroker with Home Assistant Lovelace UI
541 lines (540 loc) • 20.6 kB
JavaScript
"use strict";
var __create = Object.create;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __getProtoOf = Object.getPrototypeOf;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
// If the importer is in node compatibility mode or this is not an ESM
// file that has been converted to a CommonJS file using a Babel-
// compatible transform (i.e. "__esModule" has not been set), then set
// "default" to the CommonJS "module.exports" for node compatibility.
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
mod
));
var import_node_crypto = __toESM(require("node:crypto"));
var import_todoEntity = require("../entities/todoEntity");
const WS_OPEN = 1;
const TodoItemStatus = {
NeedsAction: "needs_action",
Completed: "completed"
};
class TodoModule {
adapter;
entityData;
getWebsocketServer;
todoListCache;
server;
/**
* Create the todo module.
*
* @param options - options
* @param options.adapter - ioBroker adapter instance
* @param options.entityData - shared entity data singleton
* @param options.getWebsocketServer - function returning the websocket server
* @param options.server - server object for sending responses and updates
*/
constructor(options) {
this.adapter = options.adapter;
this.entityData = options.entityData;
this.getWebsocketServer = options.getWebsocketServer;
this.todoListCache = {};
this.server = options.server;
}
/**
* Store todo list in ioBroker state.
*
* @param todoList - the todo list to persist
*/
_storeTodolist(todoList) {
clearTimeout(todoList.timeout);
todoList.timeout = setTimeout(async () => {
await this.adapter.setForeignStateAsync(todoList.ioBrokerId, JSON.stringify(todoList.items), true);
}, 500);
}
/**
* Publish update to all subscribed clients.
*
* @param todoList - the updated todo list to publish
*/
_publishUpdate(todoList) {
const websocketServer = this.getWebsocketServer();
if (websocketServer) {
for (const client of websocketServer.clients) {
if (client._subscribes.todo && client.readyState === WS_OPEN) {
for (const parameters of client._subscribes.todo) {
if (parameters.entityId === todoList.entity.entity_id) {
const event = {
event: { items: todoList.items },
type: "event",
id: Number(parameters.id)
};
client.send(JSON.stringify(event));
}
}
}
}
}
}
/**
* Process message for todo module.
*
* @param ws - websocket connection to the client
* @param message - the message from the frontend
*/
async processMessage(ws, message) {
const msgType = message.type;
if (msgType && (msgType.startsWith("todo/") || msgType.startsWith("shopping_list/"))) {
if (msgType === "todo/item/subscribe") {
ws._subscribes.todo = ws._subscribes.todo || [];
const result = [
{ id: message.id, type: "result", success: true, result: null },
{ id: message.id, type: "event", event: { items: [] } }
];
const parameters = {
entityId: message.entity_id,
id: Number(message.id)
};
ws._subscribes.todo.push(parameters);
const entity = this.entityData.entityId2Entity[message.entity_id];
if (entity) {
const ioBrokerId = entity.context.STATE.getId || entity.context.STATE.setId;
const state = await this.adapter.getForeignStateAsync(ioBrokerId);
if (state && state.val) {
try {
result[1].event.items = JSON.parse(
state.val
);
} catch (e) {
this.adapter.log.warn(
`Cannot parse todo items of ${ioBrokerId}: ${String(state.val)}, ${String(e)}`
);
}
}
} else {
this.adapter.log.warn(`Unknown todo entity: ${String(message.entity_id)}`);
}
console.dir(result);
ws.send(JSON.stringify(result));
} else if (msgType === "todo/item/move") {
const entity = this.entityData.entityId2Entity[message.entity_id];
const todoList = await this._getTodoList(entity);
const item = todoList.items.find((item2) => item2.uid === message.uid);
todoList.items = todoList.items.filter((item2) => item2.uid !== message.uid);
const previousItem = todoList.items.find((item2) => item2.uid === message.previous_uid);
if (previousItem) {
const index = todoList.items.indexOf(previousItem);
todoList.items.splice(index + 1, 0, item);
} else {
todoList.items.unshift(item);
}
this._storeTodolist(todoList);
this._publishUpdate(todoList);
} else if (msgType === "shopping_list/items/add") {
const entity = this.entityData.entityId2Entity["todo.shoppinglist"];
const todoList = await this._getTodoList(entity);
todoList.items.push({
name: message.name,
uid: import_node_crypto.default.randomUUID(),
status: TodoItemStatus.NeedsAction,
due: null,
description: null
});
this._storeTodolist(todoList);
this._publishUpdate(todoList);
this.server._sendResponse(ws, message.id);
this.server._sendUpdate("shopping_list_updated");
} else if (msgType === "shopping_list/items/clear") {
const entity = this.entityData.entityId2Entity["todo.shoppinglist"];
const todoList = await this._getTodoList(entity);
todoList.items = [];
this._storeTodolist(todoList);
this._publishUpdate(todoList);
this.server._sendResponse(ws, message.id);
this.server._sendUpdate("shopping_list_updated");
} else if (msgType === "shopping_list/items/update") {
const entity = this.entityData.entityId2Entity["todo.shoppinglist"];
const todoList = await this._getTodoList(entity);
const item = todoList.items.find((item2) => item2.uid === message.item_id);
if (item) {
if (message.name !== void 0) {
item.summary = message.name;
}
if (message.complete !== void 0) {
item.status = message.complete ? TodoItemStatus.Completed : TodoItemStatus.NeedsAction;
}
this._storeTodolist(todoList);
this._publishUpdate(todoList);
this.server._sendResponse(ws, message.id);
this.server._sendUpdate("shopping_list_updated");
}
}
return true;
}
return false;
}
/**
* Remove subscription for todo list from client.
*
* @param ws - websocket connection to the client
* @param msgId - the subscription message id to remove
*/
removeSubscription(ws, msgId) {
if (ws._subscribes && ws._subscribes.todo) {
ws._subscribes.todo = ws._subscribes.todo.filter(
(parameters) => parameters.id !== msgId
);
}
}
/**
* Get the todo list for an entity.
*
* @param entity - the entity whose todo list to retrieve
*/
async _getTodoList(entity) {
let todoList = this.todoListCache[entity.entity_id];
if (!todoList) {
const ioBrokerId = entity.context.STATE.getId || entity.context.STATE.setId;
todoList = { ioBrokerId, items: [], entity };
const state = await this.adapter.getForeignStateAsync(ioBrokerId);
if (state && state.val) {
try {
todoList.items = JSON.parse(state.val);
if (!todoList.items || !Array.isArray(todoList.items)) {
todoList.items = [];
}
for (const item of todoList.items) {
if (item.name && !item.summary) {
item.summary = item.name;
delete item.name;
}
if (item.id && !item.uid) {
item.uid = item.id;
delete item.id;
}
if (item.complete !== void 0 && !item.status) {
item.status = item.complete ? TodoItemStatus.Completed : TodoItemStatus.NeedsAction;
delete item.complete;
}
item.due = item.due || null;
item.description = item.description || null;
}
entity.updateTimestamp(state, true);
} catch (e) {
this.adapter.log.warn(
`Cannot parse todo items of ${ioBrokerId}: ${String(state.val)}, ${String(e)}`
);
}
}
this.todoListCache[entity.entity_id] = todoList;
}
return todoList;
}
/**
* Process service call for todo module.
*
* @param ws - websocket connection (passed to server._sendResponse)
* @param data - service call data from the frontend
*/
async processServiceCall(ws, data) {
if (data.domain === "todo") {
const serviceData = data.service_data;
const target = data.target;
if (data.service === "add_item") {
const entity = this.entityData.entityId2Entity[target.entity_id];
if (entity) {
const todoList = await this._getTodoList(entity);
const item = {
summary: serviceData.item,
uid: import_node_crypto.default.randomUUID(),
status: TodoItemStatus.NeedsAction,
due: null,
description: null
};
todoList.items.push(item);
entity.state = todoList.items.length;
this._storeTodolist(todoList);
this._publishUpdate(todoList);
this.server._sendResponse(ws, data.id);
} else {
this.adapter.log.warn(`Unknown todo entity: ${String(target.entity_id)}`);
}
} else if (data.service === "update_item") {
const entity = this.entityData.entityId2Entity[target.entity_id];
if (entity) {
const todoList = await this._getTodoList(entity);
const item = todoList.items.find((item2) => item2.uid === serviceData.item);
if (item) {
item.summary = serviceData.rename || item.summary;
item.status = serviceData.status || item.status;
item.due = serviceData.due || item.due;
item.description = serviceData.description || item.description;
this._storeTodolist(todoList);
this._publishUpdate(todoList);
this.server._sendResponse(ws, data.id);
} else {
this.adapter.log.warn(`Unknown todo item: ${String(serviceData.item)}`);
}
} else {
this.adapter.log.warn(`Unknown todo entity: ${String(target.entity_id)}`);
}
} else if (data.service === "remove_item") {
const entity = this.entityData.entityId2Entity[target.entity_id];
if (entity) {
const todoList = await this._getTodoList(entity);
const itemsToRemove = serviceData.item;
todoList.items = todoList.items.filter((item) => !itemsToRemove.includes(item.uid));
this._storeTodolist(todoList);
this._publishUpdate(todoList);
this.server._sendResponse(ws, data.id);
} else {
this.adapter.log.warn(`Unknown todo entity: ${String(target.entity_id)}`);
}
} else if (data.service === "remove_completed_items") {
const entity = this.entityData.entityId2Entity[target.entity_id];
if (entity) {
const todoList = await this._getTodoList(entity);
todoList.items = todoList.items.filter((item) => item.status !== TodoItemStatus.Completed);
this._storeTodolist(todoList);
this._publishUpdate(todoList);
this.server._sendResponse(ws, data.id);
} else {
this.adapter.log.warn(`Unknown todo entity: ${String(target.entity_id)}`);
}
} else if (data.service === "get_items") {
const entity = this.entityData.entityId2Entity[target.entity_id];
if (entity) {
const todoList = await this._getTodoList(entity);
const filterStati = serviceData.status || [TodoItemStatus.NeedsAction];
const filteredItems = todoList.items.filter((item) => filterStati.includes(item.status));
this.server._sendResponse(ws, data.id, filteredItems);
} else {
this.adapter.log.warn(`Unknown todo entity: ${String(target.entity_id)}`);
}
} else {
this.adapter.log.warn(`Unknown todo service: ${String(data.service)}`);
return false;
}
return true;
}
return false;
}
/**
* Handle state change, i.e., update the todo list.
*
* @param id - ioBroker state id that changed
* @param state - the new state value
*/
onStateChange(id, state) {
const entities = this.entityData.iobID2entity[id];
if (entities) {
for (const entity of entities) {
if (entity.context.type === "todo") {
if (id === entity.context.STATE.getId || id === entity.context.STATE.setId) {
if (!state || !state.ack) {
let items = [];
try {
if (state && state.val) {
items = JSON.parse(state.val);
}
} catch (e) {
this.adapter.log.warn(
`Cannot parse todo items of ${id}: ${String(state == null ? void 0 : state.val)}, ${String(e)}`
);
}
entity.state = items.length;
const todoList = this.todoListCache[entity.entity_id] || {
ioBrokerId: id,
items,
entity
};
todoList.items = items;
this._publishUpdate(this.todoListCache[entity.entity_id]);
this._storeTodolist(todoList);
}
}
}
}
}
}
/**
* Create a todo-list entity.
*
* @param iobId - ioBroker state id (the main object)
* @param iobObj - ioBroker object definition
* @param entity - already created entity to augment
* @param _objects - ioBroker objects cache (unused)
* @param _customPart - custom settings (unused)
*/
async processManualEntity(iobId, iobObj, entity, _objects, _customPart) {
import_todoEntity.TodoEntity.augment(entity);
const todoList = await this._getTodoList(entity);
entity.state = String(todoList.items.length);
return [entity];
}
/**
* Initialize the module.
*/
async init() {
const entityShoppingList = this.entityData.entityId2Entity["todo.shoppinglist"];
if (!entityShoppingList) {
const iobObj = await this.adapter.getObjectAsync("control.shopping_list");
const entity = new import_todoEntity.TodoEntity("Shopping List", iobObj, "todo.shoppinglist");
const todoList = await this._getTodoList(entity);
entity.state = String(todoList.items.length);
this.entityData.entities.push(entity);
this.entityData.entityId2Entity[entity.entity_id] = entity;
this.entityData.iobID2entity[iobObj._id] = [entity];
}
this.adapter.log.debug("modules/todo: init done.");
}
/**
* End tasks of the module.
*/
cleanup() {
for (const todoList of Object.values(this.todoListCache)) {
clearTimeout(todoList.timeout);
}
}
/**
* Add the todo service to the services object.
*
* @param services services to report to frontend as available.
*/
augmentServices(services) {
services.todo = {
add_item: {
name: "Add to-do list item",
description: "Add a new to-do list item.",
fields: {
item: {
required: true,
example: "Submit income tax return",
selector: { text: null },
name: "Item name",
description: "The name that represents the to-do item."
},
due_date: {
example: "2023-11-17",
selector: { date: null },
name: "Due date",
description: "The date the to-do item is expected to be completed."
},
due_datetime: {
example: "2023-11-17 13:30:00",
selector: { datetime: null },
name: "Due date and time",
description: "The date and time the to-do item is expected to be completed."
},
description: {
example: "A more complete description of the to-do item than that provided by the summary.",
selector: { text: null },
name: "Description",
description: "A more complete description of the to-do item than provided by the item name."
}
},
target: { entity: [{ domain: ["todo"], supported_features: [1] }] }
},
update_item: {
name: "Update to-do list item",
description: "Update an existing to-do list item based on its name.",
fields: {
item: {
required: true,
example: "Submit income tax return",
selector: { text: null },
name: "Item name",
description: "The name for the to-do list item."
},
rename: {
example: "Something else",
selector: { text: null },
name: "Rename item",
description: "The new name of the to-do item"
},
status: {
example: "needs_action",
selector: {
select: {
translation_key: "status",
options: ["needs_action", "completed"]
}
},
name: "Set status",
description: "A status or confirmation of the to-do item."
},
due_date: {
example: "2023-11-17",
selector: { date: null },
name: "Due date",
description: "The date the to-do item is expected to be completed."
},
due_datetime: {
example: "2023-11-17 13:30:00",
selector: { datetime: null },
name: "Due date and time",
description: "The date and time the to-do item is expected to be completed."
},
description: {
example: "A more complete description of the to-do item than that provided by the summary.",
selector: { text: null },
name: "Description",
description: "A more complete description of the to-do item than provided by the item name."
}
},
target: { entity: [{ domain: ["todo"], supported_features: [4] }] }
},
remove_item: {
name: "Remove a to-do list item",
description: "Remove an existing to-do list item by its name.",
fields: {
item: {
required: true,
selector: { text: null },
name: "Item name",
description: "The name for the to-do list items."
}
},
target: { entity: [{ domain: ["todo"], supported_features: [2] }] }
},
get_items: {
name: "Get to-do list items",
description: "Get items on a to-do list.",
fields: {
status: {
example: "needs_action",
default: "needs_action",
selector: {
select: {
translation_key: "status",
options: ["needs_action", "completed"],
multiple: true
}
},
name: "Status",
description: "Only return to-do items with the specified statuses. Returns not completed actions by default."
}
},
target: { entity: [{ domain: ["todo"] }] },
response: { optional: false }
},
remove_completed_items: {
name: "Remove all completed to-do list items",
description: "Remove all to-do list items that have been completed.",
fields: {},
target: { entity: [{ domain: ["todo"], supported_features: [2] }] }
}
};
}
}
module.exports = TodoModule;
//# sourceMappingURL=todo.js.map