mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-02-13 21:05:19 +00:00
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:
@@ -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'],
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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
|
||||
/>
|
||||
);
|
||||
};
|
||||
@@ -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} />
|
||||
);
|
||||
});
|
||||
@@ -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']()}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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',
|
||||
});
|
||||
|
||||
@@ -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
|
||||
/>
|
||||
);
|
||||
};
|
||||
@@ -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 ? (
|
||||
|
||||
@@ -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 && (
|
||||
|
||||
Reference in New Issue
Block a user