Commit cfeecea2 by zhaochengxiang

模型配置

parent 7b4642b6
......@@ -10,6 +10,7 @@ import Home from './view/Home';
import Manage from './view/Manage';
import Map from './view/Manage/Map';
import Model from './view/Manage/Model';
import ModelConfig from './view/Manage/ModelConfig';
import AssetManage from './view/Manage/AssetManage';
import AssetBrowse from './view/Manage/AssetBrowse';
import AssetRecycle from './view/Manage/AssetRecycle';
......@@ -17,6 +18,7 @@ import DatasourceManage from './view/Manage/DatasourceManage';
import AssetDetail from './view/Manage/AssetManage/Component/AssetDetail';
import ImportAction from './view/Manage/Model/Component/ImportAction';
import EditModel from './view/Manage/Model/Component/EditModel';
import EditTemplate from './view/Manage/ModelConfig/Component/EditTemplate';
import AssetTree from './view/Manage/AssetManage/Component/AssetTree';
export const AppContext = React.createContext();
......@@ -81,10 +83,12 @@ export class App extends React.Component {
<Route path={`${ContextPath}/login`} component={Signin} exact />
<Route path={`${ContextPath}/home`} component={Home} />
<Route path={`${ContextPath}/manage`} component={Manage} />
<Route path={`${ContextPath}/data-model-action`} component={EditModel} exact />
<Route path={`${ContextPath}/data-model-action`} component={EditModel} exact />
<Route path={`${ContextPath}/model-template-action`} component={EditTemplate} exact />
<Route path={'/center-home/view/datasource-manage'} component={DatasourceManage} exact />
<Route path={'/center-home/view/data-model'} component={Model} exact />
<Route path={'/center-home/view/model-config'} component={ModelConfig} exact />
<Route path={'/center-home/view/asset-map'} component={Map} exact />
<Route path={'/center-home/view/asset-manage'} component={AssetManage} exact />
<Route path={'/center-home/view/asset-browse'} component={AssetBrowse} exact />
......@@ -92,6 +96,7 @@ export class App extends React.Component {
<Route path={'/center-home/menu/datasource-manage'} component={DatasourceManage} exact />
<Route path={'/center-home/menu/data-model'} component={Model} exact />
<Route path={'/center-home/menu/model-config'} component={ModelConfig} exact />
<Route path={'/center-home/menu/asset-map'} component={Map} exact />
<Route path={'/center-home/menu/asset-manage'} component={AssetManage} exact />
<Route path={'/center-home/menu/asset-browse'} component={AssetBrowse} exact />
......
......@@ -11,4 +11,5 @@ export const ModelerData = 'mdata';
export const Editable = 'editable';
export const PermitCheckOut = 'permitCheckOut';
export const StateId = 'sid';
export const VersionId = 'vid';
\ No newline at end of file
export const VersionId = 'vid';
export const TemplateId = 'tid';
\ No newline at end of file
......@@ -346,6 +346,9 @@ const ModelTree = (props) => {
setItem(null);
itemRef.current = null;
getDirTreeData();
},
error: () => {
setLoading(false);
}
});
}
......
......@@ -6,9 +6,7 @@ import classNames from 'classnames';
import ModelTree from './Component/ModelTree';
import ModelTable from './Component/ModelTable';
import WordTemplateModal from './Component/WordTemplateModal';
import TemplateCURDDrawer from './Component/TemplateCURDDrawer';
import ConstraintDetailDrawer from './Component/ConstraintDetailDrawer';
import ImportModal from './Component/ImportModal';
import ImportStockWordDrawer from './Component/ImportStockWordDrawer';
import ExportDDLModal from './Component/ExportDDLModal';
......@@ -30,9 +28,7 @@ class Model extends React.Component {
constructor() {
super();
this.state = {
wordTemplateModalVisible: false,
templateCURDDrawerVisible: false,
constraintDetailDrawerVisible: false,
importModalVisible: false,
importStockWordDrawerVisible: false,
exportDDLModalVisible: false,
......@@ -185,18 +181,10 @@ class Model extends React.Component {
});
}
onWordTemplateCURDClick = () => {
this.setState({ wordTemplateModalVisible: true });
}
onTemplateCURDClick = () => {
this.setState({ templateCURDDrawerVisible: true });
}
onConstraintDetailClick = () => {
this.setState({ constraintDetailDrawerVisible: true });
}
onImportUnconditionBtnClick = () => {
const { catalogId, currentView } = this.state;
......@@ -259,19 +247,11 @@ class Model extends React.Component {
this.setState({ importExcelVisible: visible });
}
onWordTemplateCURDModalCancel = () => {
this.setState({ wordTemplateModalVisible: false });
}
onTemplateCURDDrawerCancel = (refresh = false) => {
this.setState({ templateCURDDrawerVisible: false });
refresh && this.onTableChange();
}
onConstraintDetailDrawerCancel = () => {
this.setState({ constraintDetailDrawerVisible: false });
}
onImportModalCancel = (refresh = false, hints = [], wordData = {}) => {
const { catalogId, currentView } = this.state;
......@@ -388,7 +368,7 @@ class Model extends React.Component {
}
render() {
const { importModalVisible, catalogId, loadingTableData, selectModelerIds, keyword, filterTableData, selectModelerNames, exportDDLModalVisible, exportOtherModalVisible, templateCURDDrawerVisible, wordTemplateModalVisible, constraintDetailDrawerVisible, importStockWordDrawerVisible , loadingStates, modelStates, currentModelState, currentView, recatalogModalVisible, exportDDLModalReference, currentModel, offset, historyAndVersionDrawerVisible, modelerId, startFlowModalVisible, expandTree } = this.state;
const { importModalVisible, catalogId, loadingTableData, selectModelerIds, keyword, filterTableData, selectModelerNames, exportDDLModalVisible, exportOtherModalVisible, templateCURDDrawerVisible, importStockWordDrawerVisible , loadingStates, modelStates, currentModelState, currentView, recatalogModalVisible, exportDDLModalReference, currentModel, offset, historyAndVersionDrawerVisible, modelerId, startFlowModalVisible, expandTree } = this.state;
const content = (
<ModelTable
......@@ -520,21 +500,11 @@ class Model extends React.Component {
</div>
</div>
<WordTemplateModal
visible={wordTemplateModalVisible}
onCancel={this.onWordTemplateCURDModalCancel}
/>
<TemplateCURDDrawer
visible={templateCURDDrawerVisible}
onCancel={this.onTemplateCURDDrawerCancel}
/>
<ConstraintDetailDrawer
visible={constraintDetailDrawerVisible}
onCancel={this.onConstraintDetailDrawerCancel}
/>
<ImportModal
view={currentView}
catalogId={catalogId}
......
import React, { useEffect, useState } from 'react';
import { Input, Drawer, Table, Tooltip } from 'antd';
import { Input, Table, Tooltip } from 'antd';
import { dispatch } from '../../../../model';
const ConstraintDetailDrawer = (props) => {
const { onCancel, visible } = props;
const [loading, setLoading] = useState(false);
const [rules, setRules] = useState([]);
const [keyword, setKeyword] = useState('');
const ConstraintDetail = (props) => {
const [ loading, setLoading ] = useState(false);
const [ rules, setRules ] = useState([]);
const [ keyword, setKeyword ] = useState('');
useEffect(() => {
if (visible) {
setLoading(true);
dispatch({
type: 'datamodel.getAllConstraints',
callback: data => {
setLoading(false);
const _rules = [];
(data||[]).forEach(constraint => {
(constraint.allRules||[]).forEach(rule => {
_rules.push({...constraint, ...{ ruleName: rule.name||'', ruleDesc: rule.desc||'' }});
});
setLoading(true);
dispatch({
type: 'datamodel.getAllConstraints',
callback: data => {
setLoading(false);
const _rules = [];
(data||[]).forEach(constraint => {
(constraint.allRules||[]).forEach(rule => {
_rules.push({...constraint, ...{ ruleName: rule.name||'', ruleDesc: rule.desc||'' }});
});
});
setRules(_rules);
},
error: () => {
setLoading(false);
}
})
}
setRules(_rules);
},
error: () => {
setLoading(false);
}
})
//eslint-disable-next-line react-hooks/exhaustive-deps
}, [visible])
}, [])
const columns = [
{
......@@ -101,18 +98,8 @@ const ConstraintDetailDrawer = (props) => {
}
return (
<Drawer
title='规范详情'
placement="right"
closable={true}
width={1200}
onClose={() => {
onCancel && onCancel();
}}
visible={visible}
>
<div>
<div className='d-flex mb-3' style={{ alignItems: 'center' }}>
<span className='mr-3'>前置依赖搜索:</span>
<Input
placeholder="请输入前置依赖名称"
allowClear
......@@ -129,8 +116,8 @@ const ConstraintDetailDrawer = (props) => {
pagination={false}
sticky
/>
</Drawer>
</div>
);
}
export default ConstraintDetailDrawer;
\ No newline at end of file
export default ConstraintDetail;
\ No newline at end of file
import React, { useState, useEffect } from 'react';
import { Form, Button, Space } from 'antd';
import LocalStorage from 'local-storage';
import TemplateAction from './TemplateAction';
import { dispatchLatest } from '../../../../model';
import { getQueryParam, showMessage } from '../../../../util';
import { Action, TemplateId } from '../../../../util/constant';
import '../../Model/Component/EditModel.less';
const EditTemplate = (props) => {
const [ actionData, setActionData ] = useState({ action: '', templateId: '', });
const [ templateData, setTemplateData ] = useState({});
const [ confirmLoading, setConfirmLoading ] = useState(false);
const { action, templateId } = actionData;
const [form] = Form.useForm();
useEffect(() => {
const _action = getQueryParam(Action, props.location.search);
const _templateId = getQueryParam(TemplateId, props.location.search);
setActionData({ action: _action, templateId: _templateId });
//eslint-disable-next-line react-hooks/exhaustive-deps
}, [])
const save = async (e) => {
try {
const row = await form.validateFields();
const newTemplateData = {...templateData, ...row};
setConfirmLoading(true);
dispatchLatest({
type: 'datamodel.saveTemplate',
payload: {
data: newTemplateData
},
callback: data => {
setConfirmLoading(false);
if (action === 'add') {
showMessage("success", '新增模型成功');
setActionData({ ...actionData, ...{ action: 'detail',templateId: data.id } });
} else {
showMessage("success", '保存模型成功');
setActionData({ ...actionData, ...{ action: 'detail', templateId: data.id||'' } });
}
LocalStorage.set('templateChange', !(LocalStorage.get('templateChange')||false));
},
error: () => {
setConfirmLoading(false);
}
})
} catch (errInfo) {
console.log('Validate Failed:', errInfo);
}
}
const edit = () => {
setActionData({ ...actionData, action: 'edit' });
}
const cancelEdit = () => {
setActionData({ ...actionData, action: 'detail' });
}
const onActionChange = (data) => {
setTemplateData(data);
}
let title = '';
if (action === 'add') {
title = '新增模版';
} else if (action === 'edit') {
title = '模版编辑';
} else if (action === 'detail') {
title = '模版详情';
}
let actionsBtn = null;
if (action==='add') {
actionsBtn = (
<Button
type='primary'
onClick={save}
loading={confirmLoading}
danger
>
保存
</Button>
)
} else if (action === 'detail') {
actionsBtn = (
<Space>
<Button type='primary' onClick={edit} danger >
编辑
</Button>
</Space>
);
} else if (action === 'edit') {
actionsBtn = (
<Space>
<Button
type='primary'
onClick={save}
loading={confirmLoading}
danger
>
保存
</Button>
<Button onClick={cancelEdit} >
取消
</Button>
</Space>
)
}
return (
<div className='edit-model position-relative'>
<div className='edit-header'>
<span style={{ fontSize: 16, fontWeight: 'bold', color: '#fff' }}>{title}</span>
</div>
<div className='edit-container'>
<div className='edit-container-card'>
<TemplateAction onChange={onActionChange} action={action} id={templateId} form={form} />
</div>
</div>
<div className='edit-footer'>
{actionsBtn}
</div>
</div>
);
}
export default EditTemplate;
\ No newline at end of file
import React, { useState, useEffect } from 'react';
import { Spin, Tabs, Popover } from 'antd';
import { QuestionCircleOutlined } from '@ant-design/icons';
import TemplateActionHeader from './TemplateActionHeader';
import ImportActionTable from '../../Model/Component/ImportActionTable';
import { dispatchLatest, dispatch } from '../../../../model';
const { TabPane } = Tabs;
const TemplateAction = (props) => {
const { action, onChange, form, id } = props;
const [ templateData, setTemplateData ] = useState(null);
const [ supportedDatatypes, setSupportedDatatypes ] = useState([]);
const [ tabKey, setTabKey ] = useState('1');
const [ loading, setLoading ] = useState(false);
useEffect(() =>{
//初始化form状态
if (action==='add'||action==='edit') {
form.setFieldsValue({
cnName: '',
name: '',
remark: '',
});
}
if (action === 'add') {
getSupportedDatatypes();
} else if ((action === 'edit'||action === 'detail') && id ) {
getCurrentTemplate();
}
//eslint-disable-next-line react-hooks/exhaustive-deps
}, [action, id ]);
const getCurrentTemplate = () => {
setLoading(true);
dispatchLatest({
type: 'datamodel.getTemplate',
payload: {
params: {
id
}
},
callback: data => {
setTemplateData(data||{});
onChange && onChange(data||{});
getSupportedDatatypes();
if (action === 'edit') {
form.setFieldsValue({
cnName: data.cnName||'',
name: data.name||'',
remark: data.remark||'',
});
}
},
error: () => {
setLoading(false);
}
})
}
const getSupportedDatatypes = () => {
setLoading(true);
dispatch({
type: 'datamodel.getSupportedDatatypes',
callback: data => {
setLoading(false);
setSupportedDatatypes(data||[]);
},
error: () => {
setLoading(false);
}
});
}
//模版不需要对字段进行校验
const onTableChange = (data) => {
let newTemplateData = {...templateData, ...{easyDataModelerDataModelAttributes: data}};
setTemplateData(newTemplateData);
onChange && onChange(newTemplateData);
}
const onTabChange = (key) => {
setTabKey(key);
}
return (
<Spin spinning={loading}>
<Tabs activeKey={tabKey} onChange={onTabChange}>
<TabPane tab='基本信息' key='1'>
<TemplateActionHeader
form={form}
editable={action!=='detail'}
templateData={templateData||{}}
/>
</TabPane>
<TabPane
tab={
<span>
<span>数据表结构</span>
{
(action!=='detail'&&action!=='flow'&&action!=='detail-version') && (
<Popover content='表格可以通过拖拽来排序'>
<QuestionCircleOutlined className='ml-1 pointer' />
</Popover>
)
}
</span>
}
key='2'
>
<ImportActionTable
type='template'
modelerData={templateData||{}}
supportedDatatypes={supportedDatatypes}
onChange={onTableChange}
editable={action!=='detail'}
/>
</TabPane>
</Tabs>
</Spin>
);
};
export default TemplateAction;
\ No newline at end of file
import React from 'react';
import { Form, Input, Row, Col, Descriptions } from 'antd';
const TemplateActionHeader = (props) => {
const { editable, form, templateData } = props;
const formItemLayout = {
labelCol: {
xs: { span: 24 },
sm: { span: 5 },
},
wrapperCol: {
xs: { span: 24 },
sm: { span: 19 },
},
};
return (
editable ? (
<Form
form={form}
{...formItemLayout}
>
<Row gutter={10}>
<Col span={12}>
<Form.Item
label="中文名称"
name="cnName"
labelAlign="left"
rules={[{ required: true, message: '请输入中文名称!' }]}
>
<Input />
</Form.Item>
</Col>
<Col span={12}>
<Form.Item
label="英文名称"
name="name"
labelAlign="left"
rules={[{ required: true, message: '请输入英文名称!' }]}
>
<Input />
</Form.Item>
</Col>
</Row>
<Row gutter={10}>
<Col span={12}>
<Form.Item
label="描述"
name="remark"
labelAlign="left"
rules={[{ required: true, message: '请输入描述!' }]}
>
<Input />
</Form.Item>
</Col>
</Row>
</Form>
) : (
<Descriptions>
<Descriptions.Item label="中文名称">{templateData.cnName||''}</Descriptions.Item>
<Descriptions.Item label="英文名称">{templateData.name||''}</Descriptions.Item>
<Descriptions.Item label="描述">{templateData.remark||''}</Descriptions.Item>
</Descriptions>
)
)
}
export default TemplateActionHeader;
\ No newline at end of file
import React, { useEffect, useState } from 'react';
import { Table, Button, Tooltip, Modal, Divider } from 'antd';
import { Action, TemplateId } from '../../../../util/constant';
import { dispatch } from '../../../../model';
import { showMessage } from '../../../../util';
const TemplateCURD = (props) => {
const [ templates, setTemplates ] = useState([]);
const [ loading, setLoading ] = useState(false);
const [modal, contextHolder] = Modal.useModal();
useEffect(() => {
getTemplates();
window?.addEventListener("storage", (e) => {
if (e.key === 'templateChange') {
getTemplates();
}
});
}, [])
const columns = [
{
title: '序号',
dataIndex: 'key',
editable: false,
render: (text, record, index) => {
return (index+1).toString();
},
width: 60,
},
{
title: '模版名称',
dataIndex: 'name',
width: 180,
ellipsis: true,
render: (text, record, _) => {
return (
<Tooltip title={text||''}>
<a onClick={()=>{detailItem(record);}}>
{text||''}
</a>
</Tooltip>
);
}
},
{
title: '中文名称',
dataIndex: 'cnName',
width: 180,
ellipsis: true,
},
{
title: '模版描述',
dataIndex: 'remark',
ellipsis: true,
},
{
title: '更新时间',
dataIndex: 'modifiedTs',
width: 120,
ellipsis: true,
},
{
title: '操作',
key: 'action',
width: 120,
render: (text,record) => {
return (
<div style={{ display: 'flex', alignItems: 'center' }}>
<Button
type='link'
size='small'
onClick={() => { editItem(record); }}
style={{ padding: 0 }}
>
编辑
</Button>
<div style={{ margin: '0 5px' }}>
<Divider type='vertical' />
</div>
<Button
type='link'
size='small'
onClick={() => { deleteItem(record); }}
style={{ padding: 0 }}
>
删除
</Button>
</div>
)
}
}
];
const getTemplates = () => {
setLoading(true);
dispatch({
type: 'datamodel.getAllTemplates',
callback: data => {
setTemplates(data||[]);
setLoading(false);
},
error: () => {
setLoading(false);
}
})
}
const editItem = (record) => {
window.open(`/data-govern/model-template-action?${Action}=edit&${TemplateId}=${record.id}`);
}
const detailItem = (record) => {
window.open(`/data-govern/model-template-action?${Action}=detail&${TemplateId}=${record.id}`);
}
const deleteItem = (record) => {
modal.confirm({
title: '提示!',
content: '您确定要删除该模版吗?',
onOk: () => {
dispatch({
type: 'datamodel.deleteTemplate',
payload: {
params: {
id: record.id
}
},
callback: () => {
showMessage('success', '模版删除成功');
getTemplates();
}
})
}
});
}
const onAddClick = () => {
window.open(`/data-govern/model-template-action?${Action}=add`);
}
return (
<div>
<div className='d-flex mb-3' style={{ alignItems: 'center' }}>
<Button
onClick={onAddClick}
>
新建
</Button>
</div>
<Table
loading={loading}
columns={columns}
rowKey={'id'}
dataSource={templates||[]}
pagination={false}
sticky
/>
{ contextHolder }
</div>
);
}
export default TemplateCURD;
\ No newline at end of file
import React, { useState } from 'react';
import { Button, Upload, Modal } from 'antd';
import { Button, Upload, Form, Row, Col } from 'antd';
import { DownloadOutlined, UploadOutlined } from '@ant-design/icons';
import { dispatchLatest } from '../../../../model';
import { showMessage } from '../../../../util';
const WordTemplateModal = (props) => {
const { onCancel, visible } = props;
const WordTemplate = (props) => {
const [ fileList, setFileList ] = useState([]);
const [ confirmLoading, setConfirmLoading ] = useState(false);
const [ form ] = Form.useForm();
const downloadTemplate = () => {
window.open("/api/datamodeler/easyDataModelerExport/word/downloadTemplate");
}
......@@ -32,62 +32,87 @@ const WordTemplateModal = (props) => {
accept:".doc,.docx",
};
const handleOk = () => {
const normFile = (e) => {
return fileList;
};
const handleOk = async() => {
try {
const row = await form.validateFields();
if ((fileList||[]).length === 0) {
showMessage('info', '请先选择模版上传');
return;
}
setConfirmLoading(true);
dispatchLatest({
type: 'datamodel.uploadWordTemplate',
payload: { fileList: row.upload },
callback: data => {
setConfirmLoading(false);
reset();
},
error: () => {
setConfirmLoading(false);
}
})
setConfirmLoading(true);
dispatchLatest({
type: 'datamodel.uploadWordTemplate',
payload: { fileList },
callback: data => {
setConfirmLoading(false);
reset();
onCancel && onCancel();
},
error: () => {
setConfirmLoading(false);
}
})
} catch (errInfo) {
console.log('Validate Failed:', errInfo);
}
}
const reset = () => {
setConfirmLoading(false);
form.resetFields();
setFileList([]);
}
const layout = {
labelCol: { span: 8 },
wrapperCol: { span: 16 },
};
const tailLayout = {
wrapperCol: { offset: 8, span: 16 },
};
return (
<Modal
forceRender
visible={visible}
title='Word模版配置'
width={520}
confirmLoading={confirmLoading}
onCancel={() => {
reset();
onCancel && onCancel();
}}
onOk={handleOk}
>
<div>
<span className='mr-3'>最新模版下载:</span>
<Button icon={<DownloadOutlined />} onClick={ downloadTemplate }>
下载
<Form form={form} {...layout} onFinish={handleOk}>
<Form.Item
label='最新模版上传'
required={true}
>
<Row>
<Col>
<Form.Item
name='upload'
valuePropName="fileList"
getValueFromEvent={normFile}
noStyle
rules={[
{
required: true,
message: '请选择文件上传',
},
]}
>
<Upload {...uploadProps}>
<Button icon={<UploadOutlined />}>
选择文件上传
</Button>
</Upload>
</Form.Item>
</Col>
<Col>
<Button className='ml-3' icon={<DownloadOutlined />} onClick={ downloadTemplate }>
模版下载
</Button>
</Col>
</Row>
</Form.Item>
<Form.Item {...tailLayout}>
<Button type="primary" htmlType="submit" loading={confirmLoading}>
确定
</Button>
</div>
<div className='mt-3'>
<span className='mr-3'>最新模版上传:</span>
<Upload {...uploadProps}>
<Button icon={<UploadOutlined />}>
选择文件上传
</Button>
</Upload>
</div>
</Modal>
</Form.Item>
</Form>
)
}
export default WordTemplateModal;
\ No newline at end of file
export default WordTemplate;
\ No newline at end of file
import React, { useState } from 'react';
import { Tabs } from 'antd';
import WordTemplate from './Component/WordTemplate';
import TemplateCURD from './Component/TemplateCURD';
import ConstraintDetail from './Component/ConstraintDetail';
import './index.less';
const { TabPane } = Tabs;
const ModelConfig = () => {
const [ tabKey, setTabKey ] = useState('1');
const onTabChange = (key) => {
setTabKey(key);
}
return (
<div className='model-config'>
<Tabs activeKey={tabKey} onChange={onTabChange}>
<TabPane tab='Word模版配置' key='1'>
<WordTemplate />
</TabPane>
<TabPane tab='数据表类型配置' key='2'>
<TemplateCURD />
</TabPane>
<TabPane tab='规范配置' key='3'>
<ConstraintDetail />
</TabPane>
</Tabs>
</div>
)
}
export default ModelConfig;
\ No newline at end of file
.model-config {
height: calc(100vh - 30px);
padding: 20px;
background: #fff;
overflow: auto;
}
\ No newline at end of file
......@@ -8,6 +8,7 @@ import { ManageLayout } from "../../layout";
import DatasourceManage from './DatasourceManage';
import Map from './Map';
import Model from './Model';
import ModelConfig from './ModelConfig';
import AssetManage from './AssetManage';
import AssetBrowse from './AssetBrowse';
import AssetRecycle from './AssetRecycle';
......@@ -27,6 +28,7 @@ class Manage extends Component {
<Switch>
<Route path={`${match.path}/datasource-manage`} component={DatasourceManage} />
<Route path={`${match.path}/data-model`} component={Model} />
<Route path={`${match.path}/model-config`} component={ModelConfig} />
<Route path={`${match.path}/asset-map`} component={Map} />
<Route path={`${match.path}/asset-manage`} component={AssetManage} />
<Route path={`${match.path}/asset-browse`} component={AssetBrowse} />
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment