1import { css } from '@emotion/react';
2import { theme } from '@expo/styleguide';
3import MDX from '@mdx-js/runtime';
4import * as React from 'react';
5
6import * as components from '~/common/translate-markdown';
7
8const STYLES_TABLE = css`
9  font-size: 1rem;
10  margin-top: 24px;
11`;
12
13const STYLES_HEAD = css`
14  background-color: ${theme.background.tertiary};
15`;
16
17const STYLES_DESCRIPTION_CELL = css`
18  word-break: break-word;
19  white-space: break-spaces;
20  padding-bottom: 0.2rem;
21`;
22
23type PropertyMeta = {
24  regexHuman?: string;
25  deprecated?: boolean;
26  hidden?: boolean;
27  expoKit?: string;
28  bareWorkflow?: string;
29};
30
31export type Property = {
32  description?: string;
33  type?: string | string[];
34  meta?: PropertyMeta;
35  pattern?: string;
36  enum?: string[];
37  example?: any;
38  exampleString?: string;
39  host?: object;
40  properties?: Record<string, Property>;
41  items?: {
42    properties?: Record<string, Property>;
43    [key: string]: any;
44  };
45  uniqueItems?: boolean;
46  additionalProperties?: boolean;
47};
48
49type FormattedProperty = {
50  name: string;
51  description: string;
52  nestingLevel: number;
53};
54
55type AppConfigSchemaProps = {
56  schema: Record<string, Property>;
57};
58
59export function formatSchema(rawSchema: [string, Property][]) {
60  const formattedSchema: FormattedProperty[] = [];
61
62  rawSchema.map(property => {
63    appendProperty(formattedSchema, property, 0);
64  });
65
66  return formattedSchema;
67}
68
69//appends a property and recursivley appends sub-properties
70function appendProperty(
71  formattedSchema: FormattedProperty[],
72  property: [string, Property],
73  _nestingLevel: number
74) {
75  let nestingLevel = _nestingLevel;
76  const propertyKey = property[0];
77  const propertyValue = property[1];
78
79  if (propertyValue.meta && (propertyValue.meta.deprecated || propertyValue.meta.hidden)) {
80    return;
81  }
82
83  formattedSchema.push({
84    name: nestingLevel
85      ? `<subpropertyAnchor level={${nestingLevel}}><inlineCode>${propertyKey}</inlineCode></subpropertyAnchor>`
86      : `<propertyAnchor level={0}><inlineCode>${propertyKey}</inlineCode></propertyAnchor>`,
87    description: createDescription(property),
88    nestingLevel,
89  });
90
91  nestingLevel++;
92
93  if (propertyValue.properties) {
94    Object.entries(propertyValue.properties).forEach(subproperty => {
95      appendProperty(formattedSchema, subproperty, nestingLevel);
96    });
97  } //Note: sub-properties are sometimes nested within "items"
98  else if (propertyValue.items && propertyValue.items.properties) {
99    Object.entries(propertyValue.items.properties).forEach(subproperty => {
100      appendProperty(formattedSchema, subproperty, nestingLevel);
101    });
102  }
103}
104
105export function _getType(propertyValue: Property) {
106  if (propertyValue.enum) {
107    return 'enum';
108  } else {
109    return propertyValue.type?.toString().replace(',', ' || ');
110  }
111}
112
113export function createDescription(propertyEntry: [string, Property]) {
114  const propertyValue = propertyEntry[1];
115
116  let propertyDescription = `**(${_getType(propertyValue)})**`;
117  if (propertyValue.description) {
118    propertyDescription += ` - ` + propertyValue.description;
119  }
120  if (propertyValue.meta && propertyValue.meta.regexHuman) {
121    propertyDescription += `\n\n` + propertyValue.meta.regexHuman;
122  }
123  if (propertyValue.meta && propertyValue.meta.expoKit) {
124    propertyDescription += `<expokitDetails>${propertyValue.meta.expoKit}</expokitDetails>`;
125  }
126  if (propertyValue.meta && propertyValue.meta.bareWorkflow) {
127    propertyDescription += `<bareworkflowDetails>${propertyValue.meta.bareWorkflow}</bareworkflowDetails>`;
128  }
129  if (propertyValue.exampleString) {
130    propertyDescription += `\n\n>` + propertyValue.exampleString;
131  }
132
133  return propertyDescription;
134}
135
136const AppConfigSchemaPropertiesTable = ({ schema }: AppConfigSchemaProps) => {
137  const rawSchema = Object.entries(schema);
138  const formattedSchema = formatSchema(rawSchema);
139
140  return (
141    <table css={STYLES_TABLE}>
142      <thead css={STYLES_HEAD}>
143        <tr>
144          <td>Property</td>
145          <td>Description</td>
146        </tr>
147      </thead>
148      <tbody>
149        {formattedSchema.map(({ name, description, nestingLevel }, index) => (
150          <tr key={index}>
151            <td>
152              <div
153                data-testid={name}
154                style={{
155                  marginLeft: `${nestingLevel * 32}px`,
156                  display: nestingLevel ? 'list-item' : 'block',
157                  listStyleType: nestingLevel % 2 ? 'default' : 'circle',
158                  width: 'fit-content',
159                  overflowX: 'visible',
160                }}>
161                <MDX components={components}>{name}</MDX>
162              </div>
163            </td>
164            <td css={STYLES_DESCRIPTION_CELL}>
165              <MDX components={components}>{description}</MDX>
166            </td>
167          </tr>
168        ))}
169      </tbody>
170    </table>
171  );
172};
173
174export default AppConfigSchemaPropertiesTable;
175