Skip to content

Commit

Permalink
feat(runtime): support Component DataSource
Browse files Browse the repository at this point in the history
  • Loading branch information
tanbowensg committed Dec 13, 2022
1 parent 0ef7cfa commit 0eefe58
Show file tree
Hide file tree
Showing 52 changed files with 801 additions and 1,148 deletions.
1 change: 1 addition & 0 deletions packages/core/src/metadata.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ export type Metadata<
description?: string;
annotations?: Record<string, any> & TAnnotations;
exampleProperties?: TExample;
isDataSource?: boolean;
};

type ComponentCategory =
Expand Down
2 changes: 1 addition & 1 deletion packages/core/src/trait.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ export function createTrait(options: CreateTraitOptions): RuntimeTrait {
kind: 'Trait' as any,
parsedVersion: parseVersion(options.version),
metadata: {
name: options.metadata.name,
...options.metadata,
description: options.metadata.description || '',
annotations: options.metadata.annotations || {},
},
Expand Down
211 changes: 211 additions & 0 deletions packages/editor-sdk/src/components/ApiForm/ApiForm.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,211 @@
import React, { useState, useEffect, useCallback } from 'react';
import { ComponentSchema } from '@sunmao-ui/core';
import { watch, FetchTraitPropertiesSpec } from '@sunmao-ui/runtime';
import { Static, Type } from '@sinclair/typebox';
import {
Box,
VStack,
HStack,
Text,
Tabs,
TabPanels,
TabPanel,
TabList,
Tab,
Select,
Button,
} from '@chakra-ui/react';
import { useFormik } from 'formik';
import { Basic } from './Basic';
import { Headers as HeadersForm } from './Headers';
import { Params } from './Params';
import { Body } from './Body';
import { Response as ResponseInfo } from './Response';
import { EditorServicesInterface } from '../../types/editor';
import { ExpressionWidget } from '../Widgets';
import { WidgetProps } from '../..';

enum TabIndex {
Basic,
Headers,
Params,
Body,
}
interface Props {
value: Static<typeof FetchTraitPropertiesSpec>;
component: ComponentSchema;
services: EditorServicesInterface;
onChange: (value: Static<typeof FetchTraitPropertiesSpec>) => void;
}

const METHODS = ['get', 'post', 'put', 'delete', 'patch'];
const EMPTY_ARRAY: string[] = [];

type FetchResultType = {
data?: unknown;
code?: number;
codeText?: string;
error?: string;
loading?: boolean;
};

export const ApiForm: React.FC<Props> = props => {
const { value, onChange, component, services } = props;
const [tabIndex, setTabIndex] = useState(0);
const [fetchResult, setFetchResult] = useState<FetchResultType | undefined>();
const formik = useFormik({
initialValues: value,
onSubmit: values => {
onChange(values);
},
});
const { values } = formik;
const URLSpec = Type.String({
widget: 'core/v1/expression',
widgetOptions: {
compactOptions: {
paddingY: '6px',
},
},
});

const onFetch = useCallback(() => {
services.apiService.send('uiMethod', {
componentId: component.id,
name: 'triggerFetch',
parameters: {},
});
}, [services.apiService, component]);
const onMethodChange = useCallback(
(e: React.ChangeEvent<HTMLSelectElement>) => {
formik.handleChange(e);
formik.handleSubmit();
if (e.target.value === 'get' && tabIndex === TabIndex.Body) {
setTabIndex(0);
}
},
[formik, tabIndex]
);
const onURLChange = useCallback(
(value: string) => {
formik.setFieldValue('url', value);
formik.handleSubmit();
},
[formik]
);
const onKeyDown = useCallback((e: React.KeyboardEvent) => {
// prevent form keyboard events to accidentally trigger operation shortcut
e.stopPropagation();
}, []);

useEffect(() => {
formik.setValues({ ...value });
// do not add formik into dependencies, otherwise it will cause infinite loop
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [value]);

useEffect(() => {
const stop = watch(
() => services.stateManager.store[component.id]?.fetch,
newValue => {
setFetchResult({ ...newValue });
}
);

return stop;
}, [component.id, services.stateManager.store]);

return (
<VStack
backgroundColor="#fff"
padding="4"
paddingBottom="0"
align="stretch"
spacing="4"
height="100%"
onKeyDown={onKeyDown}
>
<Text
title={component.id}
fontSize="lg"
fontWeight="bold"
overflow="hidden"
whiteSpace="nowrap"
textOverflow="ellipsis"
>
{component.id}
</Text>
<HStack spacing={1} flex="0 1 auto" alignItems="start">
<Select
width={200}
name="method"
value={values.method}
onChange={onMethodChange}
size="md"
>
{METHODS.map(method => (
<option key={method} value={method}>
{method.toLocaleUpperCase()}
</option>
))}
</Select>
<Box width="0" flex="1">
<ExpressionWidget
component={component}
spec={URLSpec}
value={values.url}
path={EMPTY_ARRAY}
level={1}
services={services}
onChange={onURLChange}
/>
</Box>
<Button colorScheme="blue" isLoading={fetchResult?.loading} onClick={onFetch}>
Run
</Button>
</HStack>
<Tabs
flex="1 1 0"
overflow="hidden"
index={tabIndex}
onChange={index => {
setTabIndex(index);
}}
>
<VStack height="100%" alignItems="stretch">
<TabList>
<Tab>Basic</Tab>
<Tab>Headers</Tab>
<Tab>Params</Tab>
{values.method !== 'get' ? <Tab>Body</Tab> : null}
</TabList>
<TabPanels flex={1} overflow="auto">
<TabPanel>
<Basic api={component} formik={formik} services={services} />
</TabPanel>
<TabPanel>
<HeadersForm
api={component}
spec={FetchTraitPropertiesSpec.properties.headers as WidgetProps['spec']}
services={services}
formik={formik}
/>
</TabPanel>
<TabPanel>
<Params api={component} services={services} formik={formik} />
</TabPanel>
<TabPanel>
<Body
api={component}
spec={FetchTraitPropertiesSpec.properties.body as WidgetProps['spec']}
services={services}
formik={formik}
/>
</TabPanel>
</TabPanels>
</VStack>
</Tabs>
<ResponseInfo {...fetchResult} />
</VStack>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,17 @@ import { VStack, FormControl, FormLabel, Switch } from '@chakra-ui/react';
import { FormikHelpers, FormikHandlers, FormikState } from 'formik';
import { FetchTraitPropertiesSpec } from '@sunmao-ui/runtime';
import { Static, Type } from '@sinclair/typebox';
import { EditorServices } from '../../../types';
import { ComponentSchema } from '@sunmao-ui/core';
import { SpecWidget, mergeWidgetOptionsIntoSpec } from '@sunmao-ui/editor-sdk';
import { JSONSchema7 } from 'json-schema';
import { EditorServicesInterface } from '../../types/editor';
import { mergeWidgetOptionsIntoSpec } from '../..';
import { SpecWidget } from '../Widgets';

type Values = Static<typeof FetchTraitPropertiesSpec>;
interface Props {
api: ComponentSchema;
formik: FormikHelpers<Values> & FormikHandlers & FormikState<Values>;
services: EditorServices;
services: EditorServicesInterface;
}

const DisabledSpec = Type.Boolean({
Expand Down
Original file line number Diff line number Diff line change
@@ -1,22 +1,19 @@
import React, { useCallback, useMemo } from 'react';
import { Box, Select, Text, VStack } from '@chakra-ui/react';
import {
SpecWidget,
WidgetProps,
mergeWidgetOptionsIntoSpec,
} from '@sunmao-ui/editor-sdk';
import { FormikHelpers, FormikHandlers, FormikState } from 'formik';
import { FetchTraitPropertiesSpec } from '@sunmao-ui/runtime';
import { ComponentSchema } from '@sunmao-ui/core';
import { Static } from '@sinclair/typebox';
import { EditorServices } from '../../../types';
import { EditorServicesInterface } from '../../types/editor';
import { mergeWidgetOptionsIntoSpec, WidgetProps } from '../..';
import { SpecWidget } from '../Widgets';

type Values = Static<typeof FetchTraitPropertiesSpec>;
interface Props {
api: ComponentSchema;
spec: WidgetProps['spec'];
formik: FormikHelpers<Values> & FormikHandlers & FormikState<Values>;
services: EditorServices;
services: EditorServicesInterface;
}

const EMPTY_ARRAY: string[] = [];
Expand Down
Original file line number Diff line number Diff line change
@@ -1,22 +1,19 @@
import React, { useCallback, useMemo } from 'react';
import { Box } from '@chakra-ui/react';
import {
RecordWidget,
WidgetProps,
mergeWidgetOptionsIntoSpec,
} from '@sunmao-ui/editor-sdk';
import { FormikHelpers, FormikHandlers, FormikState } from 'formik';
import { FetchTraitPropertiesSpec } from '@sunmao-ui/runtime';
import { ComponentSchema } from '@sunmao-ui/core';
import { Static } from '@sinclair/typebox';
import { EditorServices } from '../../../types';
import { EditorServicesInterface } from '../../types/editor';
import { mergeWidgetOptionsIntoSpec, WidgetProps } from '../..';
import { RecordWidget } from '../Widgets';

type Values = Static<typeof FetchTraitPropertiesSpec>;
interface Props {
api: ComponentSchema;
spec: WidgetProps['spec'];
formik: FormikHelpers<Values> & FormikHandlers & FormikState<Values>;
services: EditorServices;
services: EditorServicesInterface;
}

const EMPTY_ARRAY: string[] = [];
Expand Down
Original file line number Diff line number Diff line change
@@ -1,17 +1,18 @@
import React, { useCallback, useMemo } from 'react';
import { Box } from '@chakra-ui/react';
import { RecordWidget, mergeWidgetOptionsIntoSpec } from '@sunmao-ui/editor-sdk';
import { FormikHelpers, FormikHandlers, FormikState } from 'formik';
import { Type, Static } from '@sinclair/typebox';
import { FetchTraitPropertiesSpec } from '@sunmao-ui/runtime';
import { ComponentSchema } from '@sunmao-ui/core';
import { EditorServices } from '../../../types';
import { EditorServicesInterface } from '../../types/editor';
import { RecordWidget } from '../Widgets';
import { mergeWidgetOptionsIntoSpec } from '../..';

type Values = Static<typeof FetchTraitPropertiesSpec>;
interface Props {
api: ComponentSchema;
formik: FormikHelpers<Values> & FormikHandlers & FormikState<Values>;
services: EditorServices;
services: EditorServicesInterface;
}

const EMPTY_ARRAY: string[] = [];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import {
Spinner,
Tag,
} from '@chakra-ui/react';
import { CodeEditor } from '../../CodeEditor';
import { css } from '@emotion/css';

interface Props {
Expand Down Expand Up @@ -39,7 +38,7 @@ export const Response: React.FC<Props> = props => {
const error = useMemo(() => {
return stringify(props.error);
}, [props.error]);
return props.data || props.error || props.loading ? (
return props.data || props.error || props.loading || props.codeText ? (
<Accordion
onChange={i => setIsOpen(i === 0)}
allowToggle
Expand All @@ -52,7 +51,7 @@ export const Response: React.FC<Props> = props => {
<AccordionButton>
<HStack flex="1" textAlign="left" spacing={2}>
<span>Response</span>
{props.data || props.error ? (
{props.data || props.error || props.codeText ? (
<Tag colorScheme={CODE_MAP[String(props.code)[0]] || 'red'}>
{props.code} {(props.codeText || '').toLocaleUpperCase()}
</Tag>
Expand All @@ -62,22 +61,20 @@ export const Response: React.FC<Props> = props => {
</AccordionButton>
</h2>
<AccordionPanel pb={4} padding={0} height="250px">
<Flex alignItems="center" justifyContent="center" height="100%">
<Flex alignItems="center" justifyContent="center" height="100%" overflow="auto">
{props.loading || !isOpen ? (
<Spinner />
) : (
<CodeEditor
<pre
className={css`
width: 100%;
height: 100%;
width: 100%;
overflow: auto;
padding: 0 20px;
`}
mode={{
name: 'javascript',
json: true,
}}
defaultCode={error || data}
readOnly
/>
>
<code>{error || data}</code>
</pre>
)}
</Flex>
</AccordionPanel>
Expand Down
Loading

0 comments on commit 0eefe58

Please sign in to comment.