Files
genieacs/ui/components/parameter.ts
T
Zaid Humaimidi 4a191afdec feat(ui): redesign UI with Tailwind CSS, CodeMirror 6, and new layout
- Replace all hand-written CSS with Tailwind utility classes
- Upgrade CodeMirror 5 to CodeMirror 6 (scoped @codemirror/* packages)
- Redesign layout with responsive sidebar navigation using JSX/Mithril
- Flatten admin routes (e.g. /admin/presets -> /presets)
- Add "remember me" login option with 2-week persistent cookie
- Add delete confirmation dialogs across admin pages
- Upgrade esbuild from 0.21 to 0.27 and update browser targets
- Add Inter and Roboto Mono variable fonts with subset script
2026-03-12 14:55:10 -07:00

117 lines
3.2 KiB
TypeScript

import { ClosureComponent, Component, VnodeDOM } from "mithril";
import { m } from "../components.ts";
import * as taskQueue from "../task-queue.ts";
import * as store from "../store.ts";
import { evaluate } from "../../lib/common/expression/util.ts";
import memoize from "../../lib/common/memoize.ts";
import timeAgo from "../timeago.ts";
import { icon } from "../tailwind-utility-components.ts";
const evaluateParam = memoize((exp, obj, now: number) => {
let timestamp = now;
exp = evaluate(exp, null, now, (e) => {
if (!Array.isArray(e)) return e;
for (let i = 1; i < e.length; ++i) {
if (
Array.isArray(e[i]) &&
e[i][0] === "PARAM" &&
!Array.isArray(e[i][1])
) {
let v = null;
const p = obj[e[i][1]];
if (p?.value) {
v = p.value[0];
timestamp = Math.min(timestamp, p.valueTimestamp);
}
e = e.slice();
e[i] = v;
}
}
if (e[0] === "FUNC" && e[1] === "DATE_STRING" && !Array.isArray(e[2]))
return new Date(e[2]).toLocaleString();
return e;
});
let parameter = null;
let value = null;
if (!Array.isArray(exp)) {
value = exp;
} else if (exp[0] === "PARAM") {
const p = obj[exp[1]];
if (p?.value) {
timestamp = p.valueTimestamp;
value = p.value[0];
parameter = exp[1];
if (p.value[1] === "xsd:dateTime" && typeof value === "number")
value = new Date(value).toLocaleString();
}
}
return { value, timestamp, parameter };
});
const component: ClosureComponent = (): Component => {
return {
view: (vnode) => {
const device = vnode.attrs["device"];
const { value, timestamp, parameter } = evaluateParam(
vnode.attrs["parameter"],
device,
store.getTimestamp() + store.getClockSkew(),
);
if (value == null) return null;
let edit;
if (device[parameter]?.writable) {
edit = m(
"button",
{
title: "Edit parameter value",
onclick: () => {
taskQueue.stageSpv({
name: "setParameterValues",
devices: [device["DeviceID.ID"].value[0]],
parameterValues: [
[
parameter,
device[parameter].value[0],
device[parameter].value[1],
],
],
});
},
},
m(icon, {
name: "edit",
class: "inline h-4 w-4 ml-1 text-cyan-700 hover:text-cyan-900",
}),
);
}
const el = m("long-text", { text: `${value}` });
return m(
"span.inline-flex overflow-hidden align-top",
{
onmouseover: (e) => {
e.redraw = false;
// Don't update any child element
if (e.target === (el as VnodeDOM).dom) {
const now = Date.now() + store.getClockSkew();
const localeString = new Date(timestamp).toLocaleString();
e.target.title = `${localeString} (${timeAgo(now - timestamp)})`;
}
},
},
m("span.truncate", el),
edit,
);
},
};
};
export default component;