mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-02-15 05:37:32 +00:00
fix: avoid avatar flickering (#1319)
This commit is contained in:
@@ -1,6 +1,7 @@
|
|||||||
import { useTranslation } from '@affine/i18n';
|
import { useTranslation } from '@affine/i18n';
|
||||||
import React, {
|
import React, {
|
||||||
MouseEvent,
|
MouseEvent,
|
||||||
|
Suspense,
|
||||||
useCallback,
|
useCallback,
|
||||||
useEffect,
|
useEffect,
|
||||||
useMemo,
|
useMemo,
|
||||||
@@ -152,7 +153,10 @@ export const WorkspaceSettingDetail: React.FC<
|
|||||||
/>
|
/>
|
||||||
</StyledTabButtonWrapper>
|
</StyledTabButtonWrapper>
|
||||||
<StyledSettingContent>
|
<StyledSettingContent>
|
||||||
<Component {...props} key={currentTab} data-tab-ui={currentTab} />
|
{/* todo: add skeleton */}
|
||||||
|
<Suspense fallback="loading panel...">
|
||||||
|
<Component {...props} key={currentTab} data-tab-ui={currentTab} />
|
||||||
|
</Suspense>
|
||||||
</StyledSettingContent>
|
</StyledSettingContent>
|
||||||
</StyledSettingContainer>
|
</StyledSettingContainer>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import { Button, FlexWrapper, MuiFade } from '@affine/component';
|
import { Button, FlexWrapper, MuiFade } from '@affine/component';
|
||||||
import { useTranslation } from '@affine/i18n';
|
import { useTranslation } from '@affine/i18n';
|
||||||
import { assertExists } from '@blocksuite/store';
|
|
||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
|
|
||||||
import { useIsWorkspaceOwner } from '../../../../../hooks/affine/use-is-workspace-owner';
|
import { useIsWorkspaceOwner } from '../../../../../hooks/affine/use-is-workspace-owner';
|
||||||
|
import { useBlockSuiteWorkspaceBlobUrl } from '../../../../../hooks/use-blocksuite-workspace-blob-url';
|
||||||
import { useBlockSuiteWorkspaceName } from '../../../../../hooks/use-blocksuite-workspace-name';
|
import { useBlockSuiteWorkspaceName } from '../../../../../hooks/use-blocksuite-workspace-name';
|
||||||
import { RemWorkspaceFlavour } from '../../../../../shared';
|
import { RemWorkspaceFlavour } from '../../../../../shared';
|
||||||
import { Upload } from '../../../../pure/file-upload';
|
import { Upload } from '../../../../pure/file-upload';
|
||||||
@@ -43,14 +43,9 @@ export const GeneralPanel: React.FC<PanelProps> = ({
|
|||||||
setName(name);
|
setName(name);
|
||||||
};
|
};
|
||||||
|
|
||||||
const fileChange = async (file: File) => {
|
const [, update] = useBlockSuiteWorkspaceBlobUrl(
|
||||||
const blob = new Blob([file], { type: file.type });
|
workspace.blockSuiteWorkspace
|
||||||
const blobs = await workspace.blockSuiteWorkspace.blobs;
|
);
|
||||||
assertExists(blobs);
|
|
||||||
const blobId = await blobs.set(blob);
|
|
||||||
workspace.blockSuiteWorkspace.meta.setAvatar(blobId);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<StyledRow>
|
<StyledRow>
|
||||||
@@ -59,7 +54,7 @@ export const GeneralPanel: React.FC<PanelProps> = ({
|
|||||||
{isOwner ? (
|
{isOwner ? (
|
||||||
<Upload
|
<Upload
|
||||||
accept="image/gif,image/jpeg,image/jpg,image/png,image/svg"
|
accept="image/gif,image/jpeg,image/jpg,image/png,image/svg"
|
||||||
fileChange={fileChange}
|
fileChange={update}
|
||||||
data-testid="upload-avatar"
|
data-testid="upload-avatar"
|
||||||
>
|
>
|
||||||
<>
|
<>
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
import { UNTITLED_WORKSPACE_NAME } from '@affine/env';
|
import { UNTITLED_WORKSPACE_NAME } from '@affine/env';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
import { useBlockSuiteWorkspaceAvatar } from '../../../hooks/use-blocksuite-workspace-avatar';
|
import { useBlockSuiteWorkspaceBlobUrl } from '../../../hooks/use-blocksuite-workspace-blob-url';
|
||||||
import { useWorkspaceBlobImage } from '../../../hooks/use-workspace-blob';
|
|
||||||
import { BlockSuiteWorkspace, RemWorkspace } from '../../../shared';
|
import { BlockSuiteWorkspace, RemWorkspace } from '../../../shared';
|
||||||
import { stringToColour } from '../../../utils';
|
import { stringToColour } from '../../../utils';
|
||||||
|
|
||||||
@@ -88,14 +87,14 @@ export const BlockSuiteWorkspaceAvatar: React.FC<BlockSuiteWorkspaceAvatar> = ({
|
|||||||
style,
|
style,
|
||||||
...props
|
...props
|
||||||
}) => {
|
}) => {
|
||||||
const [avatar] = useBlockSuiteWorkspaceAvatar(workspace);
|
const [avatar] = useBlockSuiteWorkspaceBlobUrl(workspace);
|
||||||
const avatarURL = useWorkspaceBlobImage(avatar ?? null, workspace);
|
|
||||||
return (
|
return (
|
||||||
<Avatar
|
<Avatar
|
||||||
{...props}
|
{...props}
|
||||||
size={size}
|
size={size}
|
||||||
name={workspace.meta.name ?? UNTITLED_WORKSPACE_NAME}
|
name={workspace.meta.name ?? UNTITLED_WORKSPACE_NAME}
|
||||||
avatar_url={avatarURL ?? ''}
|
avatar_url={avatar ?? ''}
|
||||||
style={style}
|
style={style}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,32 +0,0 @@
|
|||||||
import { assertExists } from '@blocksuite/store';
|
|
||||||
import { useCallback, useEffect, useState } from 'react';
|
|
||||||
|
|
||||||
import { BlockSuiteWorkspace } from '../shared';
|
|
||||||
|
|
||||||
export function useBlockSuiteWorkspaceAvatar(
|
|
||||||
blockSuiteWorkspace: BlockSuiteWorkspace | null
|
|
||||||
) {
|
|
||||||
const [avatar, set] = useState<string | undefined>(
|
|
||||||
() => blockSuiteWorkspace?.meta.avatar
|
|
||||||
);
|
|
||||||
useEffect(() => {
|
|
||||||
if (blockSuiteWorkspace) {
|
|
||||||
set(blockSuiteWorkspace.meta.avatar);
|
|
||||||
const dispose = blockSuiteWorkspace.meta.commonFieldsUpdated.on(() => {
|
|
||||||
set(blockSuiteWorkspace.meta.avatar);
|
|
||||||
});
|
|
||||||
return () => {
|
|
||||||
dispose.dispose();
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}, [blockSuiteWorkspace]);
|
|
||||||
const setAvatar = useCallback(
|
|
||||||
(avatar: string) => {
|
|
||||||
assertExists(blockSuiteWorkspace);
|
|
||||||
blockSuiteWorkspace.meta.setAvatar(avatar);
|
|
||||||
set(avatar);
|
|
||||||
},
|
|
||||||
[blockSuiteWorkspace]
|
|
||||||
);
|
|
||||||
return [avatar, setAvatar] as const;
|
|
||||||
}
|
|
||||||
37
apps/web/src/hooks/use-blocksuite-workspace-blob-url.ts
Normal file
37
apps/web/src/hooks/use-blocksuite-workspace-blob-url.ts
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
import { assertExists } from '@blocksuite/store';
|
||||||
|
import { useCallback } from 'react';
|
||||||
|
import useSWR from 'swr';
|
||||||
|
|
||||||
|
import { QueryKey } from '../plugins/affine/fetcher';
|
||||||
|
import { BlockSuiteWorkspace } from '../shared';
|
||||||
|
|
||||||
|
export function useBlockSuiteWorkspaceBlobUrl(
|
||||||
|
// todo: remove `null` from type
|
||||||
|
blockSuiteWorkspace: BlockSuiteWorkspace | null
|
||||||
|
) {
|
||||||
|
const { data: avatar, mutate } = useSWR(
|
||||||
|
blockSuiteWorkspace
|
||||||
|
? [
|
||||||
|
QueryKey.getImage,
|
||||||
|
blockSuiteWorkspace.room,
|
||||||
|
blockSuiteWorkspace.meta.avatar,
|
||||||
|
]
|
||||||
|
: null,
|
||||||
|
{
|
||||||
|
fallbackData: null,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
const setAvatar = useCallback(
|
||||||
|
async (file: File) => {
|
||||||
|
assertExists(blockSuiteWorkspace);
|
||||||
|
const blob = new Blob([file], { type: file.type });
|
||||||
|
const blobs = await blockSuiteWorkspace.blobs;
|
||||||
|
assertExists(blobs);
|
||||||
|
const blobId = await blobs.set(blob);
|
||||||
|
blockSuiteWorkspace.meta.setAvatar(blobId);
|
||||||
|
await mutate(blobId);
|
||||||
|
},
|
||||||
|
[blockSuiteWorkspace, mutate]
|
||||||
|
);
|
||||||
|
return [avatar ?? null, setAvatar] as const;
|
||||||
|
}
|
||||||
@@ -1,35 +0,0 @@
|
|||||||
import { Theme } from '@affine/component';
|
|
||||||
import { useCallback, useSyncExternalStore } from 'react';
|
|
||||||
|
|
||||||
const themeRef = {
|
|
||||||
current: 'light',
|
|
||||||
media: null,
|
|
||||||
} as {
|
|
||||||
current: Theme;
|
|
||||||
media: MediaQueryList | null;
|
|
||||||
};
|
|
||||||
|
|
||||||
if (typeof window !== 'undefined') {
|
|
||||||
themeRef.media = window.matchMedia('(prefers-color-scheme: light)');
|
|
||||||
}
|
|
||||||
|
|
||||||
export function useSystemTheme() {
|
|
||||||
return useSyncExternalStore<Theme>(
|
|
||||||
useCallback(onStoreChange => {
|
|
||||||
if (themeRef.media) {
|
|
||||||
const media = themeRef.media;
|
|
||||||
media.addEventListener('change', onStoreChange);
|
|
||||||
return () => {
|
|
||||||
media.addEventListener('change', onStoreChange);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
return () => {};
|
|
||||||
}, []),
|
|
||||||
useCallback(
|
|
||||||
() =>
|
|
||||||
themeRef.media ? (themeRef.media.matches ? 'light' : 'dark') : 'light',
|
|
||||||
[]
|
|
||||||
),
|
|
||||||
useCallback(() => 'light', [])
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
import { useMemo } from 'react';
|
|
||||||
|
|
||||||
import { RemWorkspace } from '../shared';
|
|
||||||
import { useWorkspaces } from './use-workspaces';
|
|
||||||
|
|
||||||
export function useWorkspace(workspaceId: string | null): RemWorkspace | null {
|
|
||||||
const workspaces = useWorkspaces();
|
|
||||||
return useMemo(
|
|
||||||
() => workspaces.find(ws => ws.id === workspaceId) ?? null,
|
|
||||||
[workspaces, workspaceId]
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,3 +1,6 @@
|
|||||||
|
import { assertExists } from '@blocksuite/store';
|
||||||
|
|
||||||
|
import { jotaiStore, workspacesAtom } from '../../atoms';
|
||||||
import { createAffineProviders } from '../../blocksuite';
|
import { createAffineProviders } from '../../blocksuite';
|
||||||
import { Unreachable } from '../../components/affine/affine-error-eoundary';
|
import { Unreachable } from '../../components/affine/affine-error-eoundary';
|
||||||
import { AffineWorkspace, RemWorkspaceFlavour } from '../../shared';
|
import { AffineWorkspace, RemWorkspaceFlavour } from '../../shared';
|
||||||
@@ -34,6 +37,20 @@ export const fetcher = async (
|
|||||||
workspace_id: query[1],
|
workspace_id: query[1],
|
||||||
email: query[2],
|
email: query[2],
|
||||||
});
|
});
|
||||||
|
} else if (query[0] === QueryKey.getImage) {
|
||||||
|
const workspaceId = query[1];
|
||||||
|
const key = query[2];
|
||||||
|
if (typeof key !== 'string') {
|
||||||
|
throw new TypeError('key must be a string');
|
||||||
|
}
|
||||||
|
const workspaces = await jotaiStore.get(workspacesAtom);
|
||||||
|
const workspace = workspaces.find(({ id }) => id === workspaceId);
|
||||||
|
assertExists(workspace);
|
||||||
|
const storage = await workspace.blockSuiteWorkspace.blobs;
|
||||||
|
if (!storage) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return storage.get(key);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (query === QueryKey.getWorkspaces) {
|
if (query === QueryKey.getWorkspaces) {
|
||||||
@@ -60,6 +77,7 @@ export const fetcher = async (
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const QueryKey = {
|
export const QueryKey = {
|
||||||
|
getImage: 'getImage',
|
||||||
getUser: 'getUser',
|
getUser: 'getUser',
|
||||||
getWorkspaces: 'getWorkspaces',
|
getWorkspaces: 'getWorkspaces',
|
||||||
downloadWorkspace: 'downloadWorkspace',
|
downloadWorkspace: 'downloadWorkspace',
|
||||||
|
|||||||
Reference in New Issue
Block a user