mirror of
https://github.com/smartxworks/sunmao-ui.git
synced 2025-02-17 17:40:31 +08:00
Merge branch 'main' into yz-patch
* main: fix(editorMask): mask does'nt work because onHTMLElementsUpdated doesn't run in init feat(PreviewModal): fix the length of the component in the PreviewModal exceeds the modal fix(ArrayTable): fix the problem of length exceeding the table feat(Select): add filter by text chore: fix typo feat(utilMethod): add spec to utilMethod # Conflicts: # packages/core/src/index.ts # packages/editor-sdk/src/components/Widgets/EventWidget.tsx
This commit is contained in:
commit
744e75cd03
@ -106,6 +106,10 @@ export const Select = implementRuntimeComponent({
|
||||
value={value}
|
||||
{...cProps}
|
||||
showSearch={showSearch}
|
||||
filterOption={(inputValue, option) =>
|
||||
option.props.value.toLowerCase().indexOf(inputValue.toLowerCase()) >= 0 ||
|
||||
option.props.children.toLowerCase().indexOf(inputValue.toLowerCase()) >= 0
|
||||
}
|
||||
dropdownRender={menu => {
|
||||
return (
|
||||
<div className={css(customStyle?.dropdownRenderWrap)}>
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { Type, Static, TProperties, TObject } from '@sinclair/typebox';
|
||||
import { createStandaloneToast } from '@chakra-ui/react';
|
||||
import { UtilMethod } from '@sunmao-ui/runtime';
|
||||
import { implementUtilMethod } from '@sunmao-ui/runtime';
|
||||
|
||||
const ToastPosition = Type.KeyOf(
|
||||
Type.Object({
|
||||
@ -49,7 +49,7 @@ export const ToastOpenParameterSpec = Type.Object({
|
||||
export const ToastCloseParameterSpec = Type.Object({
|
||||
id: Type.String(),
|
||||
positions: Type.Array(ToastPosition, {
|
||||
defaultValue: []
|
||||
defaultValue: [],
|
||||
}),
|
||||
});
|
||||
|
||||
@ -70,37 +70,46 @@ const pickProperty = <T, U extends Record<string, any>>(
|
||||
export default function ToastUtilMethodFactory() {
|
||||
let toast: ReturnType<typeof createStandaloneToast> | undefined;
|
||||
|
||||
const toastOpen: UtilMethod<typeof ToastOpenParameterSpec> = {
|
||||
name: 'toast.open',
|
||||
method(parameters) {
|
||||
if (!toast) {
|
||||
toast = createStandaloneToast();
|
||||
}
|
||||
if (parameters) {
|
||||
toast(pickProperty(ToastOpenParameterSpec, parameters));
|
||||
}
|
||||
const toastOpen = implementUtilMethod({
|
||||
version: 'chakra_ui/v1',
|
||||
metadata: {
|
||||
name: 'openToast',
|
||||
},
|
||||
parameters: ToastOpenParameterSpec,
|
||||
};
|
||||
spec: {
|
||||
parameters: ToastOpenParameterSpec,
|
||||
},
|
||||
})(parameters => {
|
||||
if (!toast) {
|
||||
toast = createStandaloneToast();
|
||||
}
|
||||
if (parameters) {
|
||||
toast(pickProperty(ToastOpenParameterSpec, parameters));
|
||||
}
|
||||
});
|
||||
|
||||
const toastClose: UtilMethod<typeof ToastCloseParameterSpec> = {
|
||||
name: 'toast.close',
|
||||
method(parameters) {
|
||||
if (!toast) {
|
||||
return;
|
||||
}
|
||||
if (!parameters) {
|
||||
toast.closeAll();
|
||||
} else {
|
||||
const closeParameters = pickProperty(ToastCloseParameterSpec, parameters);
|
||||
if (closeParameters.id !== undefined) {
|
||||
toast.close(closeParameters.id);
|
||||
} else {
|
||||
toast.closeAll(closeParameters);
|
||||
}
|
||||
}
|
||||
const toastClose = implementUtilMethod({
|
||||
version: 'chakra_ui/v1',
|
||||
metadata: {
|
||||
name: 'closeToast',
|
||||
},
|
||||
parameters: ToastCloseParameterSpec,
|
||||
};
|
||||
spec: {
|
||||
parameters: ToastCloseParameterSpec,
|
||||
},
|
||||
})(parameters => {
|
||||
if (!toast) {
|
||||
return;
|
||||
}
|
||||
if (!parameters) {
|
||||
toast.closeAll();
|
||||
} else {
|
||||
const closeParameters = pickProperty(ToastCloseParameterSpec, parameters);
|
||||
if (closeParameters.id !== undefined) {
|
||||
toast.close(closeParameters.id);
|
||||
} else {
|
||||
toast.closeAll(closeParameters);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return [toastOpen, toastClose];
|
||||
}
|
||||
|
@ -6,3 +6,4 @@ export * from './method';
|
||||
export * from './module';
|
||||
export * from './version';
|
||||
export * from './slot';
|
||||
export * from './utilMethod';
|
||||
|
28
packages/core/src/utilMethod.ts
Normal file
28
packages/core/src/utilMethod.ts
Normal file
@ -0,0 +1,28 @@
|
||||
import { JSONSchema7 } from 'json-schema';
|
||||
import { Metadata } from './metadata';
|
||||
import { parseVersion, type Version } from './version';
|
||||
|
||||
export type UtilMethodSpec = {
|
||||
parameters: JSONSchema7;
|
||||
};
|
||||
|
||||
export type UtilMethod = {
|
||||
version: string;
|
||||
kind: 'UtilMethod';
|
||||
metadata: Metadata;
|
||||
spec: UtilMethodSpec;
|
||||
};
|
||||
|
||||
export type RuntimeUtilMethod = UtilMethod & {
|
||||
parsedVersion: Version;
|
||||
};
|
||||
|
||||
export type CreateUtilMethodOptions = Omit<UtilMethod, 'kind'>;
|
||||
|
||||
export function createUtilMethod(options: CreateUtilMethodOptions): RuntimeUtilMethod {
|
||||
return {
|
||||
...options,
|
||||
kind: 'UtilMethod',
|
||||
parsedVersion: parseVersion(options.version),
|
||||
};
|
||||
}
|
@ -24,6 +24,12 @@ const TableRowStyle = css`
|
||||
padding-bottom: var(--chakra-space-1);
|
||||
border-bottom-width: 1px;
|
||||
border-color: var(--chakra-colors-gray-100);
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
}
|
||||
& > th:last-child {
|
||||
width: 76px;
|
||||
}
|
||||
`;
|
||||
|
||||
@ -99,7 +105,7 @@ export const ArrayTable: React.FC<ArrayTableProps> = props => {
|
||||
|
||||
return (
|
||||
<div className={TableWrapperStyle}>
|
||||
<Table size="sm">
|
||||
<Table size="sm" sx={{ tableLayout: 'fixed' }}>
|
||||
<Thead>
|
||||
<Tr className={TableRowStyle}>
|
||||
<Th width="24px" />
|
||||
@ -109,7 +115,7 @@ export const ArrayTable: React.FC<ArrayTableProps> = props => {
|
||||
|
||||
return <Th key={key}>{title}</Th>;
|
||||
})}
|
||||
<Th key="button" display="flex" justifyContent="end">
|
||||
<Th key="button">
|
||||
<IconButton
|
||||
aria-label="add"
|
||||
icon={<AddIcon />}
|
||||
|
@ -19,8 +19,8 @@ export const EventWidget: React.FC<WidgetProps<EventWidgetOptionsType>> = observ
|
||||
props => {
|
||||
const { value, path, level, component, spec, services, onChange } = props;
|
||||
const { registry, editorStore, appModelManager } = services;
|
||||
const { utilMethods } = registry;
|
||||
const { components } = editorStore;
|
||||
const utilMethods = useMemo(() => registry.getAllUtilMethods(), [registry]);
|
||||
const [methods, setMethods] = useState<string[]>([]);
|
||||
|
||||
const formik = useFormik({
|
||||
@ -29,23 +29,26 @@ export const EventWidget: React.FC<WidgetProps<EventWidgetOptionsType>> = observ
|
||||
onChange(values);
|
||||
},
|
||||
});
|
||||
const findMethodsByComponent = (component?: ComponentSchema) => {
|
||||
if (!component) {
|
||||
return [];
|
||||
}
|
||||
const findMethodsByComponent = useCallback(
|
||||
(component?: ComponentSchema) => {
|
||||
if (!component) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const componentMethods = Object.entries(
|
||||
registry.getComponentByType(component.type).spec.methods
|
||||
).map(([name, parameters]) => ({
|
||||
name,
|
||||
parameters,
|
||||
}));
|
||||
const traitMethods = component.traits
|
||||
.map(trait => registry.getTraitByType(trait.type).spec.methods)
|
||||
.flat();
|
||||
const componentMethods = Object.entries(
|
||||
registry.getComponentByType(component.type).spec.methods
|
||||
).map(([name, parameters]) => ({
|
||||
name,
|
||||
parameters,
|
||||
}));
|
||||
const traitMethods = component.traits
|
||||
.map(trait => registry.getTraitByType(trait.type).spec.methods)
|
||||
.flat();
|
||||
|
||||
return ([] as any[]).concat(componentMethods, traitMethods);
|
||||
};
|
||||
return ([] as any[]).concat(componentMethods, traitMethods);
|
||||
},
|
||||
[registry]
|
||||
);
|
||||
|
||||
const eventTypes = useMemo(() => {
|
||||
return registry.getComponentByType(component.type).spec.events;
|
||||
@ -55,15 +58,14 @@ export const EventWidget: React.FC<WidgetProps<EventWidgetOptionsType>> = observ
|
||||
[formik.values.method.parameters]
|
||||
);
|
||||
const paramsSpec = useMemo(() => {
|
||||
const { values } = formik;
|
||||
const methodName = values.method.name;
|
||||
const methodType = formik.values.method.name;
|
||||
let spec: WidgetProps['spec'] = Type.Record(Type.String(), Type.String());
|
||||
|
||||
if (methodName) {
|
||||
if (methodType) {
|
||||
if (value.componentId === GLOBAL_UTIL_METHOD_ID) {
|
||||
const targetMethod = utilMethods.get(methodName);
|
||||
const targetMethod = registry.getUtilMethodByType(methodType)!;
|
||||
|
||||
spec = targetMethod?.parameters;
|
||||
spec = targetMethod.spec.parameters;
|
||||
} else {
|
||||
const targetComponent = appModelManager.appModel.getComponentById(
|
||||
value.componentId
|
||||
@ -79,20 +81,26 @@ export const EventWidget: React.FC<WidgetProps<EventWidgetOptionsType>> = observ
|
||||
}
|
||||
|
||||
return spec;
|
||||
}, [formik.values.method]);
|
||||
}, [
|
||||
formik.values.method.name,
|
||||
registry,
|
||||
appModelManager,
|
||||
value.componentId,
|
||||
findMethodsByComponent,
|
||||
]);
|
||||
const params = useMemo(() => {
|
||||
const params: Record<string, string> = {};
|
||||
const { values } = formik;
|
||||
const parameters = formik.values.method.parameters;
|
||||
|
||||
for (const key in paramsSpec?.properties ?? {}) {
|
||||
const defaultValue = (paramsSpec?.properties?.[key] as WidgetProps['spec'])
|
||||
.defaultValue;
|
||||
|
||||
params[key] = values.method.parameters?.[key] ?? defaultValue ?? '';
|
||||
params[key] = parameters?.[key] ?? defaultValue ?? '';
|
||||
}
|
||||
|
||||
return params;
|
||||
}, [formik.values.method.name]);
|
||||
}, [formik.values.method.parameters, paramsSpec?.properties]);
|
||||
const parametersPath = useMemo(() => path.concat('method', 'parameters'), [path]);
|
||||
const parametersSpec = useMemo(
|
||||
() => mergeWidgetOptionsIntoSpec(paramsSpec, { onlySetValue: true }),
|
||||
@ -107,7 +115,11 @@ export const EventWidget: React.FC<WidgetProps<EventWidgetOptionsType>> = observ
|
||||
const updateMethods = useCallback(
|
||||
(componentId: string) => {
|
||||
if (componentId === GLOBAL_UTIL_METHOD_ID) {
|
||||
setMethods(Array.from(utilMethods.keys()));
|
||||
setMethods(
|
||||
utilMethods.map(
|
||||
utilMethod => `${utilMethod.version}/${utilMethod.metadata.name}`
|
||||
)
|
||||
);
|
||||
} else {
|
||||
const component = components.find(c => c.id === componentId);
|
||||
|
||||
@ -120,7 +132,7 @@ export const EventWidget: React.FC<WidgetProps<EventWidgetOptionsType>> = observ
|
||||
}
|
||||
}
|
||||
},
|
||||
[components, registry]
|
||||
[components, utilMethods, findMethodsByComponent]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
|
@ -54,6 +54,7 @@ export class EditorMaskManager {
|
||||
|
||||
this.observeIntersection();
|
||||
this.observeResize();
|
||||
this.refreshElementIdMap();
|
||||
// listen to the DOM elements' mount and unmount events
|
||||
// TODO: This is not very accurate, because sunmao runtime 'didDOMUpdate' hook is not accurate.
|
||||
// We will refactor the 'didDOMUpdate' hook with components' life cycle in the future.
|
||||
@ -125,14 +126,7 @@ export class EditorMaskManager {
|
||||
private onHTMLElementsUpdated = () => {
|
||||
this.observeIntersection();
|
||||
this.observeResize();
|
||||
|
||||
// generate elementIdMap, this only aim to improving the performance of refreshHoverElement method
|
||||
const elementIdMap = new Map<Element, string>();
|
||||
this.eleMap.forEach((ele, id) => {
|
||||
elementIdMap.set(ele, id);
|
||||
});
|
||||
|
||||
this.elementIdMap = elementIdMap;
|
||||
this.refreshElementIdMap();
|
||||
this.refreshHoverElement();
|
||||
this.refreshMaskPosition();
|
||||
};
|
||||
@ -156,6 +150,16 @@ export class EditorMaskManager {
|
||||
};
|
||||
}
|
||||
|
||||
private refreshElementIdMap() {
|
||||
// generate elementIdMap, this only aim to improving the performance of refreshHoverElement method
|
||||
const elementIdMap = new Map<Element, string>();
|
||||
this.eleMap.forEach((ele, id) => {
|
||||
elementIdMap.set(ele, id);
|
||||
});
|
||||
|
||||
this.elementIdMap = elementIdMap;
|
||||
}
|
||||
|
||||
private refreshHoverElement() {
|
||||
const hoverElement = document.elementFromPoint(...this.mousePosition);
|
||||
if (!hoverElement) return;
|
||||
|
@ -12,9 +12,10 @@ export const GeneralModal: React.FC<{
|
||||
onClose: () => void;
|
||||
title: string;
|
||||
size?: string;
|
||||
}> = ({ title, onClose, size = 'full', children }) => {
|
||||
scrollBehavior?: 'inside' | 'outside';
|
||||
}> = ({ title, onClose, size = 'full', children, scrollBehavior = 'inside' }) => {
|
||||
return (
|
||||
<Modal onClose={onClose} size={size} isOpen>
|
||||
<Modal onClose={onClose} scrollBehavior={scrollBehavior} size={size} isOpen>
|
||||
<ModalOverlay />
|
||||
<ModalContent>
|
||||
<ModalHeader>{title}</ModalHeader>
|
||||
|
@ -28,9 +28,10 @@ import {
|
||||
ImplementedRuntimeTraitFactory,
|
||||
ImplementedRuntimeTrait,
|
||||
ImplementedRuntimeModule,
|
||||
UtilMethodFactory,
|
||||
UIServices,
|
||||
} from '../types';
|
||||
import { UtilMethod, UtilMethodFactory } from '../types/utilMethod';
|
||||
import { ImplementedUtilMethod } from '../types/utilMethod';
|
||||
import { UtilMethodManager } from './UtilMethodManager';
|
||||
|
||||
export type SunmaoLib = {
|
||||
@ -53,7 +54,7 @@ export class Registry {
|
||||
components = new Map<string, Map<string, AnyImplementedRuntimeComponent>>();
|
||||
traits = new Map<string, Map<string, ImplementedRuntimeTrait>>();
|
||||
modules = new Map<string, Map<string, ImplementedRuntimeModule>>();
|
||||
utilMethods = new Map<string, UtilMethod<any>>();
|
||||
utilMethods = new Map<string, Map<string, ImplementedUtilMethod>>();
|
||||
private services: UIServices;
|
||||
|
||||
constructor(
|
||||
@ -180,14 +181,42 @@ export class Registry {
|
||||
this.traits = new Map<string, Map<string, ImplementedRuntimeTrait>>();
|
||||
}
|
||||
|
||||
registerUtilMethod<T>(m: UtilMethod<T>) {
|
||||
if (this.utilMethods.get(m.name)) {
|
||||
throw new Error(`Already has utilMethod ${m.name} in this registry.`);
|
||||
registerUtilMethod<T>(m: ImplementedUtilMethod<T>) {
|
||||
if (this.utilMethods.get(m.version)?.get(m.metadata.name)) {
|
||||
throw new Error(
|
||||
`Already has utilMethod ${m.version}/${m.metadata.name} in this registry.`
|
||||
);
|
||||
}
|
||||
this.utilMethods.set(m.name, m);
|
||||
if (!this.utilMethods.has(m.version)) {
|
||||
this.utilMethods.set(m.version, new Map());
|
||||
}
|
||||
|
||||
this.utilMethods.get(m.version)!.set(m.metadata.name, m);
|
||||
this.utilMethodManager.listenUtilMethod(m, this.services);
|
||||
}
|
||||
|
||||
getUtilMethod(version: string, name: string): ImplementedUtilMethod<any> | null {
|
||||
return this.utilMethods.get(version)?.get(name) || null;
|
||||
}
|
||||
|
||||
getUtilMethodByType(type: string): ImplementedUtilMethod<any> | null {
|
||||
const { version, name } = parseType(type);
|
||||
|
||||
return this.getUtilMethod(version, name);
|
||||
}
|
||||
|
||||
getAllUtilMethods(): ImplementedUtilMethod[] {
|
||||
const res: ImplementedUtilMethod[] = [];
|
||||
|
||||
for (const version of this.utilMethods.values()) {
|
||||
for (const utilMethod of version.values()) {
|
||||
res.push(utilMethod);
|
||||
}
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
installLib(lib: SunmaoLib) {
|
||||
lib.components?.forEach(c => this.registerComponent(c));
|
||||
lib.traits?.forEach(t => this.registerTrait(t));
|
||||
|
@ -1,16 +1,19 @@
|
||||
import { GLOBAL_MODULE_ID, GLOBAL_UTIL_METHOD_ID } from '../constants';
|
||||
import { ApiService } from './apiService';
|
||||
import { UtilMethod, UIServices } from '../types';
|
||||
import { ImplementedUtilMethod, UIServices } from '../types';
|
||||
|
||||
export class UtilMethodManager {
|
||||
constructor(private apiService: ApiService) {
|
||||
this.listenSystemMethods();
|
||||
}
|
||||
|
||||
listenUtilMethod<T>(utilMethod: UtilMethod<T>, services: UIServices) {
|
||||
listenUtilMethod<T>(utilMethod: ImplementedUtilMethod<T>, services: UIServices) {
|
||||
this.apiService.on('uiMethod', ({ componentId, name, parameters }) => {
|
||||
if (componentId === GLOBAL_UTIL_METHOD_ID && name === utilMethod.name) {
|
||||
utilMethod.method(parameters, services);
|
||||
if (
|
||||
componentId === GLOBAL_UTIL_METHOD_ID &&
|
||||
name === `${utilMethod.version}/${utilMethod.metadata.name}`
|
||||
) {
|
||||
utilMethod.impl(parameters, services);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -1,11 +1,10 @@
|
||||
import { Static } from '@sinclair/typebox';
|
||||
import { JSONSchema7 } from 'json-schema';
|
||||
import { UIServices } from './application';
|
||||
import { RuntimeUtilMethod } from '@sunmao-ui/core';
|
||||
|
||||
export interface UtilMethod<T extends JSONSchema7> {
|
||||
name: string;
|
||||
method: (parameters: Static<T>, services: UIServices) => void;
|
||||
parameters: T;
|
||||
}
|
||||
export type UtilMethodImpl<T = any> = (parameters: T, services: UIServices) => void;
|
||||
|
||||
export type UtilMethodFactory = () => UtilMethod<any>[];
|
||||
export type ImplementedUtilMethod<T = any> = RuntimeUtilMethod & {
|
||||
impl: UtilMethodImpl<T>;
|
||||
};
|
||||
|
||||
export type UtilMethodFactory = () => ImplementedUtilMethod<any>[];
|
||||
|
@ -1,20 +1,22 @@
|
||||
import { Type } from '@sinclair/typebox';
|
||||
import { UtilMethod } from '../types/utilMethod';
|
||||
import { implementUtilMethod } from '../utils/buildKit';
|
||||
|
||||
const ScrollToComponentMethodParameters = Type.Object({
|
||||
componentId: Type.String(),
|
||||
});
|
||||
|
||||
const ScrollToComponentMethod: UtilMethod<typeof ScrollToComponentMethodParameters> = {
|
||||
name: 'scrollToComponent',
|
||||
method(parameters, services) {
|
||||
if (!parameters) return;
|
||||
const ele = services.eleMap.get(parameters?.componentId);
|
||||
if (ele) {
|
||||
ele.scrollIntoView({ behavior: 'smooth' });
|
||||
}
|
||||
export default implementUtilMethod({
|
||||
version: 'core/v1',
|
||||
metadata: {
|
||||
name: 'scrollToComponent',
|
||||
},
|
||||
parameters: ScrollToComponentMethodParameters,
|
||||
};
|
||||
|
||||
export default ScrollToComponentMethod;
|
||||
spec: {
|
||||
parameters: ScrollToComponentMethodParameters,
|
||||
},
|
||||
})((parameters, services) => {
|
||||
if (!parameters) return;
|
||||
const ele = services.eleMap.get(parameters?.componentId);
|
||||
if (ele) {
|
||||
ele.scrollIntoView({ behavior: 'smooth' });
|
||||
}
|
||||
});
|
||||
|
@ -5,12 +5,16 @@ import {
|
||||
createTrait,
|
||||
CreateTraitOptions,
|
||||
TraitSpec,
|
||||
createUtilMethod,
|
||||
CreateUtilMethodOptions,
|
||||
} from '@sunmao-ui/core';
|
||||
import {
|
||||
ComponentImpl,
|
||||
ImplementedRuntimeComponent,
|
||||
TraitImplFactory,
|
||||
ImplementedRuntimeTraitFactory,
|
||||
UtilMethodImpl,
|
||||
ImplementedUtilMethod,
|
||||
} from '../types';
|
||||
|
||||
type ToMap<U> = {
|
||||
@ -55,3 +59,14 @@ export function implementRuntimeTrait<T extends CreateTraitOptions>(
|
||||
factory,
|
||||
});
|
||||
}
|
||||
|
||||
export function implementUtilMethod<T extends CreateUtilMethodOptions>(
|
||||
options: T
|
||||
): (
|
||||
impl: UtilMethodImpl<Static<T['spec']['parameters']>>
|
||||
) => ImplementedUtilMethod<Static<T['spec']['parameters']>> {
|
||||
return impl => ({
|
||||
...createUtilMethod(options),
|
||||
impl,
|
||||
});
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user