diff --git a/desktop/frontend/package.json b/desktop/frontend/package.json index b3a8e0afe..9f44213de 100644 --- a/desktop/frontend/package.json +++ b/desktop/frontend/package.json @@ -35,6 +35,7 @@ "next-pwa": "^5.6.0", "qrcode.react": "^3.1.0", "react": "18.2.0", + "react-content-loader": "^6.2.0", "react-dom": "18.2.0", "react-draggable": "^4.4.5", "react-hook-form": "^7.40.0", diff --git a/desktop/frontend/pnpm-lock.yaml b/desktop/frontend/pnpm-lock.yaml index bdabcc5e2..ab7478895 100644 --- a/desktop/frontend/pnpm-lock.yaml +++ b/desktop/frontend/pnpm-lock.yaml @@ -31,6 +31,7 @@ specifiers: postcss: ^8.4.20 qrcode.react: ^3.1.0 react: 18.2.0 + react-content-loader: ^6.2.0 react-dom: 18.2.0 react-draggable: ^4.4.5 react-hook-form: ^7.40.0 @@ -65,6 +66,7 @@ dependencies: next-pwa: 5.6.0_next@13.1.0 qrcode.react: 3.1.0_react@18.2.0 react: 18.2.0 + react-content-loader: registry.npmmirror.com/react-content-loader/6.2.0_react@18.2.0 react-dom: 18.2.0_react@18.2.0 react-draggable: 4.4.5_biqbaboplfbrettd7655fr4n2y react-hook-form: 7.41.0_react@18.2.0 @@ -8252,3 +8254,15 @@ packages: oidc-token-hash: 5.0.1 dev: false optional: true + + registry.npmmirror.com/react-content-loader/6.2.0_react@18.2.0: + resolution: {integrity: sha512-r1dI6S+uHNLW68qraLE2njJYOuy6976PpCExuCZUcABWbfnF3FMcmuESRI8L4Bj45wnZ7n8g71hkPLzbma7/Cw==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/react-content-loader/-/react-content-loader-6.2.0.tgz} + id: registry.npmmirror.com/react-content-loader/6.2.0 + name: react-content-loader + version: 6.2.0 + engines: {node: '>=10'} + peerDependencies: + react: '>=16.0.0' + dependencies: + react: 18.2.0 + dev: false diff --git a/desktop/frontend/public/iconfont/iconfont.js b/desktop/frontend/public/iconfont/iconfont.js new file mode 100644 index 000000000..e39d5a26b --- /dev/null +++ b/desktop/frontend/public/iconfont/iconfont.js @@ -0,0 +1 @@ +window._iconfont_svg_string_3859970 = '', function (e) { var a = (a = document.getElementsByTagName("script"))[a.length - 1], t = a.getAttribute("data-injectcss"), a = a.getAttribute("data-disable-injectsvg"); if (!a) { var o, c, i, l, n, s = function (a, t) { t.parentNode.insertBefore(a, t) }; if (t && !e.__iconfont__svg__cssinject__) { e.__iconfont__svg__cssinject__ = !0; try { document.write("") } catch (a) { console && console.log(a) } } o = function () { var a, t = document.createElement("div"); t.innerHTML = e._iconfont_svg_string_3859970, (t = t.getElementsByTagName("svg")[0]) && (t.setAttribute("aria-hidden", "true"), t.style.position = "absolute", t.style.width = 0, t.style.height = 0, t.style.overflow = "hidden", t = t, (a = document.body).firstChild ? s(t, a.firstChild) : a.appendChild(t)) }, document.addEventListener ? ~["complete", "loaded", "interactive"].indexOf(document.readyState) ? setTimeout(o, 0) : (c = function () { document.removeEventListener("DOMContentLoaded", c, !1), o() }, document.addEventListener("DOMContentLoaded", c, !1)) : document.attachEvent && (i = o, l = e.document, n = !1, m(), l.onreadystatechange = function () { "complete" == l.readyState && (l.onreadystatechange = null, d()) }) } function d () { n || (n = !0, i()) } function m () { try { l.documentElement.doScroll("left") } catch (a) { return void setTimeout(m, 50) } d() } }(window) \ No newline at end of file diff --git a/desktop/frontend/public/images/appstore/appstore_background.png b/desktop/frontend/public/images/appstore/appstore_background.png deleted file mode 100644 index 1594e753c..000000000 Binary files a/desktop/frontend/public/images/appstore/appstore_background.png and /dev/null differ diff --git a/desktop/frontend/public/images/appstore/background.png b/desktop/frontend/public/images/appstore/background.png new file mode 100644 index 000000000..ff5eabf39 Binary files /dev/null and b/desktop/frontend/public/images/appstore/background.png differ diff --git a/desktop/frontend/public/images/appstore/error.svg b/desktop/frontend/public/images/appstore/error.svg new file mode 100644 index 000000000..b591bfae1 --- /dev/null +++ b/desktop/frontend/public/images/appstore/error.svg @@ -0,0 +1,210 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/desktop/frontend/public/images/appstore/image_empty.svg b/desktop/frontend/public/images/appstore/image_empty.svg new file mode 100644 index 000000000..8563cb975 --- /dev/null +++ b/desktop/frontend/public/images/appstore/image_empty.svg @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/desktop/frontend/src/applications/app_store/app_store_common.scss b/desktop/frontend/src/applications/app_store/app_store_common.scss index 0bb3e714e..48d92370f 100644 --- a/desktop/frontend/src/applications/app_store/app_store_common.scss +++ b/desktop/frontend/src/applications/app_store/app_store_common.scss @@ -28,8 +28,9 @@ $background-max-hover: rgba(255, 255, 255, 0.65); } .backgroundWrap { - background-image: url('/images/appstore/appstore_background.png'); + background-image: url('/images/appstore/background.png'); background-size: cover; + background-position: top; } .baseCard { diff --git a/desktop/frontend/src/applications/app_store/app_store_common.ts b/desktop/frontend/src/applications/app_store/app_store_common.ts index 0bfad58a9..cce5cf05e 100644 --- a/desktop/frontend/src/applications/app_store/app_store_common.ts +++ b/desktop/frontend/src/applications/app_store/app_store_common.ts @@ -27,6 +27,7 @@ export type TAppDetail = { name: string; tags: TTag[]; size: number; + type: 'cluster-image' | 'cloud-image'; }; export const ImagehubLabels = [ @@ -47,6 +48,14 @@ export type TImageLabels = { checked: boolean; }; +export type TAppInfo = { + icon: string; + keywords: string[]; + name: string; + description?: string; + size: number; +}; + export function handleImageName(imageName: string): { name: string; tag: string } { let result = imageName.split(':'); return { name: result[0], tag: result[1] }; @@ -68,3 +77,17 @@ export function getSelectLabels(labels: TImageLabels[]): string { .filter((item) => item) .join(','); } + +export function sortByName(arr: TAppInfo[]) { + const map = new Map(); + const result = []; + for (const item of arr) { + map.set(item.name, item); + } + let mapKeys = [...map.keys()]; + mapKeys.sort(); + for (const name of mapKeys) { + result.push(map.get(name)); + } + return result; +} diff --git a/desktop/frontend/src/applications/app_store/components/error.tsx b/desktop/frontend/src/applications/app_store/components/error.tsx new file mode 100644 index 000000000..83b904eb2 --- /dev/null +++ b/desktop/frontend/src/applications/app_store/components/error.tsx @@ -0,0 +1,7 @@ +import Image from 'next/image'; + +function Error() { + return err; +} + +export default Error; diff --git a/desktop/frontend/src/applications/app_store/components/imagehub_loading.tsx b/desktop/frontend/src/applications/app_store/components/imagehub_loading.tsx new file mode 100644 index 000000000..bf60303d0 --- /dev/null +++ b/desktop/frontend/src/applications/app_store/components/imagehub_loading.tsx @@ -0,0 +1,41 @@ +import React from 'react'; +import ContentLoader from 'react-content-loader'; + +const CardLoader = (props: any) => ( + + + + + + +); + +const ListContextLoading = () => { + const count = ['one', 'two', 'three', 'four', 'five']; + const cardStyle = { + background: 'rgba(255, 255, 255, 0.75)', + borderRadius: '12px', + height: '160px' + }; + return ( +
+ {count.map((i) => { + return ( +
+ +
+ ); + })} +
+ ); +}; + +export { ListContextLoading }; diff --git a/desktop/frontend/src/applications/app_store/components/labels.module.scss b/desktop/frontend/src/applications/app_store/components/labels.module.scss index e4d59fa87..9b41bc385 100644 --- a/desktop/frontend/src/applications/app_store/components/labels.module.scss +++ b/desktop/frontend/src/applications/app_store/components/labels.module.scss @@ -20,9 +20,11 @@ .xLabels { display: flex; + height: 32px; + align-items: center; border-radius: 8px; background: rgba(255, 255, 255, 0.75); - padding: 6px 16px; + padding: 0 16px; margin-bottom: 12px; & .tag + .tag { .border1px { @@ -35,15 +37,17 @@ color: rgba(35, 155, 242, 1); } -.deleteIcon svg { - width: 12px; - height: 12px; - margin-left: 8px; -} - .border1px { display: none; border-right: 1px solid rgba(218, 218, 218, 1); height: 12px; margin: 0 8px; } + +.clearLabels { + cursor: pointer; + margin-left: 24px; + font-weight: 500; + font-size: 14px; + color: #8b949e; +} diff --git a/desktop/frontend/src/applications/app_store/components/labels.tsx b/desktop/frontend/src/applications/app_store/components/labels.tsx index 9f9c670b1..49cc73689 100644 --- a/desktop/frontend/src/applications/app_store/components/labels.tsx +++ b/desktop/frontend/src/applications/app_store/components/labels.tsx @@ -12,10 +12,11 @@ type TLabels = { display: 'column' | 'row'; labels: TImageLabels[]; onChange: (value: string) => void; + onClear?: () => void; }; function Labels(props: TLabels) { - const { display, labels, onChange } = props; + const { display, labels, onChange, onClear } = props; if (display === 'row' && labels?.length > 0) { return ( @@ -31,12 +32,15 @@ function Labels(props: TLabels) { > {item.value} - - + + ); })} + + 清空 + ); } diff --git a/desktop/frontend/src/applications/app_store/detail.module.scss b/desktop/frontend/src/applications/app_store/detail.module.scss index 393d06e0b..82ca917b2 100644 --- a/desktop/frontend/src/applications/app_store/detail.module.scss +++ b/desktop/frontend/src/applications/app_store/detail.module.scss @@ -2,10 +2,6 @@ .appHeader { padding: 8px 16px 16px 16px; - .nav svg { - width: 26px; - height: 26px; - } } .mainLeft { @@ -14,16 +10,18 @@ } .mainRight { - flex: 1 1 260px; + flex: 0 1 260px; } .tag { + cursor: pointer; height: 42px; display: flex; align-items: center; padding: 0 12px; background: rgba(255, 255, 255, 0.8); border-radius: 8px; + border: 1px solid transparent; } .markDownWrap div { @@ -42,7 +40,7 @@ .markdownTitle { margin-left: 12px; - font-weight: 600; + font-weight: 500; font-size: 16px; line-height: 22px; color: #0d1a2d; @@ -109,3 +107,48 @@ margin-left: 16px; } } + +.activeTag { + border: 1px solid #239bf2; + color: #239bf2; +} + +.appCommand { + background-color: #4d5060; + border-radius: 8px; + padding: 16px; + color: #ffffff; + font-size: 12px; + line-height: 20px; + position: relative; + + .copyBtn { + position: absolute; + top: 10px; + right: 18px; + width: 24px; + height: 24px; + border-radius: 8px; + background: rgba(255, 255, 255, 0.2); + display: flex; + align-items: center; + justify-content: center; + } +} + +.popover { + opacity: 0; + position: absolute; + padding: 8px; + width: 80px; + border-radius: 16px; + background: #ffffff; + bottom: 40px; + color: #333333; + text-align: center; + transition: all 0.5 ease; +} + +.active { + opacity: 1; +} diff --git a/desktop/frontend/src/applications/app_store/detail_page.tsx b/desktop/frontend/src/applications/app_store/detail_page.tsx index cda580a40..1ed2a8ded 100644 --- a/desktop/frontend/src/applications/app_store/detail_page.tsx +++ b/desktop/frontend/src/applications/app_store/detail_page.tsx @@ -1,10 +1,8 @@ /* eslint-disable @next/next/no-img-element */ -import { DocumentOnePage20Filled, Tag16Filled } from '@fluentui/react-icons'; -import { useQuery } from '@tanstack/react-query'; +import { useQuery, useQueryClient } from '@tanstack/react-query'; import clsx from 'clsx'; import Iconfont from 'components/iconfont'; import MarkDown from 'components/markdown'; -import produce from 'immer'; import { useEffect, useRef, useState } from 'react'; import request from 'services/request'; import useSessionStore from 'stores/session'; @@ -18,97 +16,90 @@ export default function DetailPage() { const appRef = useRef(null); const [isFixed, setFixed] = useState(false); const { kubeconfig } = useSessionStore((state) => state.getSession()); - const [appDetailStatus, setAppDetailStatus] = useState(false); - const [appTags, setAppTags] = useState(); - const [selectTag, setSelectTag] = useState(''); + const [selectTag, setSelectTag] = useState(handleImageName(detailAppName).tag); + let fullImageName: string = handleImageName(detailAppName).name + ':' + selectTag; + let imageCommand: string = 'sealos run ' + fullImageName; + const [open, setOpen] = useState(false); + const queryClient = useQueryClient(); - const { data } = useQuery( - ['getAppDetail'], + const { data, isLoading, isSuccess } = useQuery( + ['getAppDetail', fullImageName], async () => { const res = await request.post('/api/image_hub/get_detail', { kubeconfig, - image_name: detailAppName + image_name: fullImageName }); - if (res.data.code === 200) { - setAppDetailStatus(true); - } return res; }, { - refetchInterval: !appDetailStatus ? 2 * 1000 : false, - enabled: !appDetailStatus + onSuccess: (data) => { + if (data.status === 201) { + queryClient.invalidateQueries({ queryKey: ['getAppDetail', fullImageName] }); + } + } } ); let appDetail = {} as TAppDetail; - if (appDetailStatus) { - appDetail = data?.data?.items[0]; + if (data?.status === 200) { + appDetail = data.data[0]; } + const handleCopy = (value: string) => { + let timer: number; + setOpen((s) => { + if (!open) { + timer = window.setTimeout(() => setOpen(false), 1000); + } else { + window.clearTimeout(timer); + } + return !s; + }); + navigator.clipboard.writeText(value); + }; + const handleScrollEvent = () => { const scrollTop = appRef.current?.scrollTop; - if (scrollTop && scrollTop > 20) { - setFixed(true); - } else { - setFixed(false); - } - }; - - const handleSelectTag = (tagName: string) => { - setAppTags( - produce((draft) => { - const lastItem = draft?.find((item) => item.checked === true); - if (lastItem) { - lastItem.checked = false; - } - const selectItem = draft?.find((item) => item.name === tagName); - if (selectItem) { - setSelectTag(selectItem.name); - selectItem.checked = true; - } - }) - ); + setFixed(Boolean(scrollTop && scrollTop > 20)); }; useEffect(() => { appRef.current?.addEventListener('scroll', handleScrollEvent); - if (appDetail.tags && appDetail.tags.length > 0) { - setAppTags( - produce(appDetail.tags, (draft) => { - draft[0].checked = true; - }) - ); - setSelectTag(appDetail.tags[0].name); - } - }, [appDetail.tags]); + return () => { + appRef.current?.removeEventListener('scroll', handleScrollEvent); + }; + }, []); return (
toPage(EPageType.StorePage, '')} >
- {appDetail?.name} + {appDetail?.name}
-
{selectTag && handleImageName(appDetail?.name).name + ':' + selectTag}
+
{fullImageName}
- + {appDetail.ID?.substring(0, 12)}

{appDetail?.description}

-
+ {/*
-
+
*/}
{appDetail?.keywords?.map((item) => { return ( @@ -119,10 +110,10 @@ export default function DetailPage() { })}
-
+ {/*
19.1K 下载量 -
+
*/}
{appDetail.size ? formattedSize(appDetail.size) : 'empty'} @@ -134,11 +125,11 @@ export default function DetailPage() { {appDetail.size ? formattedSize(appDetail.size) : ''}
-
+ {/*
-
+
*/}
@@ -149,10 +140,19 @@ export default function DetailPage() {
- +
- 概况 + 概览
+ {appDetail.type === 'cluster-image' ? ( +
+
handleCopy(imageCommand)}> + +
copyed
+
+
{imageCommand}
+
+ ) : null}
@@ -162,24 +162,23 @@ export default function DetailPage() {
- +
-
Tags
-
+
Tags
- {appTags?.map((item) => { + {appDetail.tags?.map((item) => { return (
handleSelectTag(item.name)} + className={clsx(styles.tag, { [styles.activeTag]: selectTag === item.name })} + onClick={() => setSelectTag(item.name)} >
- +
{item.name}
-
size
+ {/*
size
*/}
); })} diff --git a/desktop/frontend/src/applications/app_store/index.tsx b/desktop/frontend/src/applications/app_store/index.tsx index 82bd7d370..1bea866ac 100644 --- a/desktop/frontend/src/applications/app_store/index.tsx +++ b/desktop/frontend/src/applications/app_store/index.tsx @@ -29,7 +29,7 @@ export default function AppStore() { return ( -
+
{pageId === EPageType.StorePage && } {pageId === EPageType.OrganizationPage && } {pageId === EPageType.MinePage && } diff --git a/desktop/frontend/src/applications/app_store/store_page.tsx b/desktop/frontend/src/applications/app_store/store_page.tsx index 4792d73a0..b1a02e4bc 100644 --- a/desktop/frontend/src/applications/app_store/store_page.tsx +++ b/desktop/frontend/src/applications/app_store/store_page.tsx @@ -13,20 +13,16 @@ import { handleImageName, ImagehubLabels, TImageLabels, - getSelectLabels + getSelectLabels, + TAppInfo, + sortByName } from './app_store_common'; import Button from './components/button'; import Labels from './components/labels'; +import Error from './components/error'; import { useAppStoreContext } from './index'; import styles from './store_page.module.scss'; - -type TAppInfo = { - icon: string; - keywords: string[]; - name: string; - description?: string; - size: number; -}; +import { ListContextLoading } from './components/imagehub_loading'; function StorePage() { const { toPage } = useAppStoreContext(); @@ -46,13 +42,16 @@ function StorePage() { }, { onSuccess: (data) => { - if (data.data.code === 201) { + if (data.status === 201) { queryClient.invalidateQueries({ queryKey: ['getAppLists', selectedLabels] }); } } } ); - const appLists = data?.data?.items as TAppInfo[]; + let appLists: TAppInfo[] = []; + if (data?.status === 200) { + appLists = sortByName(data.data); + } const handleLabelsChange = (value: string) => { setImageLabels( @@ -65,6 +64,12 @@ function StorePage() { ); }; + const handleLabelsClear = () => { + setImageLabels( + produce((draft: TImageLabels[]) => draft.forEach((item) => (item.checked = false))) + ); + }; + return (
@@ -78,14 +83,16 @@ function StorePage() { display="row" labels={imageLabels.filter((item) => item.checked)} onChange={handleLabelsChange} + onClear={handleLabelsClear} />
- {isLoading && ( + {isError && (
- +
)}
+ {isLoading && } {isSuccess && (
{appLists?.map((item: TAppInfo) => { @@ -96,13 +103,18 @@ function StorePage() { onClick={() => toPage(EPageType.DetailPage, item.name)} >
- {item?.name} + {item?.name}
{handleImageName(item?.name).name}
- +
{item?.description}
@@ -121,10 +133,11 @@ function StorePage() {
-
+ {/*
19.1K
下载量 -
+
*/} +
{item.size ? formattedSize(item.size) : 'empty'} @@ -132,11 +145,11 @@ function StorePage() { 大小
-
+ {/*
-
+
*/}
); diff --git a/desktop/frontend/src/components/app_window/index.module.scss b/desktop/frontend/src/components/app_window/index.module.scss index 660b72e14..40cd3288a 100644 --- a/desktop/frontend/src/components/app_window/index.module.scss +++ b/desktop/frontend/src/components/app_window/index.module.scss @@ -347,3 +347,10 @@ body[data-theme='dark'] .win11Scroll, } } } + +.appMask { + position: absolute; + width: 100%; + height: 100%; + z-index: 4; +} diff --git a/desktop/frontend/src/components/app_window/index.tsx b/desktop/frontend/src/components/app_window/index.tsx index 316243bbc..dc7d11349 100644 --- a/desktop/frontend/src/components/app_window/index.tsx +++ b/desktop/frontend/src/components/app_window/index.tsx @@ -16,10 +16,13 @@ export default function AppWindow(props: { }) { const wnapp = props.app; - const { closeApp, updateAppInfo, switchApp } = useAppStore((state) => state); + const { closeApp, updateAppInfo, switchApp, currentApp, openedApps } = useAppStore( + (state) => state + ); const dragDom = useRef(null); const [dragging, setDragging] = useState(false); const [position, setPosition] = useState({ x: 0, y: 0 }); + // console.log(position); return ( { - switchApp(wnapp, 'clickHeader'); + switchApp({ ...wnapp, mask: false }, 'clickMask'); }} style={{ - background: wnapp.style?.bg || '#fff' + background: '#fff' }} >
@@ -121,8 +124,18 @@ export default function AppWindow(props: {
-
-
{props.children}
+
+
+ {wnapp.mask && ( +
{ + switchApp({ ...wnapp, mask: false }, 'clickMask'); + }} + >
+ )} + {props.children} +
diff --git a/desktop/frontend/src/components/app_window/tab.module.scss b/desktop/frontend/src/components/app_window/tab.module.scss index 1b4f202ca..bc37dee2c 100644 --- a/desktop/frontend/src/components/app_window/tab.module.scss +++ b/desktop/frontend/src/components/app_window/tab.module.scss @@ -79,6 +79,9 @@ } &[data-size='minimize'] { + left: 20%; + height: 80%; + width: 60%; top: calc(100% + 10px); transform: scale(0.6); transform-origin: bottom; diff --git a/desktop/frontend/src/components/iconfont/index.module.scss b/desktop/frontend/src/components/iconfont/index.module.scss deleted file mode 100644 index 13adafdc8..000000000 --- a/desktop/frontend/src/components/iconfont/index.module.scss +++ /dev/null @@ -1,7 +0,0 @@ -.icon { - width: 16px; - height: 16px; - vertical-align: middle; - fill: currentColor; - overflow: hidden; -} diff --git a/desktop/frontend/src/components/iconfont/index.tsx b/desktop/frontend/src/components/iconfont/index.tsx index a92cafae3..98786de3f 100644 --- a/desktop/frontend/src/components/iconfont/index.tsx +++ b/desktop/frontend/src/components/iconfont/index.tsx @@ -1,19 +1,18 @@ -import clsx from 'clsx'; -import styles from './index.module.scss'; - type TIconfont = { iconName: string; + color?: string; }; function Iconfont(props: TIconfont) { - const { iconName } = props; + const { iconName, color } = props; + const style = { + fill: color + }; return ( - <> - - + ); } diff --git a/desktop/frontend/src/components/taskbar/index.tsx b/desktop/frontend/src/components/taskbar/index.tsx index 6769cdabc..473c3a3b9 100644 --- a/desktop/frontend/src/components/taskbar/index.tsx +++ b/desktop/frontend/src/components/taskbar/index.tsx @@ -43,7 +43,7 @@ const Taskbar = () => { return (
{ - switchApp(item); + switchApp({ ...item, mask: false }); }} key={index} className={clsx({ diff --git a/desktop/frontend/src/mock/installedApps.ts b/desktop/frontend/src/mock/installedApps.ts index b27074d2a..3f36202a4 100644 --- a/desktop/frontend/src/mock/installedApps.ts +++ b/desktop/frontend/src/mock/installedApps.ts @@ -20,7 +20,9 @@ const installedApps: TApp[] = [ nameColor: 'text-black', helpDropDown: true, helpDocs: false - } + }, + cacheSize: 'maximize', + mask: true }, { name: 'App Store', @@ -37,9 +39,11 @@ const installedApps: TApp[] = [ size: 'maximize', menu: { nameColor: 'text-black', - helpDropDown: true, + helpDropDown: false, helpDocs: false - } + }, + cacheSize: 'maximize', + mask: true }, // { // name: 'Python Compiler', @@ -87,7 +91,9 @@ const installedApps: TApp[] = [ nameColor: 'text-black', helpDropDown: true, helpDocs: false - } + }, + cacheSize: 'maximize', + mask: true }, { name: 'Kubernetes Dashboard', @@ -106,28 +112,32 @@ const installedApps: TApp[] = [ nameColor: 'text-black', helpDropDown: true, helpDocs: false - } - }, - { - name: 'Kuboard', - icon: '/images/kuboard.svg', - type: APPTYPE.IFRAME, - data: { - url: '', - desc: 'sealos Cloud Kuboard' }, - gallery: [ - 'https: //cdn.programiz.com/cdn/farfuture/IwFGGPqycIxTfzLl7mPdcaqUaircnStXfipaHd4EBik/mtime:1605833048/sites/all/themes/programiz/assets/compiler.png', - 'https: //www.programiz.com/blog/content/images/2020/07/programiz-online-compiler.png' - ], - size: 'maximize', - menu: { - nameColor: 'text-black', - helpDropDown: true, - helpDocs: false - } + cacheSize: 'maximize', + mask: true }, // { + // name: 'Kuboard', + // icon: '/images/kuboard.svg', + // type: APPTYPE.IFRAME, + // data: { + // url: '', + // desc: 'sealos Cloud Kuboard' + // }, + // gallery: [ + // 'https: //cdn.programiz.com/cdn/farfuture/IwFGGPqycIxTfzLl7mPdcaqUaircnStXfipaHd4EBik/mtime:1605833048/sites/all/themes/programiz/assets/compiler.png', + // 'https: //www.programiz.com/blog/content/images/2020/07/programiz-online-compiler.png' + // ], + // size: 'maximize', + // menu: { + // nameColor: 'text-black', + // helpDropDown: true, + // helpDocs: false + // }, + // cacheSize: 'maximize', + // mask: true + // }, + // { // name: 'Prometheus', // icon: '/images/prometheus.svg', // type: APPTYPE.IFRAME, @@ -155,25 +165,27 @@ const installedApps: TApp[] = [ // ], // size: 'maximize' // }, - { - name: 'Redis', - icon: '/images/redis.svg', - type: APPTYPE.IFRAME, - data: { - url: '', - desc: 'redis operator' - }, - gallery: [ - 'https://cdn.programiz.com/cdn/farfuture/IwFGGPqycIxTfzLl7mPdcaqUaircnStXfipaHd4EBik/mtime:1605833048/sites/all/themes/programiz/assets/compiler.png', - 'https://www.programiz.com/blog/content/images/2020/07/programiz-online-compiler.png' - ], - size: 'maximize', - menu: { - nameColor: 'text-black', - helpDropDown: true, - helpDocs: false - } - }, + // { + // name: 'Redis', + // icon: '/images/redis.svg', + // type: APPTYPE.IFRAME, + // data: { + // url: '', + // desc: 'redis operator' + // }, + // gallery: [ + // 'https://cdn.programiz.com/cdn/farfuture/IwFGGPqycIxTfzLl7mPdcaqUaircnStXfipaHd4EBik/mtime:1605833048/sites/all/themes/programiz/assets/compiler.png', + // 'https://www.programiz.com/blog/content/images/2020/07/programiz-online-compiler.png' + // ], + // size: 'maximize', + // menu: { + // nameColor: 'text-black', + // helpDropDown: true, + // helpDocs: false + // }, + // cacheSize: 'maximize', + // mask: true + // }, // { // name: 'Postgres', // icon: '/images/pgadmin.svg', @@ -223,28 +235,32 @@ const installedApps: TApp[] = [ menu: { nameColor: '#0073D7', helpDropDown: false, - helpDocs: true - } - }, - { - name: 'AFFiNE', - icon: '/images/affine.svg', - type: APPTYPE.IFRAME, - data: { - url: '', - desc: 'AFFiNE operator' - }, - gallery: [ - 'https: //cdn.programiz.com/cdn/farfuture/IwFGGPqycIxTfzLl7mPdcaqUaircnStXfipaHd4EBik/mtime:1605833048/sites/all/themes/programiz/assets/compiler.png', - 'https: //www.programiz.com/blog/content/images/2020/07/programiz-online-compiler.png' - ], - size: 'maximize', - menu: { - nameColor: 'text-black', - helpDropDown: true, helpDocs: false - } + }, + cacheSize: 'maximize', + mask: true }, + // { + // name: 'AFFiNE', + // icon: '/images/affine.svg', + // type: APPTYPE.IFRAME, + // data: { + // url: '', + // desc: 'AFFiNE operator' + // }, + // gallery: [ + // 'https: //cdn.programiz.com/cdn/farfuture/IwFGGPqycIxTfzLl7mPdcaqUaircnStXfipaHd4EBik/mtime:1605833048/sites/all/themes/programiz/assets/compiler.png', + // 'https: //www.programiz.com/blog/content/images/2020/07/programiz-online-compiler.png' + // ], + // size: 'maximize', + // menu: { + // nameColor: 'text-black', + // helpDropDown: true, + // helpDocs: false + // }, + // cacheSize: 'maximize', + // mask: true + // }, { name: 'Postgres', icon: '/images/pgadmin.svg', @@ -257,7 +273,9 @@ const installedApps: TApp[] = [ data: { url: '', desc: 'postgres operator' - } + }, + cacheSize: 'maximize', + mask: true } ]; diff --git a/desktop/frontend/src/pages/_document.tsx b/desktop/frontend/src/pages/_document.tsx index 0ff54359b..aeee874de 100644 --- a/desktop/frontend/src/pages/_document.tsx +++ b/desktop/frontend/src/pages/_document.tsx @@ -56,11 +56,7 @@ class MyDocument extends Document { - - +
diff --git a/desktop/frontend/src/pages/api/image_hub/get_detail.ts b/desktop/frontend/src/pages/api/image_hub/get_detail.ts index 9a23ca4b8..c229a5001 100644 --- a/desktop/frontend/src/pages/api/image_hub/get_detail.ts +++ b/desktop/frontend/src/pages/api/image_hub/get_detail.ts @@ -4,7 +4,13 @@ import type { NextApiRequest, NextApiResponse } from 'next'; import { ApplyYaml, GetClusterObject, K8sApi } from 'services/backend/kubernetes'; import { CRDTemplateBuilder } from 'services/backend/wrapper'; import { hashAny } from 'utils/strings'; -import { BadRequestResp, InternalErrorResp, JsonResp, UnprocessableResp } from '../response'; +import { + BadRequestResp, + InternalErrorResp, + JsonResp, + UnprocessableResp, + CreatedJsonResp +} from '../response'; type DataPackDesc = { codes: number; @@ -41,7 +47,7 @@ export default async function handler(req: NextApiRequest, resp: NextApiResponse const datapackDesc = dataDesc.body.status as DataPackDesc; if (datapackDesc.codes === DataPackStatus.Ok) { let result = Object.values(datapackDesc.datas); - return JsonResp({ items: result, code: 200 }, resp); + return JsonResp(result, resp); } return JsonResp(datapackDesc, resp); } @@ -58,7 +64,7 @@ export default async function handler(req: NextApiRequest, resp: NextApiResponse try { const result = await ApplyYaml(kc, datapackCRD); - return JsonResp({ ...result, code: 201 }, resp); + return CreatedJsonResp(result, resp); } catch (err) { return InternalErrorResp(String(err), resp); } diff --git a/desktop/frontend/src/pages/api/image_hub/get_list.ts b/desktop/frontend/src/pages/api/image_hub/get_list.ts index d1a6ab3ee..566ed75dd 100644 --- a/desktop/frontend/src/pages/api/image_hub/get_list.ts +++ b/desktop/frontend/src/pages/api/image_hub/get_list.ts @@ -14,7 +14,13 @@ import { } from 'services/backend/kubernetes'; import { CRDTemplateBuilder } from 'services/backend/wrapper'; import { hashAny } from 'utils/strings'; -import { BadRequestResp, InternalErrorResp, JsonResp, UnprocessableResp } from '../response'; +import { + BadRequestResp, + InternalErrorResp, + JsonResp, + UnprocessableResp, + CreatedJsonResp +} from '../response'; type repositoryStatus = { items: { @@ -66,9 +72,7 @@ export default async function handler(req: NextApiRequest, resp: NextApiResponse images_names.push(item.spec.name + ':' + item.status.latestTag.name); } } - // return JsonResp(repositoryDesc.body, resp); } catch (err) { - console.log(err, 'list'); if (err instanceof k8s.HttpError) { return InternalErrorResp(err.body.message, resp); } @@ -83,7 +87,7 @@ export default async function handler(req: NextApiRequest, resp: NextApiResponse const datapackDesc = dataDesc.body.status as DataPackDesc; if (datapackDesc.codes === DataPackStatus.Ok) { let result = Object.values(datapackDesc.datas); - return JsonResp({ items: result, code: 200 }, resp); + return JsonResp(result, resp); } return JsonResp(datapackDesc, resp); } @@ -100,7 +104,7 @@ export default async function handler(req: NextApiRequest, resp: NextApiResponse try { const result = await ApplyYaml(kc, datapackCRD); - return JsonResp({ ...result, code: 201 }, resp); + return CreatedJsonResp(result, resp); } catch (err) { return InternalErrorResp(String(err), resp); } diff --git a/desktop/frontend/src/pages/api/response.ts b/desktop/frontend/src/pages/api/response.ts index dcbee4db3..ed978e9ef 100644 --- a/desktop/frontend/src/pages/api/response.ts +++ b/desktop/frontend/src/pages/api/response.ts @@ -56,6 +56,17 @@ const InternalErrorResp = (str: string, resp?: NextApiResponse) => const JsonResp = (data: any, resp?: NextApiResponse) => CommonResp({ code: 200, message: 'ok', data: data, statusCode: 200 }, resp); +const CreatedJsonResp = (data: any, resp?: NextApiResponse) => + CommonResp( + { + code: 201, + message: 'the resource has been created and needs to be requested again', + data: data, + statusCode: 201 + }, + resp + ); + export { BadRequestResp, BadAuthResp, @@ -64,5 +75,6 @@ export { MethodNotAllowedResp, InternalErrorResp, CommonResp, - JsonResp + JsonResp, + CreatedJsonResp }; diff --git a/desktop/frontend/src/services/request.ts b/desktop/frontend/src/services/request.ts index a98806c64..6d54a3dab 100644 --- a/desktop/frontend/src/services/request.ts +++ b/desktop/frontend/src/services/request.ts @@ -101,7 +101,8 @@ request.interceptors.response.use( // UnWrap const apiResp = data as ApiResp; - if (apiResp.code !== 200) { + const successfulCode = [200, 201]; + if (!successfulCode.includes(apiResp.code)) { return Promise.reject(new Error(apiResp.code + ':' + apiResp.message)); } diff --git a/desktop/frontend/src/stores/app.ts b/desktop/frontend/src/stores/app.ts index 3301cea4f..a0ab3022f 100644 --- a/desktop/frontend/src/stores/app.ts +++ b/desktop/frontend/src/stores/app.ts @@ -18,6 +18,7 @@ export type TAppFront = { } | {}; cacheSize: 'maximize' | 'maxmin' | 'minimize'; + mask: boolean; }; const initialFrantState: TAppFront = { @@ -25,6 +26,7 @@ const initialFrantState: TAppFront = { zIndex: 1, size: 'maximize', cacheSize: 'maximize', + mask: true, style: {} }; @@ -78,7 +80,7 @@ type TOSState = { openApp(app: TApp): void; // switch the app - switchApp(app: TApp, type?: 'clickHeader'): void; + switchApp(app: TApp, type?: 'clickMask'): void; updateAppInfo(app: TApp): void; @@ -141,6 +143,7 @@ const useAppStore = create()( updateAppInfo: (app: TApp) => { set((state) => { state.openedApps = state.openedApps.map((_app) => { + _app.mask = true; return _app.name === app.name ? app : _app; }); @@ -148,9 +151,9 @@ const useAppStore = create()( return _app.name === app.name ? app : _app; }); - const temp = state.openedApps.filter((item) => item.size !== 'minimize'); - temp.sort((a, b) => b.zIndex - a.zIndex); - state.currentApp = temp[0]; + const activeApps = state.openedApps.filter((item) => item.size !== 'minimize'); + activeApps.sort((a, b) => b.zIndex - a.zIndex); + state.currentApp = activeApps[0]; }); }, @@ -164,6 +167,7 @@ const useAppStore = create()( _app.zIndex = zIndex; _app.isShow = true; _app.size = 'maximize'; + _app.mask = false; get().updateAppInfo(_app); @@ -181,13 +185,16 @@ const useAppStore = create()( const _app: TApp = JSON.parse(JSON.stringify(app)); _app.zIndex = zIndex; _app.isShow = true; - if (type !== 'clickHeader') { + if (type !== 'clickMask') { if (get().currentApp?.name === _app.name) { _app.size === 'minimize' ? (_app.size = _app.cacheSize) : (_app.size = 'minimize'); } else { _app.size = _app.cacheSize; } } + + get().updateAppInfo(_app); + set((state) => { // repalce app info state.openedApps = state.openedApps.map((item) => { diff --git a/desktop/frontend/src/styles/globals.scss b/desktop/frontend/src/styles/globals.scss index 37cc786f2..a15bcdfe2 100755 --- a/desktop/frontend/src/styles/globals.scss +++ b/desktop/frontend/src/styles/globals.scss @@ -3,6 +3,7 @@ @tailwind utilities; @import './highlight.scss'; + html { font-size: 12px; scrollbar-color: transparent transparent; @@ -115,3 +116,11 @@ body::-webkit-scrollbar { --hover-bg: rgba(0, 178, 255, 0.16); --focus-bg: rgba(0, 183, 255, 0.25); } + +.icon { + width: 16px; + height: 16px; + vertical-align: -0.15em; + fill: currentColor; + overflow: hidden; +}