Merge branch 'main' into publish

* main:
  chore: remove `renderField`
  fix: show the state type by real initial value
  feat: improve the trait form
  fix: change the expression styles
  fix: fix no event type in the event widget
  adjust the editor area
  feat(table): add row click
  feat: add the TableColumn widget

# Conflicts:
#	packages/chakra-ui-lib/package.json
This commit is contained in:
Bowen Tan 2022-03-18 09:45:34 +08:00
commit d7f3b7f11a
19 changed files with 186 additions and 112 deletions

View File

@ -10,7 +10,13 @@ import { css } from '@emotion/css';
import { Type, Static } from '@sinclair/typebox';
import { FALLBACK_METADATA, getComponentProps } from '../sunmao-helper';
import { TablePropsSchema, ColumnSchema } from '../generated/types/Table';
import { ReactNode, useEffect, useMemo, useRef, useState } from 'react';
import React, {
ReactNode,
useEffect,
useMemo,
useRef,
useState,
} from 'react';
import { sortBy } from 'lodash-es';
import {
LIST_ITEM_EXP,
@ -21,6 +27,7 @@ import {
import { TableInstance } from '@arco-design/web-react/es/Table/table';
const TableStateSchema = Type.Object({
clickedRow:Type.Optional(Type.Any()),
selectedRows: Type.Array(Type.Any()),
selectedRow: Type.Optional(Type.Any()),
selectedRowKeys: Type.Array(Type.String()),
@ -52,6 +59,16 @@ const rowSelectionTypeMap: Record<string, 'checkbox' | 'radio' | undefined> = {
disable: undefined,
};
const rowClickStyle = css`
cursor: pointer;
& tr.selected td {
background-color: rgb(235, 244, 251);
}
& tr.selected:hover > td {
background-color: rgb(228, 236, 243) !important;
}
`;
export const exampleProperties: Static<typeof TablePropsSchema> = {
columns: [
{
@ -116,6 +133,7 @@ export const exampleProperties: Static<typeof TablePropsSchema> = {
pagination: {
pageSize: 6,
},
rowClick:false,
tableLayoutFixed: false,
borderCell: false,
stripe: false,
@ -149,7 +167,7 @@ export const Table = implementRuntimeComponent({
const { getElement, app, mergeState, customStyle, services, data, component } = props;
const ref = useRef<TableInstance | null>(null);
const { pagination, ...cProps } = getComponentProps(props);
const { pagination, rowClick, ...cProps } = getComponentProps(props);
const rowSelectionType = rowSelectionTypeMap[cProps.rowSelectionType];
@ -317,7 +335,10 @@ export const Table = implementRuntimeComponent({
return (
<BaseTable
ref={ref}
className={css(customStyle?.content)}
className={css`
${customStyle?.content}
${rowClick ? rowClickStyle : ''}
`}
{...cProps}
columns={columns}
pagination={{
@ -342,6 +363,24 @@ export const Table = implementRuntimeComponent({
mergeState({ selectedRows });
},
}}
onRow={
rowClick
? record => {
return {
onClick(event: React.ChangeEvent<HTMLButtonElement>) {
const tr = event.target.closest('tr');
const tbody = tr?.parentNode;
if (tbody) {
const prevSelectedEl = tbody.querySelector('.selected');
prevSelectedEl?.classList.remove('selected');
}
tr?.classList.add('selected');
mergeState({ clickedRow: record });
},
};
}
: undefined
}
/>
);
});

View File

@ -107,6 +107,11 @@ export const TablePropsSchema = Type.Object({
category: Category.Layout,
weight: 10,
}),
rowClick:Type.Boolean({
title: 'Row Click',
category: Category.Basic,
description:'If on, the table can be selected without setting the rowSelectionType'
}),
loading: Type.Boolean({
title: 'Show Loading',
category: Category.Basic,

View File

@ -23,7 +23,7 @@
"scripts": {
"dev": "vite",
"test": "jest",
"build": "tsup src/index.ts --format cjs,esm,iife --legacy-output --inject ./react-import.js --clean --no-splitting --sourcemap",
"build": "tsup src/index.ts src/widgets/index.ts --format cjs,esm,iife --legacy-output --inject ./react-import.js --clean --no-splitting --sourcemap",
"typings": "tsc --emitDeclarationOnly",
"lint": "eslint src --ext .ts",
"prepublish": "npm run build && npm run typings"
@ -35,6 +35,7 @@
"@sinclair/typebox": "^0.21.2",
"@sunmao-ui/core": "^0.5.3",
"@sunmao-ui/runtime": "^0.5.3",
"@sunmao-ui/editor-sdk": "^0.1.3",
"chakra-react-select": "^1.3.2",
"framer-motion": "^4",
"lodash-es": "^4.17.21",

View File

@ -1,5 +1,5 @@
import { Type } from '@sinclair/typebox';
import { ModuleSchema, EventHandlerSchema } from '@sunmao-ui/runtime';
import { ModuleSchema, BaseEventSchema } from '@sunmao-ui/runtime';
import { BASIC, APPEARANCE, BEHAVIOR } from '../constants/category';
export const MajorKeyPropertySchema = Type.String({
@ -57,7 +57,7 @@ export const ColumnSchema = Type.Object(
text: Type.String({
title: 'Button Text',
}),
handlers: Type.Array(EventHandlerSchema, {
handlers: Type.Array(Type.Object(BaseEventSchema, { widget: 'core/v1/Event' }), {
title: 'Button Handlers',
}),
},
@ -69,6 +69,7 @@ export const ColumnSchema = Type.Object(
},
{
title: 'Column',
widget: 'chakra_ui/v1/TableColumn'
}
);

View File

@ -0,0 +1,50 @@
import React from 'react';
import { JSONSchema7 } from 'json-schema';
import { implementWidget, WidgetProps, SchemaField } from '@sunmao-ui/editor-sdk';
export const TableColumnWidget: React.FC<WidgetProps> = props => {
const { value, level, path, schema, component, services, onChange } = props;
const { type } = value;
const properties = schema.properties || {};
const TYPE_MAP: Record<string, boolean> = {
buttonConfig: type === 'button',
module: type === 'module',
};
const propertyKeys = Object.keys(properties).filter(key => TYPE_MAP[key] !== false);
const schemas: JSONSchema7[] = propertyKeys.map(key => properties[key] as JSONSchema7);
return (
<>
{schemas.map((propertySchema, index) => {
const key = propertyKeys[index];
return (
<SchemaField
key={key}
component={component}
services={services}
schema={propertySchema}
value={value[key]}
level={level + 1}
path={path.concat(key)}
onChange={propertyValue => {
const result = {
...value,
[key]: propertyValue,
};
onChange(result);
}}
/>
);
})}
</>
);
};
export default implementWidget({
version: 'chakra_ui/v1',
metadata: {
name: 'TableColumn',
},
})(TableColumnWidget);

View File

@ -0,0 +1,5 @@
import tableColumnWidget from './TableColumn';
export * from './TableColumn';
export const widgets = [tableColumnWidget];

View File

@ -252,7 +252,7 @@ export const EventWidget: React.FC<WidgetProps<EventWidgetOptionsType>> = observ
return (
<>
{schema.properties?.type ? null : typeField}
{schema.properties?.type ? typeField : null}
{targetField}
{methodField}
{hasParams ? parametersField : null}

View File

@ -153,7 +153,7 @@ export const ExpressionWidget: React.FC<
return (
<ExpressionEditor
{...{
compactOptions={{
maxHeight: '125px',
...(widgetOptions?.compactOptions || {}),
}}

View File

@ -120,9 +120,11 @@ export const SchemaFieldWidgetOptions = Type.Object({
type SchemaFieldWidgetOptionsType = Static<typeof SchemaFieldWidgetOptions>;
type Props = WidgetProps<SchemaFieldWidgetOptionsType> & {
children?: React.ReactNode & {
title?: any;
} | null;
children?:
| (React.ReactNode & {
title?: any;
})
| null;
};
export const SchemaField: React.FC<Props> = props => {
@ -192,7 +194,10 @@ export const SchemaField: React.FC<Props> = props => {
title: children instanceof Object ? children.title : null,
content: (
<HStack>
<Box flex={schema.type === 'boolean' && isExpression === false ? '' : 1}>
<Box
flex={schema.type === 'boolean' && isExpression === false ? '' : 1}
maxWidth="100%"
>
<Component
component={component}
schema={

View File

@ -1,7 +1,6 @@
import React, { useState, useEffect } from 'react';
import { flatten } from 'lodash-es';
import React from 'react';
import { observer } from 'mobx-react-lite';
import { FormControl, FormLabel, Input, Textarea, Text, VStack } from '@chakra-ui/react';
import { FormControl, FormLabel, Input, Text, VStack } from '@chakra-ui/react';
import { SchemaField } from '@sunmao-ui/editor-sdk';
import { TSchema } from '@sinclair/typebox';
import { parseType } from '@sunmao-ui/core';
@ -18,75 +17,6 @@ type Props = {
services: EditorServices;
};
export const renderField = (properties: {
key: string;
value: unknown;
type?: string;
fullKey: string;
selectedComponentId: string;
index: number;
services: EditorServices;
}) => {
const { value, type, fullKey, selectedComponentId, index, services } = properties;
// eslint-disable-next-line react-hooks/rules-of-hooks
const [textareaValue, setTextareaValue] = useState(value as string);
const { eventBus, registry } = services;
// eslint-disable-next-line react-hooks/rules-of-hooks
useEffect(() => {
if (typeof value !== 'object') {
setTextareaValue(value as string);
}
}, [value]);
if (typeof value !== 'object') {
const ref = React.createRef<HTMLTextAreaElement>();
const onBlur = () => {
const operation = type
? genOperation(registry, 'modifyTraitProperty', {
componentId: selectedComponentId,
traitIndex: index,
properties: {
[fullKey]: ref.current?.value,
},
})
: genOperation(registry, 'modifyComponentProperty', {
componentId: selectedComponentId,
properties: {
[fullKey]: ref.current?.value,
},
});
eventBus.send('operation', operation);
};
const onChange = (event: any) => {
setTextareaValue(event.target.value);
};
return (
<FormControl key={`${selectedComponentId}-${fullKey}`}>
<FormLabel>{fullKey}</FormLabel>
<Textarea ref={ref} onChange={onChange} onBlur={onBlur} value={textareaValue} />
</FormControl>
);
} else {
const fieldArray: React.ReactElement[] = flatten(
Object.keys(value || []).map((childKey, index) => {
const childValue = (value as any)[childKey];
return renderField({
index,
key: childKey,
value: childValue,
type: type,
fullKey: `${fullKey}.${childKey}`,
selectedComponentId,
services,
});
})
);
return fieldArray;
}
};
export const ComponentForm: React.FC<Props> = observer(props => {
const { services } = props;
const { editorStore, registry, eventBus } = services;

View File

@ -55,7 +55,7 @@ export const AddTraitButton: React.FC<Props> = props => {
icon={<AddIcon />}
rightIcon={<ChevronDownIcon />}
/>
<MenuList>{menuItems}</MenuList>
<MenuList zIndex={2}>{menuItems}</MenuList>
</Menu>
</Box>
);

View File

@ -1,11 +1,13 @@
import React from 'react';
import { ComponentSchema, TraitSchema } from '@sunmao-ui/core';
import { HStack, IconButton, VStack } from '@chakra-ui/react';
import { parseTypeBox } from '@sunmao-ui/runtime';
import { CloseIcon } from '@chakra-ui/icons';
import { TSchema } from '@sinclair/typebox';
import { renderField } from '../ComponentForm';
import { formWrapperCSS } from '../style';
import { EditorServices } from '../../../types';
import { SchemaField } from '@sunmao-ui/editor-sdk';
import { genOperation } from '../../../operations';
type Props = {
component: ComponentSchema;
@ -17,7 +19,7 @@ type Props = {
export const GeneralTraitForm: React.FC<Props> = props => {
const { trait, traitIndex, component, onRemove, services } = props;
const { registry } = services;
const { registry, eventBus } = services;
const tImpl = registry.getTraitByType(trait.type);
const properties = Object.assign(
@ -26,16 +28,36 @@ export const GeneralTraitForm: React.FC<Props> = props => {
);
const fields = Object.keys(properties || []).map((key: string) => {
const value = trait.properties[key];
return renderField({
index: traitIndex,
key,
value,
fullKey: key,
type: trait.type,
selectedComponentId: component.id,
services,
});
const propertySchema = (tImpl.spec.properties as TSchema).properties?.[key];
const onChange = (newValue: any)=> {
const operation = genOperation(registry, 'modifyTraitProperty', {
componentId: component.id,
traitIndex: traitIndex,
properties: {
[key]: newValue,
},
});
eventBus.send('operation', operation);
};
return (
<SchemaField
key={key}
level={1}
path={[key]}
schema={{
...propertySchema,
title: propertySchema.title || key
}}
value={value}
services={services}
component={component}
onChange={onChange}
/>
);
});
return (
<VStack key={trait.type} className={formWrapperCSS}>
<HStack width="full" justifyContent="space-between">

View File

@ -111,6 +111,7 @@ export const DataSource: React.FC<Props> = props => {
emptyPlaceholder="No States."
states={states}
active={active}
services={services}
traitType="core/v1/state"
onItemClick={onStateItemClick}
onItemRemove={onStateItemRemove}
@ -122,6 +123,7 @@ export const DataSource: React.FC<Props> = props => {
emptyPlaceholder="No LocalStorages."
states={localStorages}
active={active}
services={services}
onItemClick={onLocalStorageItemClick}
onItemRemove={onStateItemRemove}
/>

View File

@ -9,6 +9,7 @@ import {
AccordionPanel,
} from '@chakra-ui/react';
import { DataSourceItem } from './DataSourceItem';
import { EditorServices } from '../../types';
import { ComponentSchema } from '@sunmao-ui/core';
interface Props {
@ -18,6 +19,7 @@ interface Props {
traitType: string;
filterPlaceholder: string;
emptyPlaceholder: string;
services: EditorServices;
onItemClick: (state: ComponentSchema) => void;
onItemRemove: (state: ComponentSchema) => void;
}
@ -40,27 +42,26 @@ export const State: React.FC<Props> = props => {
filterPlaceholder,
emptyPlaceholder,
title,
traitType,
services,
} = props;
const { stateManager } = services;
const { store } = stateManager;
const list = useMemo(
() => states.filter(({ id }) => id.includes(search)),
[search, states]
);
const StateItems = () => (
<>
{list.map(state => {
const trait = state.traits.find(({ type }) => type === traitType);
const properties = trait!.properties;
return (
<DataSourceItem
key={state.id}
dataSource={state}
tag={
Array.isArray(properties.initialValue)
Array.isArray(store[state.id]?.value)
? 'Array'
: STATE_MAP[typeof properties.initialValue] ?? 'Any'
: STATE_MAP[typeof store[state.id]?.value] ?? 'Any'
}
name={state.id}
active={active === state.id}

View File

@ -4,6 +4,8 @@ import { useFormik } from 'formik';
import { ComponentSchema } from '@sunmao-ui/core';
import { EditorServices } from '../../../types';
import { genOperation } from '../../../operations';
import { SchemaField } from '@sunmao-ui/editor-sdk';
import { Type } from '@sinclair/typebox';
interface Values {
key: string;
@ -67,7 +69,7 @@ export const StateForm: React.FC<Props> = props => {
return (
<VStack p="2" spacing="2" background="gray.50" onKeyDown={onKeyDown}>
<FormControl>
<FormLabel>Name</FormLabel>
<FormLabel>State Name</FormLabel>
<Input
value={name}
onChange={e => {
@ -78,12 +80,18 @@ export const StateForm: React.FC<Props> = props => {
</FormControl>
<FormControl>
<FormLabel>Initial Value</FormLabel>
<Input
name="initialValue"
<SchemaField
schema={Type.Any()}
value={values.initialValue}
onChange={formik.handleChange}
onBlur={() => formik.handleSubmit()}
/>
component={state}
level={1}
path={['initialValue']}
services={services}
onChange={(value)=> {
formik.setFieldValue('initialValue', value);
formik.handleSubmit();
}}
/>
</FormControl>
</VStack>
);

View File

@ -158,7 +158,7 @@ export const Editor: React.FC<Props> = observer(
const renderMain = () => {
const appBox = (
<Flex flexDirection="column" width="full" height="full">
<Flex flexDirection="column" width="full" height="full" overflow='hidden'>
<Box
id="editor-main"
display="flex"
@ -244,7 +244,7 @@ export const Editor: React.FC<Props> = observer(
</TabPanels>
</Tabs>
</Box>
<Flex flex={1} position="relative">
<Flex flex={1} position="relative" overflow='hidden'>
{appBox}
<Box
width="320px"

View File

@ -2,6 +2,7 @@ import { StrictMode } from 'react';
import ReactDOM from 'react-dom';
import { Registry } from '@sunmao-ui/runtime';
import { sunmaoChakraUILib } from '@sunmao-ui/chakra-ui-lib';
import { widgets as chakraWidgets } from '@sunmao-ui/chakra-ui-lib/dist/esm/widgets/index';
import { ArcoDesignLib } from '@sunmao-ui/arco-lib';
import { initSunmaoUIEditor } from './init';
import { LocalStorageManager } from './LocalStorageManager';
@ -16,6 +17,7 @@ type Options = Partial<{
const lsManager = new LocalStorageManager();
const { Editor, registry } = initSunmaoUIEditor({
libs: [sunmaoChakraUILib, ArcoDesignLib],
widgets: [...chakraWidgets],
storageHandler: {
onSaveApp(app) {
lsManager.saveAppInLS(app);

View File

@ -24,5 +24,5 @@
"declaration": true,
"declarationDir": "lib"
},
"include": ["./src"]
"include": ["./src", "./types"]
}

3
packages/editor/types/widgets.d.ts vendored Normal file
View File

@ -0,0 +1,3 @@
declare module '@sunmao-ui/chakra-ui-lib/dist/esm/widgets/index' {
export * from '@sunmao-ui/chakra-ui-lib/lib/widgets/index';
}