blob: 86e07a5c7897287a6acda47621778caca2434b8c [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import type { Cell } from '@antv/x6';
import { Alert, Form, Input, Modal } from 'antd';
import React, { useEffect, useState } from 'react';
import { useIntl } from 'umi';
import PluginDetail from '../Plugin/PluginDetail';
import { fetchList } from '../Plugin/service';
import FlowGraph from './components/FlowGraph';
import Toolbar from './components/Toolbar';
import {
DEFAULT_CONDITION_PROPS,
DEFAULT_PLUGIN_PROPS,
DEFAULT_STENCIL_WIDTH,
DEFAULT_TOOLBAR_HEIGHT,
FlowGraphEvent,
} from './constants';
import styles from './style.less';
type Props = {
chart: {
cells: Cell.Properties[];
};
readonly?: boolean;
};
type PluginProps = {
id: string;
name: string;
visible: boolean;
data: any;
};
type ConditionProps = {
id: string;
visible: boolean;
data: string;
};
const PluginFlow: React.FC<Props> = ({ chart, readonly = false }) => {
const { formatMessage } = useIntl();
// NOTE: To prevent from graph is not initialized
const [isReady, setIsReady] = useState(false);
const [plugins, setPlugins] = useState<PluginComponent.Meta[]>([]);
const [pluginProps, setPluginProps] = useState<PluginProps>(DEFAULT_PLUGIN_PROPS);
const [conditionProps, setConditionProps] = useState<ConditionProps>(DEFAULT_CONDITION_PROPS);
const getContainerSize = () => {
const leftSidebar = document.querySelector('aside.ant-layout-sider');
const blankSpaceWidth = 24 * 4;
const globalHeaderHeight = 48;
const pageHeaderHeight = 72;
const otherHeight = 191;
const width =
document.body.offsetWidth -
(leftSidebar?.clientWidth || 0) -
blankSpaceWidth -
DEFAULT_STENCIL_WIDTH;
const height = document.body.offsetHeight - globalHeaderHeight - pageHeaderHeight - otherHeight;
return {
width,
height: height < 800 ? 800 : height,
};
};
useEffect(() => {
if (!plugins.length) {
return;
}
const container = document.getElementById('container');
if (!container) {
return;
}
const siderbarCollapsedButton = document.querySelector('.ant-pro-sider-collapsed-button');
const graph = FlowGraph.init(container, plugins, chart);
(window as any).graph = FlowGraph;
setIsReady(true);
const stencilContainer = document.querySelector('#stencil') as HTMLElement;
const handleResize = () => {
const { width, height } = getContainerSize();
graph.resize(width, height);
stencilContainer.style.height = `${height + DEFAULT_TOOLBAR_HEIGHT}px`;
stencilContainer.style.width = `${DEFAULT_STENCIL_WIDTH}px`;
};
const handleLeftSidebarResize = () => {
setTimeout(() => {
handleResize();
}, 200);
};
handleResize();
graph.on(FlowGraphEvent.PLUGIN_CHANGE, setPluginProps);
graph.on(FlowGraphEvent.CONDITION_CHANGE, (props: ConditionProps) => {
setConditionProps(props);
});
if (readonly) {
graph.disableKeyboard();
}
window.addEventListener('resize', handleResize);
siderbarCollapsedButton?.addEventListener('click', handleLeftSidebarResize);
// eslint-disable-next-line
return () => {
window.removeEventListener('resize', handleResize);
siderbarCollapsedButton?.removeEventListener('click', handleLeftSidebarResize);
};
}, [plugins]);
useEffect(() => {
fetchList().then(setPlugins);
}, []);
return (
<React.Fragment>
{readonly && (
<Alert
type="warning"
message={formatMessage({ id: 'component.plugin-flow.text.preview.readonly' })}
showIcon
style={{ marginBottom: 20 }}
/>
)}
<div className={styles.container}>
<div
id="stencil"
className={styles.stencil}
style={readonly ? { width: 0, height: 0 } : {}}
/>
<div className={styles.panel}>
<div className={styles.toolbar}>{isReady && <Toolbar />}</div>
<div id="container" className={styles.flow}></div>
</div>
</div>
{pluginProps.visible && (
<PluginDetail
readonly={readonly}
schemaType="route"
name={pluginProps.name}
visible={pluginProps.visible}
pluginList={plugins}
isEnabled
initialData={{
// NOTE: We use {PluginName: data} because initialData is all plugins' data
[pluginProps.name]: pluginProps.data,
}}
onClose={() => {
setPluginProps(DEFAULT_PLUGIN_PROPS);
}}
onChange={({ formData, monacoData, shouldDelete }) => {
if (shouldDelete) {
FlowGraph.graph.removeCell(pluginProps.id);
} else {
const disable = !formData.disable;
FlowGraph.setData(pluginProps.id, { ...monacoData, disable });
}
setPluginProps(DEFAULT_PLUGIN_PROPS);
}}
/>
)}
<Modal
visible={conditionProps.visible}
title={formatMessage({ id: 'component.plugin-flow.text.condition.required' })}
onOk={() => {
FlowGraph.setData(conditionProps.id, conditionProps.data);
setConditionProps(DEFAULT_CONDITION_PROPS);
}}
onCancel={() => setConditionProps(DEFAULT_CONDITION_PROPS)}
okText={formatMessage({ id: 'component.global.confirm' })}
cancelText={formatMessage({ id: 'component.global.cancel' })}
okButtonProps={{
disabled: readonly,
}}
>
<Form.Item
label={formatMessage({ id: 'component.plugin-flow.text.condition' })}
style={{ marginBottom: 0 }}
tooltip={formatMessage({ id: 'component.plugin-flow.text.condition-rule.tooltip' })}
>
<Input
value={conditionProps.data}
disabled={readonly}
placeholder={formatMessage({ id: 'component.plugin-flow.text.condition.placeholder' })}
onChange={(e) => {
setConditionProps({
...conditionProps,
data: e.target.value,
});
}}
/>
</Form.Item>
</Modal>
</React.Fragment>
);
};
export default PluginFlow;