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,
subscribeMethods,
hideSubmit,
slotsMap,
callbackMap,
services,
customStyle,
Slot,
treeMap,
component
}) => {
const [invalidArray, setInvalidArray] = useState<boolean[]>([]);
const [isFormInvalid, setIsFormInvalid] = useState<boolean>(false);
const formDataRef = useRef<Record<string, any>>({});
const formControlIds = useMemo<string[]>(() => {
return (
slotsMap?.get('content')?.map(slot => {
treeMap[component.id]?.content.map(slot => {
return slot.id;
}) || []
);
}, [slotsMap]);
}, [component.id, treeMap]);
useEffect(() => {
setInvalidArray(

View File

@ -60,16 +60,17 @@ export default implementRuntimeComponent({
fieldName,
isRequired,
helperText,
slotsMap,
mergeState,
services,
customStyle,
Slot,
treeMap,
component,
}) => {
const [inputValue, setInputValue] = useState('');
// don't show Invalid state on component mount
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({
isInvalid: false,
errorMsg: '',

View File

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

View File

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

View File

@ -3,7 +3,6 @@ import { ApplicationComponent } from '@sunmao-ui/core';
export type ChildrenMap = Map<string, SlotsMap>;
type SlotsMap = Map<string, ApplicationComponent[]>;
// similar to resolveAppComponents
export function resolveApplicationComponents(components: ApplicationComponent[]): {
topLevelComponents: ApplicationComponent[];
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 { ImplWrapper } from './components/_internal/ImplWrapper';
import { resolveAppComponents } from './services/resolveAppComponents';
import { AppProps, UIServices } from './types/RuntimeSchema';
import { DebugEvent, DebugStore } from './services/DebugComponents';
import { RuntimeAppSchemaManager } from './services/RuntimeAppSchemaManager';
@ -28,37 +27,16 @@ export const App: React.FC<AppProps> = props => {
initStateAndMethod(services.registry, services.stateManager, app.spec.components);
const { topLevelComponents, slotComponentsMap } = useMemo(
() =>
resolveAppComponents(app.spec.components, {
services,
app,
componentWrapper,
gridCallbacks,
}),
[app, componentWrapper, gridCallbacks, services]
);
const { treeMap } = resolveTreeMap(app.spec.components);
const { treeMap, topLevelComponents } = resolveTreeMap(app.spec.components);
return (
<div className="App" style={{ height: '100vh', overflow: 'auto' }}>
{topLevelComponents.map(c => {
const slotsMap = slotComponentsMap.get(c.id);
// const Slot = genSlot({
// component: c,
// slotsMap,
// treeMap,
// ...props,
// });
return (
<ImplWrapper
key={c.id}
component={c}
services={services}
slotsMap={slotsMap}
Slot={() => null}
treeMap={treeMap}
targetSlot={null}
app={app}
componentWrapper={componentWrapper}
gridCallbacks={gridCallbacks}

View File

@ -16,7 +16,6 @@ type ApplicationTrait = ArrayElement<RuntimeApplicationComponent['traits']>;
const _ImplWrapper = React.forwardRef<HTMLDivElement, ImplWrapperProps>((props, ref) => {
const {
component: c,
targetSlot,
app,
children,
componentWrapper: ComponentWrapper,
@ -153,15 +152,13 @@ const _ImplWrapper = React.forwardRef<HTMLDivElement, ImplWrapperProps>((props,
}, [c.properties, stateManager]);
const mergedProps = { ...evaledComponentProperties, ...propsFromTraits };
const { slotsMap, ...restProps } = props;
const Slot = genSlots(props);
const C = unmount ? null : (
<Impl
key={c.id}
{...props}
{...mergedProps}
{...restProps}
Slot={Slot}
slotsMap={slotsMap}
mergeState={mergeState}
subscribeMethods={subscribeMethods}
/>
@ -175,8 +172,11 @@ const _ImplWrapper = React.forwardRef<HTMLDivElement, ImplWrapperProps>((props,
);
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
if (
@ -197,9 +197,7 @@ const _ImplWrapper = React.forwardRef<HTMLDivElement, ImplWrapperProps>((props,
const {
component,
services,
targetSlot,
app,
slotsMap,
componentWrapper,
gridCallbacks,
...restProps

View File

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

View File

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

View File

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

View File

@ -59,13 +59,10 @@ export default implementRuntimeComponent({
styleSlots: [],
events: [],
},
})(({ slotsMap, switchPolicy, subscribeMethods, mergeState }) => {
})((props) => {
return (
<Switch
slotMap={slotsMap}
switchPolicy={switchPolicy}
subscribeMethods={subscribeMethods}
mergeState={mergeState}
{...props}
></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
export type ImplWrapperProps<KSlot extends string = string> = {
component: RuntimeApplicationComponent;
// TODO: (type-safe), remove slotsMap from props
slotsMap: SlotsMap<KSlot> | undefined;
treeMap: TreeMap<KSlot>;
Slot: SlotType<KSlot>;
targetSlot: { id: string; slot: string } | null;
services: UIServices;
app?: RuntimeApplication;
} & ComponentParamsFromApp;
@ -58,15 +54,6 @@ export type TreeMap<KSlot extends string> = Record<
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 SubscribeMethods<U> = (map: {
@ -87,7 +74,9 @@ export type ComponentImplementationProps<
KEvent extends string
> = ImplWrapperProps<KSlot> &
TraitResult<KStyleSlot, KEvent>['props'] &
RuntimeFunctions<TState, TMethods>;
RuntimeFunctions<TState, TMethods> & {
Slot: SlotType<KSlot>;
};
export type TraitResult<KStyleSlot extends string, KEvent extends string> = {
props: {