farmcontrol-ui/src/desktop/serializeBundle.js
Tom Butcher 7dfaef65a4
All checks were successful
farmcontrol/farmcontrol-ui/pipeline/head This commit looks good
Add new SVG icons for various actions and UI elements
- Introduced multiple SVG icons including actionsicon, apppasswordicon, arrow icons (up, down, left, right), and various other utility icons.
- Enhanced visual representation across the application by adding icons for functionalities like check, bold, bullet list, and more.
- Improved user interface consistency and accessibility with the addition of these icons, facilitating better user interaction.
2026-09-17 18:42:10 +01:00

266 lines
7.3 KiB
JavaScript

import { objectModels, evaluateVariable } from '../database/ObjectModels'
import { getSidebarMenuSections } from '../database/Sidebars'
import { buildSvgIconUrlMap, getSvgIconUrl } from './desktopIcons'
import config from '../config'
import packageJson from '../../package.json'
const FUNCTION_KEYS = new Set([
'value',
'visible',
'readOnly',
'required',
'disabled'
])
const SKIP_KEYS = new Set(['content', 'icon', 'pages'])
const isPlainObject = (value) =>
value != null &&
typeof value === 'object' &&
!Array.isArray(value) &&
Object.prototype.toString.call(value) === '[object Object]'
const isReactish = (value) => {
if (value == null) return false
if (typeof value === 'object') {
if (value.$$typeof) return true
if (typeof value._init === 'function' && value._payload) return true
}
if (typeof value === 'function') {
const name = value.displayName || value.name || ''
if (name.endsWith('Icon') || name === 'Icon') return true
}
return false
}
const functionToIconKey = (fn) => {
if (typeof fn !== 'function') return null
const name = fn.displayName || fn.name || ''
if (!name.endsWith('Icon')) return null
const base = name.slice(0, -4)
if (!base) return null
return base.charAt(0).toLowerCase() + base.slice(1)
}
const serializeJsFunction = (fn) => {
try {
return { __js: Function.prototype.toString.call(fn) }
} catch {
return undefined
}
}
const serializeValue = (value, key) => {
if (value == null) return value
if (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean') {
return value
}
if (typeof value === 'bigint') {
return value.toString()
}
if (value instanceof Date) {
return value.toISOString()
}
if (typeof value === 'function') {
if (isReactish(value) || SKIP_KEYS.has(key)) {
return undefined
}
if (FUNCTION_KEYS.has(key) || key == null) {
return serializeJsFunction(value)
}
return serializeJsFunction(value)
}
if (Array.isArray(value)) {
return value
.map((item) => serializeValue(item, key))
.filter((item) => item !== undefined)
}
if (isReactish(value)) {
return undefined
}
if (!isPlainObject(value)) {
return undefined
}
const result = {}
for (const [childKey, childValue] of Object.entries(value)) {
if (SKIP_KEYS.has(childKey) && childKey !== 'pages') {
if (childKey === 'icon' && typeof childValue === 'function') {
const iconKey = functionToIconKey(childValue)
if (iconKey) {
result.iconKey = iconKey
}
}
continue
}
if (childKey === 'pages' && Array.isArray(childValue)) {
result.pages = childValue
.map((page) => {
if (!page || typeof page !== 'object') return null
return {
name: page.name
}
})
.filter((page) => page?.name)
continue
}
const serialized = serializeValue(childValue, childKey)
if (serialized !== undefined) {
result[childKey] = serialized
}
}
return result
}
const serializeModel = (model) => {
if (!model || typeof model !== 'object') return null
const iconKey =
model.iconKey || functionToIconKey(model.icon) || model.name || null
const serialized = serializeValue(model, null) || {}
serialized.name = model.name
serialized.label = model.label
serialized.labelPlural = model.labelPlural
serialized.url = typeof model.url === 'string' ? model.url : undefined
serialized.prefix = model.prefix
serialized.endpoint = model.endpoint || `${String(model.name || '').toLowerCase()}s`
serialized.iconKey = iconKey
serialized.columns = Array.isArray(model.columns) ? model.columns : []
serialized.filters = Array.isArray(model.filters) ? model.filters : []
serialized.sorters = Array.isArray(model.sorters) ? model.sorters : []
if (Array.isArray(model.actions)) {
serialized.actions = model.actions
.map((action) => {
if (!action || typeof action !== 'object') return null
if (action.type === 'divider') {
return { type: 'divider' }
}
const next = serializeValue(action, null) || {}
if (typeof action.icon === 'function') {
const actionIconKey = functionToIconKey(action.icon)
if (actionIconKey) next.iconKey = actionIconKey
}
delete next.content
delete next.children
return next
})
.filter(Boolean)
}
if (Array.isArray(model.properties)) {
serialized.properties = serializeValue(model.properties, 'properties')
}
return serialized
}
const attachModelsToSidebarItems = (items, modelsByUrl) => {
if (!Array.isArray(items)) return []
return items
.map((item) => {
if (!item || item.type === 'divider') {
return { type: 'divider' }
}
const next = {
key: item.key,
label: item.label,
iconKey: item.iconKey,
path: item.path
}
if (item.path && modelsByUrl.has(item.path)) {
next.model = modelsByUrl.get(item.path)
}
if (Array.isArray(item.children) && item.children.length > 0) {
next.children = attachModelsToSidebarItems(item.children, modelsByUrl)
if (
!next.model &&
!next.children.some((child) => child.model || child.children)
) {
return null
}
}
if (!next.model && !next.children) {
return null
}
return next
})
.filter(Boolean)
}
export function serializeDesktopBundle() {
const models = serializeDesktopModels()
const modelsByUrl = new Map(
models.filter((model) => model.url).map((model) => [model.url, model.name])
)
const sidebars = getSidebarMenuSections({ includeDev: false }).map(
(section) => ({
key: section.key,
label: section.label,
iconKey: section.iconKey,
items: attachModelsToSidebarItems(section.items, modelsByUrl)
})
)
const icons = buildSvgIconUrlMap()
const registerIconKey = (iconKey) => {
if (!iconKey) return
if (icons[iconKey] || icons[iconKey.toLowerCase()]) return
const urls = getSvgIconUrl(iconKey, icons)
if (urls) {
icons[iconKey] = urls
}
}
const registerSidebarIcons = (items) => {
if (!Array.isArray(items)) return
for (const item of items) {
registerIconKey(item?.iconKey)
registerSidebarIcons(item?.children)
}
}
for (const model of models) {
registerIconKey(model.iconKey)
if (Array.isArray(model.actions)) {
for (const action of model.actions) {
registerIconKey(action?.iconKey)
}
}
}
for (const sidebar of sidebars) {
registerIconKey(sidebar.iconKey)
registerSidebarIcons(sidebar.items)
}
return {
version: packageJson.version,
webAppUrl: config.webAppUrl,
apiUrl: config.backendUrl,
wsUrl: config.apiServerUrl,
printServerUrl: config.printServerUrl,
auth: {
loginPath: '/auth/web/login',
tokenPath: '/auth/web/token',
refreshPath: '/auth/refresh',
userPath: '/auth/user',
logoutPath: '/auth/logout'
},
helpers: {
evaluateVariable: { __js: evaluateVariable.toString() }
},
icons,
sidebars,
models: models.map((model) => ({
name: model.name,
url: `/desktop/models/${model.name}.json`
}))
}
}
export function serializeDesktopModels() {
return objectModels.map(serializeModel).filter(Boolean)
}