mirror of
https://github.com/smartxworks/sunmao-ui.git
synced 2024-11-21 03:15:49 +08:00
feat: add refresh button
This commit is contained in:
parent
e3f7c4ea62
commit
2ffb4cc84b
@ -1,4 +1,4 @@
|
||||
import React, { useMemo, useState, useEffect } from 'react';
|
||||
import React, { useMemo, useState, useCallback, useEffect } from 'react';
|
||||
import { Application } from '@sunmao-ui/core';
|
||||
import {
|
||||
GridCallbacks,
|
||||
@ -38,6 +38,7 @@ type Props = {
|
||||
stateStore: ReturnOfInit['stateManager']['store'];
|
||||
services: EditorServices;
|
||||
libs: SunmaoLib[];
|
||||
onRefresh: () => void;
|
||||
};
|
||||
|
||||
const getResizeBarStyle = (type: 'exploreMenu' | 'toolMenu') => {
|
||||
@ -84,7 +85,7 @@ const ApiFormStyle = css`
|
||||
`;
|
||||
|
||||
export const Editor: React.FC<Props> = observer(
|
||||
({ App, registry, stateStore, services, libs }) => {
|
||||
({ App, registry, stateStore, services, libs, onRefresh: onRefreshApp }) => {
|
||||
const { eventBus, editorStore } = services;
|
||||
const {
|
||||
components,
|
||||
@ -102,15 +103,10 @@ export const Editor: React.FC<Props> = observer(
|
||||
const [preview, setPreview] = useState(false);
|
||||
const [codeMode, setCodeMode] = useState(false);
|
||||
const [code, setCode] = useState('');
|
||||
const [recoverKey, setRecoverKey] = useState(0);
|
||||
const [isError, setIsError] = useState<boolean>(false);
|
||||
const [isDisplayApp, setIsDisplayApp] = useState(true);
|
||||
const [exploreMenuWidth, setExploreMenuWidth] = useState(EXPLORE_MENU_MIN_WIDTH);
|
||||
const [toolMenuWidth, setToolMenuWidth] = useState(TOOL_MENU_MIN_WIDTH);
|
||||
|
||||
const onError = (err: Error | null) => {
|
||||
setIsError(err !== null);
|
||||
};
|
||||
|
||||
const gridCallbacks: GridCallbacks = useMemo(() => {
|
||||
return {
|
||||
// drag an existing component
|
||||
@ -153,8 +149,8 @@ export const Editor: React.FC<Props> = observer(
|
||||
}, [components]);
|
||||
|
||||
const appComponent = useMemo(() => {
|
||||
return (
|
||||
<ErrorBoundary key={recoverKey} onError={onError}>
|
||||
return isDisplayApp ? (
|
||||
<ErrorBoundary>
|
||||
<App
|
||||
options={app}
|
||||
debugEvent={false}
|
||||
@ -162,8 +158,8 @@ export const Editor: React.FC<Props> = observer(
|
||||
gridCallbacks={gridCallbacks}
|
||||
/>
|
||||
</ErrorBoundary>
|
||||
);
|
||||
}, [App, app, gridCallbacks, recoverKey]);
|
||||
) : null;
|
||||
}, [App, app, gridCallbacks, isDisplayApp]);
|
||||
|
||||
const dataSourceForm = useMemo(() => {
|
||||
let component: React.ReactNode = <ComponentForm services={services} />;
|
||||
@ -183,16 +179,29 @@ export const Editor: React.FC<Props> = observer(
|
||||
return component;
|
||||
}, [activeDataSource, services, activeDataSourceType]);
|
||||
|
||||
useEffect(() => {
|
||||
// when errors happened, `ErrorBoundary` wouldn't update until rerender
|
||||
// so after the errors are fixed, would trigger this effect before `setError(false)`
|
||||
// the process to handle the error is:
|
||||
// app change -> error happen -> setError(true) -> setRecoverKey(recoverKey + 1) -> app change -> setRecoverKey(recoverKey + 1) -> setError(false)
|
||||
if (isError) {
|
||||
setRecoverKey(recoverKey + 1);
|
||||
const onRefresh = useCallback(()=> {
|
||||
services.stateManager.clear();
|
||||
setIsDisplayApp(false);
|
||||
onRefreshApp();
|
||||
}, [services.stateManager, onRefreshApp]);
|
||||
useEffect(()=> {
|
||||
// Wait until the app is completely unmounted before remounting it
|
||||
if (isDisplayApp === false) {
|
||||
setIsDisplayApp(true);
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [app, isError]); // it only should depend on the app schema and `isError` to update
|
||||
}, [isDisplayApp]);
|
||||
const onCodeMode = useCallback(v => {
|
||||
setCodeMode(v);
|
||||
if (!v && code) {
|
||||
eventBus.send(
|
||||
'operation',
|
||||
genOperation(registry, 'replaceApp', {
|
||||
app: new AppModel(JSON.parse(code).spec.components, registry),
|
||||
})
|
||||
);
|
||||
}
|
||||
}, [code, eventBus, registry]);
|
||||
const onPreview = useCallback(() => setPreview(true), []);
|
||||
|
||||
const renderMain = () => {
|
||||
const appBox = (
|
||||
@ -374,19 +383,10 @@ export const Editor: React.FC<Props> = observer(
|
||||
<EditorHeader
|
||||
scale={scale}
|
||||
setScale={setScale}
|
||||
onPreview={() => setPreview(true)}
|
||||
onPreview={onPreview}
|
||||
codeMode={codeMode}
|
||||
onCodeMode={v => {
|
||||
setCodeMode(v);
|
||||
if (!v && code) {
|
||||
eventBus.send(
|
||||
'operation',
|
||||
genOperation(registry, 'replaceApp', {
|
||||
app: new AppModel(JSON.parse(code).spec.components, registry),
|
||||
})
|
||||
);
|
||||
}
|
||||
}}
|
||||
onRefresh={onRefresh}
|
||||
onCodeMode={onCodeMode}
|
||||
/>
|
||||
<Box display="flex" flex="1" overflow="auto">
|
||||
{renderMain()}
|
||||
|
@ -7,7 +7,8 @@ export const EditorHeader: React.FC<{
|
||||
onPreview: () => void;
|
||||
codeMode: boolean;
|
||||
onCodeMode: (v: boolean) => void;
|
||||
}> = ({ scale, setScale, onPreview, onCodeMode, codeMode }) => {
|
||||
onRefresh: () => void;
|
||||
}> = ({ scale, setScale, onPreview, onCodeMode, onRefresh, codeMode }) => {
|
||||
return (
|
||||
<Flex p={2} borderBottomWidth="2px" borderColor="gray.200" align="center">
|
||||
<Flex flex="1">
|
||||
@ -31,6 +32,9 @@ export const EditorHeader: React.FC<{
|
||||
</Button>
|
||||
</Flex>
|
||||
<Flex flex="1" justify="end">
|
||||
<Button colorScheme="blue" marginRight="8px" onClick={onRefresh}>
|
||||
refresh
|
||||
</Button>
|
||||
<Button colorScheme="blue" onClick={onPreview}>
|
||||
preview
|
||||
</Button>
|
||||
|
@ -2,18 +2,15 @@ import React from 'react';
|
||||
|
||||
type Props = {
|
||||
onError?: (error: Error | null) => void;
|
||||
}
|
||||
};
|
||||
|
||||
class ErrorBoundary extends React.Component<
|
||||
Props,
|
||||
{ error: unknown }
|
||||
> {
|
||||
class ErrorBoundary extends React.Component<Props, { error: Error | null }> {
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
this.state = { error: null };
|
||||
}
|
||||
|
||||
static getDerivedStateFromError(error: unknown) {
|
||||
static getDerivedStateFromError(error: Error) {
|
||||
return { error };
|
||||
}
|
||||
|
||||
@ -27,7 +24,7 @@ class ErrorBoundary extends React.Component<
|
||||
|
||||
render() {
|
||||
if (this.state.error) {
|
||||
return String(this.state.error);
|
||||
return <div style={{ whiteSpace: 'pre-line' }}>{this.state.error.stack}</div>;
|
||||
}
|
||||
|
||||
return this.props.children;
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { Editor as _Editor } from './components/Editor';
|
||||
import { initSunmaoUI, SunmaoLib, SunmaoUIRuntimeProps } from '@sunmao-ui/runtime';
|
||||
import { AppModelManager } from './operations/AppModelManager';
|
||||
import React from 'react';
|
||||
import React, { useState, useCallback } from 'react';
|
||||
import {
|
||||
widgets as internalWidgets,
|
||||
WidgetManager,
|
||||
@ -95,15 +95,21 @@ export function initSunmaoUIEditor(props: SunmaoUIEditorProps = {}) {
|
||||
};
|
||||
|
||||
const Editor: React.FC = () => {
|
||||
const [store, setStore] = useState(stateManager.store);
|
||||
const onRefresh = useCallback(()=> {
|
||||
setStore(stateManager.store);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<ChakraProvider theme={editorTheme}>
|
||||
<_Editor
|
||||
App={App}
|
||||
eleMap={ui.eleMap}
|
||||
registry={registry}
|
||||
stateStore={stateManager.store}
|
||||
stateStore={store}
|
||||
services={services}
|
||||
libs={props.libs || []}
|
||||
onRefresh={onRefresh}
|
||||
/>
|
||||
</ChakraProvider>
|
||||
);
|
||||
|
@ -84,7 +84,11 @@ const _ImplWrapper = React.forwardRef<HTMLDivElement, ImplWrapperProps>((props,
|
||||
);
|
||||
|
||||
// result returned from traits
|
||||
const [traitResults, setTraitResults] = useState<TraitResult<string, string>[]>([]);
|
||||
const [traitResults, setTraitResults] = useState<TraitResult<string, string>[]>(() => {
|
||||
return c.traits.map(trait =>
|
||||
executeTrait(trait, stateManager.deepEval(trait.properties))
|
||||
);
|
||||
});
|
||||
|
||||
// eval traits' properties then execute traits
|
||||
useEffect(() => {
|
||||
@ -121,11 +125,15 @@ const _ImplWrapper = React.forwardRef<HTMLDivElement, ImplWrapperProps>((props,
|
||||
}
|
||||
|
||||
let effects = prevProps?.effects || [];
|
||||
let unmountEffects = prevProps?.unmountEffects || [];
|
||||
if (result.props?.effects) {
|
||||
effects = effects?.concat(result.props?.effects);
|
||||
}
|
||||
if (result.props?.unmountEffects) {
|
||||
unmountEffects = unmountEffects?.concat(result.props?.unmountEffects);
|
||||
}
|
||||
|
||||
return merge(prevProps, result.props, { effects });
|
||||
return merge(prevProps, result.props, { effects, unmountEffects });
|
||||
},
|
||||
{} as TraitResult<string, string>['props']
|
||||
);
|
||||
|
@ -24,12 +24,17 @@ export default implementRuntimeComponent({
|
||||
styleSlots: [],
|
||||
events: [],
|
||||
},
|
||||
})(({ effects }) => {
|
||||
})(({ effects, unmountEffects }) => {
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
effects?.forEach(e => e());
|
||||
};
|
||||
}, [effects]);
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
unmountEffects?.forEach(e => e());
|
||||
};
|
||||
}, []);
|
||||
|
||||
return null;
|
||||
});
|
||||
|
@ -27,7 +27,11 @@ const StateTraitFactory: TraitImplFactory<Static<typeof PropsSpec>> = () => {
|
||||
}
|
||||
|
||||
return {
|
||||
props: null,
|
||||
props: {
|
||||
unmountEffects: [() => {
|
||||
HasInitializedMap.delete(hashId);
|
||||
}]
|
||||
},
|
||||
};
|
||||
};
|
||||
};
|
||||
|
@ -8,6 +8,7 @@ export type TraitResult<KStyleSlot extends string, KEvent extends string> = {
|
||||
customStyle?: Record<KStyleSlot, string>;
|
||||
callbackMap?: CallbackMap<KEvent>;
|
||||
effects?: Array<() => void>;
|
||||
unmountEffects?: Array<() => void>;
|
||||
} | null;
|
||||
unmount?: boolean;
|
||||
};
|
||||
|
Loading…
Reference in New Issue
Block a user