Merge remote-tracking branch 'origin/develop' into feat/update-get-started-template

This commit is contained in:
alt0
2022-08-12 18:10:07 +08:00
19 changed files with 358 additions and 239 deletions
+2 -7
View File
@@ -17,6 +17,7 @@ const cleanupWorkspace = (workspace: string) =>
const requestPermission = async (workspace: string) => {
await cleanupWorkspace(workspace);
// @ts-ignore
const dirHandler = await window.showDirectoryPicker({
id: 'AFFiNE_' + workspace,
mode: 'readwrite',
@@ -46,7 +47,7 @@ const requestPermission = async (workspace: string) => {
};
export const FileSystem = (props: { onError: () => void }) => {
const onSelected = useLocalTrigger();
const [, onSelected] = useLocalTrigger();
const apiSupported = useMemo(() => {
try {
@@ -56,12 +57,6 @@ export const FileSystem = (props: { onError: () => void }) => {
}
}, []);
useEffect(() => {
if (process.env['NX_E2E']) {
onSelected();
}
}, []);
return (
<MuiButton
variant="outlined"
+1 -2
View File
@@ -21,7 +21,7 @@ export function Login() {
<MuiSnackbar
anchorOrigin={{ vertical: 'top', horizontal: 'right' }}
open={error}
message="Login failed, please check if you have permission"
message="Request File Permission failed, please check if you have permission"
/>
<MuiGrid item xs={8}>
<Error
@@ -32,7 +32,6 @@ export function Login() {
</MuiGrid>
<MuiGrid item xs={4}>
{' '}
<MuiBox
style={{
display: 'flex',
@@ -25,7 +25,6 @@ import { ReactEditor } from 'slate-react';
import OpenInNewIcon from '@mui/icons-material/OpenInNew';
import EditIcon from '@mui/icons-material/Edit';
import LinkOffIcon from '@mui/icons-material/LinkOff';
import AttachmentIcon from '@mui/icons-material/Attachment';
import {
MuiTooltip as Tooltip,
styled,
@@ -39,7 +38,7 @@ import {
import { getRandomString } from '../utils';
import { colors } from '../../colors';
import { LinkIcon } from '@toeverything/components/icons';
export type LinkElement = {
type: 'link';
url: string;
@@ -124,7 +123,7 @@ const LinkStyledTooltip = styled(({ className, ...props }: MuiTooltipProps) => (
))(({ theme }) => ({
[`& .${muiTooltipClasses.tooltip}`]: {
backgroundColor: '#fff',
color: '#4C6275',
color: '#3E6FDB',
boxShadow: theme.affine.shadows.shadow1,
fontSize: '14px',
},
@@ -419,8 +418,8 @@ export const LinkModal = memo((props: LinkModalProps) => {
}}
>
<div className={styles('linkModalContainerIcon')}>
<AttachmentIcon
style={{ color: colors.Gray04, fontSize: 16 }}
<LinkIcon
style={{ fontSize: '16px', marginTop: '2px' }}
/>
</div>
<input
@@ -505,8 +504,10 @@ const LinkModalContainer = styled('div')(({ theme }) => ({
const styles = style9.create({
linkModalContainerIcon: {
display: 'flex',
width: '16px',
margin: '0 16px 0 4px',
color: '#4C6275',
},
linkModalContainerInput: {
flex: '1',
@@ -517,6 +518,7 @@ const styles = style9.create({
'::-webkit-input-placeholder': {
color: '#98acbd',
},
color: '#4C6275',
},
linkMask: {
position: 'fixed',
@@ -42,6 +42,9 @@ const TextBlockContainer = styled(Text)(({ theme }) => ({
fontFamily: theme.affine.typography.body1.fontFamily,
color: theme.affine.typography.body1.color,
letterSpacing: '0.1px',
a: {
color: '#3e6fdb',
},
}));
const findSlice = (arr: string[], p: string, q: string) => {
@@ -48,7 +48,7 @@ export class SelectionManager implements VirgoSelection {
private _scrollDelay = 150;
private _selectEndDelayTime = 500;
private _hasEmitEndPending = false;
private _scrollTimer: number | null = null;
/**
*
* the selection info before current
@@ -681,6 +681,9 @@ export class SelectionManager implements VirgoSelection {
*/
public async activeNodeByNodeId(nodeId: string, position?: CursorTypes) {
try {
if (this._scrollTimer) {
clearTimeout(this._scrollTimer);
}
const node = await this._editor.getBlockById(nodeId);
if (node) {
this._activatedNodeId = nodeId;
@@ -689,7 +692,7 @@ export class SelectionManager implements VirgoSelection {
}
this.emit(nodeId, SelectEventTypes.active, this.lastPoint);
// TODO: Optimize the related logic after implementing the scroll bar
setTimeout(() => {
this._scrollTimer = window.setTimeout(() => {
this._editor.scrollManager.keepBlockInView(node);
}, this._scrollDelay);
} else {
@@ -0,0 +1,115 @@
/* eslint-disable filename-rules/match */
import { useCallback, useMemo, useState } from 'react';
import { IconButton, MuiSnackbar, styled } from '@toeverything/components/ui';
import { services } from '@toeverything/datasource/db-service';
import { useLocalTrigger } from '@toeverything/datasource/state';
import { CloseIcon } from '@toeverything/components/common';
const cleanupWorkspace = (workspace: string) =>
new Promise((resolve, reject) => {
const req = indexedDB.deleteDatabase(workspace);
req.addEventListener('error', e => reject(e));
req.addEventListener('blocked', e => reject(e));
req.addEventListener('upgradeneeded', e => reject(e));
req.addEventListener('success', e => resolve(e));
});
const requestPermission = async (workspace: string) => {
await cleanupWorkspace(workspace);
// @ts-ignore
const dirHandler = await window.showDirectoryPicker({
id: 'AFFiNE_' + workspace,
mode: 'readwrite',
startIn: 'documents',
});
const fileHandle = await dirHandler.getFileHandle('affine.db', {
create: true,
});
const file = await fileHandle.getFile();
const initialData = new Uint8Array(await file.arrayBuffer());
const exporter = async (contents: Uint8Array) => {
try {
const writable = await fileHandle.createWritable();
await writable.write(contents);
await writable.close();
} catch (e) {
console.log(e);
}
};
await services.api.editorBlock.setupDataExporter(
workspace,
new Uint8Array(initialData),
exporter
);
};
const StyledFileSystem = styled('div')<{ disabled?: boolean }>({
padding: '10px 12px',
fontWeight: 600,
fontSize: '14px',
color: '#3E6FDB',
textTransform: 'uppercase',
cursor: 'pointer',
'&:hover': {
background: '#F5F7F8',
borderRadius: '5px',
},
});
export const fsApiSupported = () => {
try {
return 'showOpenFilePicker' in window;
} catch (e) {
return false;
}
};
export const FileSystem = () => {
const [selected, onSelected] = useLocalTrigger();
const [error, setError] = useState(false);
const onError = useCallback(() => {
setError(true);
setTimeout(() => setError(false), 3000);
}, []);
const apiSupported = useMemo(() => fsApiSupported(), []);
if (apiSupported && !selected) {
return (
<>
<MuiSnackbar
anchorOrigin={{ vertical: 'top', horizontal: 'right' }}
open={error}
message="Request File Permission failed, please check if you have permission"
sx={{ marginTop: '3em' }}
action={
<IconButton
aria-label="close"
onClick={() => setError(false)}
>
<CloseIcon />
</IconButton>
}
/>
<StyledFileSystem
onClick={async () => {
try {
await requestPermission('AFFiNE');
onSelected();
} catch (e) {
onError();
}
}}
>
Sync to Disk
</StyledFileSystem>
</>
);
}
return null;
};
@@ -1,19 +1,36 @@
import { IconButton, styled, MuiButton } from '@toeverything/components/ui';
import { useMemo } from 'react';
import { IconButton, styled } from '@toeverything/components/ui';
import {
LogoIcon,
SideBarViewIcon,
SearchIcon,
SideBarViewCloseIcon,
} from '@toeverything/components/icons';
import { useShowSettingsSidebar } from '@toeverything/datasource/state';
import {
useShowSettingsSidebar,
useLocalTrigger,
} from '@toeverything/datasource/state';
import { CurrentPageTitle } from './Title';
import { EditorBoardSwitcher } from './EditorBoardSwitcher';
import { fsApiSupported, FileSystem } from './FileSystem';
import { CurrentPageTitle } from './Title';
export const LayoutHeader = () => {
const [isLocalWorkspace] = useLocalTrigger();
const { toggleSettingsSidebar: toggleInfoSidebar, showSettingsSidebar } =
useShowSettingsSidebar();
const warningTips = useMemo(() => {
if (!fsApiSupported()) {
return 'Your browser does not support the local storage feature, please upgrade to the latest version of Chrome or Edge browser';
} else if (isLocalWorkspace) {
return 'You are in DEMO mode. Changes will NOT be saved unless you SYNC TO DISK';
} else {
return 'AFFiNE is under active development and the current version is UNSTABLE. Please DO NOT store information or data';
}
}, [isLocalWorkspace]);
return (
<StyledContainerForHeaderRoot>
<StyledHeaderRoot>
@@ -25,6 +42,7 @@ export const LayoutHeader = () => {
</FlexContainer>
<FlexContainer>
<StyledHelper>
<FileSystem />
<StyledShare disabled={true}>Share</StyledShare>
<div style={{ margin: '0px 12px' }}>
<IconButton
@@ -51,10 +69,7 @@ export const LayoutHeader = () => {
</StyledContainerForEditorBoardSwitcher>
</StyledHeaderRoot>
<StyledUnstableTips>
<StyledUnstableTipsText>
AFFiNE now under active development, the version is
UNSTABLE, please DO NOT store important data in this version
</StyledUnstableTipsText>
<StyledUnstableTipsText>{warningTips}</StyledUnstableTipsText>
</StyledUnstableTips>
</StyledContainerForHeaderRoot>
);
@@ -36,19 +36,13 @@ export type DndTreeProps = {
indentationWidth?: number;
collapsible?: boolean;
removable?: boolean;
showDragIndicator?: boolean;
};
/**
* Currently does not support drag and drop using the keyboard.
*/
export function DndTree(props: DndTreeProps) {
const {
indentationWidth = 20,
collapsible,
removable,
showDragIndicator,
} = props;
const { indentationWidth = 20, collapsible, removable } = props;
const sensors = useSensors(
useSensor(PointerSensor, { activationConstraint: { distance: 8 } })
@@ -111,7 +105,6 @@ export function DndTree(props: DndTreeProps) {
: depth
}
indentationWidth={indentationWidth}
indicator={showDragIndicator}
childCount={children.length}
onCollapse={
collapsible && children.length
@@ -129,7 +122,8 @@ export function DndTree(props: DndTreeProps) {
)}
<DragOverlay
dropAnimation={dropAnimation}
style={{ marginTop: '-65px' }}
// TODO fix drag offset when the position of the page tree changes
style={{ marginTop: '-100px' }}
>
{activeId && activeItem ? (
<DndTreeItem
@@ -16,8 +16,7 @@ export function DndTreeItem({ id, depth, ...props }: DndTreeItemProps) {
isDragging,
isSorting,
listeners,
setDraggableNodeRef,
setDroppableNodeRef,
setNodeRef,
transform,
transition,
} = useSortable({ id });
@@ -29,8 +28,7 @@ export function DndTreeItem({ id, depth, ...props }: DndTreeItemProps) {
return (
<TreeItem
ref={setDraggableNodeRef}
wrapperRef={setDroppableNodeRef}
ref={setNodeRef}
pageId={id}
style={style}
depth={depth}
@@ -15,7 +15,6 @@ import {
TextLink,
TreeItemContainer,
TreeItemContent,
Wrapper,
} from './styles';
export type TreeItemProps = {
@@ -33,13 +32,10 @@ export type TreeItemProps = {
/** isDragging */
ghost?: boolean;
handleProps?: any;
indicator?: boolean;
indentationWidth: number;
onCollapse?(): void;
onRemove?(): void;
/** The ref of the outermost container is often used as droppaHTMLAttributes<HTMLLIElement>ble-node; the ref of the inner dom is often used as draggable-node */
wrapperRef?(node: HTMLLIElement): void;
} & HTMLAttributes<HTMLLIElement>;
} & HTMLAttributes<HTMLDivElement>;
export const TreeItem = forwardRef<HTMLDivElement, TreeItemProps>(
(
@@ -52,13 +48,10 @@ export const TreeItem = forwardRef<HTMLDivElement, TreeItemProps>(
ghost,
handleProps,
indentationWidth,
indicator,
collapsed,
onCollapse,
onRemove,
style,
value,
wrapperRef,
pageId,
...props
},
@@ -71,8 +64,8 @@ export const TreeItem = forwardRef<HTMLDivElement, TreeItemProps>(
);
return (
<Wrapper
ref={wrapperRef}
<TreeItemContainer
ref={ref}
clone={clone}
ghost={ghost}
disableSelection={disableSelection}
@@ -81,38 +74,38 @@ export const TreeItem = forwardRef<HTMLDivElement, TreeItemProps>(
active={pageId === page_id}
{...props}
>
<TreeItemContainer ref={ref} style={style} title={value}>
<ActionButton tabIndex={0} onClick={onCollapse}>
{childCount !== 0 ? (
collapsed ? (
<ArrowRightIcon />
) : (
<ArrowDropDownIcon />
)
) : (
<DotIcon />
)}
</ActionButton>
{childCount !== 0 ? (
collapsed ? (
<ActionButton tabIndex={0} onClick={onCollapse}>
<ArrowRightIcon />
</ActionButton>
) : (
<ActionButton tabIndex={0} onClick={onCollapse}>
<ArrowDropDownIcon />
</ActionButton>
)
) : (
<DotIcon />
)}
<TreeItemContent {...handleProps}>
<TextLink to={`/${workspace_id}/${pageId}`}>
{value}
</TextLink>
{BooleanPageTreeItemMoreActions && (
<MoreActions
workspaceId={workspace_id}
pageId={pageId}
onRemove={onRemove}
/>
)}
<TreeItemContent {...handleProps}>
<TextLink to={`/${workspace_id}/${pageId}`}>
{value}
</TextLink>
{BooleanPageTreeItemMoreActions && (
<MoreActions
workspaceId={workspace_id}
pageId={pageId}
onRemove={onRemove}
/>
)}
{/*{!clone && onRemove && <Remove onClick={onRemove} />}*/}
{clone && childCount && childCount > 1 ? (
<Counter>{childCount}</Counter>
) : null}
</TreeItemContent>
</TreeItemContainer>
</Wrapper>
{/*{!clone && onRemove && <Remove onClick={onRemove} />}*/}
{clone && childCount && childCount > 1 ? (
<Counter>{childCount}</Counter>
) : null}
</TreeItemContent>
</TreeItemContainer>
);
}
);
@@ -1,22 +1,18 @@
import { styled } from '@toeverything/components/ui';
import { Link } from 'react-router-dom';
export const TreeItemContainer = styled('div')`
box-sizing: border-box;
display: flex;
align-items: center;
color: #4c6275;
`;
export const Wrapper = styled('li')<{
export const TreeItemContainer = styled('div')<{
spacing: string;
clone?: boolean;
ghost?: boolean;
indicator?: boolean;
disableSelection?: boolean;
disableInteraction?: boolean;
active?: boolean;
}>`
display: flex;
align-items: center;
color: #4c6275;
box-sizing: border-box;
padding-left: ${({ spacing }) => spacing};
list-style: none;
@@ -26,21 +22,13 @@ export const Wrapper = styled('li')<{
${({ clone, disableSelection }) =>
(clone || disableSelection) &&
`width: 100%;
.Text,
.Count {
user-select: none;
-webkit-user-select: none;
}`}
${({ indicator }) =>
indicator &&
`width: 100%;
.Text,
.Count {
user-select: none;
-webkit-user-select: none;
}`}
`
width: 100%;
user-select: none;
opacity: 0.7;
background: transparent;
cursor: grab;
`}
${({ disableInteraction }) => disableInteraction && `pointer-events: none;`}
@@ -48,64 +36,6 @@ export const Wrapper = styled('li')<{
background: #f5f7f8;
border-radius: 5px;
}
&.clone {
display: inline-block;
padding: 0;
margin-left: 10px;
margin-top: 5px;
pointer-events: none;
${TreeItemContainer} {
padding-right: 20px;
border-radius: 4px;
box-shadow: 0px 15px 15px 0 rgba(34, 33, 81, 0.1);
}
}
&.ghost {
&.indicator {
opacity: 1;
position: relative;
z-index: 1;
margin-bottom: -1px;
${TreeItemContainer} {
position: relative;
padding: 0;
height: 8px;
border-color: #2389ff;
background-color: #56a1f8;
&:before {
position: absolute;
left: -8px;
top: -4px;
display: block;
content: '';
width: 12px;
height: 12px;
border-radius: 50%;
border: 1px solid #2389ff;
}
> * {
/* Items are hidden using height and opacity to retain focus */
opacity: 0;
height: 0;
}
}
}
&:not(.indicator) {
opacity: 0.5;
}
${TreeItemContainer} > * {
box-shadow: none;
background-color: transparent;
}
}
`;
export const Counter = styled('span')`
@@ -138,7 +68,7 @@ export const ActionButton = styled('button')<{
border: none;
outline: none;
appearance: none;
background-color: transparent;
background: transparent;
-webkit-tap-highlight-color: transparent;
svg {
@@ -151,8 +81,7 @@ export const ActionButton = styled('button')<{
}
&:active {
background-color: ${({ background }) =>
background ?? 'rgba(0, 0, 0, 0.05)'};
background: ${({ background }) => background ?? 'rgba(0, 0, 0, 0.05)'};
svg {
fill: ${({ fill }) => fill ?? '#788491'};
@@ -170,9 +99,7 @@ export const TreeItemMoreActions = styled('div')`
visibility: hidden;
`;
export const TextLink = styled(Link, {
shouldForwardProp: (prop: string) => !['active'].includes(prop),
})<{ active?: boolean }>`
export const TextLink = styled(Link)`
display: flex;
align-items: center;
flex-grow: 1;
@@ -37,6 +37,11 @@ async function _getCurrentToken() {
return undefined;
}
const _enabled = {
demo: [],
AFFiNE: ['sqlite'],
} as any;
async function _getBlockDatabase(
workspace: string,
options?: BlockInitOptions
@@ -45,16 +50,10 @@ async function _getBlockDatabase(
await waitLoading(workspace);
}
// if (
// options?.userId &&
// workspaces[workspace]?.getUserId() !== options?.userId
// ) {
// delete workspaces[workspace];
// }
if (!workspaces[workspace]) {
loading.add(workspace);
workspaces[workspace] = await BlockClient.init(workspace, {
enabled: _enabled[workspace] || ['idb'],
...options,
token: await _getCurrentToken(),
});
+23 -19
View File
@@ -18,8 +18,6 @@ import {
snapshot,
} from 'yjs';
import { IndexedDBProvider } from '@toeverything/datasource/jwt-rpc';
import {
AsyncDatabaseAdapter,
BlockListener,
@@ -48,15 +46,17 @@ type ConnectivityListener = (
workspace: string,
connectivity: Connectivity
) => void;
type YjsProviders = {
awareness: Awareness;
idb: IndexedDBProvider;
binariesIdb: IndexedDBProvider;
binaries: Doc;
doc: Doc;
gatekeeper: GateKeeper;
connListener: { listeners?: ConnectivityListener };
userId: string;
remoteToken?: string; // remote storage token
};
const _yjsDatabaseInstance = new Map<string, YjsProviders>();
const _asyncInitLoading = new Set<string>();
@@ -90,13 +90,9 @@ async function _initYjsDatabase(
const { userId, token } = options;
const doc = new Doc({ autoLoad: true, shouldLoad: true });
const idb = await new IndexedDBProvider(workspace, doc).whenSynced;
// const idb = await new IndexedDBProvider(workspace, doc).whenSynced;
const binaries = new Doc({ autoLoad: true, shouldLoad: true });
const binariesIdb = await new IndexedDBProvider(
`${workspace}_binaries`,
binaries
).whenSynced;
const awareness = new Awareness(doc);
@@ -114,16 +110,24 @@ async function _initYjsDatabase(
const emitState = (c: Connectivity) =>
connListener.listeners?.(workspace, c);
await Promise.all(
Object.entries(options.provider).map(async ([, p]) =>
p({ awareness, doc, token, workspace, emitState })
)
Object.entries(options.provider).flatMap(async ([, p]) => [
p({ awareness, doc, token, workspace, emitState }),
p({
awareness,
doc: binaries,
token,
workspace: `${workspace}_binaries`,
emitState,
}),
])
);
}
const newInstance = {
awareness,
idb,
binariesIdb,
binaries,
doc,
gatekeeper,
connListener,
userId,
remoteToken: token,
@@ -183,7 +187,7 @@ export class YjsAdapter implements AsyncDatabaseAdapter<YjsContentOperation> {
private constructor(providers: YjsProviders) {
this._provider = providers;
this._doc = providers.idb.doc;
this._doc = providers.doc;
this._awareness = providers.awareness;
this._gatekeeper = providers.gatekeeper;
this._reload = () => {
@@ -201,7 +205,7 @@ export class YjsAdapter implements AsyncDatabaseAdapter<YjsContentOperation> {
});
// @ts-ignore
this._binaries = new YjsRemoteBinaries(
providers.binariesIdb.doc.getMap(),
providers.binaries.getMap(),
providers.remoteToken
);
// @ts-ignore
@@ -336,7 +340,7 @@ export class YjsAdapter implements AsyncDatabaseAdapter<YjsContentOperation> {
});
const [file] = (await fromEvent(handles)) as File[];
const binary = await file.arrayBuffer();
await this._provider.idb.clearData();
// await this._provider.idb.clearData();
const doc = new Doc({ autoLoad: true, shouldLoad: true });
let updated = 0;
let isUpdated = false;
@@ -357,8 +361,8 @@ export class YjsAdapter implements AsyncDatabaseAdapter<YjsContentOperation> {
};
check();
});
await new IndexedDBProvider(this._provider.idb.name, doc)
.whenSynced;
// await new IndexedDBProvider(this._provider.idb.name, doc)
// .whenSynced;
applyUpdate(doc, new Uint8Array(binary));
await update_check;
console.log('load success');
+56 -41
View File
@@ -2,6 +2,7 @@ import { Doc } from 'yjs';
import { Awareness } from 'y-protocols/awareness.js';
import {
IndexedDBProvider,
SQLiteProvider,
WebsocketProvider,
} from '@toeverything/datasource/jwt-rpc';
@@ -19,7 +20,10 @@ type YjsDefaultInstances = {
export type YjsProvider = (instances: YjsDefaultInstances) => Promise<void>;
type ProviderType = 'idb' | 'sqlite' | 'ws';
export type YjsProviderOptions = {
enabled: ProviderType[];
backend: typeof BucketBackend[keyof typeof BucketBackend];
params?: Record<string, string>;
importData?: () => Promise<Uint8Array> | Uint8Array | undefined;
@@ -30,53 +34,64 @@ export type YjsProviderOptions = {
export const getYjsProviders = (
options: YjsProviderOptions
): Record<string, YjsProvider> => {
console.log('getYjsProviders', options);
return {
indexeddb: async (instances: YjsDefaultInstances) => {
if (options.enabled.includes('idb')) {
await new IndexedDBProvider(instances.workspace, instances.doc)
.whenSynced;
}
},
sqlite: async (instances: YjsDefaultInstances) => {
const fsHandle = setInterval(async () => {
if (options.hasExporter?.()) {
clearInterval(fsHandle);
const fs = new SQLiteProvider(
instances.workspace,
instances.doc,
await options.importData?.()
);
if (options.exportData) {
fs.registerExporter(options.exportData);
if (options.enabled.includes('sqlite')) {
const fsHandle = setInterval(async () => {
if (options.hasExporter?.()) {
clearInterval(fsHandle);
const fs = new SQLiteProvider(
instances.workspace,
instances.doc,
await options.importData?.()
);
if (options.exportData) {
fs.registerExporter(options.exportData);
}
await fs.whenSynced;
}
await fs.whenSynced;
}
}, 500);
}, 500);
}
},
ws: async (instances: YjsDefaultInstances) => {
if (instances.token) {
const ws = new WebsocketProvider(
instances.token,
options.backend,
instances.workspace,
instances.doc,
{
awareness: instances.awareness,
params: options.params,
}
) as any; // TODO: type is erased after cascading references
if (options.enabled.includes('ws')) {
if (instances.token) {
const ws = new WebsocketProvider(
instances.token,
options.backend,
instances.workspace,
instances.doc,
{
awareness: instances.awareness,
params: options.params,
}
) as any; // TODO: type is erased after cascading references
// Wait for ws synchronization to complete, otherwise the data will be modified in reverse, which can be optimized later
return new Promise<void>((resolve, reject) => {
// TODO: synced will also be triggered on reconnection after losing sync
// There needs to be an event mechanism to emit the synchronization state to the upper layer
ws.once('synced', () => resolve());
ws.once('lost-connection', () => resolve());
ws.once('connection-error', () => reject());
ws.on('synced', () => instances.emitState('connected'));
ws.on('lost-connection', () =>
instances.emitState('retry')
);
ws.on('connection-error', () =>
instances.emitState('retry')
);
});
} else {
return;
// Wait for ws synchronization to complete, otherwise the data will be modified in reverse, which can be optimized later
return new Promise<void>((resolve, reject) => {
// TODO: synced will also be triggered on reconnection after losing sync
// There needs to be an event mechanism to emit the synchronization state to the upper layer
ws.once('synced', () => resolve());
ws.once('lost-connection', () => resolve());
ws.once('connection-error', () => reject());
ws.on('synced', () => instances.emitState('connected'));
ws.on('lost-connection', () =>
instances.emitState('retry')
);
ws.on('connection-error', () =>
instances.emitState('retry')
);
});
} else {
return;
}
}
},
};
+1
View File
@@ -618,6 +618,7 @@ export class BlockClient<
const instance = await YjsAdapter.init(workspace, {
provider: getYjsProviders({
enabled: [],
backend: BucketBackend.YjsWebSocketAffine,
importData,
exportData,
+18 -3
View File
@@ -1,3 +1,4 @@
import { useNavigate } from 'react-router';
import {
getAuth,
onAuthStateChanged,
@@ -62,11 +63,17 @@ const BRAND_ID = 'AFFiNE';
const _localTrigger = atom<boolean>(false);
const _useUserAndSpacesForFreeLogin = () => {
const navigate = useNavigate();
const [user, setUser] = useAtom(_userAtom);
const [loading, setLoading] = useAtom(_loadingAtom);
const [localTrigger] = useAtom(_localTrigger);
useEffect(() => setLoading(false), []);
useEffect(() => {
if (loading) {
navigate('/demo');
setLoading(false);
}
}, []);
useEffect(() => {
if (localTrigger) {
@@ -77,6 +84,14 @@ const _useUserAndSpacesForFreeLogin = () => {
nickname: BRAND_ID,
email: '',
});
} else {
setUser({
photo: '',
id: 'demo',
username: 'demo',
nickname: 'demo',
email: '',
});
}
}, [localTrigger, setLoading, setUser]);
@@ -86,8 +101,8 @@ const _useUserAndSpacesForFreeLogin = () => {
};
export const useLocalTrigger = () => {
const [, setTrigger] = useAtom(_localTrigger);
return () => setTrigger(true);
const [trigger, setTrigger] = useAtom(_localTrigger);
return [trigger, () => setTrigger(true)] as [boolean, () => void];
};
export const useUserAndSpaces = process.env['NX_LOCAL']