mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-09-22 19:53:48 +08:00
feat: router & block preview
This commit is contained in:
@@ -1,7 +1,6 @@
|
|||||||
/* eslint-disable filename-rules/match */
|
/* eslint-disable filename-rules/match */
|
||||||
import { useCallback, useMemo } from 'react';
|
import { useCallback, useMemo, useState } from 'react';
|
||||||
import { initializeApp } from 'firebase/app';
|
import { initializeApp } from 'firebase/app';
|
||||||
import { Error } from './../error';
|
|
||||||
import {
|
import {
|
||||||
GoogleAuthProvider,
|
GoogleAuthProvider,
|
||||||
getAuth,
|
getAuth,
|
||||||
@@ -13,11 +12,12 @@ import { LogoImg } from '@toeverything/components/common';
|
|||||||
import {
|
import {
|
||||||
MuiButton,
|
MuiButton,
|
||||||
MuiBox,
|
MuiBox,
|
||||||
MuiTypography,
|
|
||||||
MuiContainer,
|
|
||||||
MuiGrid,
|
MuiGrid,
|
||||||
|
MuiSnackbar,
|
||||||
} from '@toeverything/components/ui';
|
} from '@toeverything/components/ui';
|
||||||
|
|
||||||
|
import { Error } from './../error';
|
||||||
|
|
||||||
const _firebaseConfig = {
|
const _firebaseConfig = {
|
||||||
apiKey: 'AIzaSyD7A_VyGaKTXsPqtga9IbwrEsbWWc4rH3Y',
|
apiKey: 'AIzaSyD7A_VyGaKTXsPqtga9IbwrEsbWWc4rH3Y',
|
||||||
authDomain: 'login.affine.pro',
|
authDomain: 'login.affine.pro',
|
||||||
@@ -83,6 +83,8 @@ export const Firebase = () => {
|
|||||||
return [auth, provider];
|
return [auth, provider];
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
const [error, setError] = useState(false);
|
||||||
|
|
||||||
const handleAuth = useCallback(() => {
|
const handleAuth = useCallback(() => {
|
||||||
signInWithPopup(auth, provider).catch(error => {
|
signInWithPopup(auth, provider).catch(error => {
|
||||||
const errorCode = error.code;
|
const errorCode = error.code;
|
||||||
@@ -90,11 +92,18 @@ export const Firebase = () => {
|
|||||||
const email = error.customData.email;
|
const email = error.customData.email;
|
||||||
const credential = GoogleAuthProvider.credentialFromError(error);
|
const credential = GoogleAuthProvider.credentialFromError(error);
|
||||||
console.log(errorCode, errorMessage, email, credential);
|
console.log(errorCode, errorMessage, email, credential);
|
||||||
|
setError(true);
|
||||||
|
setTimeout(() => setError(false), 3000);
|
||||||
});
|
});
|
||||||
}, [auth, provider]);
|
}, [auth, provider]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<MuiGrid container>
|
<MuiGrid container>
|
||||||
|
<MuiSnackbar
|
||||||
|
anchorOrigin={{ vertical: 'top', horizontal: 'right' }}
|
||||||
|
open={error}
|
||||||
|
message="Login failed, please check if you have permission"
|
||||||
|
/>
|
||||||
<MuiGrid item xs={8}>
|
<MuiGrid item xs={8}>
|
||||||
<Error
|
<Error
|
||||||
title="Welcome to Affine"
|
title="Welcome to Affine"
|
||||||
|
|||||||
@@ -32,6 +32,7 @@
|
|||||||
"code-example": "^3.3.6",
|
"code-example": "^3.3.6",
|
||||||
"codemirror": "6.0.1",
|
"codemirror": "6.0.1",
|
||||||
"keymap": "link:@codemirror/next/keymap",
|
"keymap": "link:@codemirror/next/keymap",
|
||||||
|
"nanoid": "^4.0.0",
|
||||||
"react-resizable": "^3.0.4",
|
"react-resizable": "^3.0.4",
|
||||||
"react-window": "^1.8.7",
|
"react-window": "^1.8.7",
|
||||||
"slate": "^0.81.1",
|
"slate": "^0.81.1",
|
||||||
|
|||||||
@@ -0,0 +1,132 @@
|
|||||||
|
import { memo, useEffect, useRef, useState } from 'react';
|
||||||
|
import { nanoid } from 'nanoid';
|
||||||
|
|
||||||
|
import { StyledBlockPreview } from '@toeverything/components/common';
|
||||||
|
import { services } from '@toeverything/datasource/db-service';
|
||||||
|
import { AsyncBlock } from '@toeverything/framework/virgo';
|
||||||
|
import { debounce, sleep } from '@toeverything/utils';
|
||||||
|
|
||||||
|
const updateTitle = async (
|
||||||
|
workspace: string,
|
||||||
|
blockId: string,
|
||||||
|
onFinal: (title?: string) => void,
|
||||||
|
retry = 0
|
||||||
|
) => {
|
||||||
|
const [page] = await services.api.editorBlock.search(workspace, {
|
||||||
|
tag: `id:${blockId}`,
|
||||||
|
});
|
||||||
|
if (page?.content) {
|
||||||
|
onFinal(page?.content);
|
||||||
|
} else if (retry < 20) {
|
||||||
|
await sleep(500);
|
||||||
|
updateTitle(workspace, blockId, onFinal, retry + 1);
|
||||||
|
} else {
|
||||||
|
onFinal('Untitled');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const bindBlock = async (
|
||||||
|
handleId: string,
|
||||||
|
workspace: string,
|
||||||
|
blockId: string,
|
||||||
|
onFinal: (title: string) => void,
|
||||||
|
retry = 0
|
||||||
|
): Promise<() => void> | undefined => {
|
||||||
|
const block = await services.api.editorBlock.getBlock(workspace, blockId);
|
||||||
|
if (block.id === blockId) {
|
||||||
|
const debouncedOnFinal = debounce(onFinal, 100, { maxWait: 500 });
|
||||||
|
const onUpdated = () =>
|
||||||
|
updateTitle(workspace, blockId, debouncedOnFinal);
|
||||||
|
block.on('content', handleId, onUpdated);
|
||||||
|
onUpdated();
|
||||||
|
return () => block.off('content', handleId);
|
||||||
|
} else if (retry < 20) {
|
||||||
|
await sleep(500);
|
||||||
|
return bindBlock(handleId, workspace, blockId, onFinal, retry + 1);
|
||||||
|
} else {
|
||||||
|
onFinal('Untitled');
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const useBlockTitle = (block: AsyncBlock, blockId: string) => {
|
||||||
|
const [title, setTitle] = useState('Loading...');
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
let callback: any = undefined;
|
||||||
|
|
||||||
|
bindBlock(
|
||||||
|
block.id + nanoid(8),
|
||||||
|
block.workspace,
|
||||||
|
blockId,
|
||||||
|
setTitle
|
||||||
|
).then(cb => {
|
||||||
|
callback = cb;
|
||||||
|
});
|
||||||
|
|
||||||
|
return () => callback?.();
|
||||||
|
}, [block.id, block.workspace, blockId]);
|
||||||
|
|
||||||
|
return title;
|
||||||
|
};
|
||||||
|
|
||||||
|
type BlockPreviewProps = {
|
||||||
|
block: AsyncBlock;
|
||||||
|
blockId: string;
|
||||||
|
editorElement?: () => JSX.Element;
|
||||||
|
};
|
||||||
|
|
||||||
|
const InternalBlockPreview = (props: BlockPreviewProps) => {
|
||||||
|
const container = useRef<HTMLDivElement>();
|
||||||
|
const [preview, setPreview] = useState(true);
|
||||||
|
const title = useBlockTitle(props.block, props.blockId);
|
||||||
|
|
||||||
|
const AffineEditor = props.editorElement as any;
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (container?.current) {
|
||||||
|
const element = container?.current;
|
||||||
|
const resizeObserver = new IntersectionObserver(entries => {
|
||||||
|
const height = entries?.[0]?.intersectionRect.height;
|
||||||
|
setPreview(height < 174);
|
||||||
|
});
|
||||||
|
|
||||||
|
resizeObserver.observe(element);
|
||||||
|
return () => resizeObserver.unobserve(element);
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
}, [container, props]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div ref={container}>
|
||||||
|
<StyledBlockPreview title={title}>
|
||||||
|
{preview ? (
|
||||||
|
<span
|
||||||
|
style={{
|
||||||
|
display: 'flex',
|
||||||
|
justifyContent: 'center',
|
||||||
|
fontSize: '128px',
|
||||||
|
height: '480px',
|
||||||
|
alignItems: 'center',
|
||||||
|
color: '#5591ff',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Preview
|
||||||
|
</span>
|
||||||
|
) : AffineEditor ? (
|
||||||
|
<AffineEditor
|
||||||
|
workspace={props.block.workspace}
|
||||||
|
rootBlockId={props.blockId}
|
||||||
|
/>
|
||||||
|
) : null}
|
||||||
|
</StyledBlockPreview>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const BlockPreview = memo(InternalBlockPreview, (prev, next) => {
|
||||||
|
return (
|
||||||
|
prev.block.workspace === next.block.workspace &&
|
||||||
|
prev.blockId === next.blockId
|
||||||
|
);
|
||||||
|
});
|
||||||
@@ -1,13 +1,11 @@
|
|||||||
import { FC, memo, useEffect, useMemo, useRef, useState } from 'react';
|
import { FC } from 'react';
|
||||||
|
|
||||||
import { StyledBlockPreview } from '@toeverything/components/common';
|
|
||||||
import { styled } from '@toeverything/components/ui';
|
import { styled } from '@toeverything/components/ui';
|
||||||
import { AsyncBlock, useRecastBlockScene } from '@toeverything/framework/virgo';
|
import { AsyncBlock, useRecastBlockScene } from '@toeverything/framework/virgo';
|
||||||
|
|
||||||
import { formatUrl } from './format-url';
|
import { formatUrl } from './format-url';
|
||||||
import { SCENE_CONFIG } from '../../blocks/group/config';
|
import { SCENE_CONFIG } from '../../blocks/group/config';
|
||||||
import { services } from '@toeverything/datasource/db-service';
|
import { BlockPreview } from './BlockView';
|
||||||
import { debounce } from '@toeverything/utils';
|
|
||||||
const MouseMaskContainer = styled('div')({
|
const MouseMaskContainer = styled('div')({
|
||||||
position: 'absolute',
|
position: 'absolute',
|
||||||
zIndex: 1,
|
zIndex: 1,
|
||||||
@@ -47,96 +45,6 @@ const _getLinkStyle = (scene: string) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
type BlockPreviewProps = {
|
|
||||||
block: AsyncBlock;
|
|
||||||
blockId: string;
|
|
||||||
editorElement?: () => JSX.Element;
|
|
||||||
};
|
|
||||||
|
|
||||||
const BlockPreview = (props: BlockPreviewProps) => {
|
|
||||||
const container = useRef<HTMLDivElement>();
|
|
||||||
const [preview, setPreview] = useState(true);
|
|
||||||
const [title, setTitle] = useState('Loading...');
|
|
||||||
useEffect(() => {
|
|
||||||
let callback: any = undefined;
|
|
||||||
services.api.editorBlock
|
|
||||||
.getBlock(props.block.workspace, props.blockId)
|
|
||||||
.then(block => {
|
|
||||||
if (block.id === props.blockId) {
|
|
||||||
const updateTitle = debounce(
|
|
||||||
async () => {
|
|
||||||
const [page] =
|
|
||||||
await services.api.editorBlock.search(
|
|
||||||
props.block.workspace,
|
|
||||||
{ tag: 'id:affine67Uz4DstDk6PKUbz' }
|
|
||||||
);
|
|
||||||
|
|
||||||
console.log(page);
|
|
||||||
setTitle(page?.content || 'Untitled');
|
|
||||||
},
|
|
||||||
100,
|
|
||||||
{ maxWait: 500 }
|
|
||||||
);
|
|
||||||
block.on('content', props.block.id, updateTitle);
|
|
||||||
callback = () => block.off('content', props.block.id);
|
|
||||||
updateTitle();
|
|
||||||
} else {
|
|
||||||
setTitle('Untitled');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return () => callback?.();
|
|
||||||
}, [props.block.id, props.block.workspace, props.blockId]);
|
|
||||||
|
|
||||||
const AffineEditor = props.editorElement as any;
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (container?.current) {
|
|
||||||
const element = container?.current;
|
|
||||||
const resizeObserver = new IntersectionObserver(entries => {
|
|
||||||
const height = entries?.[0]?.intersectionRect.height;
|
|
||||||
setPreview(height < 174);
|
|
||||||
});
|
|
||||||
|
|
||||||
resizeObserver.observe(element);
|
|
||||||
return () => resizeObserver.unobserve(element);
|
|
||||||
}
|
|
||||||
return undefined;
|
|
||||||
}, [container]);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div ref={container}>
|
|
||||||
<StyledBlockPreview title={title}>
|
|
||||||
{preview ? (
|
|
||||||
<span
|
|
||||||
style={{
|
|
||||||
display: 'flex',
|
|
||||||
justifyContent: 'center',
|
|
||||||
fontSize: '128px',
|
|
||||||
height: '480px',
|
|
||||||
alignItems: 'center',
|
|
||||||
color: '#5591ff',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Preview
|
|
||||||
</span>
|
|
||||||
) : AffineEditor ? (
|
|
||||||
<AffineEditor
|
|
||||||
workspace={props.block.workspace}
|
|
||||||
rootBlockId={props.blockId}
|
|
||||||
/>
|
|
||||||
) : null}
|
|
||||||
</StyledBlockPreview>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const MemoBlockPreview = memo(BlockPreview, (prev, next) => {
|
|
||||||
return (
|
|
||||||
prev.block.workspace === next.block.workspace &&
|
|
||||||
prev.blockId === next.blockId
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
const SourceViewContainer = styled('div')<{
|
const SourceViewContainer = styled('div')<{
|
||||||
isSelected: boolean;
|
isSelected: boolean;
|
||||||
scene: string;
|
scene: string;
|
||||||
@@ -186,7 +94,7 @@ export const SourceView: FC<Props> = props => {
|
|||||||
scene={SCENE_CONFIG.REFLINK}
|
scene={SCENE_CONFIG.REFLINK}
|
||||||
style={{ padding: '0' }}
|
style={{ padding: '0' }}
|
||||||
>
|
>
|
||||||
<MemoBlockPreview
|
<BlockPreview
|
||||||
block={block}
|
block={block}
|
||||||
editorElement={editorElement}
|
editorElement={editorElement}
|
||||||
blockId={src}
|
blockId={src}
|
||||||
|
|||||||
@@ -78,6 +78,7 @@ const _useUserAndSpacesForFreeLogin = () => {
|
|||||||
loading,
|
loading,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export const useUserAndSpaces = process.env['NX_LOCAL']
|
export const useUserAndSpaces = process.env['NX_LOCAL']
|
||||||
? _useUserAndSpacesForFreeLogin
|
? _useUserAndSpacesForFreeLogin
|
||||||
: _useUserAndSpace;
|
: _useUserAndSpace;
|
||||||
|
|||||||
+1
-1
@@ -4,7 +4,7 @@
|
|||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"author": "AFFiNE <developer@affine.pro>",
|
"author": "AFFiNE <developer@affine.pro>",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "env-cmd -f .env.local nx serve ligo-virgo",
|
"start": "env-cmd -f .env.local-dev nx serve ligo-virgo",
|
||||||
"start:affine": "nx serve ligo-virgo",
|
"start:affine": "nx serve ligo-virgo",
|
||||||
"start:keck": "nx serve keck",
|
"start:keck": "nx serve keck",
|
||||||
"build": "nx build ligo-virgo",
|
"build": "nx build ligo-virgo",
|
||||||
|
|||||||
Generated
+2
@@ -379,6 +379,7 @@ importers:
|
|||||||
code-example: ^3.3.6
|
code-example: ^3.3.6
|
||||||
codemirror: 6.0.1
|
codemirror: 6.0.1
|
||||||
keymap: link:@codemirror/next/keymap
|
keymap: link:@codemirror/next/keymap
|
||||||
|
nanoid: ^4.0.0
|
||||||
react-resizable: ^3.0.4
|
react-resizable: ^3.0.4
|
||||||
react-window: ^1.8.7
|
react-window: ^1.8.7
|
||||||
slate: ^0.81.1
|
slate: ^0.81.1
|
||||||
@@ -414,6 +415,7 @@ importers:
|
|||||||
code-example: 3.3.6
|
code-example: 3.3.6
|
||||||
codemirror: 6.0.1
|
codemirror: 6.0.1
|
||||||
keymap: link:@codemirror/next/keymap
|
keymap: link:@codemirror/next/keymap
|
||||||
|
nanoid: 4.0.0
|
||||||
react-resizable: 3.0.4
|
react-resizable: 3.0.4
|
||||||
react-window: 1.8.7
|
react-window: 1.8.7
|
||||||
slate: 0.81.1
|
slate: 0.81.1
|
||||||
|
|||||||
Reference in New Issue
Block a user