/* * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC * * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL */ import { create } from 'zustand'; import type { Schema, LayoutSubItem } from '@/types/schema'; export interface SearchIndexEntry { text: string; type: 'link' | 'field' | 'form'; viewName: string; section: string; breadcrumb: string; icon?: string; objectType?: 'object' | 'singleton' | 'view'; keywords?: string[]; } interface SchemaState { schema: Schema | null; isLoaded: boolean; viewToSection: Record; searchIndex: SearchIndexEntry[]; setSchema: (schema: Schema) => void; } function walkLayouts(schema: Schema): { viewToSection: Record; linkEntries: SearchIndexEntry[] } { const viewToSection: Record = {}; const linkEntries: SearchIndexEntry[] = []; function visit(items: LayoutSubItem[], sectionName: string, parentPath: string): void { for (const sub of items) { if (sub.type === 'link') { if (!(sub.viewName in viewToSection)) { viewToSection[sub.viewName] = sectionName; } linkEntries.push({ text: sub.name, type: 'link', viewName: sub.viewName, section: sectionName, breadcrumb: `${parentPath} > ${sub.name}`, }); } else if (sub.type === 'container') { visit(sub.items, sectionName, `${parentPath} > ${sub.name}`); } } } for (const layout of schema.layouts) { const sectionName = layout.name; for (const item of layout.items) { if ('link' in item) { if (!(item.link.viewName in viewToSection)) { viewToSection[item.link.viewName] = sectionName; } linkEntries.push({ text: item.link.name, type: 'link', viewName: item.link.viewName, section: sectionName, breadcrumb: `${sectionName} > ${item.link.name}`, icon: item.link.icon, }); } else if ('container' in item) { visit(item.container.items, sectionName, `${sectionName} > ${item.container.name}`); } } } return { viewToSection, linkEntries }; } function buildSearchIndex( schema: Schema, viewToSection: Record, linkEntries: SearchIndexEntry[], ): SearchIndexEntry[] { const entries: SearchIndexEntry[] = [...linkEntries]; function displayNameFor(viewName: string): string { const obj = schema.objects[viewName]; if (!obj) return viewName.replace(/^x:/, ''); let resolvedName = viewName; let resolvedObj = obj; if (obj.type === 'view') { const parent = schema.objects[obj.objectName]; if (parent && parent.type !== 'view') { resolvedName = obj.objectName; resolvedObj = parent; } } if (resolvedObj.type === 'singleton') { const form = schema.forms[viewName] ?? schema.forms[resolvedName]; if (form?.title) return form.title; } if (resolvedObj.type === 'object') { const list = schema.lists[viewName] ?? schema.lists[resolvedName]; if (list?.singularName) { return list.singularName.charAt(0).toUpperCase() + list.singularName.slice(1); } } return viewName.replace(/^x:/, ''); } for (const [name, obj] of Object.entries(schema.objects)) { if (obj.type !== 'view' && obj.description) { const section = viewToSection[name] ?? ''; const display = displayNameFor(name); entries.push({ text: obj.description, type: 'link', viewName: name, section, breadcrumb: section ? `${section} > ${display}` : display, objectType: obj.type, }); } } for (const [formKey, form] of Object.entries(schema.forms)) { const viewName = formKey; const section = viewToSection[viewName] ?? ''; const display = displayNameFor(viewName); for (const formSection of form.sections) { if (formSection.title) { entries.push({ text: formSection.title, type: 'form', viewName, section, breadcrumb: section ? `${section} > ${display} > ${formSection.title}` : `${display} > ${formSection.title}`, }); } for (const field of formSection.fields) { const keywords: string[] = []; if (field.name && field.name !== '@type') { keywords.push(field.name); } entries.push({ text: field.label, type: 'field', viewName, section, breadcrumb: section ? `${section} > ${display} > ${field.label}` : `${display} > ${field.label}`, keywords, }); } } } return entries; } export const useSchemaStore = create()((set) => ({ schema: null, isLoaded: false, viewToSection: {}, searchIndex: [], setSchema: (schema) => { const { viewToSection, linkEntries } = walkLayouts(schema); const searchIndex = buildSearchIndex(schema, viewToSection, linkEntries); set({ schema, isLoaded: true, viewToSection, searchIndex, }); }, }));