feat(core): add starAFFiNE and issueFeedback modal (#5718)

close TOV-482

https://github.com/toeverything/AFFiNE/assets/102217452/da1f74bc-4b8d-4d7f-987d-f53da98d92fe
This commit is contained in:
JimmFly 2024-02-20 12:50:51 +00:00
parent 6fad241350
commit 4068e7aeff
No known key found for this signature in database
GPG Key ID: 14A6F56854E1BED7
28 changed files with 338 additions and 522 deletions

View File

@ -16,6 +16,7 @@ export const runtimeFlagsSchema = z.object({
enableTestProperties: z.boolean(),
enableBroadcastChannelProvider: z.boolean(),
enableDebugPage: z.boolean(),
githubUrl: z.string(),
changelogUrl: z.string(),
downloadUrl: z.string(),
// see: tools/workers

View File

@ -1,192 +0,0 @@
import { cssVar } from '@toeverything/theme';
import { keyframes, style } from '@vanilla-extract/css';
export const modalStyle = style({
width: '100%',
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
backgroundColor: cssVar('backgroundSecondaryColor'),
borderRadius: '16px',
overflow: 'hidden',
});
export const titleContainerStyle = style({
width: 'calc(100% - 72px)',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
position: 'relative',
height: '60px',
overflow: 'hidden',
});
export const titleStyle = style({
fontSize: cssVar('fontH6'),
fontWeight: '600',
marginTop: '12px',
position: 'absolute',
marginBottom: '12px',
});
const slideToLeft = keyframes({
'0%': {
transform: 'translateX(0)',
opacity: 1,
},
'100%': {
transform: 'translateX(-300px)',
opacity: 0,
},
});
const slideToRight = keyframes({
'0%': {
transform: 'translateX(0)',
opacity: 1,
},
'100%': {
transform: 'translateX(300px)',
opacity: 0,
},
});
const slideFormLeft = keyframes({
'0%': {
transform: 'translateX(300px)',
opacity: 0,
},
'100%': {
transform: 'translateX(0)',
opacity: 1,
},
});
const slideFormRight = keyframes({
'0%': {
transform: 'translateX(-300px)',
opacity: 0,
},
'100%': {
transform: 'translateX(0)',
opacity: 1,
},
});
export const formSlideToLeftStyle = style({
animation: `${slideFormLeft} 0.3s ease-in-out forwards`,
});
export const formSlideToRightStyle = style({
animation: `${slideFormRight} 0.3s ease-in-out forwards`,
});
export const slideToLeftStyle = style({
animation: `${slideToLeft} 0.3s ease-in-out forwards`,
});
export const slideToRightStyle = style({
animation: `${slideToRight} 0.3s ease-in-out forwards`,
});
export const containerStyle = style({
width: '100%',
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between',
});
export const videoContainerStyle = style({
height: '300px',
width: 'calc(100% - 72px)',
display: 'flex',
alignItems: 'center',
flexGrow: 1,
justifyContent: 'space-between',
position: 'relative',
overflow: 'hidden',
});
export const videoSlideStyle = style({
width: '100%',
position: 'absolute',
top: 0,
display: 'flex',
justifyContent: 'center',
});
export const videoStyle = style({
position: 'absolute',
objectFit: 'fill',
height: '300px',
border: `1px solid ${cssVar('borderColor')}`,
transition: 'opacity 0.5s ease-in-out',
});
const fadeIn = keyframes({
'0%': {
transform: 'translateX(300px)',
},
'100%': {
transform: 'translateX(0)',
},
});
export const videoActiveStyle = style({
animation: `${fadeIn} 0.5s ease-in-out forwards`,
opacity: 0,
});
export const arrowStyle = style({
wordBreak: 'break-all',
wordWrap: 'break-word',
width: '36px',
fontSize: '32px',
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
height: '240px',
flexGrow: 0.2,
cursor: 'pointer',
});
export const descriptionContainerStyle = style({
width: 'calc(100% - 112px)',
height: '100px',
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
position: 'relative',
overflow: 'hidden',
});
export const descriptionStyle = style({
marginTop: '15px',
width: '100%',
display: 'flex',
fontSize: cssVar('fontSm'),
lineHeight: '18px',
position: 'absolute',
});
export const tabStyle = style({
width: '40px',
height: '40px',
content: '""',
margin: '40px 10px 40px 0',
transition: 'all 0.15s ease-in-out',
position: 'relative',
cursor: 'pointer',
':hover': {
opacity: 1,
},
'::after': {
content: '""',
position: 'absolute',
bottom: '20px',
left: '0',
width: '100%',
height: '2px',
background: cssVar('textPrimaryColor'),
transition: 'all 0.15s ease-in-out',
opacity: 0.2,
cursor: 'pointer',
},
});
export const tabActiveStyle = style({
'::after': {
opacity: 1,
},
});
export const tabContainerStyle = style({
width: '100%',
marginTop: '20px',
position: 'relative',
height: '2px',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
});
export const buttonDisableStyle = style({
cursor: 'not-allowed',
color: cssVar('textDisableColor'),
});

View File

@ -1 +0,0 @@
export * from './tour-modal';

View File

@ -1,160 +0,0 @@
/// <reference types="../../type.d.ts" />
import { useAFFiNEI18N } from '@affine/i18n/hooks';
import { ArrowLeftSmallIcon, ArrowRightSmallIcon } from '@blocksuite/icons';
import clsx from 'clsx';
import { useState } from 'react';
import { Modal, type ModalProps } from '../../ui/modal';
import editingVideo from './editingVideo.mp4';
import {
arrowStyle,
buttonDisableStyle,
containerStyle,
descriptionContainerStyle,
descriptionStyle,
formSlideToLeftStyle,
formSlideToRightStyle,
modalStyle,
slideToLeftStyle,
slideToRightStyle,
tabActiveStyle,
tabContainerStyle,
tabStyle,
titleContainerStyle,
titleStyle,
videoContainerStyle,
videoSlideStyle,
videoStyle,
} from './index.css';
import switchVideo from './switchVideo.mp4';
export const TourModal = (props: ModalProps) => {
const t = useAFFiNEI18N();
const [step, setStep] = useState(-1);
return (
<Modal
width={545}
contentOptions={{
['data-testid' as string]: 'onboarding-modal',
style: {
minHeight: '480px',
padding: 0,
},
}}
overlayOptions={{
style: {
background: 'transparent',
},
}}
closeButtonOptions={{
// @ts-expect-error - fix upstream type
'data-testid': 'onboarding-modal-close-button',
}}
{...props}
>
<div className={modalStyle}>
<div className={titleContainerStyle}>
{step !== -1 && (
<div
className={clsx(titleStyle, {
[slideToRightStyle]: step === 0,
[formSlideToLeftStyle]: step === 1,
})}
>
{t['com.affine.onboarding.title2']()}
</div>
)}
<div
className={clsx(titleStyle, {
[slideToLeftStyle]: step === 1,
[formSlideToRightStyle]: step === 0,
})}
>
{t['com.affine.onboarding.title1']()}
</div>
</div>
<div className={containerStyle}>
<div
className={clsx(arrowStyle, { [buttonDisableStyle]: step !== 1 })}
onClick={() => step === 1 && setStep(0)}
data-testid="onboarding-modal-pre-button"
>
<ArrowLeftSmallIcon />
</div>
<div className={videoContainerStyle}>
<div className={videoSlideStyle}>
{step !== -1 && (
<video
autoPlay
muted
loop
className={clsx(videoStyle, {
[slideToRightStyle]: step === 0,
[formSlideToLeftStyle]: step === 1,
})}
data-testid="onboarding-modal-editing-video"
>
<source src={editingVideo} type="video/mp4" />
</video>
)}
<video
autoPlay
muted
loop
className={clsx(videoStyle, {
[slideToLeftStyle]: step === 1,
[formSlideToRightStyle]: step === 0,
})}
data-testid="onboarding-modal-switch-video"
>
<source src={switchVideo} type="video/mp4" />
</video>
</div>
</div>
<div
className={clsx(arrowStyle, { [buttonDisableStyle]: step === 1 })}
onClick={() => setStep(1)}
data-testid="onboarding-modal-next-button"
>
<ArrowRightSmallIcon />
</div>
</div>
<ul className={tabContainerStyle}>
<li
className={clsx(tabStyle, {
[tabActiveStyle]: step !== 1,
})}
onClick={() => setStep(0)}
></li>
<li
className={clsx(tabStyle, { [tabActiveStyle]: step === 1 })}
onClick={() => setStep(1)}
></li>
</ul>
<div className={descriptionContainerStyle}>
{step !== -1 && (
<div
className={clsx(descriptionStyle, {
[slideToRightStyle]: step === 0,
[formSlideToLeftStyle]: step === 1,
})}
>
{t['com.affine.onboarding.videoDescription2']()}
</div>
)}
<div
className={clsx(descriptionStyle, {
[slideToLeftStyle]: step === 1,
[formSlideToRightStyle]: step === 0,
})}
>
{t['com.affine.onboarding.videoDescription1']()}
</div>
</div>
</div>
</Modal>
);
};
export default TourModal;

View File

@ -1,2 +1,3 @@
export * from './confirm-modal';
export * from './modal';
export * from './overlay-modal';

View File

@ -5,6 +5,7 @@ import { Button } from '../button';
import { Input, type InputProps } from '../input';
import { ConfirmModal, type ConfirmModalProps } from './confirm-modal';
import { Modal, type ModalProps } from './modal';
import { OverlayModal, type OverlayModalProps } from './overlay-modal';
export default {
title: 'UI/Modal',
@ -65,5 +66,38 @@ const ConfirmModalTemplate: StoryFn<ConfirmModalProps> = () => {
);
};
const OverlayModalTemplate: StoryFn<OverlayModalProps> = () => {
const [open, setOpen] = useState(false);
return (
<>
<Button onClick={() => setOpen(true)}>Open Overlay Modal</Button>
<OverlayModal
open={open}
onOpenChange={setOpen}
title="Modal Title"
description="Modal description"
confirmButtonOptions={{
type: 'primary',
}}
topImage={
<div
style={{
width: '400px',
height: '300px',
background: '#66ccff',
opacity: 0.1,
color: '#fff',
}}
></div>
}
/>
</>
);
};
export const Confirm: StoryFn<ModalProps> =
ConfirmModalTemplate.bind(undefined);
export const Overlay: StoryFn<ModalProps> =
OverlayModalTemplate.bind(undefined);

View File

@ -0,0 +1,37 @@
import { cssVar } from '@toeverything/theme';
import { style } from '@vanilla-extract/css';
export const title = style({
padding: '20px 24px 8px 24px',
fontSize: cssVar('fontH6'),
fontFamily: cssVar('fontFamily'),
fontWeight: '600',
lineHeight: '26px',
});
export const content = style({
padding: '0px 24px 8px',
fontSize: cssVar('fontBase'),
lineHeight: '24px',
fontWeight: 400,
});
export const footer = style({
padding: '20px 24px',
display: 'flex',
justifyContent: 'flex-end',
gap: '20px',
});
export const gotItBtn = style({
fontWeight: 500,
});
export const buttonText = style({
color: cssVar('pureWhite'),
textDecoration: 'none',
cursor: 'pointer',
':visited': {
color: cssVar('pureWhite'),
},
});

View File

@ -0,0 +1,102 @@
import { DialogTrigger } from '@radix-ui/react-dialog';
import { cssVar } from '@toeverything/theme';
import { memo, useCallback } from 'react';
import { Link } from 'react-router-dom';
import { Button, type ButtonProps } from '../button';
import { Modal, type ModalProps } from './modal';
import * as styles from './overlay-modal.css';
const defaultContentOptions: ModalProps['contentOptions'] = {
style: {
padding: 0,
overflow: 'hidden',
boxShadow: cssVar('menuShadow'),
},
};
const defaultOverlayOptions: ModalProps['overlayOptions'] = {
style: {
background: cssVar('white80'),
backdropFilter: 'blur(2px)',
},
};
export interface OverlayModalProps extends ModalProps {
to?: string;
external?: boolean;
topImage?: React.ReactNode;
confirmText?: string;
confirmButtonOptions?: ButtonProps;
onConfirm?: () => void;
cancelText?: string;
cancelButtonOptions?: ButtonProps;
withoutCancelButton?: boolean;
}
export const OverlayModal = memo(function OverlayModal({
open,
topImage,
onOpenChange,
title,
description,
onConfirm,
to,
external,
confirmButtonOptions,
cancelButtonOptions,
withoutCancelButton,
contentOptions = defaultContentOptions,
overlayOptions = defaultOverlayOptions,
// FIXME: we need i18n
cancelText = 'Cancel',
confirmText = 'Confirm',
width = 400,
}: OverlayModalProps) {
const handleConfirm = useCallback(() => {
onOpenChange?.(false);
onConfirm?.();
}, [onOpenChange, onConfirm]);
return (
<Modal
contentOptions={contentOptions}
overlayOptions={overlayOptions}
open={open}
width={width}
onOpenChange={onOpenChange}
withoutCloseButton
>
{topImage}
<div className={styles.title}>{title}</div>
<div className={styles.content}>{description}</div>
<div className={styles.footer}>
{!withoutCancelButton ? (
<DialogTrigger asChild>
<Button {...cancelButtonOptions}>{cancelText}</Button>
</DialogTrigger>
) : null}
{to ? (
external ? (
//FIXME: we need a more standardized way to implement this link with other click events
<a href={to} target="_blank" rel="noreferrer">
<Button onClick={handleConfirm} {...confirmButtonOptions}>
{confirmText}
</Button>
</a>
) : (
<Link to={to}>
<Button onClick={handleConfirm} {...confirmButtonOptions}>
{confirmText}
</Button>
</Link>
)
) : (
<Button onClick={handleConfirm} {...confirmButtonOptions}>
{confirmText}
</Button>
)}
</div>
</Modal>
);
});

View File

@ -23,6 +23,7 @@ export function getRuntimeConfig(buildFlags: BuildFlags): RuntimeConfig {
enableTestProperties: false,
enableBroadcastChannelProvider: true,
enableDebugPage: true,
githubUrl: 'https://github.com/toeverything/AFFiNE',
changelogUrl: 'https://affine.pro/what-is-new',
downloadUrl: 'https://affine.pro/download',
imageProxyUrl: '/api/worker/image-proxy',
@ -64,6 +65,7 @@ export function getRuntimeConfig(buildFlags: BuildFlags): RuntimeConfig {
enableTestProperties: true,
enableBroadcastChannelProvider: true,
enableDebugPage: true,
githubUrl: 'https://github.com/toeverything/AFFiNE',
changelogUrl: 'https://github.com/toeverything/AFFiNE/releases',
downloadUrl: 'https://affine.pro/download',
imageProxyUrl: '/api/worker/image-proxy',

Binary file not shown.

Binary file not shown.

View File

@ -55,21 +55,6 @@ export const guideChangeLogAtom = atom<
}));
}
);
export const guideOnboardingAtom = atom<
Guide['onBoarding'],
[open: boolean],
void
>(
get => {
return get(guidePrimitiveAtom).onBoarding;
},
(_, set, open) => {
set(guidePrimitiveAtom, tips => ({
...tips,
onBoarding: open,
}));
}
);
export const guideDownloadClientTipAtom = atom<
Guide['downloadClientTip'],

View File

@ -10,10 +10,11 @@ import type { SettingProps } from '../components/affine/setting-modal';
export const openWorkspacesModalAtom = atom(false);
export const openCreateWorkspaceModalAtom = atom<CreateWorkspaceMode>(false);
export const openQuickSearchModalAtom = atom(false);
export const openOnboardingModalAtom = atom(false);
export const openSignOutModalAtom = atom(false);
export const openPaymentDisableAtom = atom(false);
export const openQuotaModalAtom = atom(false);
export const openStarAFFiNEModalAtom = atom(false);
export const openIssueFeedbackModalAtom = atom(false);
export type SettingAtom = Pick<
SettingProps,

View File

@ -1,9 +1,9 @@
import type { useAFFiNEI18N } from '@affine/i18n/hooks';
import { ContactWithUsIcon, NewIcon, UserGuideIcon } from '@blocksuite/icons';
import { ContactWithUsIcon, NewIcon } from '@blocksuite/icons';
import { registerAffineCommand } from '@toeverything/infra/command';
import type { createStore } from 'jotai';
import { openOnboardingModalAtom, openSettingModalAtom } from '../atoms';
import { openSettingModalAtom } from '../atoms';
export function registerAffineHelpCommands({
t,
@ -39,18 +39,6 @@ export function registerAffineHelpCommands({
},
})
);
unsubs.push(
registerAffineCommand({
id: 'affine:help-getting-started',
category: 'affine:help',
icon: <UserGuideIcon />,
label: t['com.affine.cmdk.affine.getting-started'](),
preconditionStrategy: () => environment.isDesktop,
run() {
store.set(openOnboardingModalAtom, true);
},
})
);
return () => {
unsubs.forEach(unsub => unsub());

View File

@ -0,0 +1,35 @@
import { OverlayModal } from '@affine/component';
import { openIssueFeedbackModalAtom } from '@affine/core/atoms';
import { useAFFiNEI18N } from '@affine/i18n/hooks';
import { useAtom } from 'jotai';
export const IssueFeedbackModal = () => {
const t = useAFFiNEI18N();
const [open, setOpen] = useAtom(openIssueFeedbackModalAtom);
return (
<OverlayModal
open={open}
topImage={
<video
width={400}
height={300}
style={{ objectFit: 'cover' }}
src={'/static/newIssue.mp4'}
autoPlay
loop
/>
}
title={t['com.affine.issue-feedback.title']()}
onOpenChange={setOpen}
description={t['com.affine.issue-feedback.description']()}
cancelText={t['com.affine.issue-feedback.cancel']()}
to={`${runtimeConfig.githubUrl}/issues/new/choose`}
confirmText={t['com.affine.issue-feedback.confirm']()}
confirmButtonOptions={{
type: 'primary',
}}
external
/>
);
};

View File

@ -1,23 +0,0 @@
import { TourModal } from '@affine/component/tour-modal';
import { useAtom } from 'jotai';
import { memo, useCallback } from 'react';
import { openOnboardingModalAtom } from '../../atoms';
import { guideOnboardingAtom } from '../../atoms/guide';
export const OnboardingModal = memo(function OnboardingModal() {
const [open, setOpen] = useAtom(openOnboardingModalAtom);
const [guideOpen, setShowOnboarding] = useAtom(guideOnboardingAtom);
const onOpenChange = useCallback(
(open: boolean) => {
if (open) return;
setShowOnboarding(false);
setOpen(false);
},
[setOpen, setShowOnboarding]
);
return (
<TourModal open={!open ? guideOpen : open} onOpenChange={onOpenChange} />
);
});

View File

@ -1,14 +1,11 @@
import { Button, Modal, type ModalProps } from '@affine/component';
import { OverlayModal } from '@affine/component';
import type { ModalProps } from '@affine/component/ui/modal';
import { useAFFiNEI18N } from '@affine/i18n/hooks';
import { memo, useCallback, useEffect, useState } from 'react';
import { useAppConfigStorage } from '../../../hooks/use-app-config-storage';
import Thumb from './assets/thumb';
import * as styles from './workspace-guide-modal.css';
const contentOptions: ModalProps['contentOptions'] = {
style: { padding: 0, overflow: 'hidden' },
};
const overlayOptions: ModalProps['overlayOptions'] = {
style: {
background:
@ -36,7 +33,6 @@ export const WorkspaceGuideModal = memo(function WorkspaceGuideModal() {
}, [open]);
const gotIt = useCallback(() => {
setOpen(false);
setDismiss(true);
}, [setDismiss]);
@ -47,28 +43,23 @@ export const WorkspaceGuideModal = memo(function WorkspaceGuideModal() {
}, []);
return (
<Modal
withoutCloseButton
contentOptions={contentOptions}
overlayOptions={overlayOptions}
<OverlayModal
open={open}
width={400}
onOpenChange={onOpenChange}
>
<Thumb />
<div className={styles.title}>
{t['com.affine.onboarding.workspace-guide.title']()}
</div>
<div className={styles.content}>
{t['com.affine.onboarding.workspace-guide.content']()}
</div>
<div className={styles.footer}>
<Button type="primary" size="large" onClick={gotIt}>
<span className={styles.gotItBtn}>
{t['com.affine.onboarding.workspace-guide.got-it']()}
</span>
</Button>
</div>
</Modal>
topImage={<Thumb />}
title={t['com.affine.onboarding.workspace-guide.title']()}
description={t['com.affine.onboarding.workspace-guide.content']()}
onConfirm={gotIt}
overlayOptions={overlayOptions}
withoutCancelButton
confirmButtonOptions={{
style: {
fontWeight: 500,
},
type: 'primary',
size: 'large',
}}
confirmText={t['com.affine.onboarding.workspace-guide.got-it']()}
/>
);
});

View File

@ -1,8 +1,13 @@
import { WorkspaceDetailSkeleton } from '@affine/component/setting-components';
import { Modal, type ModalProps } from '@affine/component/ui/modal';
import { useAFFiNEI18N } from '@affine/i18n/hooks';
import {
openIssueFeedbackModalAtom,
openStarAFFiNEModalAtom,
} from '@affine/core/atoms';
import { Trans } from '@affine/i18n';
import { ContactWithUsIcon } from '@blocksuite/icons';
import type { WorkspaceMetadata } from '@toeverything/infra';
import { useSetAtom } from 'jotai';
import { debounce } from 'lodash-es';
import { Suspense, useCallback, useLayoutEffect, useRef } from 'react';
@ -37,7 +42,6 @@ export const SettingModal = ({
onSettingClick,
...modalProps
}: SettingProps) => {
const t = useAFFiNEI18N();
const loginStatus = useCurrentLoginStatus();
const modalContentRef = useRef<HTMLDivElement>(null);
@ -79,6 +83,16 @@ export const SettingModal = ({
},
[onSettingClick]
);
const setOpenIssueFeedbackModal = useSetAtom(openIssueFeedbackModalAtom);
const setOpenStarAFFiNEModal = useSetAtom(openStarAFFiNEModalAtom);
const handleOpenIssueFeedbackModal = useCallback(() => {
setOpenIssueFeedbackModal(true);
}, [setOpenIssueFeedbackModal]);
const handleOpenStarAFFiNEModal = useCallback(() => {
setOpenStarAFFiNEModal(true);
}, [setOpenStarAFFiNEModal]);
return (
<Modal
@ -126,17 +140,24 @@ export const SettingModal = ({
</Suspense>
</div>
<div className={style.footer}>
<a
href="https://community.affine.pro/home"
target="_blank"
rel="noreferrer"
className={style.suggestionLink}
>
<span className={style.suggestionLinkIcon}>
<ContactWithUsIcon width="16" height="16" />
</span>
{t['com.affine.settings.suggestion']()}
</a>
<ContactWithUsIcon fontSize={16} />
<Trans
i18nKey={'com.affine.settings.suggestion-2'}
components={{
1: (
<span
className={style.link}
onClick={handleOpenStarAFFiNEModal}
/>
),
2: (
<span
className={style.link}
onClick={handleOpenIssueFeedbackModal}
/>
),
}}
/>
</div>
</div>
</div>

View File

@ -42,4 +42,12 @@ export const footer = style({
justifyContent: 'center',
alignItems: 'center',
paddingBottom: '20px',
gap: '4px',
fontSize: cssVar('fontXs'),
flexWrap: 'wrap',
});
export const link = style({
color: cssVar('linkColor'),
cursor: 'pointer',
});

View File

@ -0,0 +1,35 @@
import { OverlayModal } from '@affine/component';
import { openStarAFFiNEModalAtom } from '@affine/core/atoms';
import { useAFFiNEI18N } from '@affine/i18n/hooks';
import { useAtom } from 'jotai';
export const StarAFFiNEModal = () => {
const t = useAFFiNEI18N();
const [open, setOpen] = useAtom(openStarAFFiNEModalAtom);
return (
<OverlayModal
open={open}
topImage={
<video
width={400}
height={300}
style={{ objectFit: 'cover' }}
src={'/static/gitHubStar.mp4'}
autoPlay
loop
/>
}
title={t['com.affine.star-affine.title']()}
onOpenChange={setOpen}
description={t['com.affine.star-affine.description']()}
cancelText={t['com.affine.star-affine.cancel']()}
to={runtimeConfig.githubUrl}
confirmButtonOptions={{
type: 'primary',
}}
confirmText={t['com.affine.star-affine.confirm']()}
external
/>
);
};

View File

@ -1,12 +1,12 @@
import { Tooltip } from '@affine/component/ui/tooltip';
import { useAFFiNEI18N } from '@affine/i18n/hooks';
import { CloseIcon, NewIcon, UserGuideIcon } from '@blocksuite/icons';
import { CloseIcon, NewIcon } from '@blocksuite/icons';
import { useSetAtom } from 'jotai/react';
import { useAtomValue } from 'jotai/react';
import { useCallback, useState } from 'react';
import { useParams } from 'react-router-dom';
import { openOnboardingModalAtom, openSettingModalAtom } from '../../../atoms';
import { openSettingModalAtom } from '../../../atoms';
import { currentModeAtom } from '../../../atoms/mode';
import type { SettingProps } from '../../affine/setting-modal';
import { ContactIcon, HelpIcon, KeyboardIcon } from './icons';
@ -22,14 +22,14 @@ const DEFAULT_SHOW_LIST: IslandItemNames[] = [
'contact',
'shortcuts',
];
const DESKTOP_SHOW_LIST: IslandItemNames[] = [...DEFAULT_SHOW_LIST, 'guide'];
type IslandItemNames = 'whatNew' | 'contact' | 'shortcuts' | 'guide';
const DESKTOP_SHOW_LIST: IslandItemNames[] = [...DEFAULT_SHOW_LIST];
type IslandItemNames = 'whatNew' | 'contact' | 'shortcuts';
const showList = environment.isDesktop ? DESKTOP_SHOW_LIST : DEFAULT_SHOW_LIST;
export const HelpIsland = () => {
const mode = useAtomValue(currentModeAtom);
const setOpenOnboarding = useSetAtom(openOnboardingModalAtom);
const setOpenSettingModalAtom = useSetAtom(openSettingModalAtom);
const [spread, setShowSpread] = useState(false);
const t = useAFFiNEI18N();
@ -102,22 +102,6 @@ export const HelpIsland = () => {
</StyledIconWrapper>
</Tooltip>
)}
{showList.includes('guide') && (
<Tooltip
content={t['com.affine.helpIsland.gettingStarted']()}
side="left"
>
<StyledIconWrapper
data-testid="easy-guide"
onClick={() => {
setShowSpread(false);
setOpenOnboarding(true);
}}
>
<UserGuideIcon />
</StyledIconWrapper>
</Tooltip>
)}
</StyledAnimateWrapper>
{spread ? (

View File

@ -47,11 +47,6 @@ const TmpDisableAffineCloudModal = lazy(() =>
)
);
const OnboardingModal = lazy(() =>
import('../components/affine/onboarding-modal').then(module => ({
default: module.OnboardingModal,
}))
);
const WorkspaceGuideModal = lazy(() =>
import('../components/affine/onboarding/workspace-guide-modal').then(
module => ({
@ -77,6 +72,16 @@ const CloudQuotaModal = lazy(() =>
default: module.CloudQuotaModal,
}))
);
const StarAFFiNEModal = lazy(() =>
import('../components/affine/star-affine-modal').then(module => ({
default: module.StarAFFiNEModal,
}))
);
const IssueFeedbackModal = lazy(() =>
import('../components/affine/issue-feedback-modal').then(module => ({
default: module.IssueFeedbackModal,
}))
);
export const Setting = () => {
const [{ open, workspaceMetadata, activeTab }, setOpenSettingModalAtom] =
@ -174,11 +179,8 @@ export function CurrentWorkspaceModals() {
onOpenChange={setOpenDisableCloudAlertModal}
/>
</Suspense>
{environment.isDesktop && (
<Suspense>
<OnboardingModal />
</Suspense>
)}
<StarAFFiNEModal />
<IssueFeedbackModal />
<WorkspaceGuideModal />
{currentWorkspace ? <Setting /> : null}
{currentWorkspace?.flavour === WorkspaceFlavour.LOCAL && (

View File

@ -887,6 +887,7 @@
"com.affine.settings.storage.description": "Check or change storage location",
"com.affine.settings.storage.description-alt": "Check or change storage location. Click path to edit location.",
"com.affine.settings.suggestion": "Need more customization options? Tell us in the community.",
"com.affine.settings.suggestion-2": "Love our app? <1>Star us on GitHub</1> and <2>create issues</2> for your valuable feedback!",
"com.affine.settings.translucent-style": "Translucent UI on the sidebar",
"com.affine.settings.translucent-style-description": "Use transparency effect on the sidebar.",
"com.affine.settings.workspace": "Workspace",
@ -1070,5 +1071,13 @@
"com.affine.journal.conflict-show-more": "{{count}} more articles",
"com.affine.journal.app-sidebar-title": "Journals",
"com.affine.journal.cmdk.append-to-today": "Append to Journal",
"com.affine.editor.reference-not-found": "Linked page not found"
"com.affine.editor.reference-not-found": "Linked page not found",
"com.affine.star-affine.title": "Star Us on GitHub",
"com.affine.star-affine.description": "Are you finding our app useful and enjoyable? We'd love your support to keep improving! A great way to help us out is by giving us a star on GitHub. This simple action can make a big difference and helps us continue to deliver the best experience for you.",
"com.affine.star-affine.confirm": "Star on GitHub",
"com.affine.star-affine.cancel": "Maybe Later",
"com.affine.issue-feedback.title": "Share Your Feedback on GitHub",
"com.affine.issue-feedback.description": "Got feedback? We're all ears! Create an issue on GitHub to let us know your thoughts and suggestions",
"com.affine.issue-feedback.confirm": "Create Issue on GitHub",
"com.affine.issue-feedback.cancel": "Maybe Later"
}

View File

@ -25,10 +25,6 @@ test('new page', async ({ page, workspace }) => {
// macOS only
// if (platform() === 'darwin') {
test('app sidebar router forward/back', async ({ page }) => {
await page.getByTestId('help-island').click();
await page.getByTestId('easy-guide').click();
await page.getByTestId('onboarding-modal-next-button').click();
await page.getByTestId('onboarding-modal-close-button').click();
{
// create pages
await page.waitForTimeout(500);
@ -128,25 +124,6 @@ test('app theme', async ({ page, electronApp }) => {
}
});
test('affine onboarding button', async ({ page }) => {
await page.getByTestId('help-island').click();
await page.getByTestId('easy-guide').click();
const onboardingModal = page.locator('[data-testid=onboarding-modal]');
await expect(onboardingModal).toBeVisible();
const switchVideo = page.locator(
'[data-testid=onboarding-modal-switch-video]'
);
await expect(switchVideo).toBeVisible();
await page.getByTestId('onboarding-modal-next-button').click();
const editingVideo = page.locator(
'[data-testid=onboarding-modal-editing-video]'
);
await expect(editingVideo).toBeVisible();
await page.getByTestId('onboarding-modal-close-button').click();
await expect(onboardingModal).toBeHidden();
});
test('windows only check', async ({ page }) => {
const windowOnlyUI = page.locator('[data-platform-target=win32]');
if (process.platform === 'win32') {

View File

@ -36,9 +36,6 @@ export const test = base.extend<{
}>({
page: async ({ electronApp }, use) => {
const page = await electronApp.firstWindow();
await page.getByTestId('onboarding-modal-close-button').click({
delay: 100,
});
// wait for blocksuite to be loaded
await page.waitForSelector('v-line');
if (enableCoverage) {

View File

@ -1,18 +0,0 @@
/* deepscan-disable USELESS_ARROW_FUNC_BIND */
import { TourModal } from '@affine/component/tour-modal';
import type { Meta, StoryFn } from '@storybook/react';
export default {
title: 'AFFiNE/TourModal',
component: TourModal,
parameters: {
chromatic: { disableSnapshot: true },
},
} satisfies Meta;
export const Basic: StoryFn = () => {
return <TourModal open={true} />;
};
Basic.args = {
logoSrc: '/imgs/affine-text-logo.png',
};