Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(desktop): Implement smart dock behavior #4998

Merged
merged 4 commits into from
Aug 28, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
114 changes: 73 additions & 41 deletions frontend/desktop/src/components/AppDock/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,14 @@ import useAppStore, { AppInfo } from '@/stores/app';
import { useConfigStore } from '@/stores/config';
import { useDesktopConfigStore } from '@/stores/desktopConfig';
import { APPTYPE, TApp } from '@/types';
import { I18nCommonKey } from '@/types/i18next';
import { Box, Center, Flex, Image } from '@chakra-ui/react';
import { MouseEvent, useContext, useMemo } from 'react';
import { useTranslation } from 'next-i18next';
import { MouseEvent, useContext, useEffect, useMemo, useRef, useState } from 'react';
import { Menu, useContextMenu } from 'react-contexify';
import { ChevronDownIcon } from '../icons';
import styles from './index.module.css';
import { useTranslation } from 'next-i18next';
import CustomTooltip from './CustomTooltip';
import { I18nCommonKey } from '@/types/i18next';
import styles from './index.module.css';

const APP_DOCK_MENU_ID = 'APP_DOCK_MENU_ID';

Expand All @@ -19,7 +19,6 @@ export default function AppDock() {
const {
installedApps: apps,
runningInfo,
setToHighestLayerById,
currentAppPid,
openApp,
switchAppById,
Expand All @@ -28,7 +27,9 @@ export default function AppDock() {
} = useAppStore();
const logo = useConfigStore().layoutConfig?.logo;
const moreAppsContent = useContext(MoreAppsContext);
const { isNavbarVisible, toggleNavbarVisibility } = useDesktopConfigStore();
const { isNavbarVisible, toggleNavbarVisibility, getTransitionValue } = useDesktopConfigStore();
const [isMouseOverDock, setIsMouseOverDock] = useState(false);
const timeoutRef = useRef<number | null>(null);

const { show } = useContextMenu({
id: APP_DOCK_MENU_ID
Expand Down Expand Up @@ -109,40 +110,76 @@ export default function AppDock() {
event: e,
position: {
// @ts-ignore
x: '60px',
x: '244px',
// @ts-ignore
y: '-114px'
y: '-34px'
}
});
};

const transitionValue = 'transform 200ms ease-in-out, opacity 200ms ease-in-out';
useEffect(() => {
if (!isMouseOverDock) {
const hasMaximizedApp = runningInfo.some((app) => app.size === 'maximize');
toggleNavbarVisibility(!hasMaximizedApp);
}
}, [isMouseOverDock, runningInfo, toggleNavbarVisibility]);

const handleMouseEnter = () => {
if (timeoutRef.current !== null) {
clearTimeout(timeoutRef.current);
timeoutRef.current = null;
}
setIsMouseOverDock(true);
};

const handleMouseLeave = () => {
timeoutRef.current = window.setTimeout(() => {
setIsMouseOverDock(false);
}, 500);
};

return (
<Box position="absolute" left="50%" bottom={'4px'} transform="translateX(-50%)" zIndex={'1000'}>
<Center
width={'48px'}
height={'16px'}
position={'absolute'}
color={'white'}
transition={transitionValue}
cursor={'pointer'}
bg="rgba(220, 220, 224, 0.3)"
backdropFilter="blur(80px) saturate(150%)"
boxShadow={
'0px 0px 20px -4px rgba(12, 26, 67, 0.25), 0px 0px 1px 0px rgba(24, 43, 100, 0.25)'
}
borderTopRadius={'4px'}
top={'-80px'}
transform={isNavbarVisible ? 'translate(-50%, 0)' : 'translate(-50%, 64px)'}
will-change="transform, opacity"
onClick={toggleNavbarVisibility}
>
<ChevronDownIcon
transform={isNavbarVisible ? 'rotate(0deg)' : 'rotate(180deg)'}
transition="transform 0.3s ease-in-out"
/>
</Center>
<Flex
flexDirection={'column'}
alignItems={'center'}
position="absolute"
p={'16px'}
pb={'0px'}
left="50%"
bottom={'4px'}
zIndex={'1000'}
transition={getTransitionValue()}
transform={isNavbarVisible ? 'translate(-50%, 0)' : 'translate(-50%, 64px)'}
will-change="transform, opacity"
onMouseEnter={handleMouseEnter}
onMouseLeave={handleMouseLeave}
>
{runningInfo.length > 0 && runningInfo.some((app) => app.size === 'maximize') && (
<Center
width={'48px'}
height={'16px'}
color={'white'}
transition={getTransitionValue()}
cursor={'pointer'}
bg="rgba(220, 220, 224, 0.3)"
backdropFilter="blur(80px) saturate(150%)"
boxShadow={
'0px 0px 20px -4px rgba(12, 26, 67, 0.25), 0px 0px 1px 0px rgba(24, 43, 100, 0.25)'
}
borderTopRadius={'4px'}
transform={isNavbarVisible ? 'translateY(0)' : 'translateY(-4px)'}
will-change="transform, opacity"
onClick={() => {
toggleNavbarVisibility();
}}
>
<ChevronDownIcon
transform={isNavbarVisible ? 'rotate(0deg)' : 'rotate(180deg)'}
transition="transform 200ms ease-in-out"
/>
</Center>
)}

<Flex
onContextMenu={(e) => displayMenu(e)}
borderRadius="12px"
Expand All @@ -157,13 +194,6 @@ export default function AppDock() {
gap={'12px'}
userSelect={'none'}
px={'12px'}
transition={transitionValue}
opacity={isNavbarVisible ? 1 : 0}
position="absolute"
top={'-64px'}
transform={isNavbarVisible ? 'translate(-50%, 0)' : 'translate(-50%, 68px)'}
will-change="transform, opacity"
overflow="hidden"
>
{AppMenuLists.map((item: AppInfo, index: number) => {
return (
Expand Down Expand Up @@ -199,6 +229,7 @@ export default function AppDock() {
alt={item?.name}
w="32px"
h="32px"
draggable={false}
/>
</Center>
<Box
Expand All @@ -214,6 +245,7 @@ export default function AppDock() {
);
})}
</Flex>

<Menu className={styles.contexify} id={APP_DOCK_MENU_ID}>
<>
<Box
Expand All @@ -230,6 +262,6 @@ export default function AppDock() {
<div className={styles.arrow}></div>
</>
</Menu>
</Box>
</Flex>
);
}
3 changes: 3 additions & 0 deletions frontend/desktop/src/stores/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { create } from 'zustand';
import { devtools, persist } from 'zustand/middleware';
import { immer } from 'zustand/middleware/immer';
import AppStateManager from '../utils/ProcessManager';
import { useDesktopConfigStore } from './desktopConfig';

export class AppInfo {
pid: number;
Expand Down Expand Up @@ -75,6 +76,7 @@ const useAppStore = create<TOSState>()(
},
// should use pid to close app, but it don't support multi same app process now
closeAppById: (pid: number) => {
useDesktopConfigStore.getState().temporarilyDisableAnimation();
set((state) => {
state.runner.closeApp(pid);
// make sure the process is killed
Expand Down Expand Up @@ -123,6 +125,7 @@ const useAppStore = create<TOSState>()(
},

openApp: async (app: TApp, { query, raw, pathname = '/', appSize = 'maximize' } = {}) => {
useDesktopConfigStore.getState().temporarilyDisableAnimation();
const zIndex = get().maxZIndex + 1;
// debugger
// 未支持多实例
Expand Down
27 changes: 23 additions & 4 deletions frontend/desktop/src/stores/desktopConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,24 +5,43 @@ import { immer } from 'zustand/middleware/immer';
type State = {
isAppBar: boolean;
isNavbarVisible: boolean;
isAnimationEnabled: boolean;
toggleShape: () => void;
toggleNavbarVisibility: () => void;
toggleNavbarVisibility: (forceState?: boolean) => void;
temporarilyDisableAnimation: () => void;
getTransitionValue: () => string;
};

export const useDesktopConfigStore = create<State>()(
persist(
immer((set) => ({
immer((set, get) => ({
isAppBar: true,
isNavbarVisible: true,
isAnimationEnabled: true,
toggleShape() {
set((state) => {
state.isAppBar = !state.isAppBar;
});
},
toggleNavbarVisibility() {
toggleNavbarVisibility(forceState) {
set((state) => {
state.isNavbarVisible = !state.isNavbarVisible;
state.isNavbarVisible = forceState !== undefined ? forceState : !state.isNavbarVisible;
});
},
temporarilyDisableAnimation() {
set((state) => {
state.isAnimationEnabled = false;
});
requestAnimationFrame(() => {
set((state) => {
state.isAnimationEnabled = true;
});
});
},
getTransitionValue() {
return get().isAnimationEnabled
? 'transform 200ms ease-in-out, opacity 200ms ease-in-out'
: 'none';
}
})),
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -258,7 +258,8 @@ const Form = ({
height={'80px'}
border={'1px solid'}
borderRadius={'6px'}
cursor={'pointer'}
cursor={isEdit ? 'not-allowed' : 'pointer'}
opacity={isEdit && getValues('dbType') !== item.id ? '0.4' : '1'}
fontWeight={'bold'}
color={'grayModern.900'}
{...(getValues('dbType') === item.id
Expand All @@ -275,6 +276,7 @@ const Form = ({
}
})}
onClick={() => {
if (isEdit) return;
setValue('dbType', item.id);
setValue('dbVersion', DBVersionMap[getValues('dbType')][0].id);
}}
Expand Down Expand Up @@ -303,6 +305,7 @@ const Form = ({
<Label w={100}>{t('version')}</Label>

<MySelect
isDisabled={isEdit}
width={'200px'}
placeholder={`${t('DataBase')} ${t('version')}`}
value={getValues('dbVersion')}
Expand Down
Loading