mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-02-12 04:18:54 +00:00
feat(core): expose toggle ai onboarding apis (#7039)
```ts
import { toggleGeneralAIOnboarding } from '@affine/core/components/affine/ai-onboarding/apis';
// show
// toggleGeneralAIOnboarding();
toggleGeneralAIOnboarding(true);
// dismiss
toggleGeneralAIOnboarding(false);
```
This commit is contained in:
@@ -0,0 +1,38 @@
|
||||
import { AIOnboardingType } from './type';
|
||||
|
||||
function createStorageEvent(key: string, newValue: string) {
|
||||
const event = new StorageEvent('storage', {
|
||||
key,
|
||||
newValue,
|
||||
oldValue: localStorage.getItem(key),
|
||||
storageArea: localStorage,
|
||||
});
|
||||
window.dispatchEvent(event);
|
||||
}
|
||||
|
||||
const setItem = function (key: string, value: string) {
|
||||
const oldValue = localStorage.getItem(key);
|
||||
localStorage.setItem.call(localStorage, key, value);
|
||||
if (oldValue !== value) createStorageEvent(key, value);
|
||||
};
|
||||
|
||||
/**
|
||||
* Show/Hide AI onboarding manually
|
||||
*/
|
||||
export const toggleGeneralAIOnboarding = (show = true) => {
|
||||
setItem(AIOnboardingType.GENERAL, show ? 'false' : 'true');
|
||||
};
|
||||
|
||||
/**
|
||||
* Show/Hide local AI toast manually
|
||||
*/
|
||||
export const toggleLocalAIOnboarding = (show = true) => {
|
||||
setItem(AIOnboardingType.LOCAL, show ? 'false' : 'true');
|
||||
};
|
||||
|
||||
/**
|
||||
* Show/Hide edgeless AI toast manually
|
||||
*/
|
||||
export const toggleEdgelessAIOnboarding = (show = true) => {
|
||||
setItem(AIOnboardingType.EDGELESS, show ? 'false' : 'true');
|
||||
};
|
||||
@@ -2,7 +2,6 @@ import { Button, FlexWrapper, notify } from '@affine/component';
|
||||
import { openSettingModalAtom } from '@affine/core/atoms';
|
||||
import { SubscriptionService } from '@affine/core/modules/cloud';
|
||||
import { mixpanel } from '@affine/core/utils';
|
||||
import { WorkspaceFlavour } from '@affine/env/workspace';
|
||||
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
||||
import { AiIcon } from '@blocksuite/icons';
|
||||
import {
|
||||
@@ -17,6 +16,7 @@ import Lottie from 'lottie-react';
|
||||
import { useTheme } from 'next-themes';
|
||||
import { useCallback, useEffect, useMemo, useRef } from 'react';
|
||||
|
||||
import { toggleEdgelessAIOnboarding } from './apis';
|
||||
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';
|
||||
@@ -25,7 +25,6 @@ import {
|
||||
localNotifyId$,
|
||||
showAIOnboardingGeneral$,
|
||||
} from './state';
|
||||
import type { BaseAIOnboardingDialogProps } from './type';
|
||||
|
||||
const EdgelessOnboardingAnimation = () => {
|
||||
const { resolvedTheme } = useTheme();
|
||||
@@ -46,10 +45,8 @@ const EdgelessOnboardingAnimation = () => {
|
||||
);
|
||||
};
|
||||
|
||||
export const AIOnboardingEdgeless = ({
|
||||
onDismiss,
|
||||
}: BaseAIOnboardingDialogProps) => {
|
||||
const { workspaceService, docService, subscriptionService } = useServices({
|
||||
export const AIOnboardingEdgeless = () => {
|
||||
const { docService, subscriptionService } = useServices({
|
||||
WorkspaceService,
|
||||
DocService,
|
||||
SubscriptionService,
|
||||
@@ -61,8 +58,6 @@ export const AIOnboardingEdgeless = ({
|
||||
const aiSubscription = useLiveData(subscriptionService.subscription.ai$);
|
||||
const settingModalOpen = useAtomValue(openSettingModalAtom);
|
||||
const timeoutRef = useRef<ReturnType<typeof setTimeout>>();
|
||||
const isCloud =
|
||||
workspaceService.workspace.flavour === WorkspaceFlavour.AFFINE_CLOUD;
|
||||
|
||||
const setSettingModal = useSetAtom(openSettingModalAtom);
|
||||
|
||||
@@ -87,7 +82,6 @@ export const AIOnboardingEdgeless = ({
|
||||
if (generalAIOnboardingOpened) return;
|
||||
if (notifyId) return;
|
||||
if (mode !== 'edgeless') return;
|
||||
if (!isCloud) return;
|
||||
clearTimeout(timeoutRef.current);
|
||||
timeoutRef.current = setTimeout(() => {
|
||||
// try to close local onboarding
|
||||
@@ -101,13 +95,13 @@ export const AIOnboardingEdgeless = ({
|
||||
iconColor: cssVar('processingColor'),
|
||||
thumb: <EdgelessOnboardingAnimation />,
|
||||
alignMessage: 'icon',
|
||||
onDismiss,
|
||||
onDismiss: () => toggleEdgelessAIOnboarding(false),
|
||||
footer: (
|
||||
<FlexWrapper marginTop={8} justifyContent="flex-end" gap="12px">
|
||||
<Button
|
||||
onClick={() => {
|
||||
notify.dismiss(id);
|
||||
onDismiss();
|
||||
toggleEdgelessAIOnboarding(false);
|
||||
}}
|
||||
type="plain"
|
||||
className={styles.actionButton}
|
||||
@@ -123,7 +117,7 @@ export const AIOnboardingEdgeless = ({
|
||||
onClick={() => {
|
||||
goToPricingPlans();
|
||||
notify.dismiss(id);
|
||||
onDismiss();
|
||||
toggleEdgelessAIOnboarding(false);
|
||||
}}
|
||||
>
|
||||
<span className={styles.purchaseButtonText}>
|
||||
@@ -142,10 +136,8 @@ export const AIOnboardingEdgeless = ({
|
||||
aiSubscription,
|
||||
generalAIOnboardingOpened,
|
||||
goToPricingPlans,
|
||||
isCloud,
|
||||
mode,
|
||||
notifyId,
|
||||
onDismiss,
|
||||
settingModalOpen,
|
||||
t,
|
||||
]);
|
||||
|
||||
@@ -11,11 +11,11 @@ import { useAtom } from 'jotai';
|
||||
import type { ReactNode } from 'react';
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
|
||||
import { toggleGeneralAIOnboarding } from './apis';
|
||||
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 };
|
||||
type Translate = ReturnType<typeof useAFFiNEI18N>;
|
||||
@@ -82,9 +82,7 @@ function prefetchVideos() {
|
||||
prefetched = true;
|
||||
}
|
||||
|
||||
export const AIOnboardingGeneral = ({
|
||||
onDismiss,
|
||||
}: BaseAIOnboardingDialogProps) => {
|
||||
export const AIOnboardingGeneral = () => {
|
||||
const { authService, subscriptionService } = useServices({
|
||||
AuthService,
|
||||
SubscriptionService,
|
||||
@@ -111,8 +109,8 @@ export const AIOnboardingGeneral = ({
|
||||
}, []);
|
||||
const closeAndDismiss = useCallback(() => {
|
||||
showAIOnboardingGeneral$.next(false);
|
||||
onDismiss();
|
||||
}, [onDismiss]);
|
||||
toggleGeneralAIOnboarding(false);
|
||||
}, []);
|
||||
const goToPricingPlans = useCallback(() => {
|
||||
setSettingModal({
|
||||
open: true,
|
||||
@@ -190,7 +188,7 @@ export const AIOnboardingGeneral = ({
|
||||
open={open}
|
||||
onOpenChange={v => {
|
||||
showAIOnboardingGeneral$.next(v);
|
||||
if (!v && isLast) onDismiss();
|
||||
if (!v && isLast) toggleGeneralAIOnboarding(false);
|
||||
}}
|
||||
contentOptions={{ className: styles.dialog }}
|
||||
overlayOptions={{ className: baseStyles.dialogOverlay }}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Suspense, useCallback, useState } from 'react';
|
||||
import { Suspense, useCallback, useEffect, useState } from 'react';
|
||||
|
||||
import { AIOnboardingEdgeless } from './edgeless.dialog';
|
||||
import { AIOnboardingGeneral } from './general.dialog';
|
||||
@@ -8,6 +8,15 @@ import { AIOnboardingType } from './type';
|
||||
const useDismiss = (key: AIOnboardingType) => {
|
||||
const [dismiss, setDismiss] = useState(localStorage.getItem(key) === 'true');
|
||||
|
||||
useEffect(() => {
|
||||
const handler = (e: StorageEvent) => {
|
||||
if (e.key !== key) return;
|
||||
setDismiss(localStorage.getItem(key) === 'true');
|
||||
};
|
||||
window.addEventListener('storage', handler);
|
||||
return () => window.removeEventListener('storage', handler);
|
||||
}, [key]);
|
||||
|
||||
const onDismiss = useCallback(() => {
|
||||
setDismiss(true);
|
||||
localStorage.setItem(key, 'true');
|
||||
@@ -17,32 +26,21 @@ const useDismiss = (key: AIOnboardingType) => {
|
||||
};
|
||||
|
||||
export const WorkspaceAIOnboarding = () => {
|
||||
const [dismissGeneral, onDismissGeneral] = useDismiss(
|
||||
AIOnboardingType.GENERAL
|
||||
);
|
||||
const [dismissLocal, onDismissLocal] = useDismiss(AIOnboardingType.LOCAL);
|
||||
const [dismissGeneral] = useDismiss(AIOnboardingType.GENERAL);
|
||||
const [dismissLocal] = useDismiss(AIOnboardingType.LOCAL);
|
||||
|
||||
return (
|
||||
<Suspense>
|
||||
{dismissGeneral ? null : (
|
||||
<AIOnboardingGeneral onDismiss={onDismissGeneral} />
|
||||
)}
|
||||
|
||||
{dismissLocal ? null : <AIOnboardingLocal onDismiss={onDismissLocal} />}
|
||||
{dismissGeneral ? null : <AIOnboardingGeneral />}
|
||||
{dismissLocal ? null : <AIOnboardingLocal />}
|
||||
</Suspense>
|
||||
);
|
||||
};
|
||||
|
||||
export const PageAIOnboarding = () => {
|
||||
const [dismissEdgeless, onDismissEdgeless] = useDismiss(
|
||||
AIOnboardingType.EDGELESS
|
||||
);
|
||||
const [dismissEdgeless] = useDismiss(AIOnboardingType.EDGELESS);
|
||||
|
||||
return (
|
||||
<Suspense>
|
||||
{dismissEdgeless ? null : (
|
||||
<AIOnboardingEdgeless onDismiss={onDismissEdgeless} />
|
||||
)}
|
||||
</Suspense>
|
||||
<Suspense>{dismissEdgeless ? null : <AIOnboardingEdgeless />}</Suspense>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -10,9 +10,9 @@ import { useLiveData, useService } from '@toeverything/infra';
|
||||
import { cssVar } from '@toeverything/theme';
|
||||
import { useEffect, useRef } from 'react';
|
||||
|
||||
import { toggleLocalAIOnboarding } from './apis';
|
||||
import * as styles from './local.dialog.css';
|
||||
import { edgelessNotifyId$, localNotifyId$ } from './state';
|
||||
import type { BaseAIOnboardingDialogProps } from './type';
|
||||
|
||||
const LocalOnboardingAnimation = () => {
|
||||
return (
|
||||
@@ -63,9 +63,7 @@ const FooterActions = ({ onDismiss }: { onDismiss: () => void }) => {
|
||||
);
|
||||
};
|
||||
|
||||
export const AIOnboardingLocal = ({
|
||||
onDismiss,
|
||||
}: BaseAIOnboardingDialogProps) => {
|
||||
export const AIOnboardingLocal = () => {
|
||||
const t = useAFFiNEI18N();
|
||||
const authService = useService(AuthService);
|
||||
const notifyId = useLiveData(localNotifyId$);
|
||||
@@ -94,11 +92,11 @@ export const AIOnboardingLocal = ({
|
||||
iconColor: cssVar('brandColor'),
|
||||
thumb: <LocalOnboardingAnimation />,
|
||||
alignMessage: 'icon',
|
||||
onDismiss,
|
||||
onDismiss: () => toggleLocalAIOnboarding(false),
|
||||
footer: (
|
||||
<FooterActions
|
||||
onDismiss={() => {
|
||||
onDismiss();
|
||||
toggleLocalAIOnboarding(false);
|
||||
notify.dismiss(id);
|
||||
}}
|
||||
/>
|
||||
@@ -109,7 +107,7 @@ export const AIOnboardingLocal = ({
|
||||
);
|
||||
localNotifyId$.next(id);
|
||||
}, 1000);
|
||||
}, [notSignedIn, notifyId, onDismiss, t]);
|
||||
}, [notSignedIn, notifyId, t]);
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
export interface BaseAIOnboardingDialogProps {
|
||||
onDismiss: () => void;
|
||||
}
|
||||
export enum AIOnboardingType {
|
||||
GENERAL = 'dismissAiOnboarding',
|
||||
EDGELESS = 'dismissAiOnboardingEdgeless',
|
||||
|
||||
Reference in New Issue
Block a user