import { useTranslation } from "next-i18next"; import Container from "components/services/widget/container"; import Block from "components/services/widget/block"; import classNames from "classnames"; import useWidgetAPI from "utils/proxy/use-widget-api"; import * as shvl from "utils/config/shvl"; function getValue(field, data) { let value = data; let lastField = field; let key = ""; // Support APIs that return arrays or scalars directly. if (typeof field === "undefined") { return value; } // shvl is easier, everything else is kept for backwards compatibility. if (typeof field === "string") { return shvl.get(data, field, null); } while (typeof lastField === "object") { key = Object.keys(lastField)[0] ?? null; if (key === null) { break; } value = value[key]; lastField = lastField[key]; } if (typeof value === "undefined") { return null; } return value[lastField] ?? null; } function getSize(data) { if (Array.isArray(data) || typeof data === "string") { return data.length; } else if (typeof data === "object" && data !== null) { return Object.keys(data).length; } return NaN; } function formatValue(t, mapping, rawValue) { let value = rawValue; // Remap the value. const remaps = mapping?.remap ?? []; for (let i = 0; i < remaps.length; i += 1) { const remap = remaps[i]; if (remap?.any || remap?.value === value) { value = remap.to; break; } } // Scale the value. Accepts either a number to multiply by or a string // like "12/345". const scale = mapping?.scale; if (typeof scale === "number") { value *= scale; } else if (typeof scale === "string") { const parts = scale.split("/"); const numerator = parts[0] ? parseFloat(parts[0]) : 1; const denominator = parts[1] ? parseFloat(parts[1]) : 1; value = (value * numerator) / denominator; } // Format the value using a known type. switch (mapping?.format) { case "number": value = t("common.number", { value: parseInt(value, 10) }); break; case "float": value = t("common.number", { value }); break; case "percent": value = t("common.percent", { value }); break; case "duration": value = t("common.duration", { value }); break; case "bytes": value = t("common.bytes", { value }); break; case "bitrate": value = t("common.bitrate", { value }); break; case "date": value = t("common.date", { value, lng: mapping?.locale, dateStyle: mapping?.dateStyle ?? "long", timeStyle: mapping?.timeStyle, }); break; case "relativeDate": value = t("common.relativeDate", { value, lng: mapping?.locale, style: mapping?.style, numeric: mapping?.numeric, }); break; case "size": value = t("common.number", { value: getSize(value) }); break; case "text": default: // nothing } // Apply fixed prefix. const prefix = mapping?.prefix; if (prefix) { value = `${prefix} ${value}`; } // Apply fixed suffix. const suffix = mapping?.suffix; if (suffix) { value = `${value} ${suffix}`; } return value; } function getColor(mapping, customData) { const value = getValue(mapping.additionalField.field, customData); const { color } = mapping.additionalField; switch (color) { case "adaptive": try { const number = parseFloat(value); return number > 0 ? "text-emerald-300" : "text-rose-300"; } catch (e) { return ""; } case "black": return `text-black`; case "white": return `text-white`; case "theme": return `text-theme-500`; default: return ""; } } export default function Component({ service }) { const { t } = useTranslation(); const { widget } = service; const { mappings = [], refreshInterval = 10000, display = "block" } = widget; const { data: customData, error: customError } = useWidgetAPI(widget, null, { refreshInterval: Math.max(1000, refreshInterval), }); if (customError) { return ; } if (!customData) { switch (display) { case "dynamic-list": return ( Loading... ); case "list": return ( {mappings.map((mapping) => ( {mapping.label} - ))} ); default: return ( {mappings.slice(0, 4).map((item) => ( ))} ); } } switch (display) { case "dynamic-list": let listItems = customData; if (mappings.items) listItems = shvl.get(customData, mappings.items, null); let error; if (!listItems || !Array.isArray(listItems)) { error = { message: "Unable to find items" }; } const name = mappings.name; const label = mappings.label; if (!name || !label) { error = { message: "Name and label properties are required" }; } if (error) { return ; } const target = mappings.target; if (mappings.limit && parseInt(mappings.limit, 10) > 0) { listItems.splice(mappings.limit); } return ( {listItems.length === 0 ? ( No items found ) : ( listItems.map((item, index) => { const itemName = shvl.get(item, name, ""); const itemLabel = shvl.get(item, label, ""); const itemUrl = target ? target.replace(/\{([^}]+)\}/g, (_, key) => item[key] || "") : null; const className = "bg-theme-200/50 dark:bg-theme-900/20 rounded-sm m-1 flex-1 flex flex-row items-center justify-between p-1 text-xs"; return itemUrl ? ( {itemName} {itemLabel} ) : ( {itemName} {itemLabel} ); }) )} ); case "list": return ( {mappings.map((mapping) => ( {mapping.label} {formatValue(t, mapping, getValue(mapping.field, customData))} {mapping.additionalField && ( {formatValue(t, mapping.additionalField, getValue(mapping.additionalField.field, customData))} )} ))} ); default: return ( {mappings.slice(0, 4).map((mapping) => ( ))} ); } }