1import * as React from 'react'; 2import ReactMarkdown from 'react-markdown'; 3 4import { InlineCode } from '../base/code'; 5 6import { createPermalinkedComponent } from '~/common/create-permalinked-component'; 7import { HeadingType } from '~/common/headingManager'; 8import { PDIV } from '~/components/base/paragraph'; 9import { mdComponents, mdInlineComponents } from '~/components/plugins/api/APISectionUtils'; 10import { Collapsible } from '~/ui/components/Collapsible'; 11import { Cell, HeaderCell, Row, Table, TableHead } from '~/ui/components/Table'; 12 13type PropertyMeta = { 14 regexHuman?: string; 15 deprecated?: boolean; 16 hidden?: boolean; 17 expoKit?: string; 18 bareWorkflow?: string; 19}; 20 21export type Property = { 22 description?: string; 23 type?: string | string[]; 24 meta?: PropertyMeta; 25 pattern?: string; 26 enum?: string[]; 27 example?: any; 28 exampleString?: string; 29 host?: object; 30 properties?: Record<string, Property>; 31 items?: { 32 properties?: Record<string, Property>; 33 [key: string]: any; 34 }; 35 uniqueItems?: boolean; 36 additionalProperties?: boolean; 37}; 38 39type FormattedProperty = { 40 name: string; 41 description: string; 42 nestingLevel: number; 43 type?: string; 44 example?: string; 45 expoKit?: string; 46 bareWorkflow?: string; 47}; 48 49type AppConfigSchemaProps = { 50 schema: Record<string, Property>; 51}; 52 53const Anchor = createPermalinkedComponent(PDIV, { 54 baseNestingLevel: 3, 55 sidebarType: HeadingType.InlineCode, 56}); 57 58const PropertyName = ({ name, nestingLevel }: Pick<FormattedProperty, 'name' | 'nestingLevel'>) => ( 59 <Anchor level={nestingLevel}> 60 <InlineCode>{name}</InlineCode> 61 </Anchor> 62); 63 64export function formatSchema(rawSchema: [string, Property][]) { 65 const formattedSchema: FormattedProperty[] = []; 66 67 rawSchema.map(property => { 68 appendProperty(formattedSchema, property, 0); 69 }); 70 71 return formattedSchema; 72} 73 74// Appends a property and recursively appends sub-properties 75function appendProperty( 76 formattedSchema: FormattedProperty[], 77 property: [string, Property], 78 _nestingLevel: number 79) { 80 let nestingLevel = _nestingLevel; 81 const propertyKey = property[0]; 82 const propertyValue = property[1]; 83 84 if (propertyValue.meta && (propertyValue.meta.deprecated || propertyValue.meta.hidden)) { 85 return; 86 } 87 88 formattedSchema.push({ 89 name: propertyKey, 90 description: createDescription(property), 91 nestingLevel, 92 type: _getType(propertyValue), 93 example: propertyValue.exampleString, 94 expoKit: propertyValue?.meta?.expoKit, 95 bareWorkflow: propertyValue?.meta?.bareWorkflow, 96 }); 97 98 nestingLevel++; 99 100 if (propertyValue.properties) { 101 Object.entries(propertyValue.properties).forEach(subproperty => { 102 appendProperty(formattedSchema, subproperty, nestingLevel); 103 }); 104 } //Note: sub-properties are sometimes nested within "items" 105 else if (propertyValue.items && propertyValue.items.properties) { 106 Object.entries(propertyValue.items.properties).forEach(subproperty => { 107 appendProperty(formattedSchema, subproperty, nestingLevel); 108 }); 109 } 110} 111 112export function _getType(propertyValue: Property) { 113 if (propertyValue.enum) { 114 return 'enum'; 115 } else { 116 return propertyValue.type?.toString().replace(',', ' || '); 117 } 118} 119 120export function createDescription(propertyEntry: [string, Property]) { 121 const propertyValue = propertyEntry[1]; 122 123 let propertyDescription = `**(${_getType(propertyValue)})**`; 124 if (propertyValue.description) { 125 propertyDescription += ` - ` + propertyValue.description; 126 } 127 if (propertyValue.meta && propertyValue.meta.regexHuman) { 128 propertyDescription += `\n\n` + propertyValue.meta.regexHuman; 129 } 130 131 return propertyDescription; 132} 133 134const AppConfigSchemaPropertiesTable = ({ schema }: AppConfigSchemaProps) => { 135 const rawSchema = Object.entries(schema); 136 const formattedSchema = formatSchema(rawSchema); 137 138 return ( 139 <Table> 140 <TableHead> 141 <Row> 142 <HeaderCell>Property</HeaderCell> 143 <HeaderCell>Description</HeaderCell> 144 </Row> 145 </TableHead> 146 <tbody> 147 {formattedSchema.map( 148 ({ name, description, nestingLevel, type, example, expoKit, bareWorkflow }, index) => ( 149 <Row key={index}> 150 <Cell fitContent> 151 <div 152 data-testid={name} 153 style={{ 154 marginLeft: `${nestingLevel * 32}px`, 155 display: nestingLevel ? 'list-item' : 'block', 156 listStyleType: nestingLevel % 2 ? 'default' : 'circle', 157 overflowX: 'visible', 158 }}> 159 <PropertyName name={name} nestingLevel={nestingLevel} /> 160 </div> 161 </Cell> 162 <Cell> 163 <ReactMarkdown components={mdComponents}>{description}</ReactMarkdown> 164 {expoKit && ( 165 <Collapsible summary="ExpoKit"> 166 <ReactMarkdown components={mdComponents}>{expoKit}</ReactMarkdown> 167 </Collapsible> 168 )} 169 {bareWorkflow && ( 170 <Collapsible summary="Bare Workflow"> 171 <ReactMarkdown components={mdComponents}>{bareWorkflow}</ReactMarkdown> 172 </Collapsible> 173 )} 174 {example && ( 175 <ReactMarkdown components={mdInlineComponents}> 176 {`> ${example.replaceAll('\n', '')}`} 177 </ReactMarkdown> 178 )} 179 </Cell> 180 </Row> 181 ) 182 )} 183 </tbody> 184 </Table> 185 ); 186}; 187 188export default AppConfigSchemaPropertiesTable; 189