mirror of
https://github.com/toeverything/AFFiNE.git
synced 2024-11-30 05:34:21 +03:00
feat(core): onboarding paper enter animation (#5248)
This commit is contained in:
parent
15dd20ef48
commit
841385666e
@ -50,6 +50,7 @@
|
||||
"@toeverything/theme": "^0.7.20",
|
||||
"@vanilla-extract/css": "^1.13.0",
|
||||
"@vanilla-extract/dynamic": "^2.0.3",
|
||||
"animejs": "^3.2.2",
|
||||
"async-call-rpc": "^6.3.1",
|
||||
"bytes": "^3.1.2",
|
||||
"clsx": "^2.0.0",
|
||||
@ -93,6 +94,7 @@
|
||||
"@svgr/webpack": "^8.1.0",
|
||||
"@swc/core": "^1.3.93",
|
||||
"@testing-library/react": "^14.0.0",
|
||||
"@types/animejs": "^3",
|
||||
"@types/bytes": "^3.1.3",
|
||||
"@types/image-blob-reduce": "^4.1.3",
|
||||
"@types/lodash-es": "^4.17.9",
|
||||
|
@ -0,0 +1,228 @@
|
||||
import { article, articleWrapper, text, title } from '../curve-paper/paper.css';
|
||||
import type { ArticleId, ArticleOption } from '../types';
|
||||
|
||||
const ids = ['0', '1', '2', '3', '4'] as Array<ArticleId>;
|
||||
|
||||
/** locate paper */
|
||||
const paperLocations = {
|
||||
'0': {
|
||||
x: 0,
|
||||
y: 0,
|
||||
},
|
||||
'1': {
|
||||
x: -240,
|
||||
y: -100,
|
||||
},
|
||||
'2': {
|
||||
x: 240,
|
||||
y: -100,
|
||||
},
|
||||
'3': {
|
||||
x: -480,
|
||||
y: 40,
|
||||
},
|
||||
'4': {
|
||||
x: 480,
|
||||
y: 50,
|
||||
},
|
||||
};
|
||||
|
||||
/** paper enter animation config */
|
||||
const paperEnterAnimationOriginal = {
|
||||
'0': {
|
||||
curveCenter: 4,
|
||||
curve: 292,
|
||||
delay: 800,
|
||||
fromZ: 1230,
|
||||
fromX: -76,
|
||||
fromY: 100,
|
||||
fromRotateX: 185,
|
||||
fromRotateY: -166,
|
||||
fromRotateZ: 252,
|
||||
toZ: 0,
|
||||
// toX: 12,
|
||||
// toY: -30,
|
||||
toRotateZ: 6,
|
||||
duration: '2s',
|
||||
easing: 'ease',
|
||||
},
|
||||
'1': {
|
||||
curveCenter: 4,
|
||||
curve: 280,
|
||||
delay: 0,
|
||||
fromZ: 3697,
|
||||
fromX: 25,
|
||||
fromY: -93,
|
||||
fromRotateX: 331,
|
||||
fromRotateY: 360,
|
||||
fromRotateZ: -257,
|
||||
toZ: 0,
|
||||
// toX: -18,
|
||||
// toY: -28,
|
||||
toRotateZ: -8,
|
||||
duration: '2s',
|
||||
easing: 'ease',
|
||||
},
|
||||
'2': {
|
||||
curveCenter: 3,
|
||||
curve: 660,
|
||||
delay: 1700,
|
||||
fromZ: 57379,
|
||||
fromX: 2,
|
||||
fromY: -77,
|
||||
fromRotateX: 0,
|
||||
fromRotateY: 0,
|
||||
fromRotateZ: 0,
|
||||
toZ: 0,
|
||||
// toX: -3,
|
||||
// toY: -21,
|
||||
toRotateZ: 2,
|
||||
duration: '2s',
|
||||
easing: 'ease',
|
||||
},
|
||||
'3': {
|
||||
curveCenter: 4,
|
||||
curve: 260,
|
||||
delay: 1500,
|
||||
fromZ: 4303,
|
||||
fromX: -37,
|
||||
fromY: -100,
|
||||
fromRotateX: 360,
|
||||
fromRotateY: 360,
|
||||
fromRotateZ: 8,
|
||||
toZ: 0,
|
||||
// toX: -30,
|
||||
// toY: -9,
|
||||
toRotateZ: 2,
|
||||
duration: '2s',
|
||||
easing: 'ease',
|
||||
},
|
||||
'4': {
|
||||
curveCenter: 3,
|
||||
curve: 270,
|
||||
delay: 1571,
|
||||
fromZ: 1876,
|
||||
fromX: 65,
|
||||
fromY: 48,
|
||||
fromRotateX: 101,
|
||||
fromRotateY: 188,
|
||||
fromRotateZ: -200,
|
||||
toZ: 0,
|
||||
// toX: 24,
|
||||
// toY: -2,
|
||||
toRotateZ: 8,
|
||||
duration: '2s',
|
||||
easing: 'ease',
|
||||
},
|
||||
};
|
||||
|
||||
export type PaperEnterAnimation = (typeof paperEnterAnimationOriginal)[0];
|
||||
export const paperEnterAnimations = paperEnterAnimationOriginal as Record<
|
||||
any,
|
||||
PaperEnterAnimation
|
||||
>;
|
||||
|
||||
/** Brief content */
|
||||
const paperBriefs = {
|
||||
'0': (
|
||||
<div className={articleWrapper}>
|
||||
<article className={article}>
|
||||
<h1 className={title}>Breath of the Wild: Redefining Game Design</h1>
|
||||
<p className={text}>
|
||||
“With all the time you spend watching TV,” he tells me, “you could
|
||||
have written a novel by now.” It’s hard to disagree with the sentiment
|
||||
— writing a novel is undoubtedly a better use of time than watching TV
|
||||
— but what about the hidden assumption? Such comments imply that time
|
||||
is “fungible” — that time spent watching TV can just as easily be
|
||||
spent writing a novel. And sadly, that’s just not the case.
|
||||
</p>
|
||||
</article>
|
||||
</div>
|
||||
),
|
||||
'1': (
|
||||
<div className={articleWrapper}>
|
||||
<article className={article}>
|
||||
<h1 className={title}>Learning with earning with retrieval practice</h1>
|
||||
<p className={text}>
|
||||
Are there any specific techniques to make the process of learning more
|
||||
effective?
|
||||
</p>
|
||||
<p className={text}>
|
||||
Students often re-read, underline, or highlight materials, thinking
|
||||
that it will help them learn better. But, the best method for really
|
||||
turning information into long-term memory is to use what is called
|
||||
‘retrieval practice’.
|
||||
</p>
|
||||
</article>
|
||||
</div>
|
||||
),
|
||||
'2': (
|
||||
<div className={articleWrapper}>
|
||||
<article className={article}>
|
||||
<h1 className={title}>
|
||||
Local-first software
|
||||
<br />
|
||||
You own your data, in spite of the cloud
|
||||
</h1>
|
||||
<p className={text}>
|
||||
Cloud apps like Google Docs and Trello are popular because they enable
|
||||
real-time collaboration with colleagues, and they make it easy for us
|
||||
to access our work from all of our devices. However, by centralizing
|
||||
data storage on servers, cloud apps also take away ownership and
|
||||
agency from users. If a service shuts down, the software stops
|
||||
functioning, and data created with that software is lost.
|
||||
</p>
|
||||
</article>
|
||||
</div>
|
||||
),
|
||||
'3': (
|
||||
<div className={articleWrapper}>
|
||||
<article className={article}>
|
||||
<h1 className={title}>More Is Different</h1>
|
||||
<p className={text}>
|
||||
Broken symmetry and the nature of the hierarchical structure of
|
||||
science
|
||||
</p>
|
||||
<p className={text}>
|
||||
The reductionist hypothesis may still be a topic for controversy among
|
||||
philosophers, but among the great majority of active scientists I
|
||||
think it is accepted without questions. The workings of our minds and
|
||||
bodies, and of all the animate or inanimate matter of which we have
|
||||
any detailed knowledge, are assumed to be controlled by the same set
|
||||
of fundamental laws, which except under certain extreme conditions we
|
||||
feel we know pretty well.
|
||||
</p>
|
||||
</article>
|
||||
</div>
|
||||
),
|
||||
'4': (
|
||||
<div className={articleWrapper}>
|
||||
<article className={article}>
|
||||
<h1 className={title}>HOWTO: Be more productive</h1>
|
||||
<p className={text}>
|
||||
“With all the time you spend watching TV,” he tells me, “you could
|
||||
have written a novel by now.” It’s hard to disagree with the sentiment
|
||||
— writing a novel is undoubtedly a better use of time than watching TV
|
||||
— but what about the hidden assumption? Such comments imply that time
|
||||
is “fungible” — that time spent watching TV can just as easily be
|
||||
spent writing a novel. And sadly, that’s just not the case.
|
||||
</p>
|
||||
</article>
|
||||
</div>
|
||||
),
|
||||
};
|
||||
|
||||
export const articles: Record<ArticleId, ArticleOption> = ids.reduce(
|
||||
(acc, id) => {
|
||||
return {
|
||||
...acc,
|
||||
[id]: {
|
||||
id,
|
||||
location: paperLocations[id],
|
||||
enterOptions: paperEnterAnimations[id],
|
||||
brief: paperBriefs[id],
|
||||
} satisfies ArticleOption,
|
||||
};
|
||||
},
|
||||
{} as Record<ArticleId, ArticleOption>
|
||||
);
|
@ -0,0 +1,94 @@
|
||||
import { createVar, style } from '@vanilla-extract/css';
|
||||
|
||||
import { onboardingVars } from '../style.css';
|
||||
|
||||
export const paperWidthVar = createVar();
|
||||
export const paperHeightVar = createVar();
|
||||
|
||||
export const paper = style({
|
||||
vars: {
|
||||
[paperWidthVar]: onboardingVars.paper.w,
|
||||
[paperHeightVar]: onboardingVars.paper.h,
|
||||
},
|
||||
|
||||
width: paperWidthVar,
|
||||
height: paperHeightVar,
|
||||
position: 'relative',
|
||||
});
|
||||
|
||||
export const segment = style({
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
background: onboardingVars.paper.bg,
|
||||
position: 'absolute',
|
||||
top: `calc(var(--segments-up) / var(--segments) * 100%)`,
|
||||
|
||||
selectors: {
|
||||
['&[data-root="true"]']: {
|
||||
height: `calc(1 / var(--segments) * 100%)`,
|
||||
},
|
||||
['&[data-direction="up"]']: {
|
||||
top: 'unset',
|
||||
bottom: `100%`,
|
||||
transformOrigin: 'bottom',
|
||||
},
|
||||
['&[data-direction="down"]']: {
|
||||
top: `100%`,
|
||||
transformOrigin: 'top',
|
||||
},
|
||||
['&[data-top="true"]']: {
|
||||
borderTopLeftRadius: onboardingVars.paper.r,
|
||||
borderTopRightRadius: onboardingVars.paper.r,
|
||||
},
|
||||
['&[data-bottom="true"]']: {
|
||||
borderBottomLeftRadius: onboardingVars.paper.r,
|
||||
borderBottomRightRadius: onboardingVars.paper.r,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
export const contentWrapper = style({
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
overflow: 'hidden',
|
||||
position: 'absolute',
|
||||
});
|
||||
|
||||
export const content = style({
|
||||
padding: '16px',
|
||||
overflow: 'hidden',
|
||||
fontFamily: 'var(--affine-font-family)',
|
||||
|
||||
selectors: {
|
||||
[`${contentWrapper} > &`]: {
|
||||
position: 'absolute',
|
||||
width: paperWidthVar,
|
||||
height: paperHeightVar,
|
||||
top: `calc((var(--index)) * -100%)`,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
export const articleWrapper = style({
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
overflow: 'hidden',
|
||||
});
|
||||
|
||||
export const article = style({
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
gap: '12px',
|
||||
color: onboardingVars.paper.textColor,
|
||||
});
|
||||
|
||||
export const title = style({
|
||||
fontSize: '18px',
|
||||
fontWeight: 600,
|
||||
lineHeight: '26px',
|
||||
});
|
||||
export const text = style({
|
||||
fontSize: '14px',
|
||||
fontWeight: 400,
|
||||
lineHeight: '22px',
|
||||
});
|
@ -0,0 +1,23 @@
|
||||
import type { ReactNode } from 'react';
|
||||
|
||||
import * as styles from './paper.css';
|
||||
import { Segments } from './segments';
|
||||
|
||||
export interface PaperProps {
|
||||
segments: number;
|
||||
centerIndex: number;
|
||||
content: ReactNode;
|
||||
}
|
||||
|
||||
export const Paper = (props: PaperProps) => {
|
||||
return (
|
||||
<div className={styles.paper}>
|
||||
<Segments
|
||||
level={props.segments}
|
||||
root={true}
|
||||
index={props.segments}
|
||||
{...props}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
@ -0,0 +1,43 @@
|
||||
import type { PropsWithChildren, ReactNode } from 'react';
|
||||
|
||||
import * as styles from './paper.css';
|
||||
|
||||
export interface SegmentProps extends PropsWithChildren {
|
||||
index: number;
|
||||
level?: number;
|
||||
direction?: 'up' | 'down';
|
||||
content: ReactNode;
|
||||
|
||||
isTop?: boolean;
|
||||
isBottom?: boolean;
|
||||
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
export function Segment({
|
||||
children,
|
||||
index,
|
||||
direction,
|
||||
content,
|
||||
level,
|
||||
isTop,
|
||||
isBottom,
|
||||
...attrs
|
||||
}: SegmentProps) {
|
||||
const style = { '--index': index } as React.CSSProperties;
|
||||
return (
|
||||
<div
|
||||
className={styles.segment}
|
||||
data-direction={direction}
|
||||
data-level={level}
|
||||
data-bottom={(direction === 'down' && level === 1) || isBottom}
|
||||
data-top={(direction === 'up' && level === 1) || isTop}
|
||||
{...attrs}
|
||||
>
|
||||
<div className={styles.contentWrapper} style={style}>
|
||||
<div className={styles.content}>{content}</div>
|
||||
</div>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
}
|
@ -0,0 +1,70 @@
|
||||
import { type ReactNode } from 'react';
|
||||
|
||||
import { Segment } from './segment';
|
||||
|
||||
export interface SegmentsProps {
|
||||
level?: number;
|
||||
direction?: 'up' | 'down';
|
||||
index: number;
|
||||
root?: boolean;
|
||||
centerIndex: number;
|
||||
segments: number;
|
||||
content: ReactNode;
|
||||
}
|
||||
|
||||
export function Segments({
|
||||
level,
|
||||
direction,
|
||||
root,
|
||||
index,
|
||||
centerIndex,
|
||||
segments,
|
||||
content,
|
||||
}: SegmentsProps) {
|
||||
if (!level) return null;
|
||||
|
||||
const inherits = { centerIndex, segments, content };
|
||||
|
||||
if (root) {
|
||||
const up = centerIndex;
|
||||
const down = segments - up - 1;
|
||||
const vars = {
|
||||
'--segments': segments,
|
||||
'--segments-up': up,
|
||||
'--segments-down': down,
|
||||
};
|
||||
return (
|
||||
<Segment
|
||||
data-root={true}
|
||||
style={vars}
|
||||
index={up}
|
||||
content={content}
|
||||
isTop={up === 0}
|
||||
isBottom={down === 0}
|
||||
>
|
||||
<Segments index={up - 1} level={up} direction="up" {...inherits} />
|
||||
<Segments index={up + 1} level={down} direction="down" {...inherits} />
|
||||
</Segment>
|
||||
);
|
||||
}
|
||||
|
||||
const children =
|
||||
level === 1 ? null : (
|
||||
<Segments
|
||||
direction={direction}
|
||||
index={direction === 'up' ? index - 1 : index + 1}
|
||||
level={level - 1}
|
||||
{...inherits}
|
||||
/>
|
||||
);
|
||||
return (
|
||||
<Segment
|
||||
direction={direction}
|
||||
index={index}
|
||||
content={content}
|
||||
level={level}
|
||||
>
|
||||
{children}
|
||||
</Segment>
|
||||
);
|
||||
}
|
@ -0,0 +1,44 @@
|
||||
import type { CSSProperties } from 'react';
|
||||
|
||||
import { articles } from './articles';
|
||||
import { PaperSteps } from './paper-steps';
|
||||
import * as styles from './style.css';
|
||||
|
||||
interface OnboardingProps {
|
||||
onOpenApp?: () => void;
|
||||
}
|
||||
|
||||
export const Onboarding = (_: OnboardingProps) => {
|
||||
return (
|
||||
<div className={styles.onboarding} data-is-desktop={environment.isDesktop}>
|
||||
<div className={styles.offsetOrigin}>
|
||||
{Object.entries(articles).map(([id, article]) => {
|
||||
const { enterOptions, location } = article;
|
||||
const style = {
|
||||
'--fromX': `${enterOptions.fromX}vw`,
|
||||
'--fromY': `${enterOptions.fromY}vh`,
|
||||
'--fromZ': `${enterOptions.fromZ}px`,
|
||||
'--toZ': `${enterOptions.toZ}px`,
|
||||
'--fromRotateX': `${enterOptions.fromRotateX}deg`,
|
||||
'--fromRotateY': `${enterOptions.fromRotateY}deg`,
|
||||
'--fromRotateZ': `${enterOptions.fromRotateZ}deg`,
|
||||
'--toRotateZ': `${enterOptions.toRotateZ}deg`,
|
||||
|
||||
'--delay': `${enterOptions.delay}ms`,
|
||||
'--duration': enterOptions.duration,
|
||||
'--easing': enterOptions.easing,
|
||||
|
||||
'--offset-x': `${location.x || 0}px`,
|
||||
'--offset-y': `${location.y || 0}px`,
|
||||
} as CSSProperties;
|
||||
|
||||
return (
|
||||
<div style={style} key={id}>
|
||||
<PaperSteps article={article} show={true} />
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
@ -0,0 +1,18 @@
|
||||
import { useCallback } from 'react';
|
||||
|
||||
import { AnimateIn } from './steps/animate-in';
|
||||
import type { ArticleOption } from './types';
|
||||
|
||||
interface PaperStepsProps {
|
||||
show?: boolean;
|
||||
article: ArticleOption;
|
||||
}
|
||||
|
||||
export const PaperSteps = ({ show, article }: PaperStepsProps) => {
|
||||
const onFinished = useCallback(() => {
|
||||
console.log('onFinished');
|
||||
}, []);
|
||||
|
||||
if (!show) return null;
|
||||
return <AnimateIn article={article} onFinished={onFinished} />;
|
||||
};
|
@ -0,0 +1,21 @@
|
||||
import { keyframes, style } from '@vanilla-extract/css';
|
||||
|
||||
import { paperLocation } from '../style.css';
|
||||
|
||||
const moveInAnim = keyframes({
|
||||
'0%': {
|
||||
transform: `translateZ(var(--fromZ)) translateX(var(--fromX)) translateY(var(--fromY)) rotateX(var(--fromRotateX)) rotateY(var(--fromRotateY)) rotateZ(var(--fromRotateZ))`,
|
||||
},
|
||||
'100%': {
|
||||
transform: `translateZ(var(--toZ)) translateX(0) translateY(0) rotateX(0deg) rotateY(0deg) rotateZ(var(--toRotateZ))`,
|
||||
},
|
||||
});
|
||||
|
||||
export const moveIn = style([
|
||||
paperLocation,
|
||||
{
|
||||
animation: `${moveInAnim} var(--duration) ease forwards`,
|
||||
animationDelay: 'var(--delay)',
|
||||
transform: 'translateY(100vh)', // hide on init
|
||||
},
|
||||
]);
|
@ -0,0 +1,66 @@
|
||||
import anime from 'animejs';
|
||||
import { useEffect } from 'react';
|
||||
|
||||
import { Paper, type PaperProps } from '../curve-paper/paper';
|
||||
import * as paperStyles from '../curve-paper/paper.css';
|
||||
import type { ArticleOption } from '../types';
|
||||
import * as styles from './animate-in.css';
|
||||
|
||||
interface AnimateInProps {
|
||||
paperProps?: PaperProps;
|
||||
article: ArticleOption;
|
||||
onFinished?: () => void;
|
||||
}
|
||||
|
||||
const easing = 'spring(5, 100, 10, 0)';
|
||||
const animeSync = (params: Parameters<typeof anime>[0]) => {
|
||||
return new Promise(resolve => {
|
||||
anime({ ...params, complete: () => resolve(null) });
|
||||
});
|
||||
};
|
||||
|
||||
export const AnimateIn = ({
|
||||
article,
|
||||
paperProps,
|
||||
onFinished,
|
||||
}: AnimateInProps) => {
|
||||
const { id: _id, enterOptions, brief } = article;
|
||||
const id = `onboardingMoveIn${_id}`;
|
||||
const segments = 4;
|
||||
|
||||
const rotateX = enterOptions.curve / segments;
|
||||
|
||||
useEffect(() => {
|
||||
Promise.all([
|
||||
animeSync({
|
||||
targets: `[data-id="${id}"] .${paperStyles.segment}[data-direction="up"]`,
|
||||
rotateX: [-rotateX, 0],
|
||||
easing,
|
||||
delay: enterOptions.delay,
|
||||
}),
|
||||
animeSync({
|
||||
targets: `[data-id="${id}"] ${paperStyles.segment}[data-direction="down"]`,
|
||||
rotateX: [rotateX, 0],
|
||||
easing,
|
||||
delay: enterOptions.delay,
|
||||
}),
|
||||
])
|
||||
.then(() => {
|
||||
onFinished?.();
|
||||
})
|
||||
.catch(console.error);
|
||||
}, [enterOptions.delay, id, rotateX, onFinished]);
|
||||
|
||||
const props = {
|
||||
...paperProps,
|
||||
segments,
|
||||
content: brief,
|
||||
centerIndex: Math.min(segments - 1, Math.max(0, enterOptions.curveCenter)),
|
||||
};
|
||||
|
||||
return (
|
||||
<div data-id={id} className={styles.moveIn}>
|
||||
<Paper {...props} />
|
||||
</div>
|
||||
);
|
||||
};
|
@ -0,0 +1,74 @@
|
||||
import { globalStyle, style } from '@vanilla-extract/css';
|
||||
|
||||
// in case that we need to support dark mode later
|
||||
export const onboardingVars = {
|
||||
window: {
|
||||
bg: 'var(--affine-pure-white)',
|
||||
shadow: 'var(--affine-shadow-1)',
|
||||
transition: {
|
||||
size: '0.3s ease',
|
||||
},
|
||||
},
|
||||
paper: {
|
||||
w: '230px',
|
||||
h: '302px',
|
||||
r: '8px',
|
||||
bg: 'var(--affine-pure-white)',
|
||||
// textColor: 'var(--affine-light-text-primary-color)',
|
||||
textColor: '#121212',
|
||||
},
|
||||
web: {
|
||||
bg: '#fafafa', // TODO: use var
|
||||
},
|
||||
};
|
||||
|
||||
export const perspective = style({
|
||||
perspective: '10000px',
|
||||
transformStyle: 'preserve-3d',
|
||||
});
|
||||
|
||||
export const onboarding = style([
|
||||
perspective,
|
||||
{
|
||||
width: '100vw',
|
||||
height: '100vh',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
position: 'relative',
|
||||
|
||||
selectors: {
|
||||
// hack background color for web
|
||||
'&::after': {
|
||||
content: '',
|
||||
position: 'absolute',
|
||||
inset: 0,
|
||||
background: onboardingVars.web.bg,
|
||||
transform: 'translateZ(-1000px) scale(2)',
|
||||
},
|
||||
'&[data-is-desktop="true"]::after': {
|
||||
content: 'unset',
|
||||
},
|
||||
},
|
||||
},
|
||||
]);
|
||||
|
||||
globalStyle(`${onboarding} *`, {
|
||||
perspective: '10000px',
|
||||
transformStyle: 'preserve-3d',
|
||||
});
|
||||
|
||||
export const offsetOrigin = style({
|
||||
width: 0,
|
||||
height: 0,
|
||||
position: 'relative',
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
});
|
||||
|
||||
export const paperLocation = style({
|
||||
position: 'absolute',
|
||||
left: `calc(var(--offset-x) - ${onboardingVars.paper.w} / 2)`,
|
||||
top: `calc(var(--offset-y) - ${onboardingVars.paper.h} / 2)`,
|
||||
});
|
@ -0,0 +1,47 @@
|
||||
import type { ReactNode } from 'react';
|
||||
|
||||
export type OnboardingStep = 'enter' | 'unfold' | 'mode-switch';
|
||||
export type ArticleId = '0' | '1' | '2' | '3' | '4';
|
||||
|
||||
/**
|
||||
* Paper enter animation options
|
||||
*/
|
||||
export interface PaperEnterOptions {
|
||||
// animation-curve
|
||||
curveCenter: number;
|
||||
curve: number;
|
||||
|
||||
// animation-move
|
||||
fromZ: number;
|
||||
fromX: number;
|
||||
fromY: number;
|
||||
fromRotateX: number;
|
||||
fromRotateY: number;
|
||||
fromRotateZ: number;
|
||||
toZ: number;
|
||||
toRotateZ: number;
|
||||
|
||||
// move-in animation config
|
||||
duration: number | string;
|
||||
delay: number;
|
||||
easing: string;
|
||||
}
|
||||
|
||||
export interface ArticleOption {
|
||||
/** article id */
|
||||
id: ArticleId;
|
||||
|
||||
/** paper enter animation content */
|
||||
brief: ReactNode;
|
||||
|
||||
/** paper enter animation configuration */
|
||||
enterOptions: PaperEnterOptions;
|
||||
|
||||
/** Locate paper */
|
||||
location: {
|
||||
/** offset X */
|
||||
x: number;
|
||||
/** offset Y */
|
||||
y: number;
|
||||
};
|
||||
}
|
@ -1,6 +1,7 @@
|
||||
import { Button } from '@affine/component/ui/button';
|
||||
import { useCallback } from 'react';
|
||||
import { redirect } from 'react-router-dom';
|
||||
|
||||
import { Onboarding } from '../components/affine/onboarding/onboarding';
|
||||
import {
|
||||
appConfigStorage,
|
||||
useAppConfigStorage,
|
||||
@ -18,9 +19,9 @@ export const loader = () => {
|
||||
|
||||
export const Component = () => {
|
||||
const { jumpToIndex } = useNavigateHelper();
|
||||
const [onBoarding, setOnboarding] = useAppConfigStorage('onBoarding');
|
||||
const [, setOnboarding] = useAppConfigStorage('onBoarding');
|
||||
|
||||
const openApp = () => {
|
||||
const openApp = useCallback(() => {
|
||||
if (environment.isDesktop) {
|
||||
window.apis.ui.handleOpenMainApp().catch(err => {
|
||||
console.log('failed to open main app', err);
|
||||
@ -29,24 +30,7 @@ export const Component = () => {
|
||||
jumpToIndex(RouteLogic.REPLACE);
|
||||
setOnboarding(false);
|
||||
}
|
||||
};
|
||||
}, [jumpToIndex, setOnboarding]);
|
||||
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
flexDirection: 'column',
|
||||
gap: '8px',
|
||||
height: '100vh',
|
||||
}}
|
||||
>
|
||||
<Button onClick={() => setOnboarding(!onBoarding)}>
|
||||
Toggle onboarding
|
||||
</Button>
|
||||
onboarding page, onboarding mode is {onBoarding ? 'on' : 'off'}
|
||||
<Button onClick={openApp}>Enter App</Button>
|
||||
</div>
|
||||
);
|
||||
return <Onboarding onOpenApp={openApp} />;
|
||||
};
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { assert } from 'console';
|
||||
import { BrowserWindow } from 'electron';
|
||||
import { BrowserWindow, screen } from 'electron';
|
||||
import { join } from 'path';
|
||||
|
||||
import { mainWindowOrigin } from './constants';
|
||||
@ -27,17 +27,22 @@ async function createOnboardingWindow(additionalArguments: string[]) {
|
||||
|
||||
assert(helperExposedMeta, 'helperExposedMeta should be defined');
|
||||
|
||||
// get user's screen size
|
||||
const { width, height } = screen.getPrimaryDisplay().workAreaSize;
|
||||
|
||||
const browserWindow = new BrowserWindow({
|
||||
width: 800,
|
||||
height: 600,
|
||||
width,
|
||||
height,
|
||||
frame: false,
|
||||
show: false,
|
||||
closable: false,
|
||||
minimizable: false,
|
||||
maximizable: false,
|
||||
fullscreenable: false,
|
||||
skipTaskbar: true,
|
||||
// transparent: true,
|
||||
// skipTaskbar: true,
|
||||
transparent: true,
|
||||
backgroundColor: '#00FFFFFF',
|
||||
hasShadow: false,
|
||||
webPreferences: {
|
||||
webgl: true,
|
||||
preload: join(__dirname, './preload.js'),
|
||||
@ -46,7 +51,10 @@ async function createOnboardingWindow(additionalArguments: string[]) {
|
||||
});
|
||||
|
||||
browserWindow.on('ready-to-show', () => {
|
||||
browserWindow.show();
|
||||
// TODO: add a timeout to avoid flickering, window is ready, but dom is not ready
|
||||
setTimeout(() => {
|
||||
browserWindow.show();
|
||||
}, 300);
|
||||
});
|
||||
|
||||
await browserWindow.loadURL(
|
||||
|
16
yarn.lock
16
yarn.lock
@ -379,6 +379,7 @@ __metadata:
|
||||
"@swc/core": "npm:^1.3.93"
|
||||
"@testing-library/react": "npm:^14.0.0"
|
||||
"@toeverything/theme": "npm:^0.7.20"
|
||||
"@types/animejs": "npm:^3"
|
||||
"@types/bytes": "npm:^3.1.3"
|
||||
"@types/image-blob-reduce": "npm:^4.1.3"
|
||||
"@types/lodash-es": "npm:^4.17.9"
|
||||
@ -386,6 +387,7 @@ __metadata:
|
||||
"@types/webpack-env": "npm:^1.18.2"
|
||||
"@vanilla-extract/css": "npm:^1.13.0"
|
||||
"@vanilla-extract/dynamic": "npm:^2.0.3"
|
||||
animejs: "npm:^3.2.2"
|
||||
async-call-rpc: "npm:^6.3.1"
|
||||
bytes: "npm:^3.1.2"
|
||||
clsx: "npm:^2.0.0"
|
||||
@ -14324,6 +14326,13 @@ __metadata:
|
||||
languageName: unknown
|
||||
linkType: soft
|
||||
|
||||
"@types/animejs@npm:^3":
|
||||
version: 3.1.12
|
||||
resolution: "@types/animejs@npm:3.1.12"
|
||||
checksum: 8ea5d0440236b87042ad012c0bfd90a38cf38688b8b28ad750d21eb01a3943c9256f0ec79ea0b40aad6e500ee177ed43d5aeec8f875f904b61f195e5e305d2ce
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@types/argparse@npm:1.0.38":
|
||||
version: 1.0.38
|
||||
resolution: "@types/argparse@npm:1.0.38"
|
||||
@ -16580,6 +16589,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"animejs@npm:^3.2.2":
|
||||
version: 3.2.2
|
||||
resolution: "animejs@npm:3.2.2"
|
||||
checksum: 7abdb56f415c666ba02f4e64fdbb10d457fed7e3711b0f006f97e48e5650097013397d890e8ceb31e9e06b73bf6dfd9202309d0dae0fc0b5190aa7c4e0ab7054
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"ansi-align@npm:^3.0.1":
|
||||
version: 3.0.1
|
||||
resolution: "ansi-align@npm:3.0.1"
|
||||
|
Loading…
Reference in New Issue
Block a user