mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-07-12 07:36:42 +08:00
feat(core): optimize ai onboarding trigger logic (#6579)
- don't open edgeless ai-onboarding dialog until general ai onboarding and setting modal closed - clip edgeless ai onboarding thumb to avoid "black border" - correct "try for free" - replace edgeless ai onboarding lottie resources
This commit is contained in:
@@ -9,4 +9,13 @@ export const thumb = style({
|
||||
height: 211,
|
||||
background: cssVar('backgroundOverlayPanelColor'),
|
||||
overflow: 'hidden',
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
});
|
||||
|
||||
export const thumbContent = style({
|
||||
borderRadius: 'inherit',
|
||||
width: 'calc(100% + 4px)',
|
||||
height: 'calc(100% + 4px)',
|
||||
});
|
||||
|
||||
@@ -1,44 +1,48 @@
|
||||
import { notify } from '@affine/component';
|
||||
import { openSettingModalAtom } from '@affine/core/atoms';
|
||||
import { CurrentWorkspaceService } from '@affine/core/modules/workspace';
|
||||
import { WorkspaceFlavour } from '@affine/env/workspace';
|
||||
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
||||
import { AiIcon } from '@blocksuite/icons';
|
||||
import { Doc, LiveData, useLiveData, useService } from '@toeverything/infra';
|
||||
import { Doc, useLiveData, useService } from '@toeverything/infra';
|
||||
import { cssVar } from '@toeverything/theme';
|
||||
import { useAtomValue } from 'jotai';
|
||||
import Lottie from 'lottie-react';
|
||||
import { useTheme } from 'next-themes';
|
||||
import { useEffect, useMemo, useRef } from 'react';
|
||||
|
||||
import * as styles from './edgeless.dialog.css';
|
||||
import mouseDark from './lottie/edgeless/mouse-dark.json';
|
||||
import mouseLight from './lottie/edgeless/mouse-light.json';
|
||||
import trackPadDark from './lottie/edgeless/trackpad-dark.json';
|
||||
import trackPadLight from './lottie/edgeless/trackpad-light.json';
|
||||
import mouseTrackDark from './lottie/edgeless/mouse-track-dark.json';
|
||||
import mouseTrackLight from './lottie/edgeless/mouse-track-light.json';
|
||||
import { edgelessNotifyId$, showAIOnboardingGeneral$ } from './state';
|
||||
import type { BaseAIOnboardingDialogProps } from './type';
|
||||
|
||||
const EdgelessOnboardingAnimation = () => {
|
||||
const { resolvedTheme } = useTheme();
|
||||
|
||||
const isTrackPad = false;
|
||||
|
||||
const data = useMemo(() => {
|
||||
if (isTrackPad) {
|
||||
return resolvedTheme === 'dark' ? trackPadDark : trackPadLight;
|
||||
}
|
||||
return resolvedTheme === 'dark' ? mouseDark : mouseLight;
|
||||
}, [isTrackPad, resolvedTheme]);
|
||||
return resolvedTheme === 'dark' ? mouseTrackDark : mouseTrackLight;
|
||||
}, [resolvedTheme]);
|
||||
|
||||
return <Lottie loop autoplay animationData={data} className={styles.thumb} />;
|
||||
return (
|
||||
<div className={styles.thumb}>
|
||||
<Lottie
|
||||
loop
|
||||
autoplay
|
||||
animationData={data}
|
||||
className={styles.thumbContent}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
// avoid notifying multiple times
|
||||
const notifyId$ = new LiveData<string | number | null>(null);
|
||||
|
||||
export const AIOnboardingEdgeless = ({
|
||||
onDismiss,
|
||||
}: BaseAIOnboardingDialogProps) => {
|
||||
const t = useAFFiNEI18N();
|
||||
const notifyId = useLiveData(notifyId$);
|
||||
const notifyId = useLiveData(edgelessNotifyId$);
|
||||
const generalAIOnboardingOpened = useLiveData(showAIOnboardingGeneral$);
|
||||
const settingModalOpen = useAtomValue(openSettingModalAtom);
|
||||
const timeoutRef = useRef<ReturnType<typeof setTimeout>>();
|
||||
const currentWorkspace = useLiveData(
|
||||
useService(CurrentWorkspaceService).currentWorkspace$
|
||||
@@ -49,6 +53,8 @@ export const AIOnboardingEdgeless = ({
|
||||
const mode = useLiveData(doc.mode$);
|
||||
|
||||
useEffect(() => {
|
||||
if (settingModalOpen.open) return;
|
||||
if (generalAIOnboardingOpened) return;
|
||||
if (notifyId) return;
|
||||
if (isCloud && mode === 'edgeless') {
|
||||
clearTimeout(timeoutRef.current);
|
||||
@@ -64,10 +70,18 @@ export const AIOnboardingEdgeless = ({
|
||||
},
|
||||
{ duration: 1000 * 60 * 10 }
|
||||
);
|
||||
notifyId$.next(id);
|
||||
edgelessNotifyId$.next(id);
|
||||
}, 1000);
|
||||
}
|
||||
}, [isCloud, mode, notifyId, onDismiss, t]);
|
||||
}, [
|
||||
generalAIOnboardingOpened,
|
||||
isCloud,
|
||||
mode,
|
||||
notifyId,
|
||||
onDismiss,
|
||||
settingModalOpen,
|
||||
t,
|
||||
]);
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
@@ -13,6 +13,7 @@ import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import * as baseStyles from './base-style.css';
|
||||
import * as styles from './general.dialog.css';
|
||||
import { Slider } from './slider';
|
||||
import { showAIOnboardingGeneral$ } from './state';
|
||||
import type { BaseAIOnboardingDialogProps } from './type';
|
||||
|
||||
type PlayListItem = { video: string; title: ReactNode; desc: ReactNode };
|
||||
@@ -71,7 +72,8 @@ export const AIOnboardingGeneral = ({
|
||||
);
|
||||
const isCloud = currentWorkspace?.flavour === WorkspaceFlavour.AFFINE_CLOUD;
|
||||
const t = useAFFiNEI18N();
|
||||
const [open, setOpen] = useState(true);
|
||||
// const [open, setOpen] = useState(true);
|
||||
const open = useLiveData(showAIOnboardingGeneral$);
|
||||
const [index, setIndex] = useState(0);
|
||||
const list = useMemo(() => getPlayList(t), [t]);
|
||||
const setSettingModal = useSetAtom(openSettingModalAtom);
|
||||
@@ -81,7 +83,7 @@ export const AIOnboardingGeneral = ({
|
||||
const isLast = index === list.length - 1;
|
||||
|
||||
const closeAndDismiss = useCallback(() => {
|
||||
setOpen(false);
|
||||
showAIOnboardingGeneral$.next(false);
|
||||
onDismiss();
|
||||
}, [onDismiss]);
|
||||
const goToPricingPlans = useCallback(() => {
|
||||
@@ -92,7 +94,7 @@ export const AIOnboardingGeneral = ({
|
||||
});
|
||||
closeAndDismiss();
|
||||
}, [closeAndDismiss, setSettingModal]);
|
||||
const onClose = useCallback(() => setOpen(false), []);
|
||||
const onClose = useCallback(() => showAIOnboardingGeneral$.next(false), []);
|
||||
const onPrev = useCallback(() => {
|
||||
setIndex(i => Math.max(0, i - 1));
|
||||
}, []);
|
||||
@@ -101,9 +103,16 @@ export const AIOnboardingGeneral = ({
|
||||
}, [list.length]);
|
||||
|
||||
const videoRenderer = useCallback(
|
||||
({ video }: PlayListItem) => (
|
||||
({ video }: PlayListItem, index: number) => (
|
||||
<div className={styles.videoWrapper}>
|
||||
<video src={video} className={styles.video} loop muted playsInline />
|
||||
<video
|
||||
autoPlay={index === 0}
|
||||
src={video}
|
||||
className={styles.video}
|
||||
loop
|
||||
muted
|
||||
playsInline
|
||||
/>
|
||||
</div>
|
||||
),
|
||||
[]
|
||||
@@ -117,6 +126,11 @@ export const AIOnboardingGeneral = ({
|
||||
[]
|
||||
);
|
||||
|
||||
// show dialog when it's mounted
|
||||
useEffect(() => {
|
||||
showAIOnboardingGeneral$.next(true);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const videoWrapper = videoWrapperRef.current;
|
||||
if (!videoWrapper) return;
|
||||
@@ -136,7 +150,7 @@ export const AIOnboardingGeneral = ({
|
||||
return isCloud ? (
|
||||
<Modal
|
||||
open={open}
|
||||
onOpenChange={setOpen}
|
||||
onOpenChange={v => showAIOnboardingGeneral$.next(v)}
|
||||
contentOptions={{ className: styles.dialog }}
|
||||
overlayOptions={{ className: baseStyles.dialogOverlay }}
|
||||
>
|
||||
|
||||
+2778
-704
File diff suppressed because it is too large
Load Diff
+2800
-704
File diff suppressed because it is too large
Load Diff
-21981
File diff suppressed because it is too large
Load Diff
-22003
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,8 @@
|
||||
import { LiveData } from '@toeverything/infra';
|
||||
|
||||
// to share the state between general & edgeless dialog,
|
||||
// so that we can avoid showing edgeless dialog when general dialog is opened
|
||||
export const showAIOnboardingGeneral$ = new LiveData(false);
|
||||
|
||||
// avoid notifying multiple times
|
||||
export const edgelessNotifyId$ = new LiveData<string | number | null>(null);
|
||||
@@ -1295,7 +1295,7 @@
|
||||
"com.affine.ai-onboarding.general.skip": "Alert me later",
|
||||
"com.affine.ai-onboarding.general.next": "Next",
|
||||
"com.affine.ai-onboarding.general.prev": "Back",
|
||||
"com.affine.ai-onboarding.general.try-for-free": "Tree for Free",
|
||||
"com.affine.ai-onboarding.general.try-for-free": "Try for Free",
|
||||
"com.affine.ai-onboarding.general.purchase": "Get Unlimited Usage",
|
||||
"com.affine.ai-onboarding.edgeless.title": "Meet AFFiNE AI",
|
||||
"com.affine.ai-onboarding.edgeless.message": "Lets you think bigger, create faster, work smarter and save time for every project."
|
||||
|
||||
Reference in New Issue
Block a user