mirror of
https://github.com/smartxworks/sunmao-ui.git
synced 2025-01-30 17:09:35 +08:00
Merge branch 'main' of https://github.com/webzard-io/sunmao-ui into fix/warning-area
This commit is contained in:
commit
4b99a4bc24
@ -1,5 +1,5 @@
|
||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { sortBy } from 'lodash-es';
|
||||
import { isArray, sortBy } from 'lodash-es';
|
||||
import {
|
||||
Table as BaseTable,
|
||||
Thead,
|
||||
@ -63,6 +63,7 @@ export const TableImpl = implementTable(
|
||||
}, [data, updateSelectedItem, updateSelectedItems]);
|
||||
|
||||
const sortedData = useMemo(() => {
|
||||
if (!isArray(data)) return [];
|
||||
if (!sortRule) return data;
|
||||
const sorted = sortBy(data, sortRule.key);
|
||||
return sortRule.desc ? sorted.reverse() : sorted;
|
||||
|
@ -6,6 +6,7 @@ import {
|
||||
ComponentType,
|
||||
IAppModel,
|
||||
IComponentModel,
|
||||
ModuleId,
|
||||
SlotName,
|
||||
} from './IAppModel';
|
||||
import { genComponent } from './utils';
|
||||
@ -36,6 +37,10 @@ export class AppModel implements IAppModel {
|
||||
return Object.values(this.componentMap);
|
||||
}
|
||||
|
||||
get moduleIds(): ModuleId[] {
|
||||
return this.allComponents.filter(c => c.type === 'core/v1/moduleContainer').map(c => c.properties.rawValue.id);
|
||||
}
|
||||
|
||||
toSchema(): ComponentSchema[] {
|
||||
this.schema = this.allComponents.map(c => {
|
||||
return c.toSchema();
|
||||
|
@ -39,6 +39,7 @@ export type EventName = string & {
|
||||
export interface IAppModel {
|
||||
topComponents: IComponentModel[];
|
||||
// modules: IModuleModel[];
|
||||
moduleIds: ModuleId[];
|
||||
// generated by traverse the tree. Component will be overwritten if its id is duplicated.
|
||||
allComponents: IComponentModel[];
|
||||
// all components, including orphan component
|
||||
|
@ -153,7 +153,7 @@ export const ComponentForm: React.FC<Props> = observer(props => {
|
||||
</VStack>
|
||||
</VStack>
|
||||
<EventTraitForm component={selectedComponent} services={services} />
|
||||
{ hasFetchTrait ? <FetchTraitForm component={selectedComponent} services={services} /> : null }
|
||||
{ hasFetchTrait ? <FetchTraitForm key={selectedComponent.id} component={selectedComponent} services={services} /> : null }
|
||||
<StyleTraitForm component={selectedComponent} services={services} />
|
||||
<GeneralTraitFormList component={selectedComponent} services={services} />
|
||||
</VStack>
|
||||
|
@ -13,7 +13,7 @@ import { Static } from '@sinclair/typebox';
|
||||
import { CloseIcon } from '@chakra-ui/icons';
|
||||
import { observer } from 'mobx-react-lite';
|
||||
import { useFormik } from 'formik';
|
||||
import { EventHandlerSchema } from '@sunmao-ui/runtime';
|
||||
import { EventHandlerSchema, GLOBAL_UTILS_ID } from '@sunmao-ui/runtime';
|
||||
import { formWrapperCSS } from '../style';
|
||||
import { KeyValueEditor } from '../../KeyValueEditor';
|
||||
import { EditorServices } from '../../../types';
|
||||
@ -32,20 +32,26 @@ type Props = {
|
||||
export const EventHandlerForm: React.FC<Props> = observer(props => {
|
||||
const { handler, eventTypes, onChange, onRemove, hideEventType, services } = props;
|
||||
const { registry, editorStore } = services;
|
||||
const { utilMethods } = registry;
|
||||
const { components } = editorStore;
|
||||
const [methods, setMethods] = useState<string[]>([]);
|
||||
|
||||
const updateMethods = useCallback(
|
||||
(componentId: string) => {
|
||||
const component = components.find(c => c.id === componentId);
|
||||
if (component) {
|
||||
const componentModel = new ComponentModel(
|
||||
new AppModel([], registry),
|
||||
component,
|
||||
registry
|
||||
);
|
||||
if (componentId === GLOBAL_UTILS_ID) {
|
||||
setMethods(Array.from(utilMethods.keys()));
|
||||
} else {
|
||||
const component = components.find(c => c.id === componentId);
|
||||
|
||||
setMethods(componentModel.methods.map(m => m.name));
|
||||
if (component) {
|
||||
const componentModel = new ComponentModel(
|
||||
new AppModel([], registry),
|
||||
component,
|
||||
registry
|
||||
);
|
||||
|
||||
setMethods(componentModel.methods.map(m => m.name));
|
||||
}
|
||||
}
|
||||
},
|
||||
[components, registry]
|
||||
@ -99,7 +105,7 @@ export const EventHandlerForm: React.FC<Props> = observer(props => {
|
||||
onBlur={() => formik.submitForm()}
|
||||
value={formik.values.componentId}
|
||||
>
|
||||
{components.map(c => (
|
||||
{[{ id: GLOBAL_UTILS_ID }].concat(components).map(c => (
|
||||
<option key={c.id} value={c.id}>
|
||||
{c.id}
|
||||
</option>
|
||||
|
@ -41,7 +41,7 @@ export const FetchTraitForm: React.FC<Props> = props => {
|
||||
?.properties as Static<typeof FetchTraitPropertiesSchema>;
|
||||
|
||||
const formik = useFormik({
|
||||
initialValues: fetchTrait,
|
||||
initialValues: { onComplete: [], onError: [], ...fetchTrait },
|
||||
onSubmit: values => {
|
||||
const index = component.traits.findIndex(t => t.type === 'core/v1/fetch');
|
||||
eventBus.send(
|
||||
|
@ -33,7 +33,7 @@ const ArrayField: React.FC<Props> = props => {
|
||||
<>
|
||||
{formData.map((v, idx) => {
|
||||
return (
|
||||
<Box key={idx} mb={2}>
|
||||
<Box key={idx} mb={2} border='1px solid black' borderColor='gray.200' borderRadius='4' padding='8px'>
|
||||
<ButtonGroup
|
||||
spacing={0}
|
||||
size="xs"
|
||||
|
@ -1,4 +1,4 @@
|
||||
import React, { useMemo, useState } from 'react';
|
||||
import React, { useMemo, useState, useEffect } from 'react';
|
||||
import { Application } from '@sunmao-ui/core';
|
||||
import { GridCallbacks, DIALOG_CONTAINER_ID, initSunmaoUI } from '@sunmao-ui/runtime';
|
||||
import { Box, Tabs, TabList, Tab, TabPanels, TabPanel, Flex } from '@chakra-ui/react';
|
||||
@ -35,6 +35,12 @@ export const Editor: React.FC<Props> = observer(
|
||||
const [preview, setPreview] = useState(false);
|
||||
const [codeMode, setCodeMode] = useState(false);
|
||||
const [code, setCode] = useState('');
|
||||
const [recoverKey, setRecoverKey] = useState(0);
|
||||
const [isError, setIsError] = useState<boolean>(false);
|
||||
|
||||
const onError = (err: Error | null) => {
|
||||
setIsError(err !== null);
|
||||
};
|
||||
|
||||
const gridCallbacks: GridCallbacks = useMemo(() => {
|
||||
return {
|
||||
@ -83,7 +89,10 @@ export const Editor: React.FC<Props> = observer(
|
||||
|
||||
const appComponent = useMemo(() => {
|
||||
return (
|
||||
<ErrorBoundary>
|
||||
<ErrorBoundary
|
||||
key={recoverKey}
|
||||
onError={onError}
|
||||
>
|
||||
<App
|
||||
options={app}
|
||||
debugEvent={false}
|
||||
@ -93,7 +102,7 @@ export const Editor: React.FC<Props> = observer(
|
||||
/>
|
||||
</ErrorBoundary>
|
||||
);
|
||||
}, [App, ComponentWrapper, app, gridCallbacks]);
|
||||
}, [App, ComponentWrapper, app, gridCallbacks, recoverKey]);
|
||||
|
||||
const renderMain = () => {
|
||||
const appBox = (
|
||||
@ -205,6 +214,12 @@ export const Editor: React.FC<Props> = observer(
|
||||
);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (isError) {
|
||||
setRecoverKey(recoverKey + 1);
|
||||
}
|
||||
}, [app]);
|
||||
|
||||
return (
|
||||
<KeyboardEventWrapper
|
||||
components={components}
|
||||
|
@ -1,10 +1,14 @@
|
||||
import React from 'react';
|
||||
|
||||
type Props = {
|
||||
onError?: (error: Error | null) => void;
|
||||
}
|
||||
|
||||
class ErrorBoundary extends React.Component<
|
||||
Record<string, unknown>,
|
||||
Props,
|
||||
{ error: unknown }
|
||||
> {
|
||||
constructor(props: Record<string, unknown>) {
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
this.state = { error: null };
|
||||
}
|
||||
@ -13,6 +17,14 @@ class ErrorBoundary extends React.Component<
|
||||
return { error };
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.props.onError?.(null);
|
||||
}
|
||||
|
||||
componentDidCatch(error: Error) {
|
||||
this.props.onError?.(error);
|
||||
}
|
||||
|
||||
render() {
|
||||
if (this.state.error) {
|
||||
return String(this.state.error);
|
||||
|
@ -75,6 +75,7 @@ export const WarningArea: React.FC<Props> = observer(({ services }) => {
|
||||
paddingY="2"
|
||||
paddingX="4"
|
||||
boxShadow="0 0 4px rgba(0, 0, 0, 0.1)"
|
||||
background='white'
|
||||
>
|
||||
<HStack width="full" justifyContent="space-between">
|
||||
<Text fontSize="md" fontWeight="bold">
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { get, has } from 'lodash-es';
|
||||
import { ComponentId } from '../../AppModel/IAppModel';
|
||||
import { ComponentId, ModuleId } from '../../AppModel/IAppModel';
|
||||
import {
|
||||
PropertiesValidatorRule,
|
||||
PropertiesValidateContext,
|
||||
@ -97,8 +97,11 @@ class ExpressionValidatorRule implements PropertiesValidatorRule {
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else if (appModel.moduleIds.includes(id as ModuleId)) {
|
||||
// case 3: id is a module
|
||||
// TODO: check module stateMap
|
||||
} else {
|
||||
// case 3: id doesn't exist
|
||||
// case 4: id doesn't exist
|
||||
results.push({
|
||||
message: `Cannot find '${id}' in store or window.`,
|
||||
componentId: component.id,
|
||||
|
@ -3,3 +3,5 @@ export const LIST_ITEM_INDEX_EXP = '$i';
|
||||
export const GRID_HEIGHT = 40;
|
||||
export const DIALOG_CONTAINER_ID = 'sunmao-ui-dialog-container';
|
||||
export const DROP_EXAMPLE_SIZE_PREFIX = 'exampleSize: ';
|
||||
export const GLOBAL_UTILS_ID = '$utils';
|
||||
export const GLOBAL_MODULE_ID = '$module';
|
||||
|
@ -1,4 +1,8 @@
|
||||
import mitt from 'mitt';
|
||||
import {
|
||||
GLOBAL_UTILS_ID,
|
||||
GLOBAL_MODULE_ID
|
||||
} from '../constants';
|
||||
|
||||
export type ApiService = ReturnType<typeof initApiService>;
|
||||
|
||||
@ -32,13 +36,13 @@ function mountSystemMethods(apiService: ApiService) {
|
||||
apiService.on('uiMethod', ({ componentId, name, parameters }) => {
|
||||
switch (componentId) {
|
||||
// hanlder as module event
|
||||
case '$module':
|
||||
case GLOBAL_MODULE_ID:
|
||||
apiService.send('moduleEvent', {
|
||||
fromId: parameters.moduleId,
|
||||
eventType: name,
|
||||
});
|
||||
break;
|
||||
case '$utils':
|
||||
case GLOBAL_UTILS_ID:
|
||||
// handle as window function
|
||||
if (name in window) {
|
||||
const method = window[name as keyof Window];
|
||||
|
@ -1,4 +1,5 @@
|
||||
import { parseType } from '@sunmao-ui/core';
|
||||
import { GLOBAL_UTILS_ID } from '../constants';
|
||||
// components
|
||||
/* --- plain --- */
|
||||
import PlainButton from '../components/plain/Button';
|
||||
@ -183,7 +184,7 @@ export class Registry {
|
||||
|
||||
private mountUtilMethods() {
|
||||
this.apiService.on('uiMethod', ({ componentId, name, parameters }) => {
|
||||
if (componentId === '$utils') {
|
||||
if (componentId === GLOBAL_UTILS_ID) {
|
||||
const utilMethod = this.utilMethods.get(name);
|
||||
if (utilMethod) {
|
||||
utilMethod(parameters);
|
||||
|
@ -9,6 +9,7 @@ import {
|
||||
TSchema,
|
||||
OptionalModifier,
|
||||
UnionKind,
|
||||
AnyKind,
|
||||
} from '@sinclair/typebox';
|
||||
|
||||
export function parseTypeBox(tSchema: TSchema, noOptional = false): Static<typeof tSchema> {
|
||||
@ -40,6 +41,8 @@ export function parseTypeBox(tSchema: TSchema, noOptional = false): Static<typeo
|
||||
const subSchema = (tSchema.anyOf || tSchema.oneOf)[0];
|
||||
return parseTypeBox(subSchema, noOptional);
|
||||
}
|
||||
case tSchema.kind === AnyKind:
|
||||
return undefined;
|
||||
default:
|
||||
return {};
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user