feat(infra): add ability to mount nodes to nearest FrameworkScope root (#7551)

This commit is contained in:
@@ -4,6 +4,9 @@ import type { FrameworkProvider, Scope, Service } from '../core';
|
|||||||
import { ComponentNotFoundError, Framework } from '../core';
|
import { ComponentNotFoundError, Framework } from '../core';
|
||||||
import { parseIdentifier } from '../core/identifier';
|
import { parseIdentifier } from '../core/identifier';
|
||||||
import type { GeneralIdentifier, IdentifierType, Type } from '../core/types';
|
import type { GeneralIdentifier, IdentifierType, Type } from '../core/types';
|
||||||
|
import { MountPoint } from './scope-root-components';
|
||||||
|
|
||||||
|
export { useMount } from './scope-root-components';
|
||||||
|
|
||||||
export const FrameworkStackContext = React.createContext<FrameworkProvider[]>([
|
export const FrameworkStackContext = React.createContext<FrameworkProvider[]>([
|
||||||
Framework.EMPTY.provider(),
|
Framework.EMPTY.provider(),
|
||||||
@@ -126,7 +129,7 @@ export const FrameworkScope = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<FrameworkStackContext.Provider value={nextStack}>
|
<FrameworkStackContext.Provider value={nextStack}>
|
||||||
{children}
|
<MountPoint>{children}</MountPoint>
|
||||||
</FrameworkStackContext.Provider>
|
</FrameworkStackContext.Provider>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -0,0 +1,74 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
type NodesMap = Map<
|
||||||
|
number,
|
||||||
|
{
|
||||||
|
node: React.ReactNode;
|
||||||
|
debugKey?: string;
|
||||||
|
}
|
||||||
|
>;
|
||||||
|
|
||||||
|
const ScopeRootComponentsContext = React.createContext<{
|
||||||
|
nodes: NodesMap;
|
||||||
|
setNodes: React.Dispatch<React.SetStateAction<NodesMap>>;
|
||||||
|
}>({ nodes: new Map(), setNodes: () => {} });
|
||||||
|
|
||||||
|
let _id = 0;
|
||||||
|
/**
|
||||||
|
* A hook to add nodes to the nearest scope's root
|
||||||
|
*/
|
||||||
|
export const useMount = (debugKey?: string) => {
|
||||||
|
const [id] = React.useState(_id++);
|
||||||
|
const { setNodes } = React.useContext(ScopeRootComponentsContext);
|
||||||
|
|
||||||
|
const unmount = React.useCallback(() => {
|
||||||
|
setNodes(prev => {
|
||||||
|
if (!prev.has(id)) {
|
||||||
|
return prev;
|
||||||
|
}
|
||||||
|
const next = new Map(prev);
|
||||||
|
next.delete(id);
|
||||||
|
return next;
|
||||||
|
});
|
||||||
|
}, [id, setNodes]);
|
||||||
|
|
||||||
|
const mount = React.useCallback(
|
||||||
|
(node: React.ReactNode) => {
|
||||||
|
setNodes(prev => new Map(prev).set(id, { node, debugKey }));
|
||||||
|
return unmount;
|
||||||
|
},
|
||||||
|
[setNodes, id, debugKey, unmount]
|
||||||
|
);
|
||||||
|
|
||||||
|
return React.useMemo(() => {
|
||||||
|
return {
|
||||||
|
/**
|
||||||
|
* Add a node to the nearest scope root
|
||||||
|
* ```tsx
|
||||||
|
* const { mount } = useMount();
|
||||||
|
* useEffect(() => {
|
||||||
|
* const unmount = mount(<div>Node</div>);
|
||||||
|
* return unmount;
|
||||||
|
* }, [])
|
||||||
|
* ```
|
||||||
|
* @return A function to unmount the added node.
|
||||||
|
*/
|
||||||
|
mount,
|
||||||
|
};
|
||||||
|
}, [mount]);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const MountPoint = ({ children }: React.PropsWithChildren) => {
|
||||||
|
const [nodes, setNodes] = React.useState<NodesMap>(new Map());
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ScopeRootComponentsContext.Provider value={{ nodes, setNodes }}>
|
||||||
|
{children}
|
||||||
|
{Array.from(nodes.entries()).map(([id, { node, debugKey }]) => (
|
||||||
|
<div data-testid={debugKey} key={id} style={{ display: 'contents' }}>
|
||||||
|
{node}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</ScopeRootComponentsContext.Provider>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
import { Modal } from '@affine/component';
|
import { Modal } from '@affine/component';
|
||||||
import { useCallback, useState } from 'react';
|
import { useMount } from '@toeverything/infra';
|
||||||
|
import { useCallback, useEffect, useState } from 'react';
|
||||||
|
|
||||||
import type { AllPageListConfig } from './edit-collection';
|
import type { AllPageListConfig } from './edit-collection';
|
||||||
import { SelectPage } from './select-page';
|
import { SelectPage } from './select-page';
|
||||||
@@ -20,8 +21,11 @@ export const useSelectPage = ({
|
|||||||
const handleCancel = useCallback(() => {
|
const handleCancel = useCallback(() => {
|
||||||
close(false);
|
close(false);
|
||||||
}, [close]);
|
}, [close]);
|
||||||
return {
|
|
||||||
node: (
|
const { mount } = useMount('select-page-modal');
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
return mount(
|
||||||
<Modal
|
<Modal
|
||||||
open={!!value}
|
open={!!value}
|
||||||
onOpenChange={close}
|
onOpenChange={close}
|
||||||
@@ -47,16 +51,22 @@ export const useSelectPage = ({
|
|||||||
/>
|
/>
|
||||||
) : null}
|
) : null}
|
||||||
</Modal>
|
</Modal>
|
||||||
|
);
|
||||||
|
}, [allPageListConfig, close, handleCancel, mount, value]);
|
||||||
|
|
||||||
|
return {
|
||||||
|
open: useCallback(
|
||||||
|
(init: string[]): Promise<string[]> =>
|
||||||
|
new Promise<string[]>(res => {
|
||||||
|
onChange({
|
||||||
|
init,
|
||||||
|
onConfirm: list => {
|
||||||
|
close(false);
|
||||||
|
res(list);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}),
|
||||||
|
[close]
|
||||||
),
|
),
|
||||||
open: (init: string[]): Promise<string[]> =>
|
|
||||||
new Promise<string[]>(res => {
|
|
||||||
onChange({
|
|
||||||
init,
|
|
||||||
onConfirm: list => {
|
|
||||||
close(false);
|
|
||||||
res(list);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}),
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -72,7 +72,7 @@ export const RulesMode = ({
|
|||||||
allowListPages.push(meta);
|
allowListPages.push(meta);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
const { node: selectPageNode, open } = useSelectPage({ allPageListConfig });
|
const { open } = useSelectPage({ allPageListConfig });
|
||||||
const openSelectPage = useCallback(() => {
|
const openSelectPage = useCallback(() => {
|
||||||
open(collection.allowList).then(
|
open(collection.allowList).then(
|
||||||
ids => {
|
ids => {
|
||||||
@@ -343,7 +343,6 @@ export const RulesMode = ({
|
|||||||
</div>
|
</div>
|
||||||
<div style={{ display: 'flex', alignItems: 'center' }}>{buttons}</div>
|
<div style={{ display: 'flex', alignItems: 'center' }}>{buttons}</div>
|
||||||
</div>
|
</div>
|
||||||
{selectPageNode}
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user