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
28 changed files with 338 additions and 522 deletions

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 && (