1import { css } from '@emotion/core';
2import MDX from '@mdx-js/runtime';
3import * as React from 'react';
4
5import * as components from '~/common/translate-markdown';
6import { expoColors } from '~/constants/theme';
7
8const STYLES_TABLE = css`
9  font-size: 1rem;
10  margin-top: 24px;
11`;
12
13const STYLES_HEAD = css`
14  background-color: ${expoColors.gray[100]};
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
55export function formatSchema(rawSchema: [string, Property][]) {
56  const formattedSchema: FormattedProperty[] = [];
57
58  rawSchema.map(property => {
59    appendProperty(formattedSchema, property, 0);
60  });
61
62  return formattedSchema;
63}
64
65//appends a property and recursivley appends sub-properties
66function appendProperty(
67  formattedSchema: FormattedProperty[],
68  property: [string, Property],
69  _nestingLevel: number
70) {
71  let nestingLevel = _nestingLevel;
72  const propertyKey = property[0];
73  const propertyValue = property[1];
74
75  if (propertyValue.meta && (propertyValue.meta.deprecated || propertyValue.meta.hidden)) {
76    return;
77  }
78
79  formattedSchema.push({
80    name: nestingLevel
81      ? `<subpropertyAnchor level={${nestingLevel}}><inlineCode>${propertyKey}</inlineCode></subpropertyAnchor>`
82      : `<propertyAnchor level={0}><inlineCode>${propertyKey}</inlineCode></propertyAnchor>`,
83    description: createDescription(property),
84    nestingLevel,
85  });
86
87  nestingLevel++;
88
89  if (propertyValue.properties) {
90    Object.entries(propertyValue.properties).forEach(subproperty => {
91      appendProperty(formattedSchema, subproperty, nestingLevel);
92    });
93  } //Note: sub-properties are sometimes nested within "items"
94  else if (propertyValue.items && propertyValue.items.properties) {
95    Object.entries(propertyValue.items.properties).forEach(subproperty => {
96      appendProperty(formattedSchema, subproperty, nestingLevel);
97    });
98  }
99}
100
101export function _getType(propertyValue: Property) {
102  if (propertyValue.enum) {
103    return 'enum';
104  } else {
105    return propertyValue.type?.toString().replace(',', ' || ');
106  }
107}
108
109export function createDescription(propertyEntry: [string, Property]) {
110  const propertyValue = propertyEntry[1];
111
112  let propertyDescription = `**(${_getType(propertyValue)})**`;
113  if (propertyValue.description) {
114    propertyDescription += ` - ` + propertyValue.description;
115  }
116  if (propertyValue.meta && propertyValue.meta.regexHuman) {
117    propertyDescription += `\n\n` + propertyValue.meta.regexHuman;
118  }
119  if (propertyValue.meta && propertyValue.meta.expoKit) {
120    propertyDescription += `<expokitDetails>${propertyValue.meta.expoKit}</expokitDetails>`;
121  }
122  if (propertyValue.meta && propertyValue.meta.bareWorkflow) {
123    propertyDescription += `<bareworkflowDetails>${propertyValue.meta.bareWorkflow}</bareworkflowDetails>`;
124  }
125  if (propertyValue.exampleString) {
126    propertyDescription += `\n\n>` + propertyValue.exampleString;
127  }
128
129  return propertyDescription;
130}
131
132export default class AppConfigSchemaPropertiesTable extends React.Component<{
133  schema: Record<string, Property>;
134}> {
135  render() {
136    const rawSchema = Object.entries(this.props.schema);
137    const formattedSchema = formatSchema(rawSchema);
138
139    return (
140      <table css={STYLES_TABLE}>
141        <thead css={STYLES_HEAD}>
142          <tr>
143            <td>Property</td>
144            <td>Description</td>
145          </tr>
146        </thead>
147        <tbody>
148          {formattedSchema.map((property, index) => {
149            return (
150              <tr key={index}>
151                <td>
152                  <div
153                    data-testid={property.name}
154                    style={{
155                      marginLeft: `${property.nestingLevel * 32}px`,
156                      display: property.nestingLevel ? 'list-item' : 'block',
157                      listStyleType: property.nestingLevel % 2 ? 'default' : 'circle',
158                      width: 'fit-content',
159                      overflowX: 'visible',
160                    }}>
161                    <MDX components={components}>{property.name}</MDX>
162                  </div>
163                </td>
164                <td css={STYLES_DESCRIPTION_CELL}>
165                  <MDX components={components}>{property.description}</MDX>
166                </td>
167              </tr>
168            );
169          })}
170        </tbody>
171      </table>
172    );
173  }
174}
175