diff --git a/.umirc.ts b/.umirc.ts index 1611a42..61408d7 100644 --- a/.umirc.ts +++ b/.umirc.ts @@ -7,30 +7,31 @@ export default defineConfig({ initialState: {}, request: {}, layout: { - title: '@umijs/max', + title: 'dbml-editor', + locale: false, }, routes: [ { path: '/', - redirect: '/home', + component: './Home', }, { - name: '首页', - path: '/home', - component: './Home', + name: 'Source', + path: 'https://github.com/alswl/dbml-editor', + external: true, }, { - name: '权限演示', - path: '/access', - component: './Access', + name: 'How to use DBML', + path: 'https://dbml.dbdiagram.io/home', + external: true, }, { - name: ' CRUD 示例', - path: '/table', - component: './Table', + name: '@alswl', + path: 'https://twitter.com/alswl', + external: true, }, + ], npmClient: 'pnpm', - title: 'dbml-editor by @alswl', esbuildMinifyIIFE: true, }); diff --git a/src/app.ts b/src/app.ts index bb2b25f..3512657 100644 --- a/src/app.ts +++ b/src/app.ts @@ -3,14 +3,17 @@ // 全局初始化数据配置,用于 Layout 用户信息和权限初始化 // 更多信息见文档:https://umijs.org/docs/api/runtime-config#getinitialstate export async function getInitialState(): Promise<{ name: string }> { - return { name: '@umijs/max' }; + return { name: '' }; } export const layout = () => { return { - logo: 'https://img.alicdn.com/tfs/TB1YHEpwUT1gK0jSZFhXXaAtVXa-28-27.svg', + logo: false, menu: { locale: false, }, + logout: false, + rightRender: false, + layout: 'top', }; }; diff --git a/src/components/Guide/Guide.less b/src/components/Guide/Guide.less deleted file mode 100644 index 8b116f5..0000000 --- a/src/components/Guide/Guide.less +++ /dev/null @@ -1,4 +0,0 @@ -.title { - margin: 0 auto; - font-weight: 200; -} diff --git a/src/components/Guide/Guide.tsx b/src/components/Guide/Guide.tsx deleted file mode 100644 index b86201b..0000000 --- a/src/components/Guide/Guide.tsx +++ /dev/null @@ -1,23 +0,0 @@ -import { Layout, Row, Typography } from 'antd'; -import React from 'react'; -import styles from './Guide.less'; - -interface Props { - name: string; -} - -// 脚手架示例组件 -const Guide: React.FC = (props) => { - const { name } = props; - return ( - - - - 欢迎使用 {name} ! - - - - ); -}; - -export default Guide; diff --git a/src/components/Guide/index.ts b/src/components/Guide/index.ts deleted file mode 100644 index 297390c..0000000 --- a/src/components/Guide/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -import Guide from './Guide'; -export default Guide; diff --git a/src/layouts/index.less b/src/layouts/index.less deleted file mode 100644 index 2e1d3f8..0000000 --- a/src/layouts/index.less +++ /dev/null @@ -1,10 +0,0 @@ -.navs { - ul { - padding: 0; - list-style: none; - display: flex; - } - li { - margin-right: 1em; - } -} diff --git a/src/layouts/index.tsx b/src/layouts/index.tsx deleted file mode 100644 index c2e1aa1..0000000 --- a/src/layouts/index.tsx +++ /dev/null @@ -1,22 +0,0 @@ -import { Link, Outlet } from 'umi'; -import styles from './index.less'; - -export default function Layout() { - return ( -
- - - -
- ); -} diff --git a/src/pages/Access/index.tsx b/src/pages/Access/index.tsx deleted file mode 100644 index 248b47f..0000000 --- a/src/pages/Access/index.tsx +++ /dev/null @@ -1,21 +0,0 @@ -import { PageContainer } from '@ant-design/pro-components'; -import { Access, useAccess } from '@umijs/max'; -import { Button } from 'antd'; - -const AccessPage: React.FC = () => { - const access = useAccess(); - return ( - - - - - - ); -}; - -export default AccessPage; diff --git a/src/pages/Home/index.less b/src/pages/Home/index.less index 06fdb67..4ee45a6 100644 --- a/src/pages/Home/index.less +++ b/src/pages/Home/index.less @@ -1,3 +1,32 @@ -.container { - padding-top: 80px; +.react-shape-app { + display: flex; + width: 100%; + padding: 0; + font-family: sans-serif; + + .app-content { + flex: 1; + height: 800px; + margin-right: 8px; + margin-left: 8px; + border-radius: 5px; + box-shadow: 0 12px 5px -10px rgb(0 0 0 / 10%), 0 0 4px 0 rgb(0 0 0 / 10%); + } + + .custom-react-node { + display: flex; + align-items: center; + justify-content: center; + width: 100%; + height: 100%; + background-color: #fff; + border: 1px solid #8f8f8f; + border-radius: 6px; + } + + .custom-react-node span { + display: inline-block; + width: 100%; + height: 100%; + } } diff --git a/src/pages/Home/index.tsx b/src/pages/Home/index.tsx index 809f922..11f43b2 100644 --- a/src/pages/Home/index.tsx +++ b/src/pages/Home/index.tsx @@ -1,18 +1,154 @@ -import Guide from '@/components/Guide'; -import { trim } from '@/utils/format'; +import { DagreLayout, GridLayout } from '@antv/layout'; +import { Graph, Model } from '@antv/x6'; +import { Parser } from '@dbml/core'; +import { Col, Row, theme } from 'antd'; +import { debounce } from 'lodash-es'; +import { useEffect, useRef, useState } from 'react'; +import MonacoEditor from 'react-monaco-editor'; + +import parseDatabaseToER from '@/parser/parser'; +import { Snapline } from '@antv/x6-plugin-snapline'; +import './index.less'; import { PageContainer } from '@ant-design/pro-components'; import { useModel } from '@umijs/max'; -import styles from './index.less'; -const HomePage: React.FC = () => { +export default () => { + // constructor + const { + token: { colorBgContainer, borderRadiusLG }, + } = theme.useToken(); + const initCode = `Table users { + id integer + username varchar + role varchar + created_at timestamp + + Note: 'User Table' +} + +Table posts { + id integer [primary key] + title varchar + body text [note: 'Content of the post'] + user_id integer + status post_status + created_at timestamp +} + +Enum post_status { + draft + published + private [note: 'visible via URL only'] +} + +Ref: posts.user_id > users.id // many-to-one +`; + const [code, setCode] = useState(initCode); + const [models, setModels] = useState({}); + const containerRef = useRef(null); + const parser = new Parser(); + new GridLayout({ + type: 'grid', + width: 600, + height: 400, + rows: 6, + cols: 4, + }); + const dagreLayout = new DagreLayout({ + type: 'dagre', + rankdir: 'LR', + align: 'UL', + ranksep: 80, + nodesep: 60, + controlPoints: true, + }); + const layout = dagreLayout; + + useEffect(() => { + if (containerRef.current) { + const graph = new Graph({ + container: containerRef.current, + connecting: { + anchor: { + name: 'midSide', + args: { + direction: 'H', + }, + }, + allowBlank: false, + allowEdge: false, + allowNode: false, + }, + background: { + color: '#F2F7FA', + }, + interacting: { + nodeMovable: true, + edgeMovable: false, + edgeLabelMovable: false, + arrowheadMovable: false, + vertexMovable: false, + vertexAddable: false, + vertexDeletable: false, + }, + panning: true, + mousewheel: false, + }); + graph.use( + new Snapline({ + enabled: true, + }), + ); + + graph.fromJSON(models); + graph.centerContent(); + } + }, [models]); + + // editorDidMount + const editorDidMount = (editor: any, monaco: any) => { + const database = parser.parse(code, 'dbmlv2'); + let models = parseDatabaseToER(database); + setModels(layout.layout(models)); + }; + + // onchange + const onChange = (newValue: any, e: any) => { + const database = parser.parse(newValue, 'dbmlv2'); + console.log(database); + let models = parseDatabaseToER(database); + console.log(models); + setModels(layout.layout(models)); + }; + const debouncedOnChange = debounce(onChange, 500); + const { name } = useModel('global'); + return ( - -
- -
+ + + + + + +
+
+
+ + ); }; - -export default HomePage; diff --git a/src/pages/Table/components/CreateForm.tsx b/src/pages/Table/components/CreateForm.tsx deleted file mode 100644 index e5233db..0000000 --- a/src/pages/Table/components/CreateForm.tsx +++ /dev/null @@ -1,26 +0,0 @@ -import { Modal } from 'antd'; -import React, { PropsWithChildren } from 'react'; - -interface CreateFormProps { - modalVisible: boolean; - onCancel: () => void; -} - -const CreateForm: React.FC> = (props) => { - const { modalVisible, onCancel } = props; - - return ( - onCancel()} - footer={null} - > - {props.children} - - ); -}; - -export default CreateForm; diff --git a/src/pages/Table/components/UpdateForm.tsx b/src/pages/Table/components/UpdateForm.tsx deleted file mode 100644 index 32fecde..0000000 --- a/src/pages/Table/components/UpdateForm.tsx +++ /dev/null @@ -1,138 +0,0 @@ -import { - ProFormDateTimePicker, - ProFormRadio, - ProFormSelect, - ProFormText, - ProFormTextArea, - StepsForm, -} from '@ant-design/pro-components'; -import { Modal } from 'antd'; -import React from 'react'; - -export interface FormValueType extends Partial { - target?: string; - template?: string; - type?: string; - time?: string; - frequency?: string; -} - -export interface UpdateFormProps { - onCancel: (flag?: boolean, formVals?: FormValueType) => void; - onSubmit: (values: FormValueType) => Promise; - updateModalVisible: boolean; - values: Partial; -} - -const UpdateForm: React.FC = (props) => ( - { - return ( - props.onCancel()} - > - {dom} - - ); - }} - onFinish={props.onSubmit} - > - - - - - - - - - - - - - - -); - -export default UpdateForm; diff --git a/src/pages/Table/index.tsx b/src/pages/Table/index.tsx deleted file mode 100644 index ac0efea..0000000 --- a/src/pages/Table/index.tsx +++ /dev/null @@ -1,270 +0,0 @@ -import services from '@/services/demo'; -import { - ActionType, - FooterToolbar, - PageContainer, - ProDescriptions, - ProDescriptionsItemProps, - ProTable, -} from '@ant-design/pro-components'; -import { Button, Divider, Drawer, message } from 'antd'; -import React, { useRef, useState } from 'react'; -import CreateForm from './components/CreateForm'; -import UpdateForm, { FormValueType } from './components/UpdateForm'; - -const { addUser, queryUserList, deleteUser, modifyUser } = - services.UserController; - -/** - * 添加节点 - * @param fields - */ -const handleAdd = async (fields: API.UserInfo) => { - const hide = message.loading('正在添加'); - try { - await addUser({ ...fields }); - hide(); - message.success('添加成功'); - return true; - } catch (error) { - hide(); - message.error('添加失败请重试!'); - return false; - } -}; - -/** - * 更新节点 - * @param fields - */ -const handleUpdate = async (fields: FormValueType) => { - const hide = message.loading('正在配置'); - try { - await modifyUser( - { - userId: fields.id || '', - }, - { - name: fields.name || '', - nickName: fields.nickName || '', - email: fields.email || '', - }, - ); - hide(); - - message.success('配置成功'); - return true; - } catch (error) { - hide(); - message.error('配置失败请重试!'); - return false; - } -}; - -/** - * 删除节点 - * @param selectedRows - */ -const handleRemove = async (selectedRows: API.UserInfo[]) => { - const hide = message.loading('正在删除'); - if (!selectedRows) return true; - try { - await deleteUser({ - userId: selectedRows.find((row) => row.id)?.id || '', - }); - hide(); - message.success('删除成功,即将刷新'); - return true; - } catch (error) { - hide(); - message.error('删除失败,请重试'); - return false; - } -}; - -const TableList: React.FC = () => { - const [createModalVisible, handleModalVisible] = useState(false); - const [updateModalVisible, handleUpdateModalVisible] = - useState(false); - const [stepFormValues, setStepFormValues] = useState({}); - const actionRef = useRef(); - const [row, setRow] = useState(); - const [selectedRowsState, setSelectedRows] = useState([]); - const columns: ProDescriptionsItemProps[] = [ - { - title: '名称', - dataIndex: 'name', - tip: '名称是唯一的 key', - formItemProps: { - rules: [ - { - required: true, - message: '名称为必填项', - }, - ], - }, - }, - { - title: '昵称', - dataIndex: 'nickName', - valueType: 'text', - }, - { - title: '性别', - dataIndex: 'gender', - hideInForm: true, - valueEnum: { - 0: { text: '男', status: 'MALE' }, - 1: { text: '女', status: 'FEMALE' }, - }, - }, - { - title: '操作', - dataIndex: 'option', - valueType: 'option', - render: (_, record) => ( - <> - { - handleUpdateModalVisible(true); - setStepFormValues(record); - }} - > - 配置 - - - 订阅警报 - - ), - }, - ]; - - return ( - - - headerTitle="查询表格" - actionRef={actionRef} - rowKey="id" - search={{ - labelWidth: 120, - }} - toolBarRender={() => [ - , - ]} - request={async (params, sorter, filter) => { - const { data, success } = await queryUserList({ - ...params, - // FIXME: remove @ts-ignore - // @ts-ignore - sorter, - filter, - }); - return { - data: data?.list || [], - success, - }; - }} - columns={columns} - rowSelection={{ - onChange: (_, selectedRows) => setSelectedRows(selectedRows), - }} - /> - {selectedRowsState?.length > 0 && ( - - 已选择{' '} - {selectedRowsState.length}{' '} - 项   -
- } - > - - - - )} - handleModalVisible(false)} - modalVisible={createModalVisible} - > - - onSubmit={async (value) => { - const success = await handleAdd(value); - if (success) { - handleModalVisible(false); - if (actionRef.current) { - actionRef.current.reload(); - } - } - }} - rowKey="id" - type="form" - columns={columns} - /> - - {stepFormValues && Object.keys(stepFormValues).length ? ( - { - const success = await handleUpdate(value); - if (success) { - handleUpdateModalVisible(false); - setStepFormValues({}); - if (actionRef.current) { - actionRef.current.reload(); - } - } - }} - onCancel={() => { - handleUpdateModalVisible(false); - setStepFormValues({}); - }} - updateModalVisible={updateModalVisible} - values={stepFormValues} - /> - ) : null} - - { - setRow(undefined); - }} - closable={false} - > - {row?.name && ( - - column={2} - title={row?.name} - request={async () => ({ - data: row || {}, - })} - params={{ - id: row?.name, - }} - columns={columns} - /> - )} - -
- ); -}; - -export default TableList; diff --git a/src/pages/index.less b/src/pages/index.less deleted file mode 100644 index 4ee45a6..0000000 --- a/src/pages/index.less +++ /dev/null @@ -1,32 +0,0 @@ -.react-shape-app { - display: flex; - width: 100%; - padding: 0; - font-family: sans-serif; - - .app-content { - flex: 1; - height: 800px; - margin-right: 8px; - margin-left: 8px; - border-radius: 5px; - box-shadow: 0 12px 5px -10px rgb(0 0 0 / 10%), 0 0 4px 0 rgb(0 0 0 / 10%); - } - - .custom-react-node { - display: flex; - align-items: center; - justify-content: center; - width: 100%; - height: 100%; - background-color: #fff; - border: 1px solid #8f8f8f; - border-radius: 6px; - } - - .custom-react-node span { - display: inline-block; - width: 100%; - height: 100%; - } -} diff --git a/src/pages/index.tsx b/src/pages/index.tsx deleted file mode 100644 index 69d5587..0000000 --- a/src/pages/index.tsx +++ /dev/null @@ -1,148 +0,0 @@ -import { DagreLayout, GridLayout } from '@antv/layout'; -import { Graph, Model } from '@antv/x6'; -import { Parser } from '@dbml/core'; -import { Col, Row, theme } from 'antd'; -import { debounce } from 'lodash-es'; -import { useEffect, useRef, useState } from 'react'; -import MonacoEditor from 'react-monaco-editor'; - -import parseDatabaseToER from '@/parser/parser'; -import { Snapline } from '@antv/x6-plugin-snapline'; -import './index.less'; - -export default () => { - // constructor - const { - token: { colorBgContainer, borderRadiusLG }, - } = theme.useToken(); - const initCode = `Table users { - id integer - username varchar - role varchar - created_at timestamp - - Note: 'User Table' -} - -Table posts { - id integer [primary key] - title varchar - body text [note: 'Content of the post'] - user_id integer - status post_status - created_at timestamp -} - -Enum post_status { - draft - published - private [note: 'visible via URL only'] -} - -Ref: posts.user_id > users.id // many-to-one -`; - const [code, setCode] = useState(initCode); - const [models, setModels] = useState({}); - const containerRef = useRef(null); - const parser = new Parser(); - new GridLayout({ - type: 'grid', - width: 600, - height: 400, - rows: 6, - cols: 4, - }); - const dagreLayout = new DagreLayout({ - type: 'dagre', - rankdir: 'LR', - align: 'UL', - ranksep: 80, - nodesep: 60, - controlPoints: true, - }); - const layout = dagreLayout; - - useEffect(() => { - if (containerRef.current) { - const graph = new Graph({ - container: containerRef.current, - connecting: { - anchor: { - name: 'midSide', - args: { - direction: 'H', - }, - }, - allowBlank: false, - allowEdge: false, - allowNode: false, - }, - background: { - color: '#F2F7FA', - }, - interacting: { - nodeMovable: true, - edgeMovable: false, - edgeLabelMovable: false, - arrowheadMovable: false, - vertexMovable: false, - vertexAddable: false, - vertexDeletable: false, - }, - panning: true, - mousewheel: false, - }); - graph.use( - new Snapline({ - enabled: true, - }), - ); - - graph.fromJSON(models); - graph.centerContent(); - } - }, [models]); - - // editorDidMount - const editorDidMount = (editor: any, monaco: any) => { - const database = parser.parse(code, 'dbmlv2'); - let models = parseDatabaseToER(database); - setModels(layout.layout(models)); - }; - - // onchange - const onChange = (newValue: any, e: any) => { - const database = parser.parse(newValue, 'dbmlv2'); - console.log(database); - let models = parseDatabaseToER(database); - console.log(models); - setModels(layout.layout(models)); - }; - const debouncedOnChange = debounce(onChange, 500); - - return ( - - - - - -
-
-
- - - ); -};