diff --git a/examples/graphy/src/app.tsx b/examples/graphy/src/app.tsx index ff2937f3a..b5d77ec31 100644 --- a/examples/graphy/src/app.tsx +++ b/examples/graphy/src/app.tsx @@ -1,6 +1,46 @@ -import React from 'react'; import ReactDOM from 'react-dom/client'; -import App from './pages'; + +import React, { Suspense, useEffect } from 'react'; +import { Routes, HashRouter, Route } from 'react-router-dom'; + +import { ConfigProvider } from 'antd'; + +import { IntlProvider } from 'react-intl'; +import { ROUTES, locales } from './index'; +import { Layout } from '@graphscope/studio-components'; +import { SIDE_MENU } from './pages/const'; + +interface IPagesProps {} + +const App: React.FunctionComponent = props => { + const locale = 'en-US'; + const messages = locales[locale]; + + return ( + + + + + }> + {ROUTES} + + + + + + ); +}; const root = ReactDOM.createRoot(document.getElementById('root') as HTMLElement); root.render(); diff --git a/examples/graphy/src/index.tsx b/examples/graphy/src/index.tsx index 19eb27c10..4357a3730 100644 --- a/examples/graphy/src/index.tsx +++ b/examples/graphy/src/index.tsx @@ -1,2 +1,5 @@ import GraphyApp from './pages'; +export { default as locales } from './locales'; +export { default as ROUTES } from './pages'; +export { SIDE_MENU } from './pages/const'; export default GraphyApp; diff --git a/examples/graphy/src/locales/en-US.ts b/examples/graphy/src/locales/en-US.ts index f97c8ebe6..afffc5d55 100644 --- a/examples/graphy/src/locales/en-US.ts +++ b/examples/graphy/src/locales/en-US.ts @@ -3,4 +3,5 @@ export default { Dataset: 'Dataset', Explore: 'Explore', Apps: 'Apps', + Graphy: 'Graphy', }; diff --git a/examples/graphy/src/locales/zh-CN.ts b/examples/graphy/src/locales/zh-CN.ts index a280355cf..57dc4bafb 100644 --- a/examples/graphy/src/locales/zh-CN.ts +++ b/examples/graphy/src/locales/zh-CN.ts @@ -3,4 +3,5 @@ export default { Dataset: '数据集', Explore: '图查询', Apps: '应用商城', + Graphy: 'Graphy', }; diff --git a/examples/graphy/src/pages/const.tsx b/examples/graphy/src/pages/const.tsx index ccfb47439..220c442ee 100644 --- a/examples/graphy/src/pages/const.tsx +++ b/examples/graphy/src/pages/const.tsx @@ -1,19 +1,14 @@ import React, { useState } from 'react'; import { FormattedMessage } from 'react-intl'; -import { SettingFilled, DatabaseOutlined, DeploymentUnitOutlined, AppstoreOutlined } from '@ant-design/icons'; +import { SettingFilled, FilePdfOutlined, DeploymentUnitOutlined, AppstoreOutlined } from '@ant-design/icons'; import { MenuProps } from 'antd'; export const SIDE_MENU: MenuProps['items'] = [ { - label: , + label: , key: '/dataset', - icon: , - }, - { - label: , - key: '/explore', - icon: , + icon: , }, ]; diff --git a/examples/graphy/src/pages/dataset/service.ts b/examples/graphy/src/pages/dataset/service.ts index 6b393d53a..f9b606707 100644 --- a/examples/graphy/src/pages/dataset/service.ts +++ b/examples/graphy/src/pages/dataset/service.ts @@ -1,9 +1,9 @@ -const baseURL = 'http://localhost:9999/api'; import { Utils } from '@graphscope/studio-components'; import { KuzuDriver } from '../../kuzu-driver'; import JSZip from 'jszip'; -const url = new URL(baseURL); -// url.search = new URLSearchParams(params).toString(); + +const baseURL = Utils.storage.get('graphy_endpoint') || 'http://localhost:9999/api'; + export const queryDataset = async () => { return fetch(baseURL + '/dataset', { method: 'GET', diff --git a/examples/graphy/src/pages/index.tsx b/examples/graphy/src/pages/index.tsx index 52c933f76..f9afe6896 100644 --- a/examples/graphy/src/pages/index.tsx +++ b/examples/graphy/src/pages/index.tsx @@ -1,11 +1,5 @@ -import React, { Suspense, useEffect } from 'react'; -import { BrowserRouter, Routes, Route, Navigate, Outlet, HashRouter } from 'react-router-dom'; -import { Layout } from '@graphscope/studio-components'; -import { SIDE_MENU } from './const'; -import { ConfigProvider } from 'antd'; -import locales from '../locales'; -import { IntlProvider } from 'react-intl'; -import PaperReading from '../pages/explore/paper-reading'; +import React, { Suspense } from 'react'; +import { Route, Navigate } from 'react-router-dom'; /** 注册 服务 */ import { registerServices } from '../pages/explore/paper-reading/components/registerServices'; @@ -56,69 +50,24 @@ const routes = [ path: '/dataset/cluster', component: React.lazy(() => import('./dataset/cluster')), }, - { path: '/explore', component: React.lazy(() => import('./explore')) }, ]; -const Apps = () => { - const [isReady, setIsReady] = React.useState(false); - useEffect(() => { - reload().then(res => { - setIsReady(true); - }); - }, []); +const ROUTES = routes.map(({ path, redirect, component: Component }, index) => { + if (redirect) { + return } />; + } return ( - <> - - {isReady && } - + }> + {/** @ts-ignore */} + + + } + /> ); -}; -const Pages: React.FunctionComponent = props => { - const locale = 'en-US'; - const messages = locales[locale]; - const routeComponents = routes.map(({ path, redirect, component: Component }, index) => { - if (redirect) { - return } />; - } - return ( - }> - {/** @ts-ignore */} - - - } - /> - ); - }); +}); - return ( - - - - - }> - {routeComponents} - - } /> - - - - - ); -}; - -export default Pages; +export default ROUTES; diff --git a/packages/studio-website/package.json b/packages/studio-website/package.json index 5eab956dc..01c3b437b 100644 --- a/packages/studio-website/package.json +++ b/packages/studio-website/package.json @@ -34,6 +34,7 @@ "@graphscope/studio-query": "workspace:*", "@graphscope/studio-server": "workspace:*", "@graphscope/use-zustand": "workspace:*", + "@graphscope/graphy-website": "workspace:*", "@uiw/react-codemirror": "^4.21.21", "antd": "^5.17.0", "js-yaml": "^4.1.0", diff --git a/packages/studio-website/src/app.tsx b/packages/studio-website/src/app.tsx index aa04921fa..003b174fa 100644 --- a/packages/studio-website/src/app.tsx +++ b/packages/studio-website/src/app.tsx @@ -1,6 +1,22 @@ import React from 'react'; import ReactDOM from 'react-dom/client'; import Pages from './pages'; +import { installSlot, unInstallSlot } from './slots'; +import { SIDE_MENU } from './layouts/const'; +import { ROUTES } from './pages'; +import { Utils } from '@graphscope/studio-components'; +import { ROUTES as GRAPHY_ROUTES, SIDE_MENU as GRAPHY_SIDE_MENU } from '@graphscope/graphy-website'; + +if (Utils.storage.get('PORTAL_PLUGIN_GRAPHY')) { + installSlot('SIDE_MEU', 'graphy', GRAPHY_SIDE_MENU); + installSlot('ROUTES', 'graphy', GRAPHY_ROUTES); +} else { + unInstallSlot('SIDE_MEU', 'graphy'); + unInstallSlot('ROUTES', 'graphy'); +} + +installSlot('SIDE_MEU', 'studio', SIDE_MENU); +installSlot('ROUTES', 'studio', ROUTES); const root = ReactDOM.createRoot(document.getElementById('root') as HTMLElement); root.render(); diff --git a/packages/studio-website/src/layouts/index.tsx b/packages/studio-website/src/layouts/index.tsx index e53c9dc4f..a321979be 100644 --- a/packages/studio-website/src/layouts/index.tsx +++ b/packages/studio-website/src/layouts/index.tsx @@ -6,7 +6,7 @@ import { DeploymentApiFactory } from '@graphscope/studio-server'; import { SIDE_MENU, SETTING_MENU } from './const'; import { notification } from 'antd'; import { listGraphs } from '../pages/instance/lists/service'; -import { SLOTS } from '../slots'; +import { SLOTS, getSlots } from '../slots'; export default function StudioLayout() { const { store, updateStore } = useContext(); @@ -109,7 +109,9 @@ export default function StudioLayout() { }, []); const { isReady } = state; - const _SIDE = [...(SIDE_MENU || []), ...(SLOTS.SIDE_MEU || [])]; + + const _SIDE = getSlots('SIDE_MEU'); + const { layoutBackground } = useCustomToken(); if (isReady) { return ( diff --git a/packages/studio-website/src/pages/index.tsx b/packages/studio-website/src/pages/index.tsx index 0bfef6289..6aab22268 100644 --- a/packages/studio-website/src/pages/index.tsx +++ b/packages/studio-website/src/pages/index.tsx @@ -4,6 +4,7 @@ import { StudioProvier, GlobalSpin } from '@graphscope/studio-components'; import Layout from '../layouts'; import StoreProvider from '@graphscope/use-zustand'; import { initialStore } from '../layouts/useContext'; +import { getSlots } from '../slots'; import locales from '../locales'; interface IPagesProps { @@ -27,26 +28,28 @@ const routes = [ { path: '/extension/:name', component: React.lazy(() => import('./extension/create-plugins')) }, ]; +export const ROUTES = routes.map(({ path, redirect, component: Component }, index) => { + if (redirect) { + return } />; + } + return ( + }> + {/** @ts-ignore */} + + + } + /> + ); +}); + const Pages: React.FunctionComponent = props => { const { children } = props; - - const routeComponents = routes.map(({ path, redirect, component: Component }, index) => { - if (redirect) { - return } />; - } - return ( - }> - {/** @ts-ignore */} - - - } - /> - ); - }); + const routes = getSlots('ROUTES'); + console.log('routes', routes); return ( @@ -54,7 +57,7 @@ const Pages: React.FunctionComponent = props => { }> - {routeComponents} + {routes} {children} diff --git a/packages/studio-website/src/pages/setting/index.tsx b/packages/studio-website/src/pages/setting/index.tsx index 243227937..7f168c5fb 100644 --- a/packages/studio-website/src/pages/setting/index.tsx +++ b/packages/studio-website/src/pages/setting/index.tsx @@ -9,6 +9,7 @@ import International from './International'; import QuerySetting from './query-setting'; import { FormattedMessage } from 'react-intl'; import Coordinator from './coordinator'; +import GraphyPlugin from './plugins/graphy'; const Setting: React.FunctionComponent = () => { return ( @@ -47,19 +48,30 @@ const Setting: React.FunctionComponent = () => { - - - - } - > - - - - - - + + + + + } + > + + + + + + + + + + } + > + + + diff --git a/packages/studio-website/src/pages/setting/plugins/graphy.tsx b/packages/studio-website/src/pages/setting/plugins/graphy.tsx new file mode 100644 index 000000000..8ebaddf1a --- /dev/null +++ b/packages/studio-website/src/pages/setting/plugins/graphy.tsx @@ -0,0 +1,66 @@ +import * as React from 'react'; +import { Typography, Tooltip, Switch, Input, Flex, Space, Card } from 'antd'; +import { QuestionCircleOutlined } from '@ant-design/icons'; +import { Utils, Illustration } from '@graphscope/studio-components'; +import { ROUTES, SIDE_MENU } from '@graphscope/graphy-website'; +import { installSlot, unInstallSlot } from '../../../slots'; +import { useContext } from '../../../layouts/useContext'; + +interface IGraphyPluginProps {} + +const GraphyPlugin: React.FunctionComponent = props => { + const defaultChecked = Utils.storage.get('PORTAL_PLUGIN_GRAPHY'); + const defaultEndpoint = Utils.storage.get('graphy_endpoint') || 'http://127.0.0.1:9999'; + const onChange = (checked: boolean) => { + Utils.storage.set('PORTAL_PLUGIN_GRAPHY', checked); + if (checked) { + installSlot('SIDE_MEU', 'graphy', SIDE_MENU); + installSlot('ROUTES', 'graphy', ROUTES); + } else { + unInstallSlot('SIDE_MEU', 'graphy'); + unInstallSlot('ROUTES', 'graphy'); + } + window.location.reload(); + }; + const handleChangeUrl = () => { + Utils.storage.get('graphy_endpoint'); + }; + + return ( + + {/* */} + + + + Graphy'ourData + + + + + + + An intuitive tool that transforms unstructured data into graph dataset. + + + Enable: + + + + Endpoint: + + + + + + ); +}; + +export default GraphyPlugin; diff --git a/packages/studio-website/src/slots/index.tsx b/packages/studio-website/src/slots/index.tsx index 8e76d541d..403bc775c 100644 --- a/packages/studio-website/src/slots/index.tsx +++ b/packages/studio-website/src/slots/index.tsx @@ -1,19 +1,53 @@ import React from 'react'; import type { MenuProps } from 'antd'; - +// import { SIDE_MENU } from '../layouts/const'; +// import { ROUTES } from '../pages/index'; export const SLOTS: { /** 侧边栏 */ - SIDE_MEU: MenuProps['items']; - /** 查询模块的头部 */ - QUERY_HEADER: string; - /** 导入模块的配置 */ - IMPORT_CONFIG: React.ReactNode; + SIDE_MEU: { [id: string]: MenuProps['items'] }; + /** 路由 */ + ROUTES: { + [id: string]: any[]; + }; } = { - SIDE_MEU: [], - QUERY_HEADER: '', - IMPORT_CONFIG: <>, + SIDE_MEU: { + studio: [], + }, + ROUTES: { + studio: [], + }, +}; + +export type SlotType = 'SIDE_MEU' | 'ROUTES'; +export const installSlot = (slotType: SlotType, appId: string, slot: any) => { + SLOTS[slotType] = { + ...SLOTS[slotType], + [appId]: slot, + }; + console.log('SLOTS', SLOTS); +}; +export const unInstallSlot = (slotType: SlotType, appId: string) => { + delete SLOTS[slotType][appId]; +}; + +export const getSlots = (slotType: SlotType) => { + const slots = SLOTS[slotType]; + //@ts-ignore + return Object.keys(slots).reduce((acc, appId) => { + //@ts-ignore + return [...acc, ...slots[appId]]; + }, []); +}; +export const registerSideMenuSlot = (appId: string, slot: MenuProps['items']) => { + SLOTS['SIDE_MEU'] = { + ...SLOTS['SIDE_MEU'], + [appId]: slot, + }; }; -export const registerSideMenuSlot = (slot: MenuProps['items']) => { - SLOTS['SIDE_MEU'] = slot; +export const registerRoutesSlot = (appId: string, slot: any) => { + SLOTS['ROUTES'] = { + ...SLOTS['ROUTES'], + [appId]: slot, + }; }; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a3439da50..37174ead9 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -693,6 +693,9 @@ importers: '@fortawesome/react-fontawesome': specifier: latest version: 0.2.2(@fortawesome/fontawesome-svg-core@6.7.1)(react@18.2.0) + '@graphscope/graphy-website': + specifier: workspace:* + version: link:../../examples/graphy '@graphscope/studio-components': specifier: workspace:* version: link:../studio-components