import { Accordion, AccordionContent, AccordionItem, AccordionTrigger, } from '@affine/admin/components/ui/accordion'; import { Button } from '@affine/admin/components/ui/button'; import { ScrollArea } from '@affine/admin/components/ui/scroll-area'; import { get } from 'lodash-es'; import { useCallback, useState } from 'react'; import { Header } from '../header'; import { ALL_CONFIG_DESCRIPTORS, ALL_SETTING_GROUPS, type AppConfig, } from './config'; import { type ConfigInputProps, ConfigRow } from './config-input-row'; import { useAppConfig } from './use-app-config'; export function SettingsPage() { const { appConfig, update, saveGroup, resetGroup, patchedAppConfig, isGroupDirty, isGroupSaving, getGroupVersion, } = useAppConfig(); const [expandedModules, setExpandedModules] = useState([]); return (
); } const AdminPanel = ({ expandedModules, onExpandedModulesChange, appConfig, patchedAppConfig, onUpdate, onSaveGroup, onResetGroup, isGroupDirty, isGroupSaving, getGroupVersion, }: { expandedModules: string[]; onExpandedModulesChange: (modules: string[]) => void; appConfig: AppConfig; patchedAppConfig: AppConfig; onUpdate: (path: string, value: any) => void; onSaveGroup: (module: string) => Promise; onResetGroup: (module: string) => void; isGroupDirty: (module: string) => boolean; isGroupSaving: (module: string) => boolean; getGroupVersion: (module: string) => number; }) => { const [groupErrors, setGroupErrors] = useState< Record> >({}); const onFieldErrorChange = useCallback((field: string, error?: string) => { const [module] = field.split('/'); if (!module) { return; } setGroupErrors(prev => { const moduleErrors = prev[module] ?? {}; if (error) { if (moduleErrors[field] === error) { return prev; } return { ...prev, [module]: { ...moduleErrors, [field]: error, }, }; } if (!(field in moduleErrors)) { return prev; } const nextModuleErrors = { ...moduleErrors }; delete nextModuleErrors[field]; if (Object.keys(nextModuleErrors).length === 0) { const next = { ...prev }; delete next[module]; return next; } return { ...prev, [module]: nextModuleErrors, }; }); }, []); const clearModuleErrors = useCallback((module: string) => { setGroupErrors(prev => { if (!prev[module]) { return prev; } const next = { ...prev }; delete next[module]; return next; }); }, []); return (
{ALL_SETTING_GROUPS.map(group => { const { name, module, fields, operations } = group; const dirty = isGroupDirty(module); const saving = isGroupSaving(module); const sourceConfig = patchedAppConfig[module] ?? appConfig[module]; const version = getGroupVersion(module); const hasValidationError = Boolean( groupErrors[module] && Object.keys(groupErrors[module] ?? {}).length > 0 ); return (
{name}
Manage {name.toLowerCase()} settings
{fields.map(field => { const fieldKey = typeof field === 'string' ? field : String(field.key); const effectiveIndexerProvider = sourceConfig?.enabled ? sourceConfig?.provider?.type : 'embedded'; if ( module === 'indexer' && effectiveIndexerProvider === 'embedded' && fieldKey.startsWith('provider.') && fieldKey !== 'provider.type' ) { return null; } let props: ConfigInputProps; if (typeof field === 'string') { const descriptor = ALL_CONFIG_DESCRIPTORS[module][field]; props = { field: `${module}/${field}`, desc: descriptor.desc, type: descriptor.type, options: [], defaultValue: get(sourceConfig, field), onChange: onUpdate, }; } else { const descriptor = ALL_CONFIG_DESCRIPTORS[module][field.key]; props = { field: `${module}/${field.key}${field.sub ? `/${field.sub}` : ''}`, desc: field.desc ?? descriptor.desc, type: field.type ?? descriptor.type, // @ts-expect-error for enum type options: field.options, defaultValue: module === 'indexer' && field.key === 'provider.type' ? effectiveIndexerProvider : get( sourceConfig, field.key + (field.sub ? '.' + field.sub : '') ), onChange: module === 'indexer' && field.key === 'provider.type' ? (_path, value) => { if (value === 'embedded') { onUpdate('indexer/enabled', false); } else { onUpdate('indexer/enabled', true); onUpdate('indexer/provider.type', value); } } : onUpdate, }; } return ( ); })} {operations?.map(Operation => ( ))}
{dirty ? ( ) : null}
); })}
); };