From 53bdf551596a875d318480d9f1ab90c0a5b0c798 Mon Sep 17 00:00:00 2001 From: SungChul Hong Date: Mon, 28 Oct 2024 17:26:17 +0900 Subject: [PATCH] feat: WebUI NEO start page --- react/package.json | 1 + react/pnpm-lock.yaml | 34 ++- react/src/App.tsx | 16 +- react/src/components/ActionItemContent.tsx | 108 ++++++++ react/src/components/BAIBoard.tsx | 40 +-- .../src/components/MainLayout/WebUISider.tsx | 10 +- .../SummaryItemDownloadApp.tsx | 92 ------- .../SummaryItemInvitation.tsx | 169 ------------ .../SummaryPageItems/SummaryItemStartMenu.tsx | 146 ----------- react/src/hooks/useBAISetting.tsx | 4 +- react/src/pages/Page401.tsx | 2 +- react/src/pages/Page404.tsx | 2 +- react/src/pages/StartPage.tsx | 244 ++++++++++++++++++ react/src/pages/SummaryPage.tsx | 85 ------ resources/i18n/de.json | 24 +- resources/i18n/el.json | 24 +- resources/i18n/en.json | 24 +- resources/i18n/es.json | 24 +- resources/i18n/fi.json | 24 +- resources/i18n/fr.json | 24 +- resources/i18n/id.json | 24 +- resources/i18n/it.json | 24 +- resources/i18n/ja.json | 24 +- resources/i18n/ko.json | 24 +- resources/i18n/mn.json | 24 +- resources/i18n/ms.json | 24 +- resources/i18n/pl.json | 24 +- resources/i18n/pt-BR.json | 24 +- resources/i18n/pt.json | 24 +- resources/i18n/ru.json | 24 +- resources/i18n/th.json | 24 +- resources/i18n/tr.json | 24 +- resources/i18n/vi.json | 24 +- resources/i18n/zh-CN.json | 24 +- resources/i18n/zh-TW.json | 24 +- resources/theme.json | 2 +- src/backend-ai-app.ts | 1 + src/components/backend-ai-error-view.ts | 6 +- .../backend-ai-permission-denied-view.ts | 6 +- src/components/backend-ai-storage-list.ts | 2 +- .../backend-ai-storage-proxy-list.ts | 2 +- src/components/backend-ai-summary-view.ts | 2 +- src/components/backend-ai-webui.ts | 3 +- 43 files changed, 926 insertions(+), 555 deletions(-) create mode 100644 react/src/components/ActionItemContent.tsx delete mode 100644 react/src/components/SummaryPageItems/SummaryItemDownloadApp.tsx delete mode 100644 react/src/components/SummaryPageItems/SummaryItemInvitation.tsx delete mode 100644 react/src/components/SummaryPageItems/SummaryItemStartMenu.tsx create mode 100644 react/src/pages/StartPage.tsx delete mode 100644 react/src/pages/SummaryPage.tsx diff --git a/react/package.json b/react/package.json index 6c5429c1c8..2714898711 100644 --- a/react/package.json +++ b/react/package.json @@ -10,6 +10,7 @@ "@cloudscape-design/board-components": "3.0.60", "@codemirror/language": "^6.10.2", "@melloware/react-logviewer": "^5.2.4", + "@react-hook/resize-observer": "^2.0.2", "@storybook/test": "^8.2.8", "@tanstack/react-query": "^5.51.15", "@testing-library/jest-dom": "^6.4.8", diff --git a/react/pnpm-lock.yaml b/react/pnpm-lock.yaml index 900a3696b7..4f20d0efe5 100644 --- a/react/pnpm-lock.yaml +++ b/react/pnpm-lock.yaml @@ -34,6 +34,9 @@ importers: '@melloware/react-logviewer': specifier: ^5.2.4 version: 5.2.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@react-hook/resize-observer': + specifier: ^2.0.2 + version: 2.0.2(react@18.3.1) '@storybook/test': specifier: ^8.2.8 version: 8.2.8(@types/jest@29.5.12)(jest@27.5.1(ts-node@10.9.2(@types/node@20.14.14)(typescript@5.5.4)))(storybook@8.2.8(@babel/preset-env@7.25.3(@babel/core@7.25.2))) @@ -211,7 +214,7 @@ importers: version: 5.51.15(eslint@8.57.0)(typescript@5.5.4) '@testing-library/user-event': specifier: ^14.5.2 - version: 14.5.2(@testing-library/dom@10.1.0) + version: 14.5.2(@testing-library/dom@9.3.4) '@types/react-copy-to-clipboard': specifier: ^5.0.7 version: 5.0.7 @@ -1974,6 +1977,21 @@ packages: react: '>=16.9.0' react-dom: '>=16.9.0' + '@react-hook/latest@1.0.3': + resolution: {integrity: sha512-dy6duzl+JnAZcDbNTfmaP3xHiKtbXYOaz3G51MGVljh548Y8MWzTr+PHLOfvpypEVW9zwvl+VyKjbWKEVbV1Rg==} + peerDependencies: + react: '>=16.8' + + '@react-hook/passive-layout-effect@1.2.1': + resolution: {integrity: sha512-IwEphTD75liO8g+6taS+4oqz+nnroocNfWVHWz7j+N+ZO2vYrc6PV1q7GQhuahL0IOR7JccFTsFKQ/mb6iZWAg==} + peerDependencies: + react: '>=16.8' + + '@react-hook/resize-observer@2.0.2': + resolution: {integrity: sha512-tzKKzxNpfE5TWmxuv+5Ae3IF58n0FQgQaWJmcbYkjXTRZATXxClnTprQ2uuYygYTpu1pqbBskpwMpj6jpT1djA==} + peerDependencies: + react: '>=18' + '@remix-run/router@1.19.0': resolution: {integrity: sha512-zDICCLKEwbVYTS6TjYaWtHXxkdoUvD/QXvyVZjGCsWz5vyH7aFeONlPffPdW+Y/t6KT0MgXb2Mfjun9YpWN1dA==} engines: {node: '>=14.0.0'} @@ -10951,6 +10969,20 @@ snapshots: react: 18.3.1 react-dom: 18.3.1(react@18.3.1) + '@react-hook/latest@1.0.3(react@18.3.1)': + dependencies: + react: 18.3.1 + + '@react-hook/passive-layout-effect@1.2.1(react@18.3.1)': + dependencies: + react: 18.3.1 + + '@react-hook/resize-observer@2.0.2(react@18.3.1)': + dependencies: + '@react-hook/latest': 1.0.3(react@18.3.1) + '@react-hook/passive-layout-effect': 1.2.1(react@18.3.1) + react: 18.3.1 + '@remix-run/router@1.19.0': {} '@replit/codemirror-lang-csharp@6.2.0(@codemirror/autocomplete@6.18.0(@codemirror/language@6.10.2)(@codemirror/state@6.4.1)(@codemirror/view@6.30.0)(@lezer/common@1.2.1))(@codemirror/language@6.10.2)(@codemirror/state@6.4.1)(@codemirror/view@6.30.0)(@lezer/common@1.2.1)(@lezer/highlight@1.2.0)(@lezer/lr@1.4.2)': diff --git a/react/src/App.tsx b/react/src/App.tsx index 23d67e5335..7ce04dd358 100644 --- a/react/src/App.tsx +++ b/react/src/App.tsx @@ -28,7 +28,7 @@ const ServingPage = React.lazy(() => import('./pages/ServingPage')); const EndpointDetailPage = React.lazy( () => import('./pages/EndpointDetailPage'), ); -// const SummaryPage = React.lazy(() => import('./pages/SummaryPage')); +const StartPage = React.lazy(() => import('./pages/StartPage')); const EnvironmentPage = React.lazy(() => import('./pages/EnvironmentPage')); const MyEnvironmentPage = React.lazy(() => import('./pages/MyEnvironmentPage')); const StorageHostSettingPage = React.lazy( @@ -100,18 +100,23 @@ const router = createBrowserRouter([ ), children: [ { - path: '/', - element: , + path: '/start', + element: ( + + + + ), + handle: { labelKey: 'webui.menu.Start' }, }, { //for electron dev mode path: '/build/electron-app/app/index.html', - element: , + element: , }, { //for electron prod mode path: '/app/index.html', - element: , + element: , }, { path: '/summary', @@ -126,7 +131,6 @@ const router = createBrowserRouter([ style={{ marginBottom: token.paddingContentVerticalLG }} closable /> - {/* */} ); }, diff --git a/react/src/components/ActionItemContent.tsx b/react/src/components/ActionItemContent.tsx new file mode 100644 index 0000000000..ca80efa205 --- /dev/null +++ b/react/src/components/ActionItemContent.tsx @@ -0,0 +1,108 @@ +import Flex from './Flex'; +import useResizeObserver from '@react-hook/resize-observer'; +import { Button, Divider, Typography, theme } from 'antd'; +import { useRef, useState } from 'react'; + +interface StartItemContentProps { + title: string; + description?: string; + icon?: React.ReactNode; + buttonText: string; + onClick?: () => void; + themeColor?: string; + iconBgColor?: string; +} + +const ActionItemContent: React.FC = ({ + title, + description, + icon, + buttonText, + onClick, + themeColor, + iconBgColor, +}) => { + const { token } = theme.useToken(); + const [needScroll, setNeedScroll] = useState(false); + const containerRef = useRef(null); + + useResizeObserver(containerRef, (entry) => { + entry.contentRect.width <= 220 ? setNeedScroll(true) : setNeedScroll(false); + }); + + return ( + + + + {icon} + + + + {title} + + + + {!needScroll && description} + + + + {description && ( + + )} + + + + + + ); +}; + +export default ActionItemContent; diff --git a/react/src/components/BAIBoard.tsx b/react/src/components/BAIBoard.tsx index 4f75ed034d..e0ae218e44 100644 --- a/react/src/components/BAIBoard.tsx +++ b/react/src/components/BAIBoard.tsx @@ -1,7 +1,6 @@ -import Flex from './Flex'; import Board, { BoardProps } from '@cloudscape-design/board-components/board'; import BoardItem from '@cloudscape-design/board-components/board-item'; -import { Skeleton, Typography } from 'antd'; +import { Skeleton } from 'antd'; import { createStyles } from 'antd-style'; import { Suspense } from 'react'; @@ -23,29 +22,31 @@ const useStyles = createStyles(({ css }) => { board: css` ${defaultBoard} `, - disableCustomize: css` - ${defaultBoard} - .bai_board_handle { + disableResize: css` + .bai_board_resizer { display: none !important; } - .bai_board_resizer { + `, + disableMove: css` + .bai_board_handle { display: none !important; } .bai_board_header { - height: var(--token-boardHeaderHeight, 55px) !important; + display: none !important; } `, boardItems: css` & > div:first-child { - border: 1px solid var(--token-colorBorder) !important ; + border: none !important ; border-radius: var(--token-borderRadius) !important ; background-color: var(--token-colorBgContainer) !important ; } & > div:first-child > div:first-child > div:first-child { - border-bottom: 1px solid var(--token-colorBorder) !important; margin-bottom: var(--token-margin); background-color: var(--token-colorBgContainer) !important ; + position: absolute; + z-index: 1; } `, }; @@ -56,19 +57,27 @@ interface BAICustomizableGridProps { onItemsChange: ( event: CustomEvent>, ) => void; - customizable?: boolean; + resizable?: boolean; + movable?: boolean; } const BAIBoard: React.FC = ({ items: parsedItems, - customizable = false, + resizable = false, + movable = false, ...BoardProps }) => { const { styles } = useStyles(); + + const boardStyles = [ + styles.board, + !movable && styles.disableMove, + !resizable && styles.disableResize, + ].join(' '); + return ( { return ( @@ -82,11 +91,6 @@ const BAIBoard: React.FC = ({ resizeHandleAriaLabel: '', resizeHandleAriaDescription: '', }} - header={ - - {item.data.title} - - } > }> {item.data.content} diff --git a/react/src/components/MainLayout/WebUISider.tsx b/react/src/components/MainLayout/WebUISider.tsx index f8e749f10d..a20c668e4a 100644 --- a/react/src/components/MainLayout/WebUISider.tsx +++ b/react/src/components/MainLayout/WebUISider.tsx @@ -24,6 +24,7 @@ import { FileDoneOutlined, HddOutlined, InfoCircleOutlined, + PlayCircleOutlined, SolutionOutlined, ToolOutlined, UserOutlined, @@ -85,6 +86,11 @@ const WebUISider: React.FC = (props) => { const primaryColors = usePrimaryColors(); const generalMenu = filterEmptyItem([ + { + label: {t('webui.menu.Start')}, + icon: , + key: 'start', + }, { label: {t('webui.menu.Summary')}, icon: , @@ -255,7 +261,7 @@ const WebUISider: React.FC = (props) => { height: themeConfig?.logo?.size?.height || 24, cursor: 'pointer', }} - onClick={() => webuiNavigate(themeConfig?.logo?.href || '/summary')} + onClick={() => webuiNavigate(themeConfig?.logo?.href || '/start')} /> } theme={currentSiderTheme} @@ -275,7 +281,7 @@ const WebUISider: React.FC = (props) => { height: themeConfig?.logo.sizeCollapsed?.height ?? 24, cursor: 'pointer', }} - onClick={() => webuiNavigate(themeConfig?.logo?.href || '/summary')} + onClick={() => webuiNavigate(themeConfig?.logo?.href || '/start')} /> } logoTitle={themeConfig?.logo?.logoTitle || siteDescription || 'WebUI'} diff --git a/react/src/components/SummaryPageItems/SummaryItemDownloadApp.tsx b/react/src/components/SummaryPageItems/SummaryItemDownloadApp.tsx deleted file mode 100644 index b2e487d2ce..0000000000 --- a/react/src/components/SummaryPageItems/SummaryItemDownloadApp.tsx +++ /dev/null @@ -1,92 +0,0 @@ -import { useSuspendedBackendaiClient } from '../../hooks'; -import Flex from '../Flex'; -import { Button, Select, Typography, theme } from 'antd'; -import { useState } from 'react'; - -const detectDefaultOS = () => { - if (navigator.userAgent.indexOf('Mac') !== -1) return 'MacOS'; - if (navigator.userAgent.indexOf('Win') !== -1) return 'Windows'; - if (navigator.userAgent.indexOf('Linux') !== -1) return 'Linux'; - return 'MacOS'; -}; - -const SummaryItemDownloadApp: React.FC = () => { - const { token } = theme.useToken(); - const [OS, setOS] = useState(detectDefaultOS()); - - const baiClient = useSuspendedBackendaiClient(); - const url = baiClient._config.appDownloadUrl; - const windowOS = baiClient.supports('use-win-instead-of-win32') - ? 'win' - : 'win32'; - - const appDownloadMap: Record = { - Linux: { - os: 'linux', - architecture: ['arm64', 'x64'], - extension: 'zip', - }, - MacOS: { - os: 'macos', - architecture: ['arm64', 'x64'], - extension: 'dmg', - }, - Windows: { - os: windowOS, - architecture: ['arm64', 'x64'], - extension: 'zip', - }, - }; - const downloadApp = (architecture: string) => { - //@ts-ignore - const pkgVersion = globalThis.packageVersion; - const os = appDownloadMap[OS].os; - const extension = appDownloadMap[OS].extension; - const downloadLink = `${url}/v${pkgVersion}/backend.ai-desktop-${pkgVersion}-${os}-${architecture}.${extension}`; - window.open(downloadLink, '_blank'); - }; - - return ( - -