mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-02-12 04:18:54 +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:
1
packages/common/env/src/global.ts
vendored
1
packages/common/env/src/global.ts
vendored
@@ -16,6 +16,7 @@ export const runtimeFlagsSchema = z.object({
|
|||||||
enableTestProperties: z.boolean(),
|
enableTestProperties: z.boolean(),
|
||||||
enableBroadcastChannelProvider: z.boolean(),
|
enableBroadcastChannelProvider: z.boolean(),
|
||||||
enableDebugPage: z.boolean(),
|
enableDebugPage: z.boolean(),
|
||||||
|
githubUrl: z.string(),
|
||||||
changelogUrl: z.string(),
|
changelogUrl: z.string(),
|
||||||
downloadUrl: z.string(),
|
downloadUrl: z.string(),
|
||||||
// see: tools/workers
|
// see: tools/workers
|
||||||
|
|||||||
Binary file not shown.
@@ -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'),
|
|
||||||
});
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
export * from './tour-modal';
|
|
||||||
Binary file not shown.
@@ -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;
|
|
||||||
@@ -1,2 +1,3 @@
|
|||||||
export * from './confirm-modal';
|
export * from './confirm-modal';
|
||||||
export * from './modal';
|
export * from './modal';
|
||||||
|
export * from './overlay-modal';
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import { Button } from '../button';
|
|||||||
import { Input, type InputProps } from '../input';
|
import { Input, type InputProps } from '../input';
|
||||||
import { ConfirmModal, type ConfirmModalProps } from './confirm-modal';
|
import { ConfirmModal, type ConfirmModalProps } from './confirm-modal';
|
||||||
import { Modal, type ModalProps } from './modal';
|
import { Modal, type ModalProps } from './modal';
|
||||||
|
import { OverlayModal, type OverlayModalProps } from './overlay-modal';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
title: 'UI/Modal',
|
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> =
|
export const Confirm: StoryFn<ModalProps> =
|
||||||
ConfirmModalTemplate.bind(undefined);
|
ConfirmModalTemplate.bind(undefined);
|
||||||
|
|
||||||
|
export const Overlay: StoryFn<ModalProps> =
|
||||||
|
OverlayModalTemplate.bind(undefined);
|
||||||
|
|||||||
@@ -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'),
|
||||||
|
},
|
||||||
|
});
|
||||||
102
packages/frontend/component/src/ui/modal/overlay-modal.tsx
Normal file
102
packages/frontend/component/src/ui/modal/overlay-modal.tsx
Normal 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>
|
||||||
|
);
|
||||||
|
});
|
||||||
@@ -23,6 +23,7 @@ export function getRuntimeConfig(buildFlags: BuildFlags): RuntimeConfig {
|
|||||||
enableTestProperties: false,
|
enableTestProperties: false,
|
||||||
enableBroadcastChannelProvider: true,
|
enableBroadcastChannelProvider: true,
|
||||||
enableDebugPage: true,
|
enableDebugPage: true,
|
||||||
|
githubUrl: 'https://github.com/toeverything/AFFiNE',
|
||||||
changelogUrl: 'https://affine.pro/what-is-new',
|
changelogUrl: 'https://affine.pro/what-is-new',
|
||||||
downloadUrl: 'https://affine.pro/download',
|
downloadUrl: 'https://affine.pro/download',
|
||||||
imageProxyUrl: '/api/worker/image-proxy',
|
imageProxyUrl: '/api/worker/image-proxy',
|
||||||
@@ -64,6 +65,7 @@ export function getRuntimeConfig(buildFlags: BuildFlags): RuntimeConfig {
|
|||||||
enableTestProperties: true,
|
enableTestProperties: true,
|
||||||
enableBroadcastChannelProvider: true,
|
enableBroadcastChannelProvider: true,
|
||||||
enableDebugPage: true,
|
enableDebugPage: true,
|
||||||
|
githubUrl: 'https://github.com/toeverything/AFFiNE',
|
||||||
changelogUrl: 'https://github.com/toeverything/AFFiNE/releases',
|
changelogUrl: 'https://github.com/toeverything/AFFiNE/releases',
|
||||||
downloadUrl: 'https://affine.pro/download',
|
downloadUrl: 'https://affine.pro/download',
|
||||||
imageProxyUrl: '/api/worker/image-proxy',
|
imageProxyUrl: '/api/worker/image-proxy',
|
||||||
|
|||||||
BIN
packages/frontend/core/public/static/githubStar.mp4
Normal file
BIN
packages/frontend/core/public/static/githubStar.mp4
Normal file
Binary file not shown.
BIN
packages/frontend/core/public/static/newIssue.mp4
Normal file
BIN
packages/frontend/core/public/static/newIssue.mp4
Normal file
Binary file not shown.
@@ -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<
|
export const guideDownloadClientTipAtom = atom<
|
||||||
Guide['downloadClientTip'],
|
Guide['downloadClientTip'],
|
||||||
|
|||||||
@@ -10,10 +10,11 @@ import type { SettingProps } from '../components/affine/setting-modal';
|
|||||||
export const openWorkspacesModalAtom = atom(false);
|
export const openWorkspacesModalAtom = atom(false);
|
||||||
export const openCreateWorkspaceModalAtom = atom<CreateWorkspaceMode>(false);
|
export const openCreateWorkspaceModalAtom = atom<CreateWorkspaceMode>(false);
|
||||||
export const openQuickSearchModalAtom = atom(false);
|
export const openQuickSearchModalAtom = atom(false);
|
||||||
export const openOnboardingModalAtom = atom(false);
|
|
||||||
export const openSignOutModalAtom = atom(false);
|
export const openSignOutModalAtom = atom(false);
|
||||||
export const openPaymentDisableAtom = atom(false);
|
export const openPaymentDisableAtom = atom(false);
|
||||||
export const openQuotaModalAtom = atom(false);
|
export const openQuotaModalAtom = atom(false);
|
||||||
|
export const openStarAFFiNEModalAtom = atom(false);
|
||||||
|
export const openIssueFeedbackModalAtom = atom(false);
|
||||||
|
|
||||||
export type SettingAtom = Pick<
|
export type SettingAtom = Pick<
|
||||||
SettingProps,
|
SettingProps,
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import type { useAFFiNEI18N } from '@affine/i18n/hooks';
|
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 { registerAffineCommand } from '@toeverything/infra/command';
|
||||||
import type { createStore } from 'jotai';
|
import type { createStore } from 'jotai';
|
||||||
|
|
||||||
import { openOnboardingModalAtom, openSettingModalAtom } from '../atoms';
|
import { openSettingModalAtom } from '../atoms';
|
||||||
|
|
||||||
export function registerAffineHelpCommands({
|
export function registerAffineHelpCommands({
|
||||||
t,
|
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 () => {
|
return () => {
|
||||||
unsubs.forEach(unsub => unsub());
|
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 { useAFFiNEI18N } from '@affine/i18n/hooks';
|
||||||
import { memo, useCallback, useEffect, useState } from 'react';
|
import { memo, useCallback, useEffect, useState } from 'react';
|
||||||
|
|
||||||
import { useAppConfigStorage } from '../../../hooks/use-app-config-storage';
|
import { useAppConfigStorage } from '../../../hooks/use-app-config-storage';
|
||||||
import Thumb from './assets/thumb';
|
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'] = {
|
const overlayOptions: ModalProps['overlayOptions'] = {
|
||||||
style: {
|
style: {
|
||||||
background:
|
background:
|
||||||
@@ -36,7 +33,6 @@ export const WorkspaceGuideModal = memo(function WorkspaceGuideModal() {
|
|||||||
}, [open]);
|
}, [open]);
|
||||||
|
|
||||||
const gotIt = useCallback(() => {
|
const gotIt = useCallback(() => {
|
||||||
setOpen(false);
|
|
||||||
setDismiss(true);
|
setDismiss(true);
|
||||||
}, [setDismiss]);
|
}, [setDismiss]);
|
||||||
|
|
||||||
@@ -47,28 +43,23 @@ export const WorkspaceGuideModal = memo(function WorkspaceGuideModal() {
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal
|
<OverlayModal
|
||||||
withoutCloseButton
|
|
||||||
contentOptions={contentOptions}
|
|
||||||
overlayOptions={overlayOptions}
|
|
||||||
open={open}
|
open={open}
|
||||||
width={400}
|
|
||||||
onOpenChange={onOpenChange}
|
onOpenChange={onOpenChange}
|
||||||
>
|
topImage={<Thumb />}
|
||||||
<Thumb />
|
title={t['com.affine.onboarding.workspace-guide.title']()}
|
||||||
<div className={styles.title}>
|
description={t['com.affine.onboarding.workspace-guide.content']()}
|
||||||
{t['com.affine.onboarding.workspace-guide.title']()}
|
onConfirm={gotIt}
|
||||||
</div>
|
overlayOptions={overlayOptions}
|
||||||
<div className={styles.content}>
|
withoutCancelButton
|
||||||
{t['com.affine.onboarding.workspace-guide.content']()}
|
confirmButtonOptions={{
|
||||||
</div>
|
style: {
|
||||||
<div className={styles.footer}>
|
fontWeight: 500,
|
||||||
<Button type="primary" size="large" onClick={gotIt}>
|
},
|
||||||
<span className={styles.gotItBtn}>
|
type: 'primary',
|
||||||
{t['com.affine.onboarding.workspace-guide.got-it']()}
|
size: 'large',
|
||||||
</span>
|
}}
|
||||||
</Button>
|
confirmText={t['com.affine.onboarding.workspace-guide.got-it']()}
|
||||||
</div>
|
/>
|
||||||
</Modal>
|
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,8 +1,13 @@
|
|||||||
import { WorkspaceDetailSkeleton } from '@affine/component/setting-components';
|
import { WorkspaceDetailSkeleton } from '@affine/component/setting-components';
|
||||||
import { Modal, type ModalProps } from '@affine/component/ui/modal';
|
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 { ContactWithUsIcon } from '@blocksuite/icons';
|
||||||
import type { WorkspaceMetadata } from '@toeverything/infra';
|
import type { WorkspaceMetadata } from '@toeverything/infra';
|
||||||
|
import { useSetAtom } from 'jotai';
|
||||||
import { debounce } from 'lodash-es';
|
import { debounce } from 'lodash-es';
|
||||||
import { Suspense, useCallback, useLayoutEffect, useRef } from 'react';
|
import { Suspense, useCallback, useLayoutEffect, useRef } from 'react';
|
||||||
|
|
||||||
@@ -37,7 +42,6 @@ export const SettingModal = ({
|
|||||||
onSettingClick,
|
onSettingClick,
|
||||||
...modalProps
|
...modalProps
|
||||||
}: SettingProps) => {
|
}: SettingProps) => {
|
||||||
const t = useAFFiNEI18N();
|
|
||||||
const loginStatus = useCurrentLoginStatus();
|
const loginStatus = useCurrentLoginStatus();
|
||||||
|
|
||||||
const modalContentRef = useRef<HTMLDivElement>(null);
|
const modalContentRef = useRef<HTMLDivElement>(null);
|
||||||
@@ -79,6 +83,16 @@ export const SettingModal = ({
|
|||||||
},
|
},
|
||||||
[onSettingClick]
|
[onSettingClick]
|
||||||
);
|
);
|
||||||
|
const setOpenIssueFeedbackModal = useSetAtom(openIssueFeedbackModalAtom);
|
||||||
|
const setOpenStarAFFiNEModal = useSetAtom(openStarAFFiNEModalAtom);
|
||||||
|
|
||||||
|
const handleOpenIssueFeedbackModal = useCallback(() => {
|
||||||
|
setOpenIssueFeedbackModal(true);
|
||||||
|
}, [setOpenIssueFeedbackModal]);
|
||||||
|
|
||||||
|
const handleOpenStarAFFiNEModal = useCallback(() => {
|
||||||
|
setOpenStarAFFiNEModal(true);
|
||||||
|
}, [setOpenStarAFFiNEModal]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
@@ -126,17 +140,24 @@ export const SettingModal = ({
|
|||||||
</Suspense>
|
</Suspense>
|
||||||
</div>
|
</div>
|
||||||
<div className={style.footer}>
|
<div className={style.footer}>
|
||||||
<a
|
<ContactWithUsIcon fontSize={16} />
|
||||||
href="https://community.affine.pro/home"
|
<Trans
|
||||||
target="_blank"
|
i18nKey={'com.affine.settings.suggestion-2'}
|
||||||
rel="noreferrer"
|
components={{
|
||||||
className={style.suggestionLink}
|
1: (
|
||||||
>
|
<span
|
||||||
<span className={style.suggestionLinkIcon}>
|
className={style.link}
|
||||||
<ContactWithUsIcon width="16" height="16" />
|
onClick={handleOpenStarAFFiNEModal}
|
||||||
</span>
|
/>
|
||||||
{t['com.affine.settings.suggestion']()}
|
),
|
||||||
</a>
|
2: (
|
||||||
|
<span
|
||||||
|
className={style.link}
|
||||||
|
onClick={handleOpenIssueFeedbackModal}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
}}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -42,4 +42,12 @@ export const footer = style({
|
|||||||
justifyContent: 'center',
|
justifyContent: 'center',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
paddingBottom: '20px',
|
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 { Tooltip } from '@affine/component/ui/tooltip';
|
||||||
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
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 { useSetAtom } from 'jotai/react';
|
||||||
import { useAtomValue } from 'jotai/react';
|
import { useAtomValue } from 'jotai/react';
|
||||||
import { useCallback, useState } from 'react';
|
import { useCallback, useState } from 'react';
|
||||||
import { useParams } from 'react-router-dom';
|
import { useParams } from 'react-router-dom';
|
||||||
|
|
||||||
import { openOnboardingModalAtom, openSettingModalAtom } from '../../../atoms';
|
import { openSettingModalAtom } from '../../../atoms';
|
||||||
import { currentModeAtom } from '../../../atoms/mode';
|
import { currentModeAtom } from '../../../atoms/mode';
|
||||||
import type { SettingProps } from '../../affine/setting-modal';
|
import type { SettingProps } from '../../affine/setting-modal';
|
||||||
import { ContactIcon, HelpIcon, KeyboardIcon } from './icons';
|
import { ContactIcon, HelpIcon, KeyboardIcon } from './icons';
|
||||||
@@ -22,14 +22,14 @@ const DEFAULT_SHOW_LIST: IslandItemNames[] = [
|
|||||||
'contact',
|
'contact',
|
||||||
'shortcuts',
|
'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;
|
const showList = environment.isDesktop ? DESKTOP_SHOW_LIST : DEFAULT_SHOW_LIST;
|
||||||
|
|
||||||
export const HelpIsland = () => {
|
export const HelpIsland = () => {
|
||||||
const mode = useAtomValue(currentModeAtom);
|
const mode = useAtomValue(currentModeAtom);
|
||||||
const setOpenOnboarding = useSetAtom(openOnboardingModalAtom);
|
|
||||||
const setOpenSettingModalAtom = useSetAtom(openSettingModalAtom);
|
const setOpenSettingModalAtom = useSetAtom(openSettingModalAtom);
|
||||||
const [spread, setShowSpread] = useState(false);
|
const [spread, setShowSpread] = useState(false);
|
||||||
const t = useAFFiNEI18N();
|
const t = useAFFiNEI18N();
|
||||||
@@ -102,22 +102,6 @@ export const HelpIsland = () => {
|
|||||||
</StyledIconWrapper>
|
</StyledIconWrapper>
|
||||||
</Tooltip>
|
</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>
|
</StyledAnimateWrapper>
|
||||||
|
|
||||||
{spread ? (
|
{spread ? (
|
||||||
|
|||||||
@@ -47,11 +47,6 @@ const TmpDisableAffineCloudModal = lazy(() =>
|
|||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
const OnboardingModal = lazy(() =>
|
|
||||||
import('../components/affine/onboarding-modal').then(module => ({
|
|
||||||
default: module.OnboardingModal,
|
|
||||||
}))
|
|
||||||
);
|
|
||||||
const WorkspaceGuideModal = lazy(() =>
|
const WorkspaceGuideModal = lazy(() =>
|
||||||
import('../components/affine/onboarding/workspace-guide-modal').then(
|
import('../components/affine/onboarding/workspace-guide-modal').then(
|
||||||
module => ({
|
module => ({
|
||||||
@@ -77,6 +72,16 @@ const CloudQuotaModal = lazy(() =>
|
|||||||
default: module.CloudQuotaModal,
|
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 = () => {
|
export const Setting = () => {
|
||||||
const [{ open, workspaceMetadata, activeTab }, setOpenSettingModalAtom] =
|
const [{ open, workspaceMetadata, activeTab }, setOpenSettingModalAtom] =
|
||||||
@@ -174,11 +179,8 @@ export function CurrentWorkspaceModals() {
|
|||||||
onOpenChange={setOpenDisableCloudAlertModal}
|
onOpenChange={setOpenDisableCloudAlertModal}
|
||||||
/>
|
/>
|
||||||
</Suspense>
|
</Suspense>
|
||||||
{environment.isDesktop && (
|
<StarAFFiNEModal />
|
||||||
<Suspense>
|
<IssueFeedbackModal />
|
||||||
<OnboardingModal />
|
|
||||||
</Suspense>
|
|
||||||
)}
|
|
||||||
<WorkspaceGuideModal />
|
<WorkspaceGuideModal />
|
||||||
{currentWorkspace ? <Setting /> : null}
|
{currentWorkspace ? <Setting /> : null}
|
||||||
{currentWorkspace?.flavour === WorkspaceFlavour.LOCAL && (
|
{currentWorkspace?.flavour === WorkspaceFlavour.LOCAL && (
|
||||||
|
|||||||
@@ -887,6 +887,7 @@
|
|||||||
"com.affine.settings.storage.description": "Check or change storage location",
|
"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.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": "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": "Translucent UI on the sidebar",
|
||||||
"com.affine.settings.translucent-style-description": "Use transparency effect on the sidebar.",
|
"com.affine.settings.translucent-style-description": "Use transparency effect on the sidebar.",
|
||||||
"com.affine.settings.workspace": "Workspace",
|
"com.affine.settings.workspace": "Workspace",
|
||||||
@@ -1070,5 +1071,13 @@
|
|||||||
"com.affine.journal.conflict-show-more": "{{count}} more articles",
|
"com.affine.journal.conflict-show-more": "{{count}} more articles",
|
||||||
"com.affine.journal.app-sidebar-title": "Journals",
|
"com.affine.journal.app-sidebar-title": "Journals",
|
||||||
"com.affine.journal.cmdk.append-to-today": "Append to Journal",
|
"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"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,10 +25,6 @@ test('new page', async ({ page, workspace }) => {
|
|||||||
// macOS only
|
// macOS only
|
||||||
// if (platform() === 'darwin') {
|
// if (platform() === 'darwin') {
|
||||||
test('app sidebar router forward/back', async ({ page }) => {
|
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
|
// create pages
|
||||||
await page.waitForTimeout(500);
|
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 }) => {
|
test('windows only check', async ({ page }) => {
|
||||||
const windowOnlyUI = page.locator('[data-platform-target=win32]');
|
const windowOnlyUI = page.locator('[data-platform-target=win32]');
|
||||||
if (process.platform === 'win32') {
|
if (process.platform === 'win32') {
|
||||||
|
|||||||
@@ -36,9 +36,6 @@ export const test = base.extend<{
|
|||||||
}>({
|
}>({
|
||||||
page: async ({ electronApp }, use) => {
|
page: async ({ electronApp }, use) => {
|
||||||
const page = await electronApp.firstWindow();
|
const page = await electronApp.firstWindow();
|
||||||
await page.getByTestId('onboarding-modal-close-button').click({
|
|
||||||
delay: 100,
|
|
||||||
});
|
|
||||||
// wait for blocksuite to be loaded
|
// wait for blocksuite to be loaded
|
||||||
await page.waitForSelector('v-line');
|
await page.waitForSelector('v-line');
|
||||||
if (enableCoverage) {
|
if (enableCoverage) {
|
||||||
|
|||||||
@@ -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',
|
|
||||||
};
|
|
||||||
Reference in New Issue
Block a user