render border

This commit is contained in:
Bowen Tan 2022-02-08 15:22:25 +08:00
parent 90cd805437
commit 5d182f9a0a
12 changed files with 171 additions and 16 deletions

View File

@ -55,6 +55,7 @@ export default implementRuntimeComponent({
colorScheme,
isLoading,
customStyle,
$onRef,
}) => {
useEffect(() => {
mergeState({ value: text.raw });
@ -69,6 +70,12 @@ export default implementRuntimeComponent({
});
}, [subscribeMethods]);
useEffect(() => {
if ($onRef && ref.current) {
$onRef(ref.current);
}
}, [$onRef]);
return (
<BaseButton
className={css`

View File

@ -42,7 +42,7 @@ export default implementRuntimeComponent({
methods: {},
events: [],
},
})(({ direction, wrap, align, justify, spacing, slotsElements, customStyle }) => {
})(({ direction, wrap, align, justify, spacing, slotsElements, customStyle, $ref }) => {
return (
<BaseHStack
height="full"
@ -55,6 +55,7 @@ export default implementRuntimeComponent({
className={css`
${customStyle?.content}
`}
ref={$ref}
{...{ direction, wrap, align, justify, spacing }}
>
{slotsElements.content}

View File

@ -102,6 +102,7 @@ export default implementRuntimeComponent({
subscribeMethods,
defaultValue,
customStyle,
$ref,
}) => {
const [value, setValue] = React.useState(defaultValue || ''); // TODO: pin input
const onChange = (event: React.ChangeEvent<HTMLInputElement>) =>
@ -125,9 +126,9 @@ export default implementRuntimeComponent({
},
});
}, [defaultValue, subscribeMethods]);
console.log('input', $ref);
return (
<InputGroup size={size} background="white">
<InputGroup size={size} background="white" ref={$ref as any}>
{left ? (
left.type === 'addon' ? (
<InputLeftAddon>{left.children}</InputLeftAddon>

View File

@ -42,7 +42,7 @@ export default implementRuntimeComponent({
methods: {},
events: [],
},
})(({ direction, wrap, align, justify, spacing, slotsElements, customStyle }) => {
})(({ direction, wrap, align, justify, spacing, slotsElements, customStyle, $ref }) => {
return (
<BaseVStack
width="full"
@ -55,6 +55,7 @@ export default implementRuntimeComponent({
className={css`
${customStyle?.content}
`}
ref={$ref}
{...{ direction, wrap, align, justify, spacing }}
>
{slotsElements.content}

View File

@ -0,0 +1,115 @@
/* eslint-disable react-hooks/rules-of-hooks */
import React, { useMemo, useRef } from 'react';
import { css, cx } from '@emotion/css';
import { EditorServices } from '../types';
const MaskWrapperStyle = css`
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
`;
const outlineMaskTextStyle = css`
position: absolute;
z-index: 1;
right: 0px;
padding: 0 4px;
font-size: 14px;
font-weight: black;
color: white;
&.hover {
background-color: black;
}
&.select {
background-color: red;
}
&.idle,
&.drag {
display: none;
}
&.top {
top: -4px;
transform: translateY(-100%);
border-radius: 3px 3px 0px 0px;
}
&.bottom {
bottom: -4px;
transform: translateY(100%);
border-radius: 0px 0px 3px 3px;
}
`;
const outlineMaskStyle = css`
position: absolute;
border: 1px solid;
pointer-events: none;
/* create a bfc */
transform: translate3d(0, 0, 0);
z-index: 10;
top: 100px;
left: 0;
width: 200px;
height: 100px;
&.idle {
display: none;
}
&.hover {
border-color: black;
}
&.select {
border-color: red;
}
&.drag {
border-color: orange;
}
`;
type Props = {
services: EditorServices;
eleMap: Map<string, HTMLElement>;
};
export const ComponentContainerMask: React.FC<Props> = props => {
const { eleMap } = props;
const wrapperRef = useRef<HTMLDivElement>(null);
// const { hoverComponentId } = editorStore;'
const rects = Array.from(eleMap.keys()).map(id => {
const ele = eleMap.get(id);
return {
id,
rect: ele?.getBoundingClientRect(),
};
});
const borders = useMemo(() => {
if (!wrapperRef.current) {
return null;
}
const wrapperRect = wrapperRef.current.getBoundingClientRect();
return rects.map(({ id, rect }) => {
console.log('rect', rect)
console.log('wrapperRect', wrapperRect)
const style = {
top: (rect?.top || 0) - wrapperRect.top - 2,
left: (rect?.left || 0) - wrapperRect.left - 2,
height: (rect?.height || 0) + 4,
width: (rect?.width || 0) + 4,
};
return (
<div key={id} className={cx([outlineMaskStyle, 'hover'])} style={style}>
<span className={cx([outlineMaskTextStyle, 'hover'])}>{id}</span>
</div>
);
});
}, [rects]);
return (
<div className={MaskWrapperStyle} ref={wrapperRef}>
{borders}
</div>
);
};

View File

@ -12,7 +12,6 @@ import { StructureTree } from './StructureTree';
import { ComponentList } from './ComponentsList';
import { EditorHeader } from './EditorHeader';
import { KeyboardEventWrapper } from './KeyboardEventWrapper';
import { useComponentWrapper } from './ComponentWrapper';
import { StateViewer, SchemaEditor } from './CodeEditor';
import { Explorer } from './Explorer';
import { genOperation } from '../operations';
@ -21,18 +20,20 @@ import ErrorBoundary from './ErrorBoundary';
import { PreviewModal } from './PreviewModal';
import { WarningArea } from './WarningArea';
import { EditorServices } from '../types';
import { ComponentContainerMask } from './ComponentContainerMask';
type ReturnOfInit = ReturnType<typeof initSunmaoUI>;
type Props = {
App: ReturnOfInit['App'];
eleMap: ReturnOfInit['eleMap'];
registry: ReturnOfInit['registry'];
stateStore: ReturnOfInit['stateManager']['store'];
services: EditorServices;
};
export const Editor: React.FC<Props> = observer(
({ App, registry, stateStore, services }) => {
({ App, registry, stateStore, eleMap, services }) => {
const { eventBus, editorStore } = services;
const {
components,
@ -52,6 +53,10 @@ export const Editor: React.FC<Props> = observer(
const [isError, setIsError] = useState<boolean>(false);
const [store, setStore] = useState(stateStore);
useEffect(() => {
console.log('eleMap', eleMap);
}, [eleMap]);
useEffect(() => {
watch(store, newValue => {
setStore(JSON.parse(JSON.stringify(newValue)));
@ -103,9 +108,9 @@ export const Editor: React.FC<Props> = observer(
};
}, [components]);
const ComponentWrapper = useMemo(() => {
return useComponentWrapper(services);
}, [services]);
// const ComponentWrapper = useMemo(() => {
// return useComponentWrapper(services);
// }, [services]);
const appComponent = useMemo(() => {
return (
@ -115,11 +120,11 @@ export const Editor: React.FC<Props> = observer(
debugEvent={false}
debugStore={false}
gridCallbacks={gridCallbacks}
componentWrapper={ComponentWrapper}
// componentWrapper={ComponentWrapper}
/>
</ErrorBoundary>
);
}, [App, ComponentWrapper, app, gridCallbacks, recoverKey]);
}, [App, app, gridCallbacks, recoverKey]);
const renderMain = () => {
const appBox = (
@ -138,8 +143,9 @@ export const Editor: React.FC<Props> = observer(
height="full"
position="absolute"
/>
<Box width="full" overflow="auto">
<Box width="full" overflow="auto" position="relative">
{appComponent}
<ComponentContainerMask services={services} eleMap={eleMap} />
</Box>
<WarningArea services={services} />
</Box>

View File

@ -75,6 +75,7 @@ export function initSunmaoUIEditor(props: SunmaoUIEditorProps = {}) {
<ChakraProvider theme={editorTheme}>
<_Editor
App={App}
eleMap={ui.eleMap}
registry={registry}
stateStore={stateManager.store}
services={services}

View File

@ -25,7 +25,7 @@ export const App: React.FC<AppProps> = props => {
const runtimeAppSchemaManager = useRef(new RuntimeAppSchemaManager());
const app = runtimeAppSchemaManager.current.update(options);
initStateAndMethod(services.registry, services.stateManager, app.spec.components);
const { childrenMap, topLevelComponents } = resolveChildrenMap(app.spec.components);
return (
<div className="App" style={{ height: '100vh', overflow: 'auto' }}>

View File

@ -14,7 +14,7 @@ const _ImplWrapper = React.forwardRef<HTMLDivElement, ImplWrapperProps>((props,
services,
childrenMap,
} = props;
const { registry, stateManager, globalHandlerMap, apiService } = props.services;
const { registry, stateManager, globalHandlerMap, apiService, eleMap } = props.services;
const childrenCache = new Map<RuntimeComponentSchema, React.ReactElement>();
const Impl = registry.getComponent(c.parsedType.version, c.parsedType.name).impl;
@ -24,6 +24,23 @@ const _ImplWrapper = React.forwardRef<HTMLDivElement, ImplWrapperProps>((props,
}
const handlerMap = useRef(globalHandlerMap.get(c.id)!);
const eleRef = useRef<HTMLElement>();
const onRef = (ele: HTMLElement) => {
console.log('onRef', ele);
eleMap.set(c.id, ele);
};
useEffect(() => {
if (eleRef.current) {
eleMap.set(c.id, eleRef.current);
console.log('挂载了', c.id, eleRef);
}
return () => {
console.log('卸载了', c.id);
eleMap.delete(c.id);
};
}, [c.id, eleMap]);
useEffect(() => {
const handler = (s: { componentId: string; name: string; parameters?: any }) => {
if (s.componentId !== c.id) {
@ -164,7 +181,6 @@ const _ImplWrapper = React.forwardRef<HTMLDivElement, ImplWrapperProps>((props,
}
return res;
}
const C = unmount ? null : (
<Impl
key={c.id}
@ -173,6 +189,8 @@ const _ImplWrapper = React.forwardRef<HTMLDivElement, ImplWrapperProps>((props,
slotsElements={genSlotsElements()}
mergeState={mergeState}
subscribeMethods={subscribeMethods}
$ref={eleRef}
$onRef={onRef}
/>
);

View File

@ -15,13 +15,15 @@ export function initSunmaoUI(props: SunmaoUIRuntimeProps = {}) {
const globalHandlerMap = initGlobalHandlerMap();
const apiService = initApiService();
const registry = initRegistry(apiService);
const eleMap = new Map<string, HTMLElement>();
return {
App: genApp({ registry, stateManager, globalHandlerMap, apiService }),
App: genApp({ registry, stateManager, globalHandlerMap, apiService, eleMap }),
stateManager,
registry,
globalHandlerMap,
apiService,
eleMap,
};
}

View File

@ -11,6 +11,7 @@ export type UIServices = {
stateManager: StateManager;
globalHandlerMap: GlobalHandlerMap;
apiService: ApiService;
eleMap: Map<string, HTMLElement>;
};
export type ComponentWrapperProps = {

View File

@ -25,6 +25,8 @@ export type ComponentImplProps<
TraitResult<KStyleSlot, KEvent>['props'] &
RuntimeFunctions<TState, TMethods> & {
slotsElements: Record<KSlot, React.ReactElement[] | React.ReactElement>;
$ref?: React.Ref<any>;
$onRef?: (ele: HTMLElement) => void;
};
export type ComponentImpl<