From 7970d9b8c9c4d70430aca24a885353ba77f48210 Mon Sep 17 00:00:00 2001 From: CatsJuice Date: Thu, 18 Apr 2024 15:48:19 +0000 Subject: [PATCH] feat(core): add local ai onboarding dialog (#6600) --- .../src/ui/notification/notification-card.tsx | 4 +- .../component/src/ui/notification/types.ts | 4 +- .../affine/ai-onboarding/edgeless.dialog.tsx | 45 +++++---- .../affine/ai-onboarding/local.dialog.css.ts | 40 ++++++++ .../affine/ai-onboarding/local.dialog.tsx | 95 ++++++++++++++++++- .../components/affine/ai-onboarding/state.ts | 8 +- 6 files changed, 172 insertions(+), 24 deletions(-) create mode 100644 packages/frontend/core/src/components/affine/ai-onboarding/local.dialog.css.ts diff --git a/packages/frontend/component/src/ui/notification/notification-card.tsx b/packages/frontend/component/src/ui/notification/notification-card.tsx index 0b542bb551..6b1d21f6ab 100644 --- a/packages/frontend/component/src/ui/notification/notification-card.tsx +++ b/packages/frontend/component/src/ui/notification/notification-card.tsx @@ -30,6 +30,7 @@ export const NotificationCard = ({ notification }: NotificationCardProps) => { footer, alignMessage = 'title', onDismiss, + rootAttrs, } = notification; const onActionClicked = useCallback(() => { @@ -49,7 +50,8 @@ export const NotificationCard = ({ notification }: NotificationCardProps) => { [styles.iconColor]: getIconColor(style, theme, iconColor), })} data-with-icon={icon ? '' : undefined} - className={styles.card} + {...rootAttrs} + className={clsx(styles.card, rootAttrs?.className)} > {thumb}
diff --git a/packages/frontend/component/src/ui/notification/types.ts b/packages/frontend/component/src/ui/notification/types.ts index 42bf239611..f7b7bf8d22 100644 --- a/packages/frontend/component/src/ui/notification/types.ts +++ b/packages/frontend/component/src/ui/notification/types.ts @@ -1,4 +1,4 @@ -import type { ReactNode } from 'react'; +import type { HTMLAttributes, ReactNode } from 'react'; import type { ButtonProps } from '../button'; @@ -23,6 +23,8 @@ export interface Notification { autoClose?: boolean; }; + rootAttrs?: HTMLAttributes; + // custom slots thumb?: ReactNode; title?: ReactNode; diff --git a/packages/frontend/core/src/components/affine/ai-onboarding/edgeless.dialog.tsx b/packages/frontend/core/src/components/affine/ai-onboarding/edgeless.dialog.tsx index 58bae62bc3..92de0c744c 100644 --- a/packages/frontend/core/src/components/affine/ai-onboarding/edgeless.dialog.tsx +++ b/packages/frontend/core/src/components/affine/ai-onboarding/edgeless.dialog.tsx @@ -18,7 +18,11 @@ import { useEffect, useMemo, useRef } from 'react'; import * as styles from './edgeless.dialog.css'; import mouseTrackDark from './lottie/edgeless/mouse-track-dark.json'; import mouseTrackLight from './lottie/edgeless/mouse-track-light.json'; -import { edgelessNotifyId$, showAIOnboardingGeneral$ } from './state'; +import { + edgelessNotifyId$, + localNotifyId$, + showAIOnboardingGeneral$, +} from './state'; import type { BaseAIOnboardingDialogProps } from './type'; const EdgelessOnboardingAnimation = () => { @@ -63,24 +67,27 @@ export const AIOnboardingEdgeless = ({ if (settingModalOpen.open) return; if (generalAIOnboardingOpened) return; if (notifyId) return; - if (isCloud && mode === 'edgeless') { - clearTimeout(timeoutRef.current); - timeoutRef.current = setTimeout(() => { - const id = notify( - { - title: t['com.affine.ai-onboarding.edgeless.title'](), - message: t['com.affine.ai-onboarding.edgeless.message'](), - icon: , - iconColor: cssVar('brandColor'), - thumb: , - alignMessage: 'icon', - onDismiss, - }, - { duration: 1000 * 60 * 10 } - ); - edgelessNotifyId$.next(id); - }, 1000); - } + if (mode !== 'edgeless') return; + if (!isCloud) return; + clearTimeout(timeoutRef.current); + timeoutRef.current = setTimeout(() => { + // try to close local onboarding + notify.dismiss(localNotifyId$.value); + + const id = notify( + { + title: t['com.affine.ai-onboarding.edgeless.title'](), + message: t['com.affine.ai-onboarding.edgeless.message'](), + icon: , + iconColor: cssVar('processingColor'), + thumb: , + alignMessage: 'icon', + onDismiss, + }, + { duration: 1000 * 60 * 10 } + ); + edgelessNotifyId$.next(id); + }, 1000); }, [ generalAIOnboardingOpened, isCloud, diff --git a/packages/frontend/core/src/components/affine/ai-onboarding/local.dialog.css.ts b/packages/frontend/core/src/components/affine/ai-onboarding/local.dialog.css.ts new file mode 100644 index 0000000000..d260ab953a --- /dev/null +++ b/packages/frontend/core/src/components/affine/ai-onboarding/local.dialog.css.ts @@ -0,0 +1,40 @@ +import { cssVar } from '@toeverything/theme'; +import { style } from '@vanilla-extract/css'; + +export const card = style({ + borderRadius: 12, + boxShadow: cssVar('menuShadow'), +}); + +export const thumb = style({ + width: '100%', + height: 211, + borderRadius: 'inherit', + borderBottomLeftRadius: 0, + borderBottomRightRadius: 0, + overflow: 'hidden', + display: 'flex', + justifyContent: 'center', + alignItems: 'center', +}); + +export const thumbContent = style({ + width: 'calc(100% + 4px)', + height: 'calc(100% + 4px)', +}); + +export const title = style({ + fontWeight: 500, +}); + +export const footerActions = style({ + display: 'flex', + justifyContent: 'flex-end', + gap: 12, + marginTop: 8, +}); + +export const actionButton = style({ + fontSize: cssVar('fontSm'), + padding: '0 2px', +}); diff --git a/packages/frontend/core/src/components/affine/ai-onboarding/local.dialog.tsx b/packages/frontend/core/src/components/affine/ai-onboarding/local.dialog.tsx index 2e14f2f8e2..be8e183045 100644 --- a/packages/frontend/core/src/components/affine/ai-onboarding/local.dialog.tsx +++ b/packages/frontend/core/src/components/affine/ai-onboarding/local.dialog.tsx @@ -1,5 +1,96 @@ +import { Button, notify } from '@affine/component'; +import { WorkspaceFlavour } from '@affine/env/workspace'; +import { useAFFiNEI18N } from '@affine/i18n/hooks'; +import { AiIcon } from '@blocksuite/icons'; +import { useLiveData, useService, WorkspaceService } from '@toeverything/infra'; +import { cssVar } from '@toeverything/theme'; +import { useEffect, useRef } from 'react'; + +import * as styles from './local.dialog.css'; +import { edgelessNotifyId$, localNotifyId$ } from './state'; import type { BaseAIOnboardingDialogProps } from './type'; -export const AIOnboardingLocal = (_: BaseAIOnboardingDialogProps) => { - return
{/* TODO: open local workspace for the first time */}
; +const LocalOnboardingAnimation = () => { + return ( +
+
+ ); +}; + +const FooterActions = ({ onDismiss }: { onDismiss: () => void }) => { + const t = useAFFiNEI18N(); + return ( +
+ + + + +
+ ); +}; + +export const AIOnboardingLocal = ({ + onDismiss, +}: BaseAIOnboardingDialogProps) => { + const t = useAFFiNEI18N(); + const workspaceService = useService(WorkspaceService); + const notifyId = useLiveData(localNotifyId$); + const timeoutRef = useRef>(); + + const isLocal = workspaceService.workspace.flavour === WorkspaceFlavour.LOCAL; + + useEffect(() => { + if (!isLocal) return; + if (notifyId) return; + clearTimeout(timeoutRef.current); + timeoutRef.current = setTimeout(() => { + // try to close edgeless onboarding + notify.dismiss(edgelessNotifyId$.value); + + const id = notify( + { + title: ( +
+ {t['com.affine.ai-onboarding.local.title']()} +
+ ), + message: t['com.affine.ai-onboarding.local.message'](), + icon: , + iconColor: cssVar('brandColor'), + thumb: , + alignMessage: 'icon', + onDismiss, + footer: ( + { + onDismiss(); + notify.dismiss(id); + }} + /> + ), + rootAttrs: { className: styles.card }, + }, + { duration: 1000 * 60 * 10 } + ); + localNotifyId$.next(id); + }, 1000); + }, [isLocal, notifyId, onDismiss, t]); + + return null; }; diff --git a/packages/frontend/core/src/components/affine/ai-onboarding/state.ts b/packages/frontend/core/src/components/affine/ai-onboarding/state.ts index 6322c3a76f..5aa2579f9c 100644 --- a/packages/frontend/core/src/components/affine/ai-onboarding/state.ts +++ b/packages/frontend/core/src/components/affine/ai-onboarding/state.ts @@ -5,4 +5,10 @@ import { LiveData } from '@toeverything/infra'; export const showAIOnboardingGeneral$ = new LiveData(false); // avoid notifying multiple times -export const edgelessNotifyId$ = new LiveData(null); +export const edgelessNotifyId$ = new LiveData( + undefined +); + +export const localNotifyId$ = new LiveData( + undefined +);