remove slotsMap & resolveAppComponents

This commit is contained in:
Bowen Tan 2022-01-05 15:35:33 +08:00
parent 7192c5cd7c
commit edf4b2b9d9
13 changed files with 44 additions and 173 deletions

View File

@ -39,22 +39,23 @@ export default implementRuntimeComponent({
mergeState, mergeState,
subscribeMethods, subscribeMethods,
hideSubmit, hideSubmit,
slotsMap,
callbackMap, callbackMap,
services, services,
customStyle, customStyle,
Slot, Slot,
treeMap,
component
}) => { }) => {
const [invalidArray, setInvalidArray] = useState<boolean[]>([]); const [invalidArray, setInvalidArray] = useState<boolean[]>([]);
const [isFormInvalid, setIsFormInvalid] = useState<boolean>(false); const [isFormInvalid, setIsFormInvalid] = useState<boolean>(false);
const formDataRef = useRef<Record<string, any>>({}); const formDataRef = useRef<Record<string, any>>({});
const formControlIds = useMemo<string[]>(() => { const formControlIds = useMemo<string[]>(() => {
return ( return (
slotsMap?.get('content')?.map(slot => { treeMap[component.id]?.content.map(slot => {
return slot.id; return slot.id;
}) || [] }) || []
); );
}, [slotsMap]); }, [component.id, treeMap]);
useEffect(() => { useEffect(() => {
setInvalidArray( setInvalidArray(

View File

@ -60,16 +60,17 @@ export default implementRuntimeComponent({
fieldName, fieldName,
isRequired, isRequired,
helperText, helperText,
slotsMap,
mergeState, mergeState,
services, services,
customStyle, customStyle,
Slot, Slot,
treeMap,
component,
}) => { }) => {
const [inputValue, setInputValue] = useState(''); const [inputValue, setInputValue] = useState('');
// don't show Invalid state on component mount // don't show Invalid state on component mount
const [hideInvalid, setHideInvalid] = useState(true); const [hideInvalid, setHideInvalid] = useState(true);
const inputId = useMemo(() => first(slotsMap?.get('content'))?.id || '', [slotsMap]); const inputId = useMemo(() => first(treeMap[component.id].content)?.id || '', [component.id, treeMap]);
const [validResult, setValidResult] = useState({ const [validResult, setValidResult] = useState({
isInvalid: false, isInvalid: false,
errorMsg: '', errorMsg: '',

View File

@ -82,7 +82,7 @@ export default implementRuntimeComponent({
${customStyle?.tabContent} ${customStyle?.tabContent}
`} `}
> >
{<Component /> || placeholder} {Component ? <Component /> : placeholder}
</TabPanel> </TabPanel>
); );
})} })}

View File

@ -26,10 +26,10 @@ export const ComponentTree: React.FC<Props> = props => {
if (slots.length === 0) { if (slots.length === 0) {
return null; return null;
} }
const slotsMap = childrenMap.get(component.id); const children = childrenMap.get(component.id);
return slots.map(slot => { return slots.map(slot => {
let slotContent; let slotContent;
const slotChildren = slotsMap?.get(slot); const slotChildren = children?.get(slot);
if (slotChildren && slotChildren.length > 0) { if (slotChildren && slotChildren.length > 0) {
slotContent = slotChildren.map(c => { slotContent = slotChildren.map(c => {
return ( return (

View File

@ -3,7 +3,6 @@ import { ApplicationComponent } from '@sunmao-ui/core';
export type ChildrenMap = Map<string, SlotsMap>; export type ChildrenMap = Map<string, SlotsMap>;
type SlotsMap = Map<string, ApplicationComponent[]>; type SlotsMap = Map<string, ApplicationComponent[]>;
// similar to resolveAppComponents
export function resolveApplicationComponents(components: ApplicationComponent[]): { export function resolveApplicationComponents(components: ApplicationComponent[]): {
topLevelComponents: ApplicationComponent[]; topLevelComponents: ApplicationComponent[];
childrenMap: ChildrenMap; childrenMap: ChildrenMap;

View File

@ -1,7 +1,6 @@
import React, { useMemo, useRef } from 'react'; import React, { useRef } from 'react';
import { initStateAndMethod } from './utils/initStateAndMethod'; import { initStateAndMethod } from './utils/initStateAndMethod';
import { ImplWrapper } from './components/_internal/ImplWrapper'; import { ImplWrapper } from './components/_internal/ImplWrapper';
import { resolveAppComponents } from './services/resolveAppComponents';
import { AppProps, UIServices } from './types/RuntimeSchema'; import { AppProps, UIServices } from './types/RuntimeSchema';
import { DebugEvent, DebugStore } from './services/DebugComponents'; import { DebugEvent, DebugStore } from './services/DebugComponents';
import { RuntimeAppSchemaManager } from './services/RuntimeAppSchemaManager'; import { RuntimeAppSchemaManager } from './services/RuntimeAppSchemaManager';
@ -28,37 +27,16 @@ export const App: React.FC<AppProps> = props => {
initStateAndMethod(services.registry, services.stateManager, app.spec.components); initStateAndMethod(services.registry, services.stateManager, app.spec.components);
const { topLevelComponents, slotComponentsMap } = useMemo( const { treeMap, topLevelComponents } = resolveTreeMap(app.spec.components);
() =>
resolveAppComponents(app.spec.components, {
services,
app,
componentWrapper,
gridCallbacks,
}),
[app, componentWrapper, gridCallbacks, services]
);
const { treeMap } = resolveTreeMap(app.spec.components);
return ( return (
<div className="App" style={{ height: '100vh', overflow: 'auto' }}> <div className="App" style={{ height: '100vh', overflow: 'auto' }}>
{topLevelComponents.map(c => { {topLevelComponents.map(c => {
const slotsMap = slotComponentsMap.get(c.id);
// const Slot = genSlot({
// component: c,
// slotsMap,
// treeMap,
// ...props,
// });
return ( return (
<ImplWrapper <ImplWrapper
key={c.id} key={c.id}
component={c} component={c}
services={services} services={services}
slotsMap={slotsMap}
Slot={() => null}
treeMap={treeMap} treeMap={treeMap}
targetSlot={null}
app={app} app={app}
componentWrapper={componentWrapper} componentWrapper={componentWrapper}
gridCallbacks={gridCallbacks} gridCallbacks={gridCallbacks}

View File

@ -16,7 +16,6 @@ type ApplicationTrait = ArrayElement<RuntimeApplicationComponent['traits']>;
const _ImplWrapper = React.forwardRef<HTMLDivElement, ImplWrapperProps>((props, ref) => { const _ImplWrapper = React.forwardRef<HTMLDivElement, ImplWrapperProps>((props, ref) => {
const { const {
component: c, component: c,
targetSlot,
app, app,
children, children,
componentWrapper: ComponentWrapper, componentWrapper: ComponentWrapper,
@ -153,15 +152,13 @@ const _ImplWrapper = React.forwardRef<HTMLDivElement, ImplWrapperProps>((props,
}, [c.properties, stateManager]); }, [c.properties, stateManager]);
const mergedProps = { ...evaledComponentProperties, ...propsFromTraits }; const mergedProps = { ...evaledComponentProperties, ...propsFromTraits };
const { slotsMap, ...restProps } = props;
const Slot = genSlots(props); const Slot = genSlots(props);
const C = unmount ? null : ( const C = unmount ? null : (
<Impl <Impl
key={c.id} key={c.id}
{...props}
{...mergedProps} {...mergedProps}
{...restProps}
Slot={Slot} Slot={Slot}
slotsMap={slotsMap}
mergeState={mergeState} mergeState={mergeState}
subscribeMethods={subscribeMethods} subscribeMethods={subscribeMethods}
/> />
@ -175,8 +172,11 @@ const _ImplWrapper = React.forwardRef<HTMLDivElement, ImplWrapperProps>((props,
); );
let parentComponent; let parentComponent;
if (targetSlot && app) {
parentComponent = app.spec.components.find(c => c.id === targetSlot.id); const slotTrait = c.traits.find(t => t.type === 'core/v1/slot')
if (slotTrait && app) {
parentComponent = app.spec.components.find(c => c.id === (slotTrait.properties.container as any).id);
} }
// wrap component, but grid_layout is root component and cannot be chosen, so don't wrap it // wrap component, but grid_layout is root component and cannot be chosen, so don't wrap it
if ( if (
@ -197,9 +197,7 @@ const _ImplWrapper = React.forwardRef<HTMLDivElement, ImplWrapperProps>((props,
const { const {
component, component,
services, services,
targetSlot,
app, app,
slotsMap,
componentWrapper, componentWrapper,
gridCallbacks, gridCallbacks,
...restProps ...restProps

View File

@ -9,7 +9,6 @@ import {
RuntimeApplicationComponent, RuntimeApplicationComponent,
} from '../../types/RuntimeSchema'; } from '../../types/RuntimeSchema';
import { EventHandlerSchema } from '../../types/TraitPropertiesSchema'; import { EventHandlerSchema } from '../../types/TraitPropertiesSchema';
import { resolveAppComponents } from '../../services/resolveAppComponents';
import { ImplWrapper } from './ImplWrapper'; import { ImplWrapper } from './ImplWrapper';
import { watch } from '../../utils/watchReactivity'; import { watch } from '../../utils/watchReactivity';
import { ImplementedRuntimeModule } from '../../services/registry'; import { ImplementedRuntimeModule } from '../../services/registry';
@ -149,23 +148,12 @@ const ModuleRendererContent: React.FC<Props & { moduleSpec: ImplementedRuntimeMo
}, [evaledHanlders, moduleId, services.apiService]); }, [evaledHanlders, moduleId, services.apiService]);
const result = useMemo(() => { const result = useMemo(() => {
const { topLevelComponents, slotComponentsMap } = resolveAppComponents( const { treeMap, topLevelComponents } = resolveTreeMap(evaledModuleTemplate);
evaledModuleTemplate,
{
services,
app,
}
);
const {treeMap} = resolveTreeMap(evaledModuleTemplate)
return topLevelComponents.map(c => { return topLevelComponents.map(c => {
const slotsMap = slotComponentsMap.get(c.id);
return ( return (
<ImplWrapper <ImplWrapper
key={c.id} key={c.id}
component={c} component={c}
slotsMap={slotsMap}
Slot={() => null}
targetSlot={null}
services={services} services={services}
app={app} app={app}
treeMap={treeMap} treeMap={treeMap}
@ -176,7 +164,6 @@ const ModuleRendererContent: React.FC<Props & { moduleSpec: ImplementedRuntimeMo
return <>{result}</>; return <>{result}</>;
}; };
function parseTypeComponents( function parseTypeComponents(
c: Application['spec']['components'][0] c: Application['spec']['components'][0]

View File

@ -17,15 +17,7 @@ export function genSlots<K extends string>(
return ( return (
<> <>
{childrenSchema.map(c => { {childrenSchema.map(c => {
return ( return <ImplWrapper key={c.id} {...props} component={c} />;
<ImplWrapper
Slot={() => null}
targetSlot={null}
key={c.id}
{...props}
component={c}
/>
);
})} })}
</> </>
); );
@ -41,14 +33,6 @@ export function genSlotsAsArray<K extends string>(
return []; return [];
} }
return childrenSchema.map(c => { return childrenSchema.map(c => {
return () => ( return () => <ImplWrapper key={c.id} {...props} component={c} />;
<ImplWrapper
Slot={() => null}
targetSlot={null}
key={c.id}
{...props}
component={c}
/>
);
}); });
} }

View File

@ -21,7 +21,12 @@ import {
RouterCtx, RouterCtx,
useNavigate, useNavigate,
} from './hooks'; } from './hooks';
import { SlotsMap } from '../../../types/RuntimeSchema'; import {
RuntimeApplicationComponent,
UIServices,
TreeMap,
} from '../../../types/RuntimeSchema';
import { genSlotsAsArray } from '../../../components/_internal/Slot';
export type RouteLikeElement = PropsWithChildren<{ export type RouteLikeElement = PropsWithChildren<{
path?: string; path?: string;
@ -51,7 +56,7 @@ export const Route: React.FC<RouteProps> = ({ match, children, mergeState }) =>
} }
mergeState(destroyObj); mergeState(destroyObj);
}; };
}, [params]); }, [matches, mergeState, params]);
if (!matches) return null; if (!matches) return null;
return typeof children === 'function' ? children(params) : children; return typeof children === 'function' ? children(params) : children;
}; };
@ -59,18 +64,15 @@ export const Route: React.FC<RouteProps> = ({ match, children, mergeState }) =>
type SwitchProps = { type SwitchProps = {
location?: string; location?: string;
switchPolicy: SwitchPolicy; switchPolicy: SwitchPolicy;
slotMap?: SlotsMap<string>; component: RuntimeApplicationComponent;
treeMap: TreeMap<string>;
services: UIServices;
mergeState: (partialState: any) => void; mergeState: (partialState: any) => void;
subscribeMethods: (map: { [key: string]: (parameters: any) => void }) => void; subscribeMethods: (map: { [key: string]: (parameters: any) => void }) => void;
}; };
export const Switch: React.FC<SwitchProps> = ({ export const Switch: React.FC<SwitchProps> = props => {
switchPolicy, const { switchPolicy, location, mergeState, subscribeMethods } = props;
location,
slotMap,
mergeState,
subscribeMethods,
}) => {
const [originalLocation] = useLocation(); const [originalLocation] = useLocation();
const matcher = useMemo(() => makeMatcher(), []); const matcher = useMemo(() => makeMatcher(), []);
@ -80,7 +82,7 @@ export const Switch: React.FC<SwitchProps> = ({
let defaultPath: string | undefined = undefined; let defaultPath: string | undefined = undefined;
const result = switchPolicy.map( const result = switchPolicy.map(
({ type, path, slotId, href, default: _default, exact, strict, sensitive }) => { ({ type, path, slotId, href, default: _default, exact, strict, sensitive }) => {
const componentsArr = slotMap && slotMap.get(slotId); const componentsArr = genSlotsAsArray(props, slotId);
if (defaultPath === undefined && _default) { if (defaultPath === undefined && _default) {
defaultPath = path; defaultPath = path;
} }
@ -106,7 +108,7 @@ export const Switch: React.FC<SwitchProps> = ({
if (componentsArr.length !== 1) { if (componentsArr.length !== 1) {
console.warn('router slot can only have one component'); console.warn('router slot can only have one component');
} }
const { component: C } = componentsArr[0]; const C = componentsArr[0];
if (C.displayName === 'router') { if (C.displayName === 'router') {
return ( return (
// it should match both itself and its children path // it should match both itself and its children path
@ -141,7 +143,7 @@ export const Switch: React.FC<SwitchProps> = ({
); );
} }
return result; return result;
}, [switchPolicy]); }, [mergeState, props, switchPolicy]);
useEffect(() => { useEffect(() => {
subscribeMethods({ subscribeMethods({
@ -149,7 +151,7 @@ export const Switch: React.FC<SwitchProps> = ({
naviagte(path); naviagte(path);
}, },
}); });
}, []); }, [naviagte, subscribeMethods]);
useEffect(() => { useEffect(() => {
// to assign location as a state // to assign location as a state
@ -161,7 +163,7 @@ export const Switch: React.FC<SwitchProps> = ({
route: undefined, route: undefined,
}); });
}; };
}, [loc]); }, [loc, mergeState]);
for (const element of flattenChildren(routes)) { for (const element of flattenChildren(routes)) {
const match: Match<DefaultParams> = element.props.path const match: Match<DefaultParams> = element.props.path
@ -228,7 +230,7 @@ export const Redirect: React.FC<RedirectProps> = props => {
// empty array means running the effect once, navRef is a ref so it never changes // empty array means running the effect once, navRef is a ref so it never changes
useLayoutEffect(() => { useLayoutEffect(() => {
navRef.current!(); navRef.current!();
}, []); }, [navRef]);
return null; return null;
}; };

View File

@ -59,13 +59,10 @@ export default implementRuntimeComponent({
styleSlots: [], styleSlots: [],
events: [], events: [],
}, },
})(({ slotsMap, switchPolicy, subscribeMethods, mergeState }) => { })((props) => {
return ( return (
<Switch <Switch
slotMap={slotsMap} {...props}
switchPolicy={switchPolicy}
subscribeMethods={subscribeMethods}
mergeState={mergeState}
></Switch> ></Switch>
); );
}); });

View File

@ -1,65 +0,0 @@
import React from 'react';
import { RuntimeApplication } from '@sunmao-ui/core';
import { ContainerPropertySchema } from '../traits/core/slot';
import { Static } from '@sinclair/typebox';
import {
ComponentParamsFromApp,
UIServices,
SlotComponentMap,
} from '../types/RuntimeSchema';
import { ImplWrapper } from '../components/_internal/ImplWrapper';
export function resolveAppComponents(
components: RuntimeApplication['spec']['components'],
params: {
services: UIServices;
app?: RuntimeApplication;
} & ComponentParamsFromApp
): {
topLevelComponents: RuntimeApplication['spec']['components'];
slotComponentsMap: SlotComponentMap;
} {
const topLevelComponents: RuntimeApplication['spec']['components'] = [];
const slotComponentsMap: SlotComponentMap = new Map();
for (const c of components) {
// handle component with slot trait
const slotTrait = c.traits.find(t => t.parsedType.name === 'slot');
if (slotTrait) {
const { id, slot } = (
slotTrait.properties as {
container: Static<typeof ContainerPropertySchema>;
}
).container;
if (!slotComponentsMap.has(id)) {
slotComponentsMap.set(id, new Map());
}
if (!slotComponentsMap.get(id)?.has(slot)) {
slotComponentsMap.get(id)?.set(slot, []);
}
const component = React.forwardRef<HTMLDivElement, any>((props, ref) => (
<ImplWrapper
component={c}
slotsMap={slotComponentsMap.get(c.id)}
targetSlot={{ id, slot }}
{...params}
{...props}
ref={ref}
/>
));
component.displayName = c.parsedType.name;
slotComponentsMap.get(id)?.get(slot)?.push({
component,
id: c.id,
});
}
// if the component is neither assigned with slot trait nor route trait, consider it as a top level component
!slotTrait && topLevelComponents.push(c);
}
return {
topLevelComponents,
slotComponentsMap,
};
}

View File

@ -44,11 +44,7 @@ export type AppProps = {
// TODO: (type-safe), remove fallback type // TODO: (type-safe), remove fallback type
export type ImplWrapperProps<KSlot extends string = string> = { export type ImplWrapperProps<KSlot extends string = string> = {
component: RuntimeApplicationComponent; component: RuntimeApplicationComponent;
// TODO: (type-safe), remove slotsMap from props
slotsMap: SlotsMap<KSlot> | undefined;
treeMap: TreeMap<KSlot>; treeMap: TreeMap<KSlot>;
Slot: SlotType<KSlot>;
targetSlot: { id: string; slot: string } | null;
services: UIServices; services: UIServices;
app?: RuntimeApplication; app?: RuntimeApplication;
} & ComponentParamsFromApp; } & ComponentParamsFromApp;
@ -58,15 +54,6 @@ export type TreeMap<KSlot extends string> = Record<
Record<KSlot, RuntimeApplicationComponent[]> Record<KSlot, RuntimeApplicationComponent[]>
>; >;
export type SlotComponentMap = Map<string, SlotsMap<string>>;
export type SlotsMap<K extends string> = Map<
K,
Array<{
component: React.FC;
id: string;
}>
>;
export type CallbackMap<K extends string> = Record<K, () => void>; export type CallbackMap<K extends string> = Record<K, () => void>;
export type SubscribeMethods<U> = (map: { export type SubscribeMethods<U> = (map: {
@ -87,7 +74,9 @@ export type ComponentImplementationProps<
KEvent extends string KEvent extends string
> = ImplWrapperProps<KSlot> & > = ImplWrapperProps<KSlot> &
TraitResult<KStyleSlot, KEvent>['props'] & TraitResult<KStyleSlot, KEvent>['props'] &
RuntimeFunctions<TState, TMethods>; RuntimeFunctions<TState, TMethods> & {
Slot: SlotType<KSlot>;
};
export type TraitResult<KStyleSlot extends string, KEvent extends string> = { export type TraitResult<KStyleSlot extends string, KEvent extends string> = {
props: { props: {