All checks were successful
farmcontrol/farmcontrol-ui/pipeline/head This commit looks good
- 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.
266 lines
7.3 KiB
JavaScript
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)
|
|
}
|