mirror of
https://github.com/toeverything/AFFiNE.git
synced 2025-01-03 05:58:50 +03: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:
parent
c222cf7b96
commit
bb329944ed
@ -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 }}
|
||||
>
|
||||
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
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."
|
||||
|
Loading…
Reference in New Issue
Block a user